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.
|
* available statically.
|
||||||
*/
|
*/
|
||||||
export type ResolvedValue = number | boolean | string | null | undefined | Reference | EnumValue |
|
export type ResolvedValue = number | boolean | string | null | undefined | Reference | EnumValue |
|
||||||
ResolvedValueArray | ResolvedValueMap | DynamicValue;
|
ResolvedValueArray | ResolvedValueMap | BuiltinFn | DynamicValue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An array of `ResolvedValue`s.
|
* An array of `ResolvedValue`s.
|
||||||
|
@ -81,6 +81,23 @@ export class EnumValue {
|
||||||
constructor(readonly enumRef: Reference<ts.EnumDeclaration>, readonly name: string) {}
|
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
|
* Tracks the scope of a function body, which includes `ResolvedValue`s for the parameters of that
|
||||||
* body.
|
* body.
|
||||||
|
@ -536,6 +553,8 @@ class StaticInterpreter {
|
||||||
} else if (Array.isArray(lhs)) {
|
} else if (Array.isArray(lhs)) {
|
||||||
if (rhs === 'length') {
|
if (rhs === 'length') {
|
||||||
return lhs.length;
|
return lhs.length;
|
||||||
|
} else if (rhs === 'slice') {
|
||||||
|
return new ArraySliceBuiltinFn(lhs);
|
||||||
}
|
}
|
||||||
if (typeof rhs !== 'number' || !Number.isInteger(rhs)) {
|
if (typeof rhs !== 'number' || !Number.isInteger(rhs)) {
|
||||||
return DYNAMIC_VALUE;
|
return DYNAMIC_VALUE;
|
||||||
|
@ -571,6 +590,15 @@ class StaticInterpreter {
|
||||||
|
|
||||||
private visitCallExpression(node: ts.CallExpression, context: Context): ResolvedValue {
|
private visitCallExpression(node: ts.CallExpression, context: Context): ResolvedValue {
|
||||||
const lhs = this.visitExpression(node.expression, context);
|
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)) {
|
if (!(lhs instanceof Reference)) {
|
||||||
throw new Error(`attempting to call something that is not a function: ${lhs}`);
|
throw new Error(`attempting to call something that is not a function: ${lhs}`);
|
||||||
} else if (!isFunctionOrMethodReference(lhs)) {
|
} else if (!isFunctionOrMethodReference(lhs)) {
|
||||||
|
|
|
@ -118,6 +118,10 @@ describe('ngtsc metadata', () => {
|
||||||
it('array `length` property access works',
|
it('array `length` property access works',
|
||||||
() => { expect(evaluate(`const a = [1, 2, 3];`, 'a[\'length\'] + 1')).toEqual(4); });
|
() => { 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', () => {
|
it('negation works', () => {
|
||||||
expect(evaluate(`const x = 3;`, '!x')).toEqual(false);
|
expect(evaluate(`const x = 3;`, '!x')).toEqual(false);
|
||||||
expect(evaluate(`const x = 3;`, '!!x')).toEqual(true);
|
expect(evaluate(`const x = 3;`, '!!x')).toEqual(true);
|
||||||
|
|
Loading…
Reference in New Issue