From f693be39966994f11d51c683ec866c8612fe5628 Mon Sep 17 00:00:00 2001 From: Kara Erickson Date: Wed, 14 Feb 2018 13:37:54 -0800 Subject: [PATCH] feat(ivy): add pureFunction0 instruction (#22214) PR Close #22214 --- .../core/src/core_render3_private_export.ts | 1 + packages/core/src/render3/index.ts | 1 + packages/core/src/render3/instructions.ts | 50 +++- packages/core/src/render3/pure_function.ts | 247 +++++------------- .../test/render3/compiler_canonical_spec.ts | 117 ++++++++- .../core/test/render3/integration_spec.ts | 2 +- .../core/test/render3/pure_function_spec.ts | 176 +++++++++---- 7 files changed, 353 insertions(+), 241 deletions(-) diff --git a/packages/core/src/core_render3_private_export.ts b/packages/core/src/core_render3_private_export.ts index 216e69d120..45992b9c5c 100644 --- a/packages/core/src/core_render3_private_export.ts +++ b/packages/core/src/core_render3_private_export.ts @@ -44,6 +44,7 @@ export { pb3 as ɵpb3, pb4 as ɵpb4, pbV as ɵpbV, + f0 as ɵf0, f1 as ɵf1, f2 as ɵf2, f3 as ɵf3, diff --git a/packages/core/src/render3/index.ts b/packages/core/src/render3/index.ts index 6769e0fd85..69861de784 100644 --- a/packages/core/src/render3/index.ts +++ b/packages/core/src/render3/index.ts @@ -79,6 +79,7 @@ export { queryRefresh as qR, } from './query'; export { + pureFunction0 as f0, pureFunction1 as f1, pureFunction2 as f2, pureFunction3 as f3, diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index e636e9d045..0865bc6e2c 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -1749,16 +1749,54 @@ function valueInData(data: any[], index: number, value?: T): T { return value !; } -/** Gets the binding at the current bindingIndex */ -export function peekBinding(): any { - ngDevMode && assertNotEqual(currentView.bindingStartIndex, null, 'bindingStartIndex'); - return data[bindingIndex]; -} - export function getCurrentQueries(QueryType: {new (): LQueries}): LQueries { return currentQueries || (currentQueries = new QueryType()); } +export function getCreationMode(): boolean { + return creationMode; +} + +/** Gets the current binding value and increments the binding index. */ +export function consumeBinding(): any { + ngDevMode && assertDataInRange(bindingIndex); + ngDevMode && + assertNotEqual(data[bindingIndex], NO_CHANGE, 'Stored value should never be NO_CHANGE.'); + return data[bindingIndex++]; +} + +/** Updates binding if changed, then returns whether it was updated. */ +export function bindingUpdated(value: any): boolean { + ngDevMode && assertNotEqual(value, NO_CHANGE, 'Incoming value should never be NO_CHANGE.'); + + if (creationMode || isDifferent(data[bindingIndex], value)) { + creationMode && initBindings(); + data[bindingIndex++] = value; + return true; + } else { + bindingIndex++; + return false; + } +} + +/** Updates binding if changed, then returns the latest value. */ +export function checkAndUpdateBinding(value: any): any { + bindingUpdated(value); + return value; +} + +/** Updates 2 bindings if changed, then returns whether either was updated. */ +export function bindingUpdated2(exp1: any, exp2: any): boolean { + const different = bindingUpdated(exp1); + return bindingUpdated(exp2) || different; +} + +/** Updates 4 bindings if changed, then returns whether any was updated. */ +export function bindingUpdated4(exp1: any, exp2: any, exp3: any, exp4: any): boolean { + const different = bindingUpdated2(exp1, exp2); + return bindingUpdated2(exp3, exp4) || different; +} + export function getPreviousOrParentNode(): LNode { return previousOrParentNode; } diff --git a/packages/core/src/render3/pure_function.ts b/packages/core/src/render3/pure_function.ts index 22bc3acc9b..dc50cb2112 100644 --- a/packages/core/src/render3/pure_function.ts +++ b/packages/core/src/render3/pure_function.ts @@ -6,105 +6,85 @@ * found in the LICENSE file at https://angular.io/license */ -import {NO_CHANGE, bind, peekBinding} from './instructions'; +import {bindingUpdated, bindingUpdated2, bindingUpdated4, checkAndUpdateBinding, consumeBinding, getCreationMode} from './instructions'; + + /** - * If the value of the provided exp has changed, calls the pure function to - * return an updated value. Or if the value has not changed, returns NO_CHANGE. + * If the value hasn't been saved, calls the pure function to store and return the + * value. If it has been saved, returns the saved value. * - * @param pureFn Function that returns an updated value - * @param exp Updated expression value - * @returns Updated value or NO_CHANGE + * @param pureFn Function that returns a value + * @returns value */ -export function pureFunction1(pureFn: (v: any) => any, exp: any): any { - let different = false; - const latestValue = exp === NO_CHANGE ? peekBinding() : exp; - if (bind(exp) !== NO_CHANGE) different = true; - - return different ? pureFn(latestValue) : NO_CHANGE; +export function pureFunction0(pureFn: () => T): T { + return getCreationMode() ? checkAndUpdateBinding(pureFn()) : consumeBinding(); } /** - * If the value of any provided exp has changed, calls the pure function to - * return an updated value. Or if no values have changed, returns NO_CHANGE. + * If the value of the provided exp has changed, calls the pure function to return + * an updated value. Or if the value has not changed, returns cached value. + * + * @param pureFn Function that returns an updated value + * @param exp Updated expression value + * @returns Updated value + */ +export function pureFunction1(pureFn: (v: any) => any, exp: any): any { + return bindingUpdated(exp) ? checkAndUpdateBinding(pureFn(exp)) : consumeBinding(); +} + +/** + * If the value of any provided exp has changed, calls the pure function to return + * an updated value. Or if no values have changed, returns cached value. * * @param pureFn * @param exp1 * @param exp2 - * @returns Updated value or NO_CHANGE + * @returns Updated value */ export function pureFunction2(pureFn: (v1: any, v2: any) => any, exp1: any, exp2: any): any { - let different = false; - - const latestVal1 = exp1 === NO_CHANGE ? peekBinding() : exp1; - if (bind(exp1) !== NO_CHANGE) different = true; - - const latestVal2 = exp2 === NO_CHANGE ? peekBinding() : exp2; - if (bind(exp2) !== NO_CHANGE) different = true; - - return different ? pureFn(latestVal1, latestVal2) : NO_CHANGE; + return bindingUpdated2(exp1, exp2) ? checkAndUpdateBinding(pureFn(exp1, exp2)) : consumeBinding(); } /** - * If the value of any provided exp has changed, calls the pure function to - * return an updated value. Or if no values have changed, returns NO_CHANGE. + * If the value of any provided exp has changed, calls the pure function to return + * an updated value. Or if no values have changed, returns cached value. * * @param pureFn * @param exp1 * @param exp2 * @param exp3 - * @returns Updated value or NO_CHANGE + * @returns Updated value */ export function pureFunction3( pureFn: (v1: any, v2: any, v3: any) => any, exp1: any, exp2: any, exp3: any): any { - let different = false; - - const latestVal1 = exp1 === NO_CHANGE ? peekBinding() : exp1; - if (bind(exp1) !== NO_CHANGE) different = true; - - const latestVal2 = exp2 === NO_CHANGE ? peekBinding() : exp2; - if (bind(exp2) !== NO_CHANGE) different = true; - - const latestVal3 = exp3 === NO_CHANGE ? peekBinding() : exp3; - if (bind(exp3) !== NO_CHANGE) different = true; - - return different ? pureFn(latestVal1, latestVal2, latestVal3) : NO_CHANGE; + const different = bindingUpdated2(exp1, exp2); + return bindingUpdated(exp3) || different ? checkAndUpdateBinding(pureFn(exp1, exp2, exp3)) : + consumeBinding(); } /** - * If the value of any provided exp has changed, calls the pure function to - * return an updated value. Or if no values have changed, returns NO_CHANGE. + * If the value of any provided exp has changed, calls the pure function to return + * an updated value. Or if no values have changed, returns cached value. * * @param pureFn * @param exp1 * @param exp2 * @param exp3 * @param exp4 - * @returns Updated value or NO_CHANGE + * @returns Updated value */ export function pureFunction4( pureFn: (v1: any, v2: any, v3: any, v4: any) => any, exp1: any, exp2: any, exp3: any, exp4: any): any { - let different = false; - - const latestVal1 = exp1 === NO_CHANGE ? peekBinding() : exp1; - if (bind(exp1) !== NO_CHANGE) different = true; - - const latestVal2 = exp2 === NO_CHANGE ? peekBinding() : exp2; - if (bind(exp2) !== NO_CHANGE) different = true; - - const latestVal3 = exp3 === NO_CHANGE ? peekBinding() : exp3; - if (bind(exp3) !== NO_CHANGE) different = true; - - const latestVal4 = exp4 === NO_CHANGE ? peekBinding() : exp4; - if (bind(exp4) !== NO_CHANGE) different = true; - - return different ? pureFn(latestVal1, latestVal2, latestVal3, latestVal4) : NO_CHANGE; + return bindingUpdated4(exp1, exp2, exp3, exp4) ? + checkAndUpdateBinding(pureFn(exp1, exp2, exp3, exp4)) : + consumeBinding(); } /** - * If the value of any provided exp has changed, calls the pure function to - * return an updated value. Or if no values have changed, returns NO_CHANGE. + * If the value of any provided exp has changed, calls the pure function to return + * an updated value. Or if no values have changed, returns cached value. * * @param pureFn * @param exp1 @@ -112,34 +92,20 @@ export function pureFunction4( * @param exp3 * @param exp4 * @param exp5 - * @returns Updated value or NO_CHANGE + * @returns Updated value */ export function pureFunction5( pureFn: (v1: any, v2: any, v3: any, v4: any, v5: any) => any, exp1: any, exp2: any, exp3: any, exp4: any, exp5: any): any { - let different = false; - - const latestVal1 = exp1 === NO_CHANGE ? peekBinding() : exp1; - if (bind(exp1) !== NO_CHANGE) different = true; - - const latestVal2 = exp2 === NO_CHANGE ? peekBinding() : exp2; - if (bind(exp2) !== NO_CHANGE) different = true; - - const latestVal3 = exp3 === NO_CHANGE ? peekBinding() : exp3; - if (bind(exp3) !== NO_CHANGE) different = true; - - const latestVal4 = exp4 === NO_CHANGE ? peekBinding() : exp4; - if (bind(exp4) !== NO_CHANGE) different = true; - - const latestVal5 = exp5 === NO_CHANGE ? peekBinding() : exp5; - if (bind(exp5) !== NO_CHANGE) different = true; - - return different ? pureFn(latestVal1, latestVal2, latestVal3, latestVal4, latestVal5) : NO_CHANGE; + const different = bindingUpdated4(exp1, exp2, exp3, exp4); + return bindingUpdated(exp5) || different ? + checkAndUpdateBinding(pureFn(exp1, exp2, exp3, exp4, exp5)) : + consumeBinding(); } /** - * If the value of any provided exp has changed, calls the pure function to - * return an updated value. Or if no values have changed, returns NO_CHANGE. + * If the value of any provided exp has changed, calls the pure function to return + * an updated value. Or if no values have changed, returns cached value. * * @param pureFn * @param exp1 @@ -148,39 +114,20 @@ export function pureFunction5( * @param exp4 * @param exp5 * @param exp6 - * @returns Updated value or NO_CHANGE + * @returns Updated value */ export function pureFunction6( pureFn: (v1: any, v2: any, v3: any, v4: any, v5: any, v6: any) => any, exp1: any, exp2: any, exp3: any, exp4: any, exp5: any, exp6: any): any { - let different = false; - - const latestVal1 = exp1 === NO_CHANGE ? peekBinding() : exp1; - if (bind(exp1) !== NO_CHANGE) different = true; - - const latestVal2 = exp2 === NO_CHANGE ? peekBinding() : exp2; - if (bind(exp2) !== NO_CHANGE) different = true; - - const latestVal3 = exp3 === NO_CHANGE ? peekBinding() : exp3; - if (bind(exp3) !== NO_CHANGE) different = true; - - const latestVal4 = exp4 === NO_CHANGE ? peekBinding() : exp4; - if (bind(exp4) !== NO_CHANGE) different = true; - - const latestVal5 = exp5 === NO_CHANGE ? peekBinding() : exp5; - if (bind(exp5) !== NO_CHANGE) different = true; - - const latestVal6 = exp6 === NO_CHANGE ? peekBinding() : exp6; - if (bind(exp6) !== NO_CHANGE) different = true; - - return different ? - pureFn(latestVal1, latestVal2, latestVal3, latestVal4, latestVal5, latestVal6) : - NO_CHANGE; + const different = bindingUpdated4(exp1, exp2, exp3, exp4); + return bindingUpdated2(exp5, exp6) || different ? + checkAndUpdateBinding(pureFn(exp1, exp2, exp3, exp4, exp5, exp6)) : + consumeBinding(); } /** - * If the value of any provided exp has changed, calls the pure function to - * return an updated value. Or if no values have changed, returns NO_CHANGE. + * If the value of any provided exp has changed, calls the pure function to return + * an updated value. Or if no values have changed, returns cached value. * * @param pureFn * @param exp1 @@ -190,42 +137,21 @@ export function pureFunction6( * @param exp5 * @param exp6 * @param exp7 - * @returns Updated value or NO_CHANGE + * @returns Updated value */ export function pureFunction7( pureFn: (v1: any, v2: any, v3: any, v4: any, v5: any, v6: any, v7: any) => any, exp1: any, exp2: any, exp3: any, exp4: any, exp5: any, exp6: any, exp7: any): any { - let different = false; - - const latestVal1 = exp1 === NO_CHANGE ? peekBinding() : exp1; - if (bind(exp1) !== NO_CHANGE) different = true; - - const latestVal2 = exp2 === NO_CHANGE ? peekBinding() : exp2; - if (bind(exp2) !== NO_CHANGE) different = true; - - const latestVal3 = exp3 === NO_CHANGE ? peekBinding() : exp3; - if (bind(exp3) !== NO_CHANGE) different = true; - - const latestVal4 = exp4 === NO_CHANGE ? peekBinding() : exp4; - if (bind(exp4) !== NO_CHANGE) different = true; - - const latestVal5 = exp5 === NO_CHANGE ? peekBinding() : exp5; - if (bind(exp5) !== NO_CHANGE) different = true; - - const latestVal6 = exp6 === NO_CHANGE ? peekBinding() : exp6; - if (bind(exp6) !== NO_CHANGE) different = true; - - const latestVal7 = exp7 === NO_CHANGE ? peekBinding() : exp7; - if (bind(exp7) !== NO_CHANGE) different = true; - - return different ? - pureFn(latestVal1, latestVal2, latestVal3, latestVal4, latestVal5, latestVal6, latestVal7) : - NO_CHANGE; + let different = bindingUpdated4(exp1, exp2, exp3, exp4); + different = bindingUpdated2(exp5, exp6) || different; + return bindingUpdated(exp7) || different ? + checkAndUpdateBinding(pureFn(exp1, exp2, exp3, exp4, exp5, exp6, exp7)) : + consumeBinding(); } /** - * If the value of any provided exp has changed, calls the pure function to - * return an updated value. Or if no values have changed, returns NO_CHANGE. + * If the value of any provided exp has changed, calls the pure function to return + * an updated value. Or if no values have changed, returns cached value. * * @param pureFn * @param exp1 @@ -236,62 +162,33 @@ export function pureFunction7( * @param exp6 * @param exp7 * @param exp8 - * @returns Updated value or NO_CHANGE + * @returns Updated value */ export function pureFunction8( pureFn: (v1: any, v2: any, v3: any, v4: any, v5: any, v6: any, v7: any, v8: any) => any, exp1: any, exp2: any, exp3: any, exp4: any, exp5: any, exp6: any, exp7: any, exp8: any): any { - let different = false; - - const latestVal1 = exp1 === NO_CHANGE ? peekBinding() : exp1; - if (bind(exp1) !== NO_CHANGE) different = true; - - const latestVal2 = exp2 === NO_CHANGE ? peekBinding() : exp2; - if (bind(exp2) !== NO_CHANGE) different = true; - - const latestVal3 = exp3 === NO_CHANGE ? peekBinding() : exp3; - if (bind(exp3) !== NO_CHANGE) different = true; - - const latestVal4 = exp4 === NO_CHANGE ? peekBinding() : exp4; - if (bind(exp4) !== NO_CHANGE) different = true; - - const latestVal5 = exp5 === NO_CHANGE ? peekBinding() : exp5; - if (bind(exp5) !== NO_CHANGE) different = true; - - const latestVal6 = exp6 === NO_CHANGE ? peekBinding() : exp6; - if (bind(exp6) !== NO_CHANGE) different = true; - - const latestVal7 = exp7 === NO_CHANGE ? peekBinding() : exp7; - if (bind(exp7) !== NO_CHANGE) different = true; - - const latestVal8 = exp8 === NO_CHANGE ? peekBinding() : exp8; - if (bind(exp8) !== NO_CHANGE) different = true; - - return different ? pureFn( - latestVal1, latestVal2, latestVal3, latestVal4, latestVal5, latestVal6, - latestVal7, latestVal8) : - NO_CHANGE; + const different = bindingUpdated4(exp1, exp2, exp3, exp4); + return bindingUpdated4(exp1, exp2, exp3, exp4) || different ? + checkAndUpdateBinding(pureFn(exp1, exp2, exp3, exp4, exp5, exp6, exp7, exp8)) : + consumeBinding(); } /** * pureFunction instruction that can support any number of bindings. * - * If the value of any provided exp has changed, calls the pure function to - * return an updated value. Or if no values have changed, returns NO_CHANGE. + * If the value of any provided exp has changed, calls the pure function to return + * an updated value. Or if no values have changed, returns cached value. * * @param pureFn A pure function that takes binding values and builds an object or array * containing those values. * @param exp An array of binding values - * @returns Updated value or NO_CHANGE + * @returns Updated value */ -export function pureFunctionV(pureFn: (v: any[]) => any, exps: any[]): any { +export function pureFunctionV(pureFn: (...v: any[]) => any, exps: any[]): any { let different = false; for (let i = 0; i < exps.length; i++) { - const exp = exps[i]; - if (exp === NO_CHANGE) exps[i] = peekBinding(); - if (bind(exp) !== NO_CHANGE) different = true; + bindingUpdated(exps[i]) && (different = true); } - - return different ? pureFn(exps) : NO_CHANGE; + return different ? checkAndUpdateBinding(pureFn.apply(null, exps)) : consumeBinding(); } diff --git a/packages/core/test/render3/compiler_canonical_spec.ts b/packages/core/test/render3/compiler_canonical_spec.ts index 5c56658a77..135fd2c228 100644 --- a/packages/core/test/render3/compiler_canonical_spec.ts +++ b/packages/core/test/render3/compiler_canonical_spec.ts @@ -209,7 +209,7 @@ describe('compiler specification', () => { if (cm) { $r3$.ɵT(0); } - $r3$.ɵt(0, $r3$.ɵb2('', ctx.names[0], ' ', ctx.names[1], '')); + $r3$.ɵt(0, $r3$.ɵi2('', ctx.names[0], ' ', ctx.names[1], '')); }, inputs: {names: 'names'} }); @@ -239,7 +239,7 @@ describe('compiler specification', () => { $r3$.ɵE(0, MyArrayComp); $r3$.ɵe(); } - $r3$.ɵp(0, 'names', $r3$.ɵb0($e0_arr$)); + $r3$.ɵp(0, 'names', cm ? $e0_arr$ : $r3$.ɵNC); MyArrayComp.ngComponentDef.h(1, 0); $r3$.ɵr(1, 0); } @@ -250,6 +250,101 @@ describe('compiler specification', () => { expect(renderComp(MyApp)).toEqual(`Nancy Bess`); }); + it('should support array literals of constants inside function calls', () => { + type $MyApp$ = MyApp; + + // NORMATIVE + const $e0_ff$ = () => ['Nancy', 'Bess']; + // /NORMATIVE + + @Component({ + selector: 'my-app', + template: ` + + ` + }) + class MyApp { + someFn(arr: string[]): string[] { + arr[0] = arr[0].toUpperCase(); + return arr; + } + + // NORMATIVE + static ngComponentDef = $r3$.ɵdefineComponent({ + type: MyApp, + tag: 'my-app', + factory: function MyApp_Factory() { return new MyApp(); }, + template: function MyApp_Template(ctx: $MyApp$, cm: $boolean$) { + if (cm) { + $r3$.ɵE(0, MyArrayComp); + $r3$.ɵe(); + } + $r3$.ɵp(0, 'names', $r3$.ɵb(ctx.someFn($r3$.ɵf0($e0_ff$)))); + MyArrayComp.ngComponentDef.h(1, 0); + $r3$.ɵr(1, 0); + } + }); + // /NORMATIVE + } + + expect(renderComp(MyApp)).toEqual(`NANCY Bess`); + }); + + it('should support array literals of constants inside expressions', () => { + type $MyApp$ = MyApp; + type $MyComp$ = MyComp; + + @Component({selector: 'my-comp', template: `{{ num }}`}) + class MyComp { + num: number; + + static ngComponentDef = $r3$.ɵdefineComponent({ + type: MyComp, + tag: 'my-comp', + factory: function MyComp_Factory() { return new MyComp(); }, + template: function MyComp_Template(ctx: $MyComp$, cm: $boolean$) { + if (cm) { + $r3$.ɵT(0); + } + $r3$.ɵt(0, $r3$.ɵb(ctx.num)); + }, + inputs: {num: 'num'} + }); + } + + // NORMATIVE + const $e0_ff$ = () => ['Nancy', 'Bess']; + // /NORMATIVE + + @Component({ + selector: 'my-app', + template: ` + + ` + }) + class MyApp { + // NORMATIVE + static ngComponentDef = $r3$.ɵdefineComponent({ + type: MyApp, + tag: 'my-app', + factory: function MyApp_Factory() { return new MyApp(); }, + template: function MyApp_Template(ctx: $MyApp$, cm: $boolean$) { + if (cm) { + $r3$.ɵE(0, MyComp); + $r3$.ɵe(); + } + $r3$.ɵp(0, 'num', $r3$.ɵb($r3$.ɵf0($e0_ff$).length + 1)); + MyComp.ngComponentDef.h(1, 0); + $r3$.ɵr(1, 0); + } + }); + // /NORMATIVE + } + + expect(renderComp(MyApp)).toEqual(`3`); + }); + + it('should support array literals', () => { type $MyApp$ = MyApp; @@ -276,7 +371,7 @@ describe('compiler specification', () => { $r3$.ɵE(0, MyArrayComp); $r3$.ɵe(); } - $r3$.ɵp(0, 'names', $r3$.ɵf1($e0_ff$, ctx.customName)); + $r3$.ɵp(0, 'names', $r3$.ɵb($r3$.ɵf1($e0_ff$, ctx.customName))); MyArrayComp.ngComponentDef.h(1, 0); $r3$.ɵr(1, 0); } @@ -346,8 +441,9 @@ describe('compiler specification', () => { } // NORMATIVE - const $e0_ff$ = (v: any[]) => - ['start-', v[0], v[1], v[2], v[3], v[4], '-middle-', v[5], v[6], v[7], v[8], '-end']; + const $e0_ff$ = + (v0: any, v1: any, v2: any, v3: any, v4: any, v5: any, v6: any, v7: any, + v8: any) => ['start-', v0, v1, v2, v3, v4, '-middle-', v5, v6, v7, v8, '-end']; // /NORMATIVE @Component({ @@ -380,7 +476,8 @@ describe('compiler specification', () => { } $r3$.ɵp( 0, 'names', - $r3$.ɵfV($e0_ff$, [c.n0, c.n1, c.n2, c.n3, c.n4, c.n5, c.n6, c.n7, c.n8])); + $r3$.ɵb( + $r3$.ɵfV($e0_ff$, [c.n0, c.n1, c.n2, c.n3, c.n4, c.n5, c.n6, c.n7, c.n8]))); MyComp.ngComponentDef.h(1, 0); $r3$.ɵr(1, 0); } @@ -448,7 +545,7 @@ describe('compiler specification', () => { $r3$.ɵE(0, ObjectComp); $r3$.ɵe(); } - $r3$.ɵp(0, 'config', $r3$.ɵf1($e0_ff$, ctx.name)); + $r3$.ɵp(0, 'config', $r3$.ɵb($r3$.ɵf1($e0_ff$, ctx.name))); ObjectComp.ngComponentDef.h(1, 0); $r3$.ɵr(1, 0); } @@ -527,9 +624,9 @@ describe('compiler specification', () => { $r3$.ɵe(); } $r3$.ɵp( - 0, 'config', - $r3$.ɵf2( - $e0_ff_2$, ctx.name, $r3$.ɵf1($e0_ff_1$, $r3$.ɵf1($e0_ff$, ctx.duration)))); + 0, 'config', $r3$.ɵf2( + $e0_ff_2$, ctx.name, + $r3$.ɵb($r3$.ɵf1($e0_ff_1$, $r3$.ɵf1($e0_ff$, ctx.duration))))); NestedComp.ngComponentDef.h(1, 0); $r3$.ɵr(1, 0); } diff --git a/packages/core/test/render3/integration_spec.ts b/packages/core/test/render3/integration_spec.ts index 21370a69c2..449c55f9a1 100644 --- a/packages/core/test/render3/integration_spec.ts +++ b/packages/core/test/render3/integration_spec.ts @@ -72,7 +72,7 @@ describe('render3 integration test', () => { if (cm) { text(0); } - textBinding(0, bind0(value)); + textBinding(0, cm ? value : NO_CHANGE); } expect(renderToHtml(Template, 'once')).toEqual('once'); expect(renderToHtml(Template, 'twice')).toEqual('once'); diff --git a/packages/core/test/render3/pure_function_spec.ts b/packages/core/test/render3/pure_function_spec.ts index 6d0618c425..c2c3a8c7fb 100644 --- a/packages/core/test/render3/pure_function_spec.ts +++ b/packages/core/test/render3/pure_function_spec.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ import {defineComponent} from '../../src/render3/index'; -import {componentRefresh, container, containerRefreshEnd, containerRefreshStart, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, memory} from '../../src/render3/instructions'; +import {bind, componentRefresh, container, containerRefreshEnd, containerRefreshStart, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, memory} from '../../src/render3/instructions'; import {pureFunction1, pureFunction2, pureFunction3, pureFunction4, pureFunction5, pureFunction6, pureFunction7, pureFunction8, pureFunctionV} from '../../src/render3/pure_function'; import {renderToHtml} from '../../test/render3/render_util'; @@ -26,19 +26,19 @@ describe('array literals', () => { } it('should support an array literal with a binding', () => { + const e0_ff = (v: any) => ['Nancy', v, 'Bess']; + /** */ function Template(ctx: any, cm: boolean) { if (cm) { elementStart(0, MyComp); elementEnd(); } - elementProperty(0, 'names', pureFunction1(e0_ff, ctx.customName)); + elementProperty(0, 'names', bind(pureFunction1(e0_ff, ctx.customName))); MyComp.ngComponentDef.h(1, 0); componentRefresh(1, 0); } - const e0_ff = (v: any) => ['Nancy', v, 'Bess']; - renderToHtml(Template, {customName: 'Carson'}); const firstArray = myComp !.names; expect(firstArray).toEqual(['Nancy', 'Carson', 'Bess']); @@ -52,6 +52,12 @@ describe('array literals', () => { // Identity must change if binding changes expect(firstArray).not.toBe(myComp !.names); + + // The property should not be set if the exp value is the same, so artificially + // setting the property to ensure it's not overwritten. + myComp !.names = ['should not be overwritten']; + renderToHtml(Template, {customName: 'Hannah'}); + expect(myComp !.names).toEqual(['should not be overwritten']); }); it('should support multiple array literals passed through to one node', () => { @@ -70,6 +76,9 @@ describe('array literals', () => { }); } + const e0_ff = (v: any) => ['Nancy', v]; + const e0_ff_1 = (v: any) => [v]; + /** * * @@ -79,15 +88,12 @@ describe('array literals', () => { elementStart(0, ManyPropComp); elementEnd(); } - elementProperty(0, 'names1', pureFunction1(e0_ff, ctx.customName)); - elementProperty(0, 'names2', pureFunction1(e0_ff_1, ctx.customName2)); + elementProperty(0, 'names1', bind(pureFunction1(e0_ff, ctx.customName))); + elementProperty(0, 'names2', bind(pureFunction1(e0_ff_1, ctx.customName2))); ManyPropComp.ngComponentDef.h(1, 0); componentRefresh(1, 0); } - const e0_ff = (v: any) => ['Nancy', v]; - const e0_ff_1 = (v: any) => [v]; - renderToHtml(Template, {customName: 'Carson', customName2: 'George'}); expect(manyPropComp !.names1).toEqual(['Nancy', 'Carson']); expect(manyPropComp !.names2).toEqual(['George']); @@ -97,21 +103,78 @@ describe('array literals', () => { expect(manyPropComp !.names2).toEqual(['Carson']); }); + it('should support an array literals inside fn calls', () => { + let myComps: MyComp[] = []; + + const e0_ff = (v: any) => ['Nancy', v]; + + /** */ + class ParentComp { + customName = 'Bess'; + + someFn(arr: string[]): string[] { + arr[0] = arr[0].toUpperCase(); + return arr; + } + + static ngComponentDef = defineComponent({ + type: ParentComp, + tag: 'parent-comp', + factory: () => new ParentComp(), + template: function(ctx: any, cm: boolean) { + if (cm) { + elementStart(0, MyComp); + myComps.push(memory(1)); + elementEnd(); + } + elementProperty(0, 'names', bind(ctx.someFn(pureFunction1(e0_ff, ctx.customName)))); + MyComp.ngComponentDef.h(1, 0); + componentRefresh(1, 0); + } + }); + } + + function Template(ctx: any, cm: boolean) { + if (cm) { + elementStart(0, ParentComp); + elementEnd(); + elementStart(2, ParentComp); + elementEnd(); + } + ParentComp.ngComponentDef.h(1, 0); + ParentComp.ngComponentDef.h(3, 2); + componentRefresh(1, 0); + componentRefresh(3, 2); + } + + renderToHtml(Template, {}); + const firstArray = myComps[0].names; + const secondArray = myComps[1].names; + expect(firstArray).toEqual(['NANCY', 'Bess']); + expect(secondArray).toEqual(['NANCY', 'Bess']); + expect(firstArray).not.toBe(secondArray); + + renderToHtml(Template, {}); + expect(firstArray).toEqual(['NANCY', 'Bess']); + expect(secondArray).toEqual(['NANCY', 'Bess']); + expect(firstArray).toBe(myComps[0].names); + expect(secondArray).toBe(myComps[1].names); + }); it('should support an array literal with more than 1 binding', () => { + const e0_ff = (v1: any, v2: any) => ['Nancy', v1, 'Bess', v2]; + /** */ function Template(ctx: any, cm: boolean) { if (cm) { elementStart(0, MyComp); elementEnd(); } - elementProperty(0, 'names', pureFunction2(e0_ff, ctx.customName, ctx.customName2)); + elementProperty(0, 'names', bind(pureFunction2(e0_ff, ctx.customName, ctx.customName2))); MyComp.ngComponentDef.h(1, 0); componentRefresh(1, 0); } - const e0_ff = (v1: any, v2: any) => ['Nancy', v1, 'Bess', v2]; - renderToHtml(Template, {customName: 'Carson', customName2: 'Hannah'}); const firstArray = myComp !.names; expect(firstArray).toEqual(['Nancy', 'Carson', 'Bess', 'Hannah']); @@ -126,6 +189,12 @@ describe('array literals', () => { renderToHtml(Template, {customName: 'Frank', customName2: 'Ned'}); expect(myComp !.names).toEqual(['Nancy', 'Frank', 'Bess', 'Ned']); + + // The property should not be set if the exp value is the same, so artificially + // setting the property to ensure it's not overwritten. + myComp !.names = ['should not be overwritten']; + renderToHtml(Template, {customName: 'Frank', customName2: 'Ned'}); + expect(myComp !.names).toEqual(['should not be overwritten']); }); it('should work up to 8 bindings', () => { @@ -136,6 +205,21 @@ describe('array literals', () => { let f7Comp: MyComp; let f8Comp: MyComp; + + const e0_ff = (v1: any, v2: any, v3: any) => ['a', 'b', 'c', 'd', 'e', v1, v2, v3]; + const e2_ff = (v1: any, v2: any, v3: any, v4: any) => ['a', 'b', 'c', 'd', v1, v2, v3, v4]; + const e4_ff = + (v1: any, v2: any, v3: any, v4: any, v5: any) => ['a', 'b', 'c', v1, v2, v3, v4, v5]; + const e6_ff = + (v1: any, v2: any, v3: any, v4: any, v5: any, + v6: any) => ['a', 'b', v1, v2, v3, v4, v5, v6]; + const e8_ff = + (v1: any, v2: any, v3: any, v4: any, v5: any, v6: any, + v7: any) => ['a', v1, v2, v3, v4, v5, v6, v7]; + const e10_ff = + (v1: any, v2: any, v3: any, v4: any, v5: any, v6: any, v7: any, + v8: any) => [v1, v2, v3, v4, v5, v6, v7, v8]; + function Template(c: any, cm: boolean) { if (cm) { elementStart(0, MyComp); @@ -157,13 +241,14 @@ describe('array literals', () => { f8Comp = memory(11); elementEnd(); } - elementProperty(0, 'names', pureFunction3(e0_ff, c[5], c[6], c[7])); - elementProperty(2, 'names', pureFunction4(e2_ff, c[4], c[5], c[6], c[7])); - elementProperty(4, 'names', pureFunction5(e4_ff, c[3], c[4], c[5], c[6], c[7])); - elementProperty(6, 'names', pureFunction6(e6_ff, c[2], c[3], c[4], c[5], c[6], c[7])); - elementProperty(8, 'names', pureFunction7(e8_ff, c[1], c[2], c[3], c[4], c[5], c[6], c[7])); + elementProperty(0, 'names', bind(pureFunction3(e0_ff, c[5], c[6], c[7]))); + elementProperty(2, 'names', bind(pureFunction4(e2_ff, c[4], c[5], c[6], c[7]))); + elementProperty(4, 'names', bind(pureFunction5(e4_ff, c[3], c[4], c[5], c[6], c[7]))); + elementProperty(6, 'names', bind(pureFunction6(e6_ff, c[2], c[3], c[4], c[5], c[6], c[7]))); elementProperty( - 10, 'names', pureFunction8(e10_ff, c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7])); + 8, 'names', bind(pureFunction7(e8_ff, c[1], c[2], c[3], c[4], c[5], c[6], c[7]))); + elementProperty( + 10, 'names', bind(pureFunction8(e10_ff, c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7]))); MyComp.ngComponentDef.h(1, 0); MyComp.ngComponentDef.h(3, 2); MyComp.ngComponentDef.h(5, 4); @@ -178,20 +263,6 @@ describe('array literals', () => { componentRefresh(11, 10); } - const e0_ff = (v1: any, v2: any, v3: any) => ['a', 'b', 'c', 'd', 'e', v1, v2, v3]; - const e2_ff = (v1: any, v2: any, v3: any, v4: any) => ['a', 'b', 'c', 'd', v1, v2, v3, v4]; - const e4_ff = - (v1: any, v2: any, v3: any, v4: any, v5: any) => ['a', 'b', 'c', v1, v2, v3, v4, v5]; - const e6_ff = - (v1: any, v2: any, v3: any, v4: any, v5: any, - v6: any) => ['a', 'b', v1, v2, v3, v4, v5, v6]; - const e8_ff = - (v1: any, v2: any, v3: any, v4: any, v5: any, v6: any, - v7: any) => ['a', v1, v2, v3, v4, v5, v6, v7]; - const e10_ff = - (v1: any, v2: any, v3: any, v4: any, v5: any, v6: any, v7: any, - v8: any) => [v1, v2, v3, v4, v5, v6, v7, v8]; - renderToHtml(Template, ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']); expect(f3Comp !.names).toEqual(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']); expect(f4Comp !.names).toEqual(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']); @@ -210,6 +281,12 @@ describe('array literals', () => { }); it('should work with pureFunctionV for 9+ bindings', () => { + const e0_ff = + (v0: any, v1: any, v2: any, v3: any, v4: any, v5: any, v6: any, v7: any, + v8: any) => ['start', v0, v1, v2, v3, v4, v5, v6, v7, v8, 'end']; + const e0_ff_1 = (v: any) => { return {name: v}; }; + + renderToHtml(Template, ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']); /** * * @@ -219,18 +296,13 @@ describe('array literals', () => { elementStart(0, MyComp); elementEnd(); } - elementProperty(0, 'names', pureFunctionV(e0_ff, [ + elementProperty(0, 'names', bind(pureFunctionV(e0_ff, [ c[0], c[1], c[2], c[3], pureFunction1(e0_ff_1, c[4]), c[5], c[6], c[7], c[8] - ])); + ]))); MyComp.ngComponentDef.h(1, 0); componentRefresh(1, 0); } - const e0_ff = - (v: any[]) => ['start', v[0], v[1], v[2], v[3], v[4], v[5], v[6], v[7], v[8], 'end']; - const e0_ff_1 = (v: any) => { return {name: v}; }; - - renderToHtml(Template, ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']); expect(myComp !.names).toEqual([ 'start', 'a', 'b', 'c', 'd', {name: 'e'}, 'f', 'g', 'h', 'i', 'end' ]); @@ -263,6 +335,7 @@ describe('object literals', () => { } it('should support an object literal', () => { + const e0_ff = (v: any) => { return {duration: 500, animation: v}; }; /** */ function Template(ctx: any, cm: boolean) { @@ -270,13 +343,11 @@ describe('object literals', () => { elementStart(0, ObjectComp); elementEnd(); } - elementProperty(0, 'config', pureFunction1(e0_ff, ctx.name)); + elementProperty(0, 'config', bind(pureFunction1(e0_ff, ctx.name))); ObjectComp.ngComponentDef.h(1, 0); componentRefresh(1, 0); } - const e0_ff = (v: any) => { return {duration: 500, animation: v}; }; - renderToHtml(Template, {name: 'slide'}); const firstObj = objectComp !.config; expect(objectComp !.config).toEqual({duration: 500, animation: 'slide'}); @@ -293,6 +364,10 @@ describe('object literals', () => { }); it('should support expressions nested deeply in object/array literals', () => { + const e0_ff = (v1: any, v2: any) => { return {animation: v1, actions: v2}; }; + const e0_ff_1 = (v: any) => [{opacity: 0, duration: 0}, v]; + const e0_ff_2 = (v: any) => { return {opacity: 1, duration: v}; }; + /** * @@ -305,16 +380,12 @@ describe('object literals', () => { } elementProperty( 0, 'config', - pureFunction2( - e0_ff, ctx.name, pureFunction1(e0_ff_1, pureFunction1(e0_ff_2, ctx.duration)))); + bind(pureFunction2( + e0_ff, ctx.name, pureFunction1(e0_ff_1, pureFunction1(e0_ff_2, ctx.duration))))); ObjectComp.ngComponentDef.h(1, 0); componentRefresh(1, 0); } - const e0_ff = (v1: any, v2: any) => { return {animation: v1, actions: v2}; }; - const e0_ff_1 = (v: any) => [{opacity: 0, duration: 0}, v]; - const e0_ff_2 = (v: any) => { return {opacity: 1, duration: v}; }; - renderToHtml(Template, {name: 'slide', duration: 100}); expect(objectComp !.config).toEqual({ animation: 'slide', @@ -347,6 +418,12 @@ describe('object literals', () => { animation: 'drag', actions: [{opacity: 0, duration: 0}, {opacity: 1, duration: 500}] }); + + // The property should not be set if the exp value is the same, so artificially + // setting the property to ensure it's not overwritten. + objectComp !.config = ['should not be overwritten']; + renderToHtml(Template, {name: 'drag', duration: 500}); + expect(objectComp !.config).toEqual(['should not be overwritten']); }); it('should support multiple view instances with multiple bindings', () => { @@ -371,7 +448,8 @@ describe('object literals', () => { elementEnd(); } elementProperty( - 0, 'config', pureFunction2(e0_ff, ctx.configs[i].opacity, ctx.configs[i].duration)); + 0, 'config', + bind(pureFunction2(e0_ff, ctx.configs[i].opacity, ctx.configs[i].duration))); ObjectComp.ngComponentDef.h(1, 0); componentRefresh(1, 0); embeddedViewEnd();