feat(ivy): add support for short-circuiting (#24039)

Short-circuitable expressions (using ternary & binary operators) could not use
the regular binding mechanism as it relies on the bindings being checked every
single time - the index is incremented as part of checking the bindings.

Then for pure function kind of bindings we use a different mechanism with a
fixed index. As such short circuiting a binding check does not mess with the
expected binding index.

Note that all pure function bindings are handled the same wether or not they
actually are short-circuitable. This allows to keep the compiler and compiled
code simple - and there is no runtime perf cost anyway.

PR Close #24039
This commit is contained in:
Victor Berchet 2018-05-21 15:59:25 -07:00 committed by Matias Niemelä
parent 83bb5d1922
commit 4f36340de7
13 changed files with 362 additions and 116 deletions

View File

@ -121,4 +121,7 @@ export class Identifiers {
static NgOnChangesFeature: o.ExternalReference = {name: 'ɵNgOnChangesFeature', moduleName: CORE}; static NgOnChangesFeature: o.ExternalReference = {name: 'ɵNgOnChangesFeature', moduleName: CORE};
static listener: o.ExternalReference = {name: 'ɵL', moduleName: CORE}; static listener: o.ExternalReference = {name: 'ɵL', moduleName: CORE};
// Reserve slots for pure functions
static reserveSlots: o.ExternalReference = {name: 'ɵrS', moduleName: CORE};
} }

View File

