Skip to content

Commit 4254fc2

Browse files
committed
Track recursive homomorphic mapped types by the symbol of their target
1 parent 5d04803 commit 4254fc2

File tree

2 files changed

+22
-4
lines changed

2 files changed

+22
-4
lines changed

src/compiler/checker.ts

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23484,10 +23484,18 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2348423484
// unique AST node.
2348523485
return (type as TypeReference).node!;
2348623486
}
23487-
if (type.symbol && !(getObjectFlags(type) & ObjectFlags.Anonymous && type.symbol.flags & SymbolFlags.Class)) {
23488-
// We track all object types that have an associated symbol (representing the origin of the type), but
23489-
// exclude the static side of classes from this check since it shares its symbol with the instance side.
23490-
return type.symbol;
23487+
if (type.symbol) {
23488+
// We track object types that have a symbol by that symbol (representing the origin of the type).
23489+
if (getObjectFlags(type) & ObjectFlags.Mapped) {
23490+
// When a homomorphic mapped type is applied to a type with a symbol, we use the symbol of that
23491+
// type as the recursion identity. This is a better strategy than using the symbol of the mapped
23492+
// type, which doesn't work well for recursive mapped types.
23493+
type = getMappedTargetWithSymbol(type);
23494+
}
23495+
if (!(getObjectFlags(type) & ObjectFlags.Anonymous && type.symbol.flags & SymbolFlags.Class)) {
23496+
// We exclude the static side of a class since it shares its symbol with the instance side.
23497+
return type.symbol;
23498+
}
2349123499
}
2349223500
if (isTupleType(type)) {
2349323501
return type.target;
@@ -23511,6 +23519,14 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker {
2351123519
return type;
2351223520
}
2351323521

23522+
function getMappedTargetWithSymbol(type: Type) {
23523+
let target = type;
23524+
while ((getObjectFlags(target) & ObjectFlags.InstantiatedMapped) === ObjectFlags.InstantiatedMapped && isMappedTypeWithKeyofConstraintDeclaration(target as MappedType)) {
23525+
target = getModifiersTypeFromMappedType(target as MappedType);
23526+
}
23527+
return target.symbol ? target : type;
23528+
}
23529+
2351423530
function isPropertyIdenticalTo(sourceProp: Symbol, targetProp: Symbol): boolean {
2351523531
return compareProperties(sourceProp, targetProp, compareTypesIdentical) !== Ternary.False;
2351623532
}

src/compiler/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6204,6 +6204,8 @@ export const enum ObjectFlags {
62046204
RequiresWidening = ContainsWideningType | ContainsObjectOrArrayLiteral,
62056205
/** @internal */
62066206
PropagatingFlags = ContainsWideningType | ContainsObjectOrArrayLiteral | NonInferrableType,
6207+
/** @internal */
6208+
InstantiatedMapped = Mapped | Instantiated,
62076209
// Object flags that uniquely identify the kind of ObjectType
62086210
/** @internal */
62096211
ObjectTypeKindMask = ClassOrInterface | Reference | Tuple | Anonymous | Mapped | ReverseMapped | EvolvingArray,

0 commit comments

Comments
 (0)