-
Notifications
You must be signed in to change notification settings - Fork 13.4k
Skip unnecessary instatiation of anonymous types #7448
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5045,42 +5045,29 @@ namespace ts { | |
| return t => t === source1 ? target1 : t === source2 ? target2 : t; | ||
| } | ||
|
|
||
| function createTypeMapper(sources: Type[], targets: Type[]): TypeMapper { | ||
| switch (sources.length) { | ||
| case 1: return createUnaryTypeMapper(sources[0], targets[0]); | ||
| case 2: return createBinaryTypeMapper(sources[0], targets[0], sources[1], targets[1]); | ||
| } | ||
| function createArrayTypeMapper(sources: Type[], targets: Type[]): TypeMapper { | ||
| return t => { | ||
| for (let i = 0; i < sources.length; i++) { | ||
| if (t === sources[i]) { | ||
| return targets[i]; | ||
| return targets ? targets[i] : anyType; | ||
| } | ||
| } | ||
| return t; | ||
| }; | ||
| } | ||
|
|
||
| function createUnaryTypeEraser(source: Type): TypeMapper { | ||
| return t => t === source ? anyType : t; | ||
| } | ||
|
|
||
| function createBinaryTypeEraser(source1: Type, source2: Type): TypeMapper { | ||
| return t => t === source1 || t === source2 ? anyType : t; | ||
| function createTypeMapper(sources: Type[], targets: Type[]): TypeMapper { | ||
| const count = sources.length; | ||
| const mapper: TypeMapper = | ||
| count == 1 ? createUnaryTypeMapper(sources[0], targets ? targets[0] : anyType) : | ||
| count == 2 ? createBinaryTypeMapper(sources[0], targets ? targets[0] : anyType, sources[1], targets ? targets[1] : anyType) : | ||
| createArrayTypeMapper(sources, targets); | ||
| mapper.mappedTypes = sources; | ||
| return mapper; | ||
| } | ||
|
|
||
| function createTypeEraser(sources: Type[]): TypeMapper { | ||
| switch (sources.length) { | ||
| case 1: return createUnaryTypeEraser(sources[0]); | ||
| case 2: return createBinaryTypeEraser(sources[0], sources[1]); | ||
| } | ||
| return t => { | ||
| for (const source of sources) { | ||
| if (t === source) { | ||
| return anyType; | ||
| } | ||
| } | ||
| return t; | ||
| }; | ||
| return createTypeMapper(sources, undefined); | ||
| } | ||
|
|
||
| function getInferenceMapper(context: InferenceContext): TypeMapper { | ||
|
|
@@ -5095,6 +5082,7 @@ namespace ts { | |
| } | ||
| return t; | ||
| }; | ||
| mapper.mappedTypes = context.typeParameters; | ||
| mapper.context = context; | ||
| context.mapper = mapper; | ||
| } | ||
|
|
@@ -5106,7 +5094,9 @@ namespace ts { | |
| } | ||
|
|
||
| function combineTypeMappers(mapper1: TypeMapper, mapper2: TypeMapper): TypeMapper { | ||
| return t => instantiateType(mapper1(t), mapper2); | ||
| const mapper: TypeMapper = t => instantiateType(mapper1(t), mapper2); | ||
| mapper.mappedTypes = mapper1.mappedTypes; | ||
| return mapper; | ||
| } | ||
|
|
||
| function cloneTypeParameter(typeParameter: TypeParameter): TypeParameter { | ||
|
|
@@ -5201,13 +5191,70 @@ namespace ts { | |
| return result; | ||
| } | ||
|
|
||
| function isSymbolInScopeOfMappedTypeParameter(symbol: Symbol, mapper: TypeMapper) { | ||
| 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. | ||
| let node = symbol.declarations[0].parent; | ||
| while (node) { | ||
| 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 declaration = <DeclarationWithTypeParameters>node; | ||
| if (declaration.typeParameters) { | ||
| for (const d of declaration.typeParameters) { | ||
| if (contains(mappedTypes, getDeclaredTypeOfTypeParameter(d.symbol))) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why does it work to say
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We know that type parameters are never subject to global symbol merging, so |
||
| 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.ModuleDeclaration: | ||
| case SyntaxKind.SourceFile: | ||
| return false; | ||
| } | ||
| node = node.parent; | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| function instantiateType(type: Type, mapper: TypeMapper): Type { | ||
| if (type && mapper !== identityMapper) { | ||
| if (type.flags & TypeFlags.TypeParameter) { | ||
| return mapper(<TypeParameter>type); | ||
| } | ||
| if (type.flags & TypeFlags.Anonymous) { | ||
| return type.symbol && type.symbol.flags & (SymbolFlags.Function | SymbolFlags.Method | SymbolFlags.Class | SymbolFlags.TypeLiteral | SymbolFlags.ObjectLiteral) ? | ||
| // 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) && | ||
| (type.flags & TypeFlags.Instantiated || isSymbolInScopeOfMappedTypeParameter(type.symbol, mapper)) ? | ||
| instantiateAnonymousType(<AnonymousType>type, mapper) : type; | ||
| } | ||
| if (type.flags & TypeFlags.Reference) { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In #7138 I had the equivalent of
mapper1.mappedTypes.concat(mapper2.mappedTypes)which I'm pretty sure is wrong. But it should really bemapper1.mappedTypes.intersect(mapper2.mappedTypes), I think.Since this is an optimization, I don't think it will make a practical difference, because constructing a pathological case would be very difficult.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, from an outside point of view, a combined mapper only re-maps the type parameters of
mapper1(the one in front). The fact that more mapping happens below is really irrelevant. Either way, with the current analysis we should never actually see a combined mapper because that only happens when we're instantiating already instantiated types (and we don't analyse those now).