Skip to content

Commit eb513a2

Browse files
authored
Merge pull request #29576 from Microsoft/fixContextuallyTypedParameters
Fix inference for contextually typed parameters with initializers
2 parents 0ae73a5 + 25ac1ed commit eb513a2

9 files changed

+489
-30
lines changed

src/compiler/checker.ts

+22-19
Original file line numberDiff line numberDiff line change
@@ -4866,17 +4866,8 @@ namespace ts {
48664866
function getTypeForBindingElement(declaration: BindingElement): Type | undefined {
48674867
const pattern = declaration.parent;
48684868
let parentType = getTypeForBindingElementParent(pattern.parent);
4869-
// If parent has the unknown (error) type, then so does this binding element
4870-
if (parentType === errorType) {
4871-
return errorType;
4872-
}
4873-
// If no type was specified or inferred for parent,
4874-
// infer from the initializer of the binding element if one is present.
4875-
// Otherwise, go with the undefined type of the parent.
4876-
if (!parentType) {
4877-
return declaration.initializer ? checkDeclarationInitializer(declaration) : parentType;
4878-
}
4879-
if (isTypeAny(parentType)) {
4869+
// If no type or an any type was inferred for parent, infer that for the binding element
4870+
if (!parentType || isTypeAny(parentType)) {
48804871
return parentType;
48814872
}
48824873
// Relax null check on ambient destructuring parameters, since the parameters have no implementation and are just documentation
@@ -4962,6 +4953,12 @@ namespace ts {
49624953
return strictNullChecks && optional ? getOptionalType(type) : type;
49634954
}
49644955

4956+
function isParameterOfContextuallyTypedFunction(node: Declaration) {
4957+
return node.kind === SyntaxKind.Parameter &&
4958+
(node.parent.kind === SyntaxKind.FunctionExpression || node.parent.kind === SyntaxKind.ArrowFunction) &&
4959+
!!getContextualType(<Expression>node.parent);
4960+
}
4961+
49654962
// Return the inferred type for a variable, parameter, or property declaration
49664963
function getTypeForVariableLikeDeclaration(declaration: ParameterDeclaration | PropertyDeclaration | PropertySignature | VariableDeclaration | BindingElement, includeOptionality: boolean): Type | undefined {
49674964
// A variable declared in a for..in statement is of type string, or of type keyof T when the
@@ -5045,8 +5042,9 @@ namespace ts {
50455042
}
50465043
}
50475044

5048-
// Use the type of the initializer expression if one is present
5049-
if (declaration.initializer) {
5045+
// Use the type of the initializer expression if one is present and the declaration is
5046+
// not a parameter of a contextually typed function
5047+
if (declaration.initializer && !isParameterOfContextuallyTypedFunction(declaration)) {
50505048
const type = checkDeclarationInitializer(declaration);
50515049
return addOptionality(type, isOptional);
50525050
}
@@ -5057,8 +5055,9 @@ namespace ts {
50575055
return trueType;
50585056
}
50595057

5060-
// If the declaration specifies a binding pattern, use the type implied by the binding pattern
5061-
if (isBindingPattern(declaration.name)) {
5058+
// If the declaration specifies a binding pattern and is not a parameter of a contextually
5059+
// typed function, use the type implied by the binding pattern
5060+
if (isBindingPattern(declaration.name) && !isParameterOfContextuallyTypedFunction(declaration)) {
50625061
return getTypeFromBindingPattern(declaration.name, /*includePatternInType*/ false, /*reportErrors*/ true);
50635062
}
50645063

@@ -5695,17 +5694,21 @@ namespace ts {
56955694
}
56965695

56975696
function reportCircularityError(symbol: Symbol) {
5697+
const declaration = <VariableLikeDeclaration>symbol.valueDeclaration;
56985698
// Check if variable has type annotation that circularly references the variable itself
5699-
if (getEffectiveTypeAnnotationNode(<VariableLikeDeclaration>symbol.valueDeclaration)) {
5699+
if (getEffectiveTypeAnnotationNode(declaration)) {
57005700
error(symbol.valueDeclaration, Diagnostics._0_is_referenced_directly_or_indirectly_in_its_own_type_annotation,
57015701
symbolToString(symbol));
57025702
return errorType;
57035703
}
5704-
// Otherwise variable has initializer that circularly references the variable itself
5705-
if (noImplicitAny) {
5704+
// Check if variable has initializer that circularly references the variable itself
5705+
if (noImplicitAny && (declaration.kind !== SyntaxKind.Parameter || (<HasInitializer>declaration).initializer)) {
57065706
error(symbol.valueDeclaration, Diagnostics._0_implicitly_has_type_any_because_it_does_not_have_a_type_annotation_and_is_referenced_directly_or_indirectly_in_its_own_initializer,
57075707
symbolToString(symbol));
57085708
}
5709+
// Circularities could also result from parameters in function expressions that end up
5710+
// having themselves as contextual types following type argument inference. In those cases
5711+
// we have already reported an implicit any error so we don't report anything here.
57095712
return anyType;
57105713
}
57115714

@@ -25680,7 +25683,7 @@ namespace ts {
2568025683
const parent = node.parent.parent;
2568125684
const parentType = getTypeForBindingElementParent(parent);
2568225685
const name = node.propertyName || node.name;
25683-
if (!isBindingPattern(name) && parentType) {
25686+
if (parentType && !isBindingPattern(name)) {
2568425687
const exprType = getLiteralTypeFromPropertyName(name);
2568525688
if (isTypeUsableAsPropertyName(exprType)) {
2568625689
const nameText = getPropertyNameFromType(exprType);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
tests/cases/compiler/contextuallyTypedParametersWithInitializers.ts(8,29): error TS7031: Binding element 'foo' implicitly has an 'any' type.
2+
tests/cases/compiler/contextuallyTypedParametersWithInitializers.ts(14,27): error TS7006: Parameter 'foo' implicitly has an 'any' type.
3+
tests/cases/compiler/contextuallyTypedParametersWithInitializers.ts(27,42): error TS7031: Binding element 'foo' implicitly has an 'any' type.
4+
5+
6+
==== tests/cases/compiler/contextuallyTypedParametersWithInitializers.ts (3 errors) ====
7+
declare function id1<T>(input: T): T;
8+
declare function id2<T extends (x: any) => any>(input: T): T;
9+
declare function id3<T extends (x: { foo: any }) => any>(input: T): T;
10+
declare function id4<T extends (x: { foo?: number }) => any>(input: T): T;
11+
declare function id5<T extends (x?: number) => any>(input: T): T;
12+
13+
const f10 = function ({ foo = 42 }) { return foo };
14+
const f11 = id1(function ({ foo = 42 }) { return foo }); // Implicit any error
15+
~~~
16+
!!! error TS7031: Binding element 'foo' implicitly has an 'any' type.
17+
const f12 = id2(function ({ foo = 42 }) { return foo });
18+
const f13 = id3(function ({ foo = 42 }) { return foo });
19+
const f14 = id4(function ({ foo = 42 }) { return foo });
20+
21+
const f20 = function (foo = 42) { return foo };
22+
const f21 = id1(function (foo = 42) { return foo }); // Implicit any error
23+
~~~~~~~~
24+
!!! error TS7006: Parameter 'foo' implicitly has an 'any' type.
25+
const f22 = id2(function (foo = 42) { return foo });
26+
const f25 = id5(function (foo = 42) { return foo });
27+
28+
// Repro from #28816
29+
30+
function id<T>(input: T): T { return input }
31+
32+
function getFoo ({ foo = 42 }) {
33+
return foo;
34+
}
35+
36+
const newGetFoo = id(getFoo);
37+
const newGetFoo2 = id(function getFoo ({ foo = 42 }) {
38+
~~~
39+
!!! error TS7031: Binding element 'foo' implicitly has an 'any' type.
40+
return foo;
41+
});
42+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
//// [contextuallyTypedParametersWithInitializers.ts]
2+
declare function id1<T>(input: T): T;
3+
declare function id2<T extends (x: any) => any>(input: T): T;
4+
declare function id3<T extends (x: { foo: any }) => any>(input: T): T;
5+
declare function id4<T extends (x: { foo?: number }) => any>(input: T): T;
6+
declare function id5<T extends (x?: number) => any>(input: T): T;
7+
8+
const f10 = function ({ foo = 42 }) { return foo };
9+
const f11 = id1(function ({ foo = 42 }) { return foo }); // Implicit any error
10+
const f12 = id2(function ({ foo = 42 }) { return foo });
11+
const f13 = id3(function ({ foo = 42 }) { return foo });
12+
const f14 = id4(function ({ foo = 42 }) { return foo });
13+
14+
const f20 = function (foo = 42) { return foo };
15+
const f21 = id1(function (foo = 42) { return foo }); // Implicit any error
16+
const f22 = id2(function (foo = 42) { return foo });
17+
const f25 = id5(function (foo = 42) { return foo });
18+
19+
// Repro from #28816
20+
21+
function id<T>(input: T): T { return input }
22+
23+
function getFoo ({ foo = 42 }) {
24+
return foo;
25+
}
26+
27+
const newGetFoo = id(getFoo);
28+
const newGetFoo2 = id(function getFoo ({ foo = 42 }) {
29+
return foo;
30+
});
31+
32+
33+
//// [contextuallyTypedParametersWithInitializers.js]
34+
"use strict";
35+
var f10 = function (_a) {
36+
var _b = _a.foo, foo = _b === void 0 ? 42 : _b;
37+
return foo;
38+
};
39+
var f11 = id1(function (_a) {
40+
var _b = _a.foo, foo = _b === void 0 ? 42 : _b;
41+
return foo;
42+
}); // Implicit any error
43+
var f12 = id2(function (_a) {
44+
var _b = _a.foo, foo = _b === void 0 ? 42 : _b;
45+
return foo;
46+
});
47+
var f13 = id3(function (_a) {
48+
var _b = _a.foo, foo = _b === void 0 ? 42 : _b;
49+
return foo;
50+
});
51+
var f14 = id4(function (_a) {
52+
var _b = _a.foo, foo = _b === void 0 ? 42 : _b;
53+
return foo;
54+
});
55+
var f20 = function (foo) {
56+
if (foo === void 0) { foo = 42; }
57+
return foo;
58+
};
59+
var f21 = id1(function (foo) {
60+
if (foo === void 0) { foo = 42; }
61+
return foo;
62+
}); // Implicit any error
63+
var f22 = id2(function (foo) {
64+
if (foo === void 0) { foo = 42; }
65+
return foo;
66+
});
67+
var f25 = id5(function (foo) {
68+
if (foo === void 0) { foo = 42; }
69+
return foo;
70+
});
71+
// Repro from #28816
72+
function id(input) { return input; }
73+
function getFoo(_a) {
74+
var _b = _a.foo, foo = _b === void 0 ? 42 : _b;
75+
return foo;
76+
}
77+
var newGetFoo = id(getFoo);
78+
var newGetFoo2 = id(function getFoo(_a) {
79+
var _b = _a.foo, foo = _b === void 0 ? 42 : _b;
80+
return foo;
81+
});
82+
83+
84+
//// [contextuallyTypedParametersWithInitializers.d.ts]
85+
declare function id1<T>(input: T): T;
86+
declare function id2<T extends (x: any) => any>(input: T): T;
87+
declare function id3<T extends (x: {
88+
foo: any;
89+
}) => any>(input: T): T;
90+
declare function id4<T extends (x: {
91+
foo?: number;
92+
}) => any>(input: T): T;
93+
declare function id5<T extends (x?: number) => any>(input: T): T;
94+
declare const f10: ({ foo }: {
95+
foo?: number | undefined;
96+
}) => number;
97+
declare const f11: ({ foo }: any) => any;
98+
declare const f12: ({ foo }: any) => any;
99+
declare const f13: ({ foo }: {
100+
foo: any;
101+
}) => any;
102+
declare const f14: ({ foo }: {
103+
foo?: number | undefined;
104+
}) => number;
105+
declare const f20: (foo?: number) => number;
106+
declare const f21: (foo?: any) => any;
107+
declare const f22: (foo?: any) => any;
108+
declare const f25: (foo?: number | undefined) => number;
109+
declare function id<T>(input: T): T;
110+
declare function getFoo({ foo }: {
111+
foo?: number | undefined;
112+
}): number;
113+
declare const newGetFoo: typeof getFoo;
114+
declare const newGetFoo2: ({ foo }: any) => any;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
=== tests/cases/compiler/contextuallyTypedParametersWithInitializers.ts ===
2+
declare function id1<T>(input: T): T;
3+
>id1 : Symbol(id1, Decl(contextuallyTypedParametersWithInitializers.ts, 0, 0))
4+
>T : Symbol(T, Decl(contextuallyTypedParametersWithInitializers.ts, 0, 21))
5+
>input : Symbol(input, Decl(contextuallyTypedParametersWithInitializers.ts, 0, 24))
6+
>T : Symbol(T, Decl(contextuallyTypedParametersWithInitializers.ts, 0, 21))
7+
>T : Symbol(T, Decl(contextuallyTypedParametersWithInitializers.ts, 0, 21))
8+
9+
declare function id2<T extends (x: any) => any>(input: T): T;
10+
>id2 : Symbol(id2, Decl(contextuallyTypedParametersWithInitializers.ts, 0, 37))
11+
>T : Symbol(T, Decl(contextuallyTypedParametersWithInitializers.ts, 1, 21))
12+
>x : Symbol(x, Decl(contextuallyTypedParametersWithInitializers.ts, 1, 32))
13+
>input : Symbol(input, Decl(contextuallyTypedParametersWithInitializers.ts, 1, 48))
14+
>T : Symbol(T, Decl(contextuallyTypedParametersWithInitializers.ts, 1, 21))
15+
>T : Symbol(T, Decl(contextuallyTypedParametersWithInitializers.ts, 1, 21))
16+
17+
declare function id3<T extends (x: { foo: any }) => any>(input: T): T;
18+
>id3 : Symbol(id3, Decl(contextuallyTypedParametersWithInitializers.ts, 1, 61))
19+
>T : Symbol(T, Decl(contextuallyTypedParametersWithInitializers.ts, 2, 21))
20+
>x : Symbol(x, Decl(contextuallyTypedParametersWithInitializers.ts, 2, 32))
21+
>foo : Symbol(foo, Decl(contextuallyTypedParametersWithInitializers.ts, 2, 36))
22+
>input : Symbol(input, Decl(contextuallyTypedParametersWithInitializers.ts, 2, 57))
23+
>T : Symbol(T, Decl(contextuallyTypedParametersWithInitializers.ts, 2, 21))
24+
>T : Symbol(T, Decl(contextuallyTypedParametersWithInitializers.ts, 2, 21))
25+
26+
declare function id4<T extends (x: { foo?: number }) => any>(input: T): T;
27+
>id4 : Symbol(id4, Decl(contextuallyTypedParametersWithInitializers.ts, 2, 70))
28+
>T : Symbol(T, Decl(contextuallyTypedParametersWithInitializers.ts, 3, 21))
29+
>x : Symbol(x, Decl(contextuallyTypedParametersWithInitializers.ts, 3, 32))
30+
>foo : Symbol(foo, Decl(contextuallyTypedParametersWithInitializers.ts, 3, 36))
31+
>input : Symbol(input, Decl(contextuallyTypedParametersWithInitializers.ts, 3, 61))
32+
>T : Symbol(T, Decl(contextuallyTypedParametersWithInitializers.ts, 3, 21))
33+
>T : Symbol(T, Decl(contextuallyTypedParametersWithInitializers.ts, 3, 21))
34+
35+
declare function id5<T extends (x?: number) => any>(input: T): T;
36+
>id5 : Symbol(id5, Decl(contextuallyTypedParametersWithInitializers.ts, 3, 74))
37+
>T : Symbol(T, Decl(contextuallyTypedParametersWithInitializers.ts, 4, 21))
38+
>x : Symbol(x, Decl(contextuallyTypedParametersWithInitializers.ts, 4, 32))
39+
>input : Symbol(input, Decl(contextuallyTypedParametersWithInitializers.ts, 4, 52))
40+
>T : Symbol(T, Decl(contextuallyTypedParametersWithInitializers.ts, 4, 21))
41+
>T : Symbol(T, Decl(contextuallyTypedParametersWithInitializers.ts, 4, 21))
42+
43+
const f10 = function ({ foo = 42 }) { return foo };
44+
>f10 : Symbol(f10, Decl(contextuallyTypedParametersWithInitializers.ts, 6, 5))
45+
>foo : Symbol(foo, Decl(contextuallyTypedParametersWithInitializers.ts, 6, 23))
46+
>foo : Symbol(foo, Decl(contextuallyTypedParametersWithInitializers.ts, 6, 23))
47+
48+
const f11 = id1(function ({ foo = 42 }) { return foo }); // Implicit any error
49+
>f11 : Symbol(f11, Decl(contextuallyTypedParametersWithInitializers.ts, 7, 5))
50+
>id1 : Symbol(id1, Decl(contextuallyTypedParametersWithInitializers.ts, 0, 0))
51+
>foo : Symbol(foo, Decl(contextuallyTypedParametersWithInitializers.ts, 7, 27))
52+
>foo : Symbol(foo, Decl(contextuallyTypedParametersWithInitializers.ts, 7, 27))
53+
54+
const f12 = id2(function ({ foo = 42 }) { return foo });
55+
>f12 : Symbol(f12, Decl(contextuallyTypedParametersWithInitializers.ts, 8, 5))
56+
>id2 : Symbol(id2, Decl(contextuallyTypedParametersWithInitializers.ts, 0, 37))
57+
>foo : Symbol(foo, Decl(contextuallyTypedParametersWithInitializers.ts, 8, 27))
58+
>foo : Symbol(foo, Decl(contextuallyTypedParametersWithInitializers.ts, 8, 27))
59+
60+
const f13 = id3(function ({ foo = 42 }) { return foo });
61+
>f13 : Symbol(f13, Decl(contextuallyTypedParametersWithInitializers.ts, 9, 5))
62+
>id3 : Symbol(id3, Decl(contextuallyTypedParametersWithInitializers.ts, 1, 61))
63+
>foo : Symbol(foo, Decl(contextuallyTypedParametersWithInitializers.ts, 9, 27))
64+
>foo : Symbol(foo, Decl(contextuallyTypedParametersWithInitializers.ts, 9, 27))
65+
66+
const f14 = id4(function ({ foo = 42 }) { return foo });
67+
>f14 : Symbol(f14, Decl(contextuallyTypedParametersWithInitializers.ts, 10, 5))
68+
>id4 : Symbol(id4, Decl(contextuallyTypedParametersWithInitializers.ts, 2, 70))
69+
>foo : Symbol(foo, Decl(contextuallyTypedParametersWithInitializers.ts, 10, 27))
70+
>foo : Symbol(foo, Decl(contextuallyTypedParametersWithInitializers.ts, 10, 27))
71+
72+
const f20 = function (foo = 42) { return foo };
73+
>f20 : Symbol(f20, Decl(contextuallyTypedParametersWithInitializers.ts, 12, 5))
74+
>foo : Symbol(foo, Decl(contextuallyTypedParametersWithInitializers.ts, 12, 22))
75+
>foo : Symbol(foo, Decl(contextuallyTypedParametersWithInitializers.ts, 12, 22))
76+
77+
const f21 = id1(function (foo = 42) { return foo }); // Implicit any error
78+
>f21 : Symbol(f21, Decl(contextuallyTypedParametersWithInitializers.ts, 13, 5))
79+
>id1 : Symbol(id1, Decl(contextuallyTypedParametersWithInitializers.ts, 0, 0))
80+
>foo : Symbol(foo, Decl(contextuallyTypedParametersWithInitializers.ts, 13, 26))
81+
>foo : Symbol(foo, Decl(contextuallyTypedParametersWithInitializers.ts, 13, 26))
82+
83+
const f22 = id2(function (foo = 42) { return foo });
84+
>f22 : Symbol(f22, Decl(contextuallyTypedParametersWithInitializers.ts, 14, 5))
85+
>id2 : Symbol(id2, Decl(contextuallyTypedParametersWithInitializers.ts, 0, 37))
86+
>foo : Symbol(foo, Decl(contextuallyTypedParametersWithInitializers.ts, 14, 26))
87+
>foo : Symbol(foo, Decl(contextuallyTypedParametersWithInitializers.ts, 14, 26))
88+
89+
const f25 = id5(function (foo = 42) { return foo });
90+
>f25 : Symbol(f25, Decl(contextuallyTypedParametersWithInitializers.ts, 15, 5))
91+
>id5 : Symbol(id5, Decl(contextuallyTypedParametersWithInitializers.ts, 3, 74))
92+
>foo : Symbol(foo, Decl(contextuallyTypedParametersWithInitializers.ts, 15, 26))
93+
>foo : Symbol(foo, Decl(contextuallyTypedParametersWithInitializers.ts, 15, 26))
94+
95+
// Repro from #28816
96+
97+
function id<T>(input: T): T { return input }
98+
>id : Symbol(id, Decl(contextuallyTypedParametersWithInitializers.ts, 15, 52))
99+
>T : Symbol(T, Decl(contextuallyTypedParametersWithInitializers.ts, 19, 12))
100+
>input : Symbol(input, Decl(contextuallyTypedParametersWithInitializers.ts, 19, 15))
101+
>T : Symbol(T, Decl(contextuallyTypedParametersWithInitializers.ts, 19, 12))
102+
>T : Symbol(T, Decl(contextuallyTypedParametersWithInitializers.ts, 19, 12))
103+
>input : Symbol(input, Decl(contextuallyTypedParametersWithInitializers.ts, 19, 15))
104+
105+
function getFoo ({ foo = 42 }) {
106+
>getFoo : Symbol(getFoo, Decl(contextuallyTypedParametersWithInitializers.ts, 19, 44))
107+
>foo : Symbol(foo, Decl(contextuallyTypedParametersWithInitializers.ts, 21, 18))
108+
109+
return foo;
110+
>foo : Symbol(foo, Decl(contextuallyTypedParametersWithInitializers.ts, 21, 18))
111+
}
112+
113+
const newGetFoo = id(getFoo);
114+
>newGetFoo : Symbol(newGetFoo, Decl(contextuallyTypedParametersWithInitializers.ts, 25, 5))
115+
>id : Symbol(id, Decl(contextuallyTypedParametersWithInitializers.ts, 15, 52))
116+
>getFoo : Symbol(getFoo, Decl(contextuallyTypedParametersWithInitializers.ts, 19, 44))
117+
118+
const newGetFoo2 = id(function getFoo ({ foo = 42 }) {
119+
>newGetFoo2 : Symbol(newGetFoo2, Decl(contextuallyTypedParametersWithInitializers.ts, 26, 5))
120+
>id : Symbol(id, Decl(contextuallyTypedParametersWithInitializers.ts, 15, 52))
121+
>getFoo : Symbol(getFoo, Decl(contextuallyTypedParametersWithInitializers.ts, 26, 22))
122+
>foo : Symbol(foo, Decl(contextuallyTypedParametersWithInitializers.ts, 26, 40))
123+
124+
return foo;
125+
>foo : Symbol(foo, Decl(contextuallyTypedParametersWithInitializers.ts, 26, 40))
126+
127+
});
128+

0 commit comments

Comments
 (0)