From 1148bcedb19ad7a8eb8a853f6b2a683ab15e18b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Burzy=C5=84ski?= Date: Fri, 9 Feb 2024 23:57:49 +0100 Subject: [PATCH] Instantiate earlier inferred constraints in conditional types --- src/compiler/checker.ts | 34 +++-------- .../inferTypeParameterConstraints.js | 54 +++++++++++++++++ .../inferTypeParameterConstraints.symbols | 58 +++++++++++++++++++ .../inferTypeParameterConstraints.types | 41 +++++++++++++ .../compiler/inferTypeParameterConstraints.ts | 20 +++++++ 5 files changed, 182 insertions(+), 25 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 91a5c09454b09..332389e3e8b6b 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -18636,11 +18636,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { return type; } - function maybeCloneTypeParameter(p: TypeParameter) { - const constraint = getConstraintOfTypeParameter(p); - return constraint && (isGenericObjectType(constraint) || isGenericIndexType(constraint)) ? cloneTypeParameter(p) : p; - } - function isSimpleTupleType(node: TypeNode): boolean { return isTupleTypeNode(node) && length(node.elements) > 0 && !some(node.elements, e => isOptionalTypeNode(e) || isRestTypeNode(e) || isNamedTupleMember(e) && !!(e.questionToken || e.dotDotDotToken)); @@ -18683,7 +18678,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { let combinedMapper: TypeMapper | undefined; if (root.inferTypeParameters) { // When we're looking at making an inference for an infer type, when we get its constraint, it'll automagically be - // instantiated with the context, so it doesn't need the mapper for the inference contex - however the constraint + // instantiated with the context, so it doesn't need the mapper for the inference context - however the constraint // may refer to another _root_, _uncloned_ `infer` type parameter [1], or to something mapped by `mapper` [2]. // [1] Eg, if we have `Foo` and `Foo` - `B` is constrained to `T`, which, in turn, has been instantiated // as `number` @@ -18691,36 +18686,25 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { // [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`. // So we need to: - // * Clone the type parameters so their constraints can be instantiated in the context of `mapper` (otherwise theyd only get inference context information) - // * Set the clones to both map the conditional's enclosing `mapper` and the original params - // * instantiate the extends type with the clones + // * 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 three mappers that need applying: + // This means we have two 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 clone to its inference result (`context.mapper`) - const freshParams = sameMap(root.inferTypeParameters, maybeCloneTypeParameter); - const freshMapper = freshParams !== root.inferTypeParameters ? createTypeMapper(root.inferTypeParameters, freshParams) : undefined; - const context = createInferenceContext(freshParams, /*signature*/ undefined, InferenceFlags.None); - 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; - } - } + // * The mapper that maps the infer type parameter to its inference result (`context.mapper`) + const context = createInferenceContext(root.inferTypeParameters, /*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, instantiateType(extendsType, freshMapper), InferencePriority.NoConstraints | InferencePriority.AlwaysStrict); + inferTypes(context.inferences, checkType, extendsType, InferencePriority.NoConstraints | InferencePriority.AlwaysStrict); } - const innerMapper = combineTypeMappers(freshMapper, context.mapper); // 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(innerMapper, mapper) : innerMapper; + combinedMapper = mapper ? combineTypeMappers(context.mapper, mapper) : context.mapper; } // Instantiate the extends type including inferences for 'infer T' type parameters const inferredExtendsType = combinedMapper ? instantiateType(root.extendsType, combinedMapper) : extendsType; diff --git a/tests/baselines/reference/inferTypeParameterConstraints.js b/tests/baselines/reference/inferTypeParameterConstraints.js index 3d1dc520dee8e..5a53492f37cc5 100644 --- a/tests/baselines/reference/inferTypeParameterConstraints.js +++ b/tests/baselines/reference/inferTypeParameterConstraints.js @@ -18,8 +18,62 @@ type Constrain = unknown; type Foo = A extends Constrain ? X : never; type T0 = Foo; // string + +// https://github.com/microsoft/TypeScript/issues/57286#issuecomment-1927920336 + +class BaseClass { + protected fake(): V { + throw new Error(""); + } +} + +class Klass extends BaseClass { + child = true; +} + +type Constructor> = new () => P; +type inferTest = T extends Constructor ? P : never; + +type U = inferTest>>; + +declare let m: U; +m.child; // ok //// [inferTypeParameterConstraints.js] "use strict"; // Repro from #42636 +var __extends = (this && this.__extends) || (function () { + var extendStatics = function (d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; + return extendStatics(d, b); + }; + return function (d, b) { + if (typeof b !== "function" && b !== null) + throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + }; +})(); +// https://github.com/microsoft/TypeScript/issues/57286#issuecomment-1927920336 +var BaseClass = /** @class */ (function () { + function BaseClass() { + } + BaseClass.prototype.fake = function () { + throw new Error(""); + }; + return BaseClass; +}()); +var Klass = /** @class */ (function (_super) { + __extends(Klass, _super); + function Klass() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.child = true; + return _this; + } + return Klass; +}(BaseClass)); +m.child; // ok diff --git a/tests/baselines/reference/inferTypeParameterConstraints.symbols b/tests/baselines/reference/inferTypeParameterConstraints.symbols index 7a4aeca52c0ef..75385598fc5ab 100644 --- a/tests/baselines/reference/inferTypeParameterConstraints.symbols +++ b/tests/baselines/reference/inferTypeParameterConstraints.symbols @@ -51,3 +51,61 @@ type T0 = Foo; // string >T0 : Symbol(T0, Decl(inferTypeParameterConstraints.ts, 14, 58)) >Foo : Symbol(Foo, Decl(inferTypeParameterConstraints.ts, 12, 41)) +// https://github.com/microsoft/TypeScript/issues/57286#issuecomment-1927920336 + +class BaseClass { +>BaseClass : Symbol(BaseClass, Decl(inferTypeParameterConstraints.ts, 16, 22)) +>V : Symbol(V, Decl(inferTypeParameterConstraints.ts, 20, 16)) + + protected fake(): V { +>fake : Symbol(BaseClass.fake, Decl(inferTypeParameterConstraints.ts, 20, 20)) +>V : Symbol(V, Decl(inferTypeParameterConstraints.ts, 20, 16)) + + throw new Error(""); +>Error : Symbol(Error, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --)) + } +} + +class Klass extends BaseClass { +>Klass : Symbol(Klass, Decl(inferTypeParameterConstraints.ts, 24, 1)) +>V : Symbol(V, Decl(inferTypeParameterConstraints.ts, 26, 12)) +>BaseClass : Symbol(BaseClass, Decl(inferTypeParameterConstraints.ts, 16, 22)) +>V : Symbol(V, Decl(inferTypeParameterConstraints.ts, 26, 12)) + + child = true; +>child : Symbol(Klass.child, Decl(inferTypeParameterConstraints.ts, 26, 37)) +} + +type Constructor> = new () => P; +>Constructor : Symbol(Constructor, Decl(inferTypeParameterConstraints.ts, 28, 1)) +>V : Symbol(V, Decl(inferTypeParameterConstraints.ts, 30, 17)) +>P : Symbol(P, Decl(inferTypeParameterConstraints.ts, 30, 19)) +>BaseClass : Symbol(BaseClass, Decl(inferTypeParameterConstraints.ts, 16, 22)) +>V : Symbol(V, Decl(inferTypeParameterConstraints.ts, 30, 17)) +>P : Symbol(P, Decl(inferTypeParameterConstraints.ts, 30, 19)) + +type inferTest = T extends Constructor ? P : never; +>inferTest : Symbol(inferTest, Decl(inferTypeParameterConstraints.ts, 30, 58)) +>V : Symbol(V, Decl(inferTypeParameterConstraints.ts, 31, 15)) +>T : Symbol(T, Decl(inferTypeParameterConstraints.ts, 31, 17)) +>T : Symbol(T, Decl(inferTypeParameterConstraints.ts, 31, 17)) +>Constructor : Symbol(Constructor, Decl(inferTypeParameterConstraints.ts, 28, 1)) +>V : Symbol(V, Decl(inferTypeParameterConstraints.ts, 31, 15)) +>P : Symbol(P, Decl(inferTypeParameterConstraints.ts, 31, 53)) +>P : Symbol(P, Decl(inferTypeParameterConstraints.ts, 31, 53)) + +type U = inferTest>>; +>U : Symbol(U, Decl(inferTypeParameterConstraints.ts, 31, 69)) +>inferTest : Symbol(inferTest, Decl(inferTypeParameterConstraints.ts, 30, 58)) +>Constructor : Symbol(Constructor, Decl(inferTypeParameterConstraints.ts, 28, 1)) +>Klass : Symbol(Klass, Decl(inferTypeParameterConstraints.ts, 24, 1)) + +declare let m: U; +>m : Symbol(m, Decl(inferTypeParameterConstraints.ts, 35, 11)) +>U : Symbol(U, Decl(inferTypeParameterConstraints.ts, 31, 69)) + +m.child; // ok +>m.child : Symbol(Klass.child, Decl(inferTypeParameterConstraints.ts, 26, 37)) +>m : Symbol(m, Decl(inferTypeParameterConstraints.ts, 35, 11)) +>child : Symbol(Klass.child, Decl(inferTypeParameterConstraints.ts, 26, 37)) + diff --git a/tests/baselines/reference/inferTypeParameterConstraints.types b/tests/baselines/reference/inferTypeParameterConstraints.types index 9d25d908cb75b..58386cd2159c6 100644 --- a/tests/baselines/reference/inferTypeParameterConstraints.types +++ b/tests/baselines/reference/inferTypeParameterConstraints.types @@ -26,3 +26,44 @@ type Foo = A extends Constrain ? X : never; type T0 = Foo; // string >T0 : string +// https://github.com/microsoft/TypeScript/issues/57286#issuecomment-1927920336 + +class BaseClass { +>BaseClass : BaseClass + + protected fake(): V { +>fake : () => V + + throw new Error(""); +>new Error("") : Error +>Error : ErrorConstructor +>"" : "" + } +} + +class Klass extends BaseClass { +>Klass : Klass +>BaseClass : BaseClass + + child = true; +>child : boolean +>true : true +} + +type Constructor> = new () => P; +>Constructor : Constructor + +type inferTest = T extends Constructor ? P : never; +>inferTest : inferTest + +type U = inferTest>>; +>U : Klass + +declare let m: U; +>m : Klass + +m.child; // ok +>m.child : boolean +>m : Klass +>child : boolean + diff --git a/tests/cases/compiler/inferTypeParameterConstraints.ts b/tests/cases/compiler/inferTypeParameterConstraints.ts index 4a1a6dcdbdb84..92299e38d80dd 100644 --- a/tests/cases/compiler/inferTypeParameterConstraints.ts +++ b/tests/cases/compiler/inferTypeParameterConstraints.ts @@ -17,3 +17,23 @@ type Constrain = unknown; type Foo = A extends Constrain ? X : never; type T0 = Foo; // string + +// https://github.com/microsoft/TypeScript/issues/57286#issuecomment-1927920336 + +class BaseClass { + protected fake(): V { + throw new Error(""); + } +} + +class Klass extends BaseClass { + child = true; +} + +type Constructor> = new () => P; +type inferTest = T extends Constructor ? P : never; + +type U = inferTest>>; + +declare let m: U; +m.child; // ok