@ -57,6 +57,9 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
// Maps of placeholder to node indexes for each of the i18n section // Maps of placeholder to node indexes for each of the i18n section
private _phToNodeIdxes: {[phName: string]: number[]}[] = [{}]; private _phToNodeIdxes: {[phName: string]: number[]}[] = [{}];
// Number of slots to reserve for pureFunctions
private _pureFunctionSlots = 0;
constructor( constructor(
private constantPool: ConstantPool, private contextParameter: string, private constantPool: ConstantPool, private contextParameter: string,
parentBindingScope: BindingScope, private level = 0, private contextName: string|null, parentBindingScope: BindingScope, private level = 0, private contextName: string|null,
@ -70,6 +73,7 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
}); });
this._valueConverter = new ValueConverter( this._valueConverter = new ValueConverter(
constantPool, () => this.allocateDataSlot(), constantPool, () => this.allocateDataSlot(),
(numSlots: number): number => this._pureFunctionSlots += numSlots,
(name, localName, slot, value: o.ReadVarExpr) => { (name, localName, slot, value: o.ReadVarExpr) => {
const pipeType = pipeTypeByName.get(name); const pipeType = pipeTypeByName.get(name);
if (pipeType) { if (pipeType) {
@ -139,6 +143,11 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
t.visitAll(this, nodes); t.visitAll(this, nodes);
if (this._pureFunctionSlots > 0) {
this.instruction(
this._creationCode, null, R3.reserveSlots, o.literal(this._pureFunctionSlots));
}
const creationCode = this._creationCode.length > 0 ? const creationCode = this._creationCode.length > 0 ?
[o.ifStmt( [o.ifStmt(
o.variable(RENDER_FLAGS).bitwiseAnd(o.literal(core.RenderFlags.Create), null, false), o.variable(RENDER_FLAGS).bitwiseAnd(o.literal(core.RenderFlags.Create), null, false),
@ -501,6 +510,7 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
class ValueConverter extends AstMemoryEfficientTransformer { class ValueConverter extends AstMemoryEfficientTransformer {
constructor( constructor(
private constantPool: ConstantPool, private allocateSlot: () => number, private constantPool: ConstantPool, private allocateSlot: () => number,
private allocatePureFunctionSlots: (numSlots: number) => number,
private definePipe: private definePipe:
(name: string, localName: string, slot: number, value: o.Expression) => void) { (name: string, localName: string, slot: number, value: o.Expression) => void) {
super(); super();
@ -511,14 +521,20 @@ class ValueConverter extends AstMemoryEfficientTransformer {
// Allocate a slot to create the pipe // Allocate a slot to create the pipe
const slot = this.allocateSlot(); const slot = this.allocateSlot();
const slotPseudoLocal = `PIPE:${slot}`; const slotPseudoLocal = `PIPE:${slot}`;
// Allocate one slot for the result plus one slot per pipe argument
const pureFunctionSlot = this.allocatePureFunctionSlots(2 + pipe.args.length);
const target = new PropertyRead(pipe.span, new ImplicitReceiver(pipe.span), slotPseudoLocal); const target = new PropertyRead(pipe.span, new ImplicitReceiver(pipe.span), slotPseudoLocal);
const bindingId = pipeBinding(pipe.args); const bindingId = pipeBinding(pipe.args);
this.definePipe(pipe.name, slotPseudoLocal, slot, o.importExpr(bindingId)); this.definePipe(pipe.name, slotPseudoLocal, slot, o.importExpr(bindingId));
const value = pipe.exp.visit(this); const value = pipe.exp.visit(this);
const args = this.visitAll(pipe.args); const args = this.visitAll(pipe.args);
return new FunctionCall( return new FunctionCall(pipe.span, target, [
pipe.span, target, [new LiteralPrimitive(pipe.span, slot), value, ...args]); new LiteralPrimitive(pipe.span, slot),
new LiteralPrimitive(pipe.span, pureFunctionSlot),
value,
...args,
]);
} }
visitLiteralArray(array: LiteralArray, context: any): AST { visitLiteralArray(array: LiteralArray, context: any): AST {
@ -527,8 +543,9 @@ class ValueConverter extends AstMemoryEfficientTransformer {
// calls to literal factories that compose the literal and will cache intermediate // calls to literal factories that compose the literal and will cache intermediate
// values. Otherwise, just return an literal array that contains the values. // values. Otherwise, just return an literal array that contains the values.
const literal = o.literalArr(values); const literal = o.literalArr(values);
return values.every(a => a.isConstant()) ? this.constantPool.getConstLiteral(literal, true) : return values.every(a => a.isConstant()) ?
getLiteralFactory(this.constantPool, literal); this.constantPool.getConstLiteral(literal, true) :
getLiteralFactory(this.constantPool, literal, this.allocatePureFunctionSlots);
}); });
} }
@ -539,14 +556,13 @@ class ValueConverter extends AstMemoryEfficientTransformer {
// values. Otherwise, just return an literal array that contains the values. // values. Otherwise, just return an literal array that contains the values.
const literal = o.literalMap(values.map( const literal = o.literalMap(values.map(
(value, index) => ({key: map.keys[index].key, value, quoted: map.keys[index].quoted}))); (value, index) => ({key: map.keys[index].key, value, quoted: map.keys[index].quoted})));
return values.every(a => a.isConstant()) ? this.constantPool.getConstLiteral(literal, true) : return values.every(a => a.isConstant()) ?
getLiteralFactory(this.constantPool, literal); this.constantPool.getConstLiteral(literal, true) :
getLiteralFactory(this.constantPool, literal, this.allocatePureFunctionSlots);
}); });
} }
} }
// Pipes always have at least one parameter, the value they operate on // Pipes always have at least one parameter, the value they operate on
const pipeBindingIdentifiers = [R3.pipeBind1, R3.pipeBind2, R3.pipeBind3, R3.pipeBind4]; const pipeBindingIdentifiers = [R3.pipeBind1, R3.pipeBind2, R3.pipeBind3, R3.pipeBind4];
@ -559,15 +575,20 @@ const pureFunctionIdentifiers = [
R3.pureFunction5, R3.pureFunction6, R3.pureFunction7, R3.pureFunction8 R3.pureFunction5, R3.pureFunction6, R3.pureFunction7, R3.pureFunction8
]; ];
function getLiteralFactory( function getLiteralFactory(
constantPool: ConstantPool, literal: o.LiteralArrayExpr | o.LiteralMapExpr): o.Expression { constantPool: ConstantPool, literal: o.LiteralArrayExpr | o.LiteralMapExpr,
allocateSlots: (numSlots: number) => number): o.Expression {
const {literalFactory, literalFactoryArguments} = constantPool.getLiteralFactory(literal); const {literalFactory, literalFactoryArguments} = constantPool.getLiteralFactory(literal);
// Allocate 1 slot for the result plus 1 per argument
const startSlot = allocateSlots(1 + literalFactoryArguments.length);
literalFactoryArguments.length > 0 || error(`Expected arguments to a literal factory function`); literalFactoryArguments.length > 0 || error(`Expected arguments to a literal factory function`);
let pureFunctionIdent = let pureFunctionIdent =
pureFunctionIdentifiers[literalFactoryArguments.length] || R3.pureFunctionV; pureFunctionIdentifiers[literalFactoryArguments.length] || R3.pureFunctionV;
// Literal factories are pure functions that only need to be re-invoked when the parameters // Literal factories are pure functions that only need to be re-invoked when the parameters
// change. // change.
return o.importExpr(pureFunctionIdent).callFn([literalFactory, ...literalFactoryArguments]); return o.importExpr(pureFunctionIdent).callFn([
o.literal(startSlot), literalFactory, ...literalFactoryArguments
]);
} }
/** /**

View File

@ -107,6 +107,57 @@ describe('compiler compliance', () => {
expectEmit(result.source, template, 'Incorrect template'); expectEmit(result.source, template, 'Incorrect template');
}); });
it('should reserve slots for pure functions', () => {
const files = {
app: {
'spec.ts': `
import {Component, NgModule} from '@angular/core';
@Component({
selector: 'my-component',
template: \`<div
[ternary]="cond ? [a] : [0]"
[pipe]="value | pipe:1:2"
[and]="cond && [b]"
[or]="cond || [c]"
></div>\`
})
export class MyComponent {
id = 'one';
}
@NgModule({declarations: [MyComponent]})
export class MyModule {}
`
}
};
const factory = 'factory: function MyComponent_Factory() { return new MyComponent(); }';
const template = `
template: function MyComponent_Template(rf: IDENT, ctx: IDENT) {
if (rf & 1) {
$r3$.ɵE(0, 'div');
$r3$.ɵPp(1,'pipe');
$r3$.ɵe();
$r3$.ɵrS(10);
}
if (rf & 2) {
$r3$.ɵp(0, 'ternary', $r3$.ɵb((ctx.cond ? $r3$.ɵf1(2, _c0, ctx.a): _c1)));
$r3$.ɵp(0, 'pipe', $r3$.ɵb($r3$.ɵpb3(6, 1, ctx.value, 1, 2)));
$r3$.ɵp(0, 'and', $r3$.ɵb((ctx.cond && $r3$.ɵf1(4, _c0, ctx.b))));
$r3$.ɵp(0, 'or', $r3$.ɵb((ctx.cond || $r3$.ɵf1(6, _c0, ctx.c))));
}
}
`;
const result = compile(files, angularFiles);
expectEmit(result.source, factory, 'Incorrect factory');
expectEmit(result.source, template, 'Incorrect template');
});
it('should bind to class and style names', () => { it('should bind to class and style names', () => {
const files = { const files = {
app: { app: {
@ -415,9 +466,10 @@ describe('compiler compliance', () => {
if (rf & 1) { if (rf & 1) {
$r3$.ɵE(0, 'my-comp'); $r3$.ɵE(0, 'my-comp');
$r3$.ɵe(); $r3$.ɵe();
$r3$.ɵrS(2);
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵp(0, 'names', $r3$.ɵb($r3$.ɵf1($e0_ff$, ctx.customName))); $r3$.ɵp(0, 'names', $r3$.ɵb($r3$.ɵf1(2, $e0_ff$, ctx.customName)));
} }
}, },
directives: [MyComp] directives: [MyComp]
@ -494,11 +546,12 @@ describe('compiler compliance', () => {
if (rf & 1) { if (rf & 1) {
$r3$.ɵE(0, 'my-comp'); $r3$.ɵE(0, 'my-comp');
$r3$.ɵe(); $r3$.ɵe();
$r3$.ɵrS(10);
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵp( $r3$.ɵp(
0, 'names', 0, 'names',
$r3$.ɵb($r3$.ɵfV($e0_ff$, ctx.n0, ctx.n1, ctx.n2, ctx.n3, ctx.n4, ctx.n5, ctx.n6, ctx.n7, ctx.n8))); $r3$.ɵb($r3$.ɵfV(10, $e0_ff$, ctx.n0, ctx.n1, ctx.n2, ctx.n3, ctx.n4, ctx.n5, ctx.n6, ctx.n7, ctx.n8)));
} }
}, },
directives: [MyComp] directives: [MyComp]
@ -555,9 +608,10 @@ describe('compiler compliance', () => {
if (rf & 1) { if (rf & 1) {
$r3$.ɵE(0, 'object-comp'); $r3$.ɵE(0, 'object-comp');
$r3$.ɵe(); $r3$.ɵe();
$r3$.ɵrS(2);
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵp(0, 'config', $r3$.ɵb($r3$.ɵf1($e0_ff$, ctx.name))); $r3$.ɵp(0, 'config', $r3$.ɵb($r3$.ɵf1(2, $e0_ff$, ctx.name)));
} }
}, },
directives: [ObjectComp] directives: [ObjectComp]
@ -620,12 +674,12 @@ describe('compiler compliance', () => {
if (rf & 1) { if (rf & 1) {
$r3$.ɵE(0, 'nested-comp'); $r3$.ɵE(0, 'nested-comp');
$r3$.ɵe(); $r3$.ɵe();
$r3$.ɵrS(7);
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵp( $r3$.ɵp(
0, 'config', 0, 'config',
$r3$.ɵb($r3$.ɵf2( $r3$.ɵb($r3$.ɵf2(7, $e0_ff_2$, ctx.name, $r3$.ɵf1(4, $e0_ff_1$, $r3$.ɵf1(2, $e0_ff$, ctx.duration)))));
$e0_ff_2$, ctx.name, $r3$.ɵf1($e0_ff_1$, $r3$.ɵf1($e0_ff$, ctx.duration)))));
} }
}, },
directives: [NestedComp] directives: [NestedComp]
@ -912,10 +966,11 @@ describe('compiler compliance', () => {
$r3$.ɵT(4); $r3$.ɵT(4);
$r3$.ɵPp(5, 'myPurePipe'); $r3$.ɵPp(5, 'myPurePipe');
$r3$.ɵe(); $r3$.ɵe();
$r3$.ɵrS(9);
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵt(0, $r3$.ɵi1('', $r3$.ɵpb2(1, $r3$.ɵpb2(2,ctx.name, ctx.size), ctx.size), '')); $r3$.ɵt(0, $r3$.ɵi1('', $r3$.ɵpb2(1, 3, $r3$.ɵpb2(2, 6, ctx.name, ctx.size), ctx.size), ''));
$r3$.ɵt(4, $r3$.ɵi1('', $r3$.ɵpb2(5, ctx.name, ctx.size), '')); $r3$.ɵt(4, $r3$.ɵi1('', $r3$.ɵpb2(5, 9, ctx.name, ctx.size), ''));
} }
}, },
pipes: [MyPurePipe, MyPipe] pipes: [MyPurePipe, MyPipe]

View File

@ -65,6 +65,7 @@ export {
e as ɵe, e as ɵe,
p as ɵp, p as ɵp,
pD as ɵpD, pD as ɵpD,
rS as ɵrS,
a as ɵa, a as ɵa,
s as ɵs, s as ɵs,
sn as ɵsn, sn as ɵsn,

View File

@ -64,6 +64,8 @@ export {
text as T, text as T,
textBinding as t, textBinding as t,
reserveSlots as rS,
embeddedViewStart as V, embeddedViewStart as V,
embeddedViewEnd as v, embeddedViewEnd as v,
detectChanges, detectChanges,

View File

@ -1446,12 +1446,10 @@ function generateInitialInputs(
return initialInputData; return initialInputData;
} }
////////////////////////// //////////////////////////
//// ViewContainer & View //// ViewContainer & View
////////////////////////// //////////////////////////
export function createLContainer( export function createLContainer(
parentLNode: LNode, currentView: LView, template?: ComponentTemplate<any>): LContainer { parentLNode: LNode, currentView: LView, template?: ComponentTemplate<any>): LContainer {
ngDevMode && assertNotNull(parentLNode, 'containers should have a parent'); ngDevMode && assertNotNull(parentLNode, 'containers should have a parent');
@ -2146,6 +2144,57 @@ export function bind<T>(value: T | NO_CHANGE): T|NO_CHANGE {
return changed ? value : NO_CHANGE; return changed ? value : NO_CHANGE;
} }
/**
* Reserves slots for pure functions (`pureFunctionX` instructions)
*
* Binding for pure functions are store after the LNodes in the data array but before the binding.
*
* ----------------------------------------------------------------------------
* | LNodes ... | pure function bindings | regular bindings / interpolations |
* ----------------------------------------------------------------------------
* ^
* LView.bindingStartIndex
*
* Pure function instructions are given an offset from LView.bindingStartIndex.
* Subtracting the offset from LView.bindingStartIndex gives the first index where the bindings
* are stored.
*
* NOTE: reserveSlots instructions are only ever allowed at the very end of the creation block
*/
export function reserveSlots(numSlots: number) {
// Init the slots with a unique `NO_CHANGE` value so that the first change is always detected
// whether is happens or not during the first change detection pass - pure functions checks
// might be skipped when short-circuited.
data.length += numSlots;
data.fill(NO_CHANGE, -numSlots);
// We need to initialize the binding in case a `pureFunctionX` kind of binding instruction is
// called first in the update section.
initBindings();
}
/**
* Sets up the binding index before execute any `pureFunctionX` instructions.
*
* The index must be restored after the pure function is executed
*
* {@link reserveSlots}
*/
export function moveBindingIndexToReservedSlot(offset: number): number {
const currentSlot = currentView.bindingIndex;
currentView.bindingIndex = currentView.bindingStartIndex - offset;
return currentSlot;
}
/**
* Restores the binding index to the given value.
*
* This function is typically used to restore the index after a `pureFunctionX` has
* been executed.
*/
export function restoreBindingIndex(index: number): void {
currentView.bindingIndex = index;
}
/** /**
* Create interpolation bindings with a variable number of expressions. * Create interpolation bindings with a variable number of expressions.
* *
@ -2378,6 +2427,22 @@ function assertDataNext(index: number, arr?: any[]) {
arr.length, index, `index ${index} expected to be at the end of arr (length ${arr.length})`); arr.length, index, `index ${index} expected to be at the end of arr (length ${arr.length})`);
} }
/**
* On the first template pass the reserved slots should be set `NO_CHANGE`.
*
* If not they might not have been actually reserved.
*/
export function assertReservedSlotInitialized(slotOffset: number, numSlots: number) {
if (firstTemplatePass) {
const startIndex = currentView.bindingStartIndex - slotOffset;
for (let i = 0; i < numSlots; i++) {
assertEqual(
data[startIndex + i], NO_CHANGE,
'The reserved slots should be set to `NO_CHANGE` on first template pass');
}
}
}
export function _getComponentHostLElementNode<T>(component: T): LElementNode { export function _getComponentHostLElementNode<T>(component: T): LElementNode {
ngDevMode && assertNotNull(component, 'expecting component got null'); ngDevMode && assertNotNull(component, 'expecting component got null');
const lElementNode = (component as any)[NG_HOST_SYMBOL] as LElementNode; const lElementNode = (component as any)[NG_HOST_SYMBOL] as LElementNode;

View File

@ -65,11 +65,12 @@ function getPipeDef(name: string, registry: PipeDefList | null): PipeDef<any> {
* the pipe only when an input to the pipe changes. * the pipe only when an input to the pipe changes.
* *
* @param index Pipe index where the pipe was stored on creation. * @param index Pipe index where the pipe was stored on creation.
* @param slotOffset the offset in the reserved slot space {@link reserveSlots}
* @param v1 1st argument to {@link PipeTransform#transform}. * @param v1 1st argument to {@link PipeTransform#transform}.
*/ */
export function pipeBind1(index: number, v1: any): any { export function pipeBind1(index: number, slotOffset: number, v1: any): any {
const pipeInstance = load<PipeTransform>(index); const pipeInstance = load<PipeTransform>(index);
return isPure(index) ? pureFunction1(pipeInstance.transform, v1, pipeInstance) : return isPure(index) ? pureFunction1(slotOffset, pipeInstance.transform, v1, pipeInstance) :
pipeInstance.transform(v1); pipeInstance.transform(v1);
} }
@ -80,12 +81,13 @@ export function pipeBind1(index: number, v1: any): any {
* the pipe only when an input to the pipe changes. * the pipe only when an input to the pipe changes.
* *
* @param index Pipe index where the pipe was stored on creation. * @param index Pipe index where the pipe was stored on creation.
* @param slotOffset the offset in the reserved slot space {@link reserveSlots}
* @param v1 1st argument to {@link PipeTransform#transform}. * @param v1 1st argument to {@link PipeTransform#transform}.
* @param v2 2nd argument to {@link PipeTransform#transform}. * @param v2 2nd argument to {@link PipeTransform#transform}.
*/ */
export function pipeBind2(index: number, v1: any, v2: any): any { export function pipeBind2(index: number, slotOffset: number, v1: any, v2: any): any {
const pipeInstance = load<PipeTransform>(index); const pipeInstance = load<PipeTransform>(index);
return isPure(index) ? pureFunction2(pipeInstance.transform, v1, v2, pipeInstance) : return isPure(index) ? pureFunction2(slotOffset, pipeInstance.transform, v1, v2, pipeInstance) :
pipeInstance.transform(v1, v2); pipeInstance.transform(v1, v2);
} }
@ -96,13 +98,15 @@ export function pipeBind2(index: number, v1: any, v2: any): any {
* the pipe only when an input to the pipe changes. * the pipe only when an input to the pipe changes.
* *
* @param index Pipe index where the pipe was stored on creation. * @param index Pipe index where the pipe was stored on creation.
* @param slotOffset the offset in the reserved slot space {@link reserveSlots}
* @param v1 1st argument to {@link PipeTransform#transform}. * @param v1 1st argument to {@link PipeTransform#transform}.
* @param v2 2nd argument to {@link PipeTransform#transform}. * @param v2 2nd argument to {@link PipeTransform#transform}.
* @param v3 4rd argument to {@link PipeTransform#transform}. * @param v3 4rd argument to {@link PipeTransform#transform}.
*/ */
export function pipeBind3(index: number, v1: any, v2: any, v3: any): any { export function pipeBind3(index: number, slotOffset: number, v1: any, v2: any, v3: any): any {
const pipeInstance = load<PipeTransform>(index); const pipeInstance = load<PipeTransform>(index);
return isPure(index) ? pureFunction3(pipeInstance.transform, v1, v2, v3, pipeInstance) : return isPure(index) ?
pureFunction3(slotOffset, pipeInstance.transform, v1, v2, v3, pipeInstance) :
pipeInstance.transform(v1, v2, v3); pipeInstance.transform(v1, v2, v3);
} }
@ -113,14 +117,17 @@ export function pipeBind3(index: number, v1: any, v2: any, v3: any): any {
* the pipe only when an input to the pipe changes. * the pipe only when an input to the pipe changes.
* *
* @param index Pipe index where the pipe was stored on creation. * @param index Pipe index where the pipe was stored on creation.
* @param slotOffset the offset in the reserved slot space {@link reserveSlots}
* @param v1 1st argument to {@link PipeTransform#transform}. * @param v1 1st argument to {@link PipeTransform#transform}.
* @param v2 2nd argument to {@link PipeTransform#transform}. * @param v2 2nd argument to {@link PipeTransform#transform}.
* @param v3 3rd argument to {@link PipeTransform#transform}. * @param v3 3rd argument to {@link PipeTransform#transform}.
* @param v4 4th argument to {@link PipeTransform#transform}. * @param v4 4th argument to {@link PipeTransform#transform}.
*/ */
export function pipeBind4(index: number, v1: any, v2: any, v3: any, v4: any): any { export function pipeBind4(
index: number, slotOffset: number, v1: any, v2: any, v3: any, v4: any): any {
const pipeInstance = load<PipeTransform>(index); const pipeInstance = load<PipeTransform>(index);
return isPure(index) ? pureFunction4(pipeInstance.transform, v1, v2, v3, v4, pipeInstance) : return isPure(index) ?
pureFunction4(slotOffset, pipeInstance.transform, v1, v2, v3, v4, pipeInstance) :
pipeInstance.transform(v1, v2, v3, v4); pipeInstance.transform(v1, v2, v3, v4);
} }
@ -131,11 +138,12 @@ export function pipeBind4(index: number, v1: any, v2: any, v3: any, v4: any): an
* the pipe only when an input to the pipe changes. * the pipe only when an input to the pipe changes.
* *
* @param index Pipe index where the pipe was stored on creation. * @param index Pipe index where the pipe was stored on creation.
* @param slotOffset the offset in the reserved slot space {@link reserveSlots}
* @param values Array of arguments to pass to {@link PipeTransform#transform} method. * @param values Array of arguments to pass to {@link PipeTransform#transform} method.
*/ */
export function pipeBindV(index: number, values: any[]): any { export function pipeBindV(index: number, slotOffset: number, values: any[]): any {
const pipeInstance = load<PipeTransform>(index); const pipeInstance = load<PipeTransform>(index);
return isPure(index) ? pureFunctionV(pipeInstance.transform, values, pipeInstance) : return isPure(index) ? pureFunctionV(slotOffset, pipeInstance.transform, values, pipeInstance) :
pipeInstance.transform.apply(pipeInstance, values); pipeInstance.transform.apply(pipeInstance, values);
} }

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 {bindingUpdated, bindingUpdated2, bindingUpdated4, checkAndUpdateBinding, consumeBinding, getCreationMode} from './instructions'; import {assertReservedSlotInitialized, bindingUpdated, bindingUpdated2, bindingUpdated4, checkAndUpdateBinding, consumeBinding, getCreationMode, moveBindingIndexToReservedSlot, restoreBindingIndex} from './instructions';
@ -15,92 +15,121 @@ import {bindingUpdated, bindingUpdated2, bindingUpdated4, checkAndUpdateBinding,
* value. If it has been saved, returns the saved value. * value. If it has been saved, returns the saved value.
* *
* @param pureFn Function that returns a value * @param pureFn Function that returns a value
* @param slotOffset the offset in the reserved slot space {@link reserveSlots}
* @param thisArg Optional calling context of pureFn * @param thisArg Optional calling context of pureFn
* @returns value * @returns value
*/ */
export function pureFunction0<T>(pureFn: () => T, thisArg?: any): T { export function pureFunction0<T>(slotOffset: number, pureFn: () => T, thisArg?: any): T {
return getCreationMode() ? checkAndUpdateBinding(thisArg ? pureFn.call(thisArg) : pureFn()) : ngDevMode && assertReservedSlotInitialized(slotOffset, 1);
const index = moveBindingIndexToReservedSlot(slotOffset);
const value = getCreationMode() ?
checkAndUpdateBinding(thisArg ? pureFn.call(thisArg) : pureFn()) :
consumeBinding(); consumeBinding();
restoreBindingIndex(index);
return value;
} }
/** /**
* If the value of the provided exp has changed, calls the pure function to return * 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. * an updated value. Or if the value has not changed, returns cached value.
* *
* @param slotOffset the offset in the reserved slot space {@link reserveSlots}
* @param pureFn Function that returns an updated value * @param pureFn Function that returns an updated value
* @param exp Updated expression value * @param exp Updated expression value
* @param thisArg Optional calling context of pureFn * @param thisArg Optional calling context of pureFn
* @returns Updated value * @returns Updated or cached value
*/ */
export function pureFunction1(pureFn: (v: any) => any, exp: any, thisArg?: any): any { export function pureFunction1(
return bindingUpdated(exp) ? slotOffset: number, pureFn: (v: any) => any, exp: any, thisArg?: any): any {
ngDevMode && assertReservedSlotInitialized(slotOffset, 2);
const index = moveBindingIndexToReservedSlot(slotOffset);
const value = bindingUpdated(exp) ?
checkAndUpdateBinding(thisArg ? pureFn.call(thisArg, exp) : pureFn(exp)) : checkAndUpdateBinding(thisArg ? pureFn.call(thisArg, exp) : pureFn(exp)) :
consumeBinding(); consumeBinding();
restoreBindingIndex(index);
return value;
} }
/** /**
* If the value of any provided exp has changed, calls the pure function to return * 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. * an updated value. Or if no values have changed, returns cached value.
* *
* @param slotOffset the offset in the reserved slot space {@link reserveSlots}
* @param pureFn * @param pureFn
* @param exp1 * @param exp1
* @param exp2 * @param exp2
* @param thisArg Optional calling context of pureFn * @param thisArg Optional calling context of pureFn
* @returns Updated value * @returns Updated or cached value
*/ */
export function pureFunction2( export function pureFunction2(
pureFn: (v1: any, v2: any) => any, exp1: any, exp2: any, thisArg?: any): any { slotOffset: number, pureFn: (v1: any, v2: any) => any, exp1: any, exp2: any,
return bindingUpdated2(exp1, exp2) ? thisArg?: any): any {
ngDevMode && assertReservedSlotInitialized(slotOffset, 3);
const index = moveBindingIndexToReservedSlot(slotOffset);
const value = bindingUpdated2(exp1, exp2) ?
checkAndUpdateBinding(thisArg ? pureFn.call(thisArg, exp1, exp2) : pureFn(exp1, exp2)) : checkAndUpdateBinding(thisArg ? pureFn.call(thisArg, exp1, exp2) : pureFn(exp1, exp2)) :
consumeBinding(); consumeBinding();
restoreBindingIndex(index);
return value;
} }
/** /**
* If the value of any provided exp has changed, calls the pure function to return * 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. * an updated value. Or if no values have changed, returns cached value.
* *
* @param slotOffset the offset in the reserved slot space {@link reserveSlots}
* @param pureFn * @param pureFn
* @param exp1 * @param exp1
* @param exp2 * @param exp2
* @param exp3 * @param exp3
* @param thisArg Optional calling context of pureFn * @param thisArg Optional calling context of pureFn
* @returns Updated value * @returns Updated or cached value
*/ */
export function pureFunction3( export function pureFunction3(
pureFn: (v1: any, v2: any, v3: any) => any, exp1: any, exp2: any, exp3: any, slotOffset: number, pureFn: (v1: any, v2: any, v3: any) => any, exp1: any, exp2: any, exp3: any,
thisArg?: any): any { thisArg?: any): any {
ngDevMode && assertReservedSlotInitialized(slotOffset, 4);
const index = moveBindingIndexToReservedSlot(slotOffset);
const different = bindingUpdated2(exp1, exp2); const different = bindingUpdated2(exp1, exp2);
return bindingUpdated(exp3) || different ? const value = bindingUpdated(exp3) || different ?
checkAndUpdateBinding( checkAndUpdateBinding(
thisArg ? pureFn.call(thisArg, exp1, exp2, exp3) : pureFn(exp1, exp2, exp3)) : thisArg ? pureFn.call(thisArg, exp1, exp2, exp3) : pureFn(exp1, exp2, exp3)) :
consumeBinding(); consumeBinding();
restoreBindingIndex(index);
return value;
} }
/** /**
* If the value of any provided exp has changed, calls the pure function to return * 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. * an updated value. Or if no values have changed, returns cached value.
* *
* @param slotOffset the offset in the reserved slot space {@link reserveSlots}
* @param pureFn * @param pureFn
* @param exp1 * @param exp1
* @param exp2 * @param exp2
* @param exp3 * @param exp3
* @param exp4 * @param exp4
* @param thisArg Optional calling context of pureFn * @param thisArg Optional calling context of pureFn
* @returns Updated value * @returns Updated or cached value
*/ */
export function pureFunction4( export function pureFunction4(
pureFn: (v1: any, v2: any, v3: any, v4: any) => any, exp1: any, exp2: any, exp3: any, exp4: any, slotOffset: number, pureFn: (v1: any, v2: any, v3: any, v4: any) => any, exp1: any, exp2: any,
thisArg?: any): any { exp3: any, exp4: any, thisArg?: any): any {
return bindingUpdated4(exp1, exp2, exp3, exp4) ? ngDevMode && assertReservedSlotInitialized(slotOffset, 5);
const index = moveBindingIndexToReservedSlot(slotOffset);
const value = bindingUpdated4(exp1, exp2, exp3, exp4) ?
checkAndUpdateBinding( checkAndUpdateBinding(
thisArg ? pureFn.call(thisArg, exp1, exp2, exp3, exp4) : pureFn(exp1, exp2, exp3, exp4)) : thisArg ? pureFn.call(thisArg, exp1, exp2, exp3, exp4) : pureFn(exp1, exp2, exp3, exp4)) :
consumeBinding(); consumeBinding();
restoreBindingIndex(index);
return value;
} }
/** /**
* If the value of any provided exp has changed, calls the pure function to return * 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. * an updated value. Or if no values have changed, returns cached value.
* *
* @param slotOffset the offset in the reserved slot space {@link reserveSlots}
* @param pureFn * @param pureFn
* @param exp1 * @param exp1
* @param exp2 * @param exp2
@ -108,23 +137,28 @@ export function pureFunction4(
* @param exp4 * @param exp4
* @param exp5 * @param exp5
* @param thisArg Optional calling context of pureFn * @param thisArg Optional calling context of pureFn
* @returns Updated value * @returns Updated or cached 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, slotOffset: number, pureFn: (v1: any, v2: any, v3: any, v4: any, v5: any) => any, exp1: any,
exp4: any, exp5: any, thisArg?: any): any { exp2: any, exp3: any, exp4: any, exp5: any, thisArg?: any): any {
ngDevMode && assertReservedSlotInitialized(slotOffset, 6);
const index = moveBindingIndexToReservedSlot(slotOffset);
const different = bindingUpdated4(exp1, exp2, exp3, exp4); const different = bindingUpdated4(exp1, exp2, exp3, exp4);
return bindingUpdated(exp5) || different ? const value = bindingUpdated(exp5) || different ?
checkAndUpdateBinding( checkAndUpdateBinding(
thisArg ? pureFn.call(thisArg, exp1, exp2, exp3, exp4, exp5) : thisArg ? pureFn.call(thisArg, exp1, exp2, exp3, exp4, exp5) :
pureFn(exp1, exp2, exp3, exp4, exp5)) : pureFn(exp1, exp2, exp3, exp4, exp5)) :
consumeBinding(); consumeBinding();
restoreBindingIndex(index);
return value;
} }
/** /**
* If the value of any provided exp has changed, calls the pure function to return * 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. * an updated value. Or if no values have changed, returns cached value.
* *
* @param slotOffset the offset in the reserved slot space {@link reserveSlots}
* @param pureFn * @param pureFn
* @param exp1 * @param exp1
* @param exp2 * @param exp2
@ -133,23 +167,28 @@ export function pureFunction5(
* @param exp5 * @param exp5
* @param exp6 * @param exp6
* @param thisArg Optional calling context of pureFn * @param thisArg Optional calling context of pureFn
* @returns Updated value * @returns Updated or cached 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, slotOffset: number, pureFn: (v1: any, v2: any, v3: any, v4: any, v5: any, v6: any) => any,
exp3: any, exp4: any, exp5: any, exp6: any, thisArg?: any): any { exp1: any, exp2: any, exp3: any, exp4: any, exp5: any, exp6: any, thisArg?: any): any {
ngDevMode && assertReservedSlotInitialized(slotOffset, 7);
const index = moveBindingIndexToReservedSlot(slotOffset);
const different = bindingUpdated4(exp1, exp2, exp3, exp4); const different = bindingUpdated4(exp1, exp2, exp3, exp4);
return bindingUpdated2(exp5, exp6) || different ? const value = bindingUpdated2(exp5, exp6) || different ?
checkAndUpdateBinding( checkAndUpdateBinding(
thisArg ? pureFn.call(thisArg, exp1, exp2, exp3, exp4, exp5, exp6) : thisArg ? pureFn.call(thisArg, exp1, exp2, exp3, exp4, exp5, exp6) :
pureFn(exp1, exp2, exp3, exp4, exp5, exp6)) : pureFn(exp1, exp2, exp3, exp4, exp5, exp6)) :
consumeBinding(); consumeBinding();
restoreBindingIndex(index);
return value;
} }
/** /**
* If the value of any provided exp has changed, calls the pure function to return * 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. * an updated value. Or if no values have changed, returns cached value.
* *
* @param slotOffset the offset in the reserved slot space {@link reserveSlots}
* @param pureFn * @param pureFn
* @param exp1 * @param exp1
* @param exp2 * @param exp2
@ -159,24 +198,30 @@ export function pureFunction6(
* @param exp6 * @param exp6
* @param exp7 * @param exp7
* @param thisArg Optional calling context of pureFn * @param thisArg Optional calling context of pureFn
* @returns Updated value * @returns Updated or cached value
*/ */
export function pureFunction7( export function pureFunction7(
slotOffset: number,
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, thisArg?: any): any { exp2: any, exp3: any, exp4: any, exp5: any, exp6: any, exp7: any, thisArg?: any): any {
ngDevMode && assertReservedSlotInitialized(slotOffset, 8);
const index = moveBindingIndexToReservedSlot(slotOffset);
let different = bindingUpdated4(exp1, exp2, exp3, exp4); let different = bindingUpdated4(exp1, exp2, exp3, exp4);
different = bindingUpdated2(exp5, exp6) || different; different = bindingUpdated2(exp5, exp6) || different;
return bindingUpdated(exp7) || different ? const value = bindingUpdated(exp7) || different ?
checkAndUpdateBinding( checkAndUpdateBinding(
thisArg ? pureFn.call(thisArg, exp1, exp2, exp3, exp4, exp5, exp6, exp7) : thisArg ? pureFn.call(thisArg, exp1, exp2, exp3, exp4, exp5, exp6, exp7) :
pureFn(exp1, exp2, exp3, exp4, exp5, exp6, exp7)) : pureFn(exp1, exp2, exp3, exp4, exp5, exp6, exp7)) :
consumeBinding(); consumeBinding();
restoreBindingIndex(index);
return value;
} }
/** /**
* If the value of any provided exp has changed, calls the pure function to return * 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. * an updated value. Or if no values have changed, returns cached value.
* *
* @param slotOffset the offset in the reserved slot space {@link reserveSlots}
* @param pureFn * @param pureFn
* @param exp1 * @param exp1
* @param exp2 * @param exp2
@ -187,18 +232,23 @@ export function pureFunction7(
* @param exp7 * @param exp7
* @param exp8 * @param exp8
* @param thisArg Optional calling context of pureFn * @param thisArg Optional calling context of pureFn
* @returns Updated value * @returns Updated or cached value
*/ */
export function pureFunction8( export function pureFunction8(
slotOffset: number,
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, exp1: any, exp2: any, exp3: any, exp4: any, exp5: any, exp6: any, exp7: any, exp8: any,
thisArg?: any): any { thisArg?: any): any {
ngDevMode && assertReservedSlotInitialized(slotOffset, 9);
const index = moveBindingIndexToReservedSlot(slotOffset);
const different = bindingUpdated4(exp1, exp2, exp3, exp4); const different = bindingUpdated4(exp1, exp2, exp3, exp4);
return bindingUpdated4(exp5, exp6, exp7, exp8) || different ? const value = bindingUpdated4(exp5, exp6, exp7, exp8) || different ?
checkAndUpdateBinding( checkAndUpdateBinding(
thisArg ? pureFn.call(thisArg, exp1, exp2, exp3, exp4, exp5, exp6, exp7, exp8) : thisArg ? pureFn.call(thisArg, exp1, exp2, exp3, exp4, exp5, exp6, exp7, exp8) :
pureFn(exp1, exp2, exp3, exp4, exp5, exp6, exp7, exp8)) : pureFn(exp1, exp2, exp3, exp4, exp5, exp6, exp7, exp8)) :
consumeBinding(); consumeBinding();
restoreBindingIndex(index);
return value;
} }
/** /**
@ -207,17 +257,23 @@ export function pureFunction8(
* If the value of any provided exp has changed, calls the pure function to return * 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. * an updated value. Or if no values have changed, returns cached value.
* *
* @param slotOffset the offset in the reserved slot space {@link reserveSlots}
* @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 exps An array of binding values * @param exps An array of binding values
* @param thisArg Optional calling context of pureFn * @param thisArg Optional calling context of pureFn
* @returns Updated value * @returns Updated or cached value
*/ */
export function pureFunctionV(pureFn: (...v: any[]) => any, exps: any[], thisArg?: any): any { export function pureFunctionV(
let different = false; slotOffset: number, pureFn: (...v: any[]) => any, exps: any[], thisArg?: any): any {
ngDevMode && assertReservedSlotInitialized(slotOffset, exps.length + 1);
const index = moveBindingIndexToReservedSlot(slotOffset);
let different = false;
for (let i = 0; i < exps.length; i++) { for (let i = 0; i < exps.length; i++) {
bindingUpdated(exps[i]) && (different = true); bindingUpdated(exps[i]) && (different = true);
} }
return different ? checkAndUpdateBinding(pureFn.apply(thisArg, exps)) : consumeBinding(); const value = different ? checkAndUpdateBinding(pureFn.apply(thisArg, exps)) : consumeBinding();
restoreBindingIndex(index);
return value;
} }

View File

@ -502,9 +502,10 @@ describe('components & directives', () => {
if (rf & 1) { if (rf & 1) {
$r3$.ɵE(0, 'my-array-comp'); $r3$.ɵE(0, 'my-array-comp');
$r3$.ɵe(); $r3$.ɵe();
$r3$.ɵrS(1);
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵp(0, 'names', $r3$.ɵb(ctx.someFn($r3$.ɵf0($e0_ff$)))); $r3$.ɵp(0, 'names', $r3$.ɵb(ctx.someFn($r3$.ɵf0(1, $e0_ff$))));
} }
} }
}); });
@ -563,9 +564,10 @@ describe('components & directives', () => {
if (rf & 1) { if (rf & 1) {
$r3$.ɵE(0, 'my-comp'); $r3$.ɵE(0, 'my-comp');
$r3$.ɵe(); $r3$.ɵe();
$r3$.ɵrS(1);
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵp(0, 'num', $r3$.ɵb($r3$.ɵf0($e0_ff$).length + 1)); $r3$.ɵp(0, 'num', $r3$.ɵb($r3$.ɵf0(1, $e0_ff$).length + 1));
} }
} }
}); });
@ -606,9 +608,10 @@ describe('components & directives', () => {
if (rf & 1) { if (rf & 1) {
$r3$.ɵE(0, 'my-array-comp'); $r3$.ɵE(0, 'my-array-comp');
$r3$.ɵe(); $r3$.ɵe();
$r3$.ɵrS(2);
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵp(0, 'names', $r3$.ɵb($r3$.ɵf1($e0_ff$, ctx.customName))); $r3$.ɵp(0, 'names', $r3$.ɵb($r3$.ɵf1(2, $e0_ff$, ctx.customName)));
} }
} }
}); });
@ -716,12 +719,13 @@ describe('components & directives', () => {
if (rf & 1) { if (rf & 1) {
$r3$.ɵE(0, 'my-comp'); $r3$.ɵE(0, 'my-comp');
$r3$.ɵe(); $r3$.ɵe();
$r3$.ɵrS(10);
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵp( $r3$.ɵp(
0, 'names', 0, 'names',
$r3$.ɵb( $r3$.ɵb($r3$.ɵfV(
$r3$.ɵfV($e0_ff$, [c.n0, c.n1, c.n2, c.n3, c.n4, c.n5, c.n6, c.n7, c.n8]))); 10, $e0_ff$, [c.n0, c.n1, c.n2, c.n3, c.n4, c.n5, c.n6, c.n7, c.n8])));
} }
} }
}); });
@ -794,9 +798,10 @@ describe('components & directives', () => {
if (rf & 1) { if (rf & 1) {
$r3$.ɵE(0, 'object-comp'); $r3$.ɵE(0, 'object-comp');
$r3$.ɵe(); $r3$.ɵe();
$r3$.ɵrS(2);
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵp(0, 'config', $r3$.ɵb($r3$.ɵf1($e0_ff$, ctx.name))); $r3$.ɵp(0, 'config', $r3$.ɵb($r3$.ɵf1(2, $e0_ff$, ctx.name)));
} }
} }
}); });
@ -879,12 +884,13 @@ describe('components & directives', () => {
if (rf & 1) { if (rf & 1) {
$r3$.ɵE(0, 'nested-comp'); $r3$.ɵE(0, 'nested-comp');
$r3$.ɵe(); $r3$.ɵe();
$r3$.ɵrS(7);
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵp( $r3$.ɵp(
0, 'config', $r3$.ɵf2( 0, 'config', $r3$.ɵb($r3$.ɵf2(
$e0_ff_2$, ctx.name, 7, $e0_ff_2$, ctx.name,
$r3$.ɵb($r3$.ɵf1($e0_ff_1$, $r3$.ɵf1($e0_ff$, ctx.duration))))); $r3$.ɵf1(4, $e0_ff_1$, $r3$.ɵf1(2, $e0_ff$, ctx.duration)))));
} }
} }
}); });

