Skip to content

Commit feb57c2

Browse files
authored
Instantiate earlier inferred constraints in conditional types (#57362)
1 parent 91e67ff commit feb57c2

File tree

5 files changed

+182
-25
lines changed

5 files changed

+182
-25
lines changed

Diff for: src/compiler/checker.ts

+9-25
Original file line numberDiff line numberDiff line change
@@ -18642,11 +18642,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1864218642
return type;
1864318643
}
1864418644

18645-
function maybeCloneTypeParameter(p: TypeParameter) {
18646-
const constraint = getConstraintOfTypeParameter(p);
18647-
return constraint && (isGenericObjectType(constraint) || isGenericIndexType(constraint)) ? cloneTypeParameter(p) : p;
18648-
}
18649-
1865018645
function isSimpleTupleType(node: TypeNode): boolean {
1865118646
return isTupleTypeNode(node) && length(node.elements) > 0 &&
1865218647
!some(node.elements, e => isOptionalTypeNode(e) || isRestTypeNode(e) || isNamedTupleMember(e) && !!(e.questionToken || e.dotDotDotToken));
@@ -18689,44 +18684,33 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
1868918684
let combinedMapper: TypeMapper | undefined;
1869018685
if (root.inferTypeParameters) {
1869118686
// When we're looking at making an inference for an infer type, when we get its constraint, it'll automagically be
18692-
// instantiated with the context, so it doesn't need the mapper for the inference contex - however the constraint
18687+
// instantiated with the context, so it doesn't need the mapper for the inference context - however the constraint
1869318688
// may refer to another _root_, _uncloned_ `infer` type parameter [1], or to something mapped by `mapper` [2].
1869418689
// [1] Eg, if we have `Foo<T, U extends T>` and `Foo<number, infer B>` - `B` is constrained to `T`, which, in turn, has been instantiated
1869518690
// as `number`
1869618691
// Conversely, if we have `Foo<infer A, infer B>`, `B` is still constrained to `T` and `T` is instantiated as `A`
1869718692
// [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`
1869818693
// which is in turn instantiated as `Q`, which is in turn instantiated as `number`.
1869918694
// So we need to:
18700-
// * Clone the type parameters so their constraints can be instantiated in the context of `mapper` (otherwise theyd only get inference context information)
18701-
// * Set the clones to both map the conditional's enclosing `mapper` and the original params
18702-
// * instantiate the extends type with the clones
18695+
// * combine `context.nonFixingMapper` with `mapper` so their constraints can be instantiated in the context of `mapper` (otherwise they'd only get inference context information)
1870318696
// * incorporate all of the component mappers into the combined mapper for the true and false members
18704-
// This means we have three mappers that need applying:
18697+
// This means we have two mappers that need applying:
1870518698
// * The original `mapper` used to create this conditional
18706-
// * The mapper that maps the old root type parameter to the clone (`freshMapper`)
18707-
// * The mapper that maps the clone to its inference result (`context.mapper`)
18708-
const freshParams = sameMap(root.inferTypeParameters, maybeCloneTypeParameter);
18709-
const freshMapper = freshParams !== root.inferTypeParameters ? createTypeMapper(root.inferTypeParameters, freshParams) : undefined;
18710-
const context = createInferenceContext(freshParams, /*signature*/ undefined, InferenceFlags.None);
18711-
if (freshMapper) {
18712-
const freshCombinedMapper = combineTypeMappers(mapper, freshMapper);
18713-
for (let i = 0; i < freshParams.length; i++) {
18714-
if (freshParams[i] !== root.inferTypeParameters[i]) {
18715-
freshParams[i].mapper = freshCombinedMapper;
18716-
}
18717-
}
18699+
// * The mapper that maps the infer type parameter to its inference result (`context.mapper`)
18700+
const context = createInferenceContext(root.inferTypeParameters, /*signature*/ undefined, InferenceFlags.None);
18701+
if (mapper) {
18702+
context.nonFixingMapper = combineTypeMappers(context.nonFixingMapper, mapper);
1871818703
}
1871918704
if (!checkTypeDeferred) {
1872018705
// We don't want inferences from constraints as they may cause us to eagerly resolve the
1872118706
// conditional type instead of deferring resolution. Also, we always want strict function
1872218707
// types rules (i.e. proper contravariance) for inferences.
18723-
inferTypes(context.inferences, checkType, instantiateType(extendsType, freshMapper), InferencePriority.NoConstraints | InferencePriority.AlwaysStrict);
18708+
inferTypes(context.inferences, checkType, extendsType, InferencePriority.NoConstraints | InferencePriority.AlwaysStrict);
1872418709
}
18725-
const innerMapper = combineTypeMappers(freshMapper, context.mapper);
1872618710
// It's possible for 'infer T' type paramteters to be given uninstantiated constraints when the
1872718711
// those type parameters are used in type references (see getInferredTypeParameterConstraint). For
1872818712
// that reason we need context.mapper to be first in the combined mapper. See #42636 for examples.
18729-
combinedMapper = mapper ? combineTypeMappers(innerMapper, mapper) : innerMapper;
18713+
combinedMapper = mapper ? combineTypeMappers(context.mapper, mapper) : context.mapper;
1873018714
}
1873118715
// Instantiate the extends type including inferences for 'infer T' type parameters
1873218716
const inferredExtendsType = combinedMapper ? instantiateType(root.extendsType, combinedMapper) : extendsType;

Diff for: tests/baselines/reference/inferTypeParameterConstraints.js

+54
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,62 @@ type Constrain<T extends C, C> = unknown;
1818
type Foo<A> = A extends Constrain<infer X, A> ? X : never;
1919

2020
type T0 = Foo<string>; // string
21+
22+
// https://github.com/microsoft/TypeScript/issues/57286#issuecomment-1927920336
23+
24+
class BaseClass<V> {
25+
protected fake(): V {
26+
throw new Error("");
27+
}
28+
}
29+
30+
class Klass<V> extends BaseClass<V> {
31+
child = true;
32+
}
33+
34+
type Constructor<V, P extends BaseClass<V>> = new () => P;
35+
type inferTest<V, T> = T extends Constructor<V, infer P> ? P : never;
36+
37+
type U = inferTest<number, Constructor<number, Klass<number>>>;
38+
39+
declare let m: U;
40+
m.child; // ok
2141

2242

2343
//// [inferTypeParameterConstraints.js]
2444
"use strict";
2545
// Repro from #42636
46+
var __extends = (this && this.__extends) || (function () {
47+
var extendStatics = function (d, b) {
48+
extendStatics = Object.setPrototypeOf ||
49+
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
50+
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
51+
return extendStatics(d, b);
52+
};
53+
return function (d, b) {
54+
if (typeof b !== "function" && b !== null)
55+
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
56+
extendStatics(d, b);
57+
function __() { this.constructor = d; }
58+
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
59+
};
60+
})();
61+
// https://github.com/microsoft/TypeScript/issues/57286#issuecomment-1927920336
62+
var BaseClass = /** @class */ (function () {
63+
function BaseClass() {
64+
}
65+
BaseClass.prototype.fake = function () {
66+
throw new Error("");
67+
};
68+
return BaseClass;
69+
}());
70+
var Klass = /** @class */ (function (_super) {
71+
__extends(Klass, _super);
72+
function Klass() {
73+
var _this = _super !== null && _super.apply(this, arguments) || this;
74+
_this.child = true;
75+
return _this;
76+
}
77+
return Klass;
78+
}(BaseClass));
79+
m.child; // ok

Diff for: tests/baselines/reference/inferTypeParameterConstraints.symbols

+58
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,61 @@ type T0 = Foo<string>; // string
5151
>T0 : Symbol(T0, Decl(inferTypeParameterConstraints.ts, 14, 58))
5252
>Foo : Symbol(Foo, Decl(inferTypeParameterConstraints.ts, 12, 41))
5353

54+
// https://github.com/microsoft/TypeScript/issues/57286#issuecomment-1927920336
55+
56+
class BaseClass<V> {
57+
>BaseClass : Symbol(BaseClass, Decl(inferTypeParameterConstraints.ts, 16, 22))
58+
>V : Symbol(V, Decl(inferTypeParameterConstraints.ts, 20, 16))
59+
60+
protected fake(): V {
61+
>fake : Symbol(BaseClass.fake, Decl(inferTypeParameterConstraints.ts, 20, 20))
62+
>V : Symbol(V, Decl(inferTypeParameterConstraints.ts, 20, 16))
63+
64+
throw new Error("");
65+
>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
66+
}
67+
}
68+
69+
class Klass<V> extends BaseClass<V> {
70+
>Klass : Symbol(Klass, Decl(inferTypeParameterConstraints.ts, 24, 1))
71+
>V : Symbol(V, Decl(inferTypeParameterConstraints.ts, 26, 12))
72+
>BaseClass : Symbol(BaseClass, Decl(inferTypeParameterConstraints.ts, 16, 22))
73+
>V : Symbol(V, Decl(inferTypeParameterConstraints.ts, 26, 12))
74+
75+
child = true;
76+
>child : Symbol(Klass.child, Decl(inferTypeParameterConstraints.ts, 26, 37))
77+
}
78+
79+
type Constructor<V, P extends BaseClass<V>> = new () => P;
80+
>Constructor : Symbol(Constructor, Decl(inferTypeParameterConstraints.ts, 28, 1))
81+
>V : Symbol(V, Decl(inferTypeParameterConstraints.ts, 30, 17))
82+
>P : Symbol(P, Decl(inferTypeParameterConstraints.ts, 30, 19))
83+
>BaseClass : Symbol(BaseClass, Decl(inferTypeParameterConstraints.ts, 16, 22))
84+
>V : Symbol(V, Decl(inferTypeParameterConstraints.ts, 30, 17))
85+
>P : Symbol(P, Decl(inferTypeParameterConstraints.ts, 30, 19))
86+
87+
type inferTest<V, T> = T extends Constructor<V, infer P> ? P : never;
88+
>inferTest : Symbol(inferTest, Decl(inferTypeParameterConstraints.ts, 30, 58))
89+
>V : Symbol(V, Decl(inferTypeParameterConstraints.ts, 31, 15))
90+
>T : Symbol(T, Decl(inferTypeParameterConstraints.ts, 31, 17))
91+
>T : Symbol(T, Decl(inferTypeParameterConstraints.ts, 31, 17))
92+
>Constructor : Symbol(Constructor, Decl(inferTypeParameterConstraints.ts, 28, 1))
93+
>V : Symbol(V, Decl(inferTypeParameterConstraints.ts, 31, 15))
94+
>P : Symbol(P, Decl(inferTypeParameterConstraints.ts, 31, 53))
95+
>P : Symbol(P, Decl(inferTypeParameterConstraints.ts, 31, 53))
96+
97+
type U = inferTest<number, Constructor<number, Klass<number>>>;
98+
>U : Symbol(U, Decl(inferTypeParameterConstraints.ts, 31, 69))
99+
>inferTest : Symbol(inferTest, Decl(inferTypeParameterConstraints.ts, 30, 58))
100+
>Constructor : Symbol(Constructor, Decl(inferTypeParameterConstraints.ts, 28, 1))
101+
>Klass : Symbol(Klass, Decl(inferTypeParameterConstraints.ts, 24, 1))
102+
103+
declare let m: U;
104+
>m : Symbol(m, Decl(inferTypeParameterConstraints.ts, 35, 11))
105+
>U : Symbol(U, Decl(inferTypeParameterConstraints.ts, 31, 69))
106+
107+
m.child; // ok
108+
>m.child : Symbol(Klass.child, Decl(inferTypeParameterConstraints.ts, 26, 37))
109+
>m : Symbol(m, Decl(inferTypeParameterConstraints.ts, 35, 11))
110+
>child : Symbol(Klass.child, Decl(inferTypeParameterConstraints.ts, 26, 37))
111+

Diff for: tests/baselines/reference/inferTypeParameterConstraints.types

+41
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,44 @@ type Foo<A> = A extends Constrain<infer X, A> ? X : never;
2626
type T0 = Foo<string>; // string
2727
>T0 : string
2828

29+
// https://github.com/microsoft/TypeScript/issues/57286#issuecomment-1927920336
30+
31+
class BaseClass<V> {
32+
>BaseClass : BaseClass<V>
33+
34+
protected fake(): V {
35+
>fake : () => V
36+
37+
throw new Error("");
38+
>new Error("") : Error
39+
>Error : ErrorConstructor
40+
>"" : ""
41+
}
42+
}
43+
44+
class Klass<V> extends BaseClass<V> {
45+
>Klass : Klass<V>
46+
>BaseClass : BaseClass<V>
47+
48+
child = true;
49+
>child : boolean
50+
>true : true
51+
}
52+
53+
type Constructor<V, P extends BaseClass<V>> = new () => P;
54+
>Constructor : Constructor<V, P>
55+
56+
type inferTest<V, T> = T extends Constructor<V, infer P> ? P : never;
57+
>inferTest : inferTest<V, T>
58+
59+
type U = inferTest<number, Constructor<number, Klass<number>>>;
60+
>U : Klass<number>
61+
62+
declare let m: U;
63+
>m : Klass<number>
64+
65+
m.child; // ok
66+
>m.child : boolean
67+
>m : Klass<number>
68+
>child : boolean
69+

Diff for: tests/cases/compiler/inferTypeParameterConstraints.ts

+20
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,23 @@ type Constrain<T extends C, C> = unknown;
1717
type Foo<A> = A extends Constrain<infer X, A> ? X : never;
1818

1919
type T0 = Foo<string>; // string
20+
21+
// https://github.com/microsoft/TypeScript/issues/57286#issuecomment-1927920336
22+
23+
class BaseClass<V> {
24+
protected fake(): V {
25+
throw new Error("");
26+
}
27+
}
28+
29+
class Klass<V> extends BaseClass<V> {
30+
child = true;
31+
}
32+
33+
type Constructor<V, P extends BaseClass<V>> = new () => P;
34+
type inferTest<V, T> = T extends Constructor<V, infer P> ? P : never;
35+
36+
type U = inferTest<number, Constructor<number, Klass<number>>>;
37+
38+
declare let m: U;
39+
m.child; // ok

0 commit comments

Comments
 (0)