Skip to content

Commit 3524423

Browse files
committed
Add deferred mapped types
This allows index signature inference to be inferred as well.
1 parent 7393433 commit 3524423

2 files changed

Lines changed: 43 additions & 27 deletions

File tree

src/compiler/checker.ts

Lines changed: 35 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -6114,6 +6114,23 @@ namespace ts {
61146114
}
61156115
}
61166116

6117+
function resolveDeferredMappedTypeMembers(type: DeferredMappedType) {
6118+
const indexInfo = type.targetIndexInfo;
6119+
const readonlyMask = type.mappedType.declaration.readonlyToken ? false : true;
6120+
const optionalMask = type.mappedType.declaration.questionToken ? 0 : SymbolFlags.Optional;
6121+
const stringIndexInfo = indexInfo && createIndexInfo(inferDeferredMappedType(indexInfo.type, type.mappedType), readonlyMask && indexInfo.isReadonly);
6122+
const members = createSymbolTable();
6123+
for (const prop of type.sourceProperties) {
6124+
const checkFlags = CheckFlags.Deferred | (readonlyMask && isReadonlySymbol(prop) ? CheckFlags.Readonly : 0);
6125+
const inferredProp = createSymbol(SymbolFlags.Property | prop.flags & optionalMask, prop.escapedName, checkFlags) as DeferredTransientSymbol;
6126+
inferredProp.declarations = prop.declarations;
6127+
inferredProp.propertyType = getTypeOfSymbol(prop);
6128+
inferredProp.mappedType = type.mappedType;
6129+
members.set(prop.escapedName, inferredProp);
6130+
}
6131+
setStructuredTypeMembers(type, members, emptyArray, emptyArray, stringIndexInfo, undefined);
6132+
}
6133+
61176134
/** Resolve the members of a mapped type { [P in K]: T } */
61186135
function resolveMappedTypeMembers(type: MappedType) {
61196136
const members: SymbolTable = createSymbolTable();
@@ -6253,6 +6270,9 @@ namespace ts {
62536270
else if ((<ObjectType>type).objectFlags & ObjectFlags.ClassOrInterface) {
62546271
resolveClassOrInterfaceMembers(<InterfaceType>type);
62556272
}
6273+
else if ((<DeferredMappedType>type).objectFlags & ObjectFlags.Deferred) {
6274+
resolveDeferredMappedTypeMembers(type as DeferredMappedType);
6275+
}
62566276
else if ((<ObjectType>type).objectFlags & ObjectFlags.Anonymous) {
62576277
resolveAnonymousTypeMembers(<AnonymousType>type);
62586278
}
@@ -11291,35 +11311,23 @@ namespace ts {
1129111311
}
1129211312

1129311313
function createDeferredMappedType(source: Type, target: MappedType) {
11294-
const properties = getPropertiesOfType(source);
11295-
let indexInfo = getIndexInfoOfType(source, IndexKind.String);
11296-
if (properties.length === 0 && !indexInfo) {
11314+
const properties = getPropertiesOfType(source);
11315+
let indexInfo = getIndexInfoOfType(source, IndexKind.String);
11316+
if (properties.length === 0 && !indexInfo) {
11317+
return undefined;
11318+
}
11319+
// If any property contains context sensitive functions that have been skipped, the source type
11320+
// is incomplete and we can't infer a meaningful input type.
11321+
for (const prop of properties) {
11322+
if (getTypeOfSymbol(prop).flags & TypeFlags.ContainsAnyFunctionType) {
1129711323
return undefined;
1129811324
}
11299-
const readonlyMask = target.declaration.readonlyToken ? false : true;
11300-
const optionalMask = target.declaration.questionToken ? 0 : SymbolFlags.Optional;
11301-
const members = createSymbolTable();
11302-
for (const prop of properties) {
11303-
const propType = getTypeOfSymbol(prop);
11304-
// If any property contains context sensitive functions that have been skipped, the source type
11305-
// is incomplete and we can't infer a meaningful input type.
11306-
if (propType.flags & TypeFlags.ContainsAnyFunctionType) {
11307-
return undefined;
11308-
}
11309-
const checkFlags = CheckFlags.Deferred | (readonlyMask && isReadonlySymbol(prop) ? CheckFlags.Readonly : 0);
11310-
const inferredProp = createSymbol(SymbolFlags.Property | prop.flags & optionalMask, prop.escapedName, checkFlags) as DeferredTransientSymbol;
11311-
inferredProp.declarations = prop.declarations;
11312-
inferredProp.propertyType = propType;
11313-
inferredProp.mappedType = target;
11314-
members.set(prop.escapedName, inferredProp);
11315-
}
11316-
if (indexInfo) {
11317-
// TODO: Defer this too.
11318-
// (probably the simplest way is to have a special type that defers the creation of (at least) its index info in
11319-
// resolveStructuredTypeMembers
11320-
indexInfo = createIndexInfo(inferDeferredMappedType(indexInfo.type, target), readonlyMask && indexInfo.isReadonly);
11321-
}
11322-
return createAnonymousType(undefined, members, emptyArray, emptyArray, indexInfo, undefined);
11325+
}
11326+
const deferred = createObjectType(ObjectFlags.Deferred | ObjectFlags.Anonymous, undefined) as DeferredMappedType;
11327+
deferred.mappedType = target;
11328+
deferred.sourceProperties = properties;
11329+
deferred.targetIndexInfo = indexInfo;
11330+
return deferred;
1132311331
}
1132411332

1132511333
function inferDeferredMappedType(sourceType: Type, target: MappedType): Type {

src/compiler/types.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3501,6 +3501,7 @@ namespace ts {
35013501
EvolvingArray = 1 << 8, // Evolving array type
35023502
ObjectLiteralPatternWithComputedProperties = 1 << 9, // Object literal pattern with computed properties
35033503
ContainsSpread = 1 << 10, // Object literal contains spread operation
3504+
Deferred = 1 << 11, // Object contains a deferred inferred property
35043505
ClassOrInterface = Class | Interface
35053506
}
35063507

@@ -3608,6 +3609,13 @@ namespace ts {
36083609
finalArrayType?: Type; // Final array type of evolving array type
36093610
}
36103611

3612+
/* @internal */
3613+
export interface DeferredMappedType extends ObjectType {
3614+
targetIndexInfo?: IndexInfo;
3615+
mappedType: MappedType;
3616+
sourceProperties: Symbol[];
3617+
}
3618+
36113619
/* @internal */
36123620
// Resolved object, union, or intersection type
36133621
export interface ResolvedType extends ObjectType, UnionOrIntersectionType {

0 commit comments

Comments
 (0)