* Fix #17023

* Be more general when handling matching references through binding elements

* Better cache key, PR feedback

* Deeper tests, better cache key handling
This commit is contained in:
Wesley Wigham 2017-07-18 09:12:25 -07:00 committed by GitHub
parent 7d7a06dbc2
commit 695514290f
6 changed files with 447 additions and 6 deletions

View File

@ -2061,8 +2061,10 @@ namespace ts {
case SyntaxKind.Parameter:
return bindParameter(<ParameterDeclaration>node);
case SyntaxKind.VariableDeclaration:
return bindVariableDeclarationOrBindingElement(<VariableDeclaration>node);
case SyntaxKind.BindingElement:
return bindVariableDeclarationOrBindingElement(<VariableDeclaration | BindingElement>node);
node.flowNode = currentFlow;
return bindVariableDeclarationOrBindingElement(<BindingElement>node);
case SyntaxKind.PropertyDeclaration:
case SyntaxKind.PropertySignature:
return bindPropertyWorker(node as PropertyDeclaration | PropertySignature);

View File

@ -4089,8 +4089,8 @@ namespace ts {
/** Return the inferred type for a binding element */
function getTypeForBindingElement(declaration: BindingElement): Type {
const pattern = <BindingPattern>declaration.parent;
const parentType = getTypeForBindingElementParent(<VariableLikeDeclaration>pattern.parent);
const pattern = declaration.parent;
const parentType = getTypeForBindingElementParent(pattern.parent);
// If parent has the unknown (error) type, then so does this binding element
if (parentType === unknownType) {
return unknownType;
@ -4135,7 +4135,8 @@ namespace ts {
// or otherwise the type of the string index signature.
const text = getTextOfPropertyName(name);
type = getTypeOfPropertyOfType(parentType, text) ||
const declaredType = getTypeOfPropertyOfType(parentType, text);
type = declaredType && getFlowTypeOfReference(declaration, declaredType) ||
isNumericLiteralName(text) && getIndexTypeOfType(parentType, IndexKind.Number) ||
getIndexTypeOfType(parentType, IndexKind.String);
if (!type) {
@ -10671,7 +10672,7 @@ namespace ts {
// The result is undefined if the reference isn't a dotted name. We prefix nodes
// occurring in an apparent type position with '@' because the control flow type
// of such nodes may be based on the apparent type instead of the declared type.
function getFlowCacheKey(node: Node): string {
function getFlowCacheKey(node: Node): string | undefined {
if (node.kind === SyntaxKind.Identifier) {
const symbol = getResolvedSymbol(<Identifier>node);
return symbol !== unknownSymbol ? (isApparentTypePosition(node) ? "@" : "") + getSymbolId(symbol) : undefined;
@ -10681,7 +10682,14 @@ namespace ts {
}
if (node.kind === SyntaxKind.PropertyAccessExpression) {
const key = getFlowCacheKey((<PropertyAccessExpression>node).expression);
return key && key + "." + (<PropertyAccessExpression>node).name.text;
return key && key + "." + unescapeLeadingUnderscores((<PropertyAccessExpression>node).name.text);
}
if (node.kind === SyntaxKind.BindingElement) {
const container = (node as BindingElement).parent.parent;
const key = container.kind === SyntaxKind.BindingElement ? getFlowCacheKey(container) : (container.initializer && getFlowCacheKey(container.initializer));
const text = getBindingElementNameText(node as BindingElement);
const result = key && text && (key + "." + text);
return result;
}
return undefined;
}
@ -10697,6 +10705,28 @@ namespace ts {
return undefined;
}
function getBindingElementNameText(element: BindingElement): string | undefined {
if (element.parent.kind === SyntaxKind.ObjectBindingPattern) {
const name = element.propertyName || element.name;
switch (name.kind) {
case SyntaxKind.Identifier:
return unescapeLeadingUnderscores(name.text);
case SyntaxKind.ComputedPropertyName:
if (isComputedNonLiteralName(name as PropertyName)) return undefined;
return (name.expression as LiteralExpression).text;
case SyntaxKind.StringLiteral:
case SyntaxKind.NumericLiteral:
return name.text;
default:
// Per types, array and object binding patterns remain, however they should never be present if propertyName is not defined
Debug.fail("Unexpected name kind for binding element name");
}
}
else {
return "" + element.parent.elements.indexOf(element);
}
}
function isMatchingReference(source: Node, target: Node): boolean {
switch (source.kind) {
case SyntaxKind.Identifier:
@ -10711,6 +10741,17 @@ namespace ts {
return target.kind === SyntaxKind.PropertyAccessExpression &&
(<PropertyAccessExpression>source).name.text === (<PropertyAccessExpression>target).name.text &&
isMatchingReference((<PropertyAccessExpression>source).expression, (<PropertyAccessExpression>target).expression);
case SyntaxKind.BindingElement:
if (target.kind !== SyntaxKind.PropertyAccessExpression) return false;
const t = target as PropertyAccessExpression;
if (t.name.text !== getBindingElementNameText(source as BindingElement)) return false;
if (source.parent.parent.kind === SyntaxKind.BindingElement && isMatchingReference(source.parent.parent, t.expression)) {
return true;
}
if (source.parent.parent.kind === SyntaxKind.VariableDeclaration) {
const maybeId = (source.parent.parent as VariableDeclaration).initializer;
return maybeId && isMatchingReference(maybeId, t.expression);
}
}
return false;
}
@ -11501,6 +11542,10 @@ namespace ts {
const cache = flowLoopCaches[id] || (flowLoopCaches[id] = createMap<Type>());
if (!key) {
key = getFlowCacheKey(reference);
// No cache key is generated when binding patterns are in unnarrowable situations
if (!key) {
return declaredType;
}
}
const cached = cache.get(key);
if (cached) {

View File

@ -0,0 +1,57 @@
//// [destructuringTypeGuardFlow.ts]
type foo = {
bar: number | null;
baz: string;
nested: {
a: number;
b: string | null;
}
};
const aFoo: foo = { bar: 3, baz: "b", nested: { a: 1, b: "y" } };
if (aFoo.bar && aFoo.nested.b) {
const { bar, baz, nested: {a, b: text} } = aFoo;
const right: number = aFoo.bar;
const wrong: number = bar;
const another: string = baz;
const aAgain: number = a;
const bAgain: string = text;
}
type bar = {
elem1: number | null;
elem2: foo | null;
};
const bBar = { elem1: 7, elem2: aFoo };
if (bBar.elem2 && bBar.elem2.bar && bBar.elem2.nested.b) {
const { bar, baz, nested: {a, b: text} } = bBar.elem2;
const right: number = bBar.elem2.bar;
const wrong: number = bar;
const another: string = baz;
const aAgain: number = a;
const bAgain: string = text;
}
//// [destructuringTypeGuardFlow.js]
var aFoo = { bar: 3, baz: "b", nested: { a: 1, b: "y" } };
if (aFoo.bar && aFoo.nested.b) {
var bar = aFoo.bar, baz = aFoo.baz, _a = aFoo.nested, a = _a.a, text = _a.b;
var right = aFoo.bar;
var wrong = bar;
var another = baz;
var aAgain = a;
var bAgain = text;
}
var bBar = { elem1: 7, elem2: aFoo };
if (bBar.elem2 && bBar.elem2.bar && bBar.elem2.nested.b) {
var _b = bBar.elem2, bar = _b.bar, baz = _b.baz, _c = _b.nested, a = _c.a, text = _c.b;
var right = bBar.elem2.bar;
var wrong = bar;
var another = baz;
var aAgain = a;
var bAgain = text;
}

View File

@ -0,0 +1,143 @@
=== tests/cases/compiler/destructuringTypeGuardFlow.ts ===
type foo = {
>foo : Symbol(foo, Decl(destructuringTypeGuardFlow.ts, 0, 0))
bar: number | null;
>bar : Symbol(bar, Decl(destructuringTypeGuardFlow.ts, 0, 12))
baz: string;
>baz : Symbol(baz, Decl(destructuringTypeGuardFlow.ts, 1, 21))
nested: {
>nested : Symbol(nested, Decl(destructuringTypeGuardFlow.ts, 2, 14))
a: number;
>a : Symbol(a, Decl(destructuringTypeGuardFlow.ts, 3, 11))
b: string | null;
>b : Symbol(b, Decl(destructuringTypeGuardFlow.ts, 4, 14))
}
};
const aFoo: foo = { bar: 3, baz: "b", nested: { a: 1, b: "y" } };
>aFoo : Symbol(aFoo, Decl(destructuringTypeGuardFlow.ts, 9, 5))
>foo : Symbol(foo, Decl(destructuringTypeGuardFlow.ts, 0, 0))
>bar : Symbol(bar, Decl(destructuringTypeGuardFlow.ts, 9, 19))
>baz : Symbol(baz, Decl(destructuringTypeGuardFlow.ts, 9, 27))
>nested : Symbol(nested, Decl(destructuringTypeGuardFlow.ts, 9, 37))
>a : Symbol(a, Decl(destructuringTypeGuardFlow.ts, 9, 47))
>b : Symbol(b, Decl(destructuringTypeGuardFlow.ts, 9, 53))
if (aFoo.bar && aFoo.nested.b) {
>aFoo.bar : Symbol(bar, Decl(destructuringTypeGuardFlow.ts, 0, 12))
>aFoo : Symbol(aFoo, Decl(destructuringTypeGuardFlow.ts, 9, 5))
>bar : Symbol(bar, Decl(destructuringTypeGuardFlow.ts, 0, 12))
>aFoo.nested.b : Symbol(b, Decl(destructuringTypeGuardFlow.ts, 4, 14))
>aFoo.nested : Symbol(nested, Decl(destructuringTypeGuardFlow.ts, 2, 14))
>aFoo : Symbol(aFoo, Decl(destructuringTypeGuardFlow.ts, 9, 5))
>nested : Symbol(nested, Decl(destructuringTypeGuardFlow.ts, 2, 14))
>b : Symbol(b, Decl(destructuringTypeGuardFlow.ts, 4, 14))
const { bar, baz, nested: {a, b: text} } = aFoo;
>bar : Symbol(bar, Decl(destructuringTypeGuardFlow.ts, 12, 9))
>baz : Symbol(baz, Decl(destructuringTypeGuardFlow.ts, 12, 14))
>nested : Symbol(nested, Decl(destructuringTypeGuardFlow.ts, 2, 14))
>a : Symbol(a, Decl(destructuringTypeGuardFlow.ts, 12, 29))
>b : Symbol(b, Decl(destructuringTypeGuardFlow.ts, 4, 14))
>text : Symbol(text, Decl(destructuringTypeGuardFlow.ts, 12, 31))
>aFoo : Symbol(aFoo, Decl(destructuringTypeGuardFlow.ts, 9, 5))
const right: number = aFoo.bar;
>right : Symbol(right, Decl(destructuringTypeGuardFlow.ts, 13, 7))
>aFoo.bar : Symbol(bar, Decl(destructuringTypeGuardFlow.ts, 0, 12))
>aFoo : Symbol(aFoo, Decl(destructuringTypeGuardFlow.ts, 9, 5))
>bar : Symbol(bar, Decl(destructuringTypeGuardFlow.ts, 0, 12))
const wrong: number = bar;
>wrong : Symbol(wrong, Decl(destructuringTypeGuardFlow.ts, 14, 7))
>bar : Symbol(bar, Decl(destructuringTypeGuardFlow.ts, 12, 9))
const another: string = baz;
>another : Symbol(another, Decl(destructuringTypeGuardFlow.ts, 15, 7))
>baz : Symbol(baz, Decl(destructuringTypeGuardFlow.ts, 12, 14))
const aAgain: number = a;
>aAgain : Symbol(aAgain, Decl(destructuringTypeGuardFlow.ts, 16, 7))
>a : Symbol(a, Decl(destructuringTypeGuardFlow.ts, 12, 29))
const bAgain: string = text;
>bAgain : Symbol(bAgain, Decl(destructuringTypeGuardFlow.ts, 17, 7))
>text : Symbol(text, Decl(destructuringTypeGuardFlow.ts, 12, 31))
}
type bar = {
>bar : Symbol(bar, Decl(destructuringTypeGuardFlow.ts, 18, 1))
elem1: number | null;
>elem1 : Symbol(elem1, Decl(destructuringTypeGuardFlow.ts, 20, 12))
elem2: foo | null;
>elem2 : Symbol(elem2, Decl(destructuringTypeGuardFlow.ts, 21, 23))
>foo : Symbol(foo, Decl(destructuringTypeGuardFlow.ts, 0, 0))
};
const bBar = { elem1: 7, elem2: aFoo };
>bBar : Symbol(bBar, Decl(destructuringTypeGuardFlow.ts, 25, 5))
>elem1 : Symbol(elem1, Decl(destructuringTypeGuardFlow.ts, 25, 14))
>elem2 : Symbol(elem2, Decl(destructuringTypeGuardFlow.ts, 25, 24))
>aFoo : Symbol(aFoo, Decl(destructuringTypeGuardFlow.ts, 9, 5))
if (bBar.elem2 && bBar.elem2.bar && bBar.elem2.nested.b) {
>bBar.elem2 : Symbol(elem2, Decl(destructuringTypeGuardFlow.ts, 25, 24))
>bBar : Symbol(bBar, Decl(destructuringTypeGuardFlow.ts, 25, 5))
>elem2 : Symbol(elem2, Decl(destructuringTypeGuardFlow.ts, 25, 24))
>bBar.elem2.bar : Symbol(bar, Decl(destructuringTypeGuardFlow.ts, 0, 12))
>bBar.elem2 : Symbol(elem2, Decl(destructuringTypeGuardFlow.ts, 25, 24))
>bBar : Symbol(bBar, Decl(destructuringTypeGuardFlow.ts, 25, 5))
>elem2 : Symbol(elem2, Decl(destructuringTypeGuardFlow.ts, 25, 24))
>bar : Symbol(bar, Decl(destructuringTypeGuardFlow.ts, 0, 12))
>bBar.elem2.nested.b : Symbol(b, Decl(destructuringTypeGuardFlow.ts, 4, 14))
>bBar.elem2.nested : Symbol(nested, Decl(destructuringTypeGuardFlow.ts, 2, 14))
>bBar.elem2 : Symbol(elem2, Decl(destructuringTypeGuardFlow.ts, 25, 24))
>bBar : Symbol(bBar, Decl(destructuringTypeGuardFlow.ts, 25, 5))
>elem2 : Symbol(elem2, Decl(destructuringTypeGuardFlow.ts, 25, 24))
>nested : Symbol(nested, Decl(destructuringTypeGuardFlow.ts, 2, 14))
>b : Symbol(b, Decl(destructuringTypeGuardFlow.ts, 4, 14))
const { bar, baz, nested: {a, b: text} } = bBar.elem2;
>bar : Symbol(bar, Decl(destructuringTypeGuardFlow.ts, 28, 9))
>baz : Symbol(baz, Decl(destructuringTypeGuardFlow.ts, 28, 14))
>nested : Symbol(nested, Decl(destructuringTypeGuardFlow.ts, 2, 14))
>a : Symbol(a, Decl(destructuringTypeGuardFlow.ts, 28, 29))
>b : Symbol(b, Decl(destructuringTypeGuardFlow.ts, 4, 14))
>text : Symbol(text, Decl(destructuringTypeGuardFlow.ts, 28, 31))
>bBar.elem2 : Symbol(elem2, Decl(destructuringTypeGuardFlow.ts, 25, 24))
>bBar : Symbol(bBar, Decl(destructuringTypeGuardFlow.ts, 25, 5))
>elem2 : Symbol(elem2, Decl(destructuringTypeGuardFlow.ts, 25, 24))
const right: number = bBar.elem2.bar;
>right : Symbol(right, Decl(destructuringTypeGuardFlow.ts, 29, 7))
>bBar.elem2.bar : Symbol(bar, Decl(destructuringTypeGuardFlow.ts, 0, 12))
>bBar.elem2 : Symbol(elem2, Decl(destructuringTypeGuardFlow.ts, 25, 24))
>bBar : Symbol(bBar, Decl(destructuringTypeGuardFlow.ts, 25, 5))
>elem2 : Symbol(elem2, Decl(destructuringTypeGuardFlow.ts, 25, 24))
>bar : Symbol(bar, Decl(destructuringTypeGuardFlow.ts, 0, 12))
const wrong: number = bar;
>wrong : Symbol(wrong, Decl(destructuringTypeGuardFlow.ts, 30, 7))
>bar : Symbol(bar, Decl(destructuringTypeGuardFlow.ts, 28, 9))
const another: string = baz;
>another : Symbol(another, Decl(destructuringTypeGuardFlow.ts, 31, 7))
>baz : Symbol(baz, Decl(destructuringTypeGuardFlow.ts, 28, 14))
const aAgain: number = a;
>aAgain : Symbol(aAgain, Decl(destructuringTypeGuardFlow.ts, 32, 7))
>a : Symbol(a, Decl(destructuringTypeGuardFlow.ts, 28, 29))
const bAgain: string = text;
>bAgain : Symbol(bAgain, Decl(destructuringTypeGuardFlow.ts, 33, 7))
>text : Symbol(text, Decl(destructuringTypeGuardFlow.ts, 28, 31))
}

View File

@ -0,0 +1,158 @@
=== tests/cases/compiler/destructuringTypeGuardFlow.ts ===
type foo = {
>foo : foo
bar: number | null;
>bar : number | null
>null : null
baz: string;
>baz : string
nested: {
>nested : { a: number; b: string | null; }
a: number;
>a : number
b: string | null;
>b : string | null
>null : null
}
};
const aFoo: foo = { bar: 3, baz: "b", nested: { a: 1, b: "y" } };
>aFoo : foo
>foo : foo
>{ bar: 3, baz: "b", nested: { a: 1, b: "y" } } : { bar: number; baz: string; nested: { a: number; b: string; }; }
>bar : number
>3 : 3
>baz : string
>"b" : "b"
>nested : { a: number; b: string; }
>{ a: 1, b: "y" } : { a: number; b: string; }
>a : number
>1 : 1
>b : string
>"y" : "y"
if (aFoo.bar && aFoo.nested.b) {
>aFoo.bar && aFoo.nested.b : string | 0 | null
>aFoo.bar : number | null
>aFoo : foo
>bar : number | null
>aFoo.nested.b : string | null
>aFoo.nested : { a: number; b: string | null; }
>aFoo : foo
>nested : { a: number; b: string | null; }
>b : string | null
const { bar, baz, nested: {a, b: text} } = aFoo;
>bar : number
>baz : string
>nested : any
>a : number
>b : any
>text : string
>aFoo : foo
const right: number = aFoo.bar;
>right : number
>aFoo.bar : number
>aFoo : foo
>bar : number
const wrong: number = bar;
>wrong : number
>bar : number
const another: string = baz;
>another : string
>baz : string
const aAgain: number = a;
>aAgain : number
>a : number
const bAgain: string = text;
>bAgain : string
>text : string
}
type bar = {
>bar : bar
elem1: number | null;
>elem1 : number | null
>null : null
elem2: foo | null;
>elem2 : foo | null
>foo : foo
>null : null
};
const bBar = { elem1: 7, elem2: aFoo };
>bBar : { elem1: number; elem2: foo; }
>{ elem1: 7, elem2: aFoo } : { elem1: number; elem2: foo; }
>elem1 : number
>7 : 7
>elem2 : foo
>aFoo : foo
if (bBar.elem2 && bBar.elem2.bar && bBar.elem2.nested.b) {
>bBar.elem2 && bBar.elem2.bar && bBar.elem2.nested.b : string | 0 | null
>bBar.elem2 && bBar.elem2.bar : number | null
>bBar.elem2 : foo
>bBar : { elem1: number; elem2: foo; }
>elem2 : foo
>bBar.elem2.bar : number | null
>bBar.elem2 : foo
>bBar : { elem1: number; elem2: foo; }
>elem2 : foo
>bar : number | null
>bBar.elem2.nested.b : string | null
>bBar.elem2.nested : { a: number; b: string | null; }
>bBar.elem2 : foo
>bBar : { elem1: number; elem2: foo; }
>elem2 : foo
>nested : { a: number; b: string | null; }
>b : string | null
const { bar, baz, nested: {a, b: text} } = bBar.elem2;
>bar : number
>baz : string
>nested : any
>a : number
>b : any
>text : string
>bBar.elem2 : foo
>bBar : { elem1: number; elem2: foo; }
>elem2 : foo
const right: number = bBar.elem2.bar;
>right : number
>bBar.elem2.bar : number
>bBar.elem2 : foo
>bBar : { elem1: number; elem2: foo; }
>elem2 : foo
>bar : number
const wrong: number = bar;
>wrong : number
>bar : number
const another: string = baz;
>another : string
>baz : string
const aAgain: number = a;
>aAgain : number
>a : number
const bAgain: string = text;
>bAgain : string
>text : string
}

View File

@ -0,0 +1,36 @@
// @strictNullChecks: true
type foo = {
bar: number | null;
baz: string;
nested: {
a: number;
b: string | null;
}
};
const aFoo: foo = { bar: 3, baz: "b", nested: { a: 1, b: "y" } };
if (aFoo.bar && aFoo.nested.b) {
const { bar, baz, nested: {a, b: text} } = aFoo;
const right: number = aFoo.bar;
const wrong: number = bar;
const another: string = baz;
const aAgain: number = a;
const bAgain: string = text;
}
type bar = {
elem1: number | null;
elem2: foo | null;
};
const bBar = { elem1: 7, elem2: aFoo };
if (bBar.elem2 && bBar.elem2.bar && bBar.elem2.nested.b) {
const { bar, baz, nested: {a, b: text} } = bBar.elem2;
const right: number = bBar.elem2.bar;
const wrong: number = bar;
const another: string = baz;
const aAgain: number = a;
const bAgain: string = text;
}