Skip to content
Open
Show file tree
Hide file tree
Changes from 5 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
22 changes: 16 additions & 6 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14749,6 +14749,12 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
undefined;
}
if (t.flags & TypeFlags.Index) {
if (isGenericMappedType((t as IndexType).type)) {
const mappedType = (t as IndexType).type as MappedType;
if (getNameTypeFromMappedType(mappedType) && !isMappedTypeWithKeyofConstraintDeclaration(mappedType)) {
return getBaseConstraint(getIndexTypeForMappedType(mappedType, IndexFlags.None));
}
}
return stringNumberSymbolType;
}
if (t.flags & TypeFlags.TemplateLiteral) {
Expand Down Expand Up @@ -18350,7 +18356,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
function shouldDeferIndexType(type: Type, indexFlags = IndexFlags.None) {
return !!(type.flags & TypeFlags.InstantiableNonPrimitive ||
isGenericTupleType(type) ||
isGenericMappedType(type) && (!hasDistributiveNameType(type) || getMappedTypeNameTypeKind(type) === MappedTypeNameTypeKind.Remapping) ||
isGenericMappedType(type) && getNameTypeFromMappedType(type) ||
Copy link
Contributor Author

Choose a reason for hiding this comment

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

As mentioned in the comment here:

Effectively P is always instantiated with a non-union type.

Because of that, it's not safe to skip index type deferral for any remapping mapped type as P could be used multiple times by the mapping (and the compiler shouldn't allow for a cross-product of `${P}_${P}`). Checking getMappedTypeNameTypeKind can run into an infinite loop here and it seems the easiest to just avoid checking that and assume that all generic mapped types with name types have to have deferred index types. I compensate for that by simplifying them in relationship checking etc.

type.flags & TypeFlags.Union && !(indexFlags & IndexFlags.NoReducibleCheck) && isGenericReducibleType(type) ||
type.flags & TypeFlags.Intersection && maybeTypeOfKind(type, TypeFlags.Instantiable) && some((type as IntersectionType).types, isEmptyAnonymousObjectType));
}
Expand Down Expand Up @@ -18871,6 +18877,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
function getSimplifiedType(type: Type, writing: boolean): Type {
return type.flags & TypeFlags.IndexedAccess ? getSimplifiedIndexedAccessType(type as IndexedAccessType, writing) :
type.flags & TypeFlags.Conditional ? getSimplifiedConditionalType(type as ConditionalType, writing) :
type.flags & TypeFlags.Index ? getSimplifiedIndexType(type as IndexType) :
type;
}

Expand Down Expand Up @@ -18970,6 +18977,13 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
return type;
}

function getSimplifiedIndexType(type: IndexType) {
if (isGenericMappedType(type.type) && getNameTypeFromMappedType(type.type) && !isMappedTypeWithKeyofConstraintDeclaration(type.type)) {
return getIndexTypeForMappedType(type.type, IndexFlags.None);
}
return type;
}

/**
* Invokes union simplification logic to determine if an intersection is considered empty as a union constituent
*/
Expand Down Expand Up @@ -42086,12 +42100,8 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
// Check if the index type is assignable to 'keyof T' for the object type.
const objectType = (type as IndexedAccessType).objectType;
const indexType = (type as IndexedAccessType).indexType;
// skip index type deferral on remapping mapped types
const objectIndexType = isGenericMappedType(objectType) && getMappedTypeNameTypeKind(objectType) === MappedTypeNameTypeKind.Remapping
? getIndexTypeForMappedType(objectType, IndexFlags.None)
: getIndexType(objectType, IndexFlags.None);
Comment on lines -42089 to -42092
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 reverts my own change from #55140 . I think now the check wasn't exhaustive anyway and this is now better handled by getSimplifiedIndexType

