feat(ivy): add pureFunction0 instruction (#22214)

PR Close #22214
This commit is contained in:
Kara Erickson 2018-02-14 13:37:54 -08:00 committed by Victor Berchet
parent a73d5308e0
commit f693be3996
7 changed files with 353 additions and 241 deletions

View File

@ -44,6 +44,7 @@ export {
pb3 as ɵpb3, pb3 as ɵpb3,
pb4 as ɵpb4, pb4 as ɵpb4,
pbV as ɵpbV, pbV as ɵpbV,
f0 as ɵf0,
f1 as ɵf1, f1 as ɵf1,
f2 as ɵf2, f2 as ɵf2,
f3 as ɵf3, f3 as ɵf3,

View File

@ -79,6 +79,7 @@ export {
queryRefresh as qR, queryRefresh as qR,
} from './query'; } from './query';
export { export {
pureFunction0 as f0,
pureFunction1 as f1, pureFunction1 as f1,
pureFunction2 as f2, pureFunction2 as f2,
pureFunction3 as f3, pureFunction3 as f3,

View File

@ -1749,16 +1749,54 @@ function valueInData<T>(data: any[], index: number, value?: T): T {
return value !; 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 { export function getCurrentQueries(QueryType: {new (): LQueries}): LQueries {
return currentQueries || (currentQueries = new QueryType()); 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 { export function getPreviousOrParentNode(): LNode {
return previousOrParentNode; return previousOrParentNode;
} }

View File

@ -6,105 +6,85 @@
* found in the LICENSE file at https://angular.io/license * 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 * If the value hasn't been saved, calls the pure function to store and return the
* return an updated value. Or if the value has not changed, returns NO_CHANGE. * value. If it has been saved, returns the saved value.
* *
* @param pureFn Function that returns an updated value * @param pureFn Function that returns a value
* @param exp Updated expression value * @returns value
* @returns Updated value or NO_CHANGE
*/ */
export function pureFunction1(pureFn: (v: any) => any, exp: any): any { export function pureFunction0<T>(pureFn: () => T): T {
let different = false; return getCreationMode() ? checkAndUpdateBinding(pureFn()) : consumeBinding();
const latestValue = exp === NO_CHANGE ? peekBinding() : exp;
if (bind(exp) !== NO_CHANGE) different = true;
return different ? pureFn(latestValue) : NO_CHANGE;
} }
/** /**
* If the value of any provided exp has changed, calls the pure function to * If the value of the provided exp has changed, calls the pure function to return
* return an updated value. Or if no values have changed, returns NO_CHANGE. * 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 pureFn
* @param exp1 * @param exp1
* @param exp2 * @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 { export function pureFunction2(pureFn: (v1: any, v2: any) => any, exp1: any, exp2: any): any {
let different = false; return bindingUpdated2(exp1, exp2) ? checkAndUpdateBinding(pureFn(exp1, exp2)) : consumeBinding();
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;
} }
/** /**
* If the value of any provided exp has changed, calls the pure function to * If the value of any provided exp has changed, calls the pure function to return
* return an updated value. Or if no values have changed, returns NO_CHANGE. * an updated value. Or if no values have changed, returns cached value.
* *
* @param pureFn * @param pureFn
* @param exp1 * @param exp1
* @param exp2 * @param exp2
* @param exp3 * @param exp3
* @returns Updated value or NO_CHANGE * @returns Updated value
*/ */
export function pureFunction3( export function pureFunction3(
pureFn: (v1: any, v2: any, v3: any) => any, exp1: any, exp2: any, exp3: any): any { pureFn: (v1: any, v2: any, v3: any) => any, exp1: any, exp2: any, exp3: any): any {
let different = false; const different = bindingUpdated2(exp1, exp2);
return bindingUpdated(exp3) || different ? checkAndUpdateBinding(pureFn(exp1, exp2, exp3)) :
const latestVal1 = exp1 === NO_CHANGE ? peekBinding() : exp1; consumeBinding();
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;
} }
/** /**
* If the value of any provided exp has changed, calls the pure function to * If the value of any provided exp has changed, calls the pure function to return
* return an updated value. Or if no values have changed, returns NO_CHANGE. * an updated value. Or if no values have changed, returns cached value.
* *
* @param pureFn * @param pureFn
* @param exp1 * @param exp1
* @param exp2 * @param exp2
* @param exp3 * @param exp3
* @param exp4 * @param exp4
* @returns Updated value or NO_CHANGE * @returns Updated value
*/ */
export function pureFunction4( export function pureFunction4(
pureFn: (v1: any, v2: any, v3: any, v4: any) => any, exp1: any, exp2: any, exp3: any, pureFn: (v1: any, v2: any, v3: any, v4: any) => any, exp1: any, exp2: any, exp3: any,
exp4: any): any { exp4: any): any {
let different = false; return bindingUpdated4(exp1, exp2, exp3, exp4) ?
checkAndUpdateBinding(pureFn(exp1, exp2, exp3, exp4)) :
const latestVal1 = exp1 === NO_CHANGE ? peekBinding() : exp1; consumeBinding();
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;
} }
/** /**
* If the value of any provided exp has changed, calls the pure function to * If the value of any provided exp has changed, calls the pure function to return
* return an updated value. Or if no values have changed, returns NO_CHANGE. * an updated value. Or if no values have changed, returns cached value.
* *
* @param pureFn * @param pureFn
* @param exp1 * @param exp1
@ -112,34 +92,20 @@ export function pureFunction4(
* @param exp3 * @param exp3
* @param exp4 * @param exp4
* @param exp5 * @param exp5
* @returns Updated value or NO_CHANGE * @returns Updated value
*/ */
export function pureFunction5( export function pureFunction5(
pureFn: (v1: any, v2: any, v3: any, v4: any, v5: any) => any, exp1: any, exp2: any, exp3: any, pureFn: (v1: any, v2: any, v3: any, v4: any, v5: any) => any, exp1: any, exp2: any, exp3: any,
exp4: any, exp5: any): any { exp4: any, exp5: any): any {
let different = false; const different = bindingUpdated4(exp1, exp2, exp3, exp4);
return bindingUpdated(exp5) || different ?
const latestVal1 = exp1 === NO_CHANGE ? peekBinding() : exp1; checkAndUpdateBinding(pureFn(exp1, exp2, exp3, exp4, exp5)) :
if (bind(exp1) !== NO_CHANGE) different = true; consumeBinding();
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;
} }
/** /**
* If the value of any provided exp has changed, calls the pure function to * If the value of any provided exp has changed, calls the pure function to return
* return an updated value. Or if no values have changed, returns NO_CHANGE. * an updated value. Or if no values have changed, returns cached value.
* *
* @param pureFn * @param pureFn
* @param exp1 * @param exp1
@ -148,39 +114,20 @@ export function pureFunction5(
* @param exp4 * @param exp4
* @param exp5 * @param exp5
* @param exp6 * @param exp6
* @returns Updated value or NO_CHANGE * @returns Updated value
*/ */
export function pureFunction6( export function pureFunction6(
pureFn: (v1: any, v2: any, v3: any, v4: any, v5: any, v6: any) => any, exp1: any, exp2: any, 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 { exp3: any, exp4: any, exp5: any, exp6: any): any {
let different = false; const different = bindingUpdated4(exp1, exp2, exp3, exp4);
return bindingUpdated2(exp5, exp6) || different ?
const latestVal1 = exp1 === NO_CHANGE ? peekBinding() : exp1; checkAndUpdateBinding(pureFn(exp1, exp2, exp3, exp4, exp5, exp6)) :
if (bind(exp1) !== NO_CHANGE) different = true; consumeBinding();
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;
} }
/** /**
* If the value of any provided exp has changed, calls the pure function to * If the value of any provided exp has changed, calls the pure function to return
* return an updated value. Or if no values have changed, returns NO_CHANGE. * an updated value. Or if no values have changed, returns cached value.
* *
* @param pureFn * @param pureFn
* @param exp1 * @param exp1
@ -190,42 +137,21 @@ export function pureFunction6(
* @param exp5 * @param exp5
* @param exp6 * @param exp6
* @param exp7 * @param exp7
* @returns Updated value or NO_CHANGE * @returns Updated value
*/ */
export function pureFunction7( export function pureFunction7(
pureFn: (v1: any, v2: any, v3: any, v4: any, v5: any, v6: any, v7: any) => any, exp1: any, 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 { exp2: any, exp3: any, exp4: any, exp5: any, exp6: any, exp7: any): any {
let different = false; let different = bindingUpdated4(exp1, exp2, exp3, exp4);
different = bindingUpdated2(exp5, exp6) || different;
const latestVal1 = exp1 === NO_CHANGE ? peekBinding() : exp1; return bindingUpdated(exp7) || different ?
if (bind(exp1) !== NO_CHANGE) different = true; checkAndUpdateBinding(pureFn(exp1, exp2, exp3, exp4, exp5, exp6, exp7)) :
consumeBinding();
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;
} }
/** /**
* If the value of any provided exp has changed, calls the pure function to * If the value of any provided exp has changed, calls the pure function to return
* return an updated value. Or if no values have changed, returns NO_CHANGE. * an updated value. Or if no values have changed, returns cached value.
* *
* @param pureFn * @param pureFn
* @param exp1 * @param exp1
@ -236,62 +162,33 @@ export function pureFunction7(
* @param exp6 * @param exp6
* @param exp7 * @param exp7
* @param exp8 * @param exp8
* @returns Updated value or NO_CHANGE * @returns Updated value
*/ */
export function pureFunction8( export function pureFunction8(
pureFn: (v1: any, v2: any, v3: any, v4: any, v5: any, v6: any, v7: any, v8: any) => any, 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 { exp1: any, exp2: any, exp3: any, exp4: any, exp5: any, exp6: any, exp7: any, exp8: any): any {
let different = false; const different = bindingUpdated4(exp1, exp2, exp3, exp4);
return bindingUpdated4(exp1, exp2, exp3, exp4) || different ?
const latestVal1 = exp1 === NO_CHANGE ? peekBinding() : exp1; checkAndUpdateBinding(pureFn(exp1, exp2, exp3, exp4, exp5, exp6, exp7, exp8)) :
if (bind(exp1) !== NO_CHANGE) different = true; consumeBinding();
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;
} }
/** /**
* pureFunction instruction that can support any number of bindings. * pureFunction instruction that can support any number of bindings.
* *
* If the value of any provided exp has changed, calls the pure function to * If the value of any provided exp has changed, calls the pure function to return
* return an updated value. Or if no values have changed, returns NO_CHANGE. * 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 * @param pureFn A pure function that takes binding values and builds an object or array
* containing those values. * containing those values.
* @param exp An array of binding 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; let different = false;
for (let i = 0; i < exps.length; i++) { for (let i = 0; i < exps.length; i++) {
const exp = exps[i]; bindingUpdated(exps[i]) && (different = true);
if (exp === NO_CHANGE) exps[i] = peekBinding();
if (bind(exp) !== NO_CHANGE) different = true;
} }
return different ? checkAndUpdateBinding(pureFn.apply(null, exps)) : consumeBinding();
return different ? pureFn(exps) : NO_CHANGE;
} }

View File

@ -209,7 +209,7 @@ describe('compiler specification', () => {
if (cm) { if (cm) {
$r3$.ɵT(0); $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'} inputs: {names: 'names'}
}); });
@ -239,7 +239,7 @@ describe('compiler specification', () => {
$r3$.ɵE(0, MyArrayComp); $r3$.ɵE(0, MyArrayComp);
$r3$.ɵe(); $r3$.ɵe();
} }
$r3$.ɵp(0, 'names', $r3$.ɵb0($e0_arr$)); $r3$.ɵp(0, 'names', cm ? $e0_arr$ : $r3$.ɵNC);
MyArrayComp.ngComponentDef.h(1, 0); MyArrayComp.ngComponentDef.h(1, 0);
$r3$.ɵr(1, 0); $r3$.ɵr(1, 0);
} }
@ -250,6 +250,101 @@ describe('compiler specification', () => {
expect(renderComp(MyApp)).toEqual(`<my-array-comp>Nancy Bess</my-array-comp>`); expect(renderComp(MyApp)).toEqual(`<my-array-comp>Nancy Bess</my-array-comp>`);
}); });
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: `
<my-array-comp [names]="someFn(['Nancy', 'Bess'])"></my-array-comp>
`
})
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(`<my-array-comp>NANCY Bess</my-array-comp>`);
});
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: `
<my-comp [num]="['Nancy', 'Bess'].length + 1"></my-comp>
`
})
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(`<my-comp>3</my-comp>`);
});
it('should support array literals', () => { it('should support array literals', () => {
type $MyApp$ = MyApp; type $MyApp$ = MyApp;
@ -276,7 +371,7 @@ describe('compiler specification', () => {
$r3$.ɵE(0, MyArrayComp); $r3$.ɵE(0, MyArrayComp);
$r3$.ɵe(); $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); MyArrayComp.ngComponentDef.h(1, 0);
$r3$.ɵr(1, 0); $r3$.ɵr(1, 0);
} }
@ -346,8 +441,9 @@ describe('compiler specification', () => {
} }
// NORMATIVE // NORMATIVE
const $e0_ff$ = (v: any[]) => const $e0_ff$ =
['start-', v[0], v[1], v[2], v[3], v[4], '-middle-', v[5], v[6], v[7], v[8], '-end']; (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 // /NORMATIVE
@Component({ @Component({
@ -380,7 +476,8 @@ describe('compiler specification', () => {
} }
$r3$.ɵp( $r3$.ɵp(
0, 'names', 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); MyComp.ngComponentDef.h(1, 0);
$r3$.ɵr(1, 0); $r3$.ɵr(1, 0);
} }
@ -448,7 +545,7 @@ describe('compiler specification', () => {
$r3$.ɵE(0, ObjectComp); $r3$.ɵE(0, ObjectComp);
$r3$.ɵe(); $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); ObjectComp.ngComponentDef.h(1, 0);
$r3$.ɵr(1, 0); $r3$.ɵr(1, 0);
} }
@ -527,9 +624,9 @@ describe('compiler specification', () => {
$r3$.ɵe(); $r3$.ɵe();
} }
$r3$.ɵp( $r3$.ɵp(
0, 'config', 0, 'config', $r3$.ɵf2(
$r3$.ɵf2( $e0_ff_2$, ctx.name,
$e0_ff_2$, ctx.name, $r3$.ɵf1($e0_ff_1$, $r3$.ɵf1($e0_ff$, ctx.duration)))); $r3$.ɵb($r3$.ɵf1($e0_ff_1$, $r3$.ɵf1($e0_ff$, ctx.duration)))));
NestedComp.ngComponentDef.h(1, 0); NestedComp.ngComponentDef.h(1, 0);
$r3$.ɵr(1, 0); $r3$.ɵr(1, 0);
} }

View File

@ -72,7 +72,7 @@ describe('render3 integration test', () => {
if (cm) { if (cm) {
text(0); text(0);
} }
textBinding(0, bind0(value)); textBinding(0, cm ? value : NO_CHANGE);
} }
expect(renderToHtml(Template, 'once')).toEqual('once'); expect(renderToHtml(Template, 'once')).toEqual('once');
expect(renderToHtml(Template, 'twice')).toEqual('once'); expect(renderToHtml(Template, 'twice')).toEqual('once');

View File

@ -6,7 +6,7 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {defineComponent} from '../../src/render3/index'; 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 {pureFunction1, pureFunction2, pureFunction3, pureFunction4, pureFunction5, pureFunction6, pureFunction7, pureFunction8, pureFunctionV} from '../../src/render3/pure_function';
import {renderToHtml} from '../../test/render3/render_util'; import {renderToHtml} from '../../test/render3/render_util';
@ -26,19 +26,19 @@ describe('array literals', () => {
} }
it('should support an array literal with a binding', () => { it('should support an array literal with a binding', () => {
const e0_ff = (v: any) => ['Nancy', v, 'Bess'];
/** <my-comp [names]="['Nancy', customName, 'Bess']"></my-comp> */ /** <my-comp [names]="['Nancy', customName, 'Bess']"></my-comp> */
function Template(ctx: any, cm: boolean) { function Template(ctx: any, cm: boolean) {
if (cm) { if (cm) {
elementStart(0, MyComp); elementStart(0, MyComp);
elementEnd(); elementEnd();
} }
elementProperty(0, 'names', pureFunction1(e0_ff, ctx.customName)); elementProperty(0, 'names', bind(pureFunction1(e0_ff, ctx.customName)));
MyComp.ngComponentDef.h(1, 0); MyComp.ngComponentDef.h(1, 0);
componentRefresh(1, 0); componentRefresh(1, 0);
} }
const e0_ff = (v: any) => ['Nancy', v, 'Bess'];
renderToHtml(Template, {customName: 'Carson'}); renderToHtml(Template, {customName: 'Carson'});
const firstArray = myComp !.names; const firstArray = myComp !.names;
expect(firstArray).toEqual(['Nancy', 'Carson', 'Bess']); expect(firstArray).toEqual(['Nancy', 'Carson', 'Bess']);
@ -52,6 +52,12 @@ describe('array literals', () => {
// Identity must change if binding changes // Identity must change if binding changes
expect(firstArray).not.toBe(myComp !.names); 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', () => { 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];
/** /**
* <many-prop-comp [names1]="['Nancy', customName]" [names2]="[customName2]"> * <many-prop-comp [names1]="['Nancy', customName]" [names2]="[customName2]">
* </many-prop-comp> * </many-prop-comp>
@ -79,15 +88,12 @@ describe('array literals', () => {
elementStart(0, ManyPropComp); elementStart(0, ManyPropComp);
elementEnd(); elementEnd();
} }
elementProperty(0, 'names1', pureFunction1(e0_ff, ctx.customName)); elementProperty(0, 'names1', bind(pureFunction1(e0_ff, ctx.customName)));
elementProperty(0, 'names2', pureFunction1(e0_ff_1, ctx.customName2)); elementProperty(0, 'names2', bind(pureFunction1(e0_ff_1, ctx.customName2)));
ManyPropComp.ngComponentDef.h(1, 0); ManyPropComp.ngComponentDef.h(1, 0);
componentRefresh(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'}); renderToHtml(Template, {customName: 'Carson', customName2: 'George'});
expect(manyPropComp !.names1).toEqual(['Nancy', 'Carson']); expect(manyPropComp !.names1).toEqual(['Nancy', 'Carson']);
expect(manyPropComp !.names2).toEqual(['George']); expect(manyPropComp !.names2).toEqual(['George']);
@ -97,21 +103,78 @@ describe('array literals', () => {
expect(manyPropComp !.names2).toEqual(['Carson']); expect(manyPropComp !.names2).toEqual(['Carson']);
}); });
it('should support an array literals inside fn calls', () => {
let myComps: MyComp[] = [];
const e0_ff = (v: any) => ['Nancy', v];
/** <my-comp [names]="someFn(['Nancy', customName])"></my-comp> */
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', () => { it('should support an array literal with more than 1 binding', () => {
const e0_ff = (v1: any, v2: any) => ['Nancy', v1, 'Bess', v2];
/** <my-comp [names]="['Nancy', customName, 'Bess', customName2]"></my-comp> */ /** <my-comp [names]="['Nancy', customName, 'Bess', customName2]"></my-comp> */
function Template(ctx: any, cm: boolean) { function Template(ctx: any, cm: boolean) {
if (cm) { if (cm) {
elementStart(0, MyComp); elementStart(0, MyComp);
elementEnd(); 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); MyComp.ngComponentDef.h(1, 0);
componentRefresh(1, 0); componentRefresh(1, 0);
} }
const e0_ff = (v1: any, v2: any) => ['Nancy', v1, 'Bess', v2];
renderToHtml(Template, {customName: 'Carson', customName2: 'Hannah'}); renderToHtml(Template, {customName: 'Carson', customName2: 'Hannah'});
const firstArray = myComp !.names; const firstArray = myComp !.names;
expect(firstArray).toEqual(['Nancy', 'Carson', 'Bess', 'Hannah']); expect(firstArray).toEqual(['Nancy', 'Carson', 'Bess', 'Hannah']);
@ -126,6 +189,12 @@ describe('array literals', () => {
renderToHtml(Template, {customName: 'Frank', customName2: 'Ned'}); renderToHtml(Template, {customName: 'Frank', customName2: 'Ned'});
expect(myComp !.names).toEqual(['Nancy', 'Frank', 'Bess', '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', () => { it('should work up to 8 bindings', () => {
@ -136,6 +205,21 @@ describe('array literals', () => {
let f7Comp: MyComp; let f7Comp: MyComp;
let f8Comp: 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) { function Template(c: any, cm: boolean) {
if (cm) { if (cm) {
elementStart(0, MyComp); elementStart(0, MyComp);
@ -157,13 +241,14 @@ describe('array literals', () => {
f8Comp = memory(11); f8Comp = memory(11);
elementEnd(); elementEnd();
} }
elementProperty(0, 'names', pureFunction3(e0_ff, c[5], c[6], c[7])); elementProperty(0, 'names', bind(pureFunction3(e0_ff, c[5], c[6], c[7])));
elementProperty(2, 'names', pureFunction4(e2_ff, c[4], c[5], c[6], c[7])); elementProperty(2, 'names', bind(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(4, 'names', bind(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(6, 'names', bind(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( 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(1, 0);
MyComp.ngComponentDef.h(3, 2); MyComp.ngComponentDef.h(3, 2);
MyComp.ngComponentDef.h(5, 4); MyComp.ngComponentDef.h(5, 4);
@ -178,20 +263,6 @@ describe('array literals', () => {
componentRefresh(11, 10); 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']); renderToHtml(Template, ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i']);
expect(f3Comp !.names).toEqual(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']); expect(f3Comp !.names).toEqual(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']);
expect(f4Comp !.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', () => { 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']);
/** /**
* <my-comp [names]="['start', v0, v1, v2, v3, {name: v4}, v5, v6, v7, v8, 'end']"> * <my-comp [names]="['start', v0, v1, v2, v3, {name: v4}, v5, v6, v7, v8, 'end']">
* </my-comp> * </my-comp>
@ -219,18 +296,13 @@ describe('array literals', () => {
elementStart(0, MyComp); elementStart(0, MyComp);
elementEnd(); 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] 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); MyComp.ngComponentDef.h(1, 0);
componentRefresh(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([ expect(myComp !.names).toEqual([
'start', 'a', 'b', 'c', 'd', {name: 'e'}, 'f', 'g', 'h', 'i', 'end' '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', () => { it('should support an object literal', () => {
const e0_ff = (v: any) => { return {duration: 500, animation: v}; };
/** <object-comp [config]="{duration: 500, animation: name}"></object-comp> */ /** <object-comp [config]="{duration: 500, animation: name}"></object-comp> */
function Template(ctx: any, cm: boolean) { function Template(ctx: any, cm: boolean) {
@ -270,13 +343,11 @@ describe('object literals', () => {
elementStart(0, ObjectComp); elementStart(0, ObjectComp);
elementEnd(); elementEnd();
} }
elementProperty(0, 'config', pureFunction1(e0_ff, ctx.name)); elementProperty(0, 'config', bind(pureFunction1(e0_ff, ctx.name)));
ObjectComp.ngComponentDef.h(1, 0); ObjectComp.ngComponentDef.h(1, 0);
componentRefresh(1, 0); componentRefresh(1, 0);
} }
const e0_ff = (v: any) => { return {duration: 500, animation: v}; };
renderToHtml(Template, {name: 'slide'}); renderToHtml(Template, {name: 'slide'});
const firstObj = objectComp !.config; const firstObj = objectComp !.config;
expect(objectComp !.config).toEqual({duration: 500, animation: 'slide'}); 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', () => { 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}; };
/** /**
* <object-comp [config]="{animation: name, actions: [{ opacity: 0, duration: 0}, {opacity: 1, * <object-comp [config]="{animation: name, actions: [{ opacity: 0, duration: 0}, {opacity: 1,
* duration: duration }]}"> * duration: duration }]}">
@ -305,16 +380,12 @@ describe('object literals', () => {
} }
elementProperty( elementProperty(
0, 'config', 0, 'config',
pureFunction2( bind(pureFunction2(
e0_ff, ctx.name, pureFunction1(e0_ff_1, pureFunction1(e0_ff_2, ctx.duration)))); e0_ff, ctx.name, pureFunction1(e0_ff_1, pureFunction1(e0_ff_2, ctx.duration)))));
ObjectComp.ngComponentDef.h(1, 0); ObjectComp.ngComponentDef.h(1, 0);
componentRefresh(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}); renderToHtml(Template, {name: 'slide', duration: 100});
expect(objectComp !.config).toEqual({ expect(objectComp !.config).toEqual({
animation: 'slide', animation: 'slide',
@ -347,6 +418,12 @@ describe('object literals', () => {
animation: 'drag', animation: 'drag',
actions: [{opacity: 0, duration: 0}, {opacity: 1, duration: 500}] 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', () => { it('should support multiple view instances with multiple bindings', () => {
@ -371,7 +448,8 @@ describe('object literals', () => {
elementEnd(); elementEnd();
} }
elementProperty( 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); ObjectComp.ngComponentDef.h(1, 0);
componentRefresh(1, 0); componentRefresh(1, 0);
embeddedViewEnd(); embeddedViewEnd();