feat(ivy): ngtsc support for static resolution of array.slice() (#27158)
For ngcc's processing of ES5 bundles, the spread syntax has been downleveled from `[...ARRAY]` to become `ARRAY.slice()`. This commit adds basic support for static resolution of such call. PR Close #27158
This commit is contained in:
parent
06d4a0c46e
commit
75723d5c89
|
@ -54,7 +54,7 @@ export function isDynamicValue(value: any): value is DynamicValue {
|
|||
* available statically.
|
||||
*/
|
||||
export type ResolvedValue = number | boolean | string | null | undefined | Reference | EnumValue |
|
||||
ResolvedValueArray | ResolvedValueMap | DynamicValue;
|
||||
ResolvedValueArray | ResolvedValueMap | BuiltinFn | DynamicValue;
|
||||
|
||||
/**
|
||||
* An array of `ResolvedValue`s.
|
||||
|
@ -81,6 +81,23 @@ export class EnumValue {
|
|||
constructor(readonly enumRef: Reference<ts.EnumDeclaration>, readonly name: string) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* An implementation of a builtin function, such as `Array.prototype.slice`.
|
||||
*/
|
||||
export abstract class BuiltinFn { abstract evaluate(args: ResolvedValueArray): ResolvedValue; }
|
||||
|
||||
class ArraySliceBuiltinFn extends BuiltinFn {
|
||||
constructor(private lhs: ResolvedValueArray) { super(); }
|
||||
|
||||
evaluate(args: ResolvedValueArray): ResolvedValue {
|
||||
if (args.length === 0) {
|
||||
return this.lhs;
|
||||
} else {
|
||||
return DYNAMIC_VALUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracks the scope of a function body, which includes `ResolvedValue`s for the parameters of that
|
||||
* body.
|
||||
|
@ -536,6 +553,8 @@ class StaticInterpreter {
|
|||
} else if (Array.isArray(lhs)) {
|
||||
if (rhs === 'length') {
|
||||
return lhs.length;
|
||||
} else if (rhs === 'slice') {
|
||||
return new ArraySliceBuiltinFn(lhs);
|
||||
}
|
||||
if (typeof rhs !== 'number' || !Number.isInteger(rhs)) {
|
||||
return DYNAMIC_VALUE;
|
||||
|
@ -571,6 +590,15 @@ class StaticInterpreter {
|
|||
|
||||
private visitCallExpression(node: ts.CallExpression, context: Context): ResolvedValue {
|
||||
const lhs = this.visitExpression(node.expression, context);
|
||||
if (isDynamicValue(lhs)) {
|
||||
return DYNAMIC_VALUE;
|
||||
}
|
||||
|
||||
// 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)));
|
||||
}
|
||||
|
||||
if (!(lhs instanceof Reference)) {
|
||||
throw new Error(`attempting to call something that is not a function: ${lhs}`);
|
||||
} else if (!isFunctionOrMethodReference(lhs)) {
|
||||
|
|
|
@ -118,6 +118,10 @@ describe('ngtsc metadata', () => {
|
|||
it('array `length` property access works',
|
||||
() => { expect(evaluate(`const a = [1, 2, 3];`, 'a[\'length\'] + 1')).toEqual(4); });
|
||||
|
||||
it('array `slice` function works', () => {
|
||||
expect(evaluate(`const a = [1, 2, 3];`, 'a[\'slice\']()')).toEqual([1, 2, 3]);
|
||||
});
|
||||
|
||||
it('negation works', () => {
|
||||
expect(evaluate(`const x = 3;`, '!x')).toEqual(false);
|
||||
expect(evaluate(`const x = 3;`, '!!x')).toEqual(true);
|
||||
|
|
Loading…
Reference in New Issue