Skip to content

Commit 695e440

Browse files
committed
Add 'extends' clause to 'infer' type
1 parent 0a24dee commit 695e440

File tree

8 files changed

+941
-17
lines changed

8 files changed

+941
-17
lines changed

src/compiler/checker.ts

+19-3
Original file line numberDiff line numberDiff line change
@@ -35335,6 +35335,22 @@ namespace ts {
3533535335
grammarErrorOnNode(node, Diagnostics.infer_declarations_are_only_permitted_in_the_extends_clause_of_a_conditional_type);
3533635336
}
3533735337
checkSourceElement(node.typeParameter);
35338+
const symbol = getSymbolOfNode(node.typeParameter);
35339+
if (symbol.declarations && symbol.declarations.length > 1) {
35340+
const links = getSymbolLinks(symbol);
35341+
if (!links.typeParametersChecked) {
35342+
links.typeParametersChecked = true;
35343+
const typeParameter = getDeclaredTypeOfTypeParameter(symbol);
35344+
const declarations: TypeParameterDeclaration[] = getDeclarationsOfKind(symbol, SyntaxKind.TypeParameter);
35345+
if (!areTypeParametersIdentical(declarations, [typeParameter], decl => [decl])) {
35346+
// Report an error on every conflicting declaration.
35347+
const name = symbolToString(symbol);
35348+
for (const declaration of declarations) {
35349+
error(declaration.name, Diagnostics.All_declarations_of_0_must_have_identical_constraints, name);
35350+
}
35351+
}
35352+
}
35353+
}
3533835354
registerForUnusedIdentifiersCheck(node);
3533935355
}
3534035356

@@ -38811,7 +38827,7 @@ namespace ts {
3881138827
}
3881238828

