Skip to content

Commit 28bb535

Browse files
Copilotjakebailey
andauthored
Port TS#60528: Fix index type deferral crash on generic mapped types with name types (#2750)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: jakebailey <5341706+jakebailey@users.noreply.github.com>
1 parent cbceeec commit 28bb535

11 files changed

+217
-222
lines changed

internal/checker/checker.go

Lines changed: 9 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -26205,46 +26205,11 @@ func (c *Checker) getSubstitutionIntersection(t *Type) *Type {
2620526205
func (c *Checker) shouldDeferIndexType(t *Type, indexFlags IndexFlags) bool {
2620626206
return t.flags&TypeFlagsInstantiableNonPrimitive != 0 ||
2620726207
c.isGenericTupleType(t) ||
26208-
c.isGenericMappedType(t) && (!c.hasDistributiveNameType(t) || c.getMappedTypeNameTypeKind(t) == MappedTypeNameTypeKindRemapping) ||
26208+
c.isGenericMappedType(t) && c.getNameTypeFromMappedType(t) != nil ||
2620926209
t.flags&TypeFlagsUnion != 0 && indexFlags&IndexFlagsNoReducibleCheck == 0 && c.isGenericReducibleType(t) ||
2621026210
t.flags&TypeFlagsIntersection != 0 && c.maybeTypeOfKind(t, TypeFlagsInstantiable) && core.Some(t.Types(), c.IsEmptyAnonymousObjectType)
2621126211
}
2621226212

26213-
// 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
26214-
// 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
26215-
// want to perform the reduction when the name type of a mapped type is distributive with respect to the type variable
26216-
// introduced by the 'in' clause of the mapped type. Note that non-generic types are considered to be distributive because
26217-
// they're the same type regardless of what's being distributed over.
26218-
func (c *Checker) hasDistributiveNameType(mappedType *Type) bool {
26219-
typeVariable := c.getTypeParameterFromMappedType(mappedType)
26220-
var isDistributive func(*Type) bool
26221-
isDistributive = func(t *Type) bool {
26222-
switch {
26223-
case t.flags&(TypeFlagsAnyOrUnknown|TypeFlagsPrimitive|TypeFlagsNever|TypeFlagsTypeParameter|TypeFlagsObject|TypeFlagsNonPrimitive) != 0:
26224-
return true
26225-
case t.flags&TypeFlagsConditional != 0:
26226-
return t.AsConditionalType().root.isDistributive && t.AsConditionalType().checkType == typeVariable
26227-
case t.flags&TypeFlagsUnionOrIntersection != 0:
26228-
return core.Every(t.Types(), isDistributive)
26229-
case t.flags&TypeFlagsTemplateLiteral != 0:
26230-
return core.Every(t.AsTemplateLiteralType().types, isDistributive)
26231-
case t.flags&TypeFlagsIndexedAccess != 0:
26232-
return isDistributive(t.AsIndexedAccessType().objectType) && isDistributive(t.AsIndexedAccessType().indexType)
26233-
case t.flags&TypeFlagsSubstitution != 0:
26234-
return isDistributive(t.AsSubstitutionType().baseType) && isDistributive(t.AsSubstitutionType().constraint)
26235-
case t.flags&TypeFlagsStringMapping != 0:
26236-
return isDistributive(t.Target())
26237-
default:
26238-
return false
26239-
}
26240-
}
26241-
nameType := c.getNameTypeFromMappedType(mappedType)
26242-
if nameType == nil {
26243-
nameType = typeVariable
26244-
}
26245-
return isDistributive(nameType)
26246-
}
26247-
2624826213
func (c *Checker) getMappedTypeNameTypeKind(t *Type) MappedTypeNameTypeKind {
2624926214
nameType := c.getNameTypeFromMappedType(t)
2625026215
if nameType == nil {
@@ -26297,7 +26262,7 @@ func (c *Checker) getIndexTypeForMappedType(t *Type, indexFlags IndexFlags) *Typ
2629726262
// a circular definition. For this reason, we only eagerly manifest the keys if the constraint is non-generic.
2629826263
if c.isGenericIndexType(constraintType) {
2629926264
if c.isMappedTypeWithKeyofConstraintDeclaration(t) {
26300-
// We have a generic index and a homomorphic mapping (but a distributive key remapping) - we need to defer
26265+
// We have a generic index and a homomorphic mapping and a key remapping - we need to defer
2630126266
// the whole `keyof whatever` for later since it's not safe to resolve the shape of modifier type.
2630226267
return c.getIndexTypeForGenericType(t, indexFlags)
2630326268
}
@@ -26834,16 +26799,13 @@ func (c *Checker) getBaseConstraintOrType(t *Type) *Type {
2683426799
}
2683526800

2683626801
func (c *Checker) getBaseConstraintOfType(t *Type) *Type {
26837-
if t.flags&(TypeFlagsInstantiableNonPrimitive|TypeFlagsUnionOrIntersection|TypeFlagsTemplateLiteral|TypeFlagsStringMapping) != 0 || c.isGenericTupleType(t) {
26802+
if t.flags&(TypeFlagsInstantiableNonPrimitive|TypeFlagsUnionOrIntersection|TypeFlagsTemplateLiteral|TypeFlagsStringMapping|TypeFlagsIndex) != 0 || c.isGenericTupleType(t) {
2683826803
constraint := c.getResolvedBaseConstraint(t, nil)
2683926804
if constraint != c.noConstraintType && constraint != c.circularConstraintType {
2684026805
return constraint
2684126806
}
2684226807
return nil
2684326808
}
26844-
if t.flags&TypeFlagsIndex != 0 {
26845-
return c.stringNumberSymbolType
26846-
}
2684726809
return nil
2684826810
}
2684926811

@@ -26924,6 +26886,12 @@ func (c *Checker) computeBaseConstraint(t *Type, stack []RecursionId) *Type {
2692426886
}
2692526887
return nil
2692626888
case t.flags&TypeFlagsIndex != 0:
26889+
if c.isGenericMappedType(t.AsIndexType().target) {
26890+
mappedType := t.AsIndexType().target
26891+
if c.getNameTypeFromMappedType(mappedType) != nil && !c.isMappedTypeWithKeyofConstraintDeclaration(mappedType) {
26892+
return c.getNextBaseConstraint(c.getIndexTypeForMappedType(mappedType, IndexFlagsNone), stack)
26893+
}
26894+
}
2692726895
return c.stringNumberSymbolType
2692826896
case t.flags&TypeFlagsTemplateLiteral != 0:
2692926897
types := t.Types()

internal/checker/types.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -450,7 +450,7 @@ const (
450450
TypeFlagsInstantiable = TypeFlagsInstantiableNonPrimitive | TypeFlagsInstantiablePrimitive
451451
TypeFlagsStructuredOrInstantiable = TypeFlagsStructuredType | TypeFlagsInstantiable
452452
TypeFlagsObjectFlagsType = TypeFlagsAny | TypeFlagsNullable | TypeFlagsNever | TypeFlagsObject | TypeFlagsUnion | TypeFlagsIntersection
453-
TypeFlagsSimplifiable = TypeFlagsIndexedAccess | TypeFlagsConditional
453+
TypeFlagsSimplifiable = TypeFlagsIndexedAccess | TypeFlagsConditional | TypeFlagsIndex
454454
TypeFlagsSingleton = TypeFlagsAny | TypeFlagsUnknown | TypeFlagsString | TypeFlagsNumber | TypeFlagsBoolean | TypeFlagsBigInt | TypeFlagsESSymbol | TypeFlagsVoid | TypeFlagsUndefined | TypeFlagsNull | TypeFlagsNever | TypeFlagsNonPrimitive
455455
// 'TypeFlagsNarrowable' types are types where narrowing actually narrows.
456456
// This *should* be every type other than null, undefined, void, and never

internal/testrunner/compiler_runner.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,6 @@ func (r *CompilerBaselineRunner) EnumerateTestFiles() []string {
8484
}
8585

8686
var skippedTests = []string{
87-
// Broken until further porting work is done
88-
"mappedTypeAsClauseRecursiveNoCrash1.ts",
89-
9087
// Flaky
9188
"for-of29.ts",
9289

testdata/baselines/reference/submodule/compiler/keyRemappingKeyofResult2.errors.txt

Lines changed: 0 additions & 51 deletions
This file was deleted.

testdata/baselines/reference/submodule/compiler/keyRemappingKeyofResult2.errors.txt.diff

Lines changed: 0 additions & 55 deletions
This file was deleted.
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
//// [tests/cases/conformance/types/mapped/mappedTypeAsClauseRecursiveNoCrash1.ts] ////
2+
3+
=== mappedTypeAsClauseRecursiveNoCrash1.ts ===
4+
// https://github.com/microsoft/TypeScript/issues/60476
5+
6+
export type FlattenType<Source extends object, Target> = {
7+
>FlattenType : Symbol(FlattenType, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 0, 0))
8+
>Source : Symbol(Source, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 2, 24))
9+
>Target : Symbol(Target, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 2, 46))
10+
11+
[Key in keyof Source as Key extends string
12+
>Key : Symbol(Key, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 3, 3))
13+
>Source : Symbol(Source, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 2, 24))
14+
>Key : Symbol(Key, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 3, 3))
15+
16+
? Source[Key] extends object
17+
>Source : Symbol(Source, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 2, 24))
18+
>Key : Symbol(Key, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 3, 3))
19+
20+
? `${Key}.${keyof FlattenType<Source[Key], Target> & string}`
21+
>Key : Symbol(Key, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 3, 3))
22+
>FlattenType : Symbol(FlattenType, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 0, 0))
23+
>Source : Symbol(Source, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 2, 24))
24+
>Key : Symbol(Key, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 3, 3))
25+
>Target : Symbol(Target, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 2, 46))
26+
27+
: Key
28+
>Key : Symbol(Key, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 3, 3))
29+
30+
: never]-?: Target;
31+
>Target : Symbol(Target, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 2, 46))
32+
33+
};
34+
35+
type FieldSelect = {
36+
>FieldSelect : Symbol(FieldSelect, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 8, 2))
37+
38+
table: string;
39+
>table : Symbol(table, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 10, 20))
40+
41+
field: string;
42+
>field : Symbol(field, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 11, 16))
43+
44+
};
45+
46+
type Address = {
47+
>Address : Symbol(Address, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 13, 2))
48+
49+
postCode: string;
50+
>postCode : Symbol(postCode, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 15, 16))
51+
52+
description: string;
53+
>description : Symbol(description, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 16, 19))
54+
55+
address: string;
56+
>address : Symbol(address, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 17, 22))
57+
58+
};
59+
60+
type User = {
61+
>User : Symbol(User, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 19, 2))
62+
63+
id: number;
64+
>id : Symbol(id, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 21, 13))
65+
66+
name: string;
67+
>name : Symbol(name, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 22, 13))
68+
69+
address: Address;
70+
>address : Symbol(address, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 23, 15))
71+
>Address : Symbol(Address, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 13, 2))
72+
73+
};
74+
75+
type FlattenedUser = FlattenType<User, FieldSelect>;
76+
>FlattenedUser : Symbol(FlattenedUser, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 25, 2))
77+
>FlattenType : Symbol(FlattenType, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 0, 0))
78+
>User : Symbol(User, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 19, 2))
79+
>FieldSelect : Symbol(FieldSelect, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 8, 2))
80+
81+
type FlattenedUserKeys = keyof FlattenType<User, FieldSelect>;
82+
>FlattenedUserKeys : Symbol(FlattenedUserKeys, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 27, 52))
83+
>FlattenType : Symbol(FlattenType, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 0, 0))
84+
>User : Symbol(User, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 19, 2))
85+
>FieldSelect : Symbol(FieldSelect, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 8, 2))
86+
87+
export type FlattenTypeKeys<Source extends object, Target> = keyof {
88+
>FlattenTypeKeys : Symbol(FlattenTypeKeys, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 28, 62))
89+
>Source : Symbol(Source, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 30, 28))
90+
>Target : Symbol(Target, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 30, 50))
91+
92+
[Key in keyof Source as Key extends string
93+
>Key : Symbol(Key, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 31, 3))
94+
>Source : Symbol(Source, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 30, 28))
95+
>Key : Symbol(Key, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 31, 3))
96+
97+
? Source[Key] extends object
98+
>Source : Symbol(Source, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 30, 28))
99+
>Key : Symbol(Key, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 31, 3))
100+
101+
? `${Key}.${keyof FlattenType<Source[Key], Target> & string}`
102+
>Key : Symbol(Key, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 31, 3))
103+
>FlattenType : Symbol(FlattenType, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 0, 0))
104+
>Source : Symbol(Source, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 30, 28))
105+
>Key : Symbol(Key, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 31, 3))
106+
>Target : Symbol(Target, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 30, 50))
107+
108+
: Key
109+
>Key : Symbol(Key, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 31, 3))
110+
111+
: never]-?: Target;
112+
>Target : Symbol(Target, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 30, 50))
113+
114+
};
115+
116+
type FlattenedUserKeys2 = FlattenTypeKeys<User, FieldSelect>;
117+
>FlattenedUserKeys2 : Symbol(FlattenedUserKeys2, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 36, 2))
118+
>FlattenTypeKeys : Symbol(FlattenTypeKeys, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 28, 62))
119+
>User : Symbol(User, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 19, 2))
120+
>FieldSelect : Symbol(FieldSelect, Decl(mappedTypeAsClauseRecursiveNoCrash1.ts, 8, 2))
121+

0 commit comments

Comments
 (0)