Merge pull request #20711 from Microsoft/defer-inference-for-recursive-mapped-types

Defer inference for homomorphic mapped types
This commit is contained in:
Nathan Shively-Sanders
2018-01-05 16:01:26 -08:00
committed by GitHub
17 changed files with 434 additions and 104 deletions

View File

@@ -339,6 +339,7 @@ namespace ts {
const jsObjectLiteralIndexInfo = createIndexInfo(anyType, /*isReadonly*/ false);
const globals = createSymbolTable();
const reverseMappedCache = createMap<Type | undefined>();
let ambientModulesCache: Symbol[] | undefined;
/**
* List of every ambient module with a "*" wildcard.
@@ -2860,7 +2861,10 @@ namespace ts {
typeElements.push(<ConstructSignatureDeclaration>signatureToSignatureDeclarationHelper(signature, SyntaxKind.ConstructSignature, context));
}
if (resolvedType.stringIndexInfo) {
typeElements.push(indexInfoToIndexSignatureDeclarationHelper(resolvedType.stringIndexInfo, IndexKind.String, context));
const indexInfo = resolvedType.objectFlags & ObjectFlags.ReverseMapped ?
createIndexInfo(anyType, resolvedType.stringIndexInfo.isReadonly, resolvedType.stringIndexInfo.declaration) :
resolvedType.stringIndexInfo;
typeElements.push(indexInfoToIndexSignatureDeclarationHelper(indexInfo, IndexKind.String, context));
}
if (resolvedType.numberIndexInfo) {
typeElements.push(indexInfoToIndexSignatureDeclarationHelper(resolvedType.numberIndexInfo, IndexKind.Number, context));
@@ -2872,7 +2876,7 @@ namespace ts {
}
for (const propertySymbol of properties) {
const propertyType = getTypeOfSymbol(propertySymbol);
const propertyType = getCheckFlags(propertySymbol) & CheckFlags.ReverseMapped ? anyType : getTypeOfSymbol(propertySymbol);
const saveEnclosingDeclaration = context.enclosingDeclaration;
context.enclosingDeclaration = undefined;
const propertyName = symbolToName(propertySymbol, context, SymbolFlags.Value, /*expectsIdentifier*/ true);
@@ -3681,7 +3685,10 @@ namespace ts {
writePunctuation(writer, SyntaxKind.SemicolonToken);
writer.writeLine();
}
buildIndexSignatureDisplay(resolved.stringIndexInfo, writer, IndexKind.String, enclosingDeclaration, globalFlags, symbolStack);
const stringIndexInfo = resolved.objectFlags & ObjectFlags.ReverseMapped && resolved.stringIndexInfo ?
createIndexInfo(anyType, resolved.stringIndexInfo.isReadonly, resolved.stringIndexInfo.declaration) :
resolved.stringIndexInfo;
buildIndexSignatureDisplay(stringIndexInfo, writer, IndexKind.String, enclosingDeclaration, globalFlags, symbolStack);
buildIndexSignatureDisplay(resolved.numberIndexInfo, writer, IndexKind.Number, enclosingDeclaration, globalFlags, symbolStack);
for (const p of resolved.properties) {
if (globalFlags & TypeFormatFlags.WriteClassExpressionAsTypeLiteral) {
@@ -3692,7 +3699,7 @@ namespace ts {
writer.reportPrivateInBaseOfClassExpression(symbolName(p));
}
}
const t = getTypeOfSymbol(p);
const t = getCheckFlags(p) & CheckFlags.ReverseMapped ? anyType : getTypeOfSymbol(p);
if (p.flags & (SymbolFlags.Function | SymbolFlags.Method) && !getPropertiesOfObjectType(t).length) {
const signatures = getSignaturesOfType(t, SignatureKind.Call);
for (const signature of signatures) {
@@ -4900,6 +4907,9 @@ namespace ts {
if (getCheckFlags(symbol) & CheckFlags.Instantiated) {
return getTypeOfInstantiatedSymbol(symbol);
}
if (getCheckFlags(symbol) & CheckFlags.ReverseMapped) {
return getTypeOfReverseMappedSymbol(symbol as ReverseMappedSymbol);
}
if (symbol.flags & (SymbolFlags.Variable | SymbolFlags.Property)) {
return getTypeOfVariableOrParameterOrProperty(symbol);
}
@@ -6110,6 +6120,23 @@ namespace ts {
}
}
function resolveReverseMappedTypeMembers(type: ReverseMappedType) {
const indexInfo = getIndexInfoOfType(type.source, IndexKind.String);
const readonlyMask = type.mappedType.declaration.readonlyToken ? false : true;
const optionalMask = type.mappedType.declaration.questionToken ? 0 : SymbolFlags.Optional;
const stringIndexInfo = indexInfo && createIndexInfo(inferReverseMappedType(indexInfo.type, type.mappedType), readonlyMask && indexInfo.isReadonly);
const members = createSymbolTable();
for (const prop of getPropertiesOfType(type.source)) {
const checkFlags = CheckFlags.ReverseMapped | (readonlyMask && isReadonlySymbol(prop) ? CheckFlags.Readonly : 0);
const inferredProp = createSymbol(SymbolFlags.Property | prop.flags & optionalMask, prop.escapedName, checkFlags) as ReverseMappedSymbol;
inferredProp.declarations = prop.declarations;
inferredProp.propertyType = getTypeOfSymbol(prop);
inferredProp.mappedType = type.mappedType;
members.set(prop.escapedName, inferredProp);
}
setStructuredTypeMembers(type, members, emptyArray, emptyArray, stringIndexInfo, undefined);
}
/** Resolve the members of a mapped type { [P in K]: T } */
function resolveMappedTypeMembers(type: MappedType) {
const members: SymbolTable = createSymbolTable();
@@ -6249,6 +6276,9 @@ namespace ts {
else if ((<ObjectType>type).objectFlags & ObjectFlags.ClassOrInterface) {
resolveClassOrInterfaceMembers(<InterfaceType>type);
}
else if ((<ReverseMappedType>type).objectFlags & ObjectFlags.ReverseMapped) {
resolveReverseMappedTypeMembers(type as ReverseMappedType);
}
else if ((<ObjectType>type).objectFlags & ObjectFlags.Anonymous) {
resolveAnonymousTypeMembers(<AnonymousType>type);
}
@@ -11275,42 +11305,45 @@ namespace ts {
* property is computed by inferring from the source property type to X for the type
* variable T[P] (i.e. we treat the type T[P] as the type variable we're inferring for).
*/
function inferTypeForHomomorphicMappedType(source: Type, target: MappedType, mappedTypeStack: string[]): Type {
function inferTypeForHomomorphicMappedType(source: Type, target: MappedType): Type {
const key = source.id + "," + target.id;
if (reverseMappedCache.has(key)) {
return reverseMappedCache.get(key);
}
reverseMappedCache.set(key, undefined);
const type = createReverseMappedType(source, target);
reverseMappedCache.set(key, type);
return type;
}
function createReverseMappedType(source: Type, target: MappedType) {
const properties = getPropertiesOfType(source);
let indexInfo = getIndexInfoOfType(source, IndexKind.String);
if (properties.length === 0 && !indexInfo) {
if (properties.length === 0 && !getIndexInfoOfType(source, IndexKind.String)) {
return undefined;
}
const typeParameter = <TypeParameter>getIndexedAccessType((<IndexType>getConstraintTypeFromMappedType(target)).type, getTypeParameterFromMappedType(target));
const inference = createInferenceInfo(typeParameter);
const inferences = [inference];
const templateType = getTemplateTypeFromMappedType(target);
const readonlyMask = target.declaration.readonlyToken ? false : true;
const optionalMask = target.declaration.questionToken ? 0 : SymbolFlags.Optional;
const members = createSymbolTable();
// If any property contains context sensitive functions that have been skipped, the source type
// is incomplete and we can't infer a meaningful input type.
for (const prop of properties) {
const propType = getTypeOfSymbol(prop);
// If any property contains context sensitive functions that have been skipped, the source type
// is incomplete and we can't infer a meaningful input type.
if (propType.flags & TypeFlags.ContainsAnyFunctionType) {
if (getTypeOfSymbol(prop).flags & TypeFlags.ContainsAnyFunctionType) {
return undefined;
}
const checkFlags = readonlyMask && isReadonlySymbol(prop) ? CheckFlags.Readonly : 0;
const inferredProp = createSymbol(SymbolFlags.Property | prop.flags & optionalMask, prop.escapedName, checkFlags);
inferredProp.declarations = prop.declarations;
inferredProp.type = inferTargetType(propType);
members.set(prop.escapedName, inferredProp);
}
if (indexInfo) {
indexInfo = createIndexInfo(inferTargetType(indexInfo.type), readonlyMask && indexInfo.isReadonly);
}
return createAnonymousType(undefined, members, emptyArray, emptyArray, indexInfo, undefined);
const reversed = createObjectType(ObjectFlags.ReverseMapped | ObjectFlags.Anonymous, /*symbol*/ undefined) as ReverseMappedType;
reversed.source = source;
reversed.mappedType = target;
return reversed;
}
function inferTargetType(sourceType: Type): Type {
inference.candidates = undefined;
inferTypes(inferences, sourceType, templateType, 0, mappedTypeStack);
return inference.candidates ? getUnionType(inference.candidates, UnionReduction.Subtype) : emptyObjectType;
}
function getTypeOfReverseMappedSymbol(symbol: ReverseMappedSymbol) {
return inferReverseMappedType(symbol.propertyType, symbol.mappedType);
}
function inferReverseMappedType(sourceType: Type, target: MappedType): Type {
const typeParameter = <TypeParameter>getIndexedAccessType((<IndexType>getConstraintTypeFromMappedType(target)).type, getTypeParameterFromMappedType(target));
const templateType = getTemplateTypeFromMappedType(target);
const inference = createInferenceInfo(typeParameter);
inferTypes([inference], sourceType, templateType);
return inference.candidates ? getUnionType(inference.candidates, UnionReduction.Subtype) : emptyObjectType;
}
function getUnmatchedProperty(source: Type, target: Type, requireOptionalProperties: boolean) {
@@ -11326,7 +11359,7 @@ namespace ts {
return undefined;
}
function inferTypes(inferences: InferenceInfo[], originalSource: Type, originalTarget: Type, priority: InferencePriority = 0, mappedTypeStack?: string[]) {
function inferTypes(inferences: InferenceInfo[], originalSource: Type, originalTarget: Type, priority: InferencePriority = 0) {
let symbolStack: Symbol[];
let visited: Map<boolean>;
inferFromTypes(originalSource, originalTarget);
@@ -11543,13 +11576,7 @@ namespace ts {
// such that direct inferences to T get priority over inferences to Partial<T>, for example.
const inference = getInferenceInfoForType((<IndexType>constraintType).type);
if (inference && !inference.isFixed) {
const key = (source.symbol ? getSymbolId(source.symbol) + "," : "") + getSymbolId(target.symbol);
if (contains(mappedTypeStack, key)) {
return;
}
(mappedTypeStack || (mappedTypeStack = [])).push(key);
const inferredType = inferTypeForHomomorphicMappedType(source, <MappedType>target, mappedTypeStack);
mappedTypeStack.pop();
const inferredType = inferTypeForHomomorphicMappedType(source, <MappedType>target);
if (inferredType) {
const savePriority = priority;
priority |= InferencePriority.MappedType;

View File

@@ -3251,6 +3251,7 @@ namespace ts {
ContainsPrivate = 1 << 8, // Synthetic property with private constituent(s)
ContainsStatic = 1 << 9, // Synthetic property with static constituent(s)
Late = 1 << 10, // Late-bound symbol for a computed property with a dynamic name
ReverseMapped = 1 << 11, // property of reverse-inferred homomorphic mapped type.
Synthetic = SyntheticProperty | SyntheticMethod
}
@@ -3260,6 +3261,12 @@ namespace ts {
isRestParameter?: boolean;
}
/* @internal */
export interface ReverseMappedSymbol extends TransientSymbol {
propertyType: Type;
mappedType: MappedType;
}
export const enum InternalSymbolName {
Call = "__call", // Call signatures
Constructor = "__constructor", // Constructor implementations
@@ -3494,6 +3501,7 @@ namespace ts {
EvolvingArray = 1 << 8, // Evolving array type
ObjectLiteralPatternWithComputedProperties = 1 << 9, // Object literal pattern with computed properties
ContainsSpread = 1 << 10, // Object literal contains spread operation
ReverseMapped = 1 << 11, // Object contains a property from a reverse-mapped type
ClassOrInterface = Class | Interface
}
@@ -3601,6 +3609,12 @@ namespace ts {
finalArrayType?: Type; // Final array type of evolving array type
}
/* @internal */
export interface ReverseMappedType extends ObjectType {
source: Type;
mappedType: MappedType;
}
/* @internal */
// Resolved object, union, or intersection type
export interface ResolvedType extends ObjectType, UnionOrIntersectionType {