Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed local extends constraints of infer type parameters when they can contain type variables #60345

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 26 additions & 7 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19100,26 +19100,41 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// Conversely, if we have `Foo<infer A, infer B>`, `B` is still constrained to `T` and `T` is instantiated as `A`
// [2] Eg, if we have `Foo<T, U extends T>` and `Foo<Q, infer B>` 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 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);
}
if (!checkTypeDeferred) {
// 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;
Expand Down Expand Up @@ -19209,6 +19224,10 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
}
}

function maybeCloneInferTypeParameter(p: TypeParameter) {
return getConstraintDeclaration(p) && couldContainTypeVariables(getConstraintFromTypeParameter(p)!) ? cloneTypeParameter(p) : p;
}
Comment on lines +19227 to +19229
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this only clones type parameters with "local" constraints (the ones added by infer R extends C syntax)

It could do the same with all constraints, using this patch:

git diff
diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts
index a8803f5823..52f44bcebe 100644
--- a/src/compiler/checker.ts
+++ b/src/compiler/checker.ts
@@ -19117,9 +19117,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
                     }
                 }
                 const context = createInferenceContext(freshParams, /*signature*/ undefined, InferenceFlags.None);
-                if (mapper) {
-                    context.nonFixingMapper = combineTypeMappers(context.nonFixingMapper, mapper);
-                }
                 if (!checkTypeDeferred) {
                     // 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
@@ -19220,7 +19217,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
     }
 
     function maybeCloneInferTypeParameter(p: TypeParameter) {
-        return getConstraintDeclaration(p) && couldContainTypeVariables(getConstraintFromTypeParameter(p)!) ? cloneTypeParameter(p) : p;
+        const constraint = getConstraintFromTypeParameter(p);
+        return constraint && couldContainTypeVariables(constraint) ? cloneTypeParameter(p) : p;
     }
 
     function getTrueTypeFromConditionalType(type: ConditionalType) {
diff --git a/tests/baselines/reference/inferTypes1.types b/tests/baselines/reference/inferTypes1.types
index edc97a2ea1..f6018d54a3 100644
--- a/tests/baselines/reference/inferTypes1.types
+++ b/tests/baselines/reference/inferTypes1.types
@@ -1,5 +1,8 @@
 //// [tests/cases/conformance/types/conditional/inferTypes1.ts] ////
 
+=== Performance Stats ===
+Instantiation count: 1,000
+
 === inferTypes1.ts ===
 type Unpacked<T> =
 >Unpacked : Unpacked<T>

As we can see, we could drop the context.nonFixingMapper manipulation and simplify the logic here - but that would create more type parameter clones and that's even caught by this diff (Instantiation count was reported by one of the tests when using this patch)


function getTrueTypeFromConditionalType(type: ConditionalType) {
return type.resolvedTrueType || (type.resolvedTrueType = instantiateType(getTypeFromTypeNode(type.root.node.trueType), type.mapper));
}
Expand Down
51 changes: 51 additions & 0 deletions tests/baselines/reference/inferTypeParameterConstraints.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,57 @@ type U = inferTest<number, Constructor<number, Klass<number>>>;

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 any[]> = T extends [
...infer R extends [any, any],
...any[],
]
? R
: never;
type test1 = TestType1<Data>;

type TestType2<T extends any[], Mask extends any[] = [any, any]> = T extends [
...infer R extends Mask,
...any[],
]
? R
: never;
type test2 = TestType2<Data>;

type ExcludeRest<T extends any[]> = Inner<T>;

type Inner<
T extends any[],
Copy extends any[] = T,
Mask extends any[] = [],
> = Copy extends [any, ...infer Rest]
? Inner<T, Rest, [...Mask, any]>
: Required<Copy> extends [any, ...infer Rest]
? Inner<T, Rest, [...Mask, any?]>
: 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]
Expand Down
142 changes: 142 additions & 0 deletions tests/baselines/reference/inferTypeParameterConstraints.symbols
Original file line number Diff line number Diff line change
Expand Up @@ -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 any[]> = 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<Data>;
>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 any[], Mask extends any[] = [any, any]> = 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<Data>;
>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<T extends any[]> = Inner<T>;
>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<T, Rest, [...Mask, any]>
>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<Copy> 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<T, Rest, [...Mask, any?]>
>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))

82 changes: 82 additions & 0 deletions tests/baselines/reference/inferTypeParameterConstraints.types
Original file line number Diff line number Diff line change
Expand Up @@ -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 any[]> = T extends [
>TestType1 : TestType1<T>
> : ^^^^^^^^^^^^

...infer R extends [any, any],
...any[],
]
? R
: never;
type test1 = TestType1<Data>;
>test1 : [a: 1, b: 2]
> : ^^^^^^^^^^^^

type TestType2<T extends any[], Mask extends any[] = [any, any]> = T extends [
>TestType2 : TestType2<T, Mask>
> : ^^^^^^^^^^^^^^^^^^

...infer R extends Mask,
...any[],
]
? R
: never;
type test2 = TestType2<Data>;
>test2 : [a: 1, b: 2]
> : ^^^^^^^^^^^^

type ExcludeRest<T extends any[]> = Inner<T>;
>ExcludeRest : ExcludeRest<T>
> : ^^^^^^^^^^^^^^

type Inner<
>Inner : Inner<T, Copy, Mask>
> : ^^^^^^^^^^^^^^^^^^^^

T extends any[],
Copy extends any[] = T,
Mask extends any[] = [],
> = Copy extends [any, ...infer Rest]
? Inner<T, Rest, [...Mask, any]>
: Required<Copy> extends [any, ...infer Rest]
? Inner<T, Rest, [...Mask, any?]>
: 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, TOutput>
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

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, TOutput>
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

T extends string,
TOutput extends Interpolable,
> = T extends `${infer R extends TOutput}` ? R : never;
type ResultWithInterpolable2 = TestWithInterpolable2<`100`, number>;
>ResultWithInterpolable2 : 100
> : ^^^

Loading