Skip to content

Commit b99a2e0

Browse files
committed
Merge pull request #8324 from Microsoft/fixDisplayOfNarrowedTypes
Properly display narrowed types when hovering in IDE
2 parents e00f573 + 65edb52 commit b99a2e0

File tree

2 files changed

+51
-42
lines changed

2 files changed

+51
-42
lines changed

src/compiler/checker.ts

Lines changed: 30 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -7358,12 +7358,6 @@ namespace ts {
73587358
return flowTypeCaches[flow.id] || (flowTypeCaches[flow.id] = {});
73597359
}
73607360

7361-
function isNarrowableReference(expr: Node): boolean {
7362-
return expr.kind === SyntaxKind.Identifier ||
7363-
expr.kind === SyntaxKind.ThisKeyword ||
7364-
expr.kind === SyntaxKind.PropertyAccessExpression && isNarrowableReference((<PropertyAccessExpression>expr).expression);
7365-
}
7366-
73677361
function typeMaybeAssignableTo(source: Type, target: Type) {
73687362
if (!(source.flags & TypeFlags.Union)) {
73697363
return isTypeAssignableTo(source, target);
@@ -7554,30 +7548,12 @@ namespace ts {
75547548
getInitialTypeOfBindingElement(<BindingElement>node);
75557549
}
75567550

7557-
function getNarrowedTypeOfReference(type: Type, reference: Node) {
7558-
if (!(type.flags & TypeFlags.Narrowable) || !isNarrowableReference(reference)) {
7559-
return type;
7560-
}
7561-
const leftmostNode = getLeftmostIdentifierOrThis(reference);
7562-
if (!leftmostNode) {
7563-
return type;
7564-
}
7565-
if (leftmostNode.kind === SyntaxKind.Identifier) {
7566-
const leftmostSymbol = getExportSymbolOfValueSymbolIfExported(getResolvedSymbol(<Identifier>leftmostNode));
7567-
if (!leftmostSymbol) {
7568-
return type;
7569-
}
7570-
const declaration = leftmostSymbol.valueDeclaration;
7571-
if (!declaration || declaration.kind !== SyntaxKind.VariableDeclaration && declaration.kind !== SyntaxKind.Parameter && declaration.kind !== SyntaxKind.BindingElement) {
7572-
return type;
7573-
}
7574-
}
7575-
return getFlowTypeOfReference(reference, type, type);
7576-
}
7577-
75787551
function getFlowTypeOfReference(reference: Node, declaredType: Type, initialType: Type) {
75797552
let key: string;
7580-
return reference.flowNode ? getTypeAtFlowNode(reference.flowNode) : declaredType;
7553+
if (!reference.flowNode || declaredType === initialType && !(declaredType.flags & TypeFlags.Narrowable)) {
7554+
return declaredType;
7555+
}
7556+
return getTypeAtFlowNode(reference.flowNode);
75817557

75827558
function getTypeAtFlowNode(flow: FlowNode): Type {
75837559
while (true) {
@@ -7885,19 +7861,18 @@ namespace ts {
78857861
}
78867862

78877863
function getTypeOfSymbolAtLocation(symbol: Symbol, location: Node) {
7888-
// The language service will always care about the narrowed type of a symbol, because that is
7889-
// the type the language says the symbol should have.
7890-
const type = getTypeOfSymbol(symbol);
7864+
// If we have an identifier or a property access at the given location, if the location is
7865+
// an dotted name expression, and if the location is not an assignment target, obtain the type
7866+
// of the expression (which will reflect control flow analysis). If the expression indeed
7867+
// resolved to the given symbol, return the narrowed type.
78917868
if (location.kind === SyntaxKind.Identifier) {
78927869
if (isRightSideOfQualifiedNameOrPropertyAccess(location)) {
78937870
location = location.parent;
78947871
}
7895-
// If location is an identifier or property access that references the given
7896-
// symbol, use the location as the reference with respect to which we narrow.
78977872
if (isExpression(location) && !isAssignmentTarget(location)) {
7898-
checkExpression(<Expression>location);
7873+
const type = checkExpression(<Expression>location);
78997874
if (getExportSymbolOfValueSymbolIfExported(getNodeLinks(location).resolvedSymbol) === symbol) {
7900-
return getNarrowedTypeOfReference(type, <IdentifierOrPropertyAccess>location);
7875+
return type;
79017876
}
79027877
}
79037878
}
@@ -7906,7 +7881,7 @@ namespace ts {
79067881
// to it at the given location. Since we have no control flow information for the
79077882
// hypotherical reference (control flow information is created and attached by the
79087883
// binder), we simply return the declared type of the symbol.
7909-
return type;
7884+
return getTypeOfSymbol(symbol);
79107885
}
79117886

79127887
function skipParenthesizedNodes(expression: Expression): Expression {
@@ -7975,9 +7950,6 @@ namespace ts {
79757950
const defaultsToDeclaredType = !strictNullChecks || type.flags & TypeFlags.Any || !declaration ||
79767951
declaration.kind === SyntaxKind.Parameter || isInAmbientContext(declaration) ||
79777952
getContainingFunctionOrModule(declaration) !== getContainingFunctionOrModule(node);
7978-
if (defaultsToDeclaredType && !(type.flags & TypeFlags.Narrowable)) {
7979-
return type;
7980-
}
79817953
const flowType = getFlowTypeOfReference(node, type, defaultsToDeclaredType ? type : undefinedType);
79827954
if (strictNullChecks && !(type.flags & TypeFlags.Any) && !(getNullableKind(type) & TypeFlags.Undefined) && getNullableKind(flowType) & TypeFlags.Undefined) {
79837955
error(node, Diagnostics.Variable_0_is_used_before_being_assigned, symbolToString(symbol));
@@ -8220,7 +8192,7 @@ namespace ts {
82208192
if (isClassLike(container.parent)) {
82218193
const symbol = getSymbolOfNode(container.parent);
82228194
const type = container.flags & NodeFlags.Static ? getTypeOfSymbol(symbol) : (<InterfaceType>getDeclaredTypeOfSymbol(symbol)).thisType;
8223-
return getNarrowedTypeOfReference(type, node);
8195+
return getFlowTypeOfReference(node, type, type);
82248196
}
82258197

82268198
if (isInJavaScriptFile(node)) {
@@ -9784,8 +9756,24 @@ namespace ts {
97849756
}
97859757

97869758
const propType = getTypeOfSymbol(prop);
9787-
return node.kind === SyntaxKind.PropertyAccessExpression && prop.flags & (SymbolFlags.Variable | SymbolFlags.Property) && !isAssignmentTarget(node) ?
9788-
getNarrowedTypeOfReference(propType, <PropertyAccessExpression>node) : propType;
9759+
if (node.kind !== SyntaxKind.PropertyAccessExpression || !(prop.flags & (SymbolFlags.Variable | SymbolFlags.Property)) || isAssignmentTarget(node)) {
9760+
return propType;
9761+
}
9762+
const leftmostNode = getLeftmostIdentifierOrThis(node);
9763+
if (!leftmostNode) {
9764+
return propType;
9765+
}
9766+
if (leftmostNode.kind === SyntaxKind.Identifier) {
9767+
const leftmostSymbol = getExportSymbolOfValueSymbolIfExported(getResolvedSymbol(<Identifier>leftmostNode));
9768+
if (!leftmostSymbol) {
9769+
return propType;
9770+
}
9771+
const declaration = leftmostSymbol.valueDeclaration;
9772+
if (!declaration || declaration.kind !== SyntaxKind.VariableDeclaration && declaration.kind !== SyntaxKind.Parameter && declaration.kind !== SyntaxKind.BindingElement) {
9773+
return propType;
9774+
}
9775+
}
9776+
return getFlowTypeOfReference(node, propType, propType);
97899777
}
97909778

97919779
function isValidPropertyAccess(node: PropertyAccessExpression | QualifiedName, propertyName: string): boolean {

tests/cases/fourslash/quickInfoOnNarrowedType.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
/// <reference path='fourslash.ts'/>
22

3+
// @strictNullChecks: true
4+
35
////function foo(strOrNum: string | number) {
46
//// if (typeof /*1*/strOrNum === "number") {
57
//// return /*2*/strOrNum;
@@ -9,6 +11,13 @@
911
//// }
1012
////}
1113

14+
////function bar() {
15+
//// let s: string | undefined;
16+
//// /*4*/s;
17+
//// /*5*/s = "abc";
18+
//// /*6*/s;
19+
////}
20+
1221
goTo.marker('1');
1322
verify.quickInfoIs('(parameter) strOrNum: string | number');
1423
verify.completionListContains("strOrNum", "(parameter) strOrNum: string | number");
@@ -20,3 +29,15 @@ verify.completionListContains("strOrNum", "(parameter) strOrNum: number");
2029
goTo.marker('3');
2130
verify.quickInfoIs('(parameter) strOrNum: string');
2231
verify.completionListContains("strOrNum", "(parameter) strOrNum: string");
32+
33+
goTo.marker('4');
34+
verify.quickInfoIs('let s: undefined');
35+
verify.completionListContains("s", "let s: undefined");
36+
37+
goTo.marker('5');
38+
verify.quickInfoIs('let s: string | undefined');
39+
verify.completionListContains("s", "let s: string | undefined");
40+
41+
goTo.marker('6');
42+
verify.quickInfoIs('let s: string');
43+
verify.completionListContains("s", "let s: string");

0 commit comments

Comments
 (0)