From cb34514d0554bcd5126937598ebbca064adf18da Mon Sep 17 00:00:00 2001 From: JoostK Date: Sat, 13 Apr 2019 21:07:38 +0200 Subject: [PATCH] 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 --- .../partial_evaluator/src/interpreter.ts | 48 ++++++++++++------- .../partial_evaluator/test/evaluator_spec.ts | 7 +++ 2 files changed, 39 insertions(+), 16 deletions(-) diff --git a/packages/compiler-cli/src/ngtsc/partial_evaluator/src/interpreter.ts b/packages/compiler-cli/src/ngtsc/partial_evaluator/src/interpreter.ts index 3e34af4bc6..28dec13d2d 100644 --- a/packages/compiler-cli/src/ngtsc/partial_evaluator/src/interpreter.ts +++ b/packages/compiler-cli/src/ngtsc/partial_evaluator/src/interpreter.ts @@ -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(); 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; diff --git a/packages/compiler-cli/src/ngtsc/partial_evaluator/test/evaluator_spec.ts b/packages/compiler-cli/src/ngtsc/partial_evaluator/test/evaluator_spec.ts index bdd0b2b089..2c40004df5 100644 --- a/packages/compiler-cli/src/ngtsc/partial_evaluator/test/evaluator_spec.ts +++ b/packages/compiler-cli/src/ngtsc/partial_evaluator/test/evaluator_spec.ts @@ -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', () => {