Skip to content

Commit

Permalink
Add 'extends' clause to 'infer' type
Browse files Browse the repository at this point in the history
  • Loading branch information
rbuckton committed Mar 4, 2022
1 parent 0a24dee commit bafe193
Show file tree
Hide file tree
Showing 23 changed files with 1,017 additions and 55 deletions.
22 changes: 19 additions & 3 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35335,6 +35335,22 @@ namespace ts {
grammarErrorOnNode(node, Diagnostics.infer_declarations_are_only_permitted_in_the_extends_clause_of_a_conditional_type);
}
checkSourceElement(node.typeParameter);
const symbol = getSymbolOfNode(node.typeParameter);
if (symbol.declarations && symbol.declarations.length > 1) {
const links = getSymbolLinks(symbol);
if (!links.typeParametersChecked) {
links.typeParametersChecked = true;
const typeParameter = getDeclaredTypeOfTypeParameter(symbol);
const declarations: TypeParameterDeclaration[] = getDeclarationsOfKind(symbol, SyntaxKind.TypeParameter);
if (!areTypeParametersIdentical(declarations, [typeParameter], decl => [decl])) {
// Report an error on every conflicting declaration.
const name = symbolToString(symbol);
for (const declaration of declarations) {
error(declaration.name, Diagnostics.All_declarations_of_0_must_have_identical_constraints, name);
}
}
}
}
registerForUnusedIdentifiersCheck(node);
}

Expand Down Expand Up @@ -38811,7 +38827,7 @@ namespace ts {
}

