Skip to content

Commit b97406f

Browse files
committed
assign and resolve some contextual parameter types late
1 parent 1f037fb commit b97406f

7 files changed

Lines changed: 133 additions & 69 deletions

File tree

src/compiler/checker.ts

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21088,7 +21088,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2108821088
}
2108921089

2109021090
function hasContextSensitiveReturnExpression(node: FunctionLikeDeclaration) {
21091-
if (getEffectiveReturnTypeNode(node) || !node.body) {
21091+
if (node.typeParameters || getEffectiveReturnTypeNode(node) || !node.body) {
2109221092
return false;
2109321093
}
2109421094
if (node.body.kind !== SyntaxKind.Block) {
@@ -26067,7 +26067,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2606726067
});
2606826068
}
2606926069

26070-
function applyToParameterTypes(source: Signature, target: Signature, callback: (s: Type, t: Type) => void) {
26070+
function applyToParameterTypes(source: Signature, target: Signature, callback: (s: Type, t: Type) => void, skipUnannotatedParameters = false) {
26071+
const sourceDeclaredCount = source.parameters.length - (signatureHasRestParameter(source) ? 1 : 0);
2607126072
const sourceCount = getParameterCount(source);
2607226073
const targetCount = getParameterCount(target);
2607326074
const sourceRestType = getEffectiveRestType(source);
@@ -26082,6 +26083,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2608226083
}
2608326084
}
2608426085
for (let i = 0; i < paramCount; i++) {
26086+
if (skipUnannotatedParameters) {
26087+
const decl = i < sourceDeclaredCount ? source.parameters[i] : signatureHasRestParameter(source) ? source.parameters[sourceDeclaredCount] : undefined;
26088+
if (decl?.valueDeclaration && !getEffectiveTypeAnnotationNode(decl.valueDeclaration)) {
26089+
continue
26090+
}
26091+
}
2608526092
callback(getTypeAtPosition(source, i), getTypeAtPosition(target, i));
2608626093
}
2608726094
if (targetRestType) {
@@ -41376,7 +41383,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4137641383
const inferences = map(context.inferences, info => createInferenceInfo(info.typeParameter));
4137741384
applyToParameterTypes(instantiatedSignature, contextualSignature, (source, target) => {
4137841385
inferTypes(inferences, source, target, /*priority*/ 0, /*contravariant*/ true);
41379-
});
41386+
}, /*skipUnannotatedParameters*/ true);
4138041387
if (some(inferences, hasInferenceCandidates)) {
4138141388
// We have inference candidates, indicating that one or more type parameters are referenced
4138241389
// in the parameter types of the contextual signature. Now also infer from the return type.
@@ -41389,6 +41396,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4138941396
if (!hasOverlappingInferences(context.inferences, inferences)) {
4139041397
mergeInferences(context.inferences, inferences);
4139141398
context.inferredTypeParameters = concatenate(context.inferredTypeParameters, uniqueTypeParameters);
41399+
assignContextualParameterTypes(signature, instantiateSignature(contextualSignature, context.mapper));
4139241400
return getOrCreateTypeFromSignature(instantiatedSignature);
4139341401
}
4139441402
}
@@ -41812,7 +41820,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4181241820
// or if its FunctionBody is strict code(11.1.5).
4181341821
checkGrammarModifiers(node);
4181441822