const hasNumberIndexInfo = !!getIndexInfoOfType(objectType, numberType);
if (everyType(indexType, t => isTypeAssignableTo(t, objectIndexType) || hasNumberIndexInfo && isApplicableIndexType(t, numberType))) {
if (everyType(indexType, t => isTypeAssignableTo(t, getIndexType(objectType, IndexFlags.None)) || hasNumberIndexInfo && isApplicableIndexType(t, numberType))) {
if (
accessNode.kind === SyntaxKind.ElementAccessExpression && isAssignmentTarget(accessNode) &&
getObjectFlags(objectType) & ObjectFlags.Mapped && getMappedTypeModifiers(objectType as MappedType) & MappedTypeModifiers.IncludeReadonly
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6340,7 +6340,7 @@ export const enum TypeFlags {
/** @internal */
ObjectFlagsType = Any | Nullable | Never | Object | Union | Intersection,
/** @internal */
Simplifiable = IndexedAccess | Conditional,
Simplifiable = IndexedAccess | Conditional | Index,
/** @internal */
Singleton = Any | Unknown | String | Number | Boolean | BigInt | ESSymbol | Void | Undefined | Null | Never | NonPrimitive,
// 'Narrowable' types are types where narrowing actually narrows.
Expand Down
121 changes: 121 additions & 0 deletions tests/baselines/reference/mappedTypeAsClauseRecursiveNoCrash1.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
//// [tests/cases/conformance/types/mapped/mappedTypeAsClauseRecursiveNoCrash1.ts] ////

=== mappedTypeAsClauseRecursiveNoCrash1.ts ===
// https://github.com/microsoft/TypeScript/issues/60476

export type FlattenType<Source extends object, Target> = {
>FlattenType : Symbol(FlattenType, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 0, 0))
>Source : Symbol(Source, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 2, 24))
>Target : Symbol(Target, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 2, 46))

[Key in keyof Source as Key extends string
>Key : Symbol(Key, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 3, 3))
>Source : Symbol(Source, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 2, 24))
>Key : Symbol(Key, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 3, 3))

? Source[Key] extends object
>Source : Symbol(Source, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 2, 24))
>Key : Symbol(Key, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 3, 3))

? `${Key}.${keyof FlattenType<Source[Key], Target> & string}`
>Key : Symbol(Key, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 3, 3))
>FlattenType : Symbol(FlattenType, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 0, 0))
>Source : Symbol(Source, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 2, 24))
>Key : Symbol(Key, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 3, 3))
>Target : Symbol(Target, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 2, 46))

: Key
>Key : Symbol(Key, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 3, 3))

: never]-?: Target;
>Target : Symbol(Target, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 2, 46))

};

type FieldSelect = {
>FieldSelect : Symbol(FieldSelect, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 8, 2))

table: string;
>table : Symbol(table, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 10, 20))

field: string;
>field : Symbol(field, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 11, 16))

};

type Address = {
>Address : Symbol(Address, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 13, 2))

postCode: string;
>postCode : Symbol(postCode, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 15, 16))

description: string;
>description : Symbol(description, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 16, 19))

address: string;
>address : Symbol(address, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 17, 22))

};

type User = {
>User : Symbol(User, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 19, 2))

id: number;
>id : Symbol(id, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 21, 13))

name: string;
>name : Symbol(name, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 22, 13))

address: Address;
>address : Symbol(address, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 23, 15))
>Address : Symbol(Address, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 13, 2))

};

type FlattenedUser = FlattenType<User, FieldSelect>;
>FlattenedUser : Symbol(FlattenedUser, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 25, 2))
>FlattenType : Symbol(FlattenType, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 0, 0))
>User : Symbol(User, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 19, 2))
>FieldSelect : Symbol(FieldSelect, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 8, 2))

type FlattenedUserKeys = keyof FlattenType<User, FieldSelect>;
>FlattenedUserKeys : Symbol(FlattenedUserKeys, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 27, 52))
>FlattenType : Symbol(FlattenType, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 0, 0))
>User : Symbol(User, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 19, 2))
>FieldSelect : Symbol(FieldSelect, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 8, 2))

export type FlattenTypeKeys<Source extends object, Target> = keyof {
>FlattenTypeKeys : Symbol(FlattenTypeKeys, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 28, 62))
>Source : Symbol(Source, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 30, 28))
>Target : Symbol(Target, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 30, 50))

