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', () => {