mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
refactor(compiler): pipeline phase to generate variables (#48580)
This commit implements the first "phase" of the template pipeline. Phases are individual steps in compilation that perform a transformation of the IR in order to move closer to generating runtime code for the template. This first phase is `phaseGenerateVariables()`. This phase introduces variable definition operations into the IR to define variables in each view. These variables either represent internal operations (saving/restoring the view context for listeners, for example) or variables created from user-defined names such as local references or template context properties. Every view has all possibly-referenced variables generated, regardless of whether they're actually referenced by other operations. A future phase will optimize the variables in each view, inlining those which are only read once and removing those which are not referenced at all. PR Close #48580
This commit is contained in:
parent
99a2068a5b
commit
369fd7a540
8 changed files with 596 additions and 6 deletions
|
|
@ -13,3 +13,4 @@ export * from './src/operations';
|
|||
export * from './src/ops/create';
|
||||
export * from './src/ops/shared';
|
||||
export * from './src/ops/update';
|
||||
export * from './src/variable';
|
||||
|
|
|
|||
|
|
@ -23,6 +23,11 @@ export enum OpKind {
|
|||
*/
|
||||
Statement,
|
||||
|
||||
/**
|
||||
* An operation which declares and initializes a `SemanticVariable`.
|
||||
*/
|
||||
Variable,
|
||||
|
||||
/**
|
||||
* An operation to begin rendering of an element.
|
||||
*/
|
||||
|
|
@ -72,4 +77,59 @@ export enum ExpressionKind {
|
|||
* Read of a variable in a lexical scope.
|
||||
*/
|
||||
LexicalRead,
|
||||
|
||||
/**
|
||||
* A reference to the current view context.
|
||||
*/
|
||||
Context,
|
||||
|
||||
/**
|
||||
* Read of a variable declared in a `VariableOp`.
|
||||
*/
|
||||
ReadVariable,
|
||||
|
||||
/**
|
||||
* Runtime operation to navigate to the next view context in the view hierarchy.
|
||||
*/
|
||||
NextContext,
|
||||
|
||||
/**
|
||||
* Runtime operation to retrieve the value of a local reference.
|
||||
*/
|
||||
Reference,
|
||||
|
||||
/**
|
||||
* Runtime operation to snapshot the current view context.
|
||||
*/
|
||||
GetCurrentView,
|
||||
|
||||
/**
|
||||
* Runtime operation to restore a snapshotted view.
|
||||
*/
|
||||
RestoreView,
|
||||
|
||||
/**
|
||||
* Runtime operation to reset the current view context after `RestoreView`.
|
||||
*/
|
||||
ResetView,
|
||||
}
|
||||
|
||||
/**
|
||||
* Distinguishes between different kinds of `SemanticVariable`s.
|
||||
*/
|
||||
export enum SemanticVariableKind {
|
||||
/**
|
||||
* Represents the context of a particular view.
|
||||
*/
|
||||
Context,
|
||||
|
||||
/**
|
||||
* Represents an identifier declared in the lexical scope of a view.
|
||||
*/
|
||||
Identifier,
|
||||
|
||||
/**
|
||||
* Represents a saved state that can be used to restore a view in a listener handler function.
|
||||
*/
|
||||
SavedView,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,11 +10,13 @@ import * as o from '../../../../output/output_ast';
|
|||
import type {ParseSourceSpan} from '../../../../parse_util';
|
||||
|
||||
import {ExpressionKind} from './enums';
|
||||
import {XrefId} from './operations';
|
||||
|
||||
/**
|
||||
* An `o.Expression` subtype representing a logical expression in the intermediate representation.
|
||||
*/
|
||||
export type Expression = LexicalReadExpr;
|
||||
export type Expression = LexicalReadExpr|ReferenceExpr|ContextExpr|NextContextExpr|
|
||||
GetCurrentViewExpr|RestoreViewExpr|ResetViewExpr;
|
||||
|
||||
/**
|
||||
* Transformer type which converts IR expressions into general `o.Expression`s (which may be an
|
||||
|
|
@ -68,3 +70,213 @@ export class LexicalReadExpr extends ExpressionBase {
|
|||
|
||||
override transformInternalExpressions(): void {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runtime operation to retrieve the value of a local reference.
|
||||
*/
|
||||
export class ReferenceExpr extends ExpressionBase {
|
||||
readonly kind = ExpressionKind.Reference;
|
||||
|
||||
constructor(readonly target: XrefId, readonly offset: number) {
|
||||
super();
|
||||
}
|
||||
|
||||
override visitExpression(): void {}
|
||||
|
||||
override isEquivalent(e: o.Expression): boolean {
|
||||
return e instanceof ReferenceExpr && e.target === this.target;
|
||||
}
|
||||
|
||||
override isConstant(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
override transformInternalExpressions(): void {}
|
||||
}
|
||||
|
||||
/**
|
||||
* A reference to the current view context (usually the `ctx` variable in a template function).
|
||||
*/
|
||||
export class ContextExpr extends ExpressionBase {
|
||||
readonly kind = ExpressionKind.Context;
|
||||
|
||||
constructor(readonly view: XrefId) {
|
||||
super();
|
||||
}
|
||||
|
||||
override visitExpression(): void {}
|
||||
|
||||
override isEquivalent(e: o.Expression): boolean {
|
||||
return e instanceof ContextExpr && e.view === this.view;
|
||||
}
|
||||
|
||||
override isConstant(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
override transformInternalExpressions(): void {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runtime operation to navigate to the next view context in the view hierarchy.
|
||||
*/
|
||||
export class NextContextExpr extends ExpressionBase {
|
||||
readonly kind = ExpressionKind.NextContext;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
override visitExpression(): void {}
|
||||
|
||||
override isEquivalent(e: o.Expression): boolean {
|
||||
return e instanceof NextContextExpr;
|
||||
}
|
||||
|
||||
override isConstant(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
override transformInternalExpressions(): void {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runtime operation to snapshot the current view context.
|
||||
*
|
||||
* The result of this operation can be stored in a variable and later used with the `RestoreView`
|
||||
* operation.
|
||||
*/
|
||||
export class GetCurrentViewExpr extends ExpressionBase {
|
||||
readonly kind = ExpressionKind.GetCurrentView;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
override visitExpression(): void {}
|
||||
|
||||
override isEquivalent(e: o.Expression): boolean {
|
||||
return e instanceof GetCurrentViewExpr;
|
||||
}
|
||||
|
||||
override isConstant(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
override transformInternalExpressions(): void {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runtime operation to restore a snapshotted view.
|
||||
*/
|
||||
export class RestoreViewExpr extends ExpressionBase {
|
||||
readonly kind = ExpressionKind.RestoreView;
|
||||
|
||||
constructor(public view: XrefId|o.Expression) {
|
||||
super();
|
||||
}
|
||||
|
||||
override visitExpression(visitor: o.ExpressionVisitor, context: any): void {
|
||||
if (typeof this.view !== 'number') {
|
||||
this.view.visitExpression(visitor, context);
|
||||
}
|
||||
}
|
||||
|
||||
override isEquivalent(e: o.Expression): boolean {
|
||||
if (!(e instanceof RestoreViewExpr) || typeof e.view !== typeof this.view) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (typeof this.view === 'number') {
|
||||
return this.view === e.view;
|
||||
} else {
|
||||
return this.view.isEquivalent(e.view as o.Expression);
|
||||
}
|
||||
}
|
||||
|
||||
override isConstant(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
override transformInternalExpressions(transform: ExpressionTransform): void {
|
||||
if (typeof this.view !== 'number') {
|
||||
this.view = transformExpressionsInExpression(this.view, transform);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Runtime operation to reset the current view context after `RestoreView`.
|
||||
*/
|
||||
export class ResetViewExpr extends ExpressionBase {
|
||||
readonly kind = ExpressionKind.ResetView;
|
||||
|
||||
constructor(public expr: o.Expression) {
|
||||
super();
|
||||
}
|
||||
|
||||
override visitExpression(visitor: o.ExpressionVisitor, context: any): any {
|
||||
this.expr.visitExpression(visitor, context);
|
||||
}
|
||||
|
||||
override isEquivalent(e: o.Expression): boolean {
|
||||
return e instanceof ResetViewExpr && this.expr.isEquivalent(e.expr);
|
||||
}
|
||||
|
||||
override isConstant(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
override transformInternalExpressions(transform: ExpressionTransform): void {
|
||||
this.expr = transformExpressionsInExpression(this.expr, transform);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Transform all `Expression`s in the AST of `expr` with the `transform` function.
|
||||
*
|
||||
* All such operations will be replaced with the result of applying `transform`, which may be an
|
||||
* identity transformation.
|
||||
*/
|
||||
export function transformExpressionsInExpression(
|
||||
expr: o.Expression, transform: ExpressionTransform): o.Expression {
|
||||
if (expr instanceof ExpressionBase) {
|
||||
expr.transformInternalExpressions(transform);
|
||||
return transform(expr as Expression);
|
||||
} else if (expr instanceof o.BinaryOperatorExpr) {
|
||||
expr.lhs = transformExpressionsInExpression(expr.lhs, transform);
|
||||
expr.rhs = transformExpressionsInExpression(expr.rhs, transform);
|
||||
} else if (expr instanceof o.ReadPropExpr) {
|
||||
expr.receiver = transformExpressionsInExpression(expr.receiver, transform);
|
||||
} else if (expr instanceof o.InvokeFunctionExpr) {
|
||||
expr.fn = transformExpressionsInExpression(expr.fn, transform);
|
||||
for (let i = 0; i < expr.args.length; i++) {
|
||||
expr.args[i] = transformExpressionsInExpression(expr.args[i], transform);
|
||||
}
|
||||
} else if (
|
||||
expr instanceof o.ReadVarExpr || expr instanceof o.ExternalExpr ||
|
||||
expr instanceof o.LiteralExpr) {
|
||||
// No action for these types.
|
||||
} else {
|
||||
throw new Error(`Unhandled expression kind: ${expr.constructor.name}`);
|
||||
}
|
||||
return expr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform all `Expression`s in the AST of `stmt` with the `transform` function.
|
||||
*
|
||||
* All such operations will be replaced with the result of applying `transform`, which may be an
|
||||
* identity transformation.
|
||||
*/
|
||||
export function transformExpressionsInStatement(
|
||||
stmt: o.Statement, transform: ExpressionTransform): void {
|
||||
if (stmt instanceof o.ExpressionStatement) {
|
||||
stmt.expr = transformExpressionsInExpression(stmt.expr, transform);
|
||||
} else if (stmt instanceof o.ReturnStatement) {
|
||||
stmt.value = transformExpressionsInExpression(stmt.value, transform);
|
||||
} else {
|
||||
throw new Error(`Unhandled statement kind: ${stmt.constructor.name}`);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,14 +10,14 @@ import {ElementAttributes} from '../element';
|
|||
import {OpKind} from '../enums';
|
||||
import {Op, OpList, XrefId} from '../operations';
|
||||
|
||||
import {ListEndOp, NEW_OP, StatementOp} from './shared';
|
||||
import {ListEndOp, NEW_OP, StatementOp, VariableOp} from './shared';
|
||||
import type {UpdateOp} from './update';
|
||||
|
||||
/**
|
||||
* An operation usable on the creation side of the IR.
|
||||
*/
|
||||
export type CreateOp = ListEndOp<CreateOp>|StatementOp<CreateOp>|ElementOp|ElementStartOp|
|
||||
ElementEndOp|TemplateOp|TextOp|ListenerOp;
|
||||
ElementEndOp|TemplateOp|TextOp|ListenerOp|VariableOp<CreateOp>;
|
||||
|
||||
/**
|
||||
* Representation of a local reference on an element.
|
||||
|
|
|
|||
|
|
@ -8,7 +8,8 @@
|
|||
|
||||
import * as o from '../../../../../output/output_ast';
|
||||
import {OpKind} from '../enums';
|
||||
import {Op} from '../operations';
|
||||
import {Op, XrefId} from '../operations';
|
||||
import {SemanticVariable} from '../variable';
|
||||
|
||||
/**
|
||||
* A special `Op` which is used internally in the `OpList` linked list to represent the head and
|
||||
|
|
@ -45,6 +46,50 @@ export function createStatementOp<OpT extends Op<OpT>>(statement: o.Statement):
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Operation which declares and initializes a `SemanticVariable`, that is valid either in create or
|
||||
* update IR.
|
||||
*/
|
||||
export interface VariableOp<OpT extends Op<OpT>> extends Op<OpT> {
|
||||
kind: OpKind.Variable;
|
||||
|
||||
/**
|
||||
* `XrefId` which identifies this specific variable, and is used to reference this variable from
|
||||
* other parts of the IR.
|
||||
*/
|
||||
xref: XrefId;
|
||||
|
||||
/**
|
||||
* Name assigned to this variable in generated code, or `null` if not yet assigned.
|
||||
*/
|
||||
name: string|null;
|
||||
|
||||
/**
|
||||
* The `SemanticVariable` which describes the meaning behind this variable.
|
||||
*/
|
||||
variable: SemanticVariable;
|
||||
|
||||
/**
|
||||
* Expression representing the value of the variable.
|
||||
*/
|
||||
initializer: o.Expression;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a `VariableOp`.
|
||||
*/
|
||||
export function createVariableOp<OpT extends Op<OpT>>(
|
||||
xref: XrefId, variable: SemanticVariable, initializer: o.Expression): VariableOp<OpT> {
|
||||
return {
|
||||
kind: OpKind.Variable,
|
||||
xref,
|
||||
name: null,
|
||||
variable,
|
||||
initializer,
|
||||
...NEW_OP,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Static structure shared by all operations.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -10,12 +10,13 @@ import * as o from '../../../../../output/output_ast';
|
|||
import {OpKind} from '../enums';
|
||||
import {Op, XrefId} from '../operations';
|
||||
|
||||
import {ListEndOp, NEW_OP, StatementOp} from './shared';
|
||||
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;
|
||||
export type UpdateOp =
|
||||
ListEndOp<UpdateOp>|StatementOp<UpdateOp>|PropertyOp|InterpolateTextOp|VariableOp<UpdateOp>;
|
||||
|
||||
/**
|
||||
* A logical operation to perform string interpolation on a text node.
|
||||
|
|
|
|||
51
packages/compiler/src/template/pipeline/ir/src/variable.ts
Normal file
51
packages/compiler/src/template/pipeline/ir/src/variable.ts
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import type {SemanticVariableKind} from './enums';
|
||||
import type {XrefId} from './operations';
|
||||
|
||||
/**
|
||||
* Union type for the different kinds of variables.
|
||||
*/
|
||||
export type SemanticVariable = ContextVariable|IdentifierVariable|SavedViewVariable;
|
||||
|
||||
/**
|
||||
* A variable that represents the context of a particular view.
|
||||
*/
|
||||
export interface ContextVariable {
|
||||
kind: SemanticVariableKind.Context;
|
||||
|
||||
/**
|
||||
* `XrefId` of the view that this variable represents.
|
||||
*/
|
||||
view: XrefId;
|
||||
}
|
||||
|
||||
/**
|
||||
* A variable that represents a specific identifier within a template.
|
||||
*/
|
||||
export interface IdentifierVariable {
|
||||
kind: SemanticVariableKind.Identifier;
|
||||
|
||||
/**
|
||||
* The identifier whose value in the template is tracked in this variable.
|
||||
*/
|
||||
name: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A variable that represents a saved view context.
|
||||
*/
|
||||
export interface SavedViewVariable {
|
||||
kind: SemanticVariableKind.SavedView;
|
||||
|
||||
/**
|
||||
* The view context saved in this variable.
|
||||
*/
|
||||
view: XrefId;
|
||||
}
|
||||
|
|
@ -0,0 +1,220 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import * as o from '../../../../output/output_ast';
|
||||
import * as ir from '../../ir';
|
||||
|
||||
import type {ComponentCompilation, ViewCompilation} from '../compilation';
|
||||
|
||||
/**
|
||||
* Generate a preamble sequence for each view creation block and listener function which declares
|
||||
* any variables that be referenced in other operations in the block.
|
||||
*
|
||||
* Variables generated include:
|
||||
* * a saved view context to be used to restore the current view in event listeners.
|
||||
* * the context of the restored view within event listener handlers.
|
||||
* * context variables from the current view as well as all parent views (including the root
|
||||
* context if needed).
|
||||
* * local references from elements within the current view and any lexical parents.
|
||||
*
|
||||
* Variables are generated here unconditionally, and may optimized away in future operations if it
|
||||
* turns out their values (and any side effects) are unused.
|
||||
*/
|
||||
export function phaseGenerateVariables(cpl: ComponentCompilation): void {
|
||||
recursivelyProcessView(cpl.root, /* there is no parent scope for the root view */ null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the given `ViewCompilation` and generate preambles for it and any listeners that it
|
||||
* declares.
|
||||
*
|
||||
* @param `parentScope` a scope extracted from the parent view which captures any variables which
|
||||
* should be inherited by this view. `null` if the current view is the root view.
|
||||
*/
|
||||
function recursivelyProcessView(view: ViewCompilation, parentScope: Scope|null): void {
|
||||
// Extract a `Scope` from this view.
|
||||
const scope = getScopeForView(view, parentScope);
|
||||
|
||||
// Start the view creation block with an operation to save the current view context. This may be
|
||||
// used to restore the view context in any listeners that may be present.
|
||||
view.create.prepend([
|
||||
ir.createVariableOp<ir.CreateOp>(
|
||||
view.tpl.allocateXrefId(), {
|
||||
kind: ir.SemanticVariableKind.SavedView,
|
||||
view: view.xref,
|
||||
},
|
||||
new ir.GetCurrentViewExpr()),
|
||||
]);
|
||||
|
||||
for (const op of view.create) {
|
||||
switch (op.kind) {
|
||||
case ir.OpKind.Template:
|
||||
// Descend into child embedded views.
|
||||
recursivelyProcessView(view.tpl.views.get(op.xref)!, scope);
|
||||
break;
|
||||
case ir.OpKind.Listener:
|
||||
// Listeners get a preamble which starts with a call to restore the view.
|
||||
const preambleOps = [
|
||||
ir.createVariableOp<ir.UpdateOp>(
|
||||
view.tpl.allocateXrefId(), {
|
||||
kind: ir.SemanticVariableKind.Context,
|
||||
view: view.xref,
|
||||
},
|
||||
new ir.RestoreViewExpr(view.xref)),
|
||||
// And includes all variables available to this view.
|
||||
...generateVariablesInScopeForView(view, scope)
|
||||
];
|
||||
|
||||
op.handlerOps.prepend(preambleOps);
|
||||
|
||||
// The "restore view" operation in listeners requires a call to `resetView` to reset the
|
||||
// context prior to returning from the listener operation. Find any `return` statements in
|
||||
// the listener body and wrap them in a call to reset the view.
|
||||
for (const handlerOp of op.handlerOps) {
|
||||
if (handlerOp.kind === ir.OpKind.Statement &&
|
||||
handlerOp.statement instanceof o.ReturnStatement) {
|
||||
handlerOp.statement.value = new ir.ResetViewExpr(handlerOp.statement.value);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Prepend the declarations for all available variables in scope to the `update` block.
|
||||
const preambleOps = generateVariablesInScopeForView(view, scope);
|
||||
view.update.prepend(preambleOps);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lexical scope of a view, including a reference to its parent view's scope, if any.
|
||||
*/
|
||||
interface Scope {
|
||||
/**
|
||||
* `XrefId` of the view to which this scope corresponds.
|
||||
*/
|
||||
view: ir.XrefId;
|
||||
|
||||
/**
|
||||
* Local references collected from elements within the view.
|
||||
*/
|
||||
references: Reference[];
|
||||
|
||||
/**
|
||||
* `Scope` of the parent view, if any.
|
||||
*/
|
||||
parent: Scope|null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Information needed about a local reference collected from an element within a view.
|
||||
*/
|
||||
interface Reference {
|
||||
/**
|
||||
* Name given to the local reference variable within the template.
|
||||
*
|
||||
* This is not the name which will be used for the variable declaration in the generated
|
||||
* template code.
|
||||
*/
|
||||
name: string;
|
||||
|
||||
/**
|
||||
* `XrefId` of the element-like node which this reference targets.
|
||||
*
|
||||
* The reference may be either to the element (or template) itself, or to a directive on it.
|
||||
*/
|
||||
targetId: ir.XrefId;
|
||||
|
||||
/**
|
||||
* A generated offset of this reference among all the references on a specific element.
|
||||
*/
|
||||
offset: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a view and generate a `Scope` representing the variables available for reference within
|
||||
* that view.
|
||||
*/
|
||||
function getScopeForView(view: ViewCompilation, parent: Scope|null): Scope {
|
||||
const scope: Scope = {
|
||||
view: view.xref,
|
||||
references: [],
|
||||
parent,
|
||||
};
|
||||
|
||||
for (const op of view.create) {
|
||||
switch (op.kind) {
|
||||
case ir.OpKind.Element:
|
||||
case ir.OpKind.ElementStart:
|
||||
case ir.OpKind.Template:
|
||||
if (!Array.isArray(op.localRefs)) {
|
||||
throw new Error(`AssertionError: expected localRefs to be an array`);
|
||||
}
|
||||
|
||||
// Record available local references from this element.
|
||||
for (let offset = 0; offset < op.localRefs.length; offset++) {
|
||||
scope.references.push({
|
||||
name: op.localRefs[offset].name,
|
||||
targetId: op.xref,
|
||||
offset,
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return scope;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate declarations for all variables that are in scope for a given view.
|
||||
*
|
||||
* This is a recursive process, as views inherit variables available from their parent view, which
|
||||
* itself may have inherited variables, etc.
|
||||
*/
|
||||
function generateVariablesInScopeForView(
|
||||
view: ViewCompilation, scope: Scope): ir.VariableOp<ir.UpdateOp>[] {
|
||||
const newOps: ir.VariableOp<ir.UpdateOp>[] = [];
|
||||
|
||||
if (scope.view !== view.xref) {
|
||||
// Before generating variables for a parent view, we need to switch to the context of the parent
|
||||
// view with a `nextContext` expression. This context switching operation itself declares a
|
||||
// variable, because the context of the view may be referenced directly.
|
||||
newOps.push(ir.createVariableOp(
|
||||
view.tpl.allocateXrefId(), {
|
||||
kind: ir.SemanticVariableKind.Context,
|
||||
view: scope.view,
|
||||
},
|
||||
new ir.NextContextExpr()));
|
||||
}
|
||||
|
||||
// Add variables for all context variables available in this scope's view.
|
||||
for (const [name, value] of view.tpl.views.get(scope.view)!.contextVariables) {
|
||||
newOps.push(ir.createVariableOp(
|
||||
view.tpl.allocateXrefId(), {
|
||||
kind: ir.SemanticVariableKind.Identifier,
|
||||
name,
|
||||
},
|
||||
new o.ReadPropExpr(new ir.ContextExpr(view.xref), value)));
|
||||
}
|
||||
|
||||
// Add variables for all local references declared for elements in this scope.
|
||||
for (const ref of scope.references) {
|
||||
newOps.push(ir.createVariableOp(
|
||||
view.tpl.allocateXrefId(), {
|
||||
kind: ir.SemanticVariableKind.Identifier,
|
||||
name: ref.name,
|
||||
},
|
||||
new ir.ReferenceExpr(ref.targetId, ref.offset)));
|
||||
}
|
||||
|
||||
if (scope.parent !== null) {
|
||||
// Recursively add variables from the parent scope.
|
||||
newOps.push(...generateVariablesInScopeForView(view, scope.parent));
|
||||
}
|
||||
return newOps;
|
||||
}
|
||||
Loading…
Reference in a new issue