Improved caching scheme for anonymous types

This commit is contained in:
Anders Hejlsberg 2017-09-02 10:27:48 -07:00
parent c7b4ed3a91
commit b65ff647c1
3 changed files with 107 additions and 181 deletions

View File

@ -4794,22 +4794,39 @@ namespace ts {
return typeParameters;
}
// Appends the outer type parameters of a node to a set of type parameters and returns the resulting set. The function
// allocates a new array if the input type parameter set is undefined, but otherwise it modifies the set in-place and
// returns the same array.
function appendOuterTypeParameters(typeParameters: TypeParameter[], node: Node): TypeParameter[] {
// Return the outer type parameters of a node or undefined if the node has no outer type parameters.
function getOuterTypeParameters(node: Node, includeThisTypes?: boolean): TypeParameter[] {
while (true) {
node = node.parent;
if (!node) {
return typeParameters;
return undefined;
}
if (node.kind === SyntaxKind.ClassDeclaration || node.kind === SyntaxKind.ClassExpression ||
node.kind === SyntaxKind.FunctionDeclaration || node.kind === SyntaxKind.FunctionExpression ||
node.kind === SyntaxKind.MethodDeclaration || node.kind === SyntaxKind.ArrowFunction) {
const declarations = (<ClassLikeDeclaration | FunctionLikeDeclaration>node).typeParameters;
if (declarations) {
return appendTypeParameters(appendOuterTypeParameters(typeParameters, node), declarations);
}
switch (node.kind) {
case SyntaxKind.ClassDeclaration:
case SyntaxKind.ClassExpression:
case SyntaxKind.InterfaceDeclaration:
case SyntaxKind.CallSignature:
case SyntaxKind.ConstructSignature:
case SyntaxKind.MethodSignature:
case SyntaxKind.FunctionType:
case SyntaxKind.ConstructorType:
case SyntaxKind.JSDocFunctionType:
case SyntaxKind.FunctionDeclaration:
case SyntaxKind.MethodDeclaration:
case SyntaxKind.FunctionExpression:
case SyntaxKind.ArrowFunction:
case SyntaxKind.TypeAliasDeclaration:
case SyntaxKind.JSDocTemplateTag:
case SyntaxKind.MappedType:
const outerTypeParameters = getOuterTypeParameters(node, includeThisTypes);
if (node.kind === SyntaxKind.MappedType) {
return append(outerTypeParameters, getDeclaredTypeOfTypeParameter(getSymbolOfNode((<MappedTypeNode>node).typeParameter)));
}
const outerAndOwnTypeParameters = appendTypeParameters(outerTypeParameters, getEffectiveTypeParameterDeclarations(<DeclarationWithTypeParameters>node) || emptyArray);
const thisType = includeThisTypes &&
(node.kind === SyntaxKind.ClassDeclaration || node.kind === SyntaxKind.ClassExpression || node.kind === SyntaxKind.InterfaceDeclaration) &&
getDeclaredTypeOfClassOrInterface(getSymbolOfNode(node)).thisType;
return thisType ? append(outerAndOwnTypeParameters, thisType) : outerAndOwnTypeParameters;
}
}
}
@ -4817,7 +4834,7 @@ namespace ts {
// The outer type parameters are those defined by enclosing generic classes, methods, or functions.
function getOuterTypeParametersOfClassOrInterface(symbol: Symbol): TypeParameter[] {
const declaration = symbol.flags & SymbolFlags.Class ? symbol.valueDeclaration : getDeclarationOfKind(symbol, SyntaxKind.InterfaceDeclaration);
return appendOuterTypeParameters(/*typeParameters*/ undefined, declaration);
return getOuterTypeParameters(declaration);
}
// The local type parameters are the combined set of type parameters from all declarations of the class,
@ -6800,7 +6817,7 @@ namespace ts {
const id = getTypeListId(typeArguments);
let instantiation = links.instantiations.get(id);
if (!instantiation) {
links.instantiations.set(id, instantiation = instantiateTypeNoAlias(type, createTypeMapper(typeParameters, fillMissingTypeArguments(typeArguments, typeParameters, getMinTypeArgumentCount(typeParameters)))));
links.instantiations.set(id, instantiation = instantiateType(type, createTypeMapper(typeParameters, fillMissingTypeArguments(typeArguments, typeParameters, getMinTypeArgumentCount(typeParameters)))));
}
return instantiation;
}
@ -8024,11 +8041,6 @@ namespace ts {
return instantiateList(signatures, mapper, instantiateSignature);
}
function instantiateCached<T extends Type>(type: T, mapper: TypeMapper, instantiator: (item: T, mapper: TypeMapper) => T): T {
const instantiations = mapper.instantiations || (mapper.instantiations = []);
return <T>instantiations[type.id] || (instantiations[type.id] = instantiator(type, mapper));
}
function makeUnaryTypeMapper(source: Type, target: Type) {
return (t: Type) => t === source ? target : t;
}
@ -8050,11 +8062,9 @@ namespace ts {
function createTypeMapper(sources: TypeParameter[], targets: Type[]): TypeMapper {
Debug.assert(targets === undefined || sources.length === targets.length);
const mapper: TypeMapper = sources.length === 1 ? makeUnaryTypeMapper(sources[0], targets ? targets[0] : anyType) :
return sources.length === 1 ? makeUnaryTypeMapper(sources[0], targets ? targets[0] : anyType) :
sources.length === 2 ? makeBinaryTypeMapper(sources[0], targets ? targets[0] : anyType, sources[1], targets ? targets[1] : anyType) :
makeArrayTypeMapper(sources, targets);
mapper.mappedTypes = sources;
return mapper;
makeArrayTypeMapper(sources, targets);
}
function createTypeEraser(sources: TypeParameter[]): TypeMapper {
@ -8065,10 +8075,8 @@ namespace ts {
* Maps forward-references to later types parameters to the empty object type.
* This is used during inference when instantiating type parameter defaults.
*/
function createBackreferenceMapper(typeParameters: TypeParameter[], index: number) {
const mapper: TypeMapper = t => indexOf(typeParameters, t) >= index ? emptyObjectType : t;
mapper.mappedTypes = typeParameters;
return mapper;
function createBackreferenceMapper(typeParameters: TypeParameter[], index: number): TypeMapper {
return t => indexOf(typeParameters, t) >= index ? emptyObjectType : t;
}
function isInferenceContext(mapper: TypeMapper): mapper is InferenceContext {
@ -8086,15 +8094,11 @@ namespace ts {
}
function combineTypeMappers(mapper1: TypeMapper, mapper2: TypeMapper): TypeMapper {
const mapper: TypeMapper = t => instantiateType(mapper1(t), mapper2);
mapper.mappedTypes = concatenate(mapper1.mappedTypes, mapper2.mappedTypes);
return mapper;
return t => instantiateType(mapper1(t), mapper2);
}
function createReplacementMapper(source: Type, target: Type, baseMapper: TypeMapper) {
const mapper: TypeMapper = t => t === source ? target : baseMapper(t);
mapper.mappedTypes = baseMapper.mappedTypes;
return mapper;
function createReplacementMapper(source: Type, target: Type, baseMapper: TypeMapper): TypeMapper {
return t => t === source ? target : baseMapper(t);
}
function cloneTypeParameter(typeParameter: TypeParameter): TypeParameter {
@ -8174,13 +8178,39 @@ namespace ts {
return result;
}
function instantiateAnonymousType(type: AnonymousType, mapper: TypeMapper): AnonymousType {
const result = <AnonymousType>createObjectType(ObjectFlags.Anonymous | ObjectFlags.Instantiated, type.symbol);
result.target = type.objectFlags & ObjectFlags.Instantiated ? type.target : type;
result.mapper = type.objectFlags & ObjectFlags.Instantiated ? combineTypeMappers(type.mapper, mapper) : mapper;
result.aliasSymbol = type.aliasSymbol;
result.aliasTypeArguments = instantiateTypes(type.aliasTypeArguments, mapper);
return result;
function getAnonymousTypeInstantiation(type: AnonymousType, mapper: TypeMapper) {
if (type.objectFlags & ObjectFlags.Instantiated) {
mapper = combineTypeMappers(type.mapper, mapper);
type = type.target;
}
const symbol = type.symbol;
const links = getSymbolLinks(symbol);
if (!links.typeParameters) {
// This first time an anonymous type is instantiated we compute and store a list of the type
// parameters that are in scope (and therefore potentially referenced).
const typeParameters = getOuterTypeParameters(symbol.declarations[0], /*includeThisTypes*/ true);
links.typeParameters = typeParameters || emptyArray;
if (typeParameters) {
links.instantiations = createMap<Type>();
links.instantiations.set(getTypeListId(typeParameters), type);
}
}
const typeParameters = links.typeParameters;
if (typeParameters.length) {
// We are instantiating an anonymous type that has one or more type parameters in scope. Apply the
// mapper to the type parameters to produce the effective list of type arguments, and compute the
// instantiation cache key from the type IDs of the type arguments.
const typeArguments = map(typeParameters, mapper);
const id = getTypeListId(typeArguments);
let result = links.instantiations.get(id);
if (!result) {
const newMapper = createTypeMapper(typeParameters, typeArguments);
result = type.objectFlags & ObjectFlags.Mapped ? instantiateMappedType(<MappedType>type, newMapper) : instantiateAnonymousType(type, newMapper);
links.instantiations.set(id, result);
}
return result;
}
return type;
}
function instantiateMappedType(type: MappedType, mapper: TypeMapper): Type {
@ -8197,164 +8227,64 @@ namespace ts {
if (typeVariable !== mappedTypeVariable) {
return mapType(mappedTypeVariable, t => {
if (isMappableType(t)) {
return instantiateMappedObjectType(type, createReplacementMapper(typeVariable, t, mapper));
return instantiateAnonymousType(type, createReplacementMapper(typeVariable, t, mapper));
}
return t;
});
}
}
}
return instantiateMappedObjectType(type, mapper);
return instantiateAnonymousType(type, mapper);
}
function isMappableType(type: Type) {
return type.flags & (TypeFlags.TypeParameter | TypeFlags.Object | TypeFlags.Intersection | TypeFlags.IndexedAccess);
}
function instantiateMappedObjectType(type: MappedType, mapper: TypeMapper): Type {
const result = <MappedType>createObjectType(ObjectFlags.Mapped | ObjectFlags.Instantiated, type.symbol);
result.declaration = type.declaration;
result.mapper = type.mapper ? combineTypeMappers(type.mapper, mapper) : mapper;
function instantiateAnonymousType(type: AnonymousType, mapper: TypeMapper): AnonymousType {
const result = <AnonymousType>createObjectType(type.objectFlags | ObjectFlags.Instantiated, type.symbol);
if (type.objectFlags & ObjectFlags.Mapped) {
(<MappedType>result).declaration = (<MappedType>type).declaration;
}
result.target = type;
result.mapper = mapper;
result.aliasSymbol = type.aliasSymbol;
result.aliasTypeArguments = instantiateTypes(type.aliasTypeArguments, mapper);
return result;
}
function isSymbolInScopeOfMappedTypeParameter(symbol: Symbol, mapper: TypeMapper) {
if (!(symbol.declarations && symbol.declarations.length)) {
return false;
}
const mappedTypes = mapper.mappedTypes;
// Starting with the parent of the symbol's declaration, check if the mapper maps any of
// the type parameters introduced by enclosing declarations. We just pick the first
// declaration since multiple declarations will all have the same parent anyway.
return !!findAncestor(symbol.declarations[0], node => {
if (node.kind === SyntaxKind.ModuleDeclaration || node.kind === SyntaxKind.SourceFile) {
return "quit";
}
switch (node.kind) {
case SyntaxKind.FunctionType:
case SyntaxKind.ConstructorType:
case SyntaxKind.FunctionDeclaration:
case SyntaxKind.MethodDeclaration:
case SyntaxKind.MethodSignature:
case SyntaxKind.Constructor:
case SyntaxKind.CallSignature:
case SyntaxKind.ConstructSignature:
case SyntaxKind.IndexSignature:
case SyntaxKind.GetAccessor:
case SyntaxKind.SetAccessor:
case SyntaxKind.FunctionExpression:
case SyntaxKind.ArrowFunction:
case SyntaxKind.ClassDeclaration:
case SyntaxKind.ClassExpression:
case SyntaxKind.InterfaceDeclaration:
case SyntaxKind.TypeAliasDeclaration:
const typeParameters = getEffectiveTypeParameterDeclarations(node as DeclarationWithTypeParameters);
if (typeParameters) {
for (const d of typeParameters) {
if (contains(mappedTypes, getDeclaredTypeOfTypeParameter(getSymbolOfNode(d)))) {
return true;
}
}
}
if (isClassLike(node) || node.kind === SyntaxKind.InterfaceDeclaration) {
const thisType = getDeclaredTypeOfClassOrInterface(getSymbolOfNode(node)).thisType;
if (thisType && contains(mappedTypes, thisType)) {
return true;
}
}
break;
case SyntaxKind.MappedType:
if (contains(mappedTypes, getDeclaredTypeOfTypeParameter(getSymbolOfNode((<MappedTypeNode>node).typeParameter)))) {
return true;
}
break;
case SyntaxKind.JSDocFunctionType:
const func = node as JSDocFunctionType;
for (const p of func.parameters) {
if (contains(mappedTypes, getTypeOfNode(p))) {
return true;
}
}
break;
}
});
}
function isTopLevelTypeAlias(symbol: Symbol) {
if (symbol.declarations && symbol.declarations.length) {
const parentKind = symbol.declarations[0].parent.kind;
return parentKind === SyntaxKind.SourceFile || parentKind === SyntaxKind.ModuleBlock;
}
return false;
}
function instantiateType(type: Type, mapper: TypeMapper): Type {
if (type && mapper !== identityMapper) {
// If we are instantiating a type that has a top-level type alias, obtain the instantiation through
// the type alias instead in order to share instantiations for the same type arguments. This can
// dramatically reduce the number of structurally identical types we generate. Note that we can only
// perform this optimization for top-level type aliases. Consider:
//
// function f1<T>(x: T) {
// type Foo<X> = { x: X, t: T };
// let obj: Foo<T> = { x: x };
// return obj;
// }
// function f2<U>(x: U) { return f1(x); }
// let z = f2(42);
//
// Above, the declaration of f2 has an inferred return type that is an instantiation of f1's Foo<X>
// equivalent to { x: U, t: U }. When instantiating this return type, we can't go back to Foo<X>'s
// cache because all cached instantiations are of the form { x: ???, t: T }, i.e. they have not been
// instantiated for T. Instead, we need to further instantiate the { x: U, t: U } form.
if (type.aliasSymbol && isTopLevelTypeAlias(type.aliasSymbol)) {
if (type.aliasTypeArguments) {
return getTypeAliasInstantiation(type.aliasSymbol, instantiateTypes(type.aliasTypeArguments, mapper));
if (type.flags & TypeFlags.TypeParameter) {
return mapper(<TypeParameter>type);
}
if (type.flags & TypeFlags.Object) {
if ((<ObjectType>type).objectFlags & ObjectFlags.Anonymous) {
// If the anonymous type originates in a declaration of a function, method, class, or
// interface, in an object type literal, or in an object literal expression, we may need
// to instantiate the type because it might reference a type parameter.
return type.symbol && type.symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.TypeLiteral | SymbolFlags.ObjectLiteral) && type.symbol.declarations ?
getAnonymousTypeInstantiation(<AnonymousType>type, mapper) : type;
}
if ((<ObjectType>type).objectFlags & ObjectFlags.Mapped) {
return getAnonymousTypeInstantiation(<MappedType>type, mapper);
}
if ((<ObjectType>type).objectFlags & ObjectFlags.Reference) {
return createTypeReference((<TypeReference>type).target, instantiateTypes((<TypeReference>type).typeArguments, mapper));
}
return type;
}
return instantiateTypeNoAlias(type, mapper);
}
return type;
}
function instantiateTypeNoAlias(type: Type, mapper: TypeMapper): Type {
if (type.flags & TypeFlags.TypeParameter) {
return mapper(<TypeParameter>type);
}
if (type.flags & TypeFlags.Object) {
if ((<ObjectType>type).objectFlags & ObjectFlags.Anonymous) {
// If the anonymous type originates in a declaration of a function, method, class, or
// interface, in an object type literal, or in an object literal expression, we may need
// to instantiate the type because it might reference a type parameter. We skip instantiation
// if none of the type parameters that are in scope in the type's declaration are mapped by
// the given mapper, however we can only do that analysis if the type isn't itself an
// instantiation.
return type.symbol &&
type.symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.TypeLiteral | SymbolFlags.ObjectLiteral) &&
((<ObjectType>type).objectFlags & ObjectFlags.Instantiated || isSymbolInScopeOfMappedTypeParameter(type.symbol, mapper)) ?
instantiateCached(type, mapper, instantiateAnonymousType) : type;
if (type.flags & TypeFlags.Union && !(type.flags & TypeFlags.Primitive)) {
return getUnionType(instantiateTypes((<UnionType>type).types, mapper), /*subtypeReduction*/ false, type.aliasSymbol, instantiateTypes(type.aliasTypeArguments, mapper));
}
if ((<ObjectType>type).objectFlags & ObjectFlags.Mapped) {
return instantiateCached(type, mapper, instantiateMappedType);
if (type.flags & TypeFlags.Intersection) {
return getIntersectionType(instantiateTypes((<IntersectionType>type).types, mapper), type.aliasSymbol, instantiateTypes(type.aliasTypeArguments, mapper));
}
if ((<ObjectType>type).objectFlags & ObjectFlags.Reference) {
return createTypeReference((<TypeReference>type).target, instantiateTypes((<TypeReference>type).typeArguments, mapper));
if (type.flags & TypeFlags.Index) {
return getIndexType(instantiateType((<IndexType>type).type, mapper));
}
if (type.flags & TypeFlags.IndexedAccess) {
return getIndexedAccessType(instantiateType((<IndexedAccessType>type).objectType, mapper), instantiateType((<IndexedAccessType>type).indexType, mapper));
}
}
if (type.flags & TypeFlags.Union && !(type.flags & TypeFlags.Primitive)) {
return getUnionType(instantiateTypes((<UnionType>type).types, mapper), /*subtypeReduction*/ false, type.aliasSymbol, instantiateTypes(type.aliasTypeArguments, mapper));
}
if (type.flags & TypeFlags.Intersection) {
return getIntersectionType(instantiateTypes((<IntersectionType>type).types, mapper), type.aliasSymbol, instantiateTypes(type.aliasTypeArguments, mapper));
}
if (type.flags & TypeFlags.Index) {
return getIndexType(instantiateType((<IndexType>type).type, mapper));
}
if (type.flags & TypeFlags.IndexedAccess) {
return getIndexedAccessType(instantiateType((<IndexedAccessType>type).objectType, mapper), instantiateType((<IndexedAccessType>type).indexType, mapper));
}
return type;
}
@ -10368,7 +10298,6 @@ namespace ts {
function createInferenceContext(signature: Signature, flags: InferenceFlags, compareTypes?: TypeComparer, baseInferences?: InferenceInfo[]): InferenceContext {
const inferences = baseInferences ? map(baseInferences, cloneInferenceInfo) : map(signature.typeParameters, createInferenceInfo);
const context = mapper as InferenceContext;
context.mappedTypes = signature.typeParameters;
context.signature = signature;
context.inferences = inferences;
context.flags = flags;

View File

@ -3331,13 +3331,12 @@ namespace ts {
}
/* @internal */
export interface MappedType extends ObjectType {
export interface MappedType extends AnonymousType {
declaration: MappedTypeNode;
typeParameter?: TypeParameter;
constraintType?: Type;
templateType?: Type;
modifiersType?: Type;
mapper?: TypeMapper; // Instantiation mapper
}
export interface EvolvingArrayType extends ObjectType {
@ -3469,8 +3468,6 @@ namespace ts {
/* @internal */
export interface TypeMapper {
(t: TypeParameter): Type;
mappedTypes?: TypeParameter[]; // Types mapped by this mapper
instantiations?: Type[]; // Cache of instantiations created using this type mapper.
}
export const enum InferencePriority {

View File

@ -373,7 +373,7 @@ namespace ts.SignatureHelp {
isVariadic = false; // type parameter lists are not variadic
prefixDisplayParts.push(punctuationPart(SyntaxKind.LessThanToken));
// Use `.mapper` to ensure we get the generic type arguments even if this is an instantiated version of the signature.
const typeParameters = candidateSignature.mapper ? candidateSignature.mapper.mappedTypes : candidateSignature.typeParameters;
const typeParameters = candidateSignature.typeParameters; // !!! candidateSignature.mapper ? candidateSignature.mapper.mappedTypes : candidateSignature.typeParameters;
signatureHelpParameters = typeParameters && typeParameters.length > 0 ? map(typeParameters, createSignatureHelpParameterForTypeParameter) : emptyArray;
suffixDisplayParts.push(punctuationPart(SyntaxKind.GreaterThanToken));
const parameterParts = mapToDisplayParts(writer =>