View File

@ -88,9 +88,12 @@ describe('pipes', () => {
$r3$.ɵT(0); $r3$.ɵT(0);
$r3$.ɵPp(1, 'myPipe'); $r3$.ɵPp(1, 'myPipe');
$r3$.ɵPp(2, 'myPurePipe'); $r3$.ɵPp(2, 'myPurePipe');
$r3$.ɵrS(6);
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵt(0, $r3$.ɵi1('', $r3$.ɵpb2(1, $r3$.ɵpb2(2, ctx.name, ctx.size), ctx.size), '')); $r3$.ɵt(
0,
$r3$.ɵi1('', $r3$.ɵpb2(1, 6, $r3$.ɵpb2(2, 3, ctx.name, ctx.size), ctx.size), ''));
} }
} }
}); });
@ -166,10 +169,11 @@ describe('pipes', () => {
$r3$.ɵT(2); $r3$.ɵT(2);
$r3$.ɵPp(3, 'myPurePipe'); $r3$.ɵPp(3, 'myPurePipe');
$r3$.ɵC(4, C4, '', ['oneTimeIf', '']); $r3$.ɵC(4, C4, '', ['oneTimeIf', '']);
$r3$.ɵrS(6);
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵt(0, $r3$.ɵi1('', $r3$.ɵpb2(1, ctx.name, ctx.size), '')); $r3$.ɵt(0, $r3$.ɵi1('', $r3$.ɵpb2(1, 3, ctx.name, ctx.size), ''));
$r3$.ɵt(2, $r3$.ɵi1('', $r3$.ɵpb2(3, ctx.name, ctx.size), '')); $r3$.ɵt(2, $r3$.ɵi1('', $r3$.ɵpb2(3, 6, ctx.name, ctx.size), ''));
$r3$.ɵp(4, 'oneTimeIf', $r3$.ɵb(ctx.more)); $r3$.ɵp(4, 'oneTimeIf', $r3$.ɵb(ctx.more));
$r3$.ɵcR(4); $r3$.ɵcR(4);
$r3$.ɵcr(); $r3$.ɵcr();
@ -181,9 +185,10 @@ describe('pipes', () => {
$r3$.ɵT(1); $r3$.ɵT(1);
$r3$.ɵPp(2, 'myPurePipe'); $r3$.ɵPp(2, 'myPurePipe');
$r3$.ɵe(); $r3$.ɵe();
$r3$.ɵrS(3);
} }
if (rf & 2) { if (rf & 2) {
$r3$.ɵt(1, $r3$.ɵi1('', $r3$.ɵpb2(2, ctx.name, ctx.size), '')); $r3$.ɵt(1, $r3$.ɵi1('', $r3$.ɵpb2(2, 3, ctx.name, ctx.size), ''));
} }
} }
} }

