-
Notifications
You must be signed in to change notification settings - Fork 13.2k
Fixed crash related to index type deferral on generic mapped types with name types #60528
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
base: main
Are you sure you want to change the base?
Changes from 5 commits
b9a01cb
e13ad38
880d4af
4c69959
585091e
ac23642
2d24ebc
51a631a
5480c6a
b901985
916de93
35ab81f
3d1fe73
f92747d
ebc2320
c1ba85d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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) { | ||
|
|
@@ -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) || | ||
| type.flags & TypeFlags.Union && !(indexFlags & IndexFlags.NoReducibleCheck) && isGenericReducibleType(type) || | ||
| type.flags & TypeFlags.Intersection && maybeTypeOfKind(type, TypeFlags.Instantiable) && some((type as IntersectionType).types, isEmptyAnonymousObjectType)); | ||
| } | ||
|
|
@@ -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; | ||
| } | ||
|
|
||
|
|
@@ -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 | ||
| */ | ||
|
|
@@ -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
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
| 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 | ||
|
|
||
| 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" | ||
| > : ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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'. | ||
|
|
||
|
|
||
|
|
@@ -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
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
|
||
There was a problem hiding this comment.
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:
Because of that, it's not safe to skip index type deferral for any remapping mapped type as
Pcould be used multiple times by the mapping (and the compiler shouldn't allow for a cross-product of`${P}_${P}`). CheckinggetMappedTypeNameTypeKindcan 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.