Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/compiler/binder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
55 changes: 50 additions & 5 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4088,8 +4088,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;
Expand Down Expand Up @@ -4134,7 +4134,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) {
Expand Down Expand Up @@ -10656,7 +10657,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;
Expand All @@ -10666,7 +10667,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;
}
Expand All @@ -10682,6 +10690,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:
Expand All @@ -10696,6 +10726,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;
}
Expand Down Expand Up @@ -11486,6 +11527,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) {
Expand Down
57 changes: 57 additions & 0 deletions tests/baselines/reference/destructuringTypeGuardFlow.js
Original file line number Diff line number Diff line change
@@ -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;
}
143 changes: 143 additions & 0 deletions tests/baselines/reference/destructuringTypeGuardFlow.symbols
Original file line number Diff line number Diff line change
@@ -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))
}

Loading