From 2e4fa4f098ac2377bc02cf47e957cdd5a7725e51 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Wed, 23 Mar 2022 21:00:18 -0700 Subject: [PATCH] Emit extends clause for synthetic infer typesduring declaration emit --- src/compiler/checker.ts | 19 ++++++++++-- .../reference/inferTypesWithExtends1.js | 23 +++++++++++++- .../reference/inferTypesWithExtends1.symbols | 31 +++++++++++++++++++ .../reference/inferTypesWithExtends1.types | 22 +++++++++++++ .../conditional/inferTypesWithExtends1.ts | 11 +++++++ 5 files changed, 102 insertions(+), 4 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 45d88e4b8248f..84af383d3df7c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4977,7 +4977,20 @@ namespace ts { if (type.flags & TypeFlags.TypeParameter || objectFlags & ObjectFlags.ClassOrInterface) { if (type.flags & TypeFlags.TypeParameter && contains(context.inferTypeParameters, type)) { context.approximateLength += (symbolName(type.symbol).length + 6); - return factory.createInferTypeNode(typeParameterToDeclarationWithConstraint(type as TypeParameter, context, /*constraintNode*/ undefined)); + let constraintNode: TypeNode | undefined; + const constraint = getConstraintOfTypeParameter(type as TypeParameter); + if (constraint) { + // If the infer type has a constraint that is not the same as the constraint + // we would have normally inferred based on context, we emit the constraint + // using `infer T extends ?`. We omit inferred constraints from type references + // as they may be elided. + const inferredConstraint = getInferredTypeParameterConstraint(type as TypeParameter, /*omitTypeReferences*/ true); + if (!(inferredConstraint && isTypeIdenticalTo(constraint, inferredConstraint))) { + context.approximateLength += 9; + constraintNode = constraint && typeToTypeNodeHelper(constraint, context); + } + } + return factory.createInferTypeNode(typeParameterToDeclarationWithConstraint(type as TypeParameter, context, constraintNode)); } if (context.flags & NodeBuilderFlags.GenerateNamesForShadowedTypeParams && type.flags & TypeFlags.TypeParameter && @@ -13259,7 +13272,7 @@ namespace ts { return mapDefined(filter(type.symbol && type.symbol.declarations, isTypeParameterDeclaration), getEffectiveConstraintOfTypeParameter)[0]; } - function getInferredTypeParameterConstraint(typeParameter: TypeParameter) { + function getInferredTypeParameterConstraint(typeParameter: TypeParameter, omitTypeReferences?: boolean) { let inferences: Type[] | undefined; if (typeParameter.symbol?.declarations) { for (const declaration of typeParameter.symbol.declarations) { @@ -13269,7 +13282,7 @@ namespace ts { // corresponding type parameter in 'Foo'. When multiple 'infer T' declarations are // present, we form an intersection of the inferred constraint types. const [childTypeParameter = declaration.parent, grandParent] = walkUpParenthesizedTypesAndGetParentAndChild(declaration.parent.parent); - if (grandParent.kind === SyntaxKind.TypeReference) { + if (grandParent.kind === SyntaxKind.TypeReference && !omitTypeReferences) { const typeReference = grandParent as TypeReferenceNode; const typeParameters = getTypeParametersForTypeReference(typeReference); if (typeParameters) { diff --git a/tests/baselines/reference/inferTypesWithExtends1.js b/tests/baselines/reference/inferTypesWithExtends1.js index de26103c44480..6ce0739872373 100644 --- a/tests/baselines/reference/inferTypesWithExtends1.js +++ b/tests/baselines/reference/inferTypesWithExtends1.js @@ -123,10 +123,26 @@ type X21_T5 = X21<1 | 2, 3>; // never // from mongoose type IfEquals = (() => T extends X ? 1 : 2) extends () => T extends Y ? 1 : 2 ? A : B; - + +declare const x1: () => (T extends infer U extends number ? 1 : 0); +function f1() { + return x1; +} + +type ExpectNumber = T; +declare const x2: () => (T extends ExpectNumber ? 1 : 0); +function f2() { + return x2; +} //// [inferTypesWithExtends1.js] "use strict"; +function f1() { + return x1; +} +function f2() { + return x2; +} //// [inferTypesWithExtends1.d.ts] @@ -275,3 +291,8 @@ declare type X21_T3 = X21<1 | 2, 1>; declare type X21_T4 = X21<1 | 2, 2 | 3>; declare type X21_T5 = X21<1 | 2, 3>; declare type IfEquals = (() => T extends X ? 1 : 2) extends () => T extends Y ? 1 : 2 ? A : B; +declare const x1: () => (T extends infer U extends number ? 1 : 0); +declare function f1(): () => T extends infer U extends number ? 1 : 0; +declare type ExpectNumber = T; +declare const x2: () => (T extends ExpectNumber ? 1 : 0); +declare function f2(): () => T extends infer U extends number ? 1 : 0; diff --git a/tests/baselines/reference/inferTypesWithExtends1.symbols b/tests/baselines/reference/inferTypesWithExtends1.symbols index 82fd31e4a4a8c..01cdc578af5e6 100644 --- a/tests/baselines/reference/inferTypesWithExtends1.symbols +++ b/tests/baselines/reference/inferTypesWithExtends1.symbols @@ -498,3 +498,34 @@ type IfEquals = (() => T extends X ? 1 : 2) extends () => T ex >A : Symbol(A, Decl(inferTypesWithExtends1.ts, 123, 19)) >B : Symbol(B, Decl(inferTypesWithExtends1.ts, 123, 22)) +declare const x1: () => (T extends infer U extends number ? 1 : 0); +>x1 : Symbol(x1, Decl(inferTypesWithExtends1.ts, 125, 13)) +>T : Symbol(T, Decl(inferTypesWithExtends1.ts, 125, 19)) +>T : Symbol(T, Decl(inferTypesWithExtends1.ts, 125, 19)) +>U : Symbol(U, Decl(inferTypesWithExtends1.ts, 125, 43)) + +function f1() { +>f1 : Symbol(f1, Decl(inferTypesWithExtends1.ts, 125, 70)) + + return x1; +>x1 : Symbol(x1, Decl(inferTypesWithExtends1.ts, 125, 13)) +} + +type ExpectNumber = T; +>ExpectNumber : Symbol(ExpectNumber, Decl(inferTypesWithExtends1.ts, 128, 1)) +>T : Symbol(T, Decl(inferTypesWithExtends1.ts, 130, 18)) +>T : Symbol(T, Decl(inferTypesWithExtends1.ts, 130, 18)) + +declare const x2: () => (T extends ExpectNumber ? 1 : 0); +>x2 : Symbol(x2, Decl(inferTypesWithExtends1.ts, 131, 13)) +>T : Symbol(T, Decl(inferTypesWithExtends1.ts, 131, 19)) +>T : Symbol(T, Decl(inferTypesWithExtends1.ts, 131, 19)) +>ExpectNumber : Symbol(ExpectNumber, Decl(inferTypesWithExtends1.ts, 128, 1)) +>U : Symbol(U, Decl(inferTypesWithExtends1.ts, 131, 56)) + +function f2() { +>f2 : Symbol(f2, Decl(inferTypesWithExtends1.ts, 131, 69)) + + return x2; +>x2 : Symbol(x2, Decl(inferTypesWithExtends1.ts, 131, 13)) +} diff --git a/tests/baselines/reference/inferTypesWithExtends1.types b/tests/baselines/reference/inferTypesWithExtends1.types index 83fb55727e0a9..b26386d0f4902 100644 --- a/tests/baselines/reference/inferTypesWithExtends1.types +++ b/tests/baselines/reference/inferTypesWithExtends1.types @@ -307,3 +307,25 @@ type X21_T5 = X21<1 | 2, 3>; // never type IfEquals = (() => T extends X ? 1 : 2) extends () => T extends Y ? 1 : 2 ? A : B; >IfEquals : IfEquals +declare const x1: () => (T extends infer U extends number ? 1 : 0); +>x1 : () => T extends infer U extends number ? 1 : 0 + +function f1() { +>f1 : () => () => T extends infer U extends number ? 1 : 0 + + return x1; +>x1 : () => T extends infer U extends number ? 1 : 0 +} + +type ExpectNumber = T; +>ExpectNumber : T + +declare const x2: () => (T extends ExpectNumber ? 1 : 0); +>x2 : () => T extends infer U extends number ? 1 : 0 + +function f2() { +>f2 : () => () => T extends infer U extends number ? 1 : 0 + + return x2; +>x2 : () => T extends infer U extends number ? 1 : 0 +} diff --git a/tests/cases/conformance/types/conditional/inferTypesWithExtends1.ts b/tests/cases/conformance/types/conditional/inferTypesWithExtends1.ts index 67a411fad2703..64034a62cb667 100644 --- a/tests/cases/conformance/types/conditional/inferTypesWithExtends1.ts +++ b/tests/cases/conformance/types/conditional/inferTypesWithExtends1.ts @@ -125,3 +125,14 @@ type X21_T5 = X21<1 | 2, 3>; // never // from mongoose type IfEquals = (() => T extends X ? 1 : 2) extends () => T extends Y ? 1 : 2 ? A : B; + +declare const x1: () => (T extends infer U extends number ? 1 : 0); +function f1() { + return x1; +} + +type ExpectNumber = T; +declare const x2: () => (T extends ExpectNumber ? 1 : 0); +function f2() { + return x2; +} \ No newline at end of file