From b8923b3de1a8319e0a87f8d792701d07d78e2d14 Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Tue, 7 Oct 2014 22:48:16 -0700 Subject: [PATCH 01/10] Support symbol kind for union properties --- src/compiler/checker.ts | 10 ++++++++-- src/compiler/types.ts | 2 ++ src/services/services.ts | 11 +++++++++- .../completionEntryForUnionProperty.ts | 20 +++++++++++++++++++ 4 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 tests/cases/fourslash/completionEntryForUnionProperty.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index cf2099badde37..523cd13924803 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -92,7 +92,8 @@ module ts { getContextualType: getContextualType, getFullyQualifiedName: getFullyQualifiedName, getResolvedSignature: getResolvedSignature, - getEnumMemberValue: getEnumMemberValue + getEnumMemberValue: getEnumMemberValue, + getUnionTypesOfUnionProperty: getUnionTypesOfUnionProperty }; var undefinedSymbol = createSymbol(SymbolFlags.Property | SymbolFlags.Transient, "undefined"); @@ -1750,6 +1751,10 @@ module ts { return links.type; } + function getUnionTypesOfUnionProperty(symbol: Symbol): Type[] { + return (symbol.flags & SymbolFlags.UnionProperty) ? getSymbolLinks(symbol).unionType.types : undefined; + } + function getTypeOfSymbol(symbol: Symbol): Type { if (symbol.flags & (SymbolFlags.Variable | SymbolFlags.Property)) { return getTypeOfVariableOrParameterOrProperty(symbol); @@ -3583,7 +3588,8 @@ module ts { } function getBestCommonType(types: Type[], contextualType?: Type): Type { - return contextualType && isSupertypeOfEach(contextualType, types) ? contextualType : getUnionType(types); } + return contextualType && isSupertypeOfEach(contextualType, types) ? contextualType : getUnionType(types); + } function isTypeOfObjectLiteral(type: Type): boolean { return (type.flags & TypeFlags.Anonymous) && type.symbol && (type.symbol.flags & SymbolFlags.ObjectLiteral) ? true : false; diff --git a/src/compiler/types.ts b/src/compiler/types.ts index f6691353c22f8..0d943915e448f 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -657,6 +657,8 @@ module ts { getContextualType(node: Node): Type; getResolvedSignature(node: CallExpression, candidatesOutArray?: Signature[]): Signature; + getUnionTypesOfUnionProperty(symbol: Symbol): Type[]; + // Returns the constant value of this enum member, or 'undefined' if the enum member has a // computed value. getEnumMemberValue(node: EnumMember): number; diff --git a/src/services/services.ts b/src/services/services.ts index 3e222450aa712..53e6f0e140122 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2284,8 +2284,16 @@ module ts { } } + function getConcreteSymbol(symbol: Symbol): Symbol { + if (symbol.flags & SymbolFlags.UnionProperty) { + var types = typeInfoResolver.getUnionTypesOfUnionProperty(symbol); + symbol = typeInfoResolver.getPropertyOfType(types[0], symbol.name); + } + return typeInfoResolver.getRootSymbol(symbol); + } + function getSymbolKind(symbol: Symbol): string { - var flags = typeInfoResolver.getRootSymbol(symbol).getFlags(); + var flags = getConcreteSymbol(symbol).getFlags(); if (flags & SymbolFlags.Module) return ScriptElementKind.moduleElement; if (flags & SymbolFlags.Class) return ScriptElementKind.classElement; @@ -2344,6 +2352,7 @@ module ts { } function getSymbolModifiers(symbol: Symbol): string { + symbol = getConcreteSymbol(symbol); return symbol && symbol.declarations && symbol.declarations.length > 0 ? getNodeModifiers(symbol.declarations[0]) : ScriptElementKindModifier.none; diff --git a/tests/cases/fourslash/completionEntryForUnionProperty.ts b/tests/cases/fourslash/completionEntryForUnionProperty.ts new file mode 100644 index 0000000000000..6d5a3627a9c4a --- /dev/null +++ b/tests/cases/fourslash/completionEntryForUnionProperty.ts @@ -0,0 +1,20 @@ +/// + +////interface One { +//// commonProperty: number; +//// commonFunction(): number; +////} +//// +////interface Two { +//// commonProperty: string +//// commonFunction(): number; +////} +//// +////var x : One | Two; +//// +////x./**/ + +goTo.marker(); +verify.memberListContains("commonProperty", "string | number", undefined, undefined, "property"); +verify.memberListContains("commonFunction", "() => number", undefined, undefined, "method"); +verify.memberListCount(2); \ No newline at end of file From 5669f63430e42aad473a7b84b51ab71f792ac53f Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Tue, 7 Oct 2014 22:49:06 -0700 Subject: [PATCH 02/10] add test for quick info --- .../fourslash/quickinfoForUnionProperty.ts | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 tests/cases/fourslash/quickinfoForUnionProperty.ts diff --git a/tests/cases/fourslash/quickinfoForUnionProperty.ts b/tests/cases/fourslash/quickinfoForUnionProperty.ts new file mode 100644 index 0000000000000..665d03f94357b --- /dev/null +++ b/tests/cases/fourslash/quickinfoForUnionProperty.ts @@ -0,0 +1,27 @@ +/// + +////interface One { +//// commonProperty: number; +//// commonFunction(): number; +////} +//// +////interface Two { +//// commonProperty: string +//// commonFunction(): number; +////} +//// +////var /*1*/x : One | Two; +//// +////x./*2*/commonProperty; +////x./*3*/commonFunction; + + +goTo.marker("1"); +verify.quickInfoIs("One | Two", "", "x", "var"); + + +goTo.marker("2"); +verify.quickInfoIs("string | number", "", "commonProperty", "property"); + +goTo.marker("3"); +verify.quickInfoIs("() => number", "", "commonFunction", "method"); From c439ae4a9d5e7931ce9d369f91d5350817654d46 Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Tue, 7 Oct 2014 23:17:05 -0700 Subject: [PATCH 03/10] Add support for union properties in goto def --- src/services/services.ts | 39 ++++++++++++------- .../goToDefinitionUnionTypeProperty.ts | 24 ++++++++++++ .../goToDefinitionUnionTypeProperty2.ts | 25 ++++++++++++ 3 files changed, 75 insertions(+), 13 deletions(-) create mode 100644 tests/cases/fourslash/goToDefinitionUnionTypeProperty.ts create mode 100644 tests/cases/fourslash/goToDefinitionUnionTypeProperty2.ts diff --git a/src/services/services.ts b/src/services/services.ts index 53e6f0e140122..3fc2ae7f05a7b 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2563,6 +2563,25 @@ module ts { return false; } + function getDefinitionFromSymbol(symbol: Symbol, location: Node, result: DefinitionInfo[]): void { + var declarations = symbol.getDeclarations(); + if (declarations) { + var symbolName = typeInfoResolver.symbolToString(symbol, location); + var symbolKind = getSymbolKind(symbol); + var containerSymbol = symbol.parent; + var containerName = containerSymbol ? typeInfoResolver.symbolToString(containerSymbol, location) : ""; + var containerKind = containerSymbol ? getSymbolKind(symbol) : ""; + + if (!tryAddConstructSignature(symbol, location, symbolKind, symbolName, containerName, result) && + !tryAddCallSignature(symbol, location, symbolKind, symbolName, containerName, result)) { + // Just add all the declarations. + forEach(declarations, declaration => { + result.push(getDefinitionInfo(declaration, symbolKind, symbolName, containerName)); + }); + } + } + } + synchronizeHostData(); filename = TypeScript.switchToForwardSlashes(filename); @@ -2601,26 +2620,20 @@ module ts { // Could not find a symbol e.g. node is string or number keyword, // or the symbol was an internal symbol and does not have a declaration e.g. undefined symbol - if (!symbol || !(symbol.getDeclarations())) { + if (!symbol) { return undefined; } var result: DefinitionInfo[] = []; - var declarations = symbol.getDeclarations(); - var symbolName = typeInfoResolver.symbolToString(symbol, node); - var symbolKind = getSymbolKind(symbol); - var containerSymbol = symbol.parent; - var containerName = containerSymbol ? typeInfoResolver.symbolToString(containerSymbol, node) : ""; - var containerKind = containerSymbol ? getSymbolKind(symbol) : ""; - - if (!tryAddConstructSignature(symbol, node, symbolKind, symbolName, containerName, result) && - !tryAddCallSignature(symbol, node, symbolKind, symbolName, containerName, result)) { - // Just add all the declarations. - forEach(declarations, declaration => { - result.push(getDefinitionInfo(declaration, symbolKind, symbolName, containerName)); + if (symbol.flags & SymbolFlags.UnionProperty) { + forEach(typeInfoResolver.getUnionTypesOfUnionProperty(symbol), t => { + getDefinitionFromSymbol(typeInfoResolver.getPropertyOfType(t, symbol.name), node, result); }); } + else { + getDefinitionFromSymbol(symbol, node, result); + } return result; } diff --git a/tests/cases/fourslash/goToDefinitionUnionTypeProperty.ts b/tests/cases/fourslash/goToDefinitionUnionTypeProperty.ts new file mode 100644 index 0000000000000..74b5e2aad8a9f --- /dev/null +++ b/tests/cases/fourslash/goToDefinitionUnionTypeProperty.ts @@ -0,0 +1,24 @@ +/// + +////interface One { +//// /*propertyDefinition1*/commonProperty: number; +//// commonFunction(): number; +////} +//// +////interface Two { +//// /*propertyDefinition2*/commonProperty: string +//// commonFunction(): number; +////} +//// +////var x : One | Two; +//// +////x./*propertyReference*/commonProperty; +////x./*3*/commonFunction; + +goTo.marker("propertyReference"); +goTo.definition(0); +verify.caretAtMarker("propertyDefinition1"); + +goTo.marker("propertyReference"); +goTo.definition(1); +verify.caretAtMarker("propertyDefinition2"); diff --git a/tests/cases/fourslash/goToDefinitionUnionTypeProperty2.ts b/tests/cases/fourslash/goToDefinitionUnionTypeProperty2.ts new file mode 100644 index 0000000000000..6536bb76e7dda --- /dev/null +++ b/tests/cases/fourslash/goToDefinitionUnionTypeProperty2.ts @@ -0,0 +1,25 @@ +/// +////interface HasAOrB { +//// /*propertyDefinition1*/a: string; +//// b: string; +////} +//// +////interface One { +//// common: { /*propertyDefinition2*/a : number; }; +////} +//// +////interface Two { +//// common: HasAOrB; +////} +//// +////var x : One | Two; +//// +////x.common./*propertyReference*/a; + +goTo.marker("propertyReference"); +goTo.definition(0); +verify.caretAtMarker("propertyDefinition1"); + +goTo.marker("propertyReference"); +goTo.definition(1); +verify.caretAtMarker("propertyDefinition2"); From 779db6e76a8b117e2b12a8b6b0df72fb73411b12 Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Wed, 8 Oct 2014 23:21:56 -0700 Subject: [PATCH 04/10] Support find all refs on union properties --- src/compiler/checker.ts | 20 ++- src/compiler/types.ts | 2 +- src/services/services.ts | 132 +++++++++--------- ...ontextuallyTypedObjectLiteralProperties.ts | 18 +-- ...ncesForContextuallyTypedUnionProperties.ts | 40 ++++++ .../fourslash/referencesForUnionProperties.ts | 35 +++++ 6 files changed, 168 insertions(+), 79 deletions(-) create mode 100644 tests/cases/fourslash/referencesForContextuallyTypedUnionProperties.ts create mode 100644 tests/cases/fourslash/referencesForUnionProperties.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 523cd13924803..7138cd9588acd 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -88,7 +88,7 @@ module ts { symbolToString: symbolToString, symbolToDisplayParts: symbolToDisplayParts, getAugmentedPropertiesOfApparentType: getAugmentedPropertiesOfApparentType, - getRootSymbol: getRootSymbol, + getRootSymbols: getRootSymbols, getContextualType: getContextualType, getFullyQualifiedName: getFullyQualifiedName, getResolvedSignature: getResolvedSignature, @@ -7863,8 +7863,22 @@ module ts { } } - function getRootSymbol(symbol: Symbol) { - return ((symbol.flags & SymbolFlags.Transient) && getSymbolLinks(symbol).target) || symbol; + function getRootSymbols(symbol: Symbol): Symbol[] { + if (symbol.flags & SymbolFlags.UnionProperty) { + var symbols: Symbol[] = []; + var name = symbol.name; + forEach(getSymbolLinks(symbol).unionType.types, t => { + symbols.push(getPropertyOfType(t, name)); + }); + return symbols; + } + else if (symbol.flags & SymbolFlags.Transient) { + var target = getSymbolLinks(symbol).target; + if (target) { + return [target]; + } + } + return [symbol]; } // Emitter support diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 0d943915e448f..0aa25e2efebd3 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -653,7 +653,7 @@ module ts { symbolToDisplayParts(symbol: Symbol, enclosingDeclaration?: Node, meaning?: SymbolFlags): SymbolDisplayPart[]; getFullyQualifiedName(symbol: Symbol): string; getAugmentedPropertiesOfApparentType(type: Type): Symbol[]; - getRootSymbol(symbol: Symbol): Symbol; + getRootSymbols(symbol: Symbol): Symbol[]; getContextualType(node: Node): Type; getResolvedSignature(node: CallExpression, candidatesOutArray?: Signature[]): Signature; diff --git a/src/services/services.ts b/src/services/services.ts index 3fc2ae7f05a7b..68c402b51ced9 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2284,16 +2284,8 @@ module ts { } } - function getConcreteSymbol(symbol: Symbol): Symbol { - if (symbol.flags & SymbolFlags.UnionProperty) { - var types = typeInfoResolver.getUnionTypesOfUnionProperty(symbol); - symbol = typeInfoResolver.getPropertyOfType(types[0], symbol.name); - } - return typeInfoResolver.getRootSymbol(symbol); - } - function getSymbolKind(symbol: Symbol): string { - var flags = getConcreteSymbol(symbol).getFlags(); + var flags = typeInfoResolver.getRootSymbols(symbol)[0].getFlags(); if (flags & SymbolFlags.Module) return ScriptElementKind.moduleElement; if (flags & SymbolFlags.Class) return ScriptElementKind.classElement; @@ -2352,7 +2344,7 @@ module ts { } function getSymbolModifiers(symbol: Symbol): string { - symbol = getConcreteSymbol(symbol); + symbol = typeInfoResolver.getRootSymbols(symbol)[0]; return symbol && symbol.declarations && symbol.declarations.length > 0 ? getNodeModifiers(symbol.declarations[0]) : ScriptElementKindModifier.none; @@ -3016,18 +3008,28 @@ module ts { return [getReferenceEntryFromNode(node)]; } + var declarations = symbol.getDeclarations(); + + // Handel union properties + if (symbol.flags & SymbolFlags.UnionProperty) { + declarations = []; + forEach(typeInfoResolver.getUnionTypesOfUnionProperty(symbol), t => { + declarations.push.apply(declarations, t.getProperty(symbol.name).declarations); + }); + } + // the symbol was an internal symbol and does not have a declaration e.g.undefined symbol - if (!symbol.getDeclarations()) { + if (!declarations || !declarations.length) { return undefined; } var result: ReferenceEntry[]; // Compute the meaning from the location and the symbol it references - var searchMeaning = getIntersectingMeaningFromDeclarations(getMeaningFromLocation(node), symbol.getDeclarations()); + var searchMeaning = getIntersectingMeaningFromDeclarations(getMeaningFromLocation(node), declarations); // Get the text to search for, we need to normalize it as external module names will have quote - var symbolName = getNormalizedSymbolName(symbol); + var symbolName = getNormalizedSymbolName(symbol.name, declarations); // Get syntactic diagnostics var scope = getSymbolScope(symbol); @@ -3049,15 +3051,15 @@ module ts { return result; - function getNormalizedSymbolName(symbol: Symbol): string { + function getNormalizedSymbolName(symbolName: string, declarations: Declaration[]): string { // Special case for function expressions, whose names are solely local to their bodies. - var functionExpression = getDeclarationOfKind(symbol, SyntaxKind.FunctionExpression); + var functionExpression = forEach(declarations, d => d.kind === SyntaxKind.FunctionExpression ? d : undefined); if (functionExpression && functionExpression.name) { var name = functionExpression.name.text; } else { - var name = symbol.name; + var name = symbolName; } var length = name.length; @@ -3084,22 +3086,24 @@ module ts { var scope: Node = undefined; var declarations = symbol.getDeclarations(); - for (var i = 0, n = declarations.length; i < n; i++) { - var container = getContainerNode(declarations[i]); + if (declarations) { + for (var i = 0, n = declarations.length; i < n; i++) { + var container = getContainerNode(declarations[i]); - if (scope && scope !== container) { - // Different declarations have different containers, bail out - return undefined; - } + if (scope && scope !== container) { + // Different declarations have different containers, bail out + return undefined; + } - if (container.kind === SyntaxKind.SourceFile && !isExternalModule(container)) { - // This is a global variable and not an external module, any declaration defined - // within this scope is visible outside the file - return undefined; - } + if (container.kind === SyntaxKind.SourceFile && !isExternalModule(container)) { + // This is a global variable and not an external module, any declaration defined + // within this scope is visible outside the file + return undefined; + } - // The search scope is the container node - scope = container; + // The search scope is the container node + scope = container; + } } return scope; @@ -3216,14 +3220,7 @@ module ts { } var referenceSymbol = typeInfoResolver.getSymbolInfo(referenceLocation); - - // Could not find a symbol e.g. node is string or number keyword, - // or the symbol was an internal symbol and does not have a declaration e.g. undefined symbol - if (!referenceSymbol || !(referenceSymbol.getDeclarations())) { - return; - } - - if (isRelatableToSearchSet(searchSymbols, referenceSymbol, referenceLocation)) { + if (referenceSymbol && isRelatableToSearchSet(searchSymbols, referenceSymbol, referenceLocation)) { result.push(getReferenceEntryFromNode(referenceLocation)); } }); @@ -3359,24 +3356,26 @@ module ts { // The search set contains at least the current symbol var result = [symbol]; - // If the symbol is an instantiation from a another symbol (e.g. widened symbol) , add the root the list - var rootSymbol = typeInfoResolver.getRootSymbol(symbol); - if (rootSymbol && rootSymbol !== symbol) { - result.push(rootSymbol); - } - // If the location is in a context sensitive location (i.e. in an object literal) try // to get a contextual type for it, and add the property symbol from the contextual // type to the search set if (isNameOfPropertyAssignment(location)) { var symbolFromContextualType = getPropertySymbolFromContextualType(location); - if (symbolFromContextualType) result.push(typeInfoResolver.getRootSymbol(symbolFromContextualType)); + if (symbolFromContextualType) result.push.apply(result, typeInfoResolver.getRootSymbols(symbolFromContextualType)); } - // Add symbol of properties/methods of the same name in base classes and implemented interfaces definitions - if (symbol.parent && symbol.parent.flags & (SymbolFlags.Class | SymbolFlags.Interface)) { - getPropertySymbolsFromBaseTypes(symbol.parent, symbol.getName(), result); - } + // If this is a union property, add all the symbols from all its source symbols in all unioned types. + // If the symbol is an instantiation from a another symbol (e.g. widened symbol) , add the root the list + forEach(typeInfoResolver.getRootSymbols(symbol), rootSymbol => { + if (rootSymbol !== symbol) { + result.push(rootSymbol); + } + + // Add symbol of properties/methods of the same name in base classes and implemented interfaces definitions + if (rootSymbol.parent && rootSymbol.parent.flags & (SymbolFlags.Class | SymbolFlags.Interface)) { + getPropertySymbolsFromBaseTypes(rootSymbol.parent, rootSymbol.getName(), result); + } + }); return result; } @@ -3411,33 +3410,34 @@ module ts { } function isRelatableToSearchSet(searchSymbols: Symbol[], referenceSymbol: Symbol, referenceLocation: Node): boolean { - // Unwrap symbols to get to the root (e.g. transient symbols as a result of widening) - var referenceSymbolTarget = typeInfoResolver.getRootSymbol(referenceSymbol); - - // if it is in the list, then we are done - if (searchSymbols.indexOf(referenceSymbolTarget) >= 0) { - return true; - } - // If the reference location is in an object literal, try to get the contextual type for the // object literal, lookup the property symbol in the contextual type, and use this symbol to // compare to our searchSymbol if (isNameOfPropertyAssignment(referenceLocation)) { var symbolFromContextualType = getPropertySymbolFromContextualType(referenceLocation); - if (symbolFromContextualType && searchSymbols.indexOf(typeInfoResolver.getRootSymbol(symbolFromContextualType)) >= 0) { - return true; + if (symbolFromContextualType) { + return forEach(typeInfoResolver.getRootSymbols(symbolFromContextualType), s => searchSymbols.indexOf(s) >= 0); } } - // Finally, try all properties with the same name in any type the containing type extend or implemented, and - // see if any is in the list - if (referenceSymbol.parent && referenceSymbol.parent.flags & (SymbolFlags.Class | SymbolFlags.Interface)) { - var result: Symbol[] = []; - getPropertySymbolsFromBaseTypes(referenceSymbol.parent, referenceSymbol.getName(), result); - return forEach(result, s => searchSymbols.indexOf(s) >= 0); - } + // Unwrap symbols to get to the root (e.g. transient symbols as a result of widening) + // Or a union property, use its underlying unioned symbols + return forEach(typeInfoResolver.getRootSymbols(referenceSymbol), rootSymbol => { + // if it is in the list, then we are done + if (searchSymbols.indexOf(rootSymbol) >= 0) { + return true; + } - return false; + // 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 (rootSymbol.parent && rootSymbol.parent.flags & (SymbolFlags.Class | SymbolFlags.Interface)) { + var result: Symbol[] = []; + getPropertySymbolsFromBaseTypes(rootSymbol.parent, rootSymbol.getName(), result); + return forEach(result, s => searchSymbols.indexOf(s) >= 0); + } + + return false; + }); } function getPropertySymbolFromContextualType(node: Node): Symbol { diff --git a/tests/cases/fourslash/referencesForContextuallyTypedObjectLiteralProperties.ts b/tests/cases/fourslash/referencesForContextuallyTypedObjectLiteralProperties.ts index f21f6cc3936e2..15dd57200194c 100644 --- a/tests/cases/fourslash/referencesForContextuallyTypedObjectLiteralProperties.ts +++ b/tests/cases/fourslash/referencesForContextuallyTypedObjectLiteralProperties.ts @@ -1,5 +1,5 @@ -/// - +/// + ////interface IFoo { /*1*/xy: number; } //// ////// Assignment @@ -23,10 +23,10 @@ ////var w: IFoo = { /*4*/xy: undefined }; //// ////// Untped -- should not be included -////var u = { xy: 0 }; - - -test.markers().forEach((m) => { - goTo.position(m.position, m.fileName); - verify.referencesCountIs(9); -}); +////var u = { xy: 0 }; + + +test.markers().forEach((m) => { + goTo.position(m.position, m.fileName); + verify.referencesCountIs(9); +}); diff --git a/tests/cases/fourslash/referencesForContextuallyTypedUnionProperties.ts b/tests/cases/fourslash/referencesForContextuallyTypedUnionProperties.ts new file mode 100644 index 0000000000000..f9c479de7698f --- /dev/null +++ b/tests/cases/fourslash/referencesForContextuallyTypedUnionProperties.ts @@ -0,0 +1,40 @@ +/// + +////interface A { +//// a: number; +//// common: string; +////} +//// +////interface B { +//// b: number; +//// common: number; +////} +//// +////// Assignment +////var v1: A | B = { a: 0, /*1*/common: "" }; +////var v2: A | B = { b: 0, /*2*/common: 3 }; +//// +////// Function call +////function consumer(f: A | B) { } +////consumer({ a: 0, b: 0, /*3*/common: 1 }); +//// +////// Type cast +////var c = { /*4*/common: 0, b: 0 }; +//// +////// Array literal +////var ar: Array = [{ a: 0, /*5*/common: "" }, { b: 0, /*6*/common: 0 }]; +//// +////// Nested object literal +////var ob: { aorb: A|B } = { aorb: { b: 0, /*7*/common: 0 } }; +//// +////// Widened type +////var w: A|B = { a:0, /*8*/common: undefined }; +//// +////// Untped -- should not be included +////var u1 = { a: 0, b: 0, common: "" }; +////var u2 = { b: 0, common: 0 }; + +test.markers().forEach((m) => { + goTo.position(m.position, m.fileName); + verify.referencesCountIs(10); // 8 contextually typed common, and 2 in definition (A.common, B.common) +}); diff --git a/tests/cases/fourslash/referencesForUnionProperties.ts b/tests/cases/fourslash/referencesForUnionProperties.ts new file mode 100644 index 0000000000000..f4ef85e8e4efb --- /dev/null +++ b/tests/cases/fourslash/referencesForUnionProperties.ts @@ -0,0 +1,35 @@ +/// + +////interface One { +//// common: { /*1*/a: number; }; +////} +//// +////interface Base { +//// /*2*/a: string; +//// b: string; +////} +//// +////interface HasAOrB extends Base { +//// /*3*/a: string; +//// b: string; +////} +//// +////interface Two { +//// common: HasAOrB; +////} +//// +////var x : One | Two; +//// +////x.common./*4*/a; + +goTo.marker("1"); +verify.referencesCountIs(2); // One.common.a, x.common.a + +goTo.marker("2"); +verify.referencesCountIs(3); // Base.a, HasAOrB.a, x.common.a + +goTo.marker("3"); +verify.referencesCountIs(3); // Base.a, HasAOrB.a, x.common.a + +goTo.marker("4"); +verify.referencesCountIs(4); // One.common.a, Base.a, HasAOrB.a, x.common.a \ No newline at end of file From 2eb51ab874ac576e7b10d3ec7c09b074e793620d Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Wed, 8 Oct 2014 23:28:17 -0700 Subject: [PATCH 05/10] Use getRootSymbols for all union property needs --- src/compiler/checker.ts | 5 ----- src/compiler/types.ts | 2 -- src/services/services.ts | 8 ++++---- 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 7138cd9588acd..808f7e3b84993 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -93,7 +93,6 @@ module ts { getFullyQualifiedName: getFullyQualifiedName, getResolvedSignature: getResolvedSignature, getEnumMemberValue: getEnumMemberValue, - getUnionTypesOfUnionProperty: getUnionTypesOfUnionProperty }; var undefinedSymbol = createSymbol(SymbolFlags.Property | SymbolFlags.Transient, "undefined"); @@ -1751,10 +1750,6 @@ module ts { return links.type; } - function getUnionTypesOfUnionProperty(symbol: Symbol): Type[] { - return (symbol.flags & SymbolFlags.UnionProperty) ? getSymbolLinks(symbol).unionType.types : undefined; - } - function getTypeOfSymbol(symbol: Symbol): Type { if (symbol.flags & (SymbolFlags.Variable | SymbolFlags.Property)) { return getTypeOfVariableOrParameterOrProperty(symbol); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 0aa25e2efebd3..e314466cf8cd4 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -657,8 +657,6 @@ module ts { getContextualType(node: Node): Type; getResolvedSignature(node: CallExpression, candidatesOutArray?: Signature[]): Signature; - getUnionTypesOfUnionProperty(symbol: Symbol): Type[]; - // Returns the constant value of this enum member, or 'undefined' if the enum member has a // computed value. getEnumMemberValue(node: EnumMember): number; diff --git a/src/services/services.ts b/src/services/services.ts index 68c402b51ced9..8ae8ef36f3670 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2619,8 +2619,8 @@ module ts { var result: DefinitionInfo[] = []; if (symbol.flags & SymbolFlags.UnionProperty) { - forEach(typeInfoResolver.getUnionTypesOfUnionProperty(symbol), t => { - getDefinitionFromSymbol(typeInfoResolver.getPropertyOfType(t, symbol.name), node, result); + forEach(typeInfoResolver.getRootSymbols(symbol), s => { + getDefinitionFromSymbol(s, node, result); }); } else { @@ -3013,8 +3013,8 @@ module ts { // Handel union properties if (symbol.flags & SymbolFlags.UnionProperty) { declarations = []; - forEach(typeInfoResolver.getUnionTypesOfUnionProperty(symbol), t => { - declarations.push.apply(declarations, t.getProperty(symbol.name).declarations); + forEach(typeInfoResolver.getRootSymbols(symbol), s => { + declarations.push.apply(declarations, s.declarations); }); } From 927f04f64fccfe01835173bf64ebc72389e1a50c Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Thu, 9 Oct 2014 11:16:04 -0700 Subject: [PATCH 06/10] Fix contextually typed object literal proeprties that are not properties of the union type but are properties on of one of its constituant types --- src/services/services.ts | 44 ++++++++++++++++++++++++++++++++-------- 1 file changed, 36 insertions(+), 8 deletions(-) diff --git a/src/services/services.ts b/src/services/services.ts index f7e38019997cd..4d192059815ef 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -3562,8 +3562,9 @@ module ts { // to get a contextual type for it, and add the property symbol from the contextual // type to the search set if (isNameOfPropertyAssignment(location)) { - var symbolFromContextualType = getPropertySymbolFromContextualType(location); - if (symbolFromContextualType) result.push.apply(result, typeInfoResolver.getRootSymbols(symbolFromContextualType)); + forEach(getPropertySymbolsFromContextualType(location), contextualSymbol => { + result.push.apply(result, typeInfoResolver.getRootSymbols(contextualSymbol)); + }); } // If this is a union property, add all the symbols from all its source symbols in all unioned types. @@ -3613,14 +3614,17 @@ module ts { } function isRelatableToSearchSet(searchSymbols: Symbol[], referenceSymbol: Symbol, referenceLocation: Node): boolean { + if (searchSymbols.indexOf(referenceSymbol) >= 0) { + return true; + } + // If the reference location is in an object literal, try to get the contextual type for the // object literal, lookup the property symbol in the contextual type, and use this symbol to // compare to our searchSymbol if (isNameOfPropertyAssignment(referenceLocation)) { - var symbolFromContextualType = getPropertySymbolFromContextualType(referenceLocation); - if (symbolFromContextualType) { - return forEach(typeInfoResolver.getRootSymbols(symbolFromContextualType), s => searchSymbols.indexOf(s) >= 0); - } + return forEach(getPropertySymbolsFromContextualType(referenceLocation), contextualSymbol => { + return forEach(typeInfoResolver.getRootSymbols(contextualSymbol), s => searchSymbols.indexOf(s) >= 0); + }); } // Unwrap symbols to get to the root (e.g. transient symbols as a result of widening) @@ -3643,12 +3647,36 @@ module ts { }); } - function getPropertySymbolFromContextualType(node: Node): Symbol { + function getPropertySymbolsFromContextualType(node: Node): Symbol[] { if (isNameOfPropertyAssignment(node)) { var objectLiteral = node.parent.parent; var contextualType = typeInfoResolver.getContextualType(objectLiteral); + var name = (node).text; if (contextualType) { - return typeInfoResolver.getPropertyOfType(contextualType, (node).text); + if (contextualType.flags & TypeFlags.Union) { + // This is a union type, first see if the property we are looking for is a union property (i.e. exists in all types) + // if not, search the constituent types for the property + var unionProperty = contextualType.getProperty(name) + if (unionProperty) { + return [unionProperty]; + } + else { + var result: Symbol[] = []; + forEach((contextualType).types, t => { + var symbol = t.getProperty(name); + if (symbol) { + result.push(symbol); + } + }); + return result; + } + } + else { + var symbol = contextualType.getProperty(name); + if (symbol) { + return [symbol]; + } + } } } return undefined; From 9f43ac02deb84e73b9deca092157c53feafe408a Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Fri, 10 Oct 2014 10:59:30 -0700 Subject: [PATCH 07/10] respond to code review remarks --- src/services/services.ts | 31 +++++++++++++------------------ 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/src/services/services.ts b/src/services/services.ts index 4d192059815ef..b6bea438c5a79 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2522,7 +2522,7 @@ module ts { } /// Goto definition - function getDefinitionAtPosition(filename: string, position: number): DefinitionInfo[]{ + function getDefinitionAtPosition(filename: string, position: number): DefinitionInfo[] { function getDefinitionInfo(node: Node, symbolKind: string, symbolName: string, containerName: string): DefinitionInfo { return { fileName: node.getSourceFile().filename, @@ -2554,7 +2554,7 @@ module ts { result.push(getDefinitionInfo(declarations[declarations.length - 1], symbolKind, symbolName, containerName)); return true; } - + return false; } @@ -2643,14 +2643,11 @@ module ts { var result: DefinitionInfo[] = []; - if (symbol.flags & SymbolFlags.UnionProperty) { - forEach(typeInfoResolver.getRootSymbols(symbol), s => { - getDefinitionFromSymbol(s, node, result); - }); - } - else { - getDefinitionFromSymbol(symbol, node, result); - } + // The symbol could be a unionProperty, we need to ensure we are collecting all + // declarations, so use getRootSymbol first. + forEach(typeInfoResolver.getRootSymbols(symbol), s => { + getDefinitionFromSymbol(s, node, result); + }); return result; } @@ -3167,15 +3164,13 @@ module ts { var declarations = symbol.getDeclarations(); - // Handel union properties - if (symbol.flags & SymbolFlags.UnionProperty) { - declarations = []; - forEach(typeInfoResolver.getRootSymbols(symbol), s => { - declarations.push.apply(declarations, s.declarations); - }); - } + // Handle union properties + declarations = []; + forEach(typeInfoResolver.getRootSymbols(symbol), s => { + declarations.push.apply(declarations, s.declarations); + }); - // the symbol was an internal symbol and does not have a declaration e.g.undefined symbol + // The symbol was an internal symbol and does not have a declaration e.g.undefined symbol if (!declarations || !declarations.length) { return undefined; } From f5a9feee9f735c6a198a8b63bfa198346f6cba7e Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Fri, 10 Oct 2014 16:41:05 -0700 Subject: [PATCH 08/10] ensure unionProperty symbols have declarations set at creation time --- src/compiler/checker.ts | 8 ++++++++ src/services/services.ts | 15 ++------------- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 2040ba0d85637..5bcf1871f7d67 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2144,6 +2144,14 @@ module ts { } var symbol = createSymbol(SymbolFlags.UnionProperty | SymbolFlags.Transient, prop.name); symbol.unionType = type; + + symbol.declarations = []; + for (var i = 0; i < types.length; i++) { + var s = getPropertyOfType(types[i], prop.name); + if (s.declarations) + symbol.declarations.push.apply(symbol.declarations, s.declarations); + } + members[prop.name] = symbol; }); var callSignatures = getUnionSignatures(types, SignatureKind.Call); diff --git a/src/services/services.ts b/src/services/services.ts index b6bea438c5a79..39575138cc600 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2382,7 +2382,6 @@ module ts { } function getSymbolModifiers(symbol: Symbol): string { - symbol = typeInfoResolver.getRootSymbols(symbol)[0]; return symbol && symbol.declarations && symbol.declarations.length > 0 ? getNodeModifiers(symbol.declarations[0]) : ScriptElementKindModifier.none; @@ -2643,11 +2642,7 @@ module ts { var result: DefinitionInfo[] = []; - // The symbol could be a unionProperty, we need to ensure we are collecting all - // declarations, so use getRootSymbol first. - forEach(typeInfoResolver.getRootSymbols(symbol), s => { - getDefinitionFromSymbol(s, node, result); - }); + getDefinitionFromSymbol(symbol, node, result); return result; } @@ -3162,13 +3157,7 @@ module ts { return [getReferenceEntryFromNode(node)]; } - var declarations = symbol.getDeclarations(); - - // Handle union properties - declarations = []; - forEach(typeInfoResolver.getRootSymbols(symbol), s => { - declarations.push.apply(declarations, s.declarations); - }); + var declarations = symbol.declarations; // The symbol was an internal symbol and does not have a declaration e.g.undefined symbol if (!declarations || !declarations.length) { From 2ce627c6dd8ac4bce85c87f3f41e3649ca870ddd Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Fri, 10 Oct 2014 17:07:13 -0700 Subject: [PATCH 09/10] Handle union properties completions on apparant types --- src/compiler/checker.ts | 2 +- .../completionEntryForUnionProperty2.ts | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 tests/cases/fourslash/completionEntryForUnionProperty2.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 5bcf1871f7d67..56dc73237f371 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7916,7 +7916,7 @@ module ts { var symbols: Symbol[] = []; var name = symbol.name; forEach(getSymbolLinks(symbol).unionType.types, t => { - symbols.push(getPropertyOfType(t, name)); + symbols.push(getPropertyOfType(getApparentType(t), name)); }); return symbols; } diff --git a/tests/cases/fourslash/completionEntryForUnionProperty2.ts b/tests/cases/fourslash/completionEntryForUnionProperty2.ts new file mode 100644 index 0000000000000..fef5e0dfb5051 --- /dev/null +++ b/tests/cases/fourslash/completionEntryForUnionProperty2.ts @@ -0,0 +1,19 @@ +/// + +////interface One { +//// commonProperty: number; +//// commonFunction(): number; +////} +//// +////interface Two { +//// commonProperty: string +//// commonFunction(): number; +////} +//// +////var x : One | Two; +//// +////x.commonProperty./**/ + +goTo.marker(); +verify.memberListContains("toString", "() => string", undefined, undefined, "method"); +verify.memberListCount(1); \ No newline at end of file From 4442b45bad79223c3ca6893a1c56eb81d30af23d Mon Sep 17 00:00:00 2001 From: Mohamed Hegazy Date: Fri, 10 Oct 2014 17:19:43 -0700 Subject: [PATCH 10/10] Add a temporary fix to quick info --- src/services/services.ts | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/src/services/services.ts b/src/services/services.ts index 39575138cc600..539c154f6b82d 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -2404,31 +2404,34 @@ module ts { var documentationParts = getSymbolDocumentationDisplayParts(symbol); + // TODO: handle union properties appropriately when merging with master + var symbolFlags = typeInfoResolver.getRootSymbols(symbol)[0].flags; + // Having all this logic here is pretty unclean. Consider moving to the roslyn model // where all symbol display logic is encapsulated into visitors and options. var totalParts: SymbolDisplayPart[] = []; - if (symbol.flags & SymbolFlags.Class) { + if (symbolFlags & SymbolFlags.Class) { totalParts.push(keywordPart(SyntaxKind.ClassKeyword)); totalParts.push(spacePart()); totalParts.push.apply(totalParts, typeInfoResolver.symbolToDisplayParts(symbol, sourceFile)); } - else if (symbol.flags & SymbolFlags.Interface) { + else if (symbolFlags & SymbolFlags.Interface) { totalParts.push(keywordPart(SyntaxKind.InterfaceKeyword)); totalParts.push(spacePart()); totalParts.push.apply(totalParts, typeInfoResolver.symbolToDisplayParts(symbol, sourceFile)); } - else if (symbol.flags & SymbolFlags.Enum) { + else if (symbolFlags & SymbolFlags.Enum) { totalParts.push(keywordPart(SyntaxKind.EnumKeyword)); totalParts.push(spacePart()); totalParts.push.apply(totalParts, typeInfoResolver.symbolToDisplayParts(symbol, sourceFile)); } - else if (symbol.flags & SymbolFlags.Module) { + else if (symbolFlags & SymbolFlags.Module) { totalParts.push(keywordPart(SyntaxKind.ModuleKeyword)); totalParts.push(spacePart()); totalParts.push.apply(totalParts, typeInfoResolver.symbolToDisplayParts(symbol, sourceFile)); } - else if (symbol.flags & SymbolFlags.TypeParameter) { + else if (symbolFlags & SymbolFlags.TypeParameter) { totalParts.push(punctuationPart(SyntaxKind.OpenParenToken)); totalParts.push(new SymbolDisplayPart("type parameter", SymbolDisplayPartKind.text, undefined)); totalParts.push(punctuationPart(SyntaxKind.CloseParenToken)); @@ -2439,11 +2442,11 @@ module ts { totalParts.push(punctuationPart(SyntaxKind.OpenParenToken)); var text: string; - if (symbol.flags & SymbolFlags.Property) { text = "property" } - else if (symbol.flags & SymbolFlags.EnumMember) { text = "enum member" } - else if (symbol.flags & SymbolFlags.Function) { text = "function" } - else if (symbol.flags & SymbolFlags.Variable) { text = "variable" } - else if (symbol.flags & SymbolFlags.Method) { text = "method" } + if (symbolFlags & SymbolFlags.Property) { text = "property" } + else if (symbolFlags & SymbolFlags.EnumMember) { text = "enum member" } + else if (symbolFlags & SymbolFlags.Function) { text = "function" } + else if (symbolFlags & SymbolFlags.Variable) { text = "variable" } + else if (symbolFlags & SymbolFlags.Method) { text = "method" } if (!text) { return undefined; @@ -2457,8 +2460,8 @@ module ts { var type = typeInfoResolver.getTypeOfSymbol(symbol); - if (symbol.flags & SymbolFlags.Property || - symbol.flags & SymbolFlags.Variable) { + if (symbolFlags & SymbolFlags.Property || + symbolFlags & SymbolFlags.Variable) { if (type) { totalParts.push(punctuationPart(SyntaxKind.ColonToken)); @@ -2466,13 +2469,13 @@ module ts { totalParts.push.apply(totalParts, typeInfoResolver.typeToDisplayParts(type, getContainerNode(node))); } } - else if (symbol.flags & SymbolFlags.Function || - symbol.flags & SymbolFlags.Method) { + else if (symbolFlags & SymbolFlags.Function || + symbolFlags & SymbolFlags.Method) { if (type) { totalParts.push.apply(totalParts, typeInfoResolver.typeToDisplayParts(type, getContainerNode(node))); } } - else if (symbol.flags & SymbolFlags.EnumMember) { + else if (symbolFlags & SymbolFlags.EnumMember) { var declaration = symbol.declarations[0]; if (declaration.kind === SyntaxKind.EnumMember) { var constantValue = typeInfoResolver.getEnumMemberValue(declaration);