Skip to content

Commit 7968449

Browse files
dylhunnalxhub
authored andcommitted
refactor(compiler): Introduce ConstCollectedExpr, and use it in defer on expressions (angular#52387)
Previously, we supported a `HasConst` trait, allowing an op to be const collected automatically. However, that approach had the shortcoming that each op could only collect a single constant. Instead, we now provide a `ConstCollectedExpr`, which collects constants at the expression level, allowing ops to have multiple collectible consts. Then, we use this new abstraction to support the `defer on` conditions. PR Close angular#52387
1 parent 6c507e7 commit 7968449

File tree

15 files changed

+162
-126
lines changed

15 files changed

+162
-126
lines changed

packages/compiler-cli/test/compliance/test_cases/r3_view_compiler_deferred/TEST_CASES.json

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,7 @@
1616
],
1717
"failureMessage": "Incorrect template"
1818
}
19-
],
20-
"skipForTemplatePipeline": true
19+
]
2120
},
2221
{
2322
"description": "should generate a deferred block with secondary blocks",
@@ -34,8 +33,7 @@
3433
],
3534
"failureMessage": "Incorrect template"
3635
}
37-
],
38-
"skipForTemplatePipeline": true
36+
]
3937
},
4038
{
4139
"description": "should generate a deferred block with placeholder block parameters",
@@ -181,8 +179,7 @@
181179
],
182180
"failureMessage": "Incorrect template"
183181
}
184-
],
185-
"skipForTemplatePipeline": true
182+
]
186183
},
187184
{
188185
"description": "should generate a deferred block with an interaction trigger in a parent view",
@@ -199,8 +196,7 @@
199196
],
200197
"failureMessage": "Incorrect template"
201198
}
202-
],
203-
"skipForTemplatePipeline": true
199+
]
204200
},
205201
{
206202
"description": "should generate a deferred block with an interaction trigger inside the placeholder",
@@ -217,8 +213,7 @@
217213
],
218214
"failureMessage": "Incorrect template"
219215
}
220-
],
221-
"skipForTemplatePipeline": true
216+
]
222217
},
223218
{
224219
"description": "should generate a deferred block with implicit trigger references",

packages/compiler/src/template/pipeline/ir/src/enums.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,11 @@ export enum ExpressionKind {
355355
* properties ($even, $first, etc.).
356356
*/
357357
DerivedRepeaterVar,
358+
359+
/**
360+
* An expression that will be automatically extracted to the component const array.
361+
*/
362+
ConstCollected,
358363
}
359364

360365
export enum VariableFlags {

packages/compiler/src/template/pipeline/ir/src/expression.ts

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export type Expression = LexicalReadExpr|ReferenceExpr|ContextExpr|NextContextEx
2424
GetCurrentViewExpr|RestoreViewExpr|ResetViewExpr|ReadVariableExpr|PureFunctionExpr|
2525
PureFunctionParameterExpr|PipeBindingExpr|PipeBindingVariadicExpr|SafePropertyReadExpr|
2626
SafeKeyedReadExpr|SafeInvokeFunctionExpr|EmptyExpr|AssignTemporaryExpr|ReadTemporaryExpr|
27-
SanitizerExpr|SlotLiteralExpr|ConditionalCaseExpr|DerivedRepeaterVarExpr;
27+
SanitizerExpr|SlotLiteralExpr|ConditionalCaseExpr|DerivedRepeaterVarExpr|ConstCollectedExpr;
2828

2929
/**
3030
* Transformer type which converts expressions into general `o.Expression`s (which may be an
@@ -853,6 +853,38 @@ export class DerivedRepeaterVarExpr extends ExpressionBase {
853853
}
854854
}
855855

856+
export class ConstCollectedExpr extends ExpressionBase {
857+
override readonly kind = ExpressionKind.ConstCollected;
858+
859+
constructor(public expr: o.Expression) {
860+
super();
861+
}
862+
863+
override transformInternalExpressions(transform: ExpressionTransform, flags: VisitorContextFlag):
864+
void {
865+
this.expr = transform(this.expr, flags);
866+
}
867+
868+
override visitExpression(visitor: o.ExpressionVisitor, context: any) {
869+
this.expr.visitExpression(visitor, context);
870+
}
871+
872+
override isEquivalent(e: o.Expression): boolean {
873+
if (!(e instanceof ConstCollectedExpr)) {
874+
return false;
875+
}
876+
return this.expr.isEquivalent(e.expr);
877+
}
878+
879+
override isConstant(): boolean {
880+
return this.expr.isConstant();
881+
}
882+
883+
override clone(): ConstCollectedExpr {
884+
return new ConstCollectedExpr(this.expr);
885+
}
886+
}
887+
856888
/**
857889
* Visits all `Expression`s in the AST of `op` with the `visitor` function.
858890
*/
@@ -953,11 +985,19 @@ export function transformExpressionsInOp(
953985
case OpKind.Repeater:
954986
op.collection = transformExpressionsInExpression(op.collection, transform, flags);
955987
break;
988+
case OpKind.Defer:
989+
if (op.loadingConfig !== null) {
990+
op.loadingConfig = transformExpressionsInExpression(op.loadingConfig, transform, flags);
991+
}
992+
if (op.placeholderConfig !== null) {
993+
op.placeholderConfig =
994+
transformExpressionsInExpression(op.placeholderConfig, transform, flags);
995+
}
996+
break;
956997
case OpKind.Advance:
957998
case OpKind.Container:
958999
case OpKind.ContainerEnd:
9591000
case OpKind.ContainerStart:
960-
case OpKind.Defer:
9611001
case OpKind.DeferOn:
9621002
case OpKind.DisableBindings:
9631003
case OpKind.Element:

packages/compiler/src/template/pipeline/ir/src/ops/create.ts

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -679,12 +679,18 @@ export interface DeferOp extends Op<CreateOp>, ConsumesSlotOpTrait {
679679

680680
errorSlot: SlotHandle|null;
681681

682+
placeholderMinimumTime: number|null;
683+
loadingMinimumTime: number|null;
684+
loadingAfterTime: number|null;
685+
686+
placeholderConfig: o.Expression|null;
687+
loadingConfig: o.Expression|null;
688+
682689
sourceSpan: ParseSourceSpan;
683690
}
684691

685692
export function createDeferOp(
686-
xref: XrefId, main: XrefId, mainSlot: SlotHandle, placeholderView: XrefId|null,
687-
placeholderSlot: SlotHandle|null, sourceSpan: ParseSourceSpan): DeferOp {
693+
xref: XrefId, main: XrefId, mainSlot: SlotHandle, sourceSpan: ParseSourceSpan): DeferOp {
688694
return {
689695
kind: OpKind.Defer,
690696
xref,
@@ -693,13 +699,19 @@ export function createDeferOp(
693699
mainSlot,
694700
loadingView: null,
695701
loadingSlot: null,
702+
loadingConfig: null,
703+
loadingMinimumTime: null,
704+
loadingAfterTime: null,
696705
placeholderView: null,
697-
placeholderSlot,
706+
placeholderSlot: null,
707+
placeholderConfig: null,
708+
placeholderMinimumTime: null,
698709
errorView: null,
699710
errorSlot: null,
700711
sourceSpan,
701712
...NEW_OP,
702713
...TRAIT_CONSUMES_SLOT,
714+
numSlotsUsed: 2,
703715
};
704716
}
705717
interface DeferTriggerBase {
@@ -724,7 +736,7 @@ interface DeferInteractionTrigger extends DeferTriggerBase {
724736
* The slot index of the named reference, inside the view provided below. This slot may not be
725737
* inside the current view, and is handled specially as a result.
726738
*/
727-
targetSlot: number|null;
739+
targetSlot: SlotHandle|null;
728740

729741
targetView: XrefId|null;
730742

packages/compiler/src/template/pipeline/ir/src/traits.ts

Lines changed: 0 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,6 @@ export const ConsumesVarsTrait = Symbol('ConsumesVars');
3232
*/
3333
export const UsesVarOffset = Symbol('UsesVarOffset');
3434

35-
/**
36-
* Marker symbol for `HasConst` trait.
37-
*/
38-
export const HasConst = Symbol('HasConst');
39-
4035
/**
4136
* Marks an operation as requiring allocation of one or more data slots for storage.
4237
*/
@@ -102,30 +97,6 @@ export interface UsesVarOffsetTrait {
10297
varOffset: number|null;
10398
}
10499

105-
/**
106-
* Marker trait indicating that an op or expression has some data which should be collected into the
107-
* component constant array.
108-
*
109-
*/
110-
export interface HasConstTrait {
111-
[HasConst]: true;
112-
113-
/**
114-
* The constant to be collected into the const array, if non-null.
115-
*/
116-
constValue: unknown|null;
117-
118-
/**
119-
* The index of the collected constant, after processing.
120-
*/
121-
constIndex: number|null;
122-
123-
/**
124-
* A callback which converts the constValue into an o.Expression for the const array.
125-
*/
126-
makeExpression: (value: unknown) => o.Expression;
127-
}
128-
129100
/**
130101
* Default values for most `ConsumesSlotOpTrait` fields (used with the spread operator to initialize
131102
* implementors of the trait).
@@ -161,15 +132,6 @@ export const TRAIT_USES_VAR_OFFSET: UsesVarOffsetTrait = {
161132
varOffset: null,
162133
} as const;
163134

164-
/**
165-
* Default values for `HasConst` fields (used with the spread operator to initialize
166-
* implementors of this trait).
167-
*/
168-
export const TRAIT_HAS_CONST: Omit<HasConstTrait, 'constValue'|'makeExpression'> = {
169-
[HasConst]: true,
170-
constIndex: null,
171-
} as const;
172-
173135
/**
174136
* Test whether an operation implements `ConsumesSlotOpTrait`.
175137
*/
@@ -202,12 +164,3 @@ export function hasUsesVarOffsetTrait<ExprT extends Expression>(expr: ExprT): ex
202164
UsesVarOffsetTrait {
203165
return (expr as Partial<UsesVarOffsetTrait>)[UsesVarOffset] === true;
204166
}
205-
206-
/**
207-
* Test whether an operation or expression implements `HasConstTrait`.
208-
*/
209-
export function hasConstTrait<ExprT extends Expression>(expr: ExprT): expr is ExprT&HasConstTrait;
210-
export function hasConstTrait<OpT extends Op<OpT>>(op: OpT): op is OpT&HasConstTrait;
211-
export function hasConstTrait(value: any): boolean {
212-
return (value as Partial<HasConstTrait>)[HasConst] === true;
213-
}

packages/compiler/src/template/pipeline/src/conversion.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,11 @@ export function prefixWithNamespace(strippedTag: string, namespace: ir.Namespace
5454
return `:${keyForNamespace(namespace)}:${strippedTag}`;
5555
}
5656

57-
export function literalOrArrayLiteral(value: any): o.Expression {
57+
export type LiteralType = string|number|boolean|null|Array<LiteralType>;
58+
59+
export function literalOrArrayLiteral(value: LiteralType): o.Expression {
5860
if (Array.isArray(value)) {
5961
return o.literalArr(value.map(literalOrArrayLiteral));
6062
}
61-
return o.literal(value, o.INFERRED_TYPE);
63+
return o.literal(value);
6264
}

packages/compiler/src/template/pipeline/src/emit.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import {phaseFormatI18nParams} from './phases/format_i18n_params';
2828
import {phaseGenerateAdvance} from './phases/generate_advance';
2929
import {phaseGenerateProjectionDef} from './phases/generate_projection_def';
3030
import {phaseGenerateVariables} from './phases/generate_variables';
31-
import {phaseConstTraitCollection} from './phases/has_const_trait_collection';
31+
import {phaseConstExpressionCollection} from './phases/has_const_expression_collection';
3232
import {phaseHostStylePropertyParsing} from './phases/host_style_property_parsing';
3333
import {phaseI18nConstCollection} from './phases/i18n_const_collection';
3434
import {phaseI18nMessageExtraction} from './phases/i18n_message_extraction';
@@ -69,6 +69,8 @@ import {phaseTrackVariables} from './phases/track_variables';
6969
import {phaseVarCounting} from './phases/var_counting';
7070
import {phaseVariableOptimization} from './phases/variable_optimization';
7171
import {phaseWrapIcus} from './phases/wrap_icus';
72+
import {phaseDeferResolveTargets} from './phases/defer_resolve_targets';
73+
import {phaseDeferConfigs} from './phases/defer_configs';
7274

7375
type Phase = {
7476
fn: (job: CompilationJob) => void; kind: Kind.Both | Kind.Host | Kind.Tmpl;
@@ -96,6 +98,7 @@ const phases: Phase[] = [
9698
{kind: Kind.Both, fn: phaseOrdering},
9799
{kind: Kind.Tmpl, fn: phaseConditionals},
98100
{kind: Kind.Tmpl, fn: phasePipeCreation},
101+
{kind: Kind.Tmpl, fn: phaseDeferConfigs},
99102
{kind: Kind.Tmpl, fn: phaseI18nTextExtraction},
100103
{kind: Kind.Tmpl, fn: phaseIcuExtraction},
101104
{kind: Kind.Tmpl, fn: phaseApplyI18nExpressions},
@@ -109,6 +112,7 @@ const phases: Phase[] = [
109112
{kind: Kind.Tmpl, fn: phaseRepeaterDerivedVars},
110113
{kind: Kind.Tmpl, fn: phaseTrackVariables},
111114
{kind: Kind.Both, fn: phaseResolveNames},
115+
{kind: Kind.Tmpl, fn: phaseDeferResolveTargets},
112116
{kind: Kind.Tmpl, fn: phaseTrackFnOptimization},
113117
{kind: Kind.Both, fn: phaseResolveContexts},
114118
{kind: Kind.Tmpl, fn: phaseResolveSanitizers}, // TODO: run in both
@@ -124,7 +128,7 @@ const phases: Phase[] = [
124128
{kind: Kind.Tmpl, fn: phaseFormatI18nParams},
125129
{kind: Kind.Tmpl, fn: phaseTrackFnGeneration},
126130
{kind: Kind.Tmpl, fn: phaseI18nConstCollection},
127-
{kind: Kind.Tmpl, fn: phaseConstTraitCollection},
131+
{kind: Kind.Tmpl, fn: phaseConstExpressionCollection},
128132
{kind: Kind.Both, fn: phaseConstCollection},
129133
{kind: Kind.Tmpl, fn: phaseAssignI18nSlotDependencies},
130134
{kind: Kind.Both, fn: phaseVarCounting},

packages/compiler/src/template/pipeline/src/ingest.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -363,9 +363,14 @@ function ingestDeferBlock(unit: ViewCompilationUnit, deferBlock: t.DeferredBlock
363363

364364
// Create the main defer op, and ops for all secondary views.
365365
const deferXref = unit.job.allocateXrefId();
366-
const deferOp = ir.createDeferOp(
367-
deferXref, main.xref, main.slot, placeholder?.xref ?? null, placeholder?.slot ?? null,
368-
deferBlock.sourceSpan);
366+
const deferOp = ir.createDeferOp(deferXref, main.xref, main.slot, deferBlock.sourceSpan);
367+
deferOp.placeholderView = placeholder?.xref ?? null;
368+
deferOp.placeholderSlot = placeholder?.slot ?? null;
369+
deferOp.loadingSlot = loading?.slot ?? null;
370+
deferOp.errorSlot = error?.slot ?? null;
371+
deferOp.placeholderMinimumTime = deferBlock.placeholder?.minimumTime ?? null;
372+
deferOp.loadingMinimumTime = deferBlock.loading?.minimumTime ?? null;
373+
deferOp.loadingAfterTime = deferBlock.loading?.afterTime ?? null;
369374
unit.create.push(deferOp);
370375

371376
// Configure all defer `on` conditions.

packages/compiler/src/template/pipeline/src/instruction.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -185,20 +185,22 @@ export function text(
185185

186186
export function defer(
187187
selfSlot: number, primarySlot: number, dependencyResolverFn: null, loadingSlot: number|null,
188-
placeholderSlot: number|null, errorSlot: number|null, loadingConfigIndex: number|null,
189-
placeholderConfigIndex: number|null, sourceSpan: ParseSourceSpan|null): ir.CreateOp {
190-
const args = [
188+
placeholderSlot: number|null, errorSlot: number|null, loadingConfig: o.Expression|null,
189+
placeholderConfig: o.Expression|null, sourceSpan: ParseSourceSpan|null): ir.CreateOp {
190+
const args: Array<o.Expression> = [
191191
o.literal(selfSlot),
192192
o.literal(primarySlot),
193193
o.literal(dependencyResolverFn),
194194
o.literal(loadingSlot),
195195
o.literal(placeholderSlot),
196196
o.literal(errorSlot),
197-
o.literal(loadingConfigIndex),
198-
o.literal(placeholderConfigIndex),
197+
loadingConfig ?? o.literal(null),
198+
placeholderConfig ?? o.literal(null),
199199
];
200200

201-
while (args[args.length - 1].value === null) {
201+
let expr: o.Expression;
202+
while ((expr = args[args.length - 1]) !== null && expr instanceof o.LiteralExpr &&
203+
expr.value === null) {
202204
args.pop();
203205
}
204206

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import * as o from '../../../../output/output_ast';
10+
import * as ir from '../../ir';
11+
import type {ViewCompilationUnit, ComponentCompilationJob} from '../compilation';
12+
import {literalOrArrayLiteral} from '../conversion';
13+
14+
export function phaseDeferConfigs(job: ComponentCompilationJob): void {
15+
for (const unit of job.units) {
16+
for (const op of unit.create) {
17+
if (op.kind !== ir.OpKind.Defer) {
18+
continue;
19+
}
20+
21+
if (op.placeholderMinimumTime !== null) {
22+
op.placeholderConfig =
23+
new ir.ConstCollectedExpr(literalOrArrayLiteral([op.placeholderMinimumTime]));
24+
}
25+
if (op.loadingMinimumTime !== null || op.loadingAfterTime !== null) {
26+
op.loadingConfig = new ir.ConstCollectedExpr(
27+
literalOrArrayLiteral([op.loadingMinimumTime, op.loadingAfterTime]));
28+
}
29+
}
30+
}
31+
}

0 commit comments

Comments
 (0)