From 75723d5c89e0ea958e440eef55aa715803ae32be Mon Sep 17 00:00:00 2001 From: JoostK Date: Sun, 18 Nov 2018 21:14:48 +0100 Subject: [PATCH] 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 --- .../src/ngtsc/metadata/src/resolver.ts | 30 ++++++++++++++++++- .../src/ngtsc/metadata/test/resolver_spec.ts | 4 +++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/packages/compiler-cli/src/ngtsc/metadata/src/resolver.ts b/packages/compiler-cli/src/ngtsc/metadata/src/resolver.ts index 7c5d0e7889..8223ed59b4 100644 --- a/packages/compiler-cli/src/ngtsc/metadata/src/resolver.ts +++ b/packages/compiler-cli/src/ngtsc/metadata/src/resolver.ts @@ -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, 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)) { diff --git a/packages/compiler-cli/src/ngtsc/metadata/test/resolver_spec.ts b/packages/compiler-cli/src/ngtsc/metadata/test/resolver_spec.ts index 9886e719df..8a4ed0a3fd 100644 --- a/packages/compiler-cli/src/ngtsc/metadata/test/resolver_spec.ts +++ b/packages/compiler-cli/src/ngtsc/metadata/test/resolver_spec.ts @@ -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);