[Key in keyof Source as Key extends string
>Key : Symbol(Key, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 31, 3))
>Source : Symbol(Source, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 30, 28))
>Key : Symbol(Key, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 31, 3))

? Source[Key] extends object
>Source : Symbol(Source, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 30, 28))
>Key : Symbol(Key, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 31, 3))

? `${Key}.${keyof FlattenType<Source[Key], Target> & string}`
>Key : Symbol(Key, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 31, 3))
>FlattenType : Symbol(FlattenType, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 0, 0))
>Source : Symbol(Source, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 30, 28))
>Key : Symbol(Key, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 31, 3))
>Target : Symbol(Target, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 30, 50))

: Key
>Key : Symbol(Key, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 31, 3))

: never]-?: Target;
>Target : Symbol(Target, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 30, 50))

};

type FlattenedUserKeys2 = FlattenTypeKeys<User, FieldSelect>;
>FlattenedUserKeys2 : Symbol(FlattenedUserKeys2, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 36, 2))
>FlattenTypeKeys : Symbol(FlattenTypeKeys, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 28, 62))
>User : Symbol(User, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 19, 2))
>FieldSelect : Symbol(FieldSelect, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 8, 2))

Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
//// [tests/cases/conformance/types/mapped/mappedTypeAsClauseRecursiveNoCrash1.ts] ////

=== mappedTypeAsClauseRecursiveNoCrash1.ts ===
// https://github.com/microsoft/TypeScript/issues/60476

export type FlattenType<Source extends object, Target> = {
>FlattenType : FlattenType<Source, Target>
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^

[Key in keyof Source as Key extends string
? Source[Key] extends object
? `${Key}.${keyof FlattenType<Source[Key], Target> & string}`
: Key
: never]-?: Target;
};

type FieldSelect = {
>FieldSelect : FieldSelect
> : ^^^^^^^^^^^

table: string;
>table : string
> : ^^^^^^

field: string;
>field : string
> : ^^^^^^

};

type Address = {
>Address : Address
> : ^^^^^^^

postCode: string;
>postCode : string
> : ^^^^^^

description: string;
>description : string
> : ^^^^^^

address: string;
>address : string
> : ^^^^^^

};

type User = {
>User : User
> : ^^^^

id: number;
>id : number
> : ^^^^^^

name: string;
>name : string
> : ^^^^^^

address: Address;
>address : Address
> : ^^^^^^^

};

type FlattenedUser = FlattenType<User, FieldSelect>;
>FlattenedUser : FlattenType<User, FieldSelect>
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

type FlattenedUserKeys = keyof FlattenType<User, FieldSelect>;
>FlattenedUserKeys : "id" | "name" | "address.address" | "address.postCode" | "address.description"
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

export type FlattenTypeKeys<Source extends object, Target> = keyof {
>FlattenTypeKeys : keyof { [Key in keyof Source as Key extends string ? Source[Key] extends object ? `${Key}.${keyof FlattenType<Source[Key], Target> & string}` : Key : never]-?: Target; }
> : ^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

[Key in keyof Source as Key extends string
? Source[Key] extends object
? `${Key}.${keyof FlattenType<Source[Key], Target> & string}`
: Key
: never]-?: Target;
};

type FlattenedUserKeys2 = FlattenTypeKeys<User, FieldSelect>;
>FlattenedUserKeys2 : "id" | "name" | "address.address" | "address.postCode" | "address.description"
> : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

