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:
parent
017bf0b794
commit
cb34514d05
|
@ -142,14 +142,7 @@ export class StaticInterpreter {
|
||||||
for (let i = 0; i < node.elements.length; i++) {
|
for (let i = 0; i < node.elements.length; i++) {
|
||||||
const element = node.elements[i];
|
const element = node.elements[i];
|
||||||
if (ts.isSpreadElement(element)) {
|
if (ts.isSpreadElement(element)) {
|
||||||
const spread = this.visitExpression(element.expression, context);
|
array.push(...this.visitSpreadElement(element, 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);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
array.push(this.visitExpression(element, context));
|
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 the call refers to a builtin function, attempt to evaluate the function.
|
||||||
if (lhs instanceof BuiltinFn) {
|
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)) {
|
if (!(lhs instanceof Reference)) {
|
||||||
|
@ -432,17 +425,17 @@ export class StaticInterpreter {
|
||||||
}
|
}
|
||||||
const ret = body[0] as ts.ReturnStatement;
|
const ret = body[0] as ts.ReturnStatement;
|
||||||
|
|
||||||
|
const args = this.evaluateFunctionArguments(node, context);
|
||||||
const newScope: Scope = new Map<ts.ParameterDeclaration, ResolvedValue>();
|
const newScope: Scope = new Map<ts.ParameterDeclaration, ResolvedValue>();
|
||||||
fn.parameters.forEach((param, index) => {
|
fn.parameters.forEach((param, index) => {
|
||||||
let value: ResolvedValue = undefined;
|
let arg = args[index];
|
||||||
if (index < node.arguments.length) {
|
if (param.node.dotDotDotToken !== undefined) {
|
||||||
const arg = node.arguments[index];
|
arg = args.slice(index);
|
||||||
value = this.visitExpression(arg, context);
|
|
||||||
}
|
}
|
||||||
if (value === undefined && param.initializer !== null) {
|
if (arg === undefined && param.initializer !== null) {
|
||||||
value = this.visitExpression(param.initializer, context);
|
arg = this.visitExpression(param.initializer, context);
|
||||||
}
|
}
|
||||||
newScope.set(param.node, value);
|
newScope.set(param.node, arg);
|
||||||
});
|
});
|
||||||
|
|
||||||
return ret.expression !== undefined ?
|
return ret.expression !== undefined ?
|
||||||
|
@ -509,6 +502,29 @@ export class StaticInterpreter {
|
||||||
return this.visitExpression(node.expression, context);
|
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 {
|
private stringNameFromPropertyName(node: ts.PropertyName, context: Context): string|undefined {
|
||||||
if (ts.isIdentifier(node) || ts.isStringLiteral(node) || ts.isNumericLiteral(node)) {
|
if (ts.isIdentifier(node) || ts.isStringLiteral(node) || ts.isNumericLiteral(node)) {
|
||||||
return node.text;
|
return node.text;
|
||||||
|
|
|
@ -75,6 +75,12 @@ describe('ngtsc metadata', () => {
|
||||||
expect(evaluate(`function foo(bar) { return bar; }`, 'foo("test")')).toEqual('test');
|
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', () => {
|
it('conditionals work', () => {
|
||||||
expect(evaluate(`const x = false; const y = x ? 'true' : 'false';`, 'y')).toEqual('false');
|
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([
|
expect(evaluate(`const a = [1, 2], b = 3, c = [4, 5];`, 'a[\'concat\'](b, c)')).toEqual([
|
||||||
1, 2, 3, 4, 5
|
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', () => {
|
it('negation works', () => {
|
||||||
|
|
Loading…
Reference in New Issue