Skip to content

Improve apparent type of mapped types with mapped type modifiers #58091

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

Closed
Closed
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
5 changes: 3 additions & 2 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14794,15 +14794,16 @@ 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));
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The type.mapper here looks something like:

m1: T_id -> Box<string>[]
m2: m1: K -> K
    m2: m1: T_id -> UnboxArray<T_outer>
        m2: T_id -> UnboxArray<T_outer>

Since typeVariable is T_id TS ended up "skipping" over the inner mapping of UnboxArray. The constraint (of the type) is keyof T_outer with a baseConstraint of Box<string>[].

In a sense, I'd like to map T_outer to baseConstraint (Box<string>[]), process that through type.mapper and then map T_id to that result (or smth along those lines). I couldn't find a clear way to do that given that we need to account for non-homomorphic instantiations of homomorphic mapped types here. Using the modifiers type does seem to do the trick but I'm not overly confident in this solution and I won't mind being proven that this is wrong 😉

const modifiersType = getModifiersTypeFromMappedType(type);
return instantiateType(target, prependTypeMapping(typeVariable, isGenericMappedType(modifiersType) ? getResolvedApparentTypeOfMappedType(modifiersType) : baseConstraint, type.mapper));
}
}
}
Expand Down
61 changes: 61 additions & 0 deletions tests/baselines/reference/homomorphicMappedTypeNested1.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
//// [tests/cases/compiler/homomorphicMappedTypeNested1.ts] ////

=== homomorphicMappedTypeNested1.ts ===
// https://github.com/microsoft/TypeScript/issues/58060

type ValueType = string;
>ValueType : Symbol(ValueType, Decl(homomorphicMappedTypeNested1.ts, 0, 0))

type Box<T extends ValueType> = { v: T };
>Box : Symbol(Box, Decl(homomorphicMappedTypeNested1.ts, 2, 24))
>T : Symbol(T, Decl(homomorphicMappedTypeNested1.ts, 4, 9))
>ValueType : Symbol(ValueType, Decl(homomorphicMappedTypeNested1.ts, 0, 0))
>v : Symbol(v, Decl(homomorphicMappedTypeNested1.ts, 4, 33))
>T : Symbol(T, Decl(homomorphicMappedTypeNested1.ts, 4, 9))

type Test<T extends ValueType[]> = T;
>Test : Symbol(Test, Decl(homomorphicMappedTypeNested1.ts, 4, 41))
>T : Symbol(T, Decl(homomorphicMappedTypeNested1.ts, 6, 10))
>ValueType : Symbol(ValueType, Decl(homomorphicMappedTypeNested1.ts, 0, 0))
>T : Symbol(T, Decl(homomorphicMappedTypeNested1.ts, 6, 10))

type UnboxArray<T> = {
>UnboxArray : Symbol(UnboxArray, Decl(homomorphicMappedTypeNested1.ts, 6, 37))
>T : Symbol(T, Decl(homomorphicMappedTypeNested1.ts, 8, 16))

[K in keyof T]: T[K] extends Box<infer R> ? R : never;
>K : Symbol(K, Decl(homomorphicMappedTypeNested1.ts, 9, 3))
>T : Symbol(T, Decl(homomorphicMappedTypeNested1.ts, 8, 16))
>T : Symbol(T, Decl(homomorphicMappedTypeNested1.ts, 8, 16))
>K : Symbol(K, Decl(homomorphicMappedTypeNested1.ts, 9, 3))
>Box : Symbol(Box, Decl(homomorphicMappedTypeNested1.ts, 2, 24))
>R : Symbol(R, Decl(homomorphicMappedTypeNested1.ts, 9, 40))
>R : Symbol(R, Decl(homomorphicMappedTypeNested1.ts, 9, 40))

};

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

declare function fn<T extends Array<Box<ValueType>>>(
>fn : Symbol(fn, Decl(homomorphicMappedTypeNested1.ts, 12, 44))
>T : Symbol(T, Decl(homomorphicMappedTypeNested1.ts, 14, 20))
>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
>Box : Symbol(Box, Decl(homomorphicMappedTypeNested1.ts, 2, 24))
>ValueType : Symbol(ValueType, Decl(homomorphicMappedTypeNested1.ts, 0, 0))

...args: T
>args : Symbol(args, Decl(homomorphicMappedTypeNested1.ts, 14, 53))
>T : Symbol(T, Decl(homomorphicMappedTypeNested1.ts, 14, 20))

): Test<Identity<UnboxArray<T>>>;
>Test : Symbol(Test, Decl(homomorphicMappedTypeNested1.ts, 4, 41))
>Identity : Symbol(Identity, Decl(homomorphicMappedTypeNested1.ts, 10, 2))
>UnboxArray : Symbol(UnboxArray, Decl(homomorphicMappedTypeNested1.ts, 6, 37))
>T : Symbol(T, Decl(homomorphicMappedTypeNested1.ts, 14, 20))

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

=== homomorphicMappedTypeNested1.ts ===
// https://github.com/microsoft/TypeScript/issues/58060

type ValueType = string;
>ValueType : string
> : ^^^^^^

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

type Test<T extends ValueType[]> = 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 fn<T extends Array<Box<ValueType>>>(
>fn : <T extends Box<string>[]>(...args: T) => Test<Identity<UnboxArray<T>>>
> : ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^

...args: T
>args : T
> : ^

): Test<Identity<UnboxArray<T>>>;

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

// https://github.com/microsoft/TypeScript/issues/58060

type ValueType = string;

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

type Test<T extends ValueType[]> = 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 fn<T extends Array<Box<ValueType>>>(
...args: T
): Test<Identity<UnboxArray<T>>>;
Loading