-
Notifications
You must be signed in to change notification settings - Fork 12.9k
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
Open
Andarist
wants to merge
16
commits into
microsoft:main
Choose a base branch
from
Andarist:fix/defer-index-types-on-generic-mapped-types-2
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+723
−116
Open
Changes from all commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
b9a01cb
Instantiate `nameType` of filtering mapped types when creating index …
Andarist e13ad38
just always instantiate the name type
Andarist 880d4af
Merge remote-tracking branch 'origin/main' into instantiate-name-type…
Andarist 4c69959
Revert `nameType` intantiation
Andarist 585091e
Improve relationship checking
Andarist ac23642
Reuse the logic in `getGenericMappedTypeKeys`
Andarist 2d24ebc
Defer all generic mapped types with name types
Andarist 51a631a
add a new constraint rule for index types
Andarist 5480c6a
make those indexes simplifiable
Andarist b901985
add extra test case
Andarist 916de93
add test for the fixed crash
Andarist 35ab81f
remove unused function
Andarist 3d1fe73
Merge remote-tracking branch 'origin/main' into instantiate-name-type…
Andarist f92747d
Merge branch 'instantiate-name-type-of-filtering-mapped-types-for-key…
Andarist ebc2320
update baseline
Andarist c1ba85d
remove duplicate/obsolete function
Andarist File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) { | ||
|
@@ -18250,7 +18256,7 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { | |
// a circular definition. For this reason, we only eagerly manifest the keys if the constraint is non-generic. | ||
if (isGenericIndexType(constraintType)) { | ||
if (isMappedTypeWithKeyofConstraintDeclaration(type)) { | ||
// We have a generic index and a homomorphic mapping (but a distributive key remapping) - we need to defer | ||
// We have a generic index and a homomorphic mapping and a key remapping - we need to defer | ||
// the whole `keyof whatever` for later since it's not safe to resolve the shape of modifier type. | ||
return getIndexTypeForGenericType(type, indexFlags); | ||
} | ||
|
@@ -18280,25 +18286,6 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { | |
} | ||
} | ||
|
||
// Ordinarily we reduce a keyof M, where M is a mapped type { [P in K as N<P>]: X }, to simply N<K>. This however presumes | ||
// that N distributes over union types, i.e. that N<A | B | C> is equivalent to N<A> | N<B> | N<C>. Specifically, we only | ||
// want to perform the reduction when the name type of a mapped type is distributive with respect to the type variable | ||
// introduced by the 'in' clause of the mapped type. Note that non-generic types are considered to be distributive because | ||
// they're the same type regardless of what's being distributed over. | ||
function hasDistributiveNameType(mappedType: MappedType) { | ||
const typeVariable = getTypeParameterFromMappedType(mappedType); | ||
return isDistributive(getNameTypeFromMappedType(mappedType) || typeVariable); | ||
function isDistributive(type: Type): boolean { | ||
return type.flags & (TypeFlags.AnyOrUnknown | TypeFlags.Primitive | TypeFlags.Never | TypeFlags.TypeParameter | TypeFlags.Object | TypeFlags.NonPrimitive) ? true : | ||
type.flags & TypeFlags.Conditional ? (type as ConditionalType).root.isDistributive && (type as ConditionalType).checkType === typeVariable : | ||
type.flags & (TypeFlags.UnionOrIntersection | TypeFlags.TemplateLiteral) ? every((type as UnionOrIntersectionType | TemplateLiteralType).types, isDistributive) : | ||
type.flags & TypeFlags.IndexedAccess ? isDistributive((type as IndexedAccessType).objectType) && isDistributive((type as IndexedAccessType).indexType) : | ||
type.flags & TypeFlags.Substitution ? isDistributive((type as SubstitutionType).baseType) && isDistributive((type as SubstitutionType).constraint) : | ||
type.flags & TypeFlags.StringMapping ? isDistributive((type as StringMappingType).type) : | ||
false; | ||
} | ||
} | ||
|
||
function getLiteralTypeFromPropertyName(name: PropertyName | JsxAttributeName) { | ||
if (isPrivateIdentifier(name)) { | ||
return neverType; | ||
|
@@ -18350,7 +18337,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 +18858,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 +18958,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 +42081,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
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 | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
80 changes: 80 additions & 0 deletions
80
tests/baselines/reference/keyRemappingKeyofResult.errors.txt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
keyRemappingKeyofResult.ts(69,5): error TS2322: Type 'string' is not assignable to type 'keyof Remapped'. | ||
Type '"whatever"' is not assignable to type 'unique symbol | "str" | DistributiveNonIndex<K>'. | ||
|
||
|
||
==== keyRemappingKeyofResult.ts (1 errors) ==== | ||
const sym = Symbol("") | ||
type Orig = { [k: string]: any, str: any, [sym]: any } | ||
|
||
type Okay = Exclude<keyof Orig, never> | ||
// type Okay = string | number | typeof sym | ||
|
||
type Remapped = { [K in keyof Orig as {} extends Record<K, any> ? never : K]: any } | ||
/* type Remapped = { | ||
str: any; | ||
[sym]: any; | ||
} */ | ||
// no string index signature, right? | ||
|
||
type Oops = Exclude<keyof Remapped, never> | ||
declare let x: Oops; | ||
x = sym; | ||
x = "str"; | ||
// type Oops = typeof sym <-- what happened to "str"? | ||
|
||
// equivalently, with an unresolved generic (no `exclude` shenanigans, since conditions won't execute): | ||
function f<T>() { | ||
type Orig = { [k: string]: any, str: any, [sym]: any } & T; | ||
|
||
type Okay = keyof Orig; | ||
let a: Okay; | ||
a = "str"; | ||
a = sym; | ||
a = "whatever"; | ||
// type Okay = string | number | typeof sym | ||
|
||
type Remapped = { [K in keyof Orig as {} extends Record<K, any> ? never : K]: any } | ||
/* type Remapped = { | ||
str: any; | ||
[sym]: any; | ||
} */ | ||
// no string index signature, right? | ||
|
||
type Oops = keyof Remapped; | ||
let x: Oops; | ||
x = sym; | ||
x = "str"; | ||
} | ||
|
||
// and another generic case with a _distributive_ mapping, to trigger a different branch in `getIndexType` | ||
function g<T>() { | ||
type Orig = { [k: string]: any, str: any, [sym]: any } & T; | ||
|
||
type Okay = keyof Orig; | ||
let a: Okay; | ||
a = "str"; | ||
a = sym; | ||
a = "whatever"; | ||
// type Okay = string | number | typeof sym | ||
|
||
type NonIndex<T extends PropertyKey> = {} extends Record<T, any> ? never : T; | ||
type DistributiveNonIndex<T extends PropertyKey> = T extends unknown ? NonIndex<T> : never; | ||
|
||
type Remapped = { [K in keyof Orig as DistributiveNonIndex<K>]: any } | ||
/* type Remapped = { | ||
str: any; | ||
[sym]: any; | ||
} */ | ||
// no string index signature, right? | ||
|
||
type Oops = keyof Remapped; | ||
let x: Oops; | ||
x = sym; | ||
x = "str"; | ||
x = "whatever"; // error | ||
~ | ||
!!! error TS2322: Type 'string' is not assignable to type 'keyof Remapped'. | ||
!!! error TS2322: Type '"whatever"' is not assignable to type 'unique symbol | "str" | DistributiveNonIndex<K>'. | ||
} | ||
|
||
export {}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
84 changes: 84 additions & 0 deletions
84
tests/baselines/reference/keyRemappingKeyofResult2.symbols
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
//// [tests/cases/compiler/keyRemappingKeyofResult2.ts] //// | ||
|
||
=== keyRemappingKeyofResult2.ts === | ||
// https://github.com/microsoft/TypeScript/issues/56239 | ||
|
||
type Values<T> = T[keyof T]; | ||
>Values : Symbol(Values, Decl(keyRemappingKeyofResult2.ts, 0, 0)) | ||
>T : Symbol(T, Decl(keyRemappingKeyofResult2.ts, 2, 12)) | ||
>T : Symbol(T, Decl(keyRemappingKeyofResult2.ts, 2, 12)) | ||
>T : Symbol(T, Decl(keyRemappingKeyofResult2.ts, 2, 12)) | ||
|
||
type ProvidedActor = { | ||
>ProvidedActor : Symbol(ProvidedActor, Decl(keyRemappingKeyofResult2.ts, 2, 28)) | ||
|
||
src: string; | ||
>src : Symbol(src, Decl(keyRemappingKeyofResult2.ts, 4, 22)) | ||
|
||
logic: unknown; | ||
>logic : Symbol(logic, Decl(keyRemappingKeyofResult2.ts, 5, 14)) | ||
|
||
}; | ||
|
||
interface StateMachineConfig<TActors extends ProvidedActor> { | ||
>StateMachineConfig : Symbol(StateMachineConfig, Decl(keyRemappingKeyofResult2.ts, 7, 2)) | ||
>TActors : Symbol(TActors, Decl(keyRemappingKeyofResult2.ts, 9, 29)) | ||
>ProvidedActor : Symbol(ProvidedActor, Decl(keyRemappingKeyofResult2.ts, 2, 28)) | ||
|
||
invoke: { | ||
>invoke : Symbol(StateMachineConfig.invoke, Decl(keyRemappingKeyofResult2.ts, 9, 61)) | ||
|
||
src: TActors["src"]; | ||
>src : Symbol(src, Decl(keyRemappingKeyofResult2.ts, 10, 11)) | ||
>TActors : Symbol(TActors, Decl(keyRemappingKeyofResult2.ts, 9, 29)) | ||
|
||
}; | ||
} | ||
|
||
declare function setup<TActors extends Record<string, unknown>>(_: { | ||
>setup : Symbol(setup, Decl(keyRemappingKeyofResult2.ts, 13, 1)) | ||
>TActors : Symbol(TActors, Decl(keyRemappingKeyofResult2.ts, 15, 23)) | ||
>Record : Symbol(Record, Decl(lib.es5.d.ts, --, --)) | ||
>_ : Symbol(_, Decl(keyRemappingKeyofResult2.ts, 15, 64)) | ||
|
||
actors: { | ||
>actors : Symbol(actors, Decl(keyRemappingKeyofResult2.ts, 15, 68)) | ||
|
||
[K in keyof TActors]: TActors[K]; | ||
>K : Symbol(K, Decl(keyRemappingKeyofResult2.ts, 17, 5)) | ||
>TActors : Symbol(TActors, Decl(keyRemappingKeyofResult2.ts, 15, 23)) | ||
>TActors : Symbol(TActors, Decl(keyRemappingKeyofResult2.ts, 15, 23)) | ||
>K : Symbol(K, Decl(keyRemappingKeyofResult2.ts, 17, 5)) | ||
|
||
}; | ||
}): { | ||
createMachine: ( | ||
>createMachine : Symbol(createMachine, Decl(keyRemappingKeyofResult2.ts, 19, 5)) | ||
|
||
config: StateMachineConfig< | ||
>config : Symbol(config, Decl(keyRemappingKeyofResult2.ts, 20, 18)) | ||
>StateMachineConfig : Symbol(StateMachineConfig, Decl(keyRemappingKeyofResult2.ts, 7, 2)) | ||
|
||
Values<{ | ||
>Values : Symbol(Values, Decl(keyRemappingKeyofResult2.ts, 0, 0)) | ||
|
||
[K in keyof TActors as K & string]: { | ||
>K : Symbol(K, Decl(keyRemappingKeyofResult2.ts, 23, 9)) | ||
>TActors : Symbol(TActors, Decl(keyRemappingKeyofResult2.ts, 15, 23)) | ||
>K : Symbol(K, Decl(keyRemappingKeyofResult2.ts, 23, 9)) | ||
|
||
src: K; | ||
>src : Symbol(src, Decl(keyRemappingKeyofResult2.ts, 23, 45)) | ||
>K : Symbol(K, Decl(keyRemappingKeyofResult2.ts, 23, 9)) | ||
|
||
logic: TActors[K]; | ||
>logic : Symbol(logic, Decl(keyRemappingKeyofResult2.ts, 24, 17)) | ||
>TActors : Symbol(TActors, Decl(keyRemappingKeyofResult2.ts, 15, 23)) | ||
>K : Symbol(K, Decl(keyRemappingKeyofResult2.ts, 23, 9)) | ||
|
||
}; | ||
}> | ||
>, | ||
) => void; | ||
}; | ||
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
Alternatively, maybe this could be kept but repurposed slightly. It would have to check if used template literal types don't refer to the type variable introduced by 'in' clause more than once. I don't think this is something the compiler would usually do in any other place so I'm hesitant to say that this would be a better solution.