3881338829
const type = getDeclaredTypeOfSymbol(symbol) as InterfaceType;
38814-
if (!areTypeParametersIdentical(declarations, type.localTypeParameters!)) {
38830+
if (!areTypeParametersIdentical(declarations, type.localTypeParameters!, getEffectiveTypeParameterDeclarations)) {
3881538831
// Report an error on every conflicting declaration.
3881638832
const name = symbolToString(symbol);
3881738833
for (const declaration of declarations) {
@@ -38821,13 +38837,13 @@ namespace ts {
3882138837
}
3882238838
}
3882338839

38824-
function areTypeParametersIdentical(declarations: readonly (ClassDeclaration | InterfaceDeclaration)[], targetParameters: TypeParameter[]) {
38840+
function areTypeParametersIdentical<T extends DeclarationWithTypeParameters | TypeParameterDeclaration>(declarations: readonly T[], targetParameters: TypeParameter[], getTypeParameterDeclarations: (node: T) => readonly TypeParameterDeclaration[]) {
3882538841
const maxTypeArgumentCount = length(targetParameters);
3882638842
const minTypeArgumentCount = getMinTypeArgumentCount(targetParameters);
3882738843

3882838844
for (const declaration of declarations) {
3882938845
// If this declaration has too few or too many type parameters, we report an error
38830-
const sourceParameters = getEffectiveTypeParameterDeclarations(declaration);
38846+
const sourceParameters = getTypeParameterDeclarations(declaration);
3883138847
const numTypeParameters = sourceParameters.length;
3883238848
if (numTypeParameters < minTypeArgumentCount || numTypeParameters > maxTypeArgumentCount) {
3883338849
return false;

src/compiler/diagnosticMessages.json

+4
Original file line numberDiff line numberDiff line change
@@ -3405,6 +3405,10 @@
34053405
"category": "Error",
34063406
"code": 2837
34073407
},
3408+
"All declarations of '{0}' must have identical constraints.": {
3409+
"category": "Error",
3410+
"code": 2838
3411+
},
34083412

34093413
"Import declaration '{0}' is using private name '{1}'.": {
34103414
"category": "Error",

src/compiler/parser.ts

+6-14
Original file line numberDiff line numberDiff line change
@@ -3077,6 +3077,10 @@ namespace ts {
30773077
}
30783078

30793079
function parseTypeParameter(): TypeParameterDeclaration {
3080+
return parseTypeParameterWorker(/*canHaveDefault*/ true);
3081+
}
3082+
3083+
function parseTypeParameterWorker(canHaveDefault: boolean): TypeParameterDeclaration {
30803084
const pos = getNodePos();
30813085
const name = parseIdentifier();
30823086
let constraint: TypeNode | undefined;
@@ -3101,7 +3105,7 @@ namespace ts {
31013105
}
31023106
}
31033107

3104-
const defaultType = parseOptional(SyntaxKind.EqualsToken) ? parseType() : undefined;
3108+
const defaultType = canHaveDefault && parseOptional(SyntaxKind.EqualsToken) ? parseType() : undefined;
31053109
const node = factory.createTypeParameterDeclaration(name, constraint, defaultType);
31063110
node.expression = expression;
31073111
return finishNode(node, pos);
@@ -3859,22 +3863,10 @@ namespace ts {
38593863
return finishNode(factory.createTypeOperatorNode(operator, parseTypeOperatorOrHigher()), pos);
38603864
}
38613865

3862-
function parseTypeParameterOfInferType() {
3863-
const pos = getNodePos();
3864-
return finishNode(
3865-
factory.createTypeParameterDeclaration(
3866-
parseIdentifier(),
3867-
/*constraint*/ undefined,
3868-
/*defaultType*/ undefined
3869-
),
3870-
pos
3871-
);
3872-
}
3873-
38743866
function parseInferType(): InferTypeNode {
38753867
const pos = getNodePos();
38763868
parseExpected(SyntaxKind.InferKeyword);
3877-
return finishNode(factory.createInferTypeNode(parseTypeParameterOfInferType()), pos);
3869+
return finishNode(factory.createInferTypeNode(parseTypeParameterWorker(/*canHaveDefault*/ false)), pos);
38783870
}
38793871

38803872
function parseTypeOperatorOrHigher(): TypeNode {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
tests/cases/conformance/types/conditional/inferTypesWithExtends1.ts(97,26): error TS2838: All declarations of 'U' must have identical constraints.
2+
tests/cases/conformance/types/conditional/inferTypesWithExtends1.ts(97,53): error TS2838: All declarations of 'U' must have identical constraints.
3+
4+
5+
==== tests/cases/conformance/types/conditional/inferTypesWithExtends1.ts (2 errors) ====
6+
// infer to tuple element
7+
type X1<T extends any[]> =
8+
T extends [infer U extends string] ? ["string", U] :
9+
T extends [infer U extends number] ? ["number", U] :
10+
never;
11+
12+
type X1_T1 = X1<["a"]>; // ["string", "a"]
13+
type X1_T2 = X1<[1]>; // ["number", 1]
14+
type X1_T3 = X1<[object]>; // never
15+
16+
// infer to argument
17+
type X2<T extends (...args: any[]) => void> =
18+
T extends (a: infer U extends string) => void ? ["string", U] :
19+
T extends (a: infer U extends number) => void ? ["number", U] :
20+
never;
21+
22+
type X2_T1 = X2<(a: "a") => void>; // ["string", "a"]
23+
type X2_T2 = X2<(a: 1) => void>; // ["number", 1]
24+
type X2_T3 = X2<(a: object) => void>; // never
25+
26+
// infer to return type
27+
type X3<T extends (...args: any[]) => any> =
28+
T extends (...args: any[]) => infer U extends string ? ["string", U] :
29+
T extends (...args: any[]) => infer U extends number ? ["number", U] :
30+
never;
31+
32+
type X3_T1 = X3<() => "a">; // ["string", "a"]
33+
type X3_T2 = X3<() => 1>; // ["number", 1]
34+
type X3_T3 = X3<() => object>; // never
35+
36+
// infer to instance type
37+
type X4<T extends new (...args: any[]) => any> =
38+
T extends new (...args: any[]) => infer U extends { a: string } ? ["string", U] :
39+
T extends new (...args: any[]) => infer U extends { a: number } ? ["number", U] :
40+
never;
41+
42+
type X4_T1 = X4<new () => { a: "a" }>; // ["string", { a: "a" }]
43+
type X4_T2 = X4<new () => { a: 1 }>; // ["number", { a: 1 }]
44+
type X4_T3 = X4<new () => { a: object }>; // never
45+
46+
// infer to type argument
47+
type X5<T> =
48+
T extends Promise<infer U extends string> ? ["string", U] :
49+
T extends Promise<infer U extends number> ? ["number", U] :
50+
never;
51+
52+
type X5_T1 = X5<Promise<"a" | "b">>; // ["string", "a" | "b"]
53+
type X5_T2 = X5<Promise<1 | 2>>; // ["number", 1 | 2]
54+
type X5_T3 = X5<Promise<1n | 2n>>; // never
55+
56+
// infer to property type
57+
type X6<T> =
58+
T extends { a: infer U extends string } ? ["string", U] :
59+
T extends { a: infer U extends number } ? ["number", U] :
60+
never;
61+
62+
type X6_T1 = X6<{ a: "a" }>; // ["string", "a"]
63+
type X6_T2 = X6<{ a: 1 }>; // ["number", 1]
64+
type X6_T3 = X6<{ a: object }>; // never
65+
66+
// infer twice with same constraint
67+
type X7<T> =
68+
T extends { a: infer U extends string, b: infer U extends string } ? ["string", U] :
69+
T extends { a: infer U extends number, b: infer U extends number } ? ["number", U] :
70+
never;
71+
72+
type X7_T1 = X7<{ a: "a", b: "b" }>; // ["string", "a" | "b"]
73+
type X7_T2 = X7<{ a: 1, b: 2 }>; // ["number", 1 | 2]
74+
type X7_T3 = X7<{ a: object, b: object }>; // never
75+
type X7_T4 = X7<{ a: "a", b: 1 }>; // never
76+
77+
// infer twice with missing second constraint (same behavior as class/interface)
78+
type X8<T> =
79+
T extends { a: infer U extends string, b: infer U } ? ["string", U] :
80+
T extends { a: infer U extends number, b: infer U } ? ["number", U] :
81+
never;
82+
83+
type X8_T1 = X8<{ a: "a", b: "b" }>; // ["string", "a" | "b"]
84+
type X8_T2 = X8<{ a: 1, b: 2 }>; // ["number", 1 | 2]
85+
type X8_T3 = X8<{ a: object, b: object }>; // never
86+
type X8_T4 = X8<{ a: "a", b: 1 }>; // never
87+
88+
// infer twice with missing first constraint (same behavior as class/interface)
89+
type X9<T> =
90+
T extends { a: infer U, b: infer U extends string } ? ["string", U] :
91+
T extends { a: infer U, b: infer U extends number } ? ["number", U] :
92+
never;
93+
94+
type X9_T1 = X9<{ a: "a", b: "b" }>; // ["string", "a" | "b"]
95+
type X9_T2 = X9<{ a: 1, b: 2 }>; // ["number", 1 | 2]
96+
type X9_T3 = X9<{ a: object, b: object }>; // never
97+
type X9_T4 = X9<{ a: "a", b: 1 }>; // never
98+
99+
100+
// infer twice with different constraints (same behavior as class/interface)
101+
type X10<T> =
102+
T extends { a: infer U extends string, b: infer U extends number } ? U :
103+
~
104+
!!! error TS2838: All declarations of 'U' must have identical constraints.
105+
~
106+
!!! error TS2838: All declarations of 'U' must have identical constraints.
107+
never;
108+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
//// [inferTypesWithExtends1.ts]
2+
// infer to tuple element
3+
type X1<T extends any[]> =
4+
T extends [infer U extends string] ? ["string", U] :
5+
T extends [infer U extends number] ? ["number", U] :
6+
never;
7+
8+
type X1_T1 = X1<["a"]>; // ["string", "a"]
9+
type X1_T2 = X1<[1]>; // ["number", 1]
10+
type X1_T3 = X1<[object]>; // never
11+
12+
// infer to argument
13+
type X2<T extends (...args: any[]) => void> =
14+
T extends (a: infer U extends string) => void ? ["string", U] :
15+
T extends (a: infer U extends number) => void ? ["number", U] :
16+
never;
17+
18+
type X2_T1 = X2<(a: "a") => void>; // ["string", "a"]
19+
type X2_T2 = X2<(a: 1) => void>; // ["number", 1]
20+
type X2_T3 = X2<(a: object) => void>; // never
21+
22+
// infer to return type
23+
type X3<T extends (...args: any[]) => any> =
24+
T extends (...args: any[]) => infer U extends string ? ["string", U] :
25+
T extends (...args: any[]) => infer U extends number ? ["number", U] :
26+
never;
27+
28+
type X3_T1 = X3<() => "a">; // ["string", "a"]
29+
type X3_T2 = X3<() => 1>; // ["number", 1]
30+
type X3_T3 = X3<() => object>; // never
31+
32+
// infer to instance type
33+
type X4<T extends new (...args: any[]) => any> =
34+
T extends new (...args: any[]) => infer U extends { a: string } ? ["string", U] :
35+
T extends new (...args: any[]) => infer U extends { a: number } ? ["number", U] :
36+
never;
37+
38+
type X4_T1 = X4<new () => { a: "a" }>; // ["string", { a: "a" }]
39+
type X4_T2 = X4<new () => { a: 1 }>; // ["number", { a: 1 }]
40+
type X4_T3 = X4<new () => { a: object }>; // never
41+
42+
// infer to type argument
43+
type X5<T> =
44+
T extends Promise<infer U extends string> ? ["string", U] :
45+
T extends Promise<infer U extends number> ? ["number", U] :
46+
never;
47+
48+
type X5_T1 = X5<Promise<"a" | "b">>; // ["string", "a" | "b"]
49+
type X5_T2 = X5<Promise<1 | 2>>; // ["number", 1 | 2]
50+
type X5_T3 = X5<Promise<1n | 2n>>; // never
51+
52+
// infer to property type
53+
type X6<T> =
54+
T extends { a: infer U extends string } ? ["string", U] :
55+
T extends { a: infer U extends number } ? ["number", U] :
56+
never;
57+
58+
type X6_T1 = X6<{ a: "a" }>; // ["string", "a"]
59+
type X6_T2 = X6<{ a: 1 }>; // ["number", 1]
60+
type X6_T3 = X6<{ a: object }>; // never
61+
62+
// infer twice with same constraint
63+
type X7<T> =
64+
T extends { a: infer U extends string, b: infer U extends string } ? ["string", U] :
65+
T extends { a: infer U extends number, b: infer U extends number } ? ["number", U] :
66+
never;
67+
68+
type X7_T1 = X7<{ a: "a", b: "b" }>; // ["string", "a" | "b"]
69+
type X7_T2 = X7<{ a: 1, b: 2 }>; // ["number", 1 | 2]
70+
type X7_T3 = X7<{ a: object, b: object }>; // never
71+
type X7_T4 = X7<{ a: "a", b: 1 }>; // never
72+
73+
// infer twice with missing second constraint (same behavior as class/interface)
74+
type X8<T> =
75+
T extends { a: infer U extends string, b: infer U } ? ["string", U] :
76+
T extends { a: infer U extends number, b: infer U } ? ["number", U] :
77+
never;
78+
79+
type X8_T1 = X8<{ a: "a", b: "b" }>; // ["string", "a" | "b"]
80+
type X8_T2 = X8<{ a: 1, b: 2 }>; // ["number", 1 | 2]
81+
type X8_T3 = X8<{ a: object, b: object }>; // never
82+
type X8_T4 = X8<{ a: "a", b: 1 }>; // never
83+
84+
// infer twice with missing first constraint (same behavior as class/interface)
85+
type X9<T> =
86+
T extends { a: infer U, b: infer U extends string } ? ["string", U] :
87+
T extends { a: infer U, b: infer U extends number } ? ["number", U] :
88+
never;
89+
90+
type X9_T1 = X9<{ a: "a", b: "b" }>; // ["string", "a" | "b"]
91+
type X9_T2 = X9<{ a: 1, b: 2 }>; // ["number", 1 | 2]
92+
type X9_T3 = X9<{ a: object, b: object }>; // never
93+
type X9_T4 = X9<{ a: "a", b: 1 }>; // never
94+
95+
96+
// infer twice with different constraints (same behavior as class/interface)
97+
type X10<T> =
98+
T extends { a: infer U extends string, b: infer U extends number } ? U :
99+
never;
100+
101+
102+
//// [inferTypesWithExtends1.js]

0 commit comments

Comments
 (0)