41815-
checkVariableLikeDeclaration(node);
41823+
if (getEffectiveTypeAnnotationNode(node)) {
41824+
// checking annotated parameters early allows the compiler to find circularties early
41825+
checkVariableLikeDeclaration(node);
41826+
} else {
41827+
// defer resolving the type of unannotated parameters so that late contextual parameter types can be assigned before it
41828+
checkNodeDeferred(node);
41829+
}
4181641830
const func = getContainingFunction(node)!;
4181741831
if (hasSyntacticModifier(node, ModifierFlags.ParameterPropertyModifier)) {
4181841832
if (compilerOptions.erasableSyntaxOnly) {
@@ -49158,6 +49172,9 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
4915849172
case SyntaxKind.ClassExpression:
4915949173
checkClassExpressionDeferred(node as ClassExpression);
4916049174
break;
49175+
case SyntaxKind.Parameter:
49176+
checkVariableLikeDeclaration(node as ParameterDeclaration);
49177+
break;
4916149178
case SyntaxKind.TypeParameter:
4916249179
checkTypeParameterDeferred(node as TypeParameterDeclaration);
4916349180
break;

src/compiler/utilities.ts

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10814,17 +10814,20 @@ export function getContainingNodeArray(node: Node): NodeArray<Node> | undefined
1081410814

1081510815
/** @internal */
1081610816
export function hasContextSensitiveParameters(node: FunctionLikeDeclaration): boolean {
10817-
// Functions with any parameters that lack type annotations are context sensitive.
10818-
if (some(node.parameters, p => !getEffectiveTypeAnnotationNode(p))) {
10819-
return true;
10820-
}
10821-
if (node.kind !== SyntaxKind.ArrowFunction) {
10822-
// If the first parameter is not an explicit 'this' parameter, then the function has
10823-
// an implicit 'this' parameter which is subject to contextual typing.
10824-
const parameter = firstOrUndefined(node.parameters);
10825-
if (!(parameter && parameterIsThisKeyword(parameter))) {
10817+
// Functions with type parameters are not context sensitive.
10818+
if (!node.typeParameters) {
10819+
// Functions with any parameters that lack type annotations are context sensitive.
10820+
if (some(node.parameters, p => !getEffectiveTypeAnnotationNode(p))) {
1082610821
return true;
1082710822
}
10823+
if (node.kind !== SyntaxKind.ArrowFunction) {
10824+
// If the first parameter is not an explicit 'this' parameter, then the function has
10825+
// an implicit 'this' parameter which is subject to contextual typing.
10826+
const parameter = firstOrUndefined(node.parameters);
10827+
if (!(parameter && parameterIsThisKeyword(parameter))) {
10828+
return true;
10829+
}
10830+
}
1082810831
}
1082910832
return false;
1083010833
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
contextualTypingGenericFunction2.ts(40,22): error TS18046: 'a' is of type 'unknown'.
2+
3+
4+
==== contextualTypingGenericFunction2.ts (1 errors) ====
5+
// https://github.com/microsoft/TypeScript/issues/61791
6+
7+
declare const fn1: <T, Args extends Array<any>, Ret>(
8+
self: T,
9+
body: (this: T, ...args: Args) => Ret,
10+
) => (...args: Args) => Ret;
11+
12+
export const ok1 = fn1({ message: "foo" }, function (n: number) {
13+
this.message;
14+
});
15+
16+
export const ok2 = fn1({ message: "foo" }, function <N>(n: N) {
17+
this.message;
18+
});
19+
20+
declare const fn2: <Args extends Array<any>, Ret>(
21+
body: (first: string, ...args: Args) => Ret,
22+
) => (...args: Args) => Ret;
23+
24+
export const ok3 = fn2(function <N>(first, n: N) {});
25+
26+
declare const fn3: <Args extends Array<any>, Ret>(
27+
body: (...args: Args) => (arg: string) => Ret,
28+
) => (...args: Args) => Ret;
29+
30+
export const ok4 = fn3(function <N>(n: N) {
31+
return (arg) => {
32+
return 10
33+
}
34+
});
35+
36+
declare function fn4<T, P>(config: {
37+
context: T;
38+
callback: (params: P) => (context: T, params: P) => number;
39+
}): (params: P) => number;
40+
41+
export const ok5 = fn4({
42+
context: 1,
43+
callback: <T,>(params: T) => {
44+
return (a, b) => a + 1;
45+
~
46+
!!! error TS18046: 'a' is of type 'unknown'.
47+
},
48+
});
49+
50+
declare const fnGen1: <T, Args extends Array<any>, Ret>(
51+
self: T,
52+
body: (this: T, ...args: Args) => Generator<any, Ret, never>,
53+
) => (...args: Args) => Ret;
54+
55+
export const ok6 = fnGen1({ message: "foo" }, function* (n: number) {
56+
this.message;
57+
});
58+
59+
export const ok7 = fnGen1({ message: "foo" }, function* <N>(n: N) {
60+
this.message;
61+
});
62+

tests/baselines/reference/contextualTypingGenericFunction2.js

Lines changed: 3 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -62,28 +62,9 @@ export const ok7 = fnGen1({ message: "foo" }, function* <N>(n: N) {
6262

6363
//// [contextualTypingGenericFunction2.d.ts]
6464
export declare const ok1: (n: number) => void;
65-
export declare const ok2: (n: any) => void;
65+
export declare const ok2: <N>(n: N) => void;
6666
export declare const ok3: <N>(n: N) => void;
6767
export declare const ok4: <N>(n: N) => number;
68-
export declare const ok5: (params: T) => number;
68+
export declare const ok5: <T>(params: T) => number;
6969
export declare const ok6: (n: number) => void;
70-
export declare const ok7: (n: any) => void;
71-
72-
73-
//// [DtsFileErrors]
74-
75-
76-
contextualTypingGenericFunction2.d.ts(5,36): error TS2304: Cannot find name 'T'.
77-
78-
79-
==== contextualTypingGenericFunction2.d.ts (1 errors) ====
80-
export declare const ok1: (n: number) => void;
81-
export declare const ok2: (n: any) => void;
82-
export declare const ok3: <N>(n: N) => void;
83-
export declare const ok4: <N>(n: N) => number;
84-
export declare const ok5: (params: T) => number;
85-
~
86-
!!! error TS2304: Cannot find name 'T'.
87-
export declare const ok6: (n: number) => void;
88-
export declare const ok7: (n: any) => void;
89-
70+
export declare const ok7: <N>(n: N) => void;

tests/baselines/reference/contextualTypingGenericFunction2.types

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,10 @@ export const ok1 = fn1({ message: "foo" }, function (n: number) {
5656
});
5757

5858
export const ok2 = fn1({ message: "foo" }, function <N>(n: N) {
59-
>ok2 : (n: any) => void
60-
> : ^^^^^^^^^^^^^^^^
61-
>fn1({ message: "foo" }, function <N>(n: N) { this.message;}) : (n: any) => void
62-
> : ^^^^^^^^^^^^^^^^
59+
>ok2 : <N>(n: N) => void
60+
> : ^ ^^^^^^^^^^^^^^^
61+
>fn1({ message: "foo" }, function <N>(n: N) { this.message;}) : <N>(n: N) => void
62+
> : ^ ^^^^^^^^^^^^^^^
6363
>fn1 : <T, Args extends Array<any>, Ret>(self: T, body: (this: T, ...args: Args) => Ret) => (...args: Args) => Ret
6464
> : ^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^ ^^^^^
6565
>{ message: "foo" } : { message: string; }
@@ -178,14 +178,14 @@ declare function fn4<T, P>(config: {
178178
> : ^
179179

180180
export const ok5 = fn4({
181-
>ok5 : (params: T) => number
182-
> : ^ ^^^^^^^^
183-
>fn4({ context: 1, callback: <T,>(params: T) => { return (a, b) => a + 1; },}) : (params: T) => number
184-
> : ^ ^^^^^^^^
181+
>ok5 : <T>(params: T) => number
182+
> : ^ ^^ ^^^^^^^^
183+
>fn4({ context: 1, callback: <T,>(params: T) => { return (a, b) => a + 1; },}) : <T>(params: T) => number
184+
> : ^ ^^ ^^^^^^^^
185185
>fn4 : <T, P>(config: { context: T; callback: (params: P) => (context: T, params: P) => number; }) => (params: P) => number
186186
> : ^ ^^ ^^ ^^ ^^^^^
187-
>{ context: 1, callback: <T,>(params: T) => { return (a, b) => a + 1; },} : { context: number; callback: <T>(params: T) => (a: number, b: T) => number; }
188-
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^ ^^^^^^ ^^^^^^^^^^ ^^^^^^^^^^^^^^^^^
187+
>{ context: 1, callback: <T,>(params: T) => { return (a, b) => a + 1; },} : { context: number; callback: <T>(params: T) => (a: unknown, b: unknown) => any; }
188+
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^ ^^ ^^^^^^ ^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^
189189

190190
context: 1,
191191
>context : number
@@ -194,24 +194,24 @@ export const ok5 = fn4({
194194
> : ^
195195

196196
callback: <T,>(params: T) => {
197-
>callback : <T>(params: T) => (a: number, b: T) => number
198-
> : ^ ^^ ^^ ^^^^^^ ^^^^^^^^^^ ^^^^^^^^^^^^^^
199-
><T,>(params: T) => { return (a, b) => a + 1; } : <T>(params: T) => (a: number, b: T) => number
200-
> : ^ ^^ ^^ ^^^^^^ ^^^^^^^^^^ ^^^^^^^^^^^^^^
197+
>callback : <T>(params: T) => (a: unknown, b: unknown) => any
198+
> : ^ ^^ ^^ ^^^^^^ ^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^
199+
><T,>(params: T) => { return (a, b) => a + 1; } : <T>(params: T) => (a: unknown, b: unknown) => any
200+
> : ^ ^^ ^^ ^^^^^^ ^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^
201201
>params : T
202202
> : ^
203203

204204
return (a, b) => a + 1;
205-
>(a, b) => a + 1 : (a: number, b: T) => number
206-
> : ^ ^^^^^^^^^^ ^^^^^^^^^^^^^^
207-
>a : number
208-
> : ^^^^^^
209-
>b : T
210-
> : ^
211-
>a + 1 : number
212-
> : ^^^^^^
213-
>a : number
214-
> : ^^^^^^
205+
>(a, b) => a + 1 : (a: unknown, b: unknown) => any
206+
> : ^ ^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^
207+
>a : unknown
208+
> : ^^^^^^^
209+
>b : unknown
210+
> : ^^^^^^^
211+
>a + 1 : any
212+
> : ^^^
213+
>a : unknown
214+
> : ^^^^^^^
215215
>1 : 1
216216
> : ^
217217

@@ -267,10 +267,10 @@ export const ok6 = fnGen1({ message: "foo" }, function* (n: number) {
267267
});
268268

269269
export const ok7 = fnGen1({ message: "foo" }, function* <N>(n: N) {
270-
>ok7 : (n: any) => void
271-
> : ^^^^^^^^^^^^^^^^
272-
>fnGen1({ message: "foo" }, function* <N>(n: N) { this.message;}) : (n: any) => void
273-
> : ^^^^^^^^^^^^^^^^
270+
>ok7 : <N>(n: N) => void
271+
> : ^ ^^^^^^^^^^^^^^^
272+
>fnGen1({ message: "foo" }, function* <N>(n: N) { this.message;}) : <N>(n: N) => void
273+
> : ^ ^^^^^^^^^^^^^^^
274274
>fnGen1 : <T, Args extends Array<any>, Ret>(self: T, body: (this: T, ...args: Args) => Generator<any, Ret, never>) => (...args: Args) => Ret
275275
> : ^ ^^ ^^^^^^^^^ ^^ ^^ ^^ ^^ ^^ ^^^^^
276276
>{ message: "foo" } : { message: string; }

tests/baselines/reference/literalTypes2.types

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -375,9 +375,9 @@ function f3() {
375375
> : ^^^^^
376376

377377
const c3 = cond ? E.A : cond ? true : 123;
378-
>c3 : true | E.A | 123
378+
>c3 : true | 123 | E.A
379379
> : ^^^^^^^^^^^^^^^^
380-
>cond ? E.A : cond ? true : 123 : true | E.A | 123
380+
>cond ? E.A : cond ? true : 123 : true | 123 | E.A
381381
> : ^^^^^^^^^^^^^^^^
382382
>cond : boolean
383383
> : ^^^^^^^
@@ -473,7 +473,7 @@ function f3() {
473473
let x3 = c3;
474474
>x3 : number | boolean
475475
> : ^^^^^^^^^^^^^^^^
476-
>c3 : true | E.A | 123
476+
>c3 : true | 123 | E.A
477477
> : ^^^^^^^^^^^^^^^^
478478

479479
let x4 = c4;
Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/// <reference path='fourslash.ts'/>
22

3+
// should not contextually type the RHS because it introduces type parameters
34
////var obj: { f<T>(x: T): T } = { f: <S>(/*1*/x) => x };
45
////var obj2: <T>(x: T) => T = <S>(/*2*/x) => x;
56
////
@@ -10,7 +11,7 @@
1011
////c.obj = <S>(/*3*/x) => x;
1112

1213
verify.quickInfos({
13-
1: "(parameter) x: T",
14-
2: "(parameter) x: T",
15-
3: "(parameter) x: T"
14+
1: "(parameter) x: any",
15+
2: "(parameter) x: any",
16+
3: "(parameter) x: any"
1617
});

0 commit comments

Comments
 (0)