View File

@ -10,7 +10,7 @@ import {Directive, OnChanges, OnDestroy, Pipe, PipeTransform} from '@angular/cor
import {expect} from '@angular/platform-browser/testing/src/matchers'; import {expect} from '@angular/platform-browser/testing/src/matchers';
import {defineDirective, definePipe} from '../../src/render3/definition'; import {defineDirective, definePipe} from '../../src/render3/definition';
import {bind, container, containerRefreshEnd, containerRefreshStart, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, interpolation1, load, loadDirective, text, textBinding} from '../../src/render3/instructions'; import {bind, container, containerRefreshEnd, containerRefreshStart, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, interpolation1, load, loadDirective, reserveSlots, text, textBinding} from '../../src/render3/instructions';
import {RenderFlags} from '../../src/render3/interfaces/definition'; import {RenderFlags} from '../../src/render3/interfaces/definition';
import {pipe, pipeBind1, pipeBind3, pipeBind4, pipeBindV} from '../../src/render3/pipe'; import {pipe, pipeBind1, pipeBind3, pipeBind4, pipeBindV} from '../../src/render3/pipe';
@ -38,9 +38,10 @@ describe('pipe', () => {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
text(0); text(0);
pipe(1, 'countingPipe'); pipe(1, 'countingPipe');
reserveSlots(2);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
textBinding(0, interpolation1('', pipeBind1(1, person.name), '')); textBinding(0, interpolation1('', pipeBind1(1, 2, person.name), ''));
} }
} }
@ -53,9 +54,10 @@ describe('pipe', () => {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
text(0); text(0);
pipe(1, 'randomPipeName'); pipe(1, 'randomPipeName');
reserveSlots(2);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
textBinding(0, interpolation1('', pipeBind1(1, ctx.value), '')); textBinding(0, interpolation1('', pipeBind1(1, 2, ctx.value), ''));
} }
}, [], pipes); }, [], pipes);
@ -97,9 +99,10 @@ describe('pipe', () => {
elementStart(0, 'div', ['myDir', '']); elementStart(0, 'div', ['myDir', '']);
pipe(1, 'double'); pipe(1, 'double');
elementEnd(); elementEnd();
reserveSlots(2);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
elementProperty(0, 'elprop', bind(pipeBind1(1, ctx))); elementProperty(0, 'elprop', bind(pipeBind1(1, 2, ctx)));
directive = loadDirective(0); directive = loadDirective(0);
} }
} }
@ -112,10 +115,11 @@ describe('pipe', () => {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
text(0); text(0);
pipe(1, 'multiArgPipe'); pipe(1, 'multiArgPipe');
reserveSlots(4);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
textBinding( textBinding(
0, interpolation1('', pipeBind3(1, person.name, 'one', person.address !.city), '')); 0, interpolation1('', pipeBind3(1, 4, person.name, 'one', person.address !.city), ''));
} }
} }
@ -129,11 +133,12 @@ describe('pipe', () => {
text(0); text(0);
pipe(1, 'multiArgPipe'); pipe(1, 'multiArgPipe');
pipe(2, 'multiArgPipe'); pipe(2, 'multiArgPipe');
reserveSlots(9);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
textBinding( textBinding(
0, 0, interpolation1(
interpolation1('', pipeBind4(2, pipeBindV(1, [person.name, 'a', 'b']), 0, 1, 2), '')); '', pipeBind4(2, 9, pipeBindV(1, 4, [person.name, 'a', 'b']), 0, 1, 2), ''));
} }
} }
@ -158,9 +163,10 @@ describe('pipe', () => {
elementStart(0, 'div'); elementStart(0, 'div');
pipe(1, 'identityPipe'); pipe(1, 'identityPipe');
elementEnd(); elementEnd();
reserveSlots(2);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
elementProperty(0, 'someProp', bind(pipeBind1(1, 'Megatron'))); elementProperty(0, 'someProp', bind(pipeBind1(1, 2, 'Megatron')));
} }
} }
@ -178,9 +184,10 @@ describe('pipe', () => {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
text(0); text(0);
pipe(1, 'countingPipe'); pipe(1, 'countingPipe');
reserveSlots(2);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
textBinding(0, interpolation1('', pipeBind1(1, person.name), '')); textBinding(0, interpolation1('', pipeBind1(1, 2, person.name), ''));
} }
} }
@ -207,9 +214,10 @@ describe('pipe', () => {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
text(0); text(0);
pipe(1, 'countingImpurePipe'); pipe(1, 'countingImpurePipe');
reserveSlots(2);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
textBinding(0, interpolation1('', pipeBind1(1, person.name), '')); textBinding(0, interpolation1('', pipeBind1(1, 2, person.name), ''));
} }
} }
@ -228,10 +236,11 @@ describe('pipe', () => {
pipe(3, 'countingImpurePipe'); pipe(3, 'countingImpurePipe');
elementEnd(); elementEnd();
container(4); container(4);
reserveSlots(4);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
elementProperty(0, 'someProp', bind(pipeBind1(1, true))); elementProperty(0, 'someProp', bind(pipeBind1(1, 2, true)));
elementProperty(2, 'someProp', bind(pipeBind1(3, true))); elementProperty(2, 'someProp', bind(pipeBind1(3, 4, true)));
pipeInstances.push(load<CountingImpurePipe>(1), load(3)); pipeInstances.push(load<CountingImpurePipe>(1), load(3));
containerRefreshStart(4); containerRefreshStart(4);
{ {
@ -242,9 +251,10 @@ describe('pipe', () => {
elementStart(0, 'div'); elementStart(0, 'div');
pipe(1, 'countingImpurePipe'); pipe(1, 'countingImpurePipe');
elementEnd(); elementEnd();
reserveSlots(2);
} }
if (rf1 & RenderFlags.Update) { if (rf1 & RenderFlags.Update) {
elementProperty(0, 'someProp', bind(pipeBind1(1, true))); elementProperty(0, 'someProp', bind(pipeBind1(1, 2, true)));
pipeInstances.push(load<CountingImpurePipe>(1)); pipeInstances.push(load<CountingImpurePipe>(1));
} }
} }
@ -296,9 +306,10 @@ describe('pipe', () => {
if (rf1 & RenderFlags.Create) { if (rf1 & RenderFlags.Create) {
text(0); text(0);
pipe(1, 'pipeWithOnDestroy'); pipe(1, 'pipeWithOnDestroy');
reserveSlots(2);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
textBinding(0, interpolation1('', pipeBind1(1, person.age), '')); textBinding(0, interpolation1('', pipeBind1(1, 2, person.age), ''));
} }
} }
embeddedViewEnd(); embeddedViewEnd();

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 {bind, container, containerRefreshEnd, containerRefreshStart, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, load, loadDirective} from '../../src/render3/instructions'; import {bind, container, containerRefreshEnd, containerRefreshStart, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, load, loadDirective, reserveSlots} from '../../src/render3/instructions';
import {RenderFlags} from '../../src/render3/interfaces/definition'; import {RenderFlags} from '../../src/render3/interfaces/definition';
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';
@ -36,9 +36,10 @@ describe('array literals', () => {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
elementStart(0, 'my-comp'); elementStart(0, 'my-comp');
elementEnd(); elementEnd();
reserveSlots(2);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
elementProperty(0, 'names', bind(pureFunction1(e0_ff, ctx.customName))); elementProperty(0, 'names', bind(pureFunction1(2, e0_ff, ctx.customName)));
} }
} }
@ -90,10 +91,11 @@ describe('array literals', () => {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
elementStart(0, 'many-prop-comp'); elementStart(0, 'many-prop-comp');
elementEnd(); elementEnd();
reserveSlots(4);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
elementProperty(0, 'names1', bind(pureFunction1(e0_ff, ctx.customName))); elementProperty(0, 'names1', bind(pureFunction1(2, e0_ff, ctx.customName)));
elementProperty(0, 'names2', bind(pureFunction1(e0_ff_1, ctx.customName2))); elementProperty(0, 'names2', bind(pureFunction1(4, e0_ff_1, ctx.customName2)));
} }
} }
@ -130,9 +132,10 @@ describe('array literals', () => {
elementStart(0, 'my-comp'); elementStart(0, 'my-comp');
myComps.push(loadDirective(0)); myComps.push(loadDirective(0));
elementEnd(); elementEnd();
reserveSlots(2);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
elementProperty(0, 'names', bind(ctx.someFn(pureFunction1(e0_ff, ctx.customName)))); elementProperty(0, 'names', bind(ctx.someFn(pureFunction1(2, e0_ff, ctx.customName))));
} }
}, },
directives: directives directives: directives
@ -170,9 +173,10 @@ describe('array literals', () => {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
elementStart(0, 'my-comp'); elementStart(0, 'my-comp');
elementEnd(); elementEnd();
reserveSlots(3);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
elementProperty(0, 'names', bind(pureFunction2(e0_ff, ctx.customName, ctx.customName2))); elementProperty(0, 'names', bind(pureFunction2(3, e0_ff, ctx.customName, ctx.customName2)));
} }
} }
@ -241,17 +245,19 @@ describe('array literals', () => {
elementStart(5, 'my-comp'); elementStart(5, 'my-comp');
f8Comp = loadDirective(5); f8Comp = loadDirective(5);
elementEnd(); elementEnd();
reserveSlots(39);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
elementProperty(0, 'names', bind(pureFunction3(e0_ff, c[5], c[6], c[7]))); elementProperty(0, 'names', bind(pureFunction3(4, e0_ff, c[5], c[6], c[7])));
elementProperty(1, 'names', bind(pureFunction4(e2_ff, c[4], c[5], c[6], c[7]))); elementProperty(1, 'names', bind(pureFunction4(9, e2_ff, c[4], c[5], c[6], c[7])));
elementProperty(2, 'names', bind(pureFunction5(e4_ff, c[3], c[4], c[5], c[6], c[7]))); elementProperty(2, 'names', bind(pureFunction5(15, e4_ff, c[3], c[4], c[5], c[6], c[7])));
elementProperty(3, 'names', bind(pureFunction6(e6_ff, c[2], c[3], c[4], c[5], c[6], c[7])));
elementProperty( elementProperty(
4, 'names', bind(pureFunction7(e8_ff, c[1], c[2], c[3], c[4], c[5], c[6], c[7]))); 3, 'names', bind(pureFunction6(22, e6_ff, c[2], c[3], c[4], c[5], c[6], c[7])));
elementProperty(
4, 'names', bind(pureFunction7(30, e8_ff, c[1], c[2], c[3], c[4], c[5], c[6], c[7])));
elementProperty( elementProperty(
5, 'names', 5, 'names',
bind(pureFunction8(e10_ff, c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7]))); bind(pureFunction8(39, e10_ff, c[0], c[1], c[2], c[3], c[4], c[5], c[6], c[7])));
} }
} }
@ -295,11 +301,12 @@ describe('array literals', () => {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
elementStart(0, 'my-comp'); elementStart(0, 'my-comp');
elementEnd(); elementEnd();
reserveSlots(12);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
elementProperty( elementProperty(
0, 'names', bind(pureFunctionV(e0_ff, [ 0, 'names', bind(pureFunctionV(12, 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(2, e0_ff_1, c[4]), c[5], c[6], c[7], c[8]
]))); ])));
} }
} }
@ -345,9 +352,10 @@ describe('object literals', () => {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
elementStart(0, 'object-comp'); elementStart(0, 'object-comp');
elementEnd(); elementEnd();
reserveSlots(2);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
elementProperty(0, 'config', bind(pureFunction1(e0_ff, ctx.name))); elementProperty(0, 'config', bind(pureFunction1(2, e0_ff, ctx.name)));
} }
} }
@ -380,12 +388,13 @@ describe('object literals', () => {
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Create) {
elementStart(0, 'object-comp'); elementStart(0, 'object-comp');
elementEnd(); elementEnd();
reserveSlots(7);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
elementProperty( elementProperty(
0, 'config', 0, 'config', bind(pureFunction2(
bind(pureFunction2( 7, e0_ff, ctx.name,
e0_ff, ctx.name, pureFunction1(e0_ff_1, pureFunction1(e0_ff_2, ctx.duration))))); pureFunction1(4, e0_ff_1, pureFunction1(2, e0_ff_2, ctx.duration)))));
} }
} }
@ -451,11 +460,12 @@ describe('object literals', () => {
elementStart(0, 'object-comp'); elementStart(0, 'object-comp');
objectComps.push(loadDirective(0)); objectComps.push(loadDirective(0));
elementEnd(); elementEnd();
reserveSlots(3);
} }
if (rf1 & RenderFlags.Update) { if (rf1 & RenderFlags.Update) {
elementProperty( elementProperty(
0, 'config', 0, 'config',
bind(pureFunction2(e0_ff, ctx.configs[i].opacity, ctx.configs[i].duration))); bind(pureFunction2(3, e0_ff, ctx.configs[i].opacity, ctx.configs[i].duration)));
} }
embeddedViewEnd(); embeddedViewEnd();
} }

