feat(ivy): let ngtsc evaluate the spread operator in function calls (#29888)

Previously, ngtsc's static evaluator did not take spread operators into
account when evaluating function calls, nor did it handle rest arguments
correctly. This commit adds support for static evaluation of these
language features.

PR Close #29888
This commit is contained in:
JoostK 2019-04-13 21:07:38 +02:00 committed by Ben Lesh
parent 017bf0b794
commit cb34514d05
2 changed files with 39 additions and 16 deletions

View File

@ -142,14 +142,7 @@ export class StaticInterpreter {
for (let i = 0; i < node.elements.length; i++) {
const element = node.elements[i];
if (ts.isSpreadElement(element)) {
const spread = this.visitExpression(element.expression, context);
if (spread instanceof DynamicValue) {
array.push(DynamicValue.fromDynamicInput(element.expression, spread));
} else if (!Array.isArray(spread)) {
throw new Error(`Unexpected value in spread expression: ${spread}`);
} else {
array.push(...spread);
}
array.push(...this.visitSpreadElement(element, context));
} else {
array.push(this.visitExpression(element, context));
}
@ -383,7 +376,7 @@ export class StaticInterpreter {
// If the call refers to a builtin function, attempt to evaluate the function.
if (lhs instanceof BuiltinFn) {
return lhs.evaluate(node.arguments.map(arg => this.visitExpression(arg, context)));
return lhs.evaluate(this.evaluateFunctionArguments(node, context));
}
if (!(lhs instanceof Reference)) {
@ -432,17 +425,17 @@ export class StaticInterpreter {
}
const ret = body[0] as ts.ReturnStatement;
const args = this.evaluateFunctionArguments(node, context);
const newScope: Scope = new Map<ts.ParameterDeclaration, ResolvedValue>();
fn.parameters.forEach((param, index) => {
let value: ResolvedValue = undefined;
if (index < node.arguments.length) {
const arg = node.arguments[index];
value = this.visitExpression(arg, context);
let arg = args[index];
if (param.node.dotDotDotToken !== undefined) {
arg = args.slice(index);
}
if (value === undefined && param.initializer !== null) {
value = this.visitExpression(param.initializer, context);
if (arg === undefined && param.initializer !== null) {
arg = this.visitExpression(param.initializer, context);
}
newScope.set(param.node, value);
newScope.set(param.node, arg);
});
return ret.expression !== undefined ?
@ -509,6 +502,29 @@ export class StaticInterpreter {
return this.visitExpression(node.expression, context);
}
private evaluateFunctionArguments(node: ts.CallExpression, context: Context): ResolvedValueArray {
const args: ResolvedValueArray = [];
for (const arg of node.arguments) {
if (ts.isSpreadElement(arg)) {
args.push(...this.visitSpreadElement(arg, context));
} else {
args.push(this.visitExpression(arg, context));
}
}
return args;
}
private visitSpreadElement(node: ts.SpreadElement, context: Context): ResolvedValueArray {
const spread = this.visitExpression(node.expression, context);
if (spread instanceof DynamicValue) {
return [DynamicValue.fromDynamicInput(node.expression, spread)];
} else if (!Array.isArray(spread)) {
throw new Error(`Unexpected value in spread expression: ${spread}`);
} else {
return spread;
}
}
private stringNameFromPropertyName(node: ts.PropertyName, context: Context): string|undefined {
if (ts.isIdentifier(node) || ts.isStringLiteral(node) || ts.isNumericLiteral(node)) {
return node.text;

View File

@ -75,6 +75,12 @@ describe('ngtsc metadata', () => {
expect(evaluate(`function foo(bar) { return bar; }`, 'foo("test")')).toEqual('test');
});
it('function call spread works', () => {
expect(evaluate(`function foo(a, ...b) { return [a, b]; }`, 'foo(1, ...[2, 3])')).toEqual([
1, [2, 3]
]);
});
it('conditionals work', () => {
expect(evaluate(`const x = false; const y = x ? 'true' : 'false';`, 'y')).toEqual('false');
});
@ -136,6 +142,7 @@ describe('ngtsc metadata', () => {
expect(evaluate(`const a = [1, 2], b = 3, c = [4, 5];`, 'a[\'concat\'](b, c)')).toEqual([
1, 2, 3, 4, 5
]);
expect(evaluate(`const a = [1, 2], b = [3, 4]`, 'a[\'concat\'](...b)')).toEqual([1, 2, 3, 4]);
});
it('negation works', () => {