Skip to content

Commit

Permalink
refactor(compiler): Implement switch blocks in template pipeline (a…
Browse files Browse the repository at this point in the history
…ngular#51518)

`switch` blocks are part of the new control flow syntax. This commit adds support for processing them, and emitting the appropriate templates and conditional instructions.

PR Close angular#51518
  • Loading branch information
dylhunn authored and thePunderWoman committed Aug 29, 2023
1 parent f7364ec commit ab0f9ee
Show file tree
Hide file tree
Showing 10 changed files with 299 additions and 88 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
{
"description": "should generate a basic switch block",
"angularCompilerOptions": {
"_enabledBlockTypes": ["switch"]
"_enabledBlockTypes": [
"switch"
]
},
"inputFiles": [
"basic_switch.ts"
Expand All @@ -19,13 +21,14 @@
],
"failureMessage": "Incorrect template"
}
],
"skipForTemplatePipeline": true
]
},
{
"description": "should generate a switch block without a default block",
"angularCompilerOptions": {
"_enabledBlockTypes": ["switch"]
"_enabledBlockTypes": [
"switch"
]
},
"inputFiles": [
"switch_without_default.ts"
Expand All @@ -46,7 +49,9 @@
{
"description": "should generate nested switch blocks",
"angularCompilerOptions": {
"_enabledBlockTypes": ["switch"]
"_enabledBlockTypes": [
"switch"
]
},
"inputFiles": [
"nested_switch.ts"
Expand All @@ -67,7 +72,9 @@
{
"description": "should generate switch block with a pipe in its expression",
"angularCompilerOptions": {
"_enabledBlockTypes": ["switch"]
"_enabledBlockTypes": [
"switch"
]
},
"inputFiles": [
"switch_with_pipe.ts"
Expand All @@ -88,7 +95,9 @@
{
"description": "should generate a basic if block",
"angularCompilerOptions": {
"_enabledBlockTypes": ["if"]
"_enabledBlockTypes": [
"if"
]
},
"inputFiles": [
"basic_if.ts"
Expand All @@ -109,7 +118,9 @@
{
"description": "should generate a basic if/else block",
"angularCompilerOptions": {
"_enabledBlockTypes": ["if"]
"_enabledBlockTypes": [
"if"
]
},
"inputFiles": [
"basic_if_else.ts"
Expand All @@ -130,7 +141,9 @@
{
"description": "should generate a basic if/else if block",
"angularCompilerOptions": {
"_enabledBlockTypes": ["if"]
"_enabledBlockTypes": [
"if"
]
},
"inputFiles": [
"basic_if_else_if.ts"
Expand All @@ -151,7 +164,9 @@
{
"description": "should generate a nested if block",
"angularCompilerOptions": {
"_enabledBlockTypes": ["if"]
"_enabledBlockTypes": [
"if"
]
},
"inputFiles": [
"nested_if.ts"
Expand All @@ -172,7 +187,9 @@
{
"description": "should generate an if block using pipes in its conditions",
"angularCompilerOptions": {
"_enabledBlockTypes": ["if"]
"_enabledBlockTypes": [
"if"
]
},
"inputFiles": [
"if_with_pipe.ts"
Expand All @@ -193,7 +210,9 @@
{
"description": "should generate an if block with an aliased expression",
"angularCompilerOptions": {
"_enabledBlockTypes": ["if"]
"_enabledBlockTypes": [
"if"
]
},
"inputFiles": [
"if_with_alias.ts"
Expand All @@ -214,7 +233,9 @@
{
"description": "should expose the alias to nested conditional blocks",
"angularCompilerOptions": {
"_enabledBlockTypes": ["if"]
"_enabledBlockTypes": [
"if"
]
},
"inputFiles": [
"if_nested_alias.ts"
Expand All @@ -235,7 +256,9 @@
{
"description": "should expose the alias to nested event listeners",
"angularCompilerOptions": {
"_enabledBlockTypes": ["if"]
"_enabledBlockTypes": [
"if"
]
},
"inputFiles": [
"if_nested_alias_listeners.ts"
Expand All @@ -254,4 +277,4 @@
"skipForTemplatePipeline": true
}
]
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ function MyApp_Template(rf, ctx) {
$r3$.ɵɵelementEnd();
}
if (rf & 2) {
let MyApp_contFlowTmp;
let $MyApp_contFlowTmp$;
$r3$.ɵɵadvance(1);
$r3$.ɵɵtextInterpolate1(" ", ctx.message, " ");
$r3$.ɵɵadvance(1);
$r3$.ɵɵconditional(2, (MyApp_contFlowTmp = ctx.value()) === 0 ? 2 : MyApp_contFlowTmp === 1 ? 3 : MyApp_contFlowTmp === 2 ? 4 : 5);
$r3$.ɵɵconditional(2, ($MyApp_contFlowTmp$ = ctx.value()) === 0 ? 2 : $MyApp_contFlowTmp$ === 1 ? 3 : $MyApp_contFlowTmp$ === 2 ? 4 : 5);
}
}
10 changes: 10 additions & 0 deletions packages/compiler/src/template/pipeline/ir/src/enums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@ export enum OpKind {
*/
DisableBindings,

/**
* An op to conditionally render a template.
*/
Conditional,

/**
* An operation to re-enable binding, after it was previously disabled.
*/
Expand Down Expand Up @@ -276,6 +281,11 @@ export enum ExpressionKind {
* An expression representing a sanitizer function.
*/
SanitizerExpr,

/**
* An expression that will cause a literal slot index to be emitted.
*/
SlotLiteralExpr,
}

/**
Expand Down
43 changes: 41 additions & 2 deletions packages/compiler/src/template/pipeline/ir/src/expression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import * as o from '../../../../output/output_ast';
import type {ParseSourceSpan} from '../../../../parse_util';

import {ExpressionKind, OpKind, SanitizerFn} from './enums';
import {ConsumesVarsTrait, UsesSlotIndex, UsesSlotIndexTrait, UsesVarOffset, UsesVarOffsetTrait} from './traits';
import {ConsumesVarsTrait, TRAIT_USES_SLOT_INDEX, UsesSlotIndex, UsesSlotIndexTrait, UsesVarOffset, UsesVarOffsetTrait} from './traits';

import type {XrefId} from './operations';
import type {CreateOp} from './ops/create';
Expand All @@ -23,7 +23,7 @@ export type Expression =
LexicalReadExpr|ReferenceExpr|ContextExpr|NextContextExpr|GetCurrentViewExpr|RestoreViewExpr|
ResetViewExpr|ReadVariableExpr|PureFunctionExpr|PureFunctionParameterExpr|PipeBindingExpr|
PipeBindingVariadicExpr|SafePropertyReadExpr|SafeKeyedReadExpr|SafeInvokeFunctionExpr|EmptyExpr|
AssignTemporaryExpr|ReadTemporaryExpr|SanitizerExpr;
AssignTemporaryExpr|ReadTemporaryExpr|SanitizerExpr|SlotLiteralExpr;

/**
* Transformer type which converts expressions into general `o.Expression`s (which may be an
Expand Down Expand Up @@ -734,6 +734,33 @@ export class SanitizerExpr extends ExpressionBase {
override transformInternalExpressions(): void {}
}

export class SlotLiteralExpr extends ExpressionBase {
override readonly kind = ExpressionKind.SlotLiteralExpr;
readonly[UsesSlotIndex] = true;

constructor(readonly target: XrefId) {
super();
}

slot: number|null = null;

override visitExpression(visitor: o.ExpressionVisitor, context: any): any {}

override isEquivalent(e: Expression): boolean {
return e instanceof SlotLiteralExpr && e.target === this.target && e.slot === this.slot;
}

override isConstant() {
return true;
}

override clone(): SlotLiteralExpr {
return new SlotLiteralExpr(this.target);
}

override transformInternalExpressions(): void {}
}

/**
* Visits all `Expression`s in the AST of `op` with the `visitor` function.
*/
Expand Down Expand Up @@ -798,6 +825,18 @@ export function transformExpressionsInOp(
case OpKind.Variable:
op.initializer = transformExpressionsInExpression(op.initializer, transform, flags);
break;
case OpKind.Conditional:
for (const condition of op.conditions) {
if (condition[1] === null) {
// This is a default case.
continue;
}
condition[1] = transformExpressionsInExpression(condition[1]!, transform, flags);
}
if (op.processed !== null) {
op.processed = transformExpressionsInExpression(op.processed, transform, flags);
}
break;
case OpKind.Listener:
for (const innerOp of op.handlerOps) {
transformExpressionsInOp(innerOp, transform, flags | VisitorContextFlag.InChildOperation);
Expand Down
64 changes: 60 additions & 4 deletions packages/compiler/src/template/pipeline/ir/src/ops/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import * as o from '../../../../../output/output_ast';
import {ParseSourceSpan} from '../../../../../parse_util';
import {BindingKind, OpKind} from '../enums';
import {Op, XrefId} from '../operations';
import {ConsumesVarsTrait, DependsOnSlotContextOpTrait, TRAIT_CONSUMES_VARS, TRAIT_DEPENDS_ON_SLOT_CONTEXT} from '../traits';
import {ConsumesVarsTrait, DependsOnSlotContextOpTrait, TRAIT_CONSUMES_VARS, TRAIT_DEPENDS_ON_SLOT_CONTEXT, TRAIT_USES_SLOT_INDEX, UsesSlotIndexTrait} from '../traits';

import type {HostPropertyOp} from './host';
import {ListEndOp, NEW_OP, StatementOp, VariableOp} from './shared';
Expand All @@ -20,9 +20,9 @@ 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|AttributeOp|StylePropOp|ClassPropOp|
StyleMapOp|ClassMapOp|InterpolateTextOp|AdvanceOp|VariableOp<UpdateOp>|BindingOp|HostPropertyOp;
export type UpdateOp = ListEndOp<UpdateOp>|StatementOp<UpdateOp>|PropertyOp|AttributeOp|StylePropOp|
ClassPropOp|StyleMapOp|ClassMapOp|InterpolateTextOp|AdvanceOp|VariableOp<UpdateOp>|BindingOp|
HostPropertyOp|ConditionalOp;

/**
* A logical operation to perform string interpolation on a text node.
Expand Down Expand Up @@ -457,3 +457,59 @@ export function createAdvanceOp(delta: number, sourceSpan: ParseSourceSpan): Adv
...NEW_OP,
};
}

/**
* Logical operation representing a conditional expression in the update IR.
*/
export interface ConditionalOp extends Op<ConditionalOp>, DependsOnSlotContextOpTrait,
UsesSlotIndexTrait {
kind: OpKind.Conditional;

/**
* The insertion point, which is the first template in the creation block belonging to this
* condition.
*/
target: XrefId;

/**
* The slot of the target, to be populated during slot allocation.
*/
slot: number|null;

/**
* The main test expression.
*/
test: o.Expression;

/**
* Each possible embedded view that could be displayed has a condition (or is default). This
* structure maps each view xref to its corresponding condition.
*/
conditions: Array<[XrefId, o.Expression|null]>;

/**
* After processing, this will be a single collapsed Joost-expression that evaluates to the right
* slot..
*/
processed: o.Expression|null;

sourceSpan: ParseSourceSpan;
}

/**
* Create a conditional op, which will display an embedded view according to a condtion.
*/
export function createConditionalOp(
target: XrefId, test: o.Expression, sourceSpan: ParseSourceSpan): ConditionalOp {
return {
kind: OpKind.Conditional,
target,
test,
conditions: [],
processed: null,
sourceSpan,
...NEW_OP,
...TRAIT_USES_SLOT_INDEX,
...TRAIT_DEPENDS_ON_SLOT_CONTEXT,
};
}
2 changes: 2 additions & 0 deletions packages/compiler/src/template/pipeline/src/emit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {phaseFindAnyCasts} from './phases/any_cast';
import {phaseAttributeExtraction} from './phases/attribute_extraction';
import {phaseBindingSpecialization} from './phases/binding_specialization';
import {phaseChaining} from './phases/chaining';
import {phaseConditionals} from './phases/conditionals';
import {phaseConstCollection} from './phases/const_collection';
import {phaseEmptyElements} from './phases/empty_elements';
import {phaseExpandSafeReads} from './phases/expand_safe_reads';
Expand Down Expand Up @@ -75,6 +76,7 @@ const phases: Phase[] = [
{kind: Kind.Both, fn: phaseAttributeExtraction},
{kind: Kind.Both, fn: phaseParseExtractedStyles},
{kind: Kind.Tmpl, fn: phaseRemoveEmptyBindings},
{kind: Kind.Tmpl, fn: phaseConditionals},
{kind: Kind.Tmpl, fn: phaseNoListenersOnTemplates},
{kind: Kind.Tmpl, fn: phasePipeCreation},
{kind: Kind.Tmpl, fn: phasePipeVariadic},
Expand Down
Loading

0 comments on commit ab0f9ee

Please sign in to comment.