Skip to content

Commit

Permalink
🤖 Pick PR #58098 (Fix constraints of nested homomorph...) into releas…
Browse files Browse the repository at this point in the history
…e-5.4 (#58118)

Co-authored-by: Anders Hejlsberg <[email protected]>
  • Loading branch information
TypeScript Bot and ahejlsberg authored Apr 10, 2024
1 parent 71b2f84 commit 9f33bf1
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 7 deletions.
18 changes: 11 additions & 7 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14552,16 +14552,20 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return type.resolvedApparentType || (type.resolvedApparentType = getResolvedApparentTypeOfMappedType(type));
}

function getResolvedApparentTypeOfMappedType(type: MappedType) {
function getResolvedApparentTypeOfMappedType(type: MappedType): Type {
const target = (type.target ?? type) as MappedType;
const typeVariable = getHomomorphicTypeVariable(target);
if (typeVariable && !target.declaration.nameType) {
const constraint = getConstraintTypeFromMappedType(type);
if (constraint.flags & TypeFlags.Index) {
const baseConstraint = getBaseConstraintOfType((constraint as IndexType).type);
if (baseConstraint && everyType(baseConstraint, t => isArrayOrTupleType(t) || isArrayOrTupleOrIntersection(t))) {
return instantiateType(target, prependTypeMapping(typeVariable, baseConstraint, type.mapper));
}
// We have a homomorphic mapped type or an instantiation of a homomorphic mapped type, i.e. a type
// of the form { [P in keyof T]: X }. Obtain the modifiers type (the T of the keyof T), and if it is
// another generic mapped type, recursively obtain its apparent type. Otherwise, obtain its base
// constraint. Then, if every constituent of the base constraint is an array or tuple type, apply
// this mapped type to the base constraint. It is safe to recurse when the modifiers type is a
// mapped type because we protect again circular constraints in getTypeFromMappedTypeNode.
const modifiersType = getModifiersTypeFromMappedType(type);
const baseConstraint = isGenericMappedType(modifiersType) ? getApparentTypeOfMappedType(modifiersType) : getBaseConstraintOfType(modifiersType);
if (baseConstraint && everyType(baseConstraint, t => isArrayOrTupleType(t) || isArrayOrTupleOrIntersection(t))) {
return instantiateType(target, prependTypeMapping(typeVariable, baseConstraint, type.mapper));
}
}
return type;
Expand Down
51 changes: 51 additions & 0 deletions tests/baselines/reference/homomorphicMappedTypeNesting.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
//// [tests/cases/compiler/homomorphicMappedTypeNesting.ts] ////

=== homomorphicMappedTypeNesting.ts ===
// Repro from #58060

type Box<T extends string> = { v: T };
>Box : Symbol(Box, Decl(homomorphicMappedTypeNesting.ts, 0, 0))
>T : Symbol(T, Decl(homomorphicMappedTypeNesting.ts, 2, 9))
>v : Symbol(v, Decl(homomorphicMappedTypeNesting.ts, 2, 30))
>T : Symbol(T, Decl(homomorphicMappedTypeNesting.ts, 2, 9))

type Test<T extends string[]> = T
>Test : Symbol(Test, Decl(homomorphicMappedTypeNesting.ts, 2, 38))
>T : Symbol(T, Decl(homomorphicMappedTypeNesting.ts, 4, 10))
>T : Symbol(T, Decl(homomorphicMappedTypeNesting.ts, 4, 10))

type UnboxArray<T> = {
>UnboxArray : Symbol(UnboxArray, Decl(homomorphicMappedTypeNesting.ts, 4, 33))
>T : Symbol(T, Decl(homomorphicMappedTypeNesting.ts, 6, 16))

[K in keyof T]: T[K] extends Box<infer R> ? R : never;
>K : Symbol(K, Decl(homomorphicMappedTypeNesting.ts, 7, 5))
>T : Symbol(T, Decl(homomorphicMappedTypeNesting.ts, 6, 16))
>T : Symbol(T, Decl(homomorphicMappedTypeNesting.ts, 6, 16))
>K : Symbol(K, Decl(homomorphicMappedTypeNesting.ts, 7, 5))
>Box : Symbol(Box, Decl(homomorphicMappedTypeNesting.ts, 0, 0))
>R : Symbol(R, Decl(homomorphicMappedTypeNesting.ts, 7, 42))
>R : Symbol(R, Decl(homomorphicMappedTypeNesting.ts, 7, 42))

};

type Identity<T> = { [K in keyof T]: T[K] };
>Identity : Symbol(Identity, Decl(homomorphicMappedTypeNesting.ts, 8, 2))
>T : Symbol(T, Decl(homomorphicMappedTypeNesting.ts, 10, 14))
>K : Symbol(K, Decl(homomorphicMappedTypeNesting.ts, 10, 22))
>T : Symbol(T, Decl(homomorphicMappedTypeNesting.ts, 10, 14))
>T : Symbol(T, Decl(homomorphicMappedTypeNesting.ts, 10, 14))
>K : Symbol(K, Decl(homomorphicMappedTypeNesting.ts, 10, 22))

declare function fnBad<T extends Array<Box<string>>>(...args: T): Test<Identity<UnboxArray<T>>>;
>fnBad : Symbol(fnBad, Decl(homomorphicMappedTypeNesting.ts, 10, 44))
>T : Symbol(T, Decl(homomorphicMappedTypeNesting.ts, 12, 23))
>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>Box : Symbol(Box, Decl(homomorphicMappedTypeNesting.ts, 0, 0))
>args : Symbol(args, Decl(homomorphicMappedTypeNesting.ts, 12, 53))
>T : Symbol(T, Decl(homomorphicMappedTypeNesting.ts, 12, 23))
>Test : Symbol(Test, Decl(homomorphicMappedTypeNesting.ts, 2, 38))
>Identity : Symbol(Identity, Decl(homomorphicMappedTypeNesting.ts, 8, 2))
>UnboxArray : Symbol(UnboxArray, Decl(homomorphicMappedTypeNesting.ts, 4, 33))
>T : Symbol(T, Decl(homomorphicMappedTypeNesting.ts, 12, 23))

25 changes: 25 additions & 0 deletions tests/baselines/reference/homomorphicMappedTypeNesting.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//// [tests/cases/compiler/homomorphicMappedTypeNesting.ts] ////

=== homomorphicMappedTypeNesting.ts ===
// Repro from #58060

type Box<T extends string> = { v: T };
>Box : Box<T>
>v : T

type Test<T extends string[]> = T
>Test : T

type UnboxArray<T> = {
>UnboxArray : UnboxArray<T>

[K in keyof T]: T[K] extends Box<infer R> ? R : never;
};

type Identity<T> = { [K in keyof T]: T[K] };
>Identity : Identity<T>

declare function fnBad<T extends Array<Box<string>>>(...args: T): Test<Identity<UnboxArray<T>>>;
>fnBad : <T extends Box<string>[]>(...args: T) => Test<Identity<UnboxArray<T>>>
>args : T

16 changes: 16 additions & 0 deletions tests/cases/compiler/homomorphicMappedTypeNesting.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// @strict: true
// @noEmit: true

// Repro from #58060

type Box<T extends string> = { v: T };

type Test<T extends string[]> = T

type UnboxArray<T> = {
[K in keyof T]: T[K] extends Box<infer R> ? R : never;
};

type Identity<T> = { [K in keyof T]: T[K] };

declare function fnBad<T extends Array<Box<string>>>(...args: T): Test<Identity<UnboxArray<T>>>;

0 comments on commit 9f33bf1

Please sign in to comment.