From a02f3b393b76395a35a4fa24d582a1ea5aed50c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Sat, 26 Oct 2024 19:18:48 +0200 Subject: [PATCH 1/2] Fixed local `extends` constraints of infer type parameters when they can contain type variables --- src/compiler/checker.ts | 26 +++- .../inferTypeParameterConstraints.js | 51 +++++++ .../inferTypeParameterConstraints.symbols | 142 ++++++++++++++++++ .../inferTypeParameterConstraints.types | 82 ++++++++++ .../compiler/inferTypeParameterConstraints.ts | 51 +++++++ 5 files changed, 346 insertions(+), 6 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 2d373bc6ece4c..a8803f5823c23 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -19106,7 +19106,17 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // This means we have two mappers that need applying: // * The original `mapper` used to create this conditional // * The mapper that maps the infer type parameter to its inference result (`context.mapper`) - const context = createInferenceContext(root.inferTypeParameters, /*signature*/ undefined, InferenceFlags.None); + const freshParams = sameMap(root.inferTypeParameters, maybeCloneInferTypeParameter); + const freshMapper = freshParams !== root.inferTypeParameters ? createTypeMapper(root.inferTypeParameters, freshParams) : undefined; + if (freshMapper) { + const freshCombinedMapper = combineTypeMappers(mapper, freshMapper); + for (let i = 0; i < freshParams.length; i++) { + if (freshParams[i] !== root.inferTypeParameters[i]) { + freshParams[i].mapper = freshCombinedMapper; + } + } + } + const context = createInferenceContext(freshParams, /*signature*/ undefined, InferenceFlags.None); if (mapper) { context.nonFixingMapper = combineTypeMappers(context.nonFixingMapper, mapper); } @@ -19114,12 +19124,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // We don't want inferences from constraints as they may cause us to eagerly resolve the // conditional type instead of deferring resolution. Also, we always want strict function // types rules (i.e. proper contravariance) for inferences. - inferTypes(context.inferences, checkType, extendsType, InferencePriority.NoConstraints | InferencePriority.AlwaysStrict); + inferTypes(context.inferences, checkType, instantiateType(extendsType, freshMapper), InferencePriority.NoConstraints | InferencePriority.AlwaysStrict); } - // It's possible for 'infer T' type paramteters to be given uninstantiated constraints when the - // those type parameters are used in type references (see getInferredTypeParameterConstraint). For - // that reason we need context.mapper to be first in the combined mapper. See #42636 for examples. - combinedMapper = mapper ? combineTypeMappers(context.mapper, mapper) : context.mapper; + const innerMapper = combineTypeMappers(freshMapper, context.mapper); + // It's possible for 'infer T extends C' type parameters to be given uninstantiated constraints. For + // that reason we need context.mapper (and innerMapper contains that) to be first in the combined mapper. See #60299 for examples. + combinedMapper = mapper ? combineTypeMappers(innerMapper, mapper) : innerMapper; } // Instantiate the extends type including inferences for 'infer T' type parameters const inferredExtendsType = combinedMapper ? instantiateType(root.extendsType, combinedMapper) : extendsType; @@ -19209,6 +19219,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } + function maybeCloneInferTypeParameter(p: TypeParameter) { + return getConstraintDeclaration(p) && couldContainTypeVariables(getConstraintFromTypeParameter(p)!) ? cloneTypeParameter(p) : p; + } + function getTrueTypeFromConditionalType(type: ConditionalType) { return type.resolvedTrueType || (type.resolvedTrueType = instantiateType(getTypeFromTypeNode(type.root.node.trueType), type.mapper)); } diff --git a/tests/baselines/reference/inferTypeParameterConstraints.js b/tests/baselines/reference/inferTypeParameterConstraints.js index 5a53492f37cc5..a4e3a968ebfbe 100644 --- a/tests/baselines/reference/inferTypeParameterConstraints.js +++ b/tests/baselines/reference/inferTypeParameterConstraints.js @@ -38,6 +38,57 @@ type U = inferTest>>; declare let m: U; m.child; // ok + +// https://github.com/microsoft/TypeScript/issues/60299 + +type Data = [a: 1, b: 2, ...c: 3[]]; + +type TestType1 = T extends [ + ...infer R extends [any, any], + ...any[], +] + ? R + : never; +type test1 = TestType1; + +type TestType2 = T extends [ + ...infer R extends Mask, + ...any[], +] + ? R + : never; +type test2 = TestType2; + +type ExcludeRest = Inner; + +type Inner< + T extends any[], + Copy extends any[] = T, + Mask extends any[] = [], +> = Copy extends [any, ...infer Rest] + ? Inner + : Required extends [any, ...infer Rest] + ? Inner + : T extends [...infer Result extends Mask, ...any[]] + ? Result + : never; + +type test3 = ExcludeRest<[a: 1, b: 2, c?: 3, ...d: 4[]]>; + +type Interpolable = string | number | bigint | boolean | null | undefined; + +type TestWithInterpolable1< + T extends string, + TOutput extends Interpolable = number, +> = T extends `${infer R extends TOutput}` ? R : never; + +type ResultWithInterpolable1 = TestWithInterpolable1<`100`>; + +type TestWithInterpolable2< + T extends string, + TOutput extends Interpolable, +> = T extends `${infer R extends TOutput}` ? R : never; +type ResultWithInterpolable2 = TestWithInterpolable2<`100`, number>; //// [inferTypeParameterConstraints.js] diff --git a/tests/baselines/reference/inferTypeParameterConstraints.symbols b/tests/baselines/reference/inferTypeParameterConstraints.symbols index 75385598fc5ab..9702b60841383 100644 --- a/tests/baselines/reference/inferTypeParameterConstraints.symbols +++ b/tests/baselines/reference/inferTypeParameterConstraints.symbols @@ -109,3 +109,145 @@ m.child; // ok >m : Symbol(m, Decl(inferTypeParameterConstraints.ts, 35, 11)) >child : Symbol(Klass.child, Decl(inferTypeParameterConstraints.ts, 26, 37)) +// https://github.com/microsoft/TypeScript/issues/60299 + +type Data = [a: 1, b: 2, ...c: 3[]]; +>Data : Symbol(Data, Decl(inferTypeParameterConstraints.ts, 36, 8)) + +type TestType1 = T extends [ +>TestType1 : Symbol(TestType1, Decl(inferTypeParameterConstraints.ts, 40, 36)) +>T : Symbol(T, Decl(inferTypeParameterConstraints.ts, 42, 15)) +>T : Symbol(T, Decl(inferTypeParameterConstraints.ts, 42, 15)) + + ...infer R extends [any, any], +>R : Symbol(R, Decl(inferTypeParameterConstraints.ts, 43, 10)) + + ...any[], +] + ? R +>R : Symbol(R, Decl(inferTypeParameterConstraints.ts, 43, 10)) + + : never; +type test1 = TestType1; +>test1 : Symbol(test1, Decl(inferTypeParameterConstraints.ts, 47, 10)) +>TestType1 : Symbol(TestType1, Decl(inferTypeParameterConstraints.ts, 40, 36)) +>Data : Symbol(Data, Decl(inferTypeParameterConstraints.ts, 36, 8)) + +type TestType2 = T extends [ +>TestType2 : Symbol(TestType2, Decl(inferTypeParameterConstraints.ts, 48, 29)) +>T : Symbol(T, Decl(inferTypeParameterConstraints.ts, 50, 15)) +>Mask : Symbol(Mask, Decl(inferTypeParameterConstraints.ts, 50, 31)) +>T : Symbol(T, Decl(inferTypeParameterConstraints.ts, 50, 15)) + + ...infer R extends Mask, +>R : Symbol(R, Decl(inferTypeParameterConstraints.ts, 51, 10)) +>Mask : Symbol(Mask, Decl(inferTypeParameterConstraints.ts, 50, 31)) + + ...any[], +] + ? R +>R : Symbol(R, Decl(inferTypeParameterConstraints.ts, 51, 10)) + + : never; +type test2 = TestType2; +>test2 : Symbol(test2, Decl(inferTypeParameterConstraints.ts, 55, 10)) +>TestType2 : Symbol(TestType2, Decl(inferTypeParameterConstraints.ts, 48, 29)) +>Data : Symbol(Data, Decl(inferTypeParameterConstraints.ts, 36, 8)) + +type ExcludeRest = Inner; +>ExcludeRest : Symbol(ExcludeRest, Decl(inferTypeParameterConstraints.ts, 56, 29)) +>T : Symbol(T, Decl(inferTypeParameterConstraints.ts, 58, 17)) +>Inner : Symbol(Inner, Decl(inferTypeParameterConstraints.ts, 58, 45)) +>T : Symbol(T, Decl(inferTypeParameterConstraints.ts, 58, 17)) + +type Inner< +>Inner : Symbol(Inner, Decl(inferTypeParameterConstraints.ts, 58, 45)) + + T extends any[], +>T : Symbol(T, Decl(inferTypeParameterConstraints.ts, 60, 11)) + + Copy extends any[] = T, +>Copy : Symbol(Copy, Decl(inferTypeParameterConstraints.ts, 61, 18)) +>T : Symbol(T, Decl(inferTypeParameterConstraints.ts, 60, 11)) + + Mask extends any[] = [], +>Mask : Symbol(Mask, Decl(inferTypeParameterConstraints.ts, 62, 25)) + +> = Copy extends [any, ...infer Rest] +>Copy : Symbol(Copy, Decl(inferTypeParameterConstraints.ts, 61, 18)) +>Rest : Symbol(Rest, Decl(inferTypeParameterConstraints.ts, 64, 31)) + + ? Inner +>Inner : Symbol(Inner, Decl(inferTypeParameterConstraints.ts, 58, 45)) +>T : Symbol(T, Decl(inferTypeParameterConstraints.ts, 60, 11)) +>Rest : Symbol(Rest, Decl(inferTypeParameterConstraints.ts, 64, 31)) +>Mask : Symbol(Mask, Decl(inferTypeParameterConstraints.ts, 62, 25)) + + : Required extends [any, ...infer Rest] +>Required : Symbol(Required, Decl(lib.es5.d.ts, --, --)) +>Copy : Symbol(Copy, Decl(inferTypeParameterConstraints.ts, 61, 18)) +>Rest : Symbol(Rest, Decl(inferTypeParameterConstraints.ts, 66, 41)) + + ? Inner +>Inner : Symbol(Inner, Decl(inferTypeParameterConstraints.ts, 58, 45)) +>T : Symbol(T, Decl(inferTypeParameterConstraints.ts, 60, 11)) +>Rest : Symbol(Rest, Decl(inferTypeParameterConstraints.ts, 66, 41)) +>Mask : Symbol(Mask, Decl(inferTypeParameterConstraints.ts, 62, 25)) + + : T extends [...infer Result extends Mask, ...any[]] +>T : Symbol(T, Decl(inferTypeParameterConstraints.ts, 60, 11)) +>Result : Symbol(Result, Decl(inferTypeParameterConstraints.ts, 68, 23)) +>Mask : Symbol(Mask, Decl(inferTypeParameterConstraints.ts, 62, 25)) + + ? Result +>Result : Symbol(Result, Decl(inferTypeParameterConstraints.ts, 68, 23)) + + : never; + +type test3 = ExcludeRest<[a: 1, b: 2, c?: 3, ...d: 4[]]>; +>test3 : Symbol(test3, Decl(inferTypeParameterConstraints.ts, 70, 10)) +>ExcludeRest : Symbol(ExcludeRest, Decl(inferTypeParameterConstraints.ts, 56, 29)) + +type Interpolable = string | number | bigint | boolean | null | undefined; +>Interpolable : Symbol(Interpolable, Decl(inferTypeParameterConstraints.ts, 72, 57)) + +type TestWithInterpolable1< +>TestWithInterpolable1 : Symbol(TestWithInterpolable1, Decl(inferTypeParameterConstraints.ts, 74, 74)) + + T extends string, +>T : Symbol(T, Decl(inferTypeParameterConstraints.ts, 76, 27)) + + TOutput extends Interpolable = number, +>TOutput : Symbol(TOutput, Decl(inferTypeParameterConstraints.ts, 77, 19)) +>Interpolable : Symbol(Interpolable, Decl(inferTypeParameterConstraints.ts, 72, 57)) + +> = T extends `${infer R extends TOutput}` ? R : never; +>T : Symbol(T, Decl(inferTypeParameterConstraints.ts, 76, 27)) +>R : Symbol(R, Decl(inferTypeParameterConstraints.ts, 79, 22)) +>TOutput : Symbol(TOutput, Decl(inferTypeParameterConstraints.ts, 77, 19)) +>R : Symbol(R, Decl(inferTypeParameterConstraints.ts, 79, 22)) + +type ResultWithInterpolable1 = TestWithInterpolable1<`100`>; +>ResultWithInterpolable1 : Symbol(ResultWithInterpolable1, Decl(inferTypeParameterConstraints.ts, 79, 55)) +>TestWithInterpolable1 : Symbol(TestWithInterpolable1, Decl(inferTypeParameterConstraints.ts, 74, 74)) + +type TestWithInterpolable2< +>TestWithInterpolable2 : Symbol(TestWithInterpolable2, Decl(inferTypeParameterConstraints.ts, 81, 60)) + + T extends string, +>T : Symbol(T, Decl(inferTypeParameterConstraints.ts, 83, 27)) + + TOutput extends Interpolable, +>TOutput : Symbol(TOutput, Decl(inferTypeParameterConstraints.ts, 84, 19)) +>Interpolable : Symbol(Interpolable, Decl(inferTypeParameterConstraints.ts, 72, 57)) + +> = T extends `${infer R extends TOutput}` ? R : never; +>T : Symbol(T, Decl(inferTypeParameterConstraints.ts, 83, 27)) +>R : Symbol(R, Decl(inferTypeParameterConstraints.ts, 86, 22)) +>TOutput : Symbol(TOutput, Decl(inferTypeParameterConstraints.ts, 84, 19)) +>R : Symbol(R, Decl(inferTypeParameterConstraints.ts, 86, 22)) + +type ResultWithInterpolable2 = TestWithInterpolable2<`100`, number>; +>ResultWithInterpolable2 : Symbol(ResultWithInterpolable2, Decl(inferTypeParameterConstraints.ts, 86, 55)) +>TestWithInterpolable2 : Symbol(TestWithInterpolable2, Decl(inferTypeParameterConstraints.ts, 81, 60)) + diff --git a/tests/baselines/reference/inferTypeParameterConstraints.types b/tests/baselines/reference/inferTypeParameterConstraints.types index d3a97252cd98d..ebcdde48be5ef 100644 --- a/tests/baselines/reference/inferTypeParameterConstraints.types +++ b/tests/baselines/reference/inferTypeParameterConstraints.types @@ -90,3 +90,85 @@ m.child; // ok >child : boolean > : ^^^^^^^ +// https://github.com/microsoft/TypeScript/issues/60299 + +type Data = [a: 1, b: 2, ...c: 3[]]; +>Data : Data +> : ^^^^ + +type TestType1 = T extends [ +>TestType1 : TestType1 +> : ^^^^^^^^^^^^ + + ...infer R extends [any, any], + ...any[], +] + ? R + : never; +type test1 = TestType1; +>test1 : [a: 1, b: 2] +> : ^^^^^^^^^^^^ + +type TestType2 = T extends [ +>TestType2 : TestType2 +> : ^^^^^^^^^^^^^^^^^^ + + ...infer R extends Mask, + ...any[], +] + ? R + : never; +type test2 = TestType2; +>test2 : [a: 1, b: 2] +> : ^^^^^^^^^^^^ + +type ExcludeRest = Inner; +>ExcludeRest : ExcludeRest +> : ^^^^^^^^^^^^^^ + +type Inner< +>Inner : Inner +> : ^^^^^^^^^^^^^^^^^^^^ + + T extends any[], + Copy extends any[] = T, + Mask extends any[] = [], +> = Copy extends [any, ...infer Rest] + ? Inner + : Required extends [any, ...infer Rest] + ? Inner + : T extends [...infer Result extends Mask, ...any[]] + ? Result + : never; + +type test3 = ExcludeRest<[a: 1, b: 2, c?: 3, ...d: 4[]]>; +>test3 : [a: 1, b: 2, c?: 3 | undefined] +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +type Interpolable = string | number | bigint | boolean | null | undefined; +>Interpolable : Interpolable +> : ^^^^^^^^^^^^ + +type TestWithInterpolable1< +>TestWithInterpolable1 : TestWithInterpolable1 +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + T extends string, + TOutput extends Interpolable = number, +> = T extends `${infer R extends TOutput}` ? R : never; + +type ResultWithInterpolable1 = TestWithInterpolable1<`100`>; +>ResultWithInterpolable1 : 100 +> : ^^^ + +type TestWithInterpolable2< +>TestWithInterpolable2 : TestWithInterpolable2 +> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + T extends string, + TOutput extends Interpolable, +> = T extends `${infer R extends TOutput}` ? R : never; +type ResultWithInterpolable2 = TestWithInterpolable2<`100`, number>; +>ResultWithInterpolable2 : 100 +> : ^^^ + diff --git a/tests/cases/compiler/inferTypeParameterConstraints.ts b/tests/cases/compiler/inferTypeParameterConstraints.ts index 92299e38d80dd..7b7b95864cc4c 100644 --- a/tests/cases/compiler/inferTypeParameterConstraints.ts +++ b/tests/cases/compiler/inferTypeParameterConstraints.ts @@ -37,3 +37,54 @@ type U = inferTest>>; declare let m: U; m.child; // ok + +// https://github.com/microsoft/TypeScript/issues/60299 + +type Data = [a: 1, b: 2, ...c: 3[]]; + +type TestType1 = T extends [ + ...infer R extends [any, any], + ...any[], +] + ? R + : never; +type test1 = TestType1; + +type TestType2 = T extends [ + ...infer R extends Mask, + ...any[], +] + ? R + : never; +type test2 = TestType2; + +type ExcludeRest = Inner; + +type Inner< + T extends any[], + Copy extends any[] = T, + Mask extends any[] = [], +> = Copy extends [any, ...infer Rest] + ? Inner + : Required extends [any, ...infer Rest] + ? Inner + : T extends [...infer Result extends Mask, ...any[]] + ? Result + : never; + +type test3 = ExcludeRest<[a: 1, b: 2, c?: 3, ...d: 4[]]>; + +type Interpolable = string | number | bigint | boolean | null | undefined; + +type TestWithInterpolable1< + T extends string, + TOutput extends Interpolable = number, +> = T extends `${infer R extends TOutput}` ? R : never; + +type ResultWithInterpolable1 = TestWithInterpolable1<`100`>; + +type TestWithInterpolable2< + T extends string, + TOutput extends Interpolable, +> = T extends `${infer R extends TOutput}` ? R : never; +type ResultWithInterpolable2 = TestWithInterpolable2<`100`, number>; From 7cf0d7c708d4baec70fd19075150657ac9a150bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Sat, 26 Oct 2024 20:25:14 +0200 Subject: [PATCH 2/2] add comments --- src/compiler/checker.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a8803f5823c23..713942d901be2 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -19100,11 +19100,16 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // Conversely, if we have `Foo`, `B` is still constrained to `T` and `T` is instantiated as `A` // [2] Eg, if we have `Foo` and `Foo` where `Q` is mapped by `mapper` into `number` - `B` is constrained to `T` // which is in turn instantiated as `Q`, which is in turn instantiated as `number`. + // [3] Eq, if we have `T extends `${infer R extends TOutput}` ? R : never` where `TOutput` is be mapped by `mapper` into `number` + // the R`s constraint has to be instantiated by `mapper` as that can influence inferences made for it // So we need to: + /// * clone the infer type parameters with local `extends` constraints + // * set the clones to both map the conditional's enclosing `mapper` and the original params // * combine `context.nonFixingMapper` with `mapper` so their constraints can be instantiated in the context of `mapper` (otherwise they'd only get inference context information) // * incorporate all of the component mappers into the combined mapper for the true and false members - // This means we have two mappers that need applying: + // This means we have three mappers that need applying: // * The original `mapper` used to create this conditional + // * The mapper that maps the old root type parameter to the clone (`freshMapper`) // * The mapper that maps the infer type parameter to its inference result (`context.mapper`) const freshParams = sameMap(root.inferTypeParameters, maybeCloneInferTypeParameter); const freshMapper = freshParams !== root.inferTypeParameters ? createTypeMapper(root.inferTypeParameters, freshParams) : undefined;