fix(compiler): unable to resolve destructuring variable declarations (#37497)

Currently the partial evaluator isn't able to resolve a variable declaration that uses destructuring in the form of `const {value} = {value: 0}; const foo = value;`. These changes add some logic to allow for us to resolve the variable's value.

Fixes #36917.

PR Close #37497
This commit is contained in:
crisbeto 2020-06-09 21:32:26 +02:00 committed by Misko Hevery
parent 12f2101d08
commit 2e355fd4d3
2 changed files with 97 additions and 3 deletions

View File

@ -266,6 +266,8 @@ export class StaticInterpreter {
return this.visitEnumDeclaration(node, context);
} else if (ts.isSourceFile(node)) {
return this.visitSourceFile(node, context);
} else if (ts.isBindingElement(node)) {
return this.visitBindingElement(node, context);
} else {
return this.getReference(node, context);
}
@ -347,9 +349,8 @@ export class StaticInterpreter {
});
}
private accessHelper(
node: ts.Expression, lhs: ResolvedValue, rhs: string|number,
context: Context): ResolvedValue {
private accessHelper(node: ts.Node, lhs: ResolvedValue, rhs: string|number, context: Context):
ResolvedValue {
const strIndex = `${rhs}`;
if (lhs instanceof Map) {
if (lhs.has(strIndex)) {
@ -598,6 +599,47 @@ export class StaticInterpreter {
}
}
private visitBindingElement(node: ts.BindingElement, context: Context): ResolvedValue {
const path: ts.BindingElement[] = [];
let closestDeclaration: ts.Node = node;
while (ts.isBindingElement(closestDeclaration) ||
ts.isArrayBindingPattern(closestDeclaration) ||
ts.isObjectBindingPattern(closestDeclaration)) {
if (ts.isBindingElement(closestDeclaration)) {
path.unshift(closestDeclaration);
}
closestDeclaration = closestDeclaration.parent;
}
if (!ts.isVariableDeclaration(closestDeclaration) ||
closestDeclaration.initializer === undefined) {
return DynamicValue.fromUnknown(node);
}
let value = this.visit(closestDeclaration.initializer, context);
for (const element of path) {
let key: number|string;
if (ts.isArrayBindingPattern(element.parent)) {
key = element.parent.elements.indexOf(element);
} else {
const name = element.propertyName || element.name;
if (ts.isIdentifier(name)) {
key = name.text;
} else {
return DynamicValue.fromUnknown(element);
}
}
value = this.accessHelper(element, value, key, context);
if (value instanceof DynamicValue) {
return value;
}
}
return value;
}
private stringNameFromPropertyName(node: ts.PropertyName, context: Context): string|undefined {
if (ts.isIdentifier(node) || ts.isStringLiteral(node) || ts.isNumericLiteral(node)) {
return node.text;

View File

@ -200,6 +200,58 @@ runInEachFileSystem(() => {
expect(evaluate('const a = null;', 'a')).toEqual(null);
});
it('supports destructuring array variable declarations', () => {
const code = `
const [a, b, c, d] = [0, 1, 2, 3];
const e = c;
`;
expect(evaluate(code, 'a')).toBe(0);
expect(evaluate(code, 'b')).toBe(1);
expect(evaluate(code, 'c')).toBe(2);
expect(evaluate(code, 'd')).toBe(3);
expect(evaluate(code, 'e')).toBe(2);
});
it('supports destructuring object variable declaration', () => {
const code = `
const {a, b, c, d} = {a: 0, b: 1, c: 2, d: 3};
const e = c;
`;
expect(evaluate(code, 'a')).toBe(0);
expect(evaluate(code, 'b')).toBe(1);
expect(evaluate(code, 'c')).toBe(2);
expect(evaluate(code, 'd')).toBe(3);
expect(evaluate(code, 'e')).toBe(2);
});
it('supports destructuring object variable declaration with an alias', () => {
expect(evaluate(`const {a: value} = {a: 5}; const e = value;`, 'e')).toBe(5);
});
it('supports nested destructuring object variable declarations', () => {
expect(evaluate(`const {a: {b: {c}}} = {a: {b: {c: 0}}};`, 'c')).toBe(0);
});
it('supports nested destructuring array variable declarations', () => {
expect(evaluate(`const [[[a]]] = [[[1]]];`, 'a')).toBe(1);
});
it('supports nested destructuring variable declarations mixing arrays and objects', () => {
expect(evaluate(`const {a: {b: [[c]]}} = {a: {b: [[1337]]}};`, 'c')).toBe(1337);
});
it('resolves unknown values in a destructured variable declaration as dynamic values', () => {
const value = evaluate(
`const {a: {body}} = {a: window};`, 'body',
[{name: _('/window.ts'), contents: `declare const window: any;`}]);
if (!(value instanceof DynamicValue)) {
return fail(`Should have resolved to a DynamicValue`);
}
expect(value.node.getText()).toBe('body');
});
it('resolves unknown binary operators as dynamic value', () => {
const value = evaluate('declare const window: any;', '"location" in window');
if (!(value instanceof DynamicValue)) {