diff --git a/src/services/services.ts b/src/services/services.ts index 914ae98746a..511d76d30f7 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -1844,6 +1844,13 @@ namespace ts { owners: string[]; } + // Internal interface used for tracking state in find all references when checking + // the inheritance hierarchy of property access expressions + interface SymbolInheritanceState { + symbol: Symbol; + cachedInheritanceResults: Map; + } + export interface DisplayPartsSymbolWriter extends SymbolWriter { displayParts(): SymbolDisplayPart[]; } @@ -6546,13 +6553,17 @@ namespace ts { // symbol of the local type of the symbol the property is being accessed on. This is because our search // symbol may have a different parent symbol if the local type's symbol does not declare the property // being accessed (i.e. it is declared in some parent class or interface) - let parentSymbol: Symbol = undefined; - let inheritanceCache: Map = undefined; - if (implementations && searchLocation.parent && searchLocation.parent.kind === SyntaxKind.PropertyAccessExpression && searchLocation === (searchLocation.parent).name) { + let parentSymbols: SymbolInheritanceState[] = undefined; + + if (implementations && isRightSideOfPropertyAccess(searchLocation)) { const localParentType = typeChecker.getTypeAtLocation((searchLocation.parent).expression); - if (localParentType && localParentType.symbol && localParentType.symbol.getFlags() & (SymbolFlags.Interface | SymbolFlags.Class) && localParentType.symbol.parent !== searchSymbol.parent) { - parentSymbol = localParentType.symbol; - inheritanceCache = createMap(); + if (localParentType) { + if (localParentType.symbol && isClassOrInterfaceReference(localParentType.symbol) && localParentType.symbol.parent !== searchSymbol.parent) { + parentSymbols = [createSymbolInheritanceState(localParentType.symbol)]; + } + else if (localParentType.getFlags() & TypeFlags.UnionOrIntersection) { + parentSymbols = map(getSymbolsForComponentTypes(localParentType), createSymbolInheritanceState); + } } } @@ -6596,7 +6607,7 @@ namespace ts { if (referenceSymbol) { const referenceSymbolDeclaration = referenceSymbol.valueDeclaration; const shorthandValueSymbol = typeChecker.getShorthandAssignmentValueSymbol(referenceSymbolDeclaration); - const relatedSymbol = getRelatedSymbol(searchSymbols, referenceSymbol, referenceLocation, parentSymbol, inheritanceCache); + const relatedSymbol = getRelatedSymbol(searchSymbols, referenceSymbol, referenceLocation, parentSymbols); if (relatedSymbol) { const referenceEntry = implementations ? getImplementationReferenceEntryForNode(referenceLocation) : getReferenceEntryFromNode(referenceLocation); @@ -6691,6 +6702,18 @@ namespace ts { } } + function getSymbolsForComponentTypes(type: UnionOrIntersectionType, result: Symbol[] = []): Symbol[] { + for (const componentType of type.types) { + if (componentType.symbol && componentType.symbol.getFlags() & (SymbolFlags.Class | SymbolFlags.Interface)) { + result.push(componentType.symbol); + } + if (componentType.getFlags() & TypeFlags.UnionOrIntersection) { + getSymbolsForComponentTypes(componentType, result); + } + } + return result; + } + function getContainingTypeReference(node: Node): Node { if (node) { if (node.kind === SyntaxKind.TypeReference) { @@ -6786,7 +6809,7 @@ namespace ts { return inherits; } - function searchTypeReference(typeReference: ExpressionWithTypeArguments, cachedResults: Map) { + function searchTypeReference(typeReference: ExpressionWithTypeArguments, cachedResults: Map): boolean { if (typeReference) { const type = typeChecker.getTypeAtLocation(typeReference); if (type && type.symbol) { @@ -6797,6 +6820,13 @@ namespace ts { } } + function createSymbolInheritanceState(symbol: Symbol): SymbolInheritanceState { + return { + symbol, + cachedInheritanceResults: createMap() + }; + } + function getReferencesForSuperKeyword(superKeyword: Node): ReferencedSymbol[] { let searchSpaceNode = getSuperContainer(superKeyword, /*stopOnFunctions*/ false); if (!searchSpaceNode) { @@ -7144,7 +7174,7 @@ namespace ts { } } - function getRelatedSymbol(searchSymbols: Symbol[], referenceSymbol: Symbol, referenceLocation: Node, parentSymbol: Symbol, inheritanceCache: Map): Symbol { + function getRelatedSymbol(searchSymbols: Symbol[], referenceSymbol: Symbol, referenceLocation: Node, parentSymbols: SymbolInheritanceState[]): Symbol { if (searchSymbols.indexOf(referenceSymbol) >= 0) { return referenceSymbol; } @@ -7153,7 +7183,7 @@ namespace ts { // symbols but by looking up for related symbol of this alias so it can handle multiple level of indirectness. const aliasSymbol = getAliasSymbolForPropertyNameSymbol(referenceSymbol, referenceLocation); if (aliasSymbol) { - return getRelatedSymbol(searchSymbols, aliasSymbol, referenceLocation, parentSymbol, inheritanceCache); + return getRelatedSymbol(searchSymbols, aliasSymbol, referenceLocation, parentSymbols); } // If the reference location is in an object literal, try to get the contextual type for the @@ -7198,7 +7228,13 @@ namespace ts { // Finally, try all properties with the same name in any type the containing type extended or implemented, and // see if any is in the list. If we were passed a parent symbol, only include types that are subtypes of the // parent symbol - if (rootSymbol.parent && rootSymbol.parent.flags & (SymbolFlags.Class | SymbolFlags.Interface) && (!parentSymbol || inheritsFrom(rootSymbol.parent, parentSymbol, inheritanceCache))) { + if (rootSymbol.parent && rootSymbol.parent.flags & (SymbolFlags.Class | SymbolFlags.Interface)) { + if (parentSymbols) { + if (!forEach(parentSymbols, ({symbol, cachedInheritanceResults}) => inheritsFrom(rootSymbol.parent, symbol, cachedInheritanceResults))) { + return undefined; + } + } + const result: Symbol[] = []; getPropertySymbolsFromBaseTypes(rootSymbol.parent, rootSymbol.getName(), result, /*previousIterationSymbolsCache*/ createMap()); return forEach(result, s => searchSymbols.indexOf(s) >= 0 ? s : undefined); diff --git a/tests/cases/fourslash/goToImplementationInterfaceMethod_10.ts b/tests/cases/fourslash/goToImplementationInterfaceMethod_10.ts index eed8c418043..76d4e750ad9 100644 --- a/tests/cases/fourslash/goToImplementationInterfaceMethod_10.ts +++ b/tests/cases/fourslash/goToImplementationInterfaceMethod_10.ts @@ -1,9 +1,12 @@ /// -// Should handle intersection types +// Should handle union and intersection types -//// interface Foo { +//// interface BaseFoo { //// hello(): void; +//// } +//// +//// interface Foo extends BaseFoo { //// aloha(): void; //// } //// @@ -13,12 +16,16 @@ //// } //// //// class FooImpl implements Foo { -//// hello() {/**FooImpl*/} +//// [|hello() {/**FooImpl*/}|] //// aloha() {} //// } //// +//// class BaseFooImpl implements BaseFoo { +//// hello() {/**BaseFooImpl*/} // Should not show up +//// } +//// //// class BarImpl implements Bar { -//// hello() {/**BarImpl*/} +//// [|hello() {/**BarImpl*/}|] //// goodbye() {} //// } //// @@ -28,9 +35,15 @@ //// goodbye() {} //// } //// -//// function someFunction(x: Foo & Bar) { -//// x.he/*function_call*/llo(); +//// function someFunction(x: Foo | Bar) { +//// x.he/*function_call0*/llo(); +//// } +//// +//// function anotherFunction(x: Foo & Bar) { +//// x.he/*function_call1*/llo(); //// } -goTo.marker("function_call"); -verify.allRangesAppearInImplementationList(); +for (var i = 0; i < 2; i++) { + goTo.marker("function_call" + i); + verify.allRangesAppearInImplementationList(); +} diff --git a/tests/cases/fourslash/goToImplementationInterfaceMethod_11.ts b/tests/cases/fourslash/goToImplementationInterfaceMethod_11.ts index 7cf0533de5f..7d948006fb3 100644 --- a/tests/cases/fourslash/goToImplementationInterfaceMethod_11.ts +++ b/tests/cases/fourslash/goToImplementationInterfaceMethod_11.ts @@ -1,36 +1,12 @@ -/// - -// Should handle union types - -//// interface Foo { -//// hello(): void; -//// aloha(): void; -//// } -//// -//// interface Bar { -//// hello(): void; -//// goodbye(): void; -//// } -//// -//// class FooImpl implements Foo { -//// [|hello() {/**FooImpl*/}|] -//// aloha() {} -//// } -//// -//// class BarImpl implements Bar { -//// [|hello() {/**BarImpl*/}|] -//// goodbye() {} -//// } -//// -//// class FooAndBarImpl implements Foo, Bar { -//// [|hello() {/**FooAndBarImpl*/}|] -//// aloha() {} -//// goodbye() {} -//// } -//// -//// function someFunction(x: Foo | Bar) { -//// x.he/*function_call*/llo(); -//// } - -goTo.marker("function_call"); -verify.allRangesAppearInImplementationList(); +/// + +// Should handle members of object literals in type assertion expressions + +//// interface Foo { +//// hel/*reference*/lo(): void; +//// } +//// +//// var x = { [|hello: () => {}|] }; +//// var y = (((({ [|hello: () => {}|] })))); +goTo.marker("reference"); +verify.allRangesAppearInImplementationList(); \ No newline at end of file diff --git a/tests/cases/fourslash/goToImplementationInterfaceMethod_12.ts b/tests/cases/fourslash/goToImplementationInterfaceMethod_12.ts deleted file mode 100644 index 7d948006fb3..00000000000 --- a/tests/cases/fourslash/goToImplementationInterfaceMethod_12.ts +++ /dev/null @@ -1,12 +0,0 @@ -/// - -// Should handle members of object literals in type assertion expressions - -//// interface Foo { -//// hel/*reference*/lo(): void; -//// } -//// -//// var x = { [|hello: () => {}|] }; -//// var y = (((({ [|hello: () => {}|] })))); -goTo.marker("reference"); -verify.allRangesAppearInImplementationList(); \ No newline at end of file