Skip to content

Commit

Permalink
refactor(compiler): support property interpolation in the template pi…
Browse files Browse the repository at this point in the history
…peline (angular#50118)

This commit adds support for interpolated properties to the template
pipeline.

PR Close angular#50118
  • Loading branch information
alxhub authored and dylhunn committed Jun 1, 2023
1 parent 6984431 commit 9847085
Show file tree
Hide file tree
Showing 7 changed files with 138 additions and 10 deletions.
5 changes: 5 additions & 0 deletions packages/compiler/src/template/pipeline/ir/src/enums.ts
Expand Up @@ -83,6 +83,11 @@ export enum OpKind {
*/
Property,

/**
* An operation to interpolate text into a property binding.
*/
InterpolateProperty,

/**
* An operation to advance the runtime's implicit slot context during the update phase of a view.
*/
Expand Down
5 changes: 5 additions & 0 deletions packages/compiler/src/template/pipeline/ir/src/expression.ts
Expand Up @@ -459,6 +459,11 @@ export function transformExpressionsInOp(
case OpKind.Property:
op.expression = transformExpressionsInExpression(op.expression, transform, flags);
break;
case OpKind.InterpolateProperty:
for (let i = 0; i < op.expressions.length; i++) {
op.expressions[i] = transformExpressionsInExpression(op.expressions[i], transform, flags);
}
break;
case OpKind.Statement:
transformExpressionsInStatement(op.statement, transform, flags);
break;
Expand Down
58 changes: 56 additions & 2 deletions packages/compiler/src/template/pipeline/ir/src/ops/update.ts
Expand Up @@ -16,8 +16,8 @@ import {ListEndOp, NEW_OP, StatementOp, VariableOp} from './shared';
/**
* An operation usable on the update side of the IR.
*/
export type UpdateOp = ListEndOp<UpdateOp>|StatementOp<UpdateOp>|PropertyOp|InterpolateTextOp|
AdvanceOp|VariableOp<UpdateOp>;
export type UpdateOp = ListEndOp<UpdateOp>|StatementOp<UpdateOp>|PropertyOp|InterpolatePropertyOp|
InterpolateTextOp|AdvanceOp|VariableOp<UpdateOp>;

/**
* A logical operation to perform string interpolation on a text node.
Expand Down Expand Up @@ -102,6 +102,60 @@ export function createPropertyOp(xref: XrefId, name: string, expression: o.Expre
};
}



/**
* A logical operation representing binding an interpolation to a property in the update IR.
*/
export interface InterpolatePropertyOp extends Op<UpdateOp>, ConsumesVarsTrait,
DependsOnSlotContextOpTrait {
kind: OpKind.InterpolateProperty;

/**
* Reference to the element on which the property is bound.
*/
target: XrefId;

/**
* Name of the bound property.
*/
name: string;

/**
* All of the literal strings in the property interpolation, in order.
*
* Conceptually interwoven around the `expressions`.
*/
strings: string[];

/**
* All of the dynamic expressions in the property interpolation, in order.
*
* Conceptually interwoven in between the `strings`.
*/
expressions: o.Expression[];
}

/**
* Create a `InterpolateProperty`.
*/
export function createInterpolatePropertyOp(
xref: XrefId, name: string, strings: string[],
expressions: o.Expression[]): InterpolatePropertyOp {
return {
kind: OpKind.InterpolateProperty,
target: xref,
name,
strings,
expressions,
...TRAIT_DEPENDS_ON_SLOT_CONTEXT,
...TRAIT_CONSUMES_VARS,
...NEW_OP,
};
}



/**
* Logical operation to advance the runtime's internal slot pointer in the update IR.
*/
Expand Down
25 changes: 17 additions & 8 deletions packages/compiler/src/template/pipeline/src/ingest.ts
Expand Up @@ -214,19 +214,15 @@ function ingestAttributes(op: ir.ElementOpBase, element: t.Element|t.Template):
function ingestBindings(
view: ViewCompilation, op: ir.ElementOpBase, element: t.Element|t.Template): void {
if (element instanceof t.Template) {
// TODO: Are ng-template inputs handled differently from element inputs?
// <ng-template dir [foo]="...">
// <item-cmp *ngFor="let item of items" [item]="item">
for (const input of [...element.templateAttrs, ...element.inputs]) {
if (!(input instanceof t.BoundAttribute)) {
for (const attr of [...element.templateAttrs, ...element.inputs]) {
if (!(attr instanceof t.BoundAttribute)) {
continue;
}

view.update.push(ir.createPropertyOp(op.xref, input.name, convertAst(input.value, view.tpl)));
ingestPropertyBinding(view, op.xref, attr.name, attr.value);
}
} else {
for (const input of element.inputs) {
view.update.push(ir.createPropertyOp(op.xref, input.name, convertAst(input.value, view.tpl)));
ingestPropertyBinding(view, op.xref, input.name, input.value);
}

for (const output of element.outputs) {
Expand Down Expand Up @@ -262,6 +258,19 @@ function ingestBindings(
}
}

function ingestPropertyBinding(
view: ViewCompilation, xref: ir.XrefId, name: string, value: e.AST): void {
if (value instanceof e.ASTWithSource) {
value = value.ast;
}
if (value instanceof e.Interpolation) {
view.update.push(ir.createInterpolatePropertyOp(
xref, name, value.strings, value.expressions.map(expr => convertAst(expr, view.tpl))));
} else {
view.update.push(ir.createPropertyOp(xref, name, convertAst(value, view.tpl)));
}
}

/**
* Process all of the local references on an element-like structure in the template AST and convert
* them to their IR representation.
Expand Down
48 changes: 48 additions & 0 deletions packages/compiler/src/template/pipeline/src/instruction.ts
Expand Up @@ -189,6 +189,29 @@ export function textInterpolate(strings: string[], expressions: o.Expression[]):
return callVariadicInstruction(TEXT_INTERPOLATE_CONFIG, [], interpolationArgs);
}


export function propertyInterpolate(
name: string, strings: string[], expressions: o.Expression[]): ir.UpdateOp {
if (strings.length < 1 || expressions.length !== strings.length - 1) {
throw new Error(
`AssertionError: expected specific shape of args for strings/expressions in interpolation`);
}
const interpolationArgs: o.Expression[] = [];

if (expressions.length === 1 && strings[0] === '' && strings[1] === '') {
interpolationArgs.push(expressions[0]);
} else {
let idx: number;
for (idx = 0; idx < expressions.length; idx++) {
interpolationArgs.push(o.literal(strings[idx]), expressions[idx]);
}
// idx points at the last string.
interpolationArgs.push(o.literal(strings[idx]));
}

return callVariadicInstruction(PROPERTY_INTERPOLATE_CONFIG, [o.literal(name)], interpolationArgs);
}

export function pureFunction(
varOffset: number, fn: o.Expression, args: o.Expression[]): o.Expression {
return callVariadicInstructionExpr(
Expand Down Expand Up @@ -240,6 +263,31 @@ const TEXT_INTERPOLATE_CONFIG: VariadicInstructionConfig = {
},
};


/**
* `InterpolationConfig` for the `propertyInterpolate` instruction.
*/
const PROPERTY_INTERPOLATE_CONFIG: VariadicInstructionConfig = {
constant: [
Identifiers.propertyInterpolate,
Identifiers.propertyInterpolate1,
Identifiers.propertyInterpolate2,
Identifiers.propertyInterpolate3,
Identifiers.propertyInterpolate4,
Identifiers.propertyInterpolate5,
Identifiers.propertyInterpolate6,
Identifiers.propertyInterpolate7,
Identifiers.propertyInterpolate8,
],
variable: Identifiers.propertyInterpolateV,
mapping: n => {
if (n % 2 === 0) {
throw new Error(`Expected odd number of arguments`);
}
return (n - 1) / 2;
},
};

const PURE_FUNCTION_CONFIG: VariadicInstructionConfig = {
constant: [
Identifiers.pureFunction0,
Expand Down
3 changes: 3 additions & 0 deletions packages/compiler/src/template/pipeline/src/phases/reify.ts
Expand Up @@ -121,6 +121,9 @@ function reifyUpdateOperations(_view: ViewCompilation, ops: ir.OpList<ir.UpdateO
case ir.OpKind.Property:
ir.OpList.replace(op, ng.property(op.name, op.expression));
break;
case ir.OpKind.InterpolateProperty:
ir.OpList.replace(op, ng.propertyInterpolate(op.name, op.strings, op.expressions));
break;
case ir.OpKind.InterpolateText:
ir.OpList.replace(op, ng.textInterpolate(op.strings, op.expressions));
break;
Expand Down
Expand Up @@ -67,6 +67,10 @@ function varsUsedByOp(op: (ir.CreateOp|ir.UpdateOp)&ir.ConsumesVarsTrait): numbe
case ir.OpKind.InterpolateText:
// `ir.InterpolateTextOp`s use a variable slot for each dynamic expression.
return op.expressions.length;
case ir.OpKind.InterpolateProperty:
// `ir.InterpolatePropertyOp`s use a variable slot for each dynamic expression, plus one for
// the result.
return 1 + op.expressions.length;
default:
throw new Error(`Unhandled op: ${ir.OpKind[op.kind]}`);
}
Expand Down

0 comments on commit 9847085

Please sign in to comment.