From 4f36340de7d2c7a4cbeeaaa0a31fc9999de7a159 Mon Sep 17 00:00:00 2001 From: Victor Berchet Date: Mon, 21 May 2018 15:59:25 -0700 Subject: [PATCH] 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 --- .../compiler/src/render3/r3_identifiers.ts | 3 + .../compiler/src/render3/view/template.ts | 41 ++++-- .../render3/r3_compiler_compliance_spec.ts | 69 +++++++++- .../core/src/core_render3_private_export.ts | 1 + packages/core/src/render3/index.ts | 2 + packages/core/src/render3/instructions.ts | 69 +++++++++- packages/core/src/render3/pipe.ts | 32 +++-- packages/core/src/render3/pure_function.ts | 122 +++++++++++++----- .../component_directives_spec.ts | 24 ++-- .../render3/compiler_canonical/pipes_spec.ts | 13 +- packages/core/test/render3/pipe_spec.ts | 39 ++++-- .../core/test/render3/pure_function_spec.ts | 48 ++++--- .../test/render3/view_container_ref_spec.ts | 15 ++- 13 files changed, 362 insertions(+), 116 deletions(-) diff --git a/packages/compiler/src/render3/r3_identifiers.ts b/packages/compiler/src/render3/r3_identifiers.ts index 599ec956d8..b9bf96b0d3 100644 --- a/packages/compiler/src/render3/r3_identifiers.ts +++ b/packages/compiler/src/render3/r3_identifiers.ts @@ -121,4 +121,7 @@ export class Identifiers { static NgOnChangesFeature: o.ExternalReference = {name: 'ɵNgOnChangesFeature', moduleName: CORE}; static listener: o.ExternalReference = {name: 'ɵL', moduleName: CORE}; + + // Reserve slots for pure functions + static reserveSlots: o.ExternalReference = {name: 'ɵrS', moduleName: CORE}; } diff --git a/packages/compiler/src/render3/view/template.ts b/packages/compiler/src/render3/view/template.ts index 91e2f4b8b9..c6dbadfe54 100644 --- a/packages/compiler/src/render3/view/template.ts +++ b/packages/compiler/src/render3/view/template.ts @@ -57,6 +57,9 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver // Maps of placeholder to node indexes for each of the i18n section private _phToNodeIdxes: {[phName: string]: number[]}[] = [{}]; + // Number of slots to reserve for pureFunctions + private _pureFunctionSlots = 0; + constructor( private constantPool: ConstantPool, private contextParameter: string, parentBindingScope: BindingScope, private level = 0, private contextName: string|null, @@ -70,6 +73,7 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver }); this._valueConverter = new ValueConverter( constantPool, () => this.allocateDataSlot(), + (numSlots: number): number => this._pureFunctionSlots += numSlots, (name, localName, slot, value: o.ReadVarExpr) => { const pipeType = pipeTypeByName.get(name); if (pipeType) { @@ -139,6 +143,11 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver 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 ? [o.ifStmt( o.variable(RENDER_FLAGS).bitwiseAnd(o.literal(core.RenderFlags.Create), null, false), @@ -501,6 +510,7 @@ export class TemplateDefinitionBuilder implements t.Visitor, LocalResolver class ValueConverter extends AstMemoryEfficientTransformer { constructor( private constantPool: ConstantPool, private allocateSlot: () => number, + private allocatePureFunctionSlots: (numSlots: number) => number, private definePipe: (name: string, localName: string, slot: number, value: o.Expression) => void) { super(); @@ -511,14 +521,20 @@ class ValueConverter extends AstMemoryEfficientTransformer { // Allocate a slot to create the pipe const slot = this.allocateSlot(); 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 bindingId = pipeBinding(pipe.args); this.definePipe(pipe.name, slotPseudoLocal, slot, o.importExpr(bindingId)); const value = pipe.exp.visit(this); const args = this.visitAll(pipe.args); - return new FunctionCall( - pipe.span, target, [new LiteralPrimitive(pipe.span, slot), value, ...args]); + return new FunctionCall(pipe.span, target, [ + new LiteralPrimitive(pipe.span, slot), + new LiteralPrimitive(pipe.span, pureFunctionSlot), + value, + ...args, + ]); } 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 // values. Otherwise, just return an literal array that contains the values. const literal = o.literalArr(values); - return values.every(a => a.isConstant()) ? this.constantPool.getConstLiteral(literal, true) : - getLiteralFactory(this.constantPool, literal); + return values.every(a => a.isConstant()) ? + 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. const literal = o.literalMap(values.map( (value, index) => ({key: map.keys[index].key, value, quoted: map.keys[index].quoted}))); - return values.every(a => a.isConstant()) ? this.constantPool.getConstLiteral(literal, true) : - getLiteralFactory(this.constantPool, literal); + return values.every(a => a.isConstant()) ? + this.constantPool.getConstLiteral(literal, true) : + getLiteralFactory(this.constantPool, literal, this.allocatePureFunctionSlots); }); } } - - // Pipes always have at least one parameter, the value they operate on const pipeBindingIdentifiers = [R3.pipeBind1, R3.pipeBind2, R3.pipeBind3, R3.pipeBind4]; @@ -559,15 +575,20 @@ const pureFunctionIdentifiers = [ R3.pureFunction5, R3.pureFunction6, R3.pureFunction7, R3.pureFunction8 ]; 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); + // 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`); let pureFunctionIdent = pureFunctionIdentifiers[literalFactoryArguments.length] || R3.pureFunctionV; // Literal factories are pure functions that only need to be re-invoked when the parameters // change. - return o.importExpr(pureFunctionIdent).callFn([literalFactory, ...literalFactoryArguments]); + return o.importExpr(pureFunctionIdent).callFn([ + o.literal(startSlot), literalFactory, ...literalFactoryArguments + ]); } /** diff --git a/packages/compiler/test/render3/r3_compiler_compliance_spec.ts b/packages/compiler/test/render3/r3_compiler_compliance_spec.ts index 8dd56318ab..127de2a3b4 100644 --- a/packages/compiler/test/render3/r3_compiler_compliance_spec.ts +++ b/packages/compiler/test/render3/r3_compiler_compliance_spec.ts @@ -107,6 +107,57 @@ describe('compiler compliance', () => { 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: \`
\` + }) + 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', () => { const files = { app: { @@ -415,9 +466,10 @@ describe('compiler compliance', () => { if (rf & 1) { $r3$.ɵE(0, 'my-comp'); $r3$.ɵe(); + $r3$.ɵrS(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] @@ -494,11 +546,12 @@ describe('compiler compliance', () => { if (rf & 1) { $r3$.ɵE(0, 'my-comp'); $r3$.ɵe(); + $r3$.ɵrS(10); } if (rf & 2) { $r3$.ɵp( 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] @@ -555,9 +608,10 @@ describe('compiler compliance', () => { if (rf & 1) { $r3$.ɵE(0, 'object-comp'); $r3$.ɵe(); + $r3$.ɵrS(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] @@ -620,12 +674,12 @@ describe('compiler compliance', () => { if (rf & 1) { $r3$.ɵE(0, 'nested-comp'); $r3$.ɵe(); + $r3$.ɵrS(7); } if (rf & 2) { $r3$.ɵp( 0, 'config', - $r3$.ɵb($r3$.ɵf2( - $e0_ff_2$, ctx.name, $r3$.ɵf1($e0_ff_1$, $r3$.ɵf1($e0_ff$, ctx.duration))))); + $r3$.ɵb($r3$.ɵf2(7, $e0_ff_2$, ctx.name, $r3$.ɵf1(4, $e0_ff_1$, $r3$.ɵf1(2, $e0_ff$, ctx.duration))))); } }, directives: [NestedComp] @@ -912,10 +966,11 @@ describe('compiler compliance', () => { $r3$.ɵT(4); $r3$.ɵPp(5, 'myPurePipe'); $r3$.ɵe(); + $r3$.ɵrS(9); } if (rf & 2) { - $r3$.ɵt(0, $r3$.ɵi1('', $r3$.ɵpb2(1, $r3$.ɵpb2(2,ctx.name, ctx.size), ctx.size), '')); - $r3$.ɵt(4, $r3$.ɵi1('', $r3$.ɵpb2(5, ctx.name, 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, 9, ctx.name, ctx.size), '')); } }, pipes: [MyPurePipe, MyPipe] diff --git a/packages/core/src/core_render3_private_export.ts b/packages/core/src/core_render3_private_export.ts index 136b4fd3e4..ff8c7449b4 100644 --- a/packages/core/src/core_render3_private_export.ts +++ b/packages/core/src/core_render3_private_export.ts @@ -65,6 +65,7 @@ export { e as ɵe, p as ɵp, pD as ɵpD, + rS as ɵrS, a as ɵa, s as ɵs, sn as ɵsn, diff --git a/packages/core/src/render3/index.ts b/packages/core/src/render3/index.ts index ea2206fa10..421b421364 100644 --- a/packages/core/src/render3/index.ts +++ b/packages/core/src/render3/index.ts @@ -64,6 +64,8 @@ export { text as T, textBinding as t, + reserveSlots as rS, + embeddedViewStart as V, embeddedViewEnd as v, detectChanges, diff --git a/packages/core/src/render3/instructions.ts b/packages/core/src/render3/instructions.ts index 0a23c5b424..345e8730dd 100644 --- a/packages/core/src/render3/instructions.ts +++ b/packages/core/src/render3/instructions.ts @@ -1446,12 +1446,10 @@ function generateInitialInputs( return initialInputData; } - ////////////////////////// //// ViewContainer & View ////////////////////////// - export function createLContainer( parentLNode: LNode, currentView: LView, template?: ComponentTemplate): LContainer { ngDevMode && assertNotNull(parentLNode, 'containers should have a parent'); @@ -2146,6 +2144,57 @@ export function bind(value: T | NO_CHANGE): T|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. * @@ -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})`); } +/** + * 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(component: T): LElementNode { ngDevMode && assertNotNull(component, 'expecting component got null'); const lElementNode = (component as any)[NG_HOST_SYMBOL] as LElementNode; diff --git a/packages/core/src/render3/pipe.ts b/packages/core/src/render3/pipe.ts index c8f449148a..fd9031ca07 100644 --- a/packages/core/src/render3/pipe.ts +++ b/packages/core/src/render3/pipe.ts @@ -65,11 +65,12 @@ function getPipeDef(name: string, registry: PipeDefList | null): PipeDef { * the pipe only when an input to the pipe changes. * * @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}. */ -export function pipeBind1(index: number, v1: any): any { +export function pipeBind1(index: number, slotOffset: number, v1: any): any { const pipeInstance = load(index); - return isPure(index) ? pureFunction1(pipeInstance.transform, v1, pipeInstance) : + return isPure(index) ? pureFunction1(slotOffset, pipeInstance.transform, v1, pipeInstance) : 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. * * @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 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(index); - return isPure(index) ? pureFunction2(pipeInstance.transform, v1, v2, pipeInstance) : + return isPure(index) ? pureFunction2(slotOffset, pipeInstance.transform, v1, v2, pipeInstance) : pipeInstance.transform(v1, v2); } @@ -96,14 +98,16 @@ export function pipeBind2(index: number, v1: any, v2: any): any { * the pipe only when an input to the pipe changes. * * @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 v2 2nd 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(index); - return isPure(index) ? pureFunction3(pipeInstance.transform, v1, v2, v3, pipeInstance) : - pipeInstance.transform(v1, v2, v3); + return isPure(index) ? + pureFunction3(slotOffset, pipeInstance.transform, v1, v2, v3, pipeInstance) : + pipeInstance.transform(v1, v2, v3); } /** @@ -113,15 +117,18 @@ export function pipeBind3(index: number, v1: any, v2: any, v3: any): any { * the pipe only when an input to the pipe changes. * * @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 v2 2nd argument to {@link PipeTransform#transform}. * @param v3 3rd 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(index); - return isPure(index) ? pureFunction4(pipeInstance.transform, v1, v2, v3, v4, pipeInstance) : - pipeInstance.transform(v1, v2, v3, v4); + return isPure(index) ? + pureFunction4(slotOffset, pipeInstance.transform, v1, v2, v3, v4, pipeInstance) : + 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. * * @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. */ -export function pipeBindV(index: number, values: any[]): any { +export function pipeBindV(index: number, slotOffset: number, values: any[]): any { const pipeInstance = load(index); - return isPure(index) ? pureFunctionV(pipeInstance.transform, values, pipeInstance) : + return isPure(index) ? pureFunctionV(slotOffset, pipeInstance.transform, values, pipeInstance) : pipeInstance.transform.apply(pipeInstance, values); } diff --git a/packages/core/src/render3/pure_function.ts b/packages/core/src/render3/pure_function.ts index dad4424647..ecf5be9666 100644 --- a/packages/core/src/render3/pure_function.ts +++ b/packages/core/src/render3/pure_function.ts @@ -6,7 +6,7 @@ * 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. * * @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 * @returns value */ -export function pureFunction0(pureFn: () => T, thisArg?: any): T { - return getCreationMode() ? checkAndUpdateBinding(thisArg ? pureFn.call(thisArg) : pureFn()) : - consumeBinding(); +export function pureFunction0(slotOffset: number, pureFn: () => T, thisArg?: any): T { + ngDevMode && assertReservedSlotInitialized(slotOffset, 1); + const index = moveBindingIndexToReservedSlot(slotOffset); + const value = getCreationMode() ? + checkAndUpdateBinding(thisArg ? pureFn.call(thisArg) : pureFn()) : + consumeBinding(); + restoreBindingIndex(index); + return value; } /** * If the value of the provided exp has changed, calls the pure function to return * an updated value. Or if the value has not changed, returns cached value. * + * @param slotOffset the offset in the reserved slot space {@link reserveSlots} * @param pureFn Function that returns an updated value * @param exp Updated expression value * @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 { - return bindingUpdated(exp) ? +export function pureFunction1( + 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)) : consumeBinding(); + restoreBindingIndex(index); + return value; } /** * 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 slotOffset the offset in the reserved slot space {@link reserveSlots} * @param pureFn * @param exp1 * @param exp2 * @param thisArg Optional calling context of pureFn - * @returns Updated value + * @returns Updated or cached value */ export function pureFunction2( - pureFn: (v1: any, v2: any) => any, exp1: any, exp2: any, thisArg?: any): any { - return bindingUpdated2(exp1, exp2) ? + slotOffset: number, pureFn: (v1: any, v2: any) => any, exp1: any, exp2: any, + 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)) : consumeBinding(); + restoreBindingIndex(index); + return value; } /** * 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 slotOffset the offset in the reserved slot space {@link reserveSlots} * @param pureFn * @param exp1 * @param exp2 * @param exp3 * @param thisArg Optional calling context of pureFn - * @returns Updated value + * @returns Updated or cached value */ 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 { + ngDevMode && assertReservedSlotInitialized(slotOffset, 4); + const index = moveBindingIndexToReservedSlot(slotOffset); const different = bindingUpdated2(exp1, exp2); - return bindingUpdated(exp3) || different ? + const value = bindingUpdated(exp3) || different ? checkAndUpdateBinding( thisArg ? pureFn.call(thisArg, exp1, exp2, exp3) : pureFn(exp1, exp2, exp3)) : consumeBinding(); + restoreBindingIndex(index); + return value; } /** * 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 slotOffset the offset in the reserved slot space {@link reserveSlots} * @param pureFn * @param exp1 * @param exp2 * @param exp3 * @param exp4 * @param thisArg Optional calling context of pureFn - * @returns Updated value + * @returns Updated or cached value */ export function pureFunction4( - pureFn: (v1: any, v2: any, v3: any, v4: any) => any, exp1: any, exp2: any, exp3: any, exp4: any, - thisArg?: any): any { - return bindingUpdated4(exp1, exp2, exp3, exp4) ? + slotOffset: number, pureFn: (v1: any, v2: any, v3: any, v4: any) => any, exp1: any, exp2: any, + exp3: any, exp4: any, thisArg?: any): any { + ngDevMode && assertReservedSlotInitialized(slotOffset, 5); + const index = moveBindingIndexToReservedSlot(slotOffset); + const value = bindingUpdated4(exp1, exp2, exp3, exp4) ? checkAndUpdateBinding( thisArg ? pureFn.call(thisArg, exp1, exp2, exp3, exp4) : pureFn(exp1, exp2, exp3, exp4)) : consumeBinding(); + restoreBindingIndex(index); + return value; } /** * 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 slotOffset the offset in the reserved slot space {@link reserveSlots} * @param pureFn * @param exp1 * @param exp2 @@ -108,23 +137,28 @@ export function pureFunction4( * @param exp4 * @param exp5 * @param thisArg Optional calling context of pureFn - * @returns Updated value + * @returns Updated or cached value */ export function pureFunction5( - pureFn: (v1: any, v2: any, v3: any, v4: any, v5: any) => any, exp1: any, exp2: any, exp3: any, - exp4: any, exp5: any, thisArg?: any): any { + slotOffset: number, pureFn: (v1: any, v2: any, v3: any, v4: any, v5: any) => any, exp1: 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); - return bindingUpdated(exp5) || different ? + const value = bindingUpdated(exp5) || different ? checkAndUpdateBinding( thisArg ? pureFn.call(thisArg, exp1, exp2, exp3, exp4, exp5) : pureFn(exp1, exp2, exp3, exp4, exp5)) : consumeBinding(); + restoreBindingIndex(index); + return value; } /** * 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 slotOffset the offset in the reserved slot space {@link reserveSlots} * @param pureFn * @param exp1 * @param exp2 @@ -133,23 +167,28 @@ export function pureFunction5( * @param exp5 * @param exp6 * @param thisArg Optional calling context of pureFn - * @returns Updated value + * @returns Updated or cached value */ export function pureFunction6( - pureFn: (v1: any, v2: any, v3: any, v4: any, v5: any, v6: any) => any, exp1: any, exp2: any, - exp3: any, exp4: any, exp5: any, exp6: any, thisArg?: any): any { + slotOffset: number, 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, thisArg?: any): any { + ngDevMode && assertReservedSlotInitialized(slotOffset, 7); + const index = moveBindingIndexToReservedSlot(slotOffset); const different = bindingUpdated4(exp1, exp2, exp3, exp4); - return bindingUpdated2(exp5, exp6) || different ? + const value = bindingUpdated2(exp5, exp6) || different ? checkAndUpdateBinding( thisArg ? pureFn.call(thisArg, exp1, exp2, exp3, exp4, exp5, exp6) : pureFn(exp1, exp2, exp3, exp4, exp5, exp6)) : consumeBinding(); + restoreBindingIndex(index); + return value; } /** * 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 slotOffset the offset in the reserved slot space {@link reserveSlots} * @param pureFn * @param exp1 * @param exp2 @@ -159,24 +198,30 @@ export function pureFunction6( * @param exp6 * @param exp7 * @param thisArg Optional calling context of pureFn - * @returns Updated value + * @returns Updated or cached value */ export function pureFunction7( + slotOffset: number, 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 { + ngDevMode && assertReservedSlotInitialized(slotOffset, 8); + const index = moveBindingIndexToReservedSlot(slotOffset); let different = bindingUpdated4(exp1, exp2, exp3, exp4); different = bindingUpdated2(exp5, exp6) || different; - return bindingUpdated(exp7) || different ? + const value = bindingUpdated(exp7) || different ? checkAndUpdateBinding( thisArg ? pureFn.call(thisArg, exp1, exp2, exp3, exp4, exp5, exp6, exp7) : pureFn(exp1, exp2, exp3, exp4, exp5, exp6, exp7)) : consumeBinding(); + restoreBindingIndex(index); + return value; } /** * 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 slotOffset the offset in the reserved slot space {@link reserveSlots} * @param pureFn * @param exp1 * @param exp2 @@ -187,18 +232,23 @@ export function pureFunction7( * @param exp7 * @param exp8 * @param thisArg Optional calling context of pureFn - * @returns Updated value + * @returns Updated or cached value */ export function pureFunction8( + slotOffset: number, 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, thisArg?: any): any { + ngDevMode && assertReservedSlotInitialized(slotOffset, 9); + const index = moveBindingIndexToReservedSlot(slotOffset); const different = bindingUpdated4(exp1, exp2, exp3, exp4); - return bindingUpdated4(exp5, exp6, exp7, exp8) || different ? + const value = bindingUpdated4(exp5, exp6, exp7, exp8) || different ? checkAndUpdateBinding( thisArg ? pureFn.call(thisArg, exp1, exp2, exp3, exp4, exp5, exp6, exp7, exp8) : pureFn(exp1, exp2, exp3, exp4, exp5, exp6, exp7, exp8)) : 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 * 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 * containing those values. * @param exps An array of binding values * @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 { - let different = false; +export function pureFunctionV( + 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++) { 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; } diff --git a/packages/core/test/render3/compiler_canonical/component_directives_spec.ts b/packages/core/test/render3/compiler_canonical/component_directives_spec.ts index 65a49ecec9..7c1e6a7845 100644 --- a/packages/core/test/render3/compiler_canonical/component_directives_spec.ts +++ b/packages/core/test/render3/compiler_canonical/component_directives_spec.ts @@ -502,9 +502,10 @@ describe('components & directives', () => { if (rf & 1) { $r3$.ɵE(0, 'my-array-comp'); $r3$.ɵe(); + $r3$.ɵrS(1); } 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) { $r3$.ɵE(0, 'my-comp'); $r3$.ɵe(); + $r3$.ɵrS(1); } 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) { $r3$.ɵE(0, 'my-array-comp'); $r3$.ɵe(); + $r3$.ɵrS(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) { $r3$.ɵE(0, 'my-comp'); $r3$.ɵe(); + $r3$.ɵrS(10); } if (rf & 2) { $r3$.ɵp( 0, 'names', - $r3$.ɵb( - $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( + 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) { $r3$.ɵE(0, 'object-comp'); $r3$.ɵe(); + $r3$.ɵrS(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) { $r3$.ɵE(0, 'nested-comp'); $r3$.ɵe(); + $r3$.ɵrS(7); } if (rf & 2) { $r3$.ɵp( - 0, 'config', $r3$.ɵf2( - $e0_ff_2$, ctx.name, - $r3$.ɵb($r3$.ɵf1($e0_ff_1$, $r3$.ɵf1($e0_ff$, ctx.duration))))); + 0, 'config', $r3$.ɵb($r3$.ɵf2( + 7, $e0_ff_2$, ctx.name, + $r3$.ɵf1(4, $e0_ff_1$, $r3$.ɵf1(2, $e0_ff$, ctx.duration))))); } } }); diff --git a/packages/core/test/render3/compiler_canonical/pipes_spec.ts b/packages/core/test/render3/compiler_canonical/pipes_spec.ts index 625b4e2c32..ea6316fdac 100644 --- a/packages/core/test/render3/compiler_canonical/pipes_spec.ts +++ b/packages/core/test/render3/compiler_canonical/pipes_spec.ts @@ -88,9 +88,12 @@ describe('pipes', () => { $r3$.ɵT(0); $r3$.ɵPp(1, 'myPipe'); $r3$.ɵPp(2, 'myPurePipe'); + $r3$.ɵrS(6); } 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$.ɵPp(3, 'myPurePipe'); $r3$.ɵC(4, C4, '', ['oneTimeIf', '']); + $r3$.ɵrS(6); } if (rf & 2) { - $r3$.ɵt(0, $r3$.ɵi1('', $r3$.ɵpb2(1, ctx.name, ctx.size), '')); - $r3$.ɵt(2, $r3$.ɵi1('', $r3$.ɵpb2(3, 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, 6, ctx.name, ctx.size), '')); $r3$.ɵp(4, 'oneTimeIf', $r3$.ɵb(ctx.more)); $r3$.ɵcR(4); $r3$.ɵcr(); @@ -181,9 +185,10 @@ describe('pipes', () => { $r3$.ɵT(1); $r3$.ɵPp(2, 'myPurePipe'); $r3$.ɵe(); + $r3$.ɵrS(3); } 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), '')); } } } diff --git a/packages/core/test/render3/pipe_spec.ts b/packages/core/test/render3/pipe_spec.ts index 2a49d26b4e..e5da9bbcbe 100644 --- a/packages/core/test/render3/pipe_spec.ts +++ b/packages/core/test/render3/pipe_spec.ts @@ -10,7 +10,7 @@ import {Directive, OnChanges, OnDestroy, Pipe, PipeTransform} from '@angular/cor import {expect} from '@angular/platform-browser/testing/src/matchers'; 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 {pipe, pipeBind1, pipeBind3, pipeBind4, pipeBindV} from '../../src/render3/pipe'; @@ -38,9 +38,10 @@ describe('pipe', () => { if (rf & RenderFlags.Create) { text(0); pipe(1, 'countingPipe'); + reserveSlots(2); } 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) { text(0); pipe(1, 'randomPipeName'); + reserveSlots(2); } if (rf & RenderFlags.Update) { - textBinding(0, interpolation1('', pipeBind1(1, ctx.value), '')); + textBinding(0, interpolation1('', pipeBind1(1, 2, ctx.value), '')); } }, [], pipes); @@ -97,9 +99,10 @@ describe('pipe', () => { elementStart(0, 'div', ['myDir', '']); pipe(1, 'double'); elementEnd(); + reserveSlots(2); } if (rf & RenderFlags.Update) { - elementProperty(0, 'elprop', bind(pipeBind1(1, ctx))); + elementProperty(0, 'elprop', bind(pipeBind1(1, 2, ctx))); directive = loadDirective(0); } } @@ -112,10 +115,11 @@ describe('pipe', () => { if (rf & RenderFlags.Create) { text(0); pipe(1, 'multiArgPipe'); + reserveSlots(4); } if (rf & RenderFlags.Update) { 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); pipe(1, 'multiArgPipe'); pipe(2, 'multiArgPipe'); + reserveSlots(9); } if (rf & RenderFlags.Update) { textBinding( - 0, - interpolation1('', pipeBind4(2, pipeBindV(1, [person.name, 'a', 'b']), 0, 1, 2), '')); + 0, interpolation1( + '', pipeBind4(2, 9, pipeBindV(1, 4, [person.name, 'a', 'b']), 0, 1, 2), '')); } } @@ -158,9 +163,10 @@ describe('pipe', () => { elementStart(0, 'div'); pipe(1, 'identityPipe'); elementEnd(); + reserveSlots(2); } 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) { text(0); pipe(1, 'countingPipe'); + reserveSlots(2); } 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) { text(0); pipe(1, 'countingImpurePipe'); + reserveSlots(2); } 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'); elementEnd(); container(4); + reserveSlots(4); } if (rf & RenderFlags.Update) { - elementProperty(0, 'someProp', bind(pipeBind1(1, true))); - elementProperty(2, 'someProp', bind(pipeBind1(3, true))); + elementProperty(0, 'someProp', bind(pipeBind1(1, 2, true))); + elementProperty(2, 'someProp', bind(pipeBind1(3, 4, true))); pipeInstances.push(load(1), load(3)); containerRefreshStart(4); { @@ -242,9 +251,10 @@ describe('pipe', () => { elementStart(0, 'div'); pipe(1, 'countingImpurePipe'); elementEnd(); + reserveSlots(2); } if (rf1 & RenderFlags.Update) { - elementProperty(0, 'someProp', bind(pipeBind1(1, true))); + elementProperty(0, 'someProp', bind(pipeBind1(1, 2, true))); pipeInstances.push(load(1)); } } @@ -296,9 +306,10 @@ describe('pipe', () => { if (rf1 & RenderFlags.Create) { text(0); pipe(1, 'pipeWithOnDestroy'); + reserveSlots(2); } if (rf & RenderFlags.Update) { - textBinding(0, interpolation1('', pipeBind1(1, person.age), '')); + textBinding(0, interpolation1('', pipeBind1(1, 2, person.age), '')); } } embeddedViewEnd(); diff --git a/packages/core/test/render3/pure_function_spec.ts b/packages/core/test/render3/pure_function_spec.ts index 4cb71ae428..c3290895f1 100644 --- a/packages/core/test/render3/pure_function_spec.ts +++ b/packages/core/test/render3/pure_function_spec.ts @@ -6,7 +6,7 @@ * found in the LICENSE file at https://angular.io/license */ import {defineComponent} from '../../src/render3/index'; -import {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 {pureFunction1, pureFunction2, pureFunction3, pureFunction4, pureFunction5, pureFunction6, pureFunction7, pureFunction8, pureFunctionV} from '../../src/render3/pure_function'; import {renderToHtml} from '../../test/render3/render_util'; @@ -36,9 +36,10 @@ describe('array literals', () => { if (rf & RenderFlags.Create) { elementStart(0, 'my-comp'); elementEnd(); + reserveSlots(2); } 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) { elementStart(0, 'many-prop-comp'); elementEnd(); + reserveSlots(4); } if (rf & RenderFlags.Update) { - elementProperty(0, 'names1', bind(pureFunction1(e0_ff, ctx.customName))); - elementProperty(0, 'names2', bind(pureFunction1(e0_ff_1, ctx.customName2))); + elementProperty(0, 'names1', bind(pureFunction1(2, e0_ff, ctx.customName))); + elementProperty(0, 'names2', bind(pureFunction1(4, e0_ff_1, ctx.customName2))); } } @@ -130,9 +132,10 @@ describe('array literals', () => { elementStart(0, 'my-comp'); myComps.push(loadDirective(0)); elementEnd(); + reserveSlots(2); } 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 @@ -170,9 +173,10 @@ describe('array literals', () => { if (rf & RenderFlags.Create) { elementStart(0, 'my-comp'); elementEnd(); + reserveSlots(3); } 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'); f8Comp = loadDirective(5); elementEnd(); + reserveSlots(39); } if (rf & RenderFlags.Update) { - elementProperty(0, 'names', bind(pureFunction3(e0_ff, c[5], c[6], c[7]))); - elementProperty(1, 'names', bind(pureFunction4(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(3, 'names', bind(pureFunction6(e6_ff, c[2], c[3], c[4], 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(9, e2_ff, 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( - 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( 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) { elementStart(0, 'my-comp'); elementEnd(); + reserveSlots(12); } if (rf & RenderFlags.Update) { 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] + 0, 'names', bind(pureFunctionV(12, e0_ff, [ + 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) { elementStart(0, 'object-comp'); elementEnd(); + reserveSlots(2); } 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) { elementStart(0, 'object-comp'); elementEnd(); + reserveSlots(7); } if (rf & RenderFlags.Update) { elementProperty( - 0, 'config', - bind(pureFunction2( - e0_ff, ctx.name, pureFunction1(e0_ff_1, pureFunction1(e0_ff_2, ctx.duration))))); + 0, 'config', bind(pureFunction2( + 7, e0_ff, ctx.name, + pureFunction1(4, e0_ff_1, pureFunction1(2, e0_ff_2, ctx.duration))))); } } @@ -451,11 +460,12 @@ describe('object literals', () => { elementStart(0, 'object-comp'); objectComps.push(loadDirective(0)); elementEnd(); + reserveSlots(3); } if (rf1 & RenderFlags.Update) { elementProperty( 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(); } diff --git a/packages/core/test/render3/view_container_ref_spec.ts b/packages/core/test/render3/view_container_ref_spec.ts index 3bb2328b21..8dc809fa6e 100644 --- a/packages/core/test/render3/view_container_ref_spec.ts +++ b/packages/core/test/render3/view_container_ref_spec.ts @@ -9,7 +9,7 @@ import {Component, Directive, Pipe, PipeTransform, TemplateRef, ViewContainerRef} from '../../src/core'; import {getOrCreateNodeInjectorForNode, getOrCreateTemplateRef} from '../../src/render3/di'; 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 {pipe, pipeBind1} from '../../src/render3/pipe'; @@ -383,22 +383,25 @@ describe('ViewContainerRef', () => { elementStart(0, 'child'); elementEnd(); pipe(1, 'starPipe'); + reserveSlots(2); } - if (rf & RenderFlags.Create) { - elementProperty(0, 'name', bind(pipeBind1(1, 'C'))); + if (rf & RenderFlags.Update) { + elementProperty(0, 'name', bind(pipeBind1(1, 2, 'C'))); } }); pipe(1, 'starPipe'); elementStart(2, 'child', ['vcref', '']); elementEnd(); - elementStart(3, 'child'); + pipe(3, 'starPipe'); + elementStart(4, 'child'); elementEnd(); + reserveSlots(4); } if (rf & RenderFlags.Update) { const tplRef = getOrCreateTemplateRef(getOrCreateNodeInjectorForNode(load(0))); elementProperty(2, 'tplRef', bind(tplRef)); - elementProperty(2, 'name', bind(pipeBind1(1, 'A'))); - elementProperty(3, 'name', bind(pipeBind1(1, 'B'))); + elementProperty(2, 'name', bind(pipeBind1(1, 2, 'A'))); + elementProperty(4, 'name', bind(pipeBind1(1, 4, 'B'))); } }, directives: [Child, DirectiveWithVCRef],