Skip to content

Commit d1ebf12

Browse files
authored
Do not consider binding patterns in contextual types for return type inference where all the signature type parameters have defaults (microsoft#39081)
1 parent 785e447 commit d1ebf12

6 files changed

+271
-8
lines changed

src/compiler/checker.ts

+7-7
Original file line numberDiff line numberDiff line change
@@ -22855,14 +22855,14 @@ namespace ts {
2285522855
// the contextual type of an initializer expression is the type implied by the binding pattern.
2285622856
// Otherwise, in a binding pattern inside a variable or parameter declaration,
2285722857
// the contextual type of an initializer expression is the type annotation of the containing declaration, if present.
22858-
function getContextualTypeForInitializerExpression(node: Expression): Type | undefined {
22858+
function getContextualTypeForInitializerExpression(node: Expression, contextFlags?: ContextFlags): Type | undefined {
2285922859
const declaration = <VariableLikeDeclaration>node.parent;
2286022860
if (hasInitializer(declaration) && node === declaration.initializer) {
2286122861
const result = getContextualTypeForVariableLikeDeclaration(declaration);
2286222862
if (result) {
2286322863
return result;
2286422864
}
22865-
if (isBindingPattern(declaration.name)) { // This is less a contextual type and more an implied shape - in some cases, this may be undesirable
22865+
if (!(contextFlags! & ContextFlags.SkipBindingPatterns) && isBindingPattern(declaration.name)) { // This is less a contextual type and more an implied shape - in some cases, this may be undesirable
2286622866
return getTypeFromBindingPattern(declaration.name, /*includePatternInType*/ true, /*reportErrors*/ false);
2286722867
}
2286822868
}
@@ -22889,8 +22889,8 @@ namespace ts {
2288922889
return undefined;
2289022890
}
2289122891

22892-
function getContextualTypeForAwaitOperand(node: AwaitExpression): Type | undefined {
22893-
const contextualType = getContextualType(node);
22892+
function getContextualTypeForAwaitOperand(node: AwaitExpression, contextFlags?: ContextFlags): Type | undefined {
22893+
const contextualType = getContextualType(node, contextFlags);
2289422894
if (contextualType) {
2289522895
const contextualAwaitedType = getAwaitedType(contextualType);
2289622896
return contextualAwaitedType && getUnionType([contextualAwaitedType, createPromiseLikeType(contextualAwaitedType)]);
@@ -23355,14 +23355,14 @@ namespace ts {
2335523355
case SyntaxKind.PropertyDeclaration:
2335623356
case SyntaxKind.PropertySignature:
2335723357
case SyntaxKind.BindingElement:
23358-
return getContextualTypeForInitializerExpression(node);
23358+
return getContextualTypeForInitializerExpression(node, contextFlags);
2335923359
case SyntaxKind.ArrowFunction:
2336023360
case SyntaxKind.ReturnStatement:
2336123361
return getContextualTypeForReturnExpression(node);
2336223362
case SyntaxKind.YieldExpression:
2336323363
return getContextualTypeForYieldOperand(<YieldExpression>parent);
2336423364
case SyntaxKind.AwaitExpression:
23365-
return getContextualTypeForAwaitOperand(<AwaitExpression>parent);
23365+
return getContextualTypeForAwaitOperand(<AwaitExpression>parent, contextFlags);
2336623366
case SyntaxKind.CallExpression:
2336723367
if ((<CallExpression>parent).expression.kind === SyntaxKind.ImportKeyword) {
2336823368
return stringType;
@@ -25700,7 +25700,7 @@ namespace ts {
2570025700
// 'let f: (x: string) => number = wrap(s => s.length)', we infer from the declared type of 'f' to the
2570125701
// return type of 'wrap'.
2570225702
if (node.kind !== SyntaxKind.Decorator) {
25703-
const contextualType = getContextualType(node);
25703+
const contextualType = getContextualType(node, every(signature.typeParameters, p => !!getDefaultFromTypeParameter(p)) ? ContextFlags.SkipBindingPatterns : ContextFlags.None);
2570425704
if (contextualType) {
2570525705
// We clone the inference context to avoid disturbing a resolution in progress for an
2570625706
// outer call expression. Effectively we just want a snapshot of whatever has been

src/compiler/types.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -4136,7 +4136,7 @@ namespace ts {
41364136
Signature = 1 << 0, // Obtaining contextual signature
41374137
NoConstraints = 1 << 1, // Don't obtain type variable constraints
41384138
Completions = 1 << 2, // Ignore inference to current node and parent nodes out to the containing call for completions
4139-
4139+
SkipBindingPatterns = 1 << 3, // Ignore contextual types applied by binding patterns
41404140
}
41414141

41424142
// NOTE: If modifying this enum, must modify `TypeFormatFlags` too!
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
//// [destructureOfVariableSameAsShorthand.ts]
2+
// https://github.com/microsoft/TypeScript/issues/38969
3+
interface AxiosResponse<T = never> {
4+
data: T;
5+
}
6+
7+
declare function get<T = never, R = AxiosResponse<T>>(): Promise<R>;
8+
9+
async function main() {
10+
// These work examples as expected
11+
get().then((response) => {
12+
// body is never
13+
const body = response.data;
14+
})
15+
get().then(({ data }) => {
16+
// data is never
17+
})
18+
const response = await get()
19+
// body is never
20+
const body = response.data;
21+
// data is never
22+
const { data } = await get<never>();
23+
24+
// The following did not work as expected.
25+
// shouldBeNever should be never, but was any
26+
const { data: shouldBeNever } = await get();
27+
}
28+
29+
//// [destructureOfVariableSameAsShorthand.js]
30+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
31+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
32+
return new (P || (P = Promise))(function (resolve, reject) {
33+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
34+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
35+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
36+
step((generator = generator.apply(thisArg, _arguments || [])).next());
37+
});
38+
};
39+
var __generator = (this && this.__generator) || function (thisArg, body) {
40+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
41+
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
42+
function verb(n) { return function (v) { return step([n, v]); }; }
43+
function step(op) {
44+
if (f) throw new TypeError("Generator is already executing.");
45+
while (_) try {
46+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
47+
if (y = 0, t) op = [op[0] & 2, t.value];
48+
switch (op[0]) {
49+
case 0: case 1: t = op; break;
50+
case 4: _.label++; return { value: op[1], done: false };
51+
case 5: _.label++; y = op[1]; op = [0]; continue;
52+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
53+
default:
54+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
55+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
56+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
57+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
58+
if (t[2]) _.ops.pop();
59+
_.trys.pop(); continue;
60+
}
61+
op = body.call(thisArg, _);
62+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
63+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
64+
}
65+
};
66+
function main() {
67+
return __awaiter(this, void 0, void 0, function () {
68+
var response, body, data, shouldBeNever;
69+
return __generator(this, function (_a) {
70+
switch (_a.label) {
71+
case 0:
72+
// These work examples as expected
73+
get().then(function (response) {
74+
// body is never
75+
var body = response.data;
76+
});
77+
get().then(function (_a) {
78+
var data = _a.data;
79+
// data is never
80+
});
81+
return [4 /*yield*/, get()
82+
// body is never
83+
];
84+
case 1:
85+
response = _a.sent();
86+
body = response.data;
87+
return [4 /*yield*/, get()];
88+
case 2:
89+
data = (_a.sent()).data;
90+
return [4 /*yield*/, get()];
91+
case 3:
92+
shouldBeNever = (_a.sent()).data;
93+
return [2 /*return*/];
94+
}
95+
});
96+
});
97+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
=== tests/cases/compiler/destructureOfVariableSameAsShorthand.ts ===
2+
// https://github.com/microsoft/TypeScript/issues/38969
3+
interface AxiosResponse<T = never> {
4+
>AxiosResponse : Symbol(AxiosResponse, Decl(destructureOfVariableSameAsShorthand.ts, 0, 0))
5+
>T : Symbol(T, Decl(destructureOfVariableSameAsShorthand.ts, 1, 24))
6+
7+
data: T;
8+
>data : Symbol(AxiosResponse.data, Decl(destructureOfVariableSameAsShorthand.ts, 1, 36))
9+
>T : Symbol(T, Decl(destructureOfVariableSameAsShorthand.ts, 1, 24))
10+
}
11+
12+
declare function get<T = never, R = AxiosResponse<T>>(): Promise<R>;
13+
>get : Symbol(get, Decl(destructureOfVariableSameAsShorthand.ts, 3, 1))
14+
>T : Symbol(T, Decl(destructureOfVariableSameAsShorthand.ts, 5, 21))
15+
>R : Symbol(R, Decl(destructureOfVariableSameAsShorthand.ts, 5, 31))
16+
>AxiosResponse : Symbol(AxiosResponse, Decl(destructureOfVariableSameAsShorthand.ts, 0, 0))
17+
>T : Symbol(T, Decl(destructureOfVariableSameAsShorthand.ts, 5, 21))
18+
>Promise : Symbol(Promise, Decl(lib.es5.d.ts, --, --))
19+
>R : Symbol(R, Decl(destructureOfVariableSameAsShorthand.ts, 5, 31))
20+
21+
async function main() {
22+
>main : Symbol(main, Decl(destructureOfVariableSameAsShorthand.ts, 5, 68))
23+
24+
// These work examples as expected
25+
get().then((response) => {
26+
>get().then : Symbol(Promise.then, Decl(lib.es5.d.ts, --, --))
27+
>get : Symbol(get, Decl(destructureOfVariableSameAsShorthand.ts, 3, 1))
28+
>then : Symbol(Promise.then, Decl(lib.es5.d.ts, --, --))
29+
>response : Symbol(response, Decl(destructureOfVariableSameAsShorthand.ts, 9, 16))
30+
31+
// body is never
32+
const body = response.data;
33+
>body : Symbol(body, Decl(destructureOfVariableSameAsShorthand.ts, 11, 13))
34+
>response.data : Symbol(AxiosResponse.data, Decl(destructureOfVariableSameAsShorthand.ts, 1, 36))
35+
>response : Symbol(response, Decl(destructureOfVariableSameAsShorthand.ts, 9, 16))
36+
>data : Symbol(AxiosResponse.data, Decl(destructureOfVariableSameAsShorthand.ts, 1, 36))
37+
38+
})
39+
get().then(({ data }) => {
40+
>get().then : Symbol(Promise.then, Decl(lib.es5.d.ts, --, --))
41+
>get : Symbol(get, Decl(destructureOfVariableSameAsShorthand.ts, 3, 1))
42+
>then : Symbol(Promise.then, Decl(lib.es5.d.ts, --, --))
43+
>data : Symbol(data, Decl(destructureOfVariableSameAsShorthand.ts, 13, 17))
44+
45+
// data is never
46+
})
47+
const response = await get()
48+
>response : Symbol(response, Decl(destructureOfVariableSameAsShorthand.ts, 16, 9))
49+
>get : Symbol(get, Decl(destructureOfVariableSameAsShorthand.ts, 3, 1))
50+
51+
// body is never
52+
const body = response.data;
53+
>body : Symbol(body, Decl(destructureOfVariableSameAsShorthand.ts, 18, 9))
54+
>response.data : Symbol(AxiosResponse.data, Decl(destructureOfVariableSameAsShorthand.ts, 1, 36))
55+
>response : Symbol(response, Decl(destructureOfVariableSameAsShorthand.ts, 16, 9))
56+
>data : Symbol(AxiosResponse.data, Decl(destructureOfVariableSameAsShorthand.ts, 1, 36))
57+
58+
// data is never
59+
const { data } = await get<never>();
60+
>data : Symbol(data, Decl(destructureOfVariableSameAsShorthand.ts, 20, 11))
61+
>get : Symbol(get, Decl(destructureOfVariableSameAsShorthand.ts, 3, 1))
62+
63+
// The following did not work as expected.
64+
// shouldBeNever should be never, but was any
65+
const { data: shouldBeNever } = await get();
66+
>data : Symbol(AxiosResponse.data, Decl(destructureOfVariableSameAsShorthand.ts, 1, 36))
67+
>shouldBeNever : Symbol(shouldBeNever, Decl(destructureOfVariableSameAsShorthand.ts, 24, 11))
68+
>get : Symbol(get, Decl(destructureOfVariableSameAsShorthand.ts, 3, 1))
69+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
=== tests/cases/compiler/destructureOfVariableSameAsShorthand.ts ===
2+
// https://github.com/microsoft/TypeScript/issues/38969
3+
interface AxiosResponse<T = never> {
4+
data: T;
5+
>data : T
6+
}
7+
8+
declare function get<T = never, R = AxiosResponse<T>>(): Promise<R>;
9+
>get : <T = never, R = AxiosResponse<T>>() => Promise<R>
10+
11+
async function main() {
12+
>main : () => Promise<void>
13+
14+
// These work examples as expected
15+
get().then((response) => {
16+
>get().then((response) => { // body is never const body = response.data; }) : Promise<void>
17+
>get().then : <TResult1 = AxiosResponse<never>, TResult2 = never>(onfulfilled?: (value: AxiosResponse<never>) => TResult1 | PromiseLike<TResult1>, onrejected?: (reason: any) => TResult2 | PromiseLike<TResult2>) => Promise<TResult1 | TResult2>
18+
>get() : Promise<AxiosResponse<never>>
19+
>get : <T = never, R = AxiosResponse<T>>() => Promise<R>
20+
>then : <TResult1 = AxiosResponse<never>, TResult2 = never>(onfulfilled?: (value: AxiosResponse<never>) => TResult1 | PromiseLike<TResult1>, onrejected?: (reason: any) => TResult2 | PromiseLike<TResult2>) => Promise<TResult1 | TResult2>
21+
>(response) => { // body is never const body = response.data; } : (response: AxiosResponse<never>) => void
22+
>response : AxiosResponse<never>
23+
24+
// body is never
25+
const body = response.data;
26+
>body : never
27+
>response.data : never
28+
>response : AxiosResponse<never>
29+
>data : never
30+
31+
})
32+
get().then(({ data }) => {
33+
>get().then(({ data }) => { // data is never }) : Promise<void>
34+
>get().then : <TResult1 = AxiosResponse<never>, TResult2 = never>(onfulfilled?: (value: AxiosResponse<never>) => TResult1 | PromiseLike<TResult1>, onrejected?: (reason: any) => TResult2 | PromiseLike<TResult2>) => Promise<TResult1 | TResult2>
35+
>get() : Promise<AxiosResponse<never>>
36+
>get : <T = never, R = AxiosResponse<T>>() => Promise<R>
37+
>then : <TResult1 = AxiosResponse<never>, TResult2 = never>(onfulfilled?: (value: AxiosResponse<never>) => TResult1 | PromiseLike<TResult1>, onrejected?: (reason: any) => TResult2 | PromiseLike<TResult2>) => Promise<TResult1 | TResult2>
38+
>({ data }) => { // data is never } : ({ data }: AxiosResponse<never>) => void
39+
>data : never
40+
41+
// data is never
42+
})
43+
const response = await get()
44+
>response : AxiosResponse<never>
45+
>await get() : AxiosResponse<never>
46+
>get() : Promise<AxiosResponse<never>>
47+
>get : <T = never, R = AxiosResponse<T>>() => Promise<R>
48+
49+
// body is never
50+
const body = response.data;
51+
>body : never
52+
>response.data : never
53+
>response : AxiosResponse<never>
54+
>data : never
55+
56+
// data is never
57+
const { data } = await get<never>();
58+
>data : never
59+
>await get<never>() : AxiosResponse<never>
60+
>get<never>() : Promise<AxiosResponse<never>>
61+
>get : <T = never, R = AxiosResponse<T>>() => Promise<R>
62+
63+
// The following did not work as expected.
64+
// shouldBeNever should be never, but was any
65+
const { data: shouldBeNever } = await get();
66+
>data : any
67+
>shouldBeNever : never
68+
>await get() : AxiosResponse<never>
69+
>get() : Promise<AxiosResponse<never>>
70+
>get : <T = never, R = AxiosResponse<T>>() => Promise<R>
71+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// https://github.com/microsoft/TypeScript/issues/38969
2+
interface AxiosResponse<T = never> {
3+
data: T;
4+
}
5+
6+
declare function get<T = never, R = AxiosResponse<T>>(): Promise<R>;
7+
8+
async function main() {
9+
// These work examples as expected
10+
get().then((response) => {
11+
// body is never
12+
const body = response.data;
13+
})
14+
get().then(({ data }) => {
15+
// data is never
16+
})
17+
const response = await get()
18+
// body is never
19+
const body = response.data;
20+
// data is never
21+
const { data } = await get<never>();
22+
23+
// The following did not work as expected.
24+
// shouldBeNever should be never, but was any
25+
const { data: shouldBeNever } = await get();
26+
}

0 commit comments

Comments
 (0)