Skip to content

Commit a42e1ae

Browse files
committed
Do not consider binding patterns in contextual types for return type inference where all the signature type parameters have defaults
1 parent 6a777ff commit a42e1ae

6 files changed

+271
-8
lines changed

src/compiler/checker.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22416,14 +22416,14 @@ namespace ts {
2241622416
// the contextual type of an initializer expression is the type implied by the binding pattern.
2241722417
// Otherwise, in a binding pattern inside a variable or parameter declaration,
2241822418
// the contextual type of an initializer expression is the type annotation of the containing declaration, if present.
22419-
function getContextualTypeForInitializerExpression(node: Expression): Type | undefined {
22419+
function getContextualTypeForInitializerExpression(node: Expression, contextFlags?: ContextFlags): Type | undefined {
2242022420
const declaration = <VariableLikeDeclaration>node.parent;
2242122421
if (hasInitializer(declaration) && node === declaration.initializer) {
2242222422
const result = getContextualTypeForVariableLikeDeclaration(declaration);
2242322423
if (result) {
2242422424
return result;
2242522425
}
22426-
if (isBindingPattern(declaration.name)) { // This is less a contextual type and more an implied shape - in some cases, this may be undesirable
22426+
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
2242722427
return getTypeFromBindingPattern(declaration.name, /*includePatternInType*/ true, /*reportErrors*/ false);
2242822428
}
2242922429
}
@@ -22450,8 +22450,8 @@ namespace ts {
2245022450
return undefined;
2245122451
}
2245222452

22453-
function getContextualTypeForAwaitOperand(node: AwaitExpression): Type | undefined {
22454-
const contextualType = getContextualType(node);
22453+
function getContextualTypeForAwaitOperand(node: AwaitExpression, contextFlags?: ContextFlags): Type | undefined {
22454+
const contextualType = getContextualType(node, contextFlags);
2245522455
if (contextualType) {
2245622456
const contextualAwaitedType = getAwaitedType(contextualType);
2245722457
return contextualAwaitedType && getUnionType([contextualAwaitedType, createPromiseLikeType(contextualAwaitedType)]);
@@ -22916,14 +22916,14 @@ namespace ts {
2291622916
case SyntaxKind.PropertyDeclaration:
2291722917
case SyntaxKind.PropertySignature:
2291822918
case SyntaxKind.BindingElement:
22919-
return getContextualTypeForInitializerExpression(node);
22919+
return getContextualTypeForInitializerExpression(node, contextFlags);
2292022920
case SyntaxKind.ArrowFunction:
2292122921
case SyntaxKind.ReturnStatement:
2292222922
return getContextualTypeForReturnExpression(node);
2292322923
case SyntaxKind.YieldExpression:
2292422924
return getContextualTypeForYieldOperand(<YieldExpression>parent);
2292522925
case SyntaxKind.AwaitExpression:
22926-
return getContextualTypeForAwaitOperand(<AwaitExpression>parent);
22926+
return getContextualTypeForAwaitOperand(<AwaitExpression>parent, contextFlags);
2292722927
case SyntaxKind.CallExpression:
2292822928
if ((<CallExpression>parent).expression.kind === SyntaxKind.ImportKeyword) {
2292922929
return stringType;
@@ -25271,7 +25271,7 @@ namespace ts {
2527125271
// 'let f: (x: string) => number = wrap(s => s.length)', we infer from the declared type of 'f' to the
2527225272
// return type of 'wrap'.
2527325273
if (node.kind !== SyntaxKind.Decorator) {
25274-
const contextualType = getContextualType(node);
25274+
const contextualType = getContextualType(node, every(signature.typeParameters, p => !!getDefaultFromTypeParameter(p)) ? ContextFlags.SkipBindingPatterns : ContextFlags.None);
2527525275
if (contextualType) {
2527625276
// We clone the inference context to avoid disturbing a resolution in progress for an
2527725277
// outer call expression. Effectively we just want a snapshot of whatever has been

src/compiler/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3676,7 +3676,7 @@ namespace ts {
36763676
Signature = 1 << 0, // Obtaining contextual signature
36773677
NoConstraints = 1 << 1, // Don't obtain type variable constraints
36783678
Completions = 1 << 2, // Ignore inference to current node and parent nodes out to the containing call for completions
3679-
3679+
SkipBindingPatterns = 1 << 3, // Ignore contextual types applied by binding patterns
36803680
}
36813681

36823682
// NOTE: If modifying this enum, must modify `TypeFormatFlags` too!
Lines changed: 97 additions & 0 deletions
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+
}
Lines changed: 69 additions & 0 deletions
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+
}
Lines changed: 71 additions & 0 deletions
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+
}
Lines changed: 26 additions & 0 deletions
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)