20 changes: 14 additions & 6 deletions tests/baselines/reference/mappedTypeConstraints2.errors.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ mappedTypeConstraints2.ts(16,11): error TS2322: Type 'Mapped3<K>[Uppercase<K>]'
Type 'Mapped3<K>[Uppercase<string>]' is not assignable to type '{ a: K; }'.
Type 'Mapped3<K>[string]' is not assignable to type '{ a: K; }'.
mappedTypeConstraints2.ts(42,7): error TS2322: Type 'Mapped6<K>[keyof Mapped6<K>]' is not assignable to type '`_${string}`'.
Type 'Mapped6<K>[string] | Mapped6<K>[number] | Mapped6<K>[symbol]' is not assignable to type '`_${string}`'.
Type 'Mapped6<K>[string]' is not assignable to type '`_${string}`'.
mappedTypeConstraints2.ts(51,57): error TS2322: Type 'Foo<T>[`get${T}`]' is not assignable to type 'T'.
Type 'Mapped6<K>[`_${K}`]' is not assignable to type '`_${string}`'.
Type 'Mapped6<K>[`_${string}`]' is not assignable to type '`_${string}`'.
mappedTypeConstraints2.ts(59,57): error TS2322: Type 'Foo<T>[`get${T}`]' is not assignable to type 'T'.
'T' could be instantiated with an arbitrary type which could be unrelated to 'Foo<T>[`get${T}`]'.
mappedTypeConstraints2.ts(82,9): error TS2322: Type 'ObjectWithUnderscoredKeys<K>[`_${K}`]' is not assignable to type 'true'.
mappedTypeConstraints2.ts(90,9): error TS2322: Type 'ObjectWithUnderscoredKeys<K>[`_${K}`]' is not assignable to type 'true'.
Type 'ObjectWithUnderscoredKeys<K>[`_${string}`]' is not assignable to type 'true'.


Expand Down Expand Up @@ -64,8 +64,16 @@ mappedTypeConstraints2.ts(82,9): error TS2322: Type 'ObjectWithUnderscoredKeys<K
let s: `_${string}` = obj[key]; // Error
~
!!! error TS2322: Type 'Mapped6<K>[keyof Mapped6<K>]' is not assignable to type '`_${string}`'.
!!! error TS2322: Type 'Mapped6<K>[string] | Mapped6<K>[number] | Mapped6<K>[symbol]' is not assignable to type '`_${string}`'.
!!! error TS2322: Type 'Mapped6<K>[string]' is not assignable to type '`_${string}`'.
!!! error TS2322: Type 'Mapped6<K>[`_${K}`]' is not assignable to type '`_${string}`'.
!!! error TS2322: Type 'Mapped6<K>[`_${string}`]' is not assignable to type '`_${string}`'.
Comment on lines +67 to +68
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I believe this change is a fix. The new error matches what was reported by TS 5.0: TS playground

}

type Mapped7<K extends string> = {
[P in K as [P] extends [`_${string}`] ? P : never]: P;
};

function f7<K extends string>(obj: Mapped7<K>, key: keyof Mapped7<K>) {
let s: `_${string}` = obj[key];
}

// Repro from #47794
Expand Down
15 changes: 15 additions & 0 deletions tests/baselines/reference/mappedTypeConstraints2.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,14 @@ function f6<K extends string>(obj: Mapped6<K>, key: keyof Mapped6<K>) {
let s: `_${string}` = obj[key]; // Error
}

type Mapped7<K extends string> = {
[P in K as [P] extends [`_${string}`] ? P : never]: P;
};

function f7<K extends string>(obj: Mapped7<K>, key: keyof Mapped7<K>) {
let s: `_${string}` = obj[key];
}

// Repro from #47794

type Foo<T extends string> = {
Expand Down Expand Up @@ -106,6 +114,9 @@ function f5(obj, key) {
function f6(obj, key) {
let s = obj[key]; // Error
}
function f7(obj, key) {
let s = obj[key];
}
const get = (t, foo) => foo[`get${t}`]; // Type 'Foo<T>[`get${T}`]' is not assignable to type 'T'
function validate(obj, bounds) {
for (const [key, val] of Object.entries(obj)) {
Expand Down Expand Up @@ -154,6 +165,10 @@ type Mapped6<K extends string> = {
[P in K as `_${P}`]: P;
};
declare function f6<K extends string>(obj: Mapped6<K>, key: keyof Mapped6<K>): void;
type Mapped7<K extends string> = {
[P in K as [P] extends [`_${string}`] ? P : never]: P;
};
declare function f7<K extends string>(obj: Mapped7<K>, key: keyof Mapped7<K>): void;
type Foo<T extends string> = {
[RemappedT in T as `get${RemappedT}`]: RemappedT;
};
Expand Down
Loading
Loading