View File

@ -9,7 +9,7 @@
import {Component, Directive, Pipe, PipeTransform, TemplateRef, ViewContainerRef} from '../../src/core'; import {Component, Directive, Pipe, PipeTransform, TemplateRef, ViewContainerRef} from '../../src/core';
import {getOrCreateNodeInjectorForNode, getOrCreateTemplateRef} from '../../src/render3/di'; import {getOrCreateNodeInjectorForNode, getOrCreateTemplateRef} from '../../src/render3/di';
import {NgOnChangesFeature, defineComponent, defineDirective, definePipe, injectTemplateRef, injectViewContainerRef} from '../../src/render3/index'; import {NgOnChangesFeature, defineComponent, defineDirective, definePipe, injectTemplateRef, injectViewContainerRef} from '../../src/render3/index';
import {bind, container, containerRefreshEnd, containerRefreshStart, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, interpolation1, load, loadDirective, projection, projectionDef, text, textBinding} from '../../src/render3/instructions'; import {bind, container, containerRefreshEnd, containerRefreshStart, elementEnd, elementProperty, elementStart, embeddedViewEnd, embeddedViewStart, interpolation1, load, loadDirective, projection, projectionDef, reserveSlots, text, textBinding} from '../../src/render3/instructions';
import {RenderFlags} from '../../src/render3/interfaces/definition'; import {RenderFlags} from '../../src/render3/interfaces/definition';
import {pipe, pipeBind1} from '../../src/render3/pipe'; import {pipe, pipeBind1} from '../../src/render3/pipe';
@ -383,22 +383,25 @@ describe('ViewContainerRef', () => {
elementStart(0, 'child'); elementStart(0, 'child');
elementEnd(); elementEnd();
pipe(1, 'starPipe'); pipe(1, 'starPipe');
reserveSlots(2);
} }
if (rf & RenderFlags.Create) { if (rf & RenderFlags.Update) {
elementProperty(0, 'name', bind(pipeBind1(1, 'C'))); elementProperty(0, 'name', bind(pipeBind1(1, 2, 'C')));
} }
}); });
pipe(1, 'starPipe'); pipe(1, 'starPipe');
elementStart(2, 'child', ['vcref', '']); elementStart(2, 'child', ['vcref', '']);
elementEnd(); elementEnd();
elementStart(3, 'child'); pipe(3, 'starPipe');
elementStart(4, 'child');
elementEnd(); elementEnd();
reserveSlots(4);
} }
if (rf & RenderFlags.Update) { if (rf & RenderFlags.Update) {
const tplRef = getOrCreateTemplateRef(getOrCreateNodeInjectorForNode(load(0))); const tplRef = getOrCreateTemplateRef(getOrCreateNodeInjectorForNode(load(0)));
elementProperty(2, 'tplRef', bind(tplRef)); elementProperty(2, 'tplRef', bind(tplRef));
elementProperty(2, 'name', bind(pipeBind1(1, 'A'))); elementProperty(2, 'name', bind(pipeBind1(1, 2, 'A')));
elementProperty(3, 'name', bind(pipeBind1(1, 'B'))); elementProperty(4, 'name', bind(pipeBind1(1, 4, 'B')));
} }
}, },
directives: [Child, DirectiveWithVCRef], directives: [Child, DirectiveWithVCRef],