From 700435bb8e22f35c9cdc9794898ff0c678dd9793 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Tue, 26 Apr 2016 17:32:00 -0700 Subject: [PATCH 1/2] Properly display narrowed types when hovering in IDE --- src/compiler/checker.ts | 72 +++++++++++++++++------------------------ 1 file changed, 30 insertions(+), 42 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 6a42dc43d5..ddaea2285c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7358,12 +7358,6 @@ namespace ts { return flowTypeCaches[flow.id] || (flowTypeCaches[flow.id] = {}); } - function isNarrowableReference(expr: Node): boolean { - return expr.kind === SyntaxKind.Identifier || - expr.kind === SyntaxKind.ThisKeyword || - expr.kind === SyntaxKind.PropertyAccessExpression && isNarrowableReference((expr).expression); - } - function typeMaybeAssignableTo(source: Type, target: Type) { if (!(source.flags & TypeFlags.Union)) { return isTypeAssignableTo(source, target); @@ -7554,30 +7548,12 @@ namespace ts { getInitialTypeOfBindingElement(node); } - function getNarrowedTypeOfReference(type: Type, reference: Node) { - if (!(type.flags & TypeFlags.Narrowable) || !isNarrowableReference(reference)) { - return type; - } - const leftmostNode = getLeftmostIdentifierOrThis(reference); - if (!leftmostNode) { - return type; - } - if (leftmostNode.kind === SyntaxKind.Identifier) { - const leftmostSymbol = getExportSymbolOfValueSymbolIfExported(getResolvedSymbol(leftmostNode)); - if (!leftmostSymbol) { - return type; - } - const declaration = leftmostSymbol.valueDeclaration; - if (!declaration || declaration.kind !== SyntaxKind.VariableDeclaration && declaration.kind !== SyntaxKind.Parameter && declaration.kind !== SyntaxKind.BindingElement) { - return type; - } - } - return getFlowTypeOfReference(reference, type, type); - } - function getFlowTypeOfReference(reference: Node, declaredType: Type, initialType: Type) { let key: string; - return reference.flowNode ? getTypeAtFlowNode(reference.flowNode) : declaredType; + if (!reference.flowNode || declaredType === initialType && !(declaredType.flags & TypeFlags.Narrowable)) { + return declaredType; + } + return getTypeAtFlowNode(reference.flowNode); function getTypeAtFlowNode(flow: FlowNode): Type { while (true) { @@ -7885,19 +7861,18 @@ namespace ts { } function getTypeOfSymbolAtLocation(symbol: Symbol, location: Node) { - // The language service will always care about the narrowed type of a symbol, because that is - // the type the language says the symbol should have. - const type = getTypeOfSymbol(symbol); + // If we have an identifier or a property access at the given location, if the location is + // an dotted name expression, and if the location is not an assignment target, obtain the type + // of the expression (which will reflect control flow analysis). If the expression indeed + // resolved to the given symbol, return the narrowed type. if (location.kind === SyntaxKind.Identifier) { if (isRightSideOfQualifiedNameOrPropertyAccess(location)) { location = location.parent; } - // If location is an identifier or property access that references the given - // symbol, use the location as the reference with respect to which we narrow. if (isExpression(location) && !isAssignmentTarget(location)) { - checkExpression(location); + const type = checkExpression(location); if (getExportSymbolOfValueSymbolIfExported(getNodeLinks(location).resolvedSymbol) === symbol) { - return getNarrowedTypeOfReference(type, location); + return type; } } } @@ -7906,7 +7881,7 @@ namespace ts { // to it at the given location. Since we have no control flow information for the // hypotherical reference (control flow information is created and attached by the // binder), we simply return the declared type of the symbol. - return type; + return getTypeOfSymbol(symbol); } function skipParenthesizedNodes(expression: Expression): Expression { @@ -7975,9 +7950,6 @@ namespace ts { const defaultsToDeclaredType = !strictNullChecks || type.flags & TypeFlags.Any || !declaration || declaration.kind === SyntaxKind.Parameter || isInAmbientContext(declaration) || getContainingFunctionOrModule(declaration) !== getContainingFunctionOrModule(node); - if (defaultsToDeclaredType && !(type.flags & TypeFlags.Narrowable)) { - return type; - } const flowType = getFlowTypeOfReference(node, type, defaultsToDeclaredType ? type : undefinedType); if (strictNullChecks && !(type.flags & TypeFlags.Any) && !(getNullableKind(type) & TypeFlags.Undefined) && getNullableKind(flowType) & TypeFlags.Undefined) { error(node, Diagnostics.Variable_0_is_used_before_being_assigned, symbolToString(symbol)); @@ -8220,7 +8192,7 @@ namespace ts { if (isClassLike(container.parent)) { const symbol = getSymbolOfNode(container.parent); const type = container.flags & NodeFlags.Static ? getTypeOfSymbol(symbol) : (getDeclaredTypeOfSymbol(symbol)).thisType; - return getNarrowedTypeOfReference(type, node); + return getFlowTypeOfReference(node, type, type); } if (isInJavaScriptFile(node)) { @@ -9784,8 +9756,24 @@ namespace ts { } const propType = getTypeOfSymbol(prop); - return node.kind === SyntaxKind.PropertyAccessExpression && prop.flags & (SymbolFlags.Variable | SymbolFlags.Property) && !isAssignmentTarget(node) ? - getNarrowedTypeOfReference(propType, node) : propType; + if (node.kind !== SyntaxKind.PropertyAccessExpression || !(prop.flags & (SymbolFlags.Variable | SymbolFlags.Property)) || isAssignmentTarget(node)) { + return propType; + } + const leftmostNode = getLeftmostIdentifierOrThis(node); + if (!leftmostNode) { + return propType; + } + if (leftmostNode.kind === SyntaxKind.Identifier) { + const leftmostSymbol = getExportSymbolOfValueSymbolIfExported(getResolvedSymbol(leftmostNode)); + if (!leftmostSymbol) { + return propType; + } + const declaration = leftmostSymbol.valueDeclaration; + if (!declaration || declaration.kind !== SyntaxKind.VariableDeclaration && declaration.kind !== SyntaxKind.Parameter && declaration.kind !== SyntaxKind.BindingElement) { + return propType; + } + } + return getFlowTypeOfReference(node, propType, propType); } function isValidPropertyAccess(node: PropertyAccessExpression | QualifiedName, propertyName: string): boolean { From 65edb527f5aeea4a2f364232f8ba8c4175a6fbad Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Wed, 27 Apr 2016 09:08:45 -0700 Subject: [PATCH 2/2] Adding fourslash test --- .../fourslash/quickInfoOnNarrowedType.ts | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/tests/cases/fourslash/quickInfoOnNarrowedType.ts b/tests/cases/fourslash/quickInfoOnNarrowedType.ts index bc1d8126d7..53fe1bed23 100644 --- a/tests/cases/fourslash/quickInfoOnNarrowedType.ts +++ b/tests/cases/fourslash/quickInfoOnNarrowedType.ts @@ -1,5 +1,7 @@ /// +// @strictNullChecks: true + ////function foo(strOrNum: string | number) { //// if (typeof /*1*/strOrNum === "number") { //// return /*2*/strOrNum; @@ -9,6 +11,13 @@ //// } ////} +////function bar() { +//// let s: string | undefined; +//// /*4*/s; +//// /*5*/s = "abc"; +//// /*6*/s; +////} + goTo.marker('1'); verify.quickInfoIs('(parameter) strOrNum: string | number'); verify.completionListContains("strOrNum", "(parameter) strOrNum: string | number"); @@ -20,3 +29,15 @@ verify.completionListContains("strOrNum", "(parameter) strOrNum: number"); goTo.marker('3'); verify.quickInfoIs('(parameter) strOrNum: string'); verify.completionListContains("strOrNum", "(parameter) strOrNum: string"); + +goTo.marker('4'); +verify.quickInfoIs('let s: undefined'); +verify.completionListContains("s", "let s: undefined"); + +goTo.marker('5'); +verify.quickInfoIs('let s: string | undefined'); +verify.completionListContains("s", "let s: string | undefined"); + +goTo.marker('6'); +verify.quickInfoIs('let s: string'); +verify.completionListContains("s", "let s: string");