const type = getDeclaredTypeOfSymbol(symbol) as InterfaceType;
if (!areTypeParametersIdentical(declarations, type.localTypeParameters!)) {
if (!areTypeParametersIdentical(declarations, type.localTypeParameters!, getEffectiveTypeParameterDeclarations)) {
// Report an error on every conflicting declaration.
const name = symbolToString(symbol);
for (const declaration of declarations) {
Expand All @@ -38821,13 +38837,13 @@ namespace ts {
}
}

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

for (const declaration of declarations) {
// If this declaration has too few or too many type parameters, we report an error
const sourceParameters = getEffectiveTypeParameterDeclarations(declaration);
const sourceParameters = getTypeParameterDeclarations(declaration);
const numTypeParameters = sourceParameters.length;
if (numTypeParameters < minTypeArgumentCount || numTypeParameters > maxTypeArgumentCount) {
return false;
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -3405,6 +3405,10 @@
"category": "Error",
"code": 2837
},
"All declarations of '{0}' must have identical constraints.": {
"category": "Error",
"code": 2838
},

"Import declaration '{0}' is using private name '{1}'.": {
"category": "Error",
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/factory/nodeFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1989,7 +1989,7 @@ namespace ts {
// @api
function createConditionalTypeNode(checkType: TypeNode, extendsType: TypeNode, trueType: TypeNode, falseType: TypeNode) {
const node = createBaseNode<ConditionalTypeNode>(SyntaxKind.ConditionalType);
node.checkType = parenthesizerRules().parenthesizeMemberOfConditionalType(checkType);
node.checkType = parenthesizerRules().parenthesizeCheckTypeOfConditionalType(checkType);
node.extendsType = parenthesizerRules().parenthesizeMemberOfConditionalType(extendsType);
node.trueType = trueType;
node.falseType = falseType;
Expand Down
7 changes: 7 additions & 0 deletions src/compiler/factory/parenthesizerRules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ namespace ts {
parenthesizeExpressionForDisallowedComma,
parenthesizeExpressionOfExpressionStatement,
parenthesizeConciseBodyOfArrowFunction,
parenthesizeCheckTypeOfConditionalType,
parenthesizeMemberOfConditionalType,
parenthesizeMemberOfElementType,
parenthesizeElementTypeOfArrayType,
Expand Down Expand Up @@ -388,6 +389,11 @@ namespace ts {
return body;
}

function parenthesizeCheckTypeOfConditionalType(member: TypeNode): TypeNode {
return isInferTypeNode(member) ? factory.createParenthesizedType(member) :
parenthesizeMemberOfConditionalType(member);
}

function parenthesizeMemberOfConditionalType(member: TypeNode): TypeNode {
return member.kind === SyntaxKind.ConditionalType ? factory.createParenthesizedType(member) : member;
}
Expand Down Expand Up @@ -446,6 +452,7 @@ namespace ts {
parenthesizeExpressionForDisallowedComma: identity,
parenthesizeExpressionOfExpressionStatement: identity,
parenthesizeConciseBodyOfArrowFunction: identity,
parenthesizeCheckTypeOfConditionalType: identity,
parenthesizeMemberOfConditionalType: identity,
parenthesizeMemberOfElementType: identity,
parenthesizeElementTypeOfArrayType: identity,
Expand Down
20 changes: 6 additions & 14 deletions src/compiler/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3077,6 +3077,10 @@ namespace ts {
}

function parseTypeParameter(): TypeParameterDeclaration {
return parseTypeParameterWorker(/*canHaveDefault*/ true);
}

function parseTypeParameterWorker(canHaveDefault: boolean): TypeParameterDeclaration {
const pos = getNodePos();
const name = parseIdentifier();
let constraint: TypeNode | undefined;
Expand All @@ -3101,7 +3105,7 @@ namespace ts {
}
}

const defaultType = parseOptional(SyntaxKind.EqualsToken) ? parseType() : undefined;
const defaultType = canHaveDefault && parseOptional(SyntaxKind.EqualsToken) ? parseType() : undefined;
const node = factory.createTypeParameterDeclaration(name, constraint, defaultType);
node.expression = expression;
return finishNode(node, pos);
Expand Down Expand Up @@ -3859,22 +3863,10 @@ namespace ts {
return finishNode(factory.createTypeOperatorNode(operator, parseTypeOperatorOrHigher()), pos);
}

function parseTypeParameterOfInferType() {
const pos = getNodePos();
return finishNode(
factory.createTypeParameterDeclaration(
parseIdentifier(),
/*constraint*/ undefined,
/*defaultType*/ undefined
),
pos
);
}

function parseInferType(): InferTypeNode {
const pos = getNodePos();
parseExpected(SyntaxKind.InferKeyword);
return finishNode(factory.createInferTypeNode(parseTypeParameterOfInferType()), pos);
return finishNode(factory.createInferTypeNode(parseTypeParameterWorker(/*canHaveDefault*/ false)), pos);
}

function parseTypeOperatorOrHigher(): TypeNode {
Expand Down
1 change: 1 addition & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7090,6 +7090,7 @@ namespace ts {
parenthesizeExpressionOfExpressionStatement(expression: Expression): Expression;
parenthesizeConciseBodyOfArrowFunction(body: Expression): Expression;
parenthesizeConciseBodyOfArrowFunction(body: ConciseBody): ConciseBody;
parenthesizeCheckTypeOfConditionalType(member: TypeNode): TypeNode;
parenthesizeMemberOfConditionalType(member: TypeNode): TypeNode;
parenthesizeMemberOfElementType(member: TypeNode): TypeNode;
parenthesizeElementTypeOfArrayType(member: TypeNode): TypeNode;
Expand Down
16 changes: 8 additions & 8 deletions tests/baselines/reference/inferTypes1.errors.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ tests/cases/conformance/types/conditional/inferTypes1.ts(55,25): error TS2344: T
tests/cases/conformance/types/conditional/inferTypes1.ts(56,25): error TS2344: Type 'Function' does not satisfy the constraint '(x: any) => any'.
Type 'Function' provides no match for the signature '(x: any): any'.
tests/cases/conformance/types/conditional/inferTypes1.ts(82,12): error TS1338: 'infer' declarations are only permitted in the 'extends' clause of a conditional type.
tests/cases/conformance/types/conditional/inferTypes1.ts(83,15): error TS1338: 'infer' declarations are only permitted in the 'extends' clause of a conditional type.
tests/cases/conformance/types/conditional/inferTypes1.ts(83,41): error TS1338: 'infer' declarations are only permitted in the 'extends' clause of a conditional type.
tests/cases/conformance/types/conditional/inferTypes1.ts(83,51): error TS1338: 'infer' declarations are only permitted in the 'extends' clause of a conditional type.
tests/cases/conformance/types/conditional/inferTypes1.ts(83,16): error TS1338: 'infer' declarations are only permitted in the 'extends' clause of a conditional type.
tests/cases/conformance/types/conditional/inferTypes1.ts(83,43): error TS1338: 'infer' declarations are only permitted in the 'extends' clause of a conditional type.
tests/cases/conformance/types/conditional/inferTypes1.ts(83,53): error TS1338: 'infer' declarations are only permitted in the 'extends' clause of a conditional type.
tests/cases/conformance/types/conditional/inferTypes1.ts(84,15): error TS2304: Cannot find name 'U'.
tests/cases/conformance/types/conditional/inferTypes1.ts(84,15): error TS4081: Exported type alias 'T62' has or is using private name 'U'.
tests/cases/conformance/types/conditional/inferTypes1.ts(84,43): error TS2304: Cannot find name 'U'.
Expand Down Expand Up @@ -120,12 +120,12 @@ tests/cases/conformance/types/conditional/inferTypes1.ts(153,40): error TS2322:
type T60 = infer U; // Error
~~~~~~~
!!! error TS1338: 'infer' declarations are only permitted in the 'extends' clause of a conditional type.
type T61<T> = infer A extends infer B ? infer C : infer D; // Error
~~~~~~~
type T61<T> = (infer A) extends infer B ? infer C : infer D; // Error
~~~~~~~
!!! error TS1338: 'infer' declarations are only permitted in the 'extends' clause of a conditional type.
~~~~~~~
~~~~~~~
!!! error TS1338: 'infer' declarations are only permitted in the 'extends' clause of a conditional type.
~~~~~~~
~~~~~~~
!!! error TS1338: 'infer' declarations are only permitted in the 'extends' clause of a conditional type.
type T62<T> = U extends (infer U)[] ? U : U; // Error
~
Expand All @@ -136,7 +136,7 @@ tests/cases/conformance/types/conditional/inferTypes1.ts(153,40): error TS2322:
!!! error TS2304: Cannot find name 'U'.
~
!!! error TS4081: Exported type alias 'T62' has or is using private name 'U'.
type T63<T> = T extends (infer A extends infer B ? infer C : infer D) ? string : number;
type T63<T> = T extends ((infer A) extends infer B ? infer C : infer D) ? string : number;

type T70<T extends string> = { x: T };
type T71<T> = T extends T70<infer U> ? T70<U> : never;
Expand Down
4 changes: 2 additions & 2 deletions tests/baselines/reference/inferTypes1.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,9 @@ type T53 = X3<{ a: (x: number) => void, b: (x: string) => void }>; // never
type T54 = X3<{ a: (x: number) => void, b: () => void }>; // number

type T60 = infer U; // Error
type T61<T> = infer A extends infer B ? infer C : infer D; // Error
type T61<T> = (infer A) extends infer B ? infer C : infer D; // Error
type T62<T> = U extends (infer U)[] ? U : U; // Error
type T63<T> = T extends (infer A extends infer B ? infer C : infer D) ? string : number;
type T63<T> = T extends ((infer A) extends infer B ? infer C : infer D) ? string : number;

type T70<T extends string> = { x: T };
type T71<T> = T extends T70<infer U> ? T70<U> : never;
Expand Down
32 changes: 16 additions & 16 deletions tests/baselines/reference/inferTypes1.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -349,33 +349,33 @@ type T60 = infer U; // Error
>T60 : Symbol(T60, Decl(inferTypes1.ts, 79, 57))
>U : Symbol(U, Decl(inferTypes1.ts, 81, 16))

type T61<T> = infer A extends infer B ? infer C : infer D; // Error
type T61<T> = (infer A) extends infer B ? infer C : infer D; // Error
>T61 : Symbol(T61, Decl(inferTypes1.ts, 81, 19))
>T : Symbol(T, Decl(inferTypes1.ts, 82, 9))
>A : Symbol(A, Decl(inferTypes1.ts, 82, 19))
>B : Symbol(B, Decl(inferTypes1.ts, 82, 35))
>C : Symbol(C, Decl(inferTypes1.ts, 82, 45))
>D : Symbol(D, Decl(inferTypes1.ts, 82, 55))
>A : Symbol(A, Decl(inferTypes1.ts, 82, 20))
>B : Symbol(B, Decl(inferTypes1.ts, 82, 37))
>C : Symbol(C, Decl(inferTypes1.ts, 82, 47))
>D : Symbol(D, Decl(inferTypes1.ts, 82, 57))

type T62<T> = U extends (infer U)[] ? U : U; // Error
>T62 : Symbol(T62, Decl(inferTypes1.ts, 82, 58))
>T62 : Symbol(T62, Decl(inferTypes1.ts, 82, 60))
>T : Symbol(T, Decl(inferTypes1.ts, 83, 9))
>U : Symbol(U)
>U : Symbol(U, Decl(inferTypes1.ts, 83, 30))
>U : Symbol(U, Decl(inferTypes1.ts, 83, 30))
>U : Symbol(U)

type T63<T> = T extends (infer A extends infer B ? infer C : infer D) ? string : number;
type T63<T> = T extends ((infer A) extends infer B ? infer C : infer D) ? string : number;
>T63 : Symbol(T63, Decl(inferTypes1.ts, 83, 44))
>T : Symbol(T, Decl(inferTypes1.ts, 84, 9))
>T : Symbol(T, Decl(inferTypes1.ts, 84, 9))
>A : Symbol(A, Decl(inferTypes1.ts, 84, 30))
>B : Symbol(B, Decl(inferTypes1.ts, 84, 46))
>C : Symbol(C, Decl(inferTypes1.ts, 84, 56))
>D : Symbol(D, Decl(inferTypes1.ts, 84, 66))
>A : Symbol(A, Decl(inferTypes1.ts, 84, 31))
>B : Symbol(B, Decl(inferTypes1.ts, 84, 48))
>C : Symbol(C, Decl(inferTypes1.ts, 84, 58))
>D : Symbol(D, Decl(inferTypes1.ts, 84, 68))

type T70<T extends string> = { x: T };
>T70 : Symbol(T70, Decl(inferTypes1.ts, 84, 88))
>T70 : Symbol(T70, Decl(inferTypes1.ts, 84, 90))
>T : Symbol(T, Decl(inferTypes1.ts, 86, 9))
>x : Symbol(x, Decl(inferTypes1.ts, 86, 30))
>T : Symbol(T, Decl(inferTypes1.ts, 86, 9))
Expand All @@ -384,9 +384,9 @@ type T71<T> = T extends T70<infer U> ? T70<U> : never;
>T71 : Symbol(T71, Decl(inferTypes1.ts, 86, 38))
>T : Symbol(T, Decl(inferTypes1.ts, 87, 9))
>T : Symbol(T, Decl(inferTypes1.ts, 87, 9))
>T70 : Symbol(T70, Decl(inferTypes1.ts, 84, 88))
>T70 : Symbol(T70, Decl(inferTypes1.ts, 84, 90))
>U : Symbol(U, Decl(inferTypes1.ts, 87, 33))
>T70 : Symbol(T70, Decl(inferTypes1.ts, 84, 88))
>T70 : Symbol(T70, Decl(inferTypes1.ts, 84, 90))
>U : Symbol(U, Decl(inferTypes1.ts, 87, 33))

type T72<T extends number> = { y: T };
Expand All @@ -401,7 +401,7 @@ type T73<T> = T extends T72<infer U> ? T70<U> : never; // Error
>T : Symbol(T, Decl(inferTypes1.ts, 90, 9))
>T72 : Symbol(T72, Decl(inferTypes1.ts, 87, 54))
>U : Symbol(U, Decl(inferTypes1.ts, 90, 33))
>T70 : Symbol(T70, Decl(inferTypes1.ts, 84, 88))
>T70 : Symbol(T70, Decl(inferTypes1.ts, 84, 90))
>U : Symbol(U, Decl(inferTypes1.ts, 90, 33))

type T74<T extends number, U extends string> = { x: T, y: U };
Expand All @@ -420,7 +420,7 @@ type T75<T> = T extends T74<infer U, infer U> ? T70<U> | T72<U> | T74<U, U> : ne
>T74 : Symbol(T74, Decl(inferTypes1.ts, 90, 54))
>U : Symbol(U, Decl(inferTypes1.ts, 93, 33), Decl(inferTypes1.ts, 93, 42))
>U : Symbol(U, Decl(inferTypes1.ts, 93, 33), Decl(inferTypes1.ts, 93, 42))
>T70 : Symbol(T70, Decl(inferTypes1.ts, 84, 88))
>T70 : Symbol(T70, Decl(inferTypes1.ts, 84, 90))
>U : Symbol(U, Decl(inferTypes1.ts, 93, 33), Decl(inferTypes1.ts, 93, 42))
>T72 : Symbol(T72, Decl(inferTypes1.ts, 87, 54))
>U : Symbol(U, Decl(inferTypes1.ts, 93, 33), Decl(inferTypes1.ts, 93, 42))
Expand Down
4 changes: 2 additions & 2 deletions tests/baselines/reference/inferTypes1.types
Original file line number Diff line number Diff line change
Expand Up @@ -253,13 +253,13 @@ type T54 = X3<{ a: (x: number) => void, b: () => void }>; // number
type T60 = infer U; // Error
>T60 : U

type T61<T> = infer A extends infer B ? infer C : infer D; // Error
type T61<T> = (infer A) extends infer B ? infer C : infer D; // Error
>T61 : T61<T>

type T62<T> = U extends (infer U)[] ? U : U; // Error
>T62 : any

type T63<T> = T extends (infer A extends infer B ? infer C : infer D) ? string : number;
type T63<T> = T extends ((infer A) extends infer B ? infer C : infer D) ? string : number;
>T63 : T63<T>

type T70<T extends string> = { x: T };
Expand Down
2 changes: 1 addition & 1 deletion tests/baselines/reference/inferTypes2.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,5 @@ export declare function foo2<T>(obj: T): T extends {
[K in keyof BadNested<infer P>]: BadNested<infer P>[K];
} ? P : never;
export declare function bar2<T>(obj: T): T extends {
x: infer P extends number ? infer P : string;
x: (infer P) extends number ? infer P : string;
} ? P : never;
8 changes: 4 additions & 4 deletions tests/baselines/reference/inferTypes2.types
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,16 @@ export type BadNested<T> = { x: T extends number ? T : string };
>x : T extends number ? T : string

export declare function foo2<T>(obj: T): T extends { [K in keyof BadNested<infer P>]: BadNested<infer P>[K] } ? P : never;
>foo2 : <T>(obj: T) => T extends { x: infer P extends number ? infer P : string; } ? P : never
>foo2 : <T>(obj: T) => T extends { x: (infer P) extends number ? infer P : string; } ? P : never
>obj : T

export function bar2<T>(obj: T) {
>bar2 : <T>(obj: T) => T extends { x: infer P extends number ? infer P : string; } ? P : never
>bar2 : <T>(obj: T) => T extends { x: (infer P) extends number ? infer P : string; } ? P : never
>obj : T

return foo2(obj);
>foo2(obj) : T extends { x: infer P extends number ? infer P : string; } ? P : never
>foo2 : <T>(obj: T) => T extends { x: infer P extends number ? infer P : string; } ? P : never
>foo2(obj) : T extends { x: (infer P) extends number ? infer P : string; } ? P : never
>foo2 : <T>(obj: T) => T extends { x: (infer P) extends number ? infer P : string; } ? P : never
>obj : T
}

Expand Down
Loading

0 comments on commit bafe193

Please sign in to comment.