fix(ivy): convert context code into a tree-shakable instruction (#24943)
PR Close #24943
This commit is contained in:

committed by
Igor Minar

parent
fe14f180a6
commit
2ef777b0b2
@ -338,6 +338,8 @@ export class WriteVarExpr extends Expression {
|
||||
toDeclStmt(type?: Type|null, modifiers?: StmtModifier[]|null): DeclareVarStmt {
|
||||
return new DeclareVarStmt(this.name, this.value, type, modifiers, this.sourceSpan);
|
||||
}
|
||||
|
||||
toConstDecl(): DeclareVarStmt { return this.toDeclStmt(INFERRED_TYPE, [StmtModifier.Final]); }
|
||||
}
|
||||
|
||||
|
||||
|
@ -45,6 +45,8 @@ export class Identifiers {
|
||||
|
||||
static containerCreate: o.ExternalReference = {name: 'ɵC', moduleName: CORE};
|
||||
|
||||
static nextContext: o.ExternalReference = {name: 'ɵx', moduleName: CORE};
|
||||
|
||||
static text: o.ExternalReference = {name: 'ɵT', moduleName: CORE};
|
||||
|
||||
static textBinding: o.ExternalReference = {name: 'ɵt', moduleName: CORE};
|
||||
|
@ -149,7 +149,7 @@ export function compileComponentFromMetadata(
|
||||
const template = meta.template;
|
||||
const templateFunctionExpression =
|
||||
new TemplateDefinitionBuilder(
|
||||
constantPool, CONTEXT_NAME, BindingScope.ROOT_SCOPE, 0, templateTypeName, templateName,
|
||||
constantPool, BindingScope.ROOT_SCOPE, 0, templateTypeName, templateName,
|
||||
meta.viewQueries, directiveMatcher, directivesUsed, meta.pipes, pipesUsed,
|
||||
R3.namespaceHTML)
|
||||
.buildTemplateFunction(
|
||||
|
@ -57,12 +57,33 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
||||
private _bindingContext = 0;
|
||||
private _prefixCode: o.Statement[] = [];
|
||||
private _creationCode: o.Statement[] = [];
|
||||
private _variableCode: o.Statement[] = [];
|
||||
private _bindingCode: (() => o.Statement)[] = [];
|
||||
private _nestedTemplates: (() => void)[] = [];
|
||||
/**
|
||||
* List of callbacks to generate update mode instructions. We store them here as we process
|
||||
* the template so bindings are resolved only once all nodes have been visited. This ensures
|
||||
* all local refs and context variables are available for matching.
|
||||
*/
|
||||
private _updateCodeFns: (() => o.Statement)[] = [];
|
||||
/** Temporary variable declarations generated from visiting pipes, literals, etc. */
|
||||
private _tempVariables: o.Statement[] = [];
|
||||
/**
|
||||
* List of callbacks to build nested templates. Nested templates must not be visited until
|
||||
* after the parent template has finished visiting all of its nodes. This ensures that all
|
||||
* local ref bindings in nested templates are able to find local ref values if the refs
|
||||
* are defined after the template declaration.
|
||||
*/
|
||||
private _nestedTemplateFns: (() => void)[] = [];
|
||||
/**
|
||||
* This scope contains local variables declared in the update mode block of the template.
|
||||
* (e.g. refs and context vars in bindings)
|
||||
*/
|
||||
private _updateScope: BindingScope;
|
||||
/**
|
||||
* This scope contains local variables declared in the creation mode block of the template
|
||||
* (e.g. refs and context vars in listeners)
|
||||
*/
|
||||
private _creationScope: BindingScope;
|
||||
private _valueConverter: ValueConverter;
|
||||
private _unsupported = unsupported;
|
||||
private _bindingScope: BindingScope;
|
||||
|
||||
// Whether we are inside a translatable element (`<p i18n>... somewhere here ... </p>)
|
||||
private _inI18nSection: boolean = false;
|
||||
@ -74,20 +95,19 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
||||
private _pureFunctionSlots = 0;
|
||||
|
||||
constructor(
|
||||
private constantPool: ConstantPool, private contextParameter: string,
|
||||
parentBindingScope: BindingScope, private level = 0, private contextName: string|null,
|
||||
private templateName: string|null, private viewQueries: R3QueryMetadata[],
|
||||
private directiveMatcher: SelectorMatcher|null, private directives: Set<o.Expression>,
|
||||
private pipeTypeByName: Map<string, o.Expression>, private pipes: Set<o.Expression>,
|
||||
private _namespace: o.ExternalReference) {
|
||||
private constantPool: ConstantPool, parentBindingScope: BindingScope, private level = 0,
|
||||
private contextName: string|null, private templateName: string|null,
|
||||
private viewQueries: R3QueryMetadata[], private directiveMatcher: SelectorMatcher|null,
|
||||
private directives: Set<o.Expression>, private pipeTypeByName: Map<string, o.Expression>,
|
||||
private pipes: Set<o.Expression>, private _namespace: o.ExternalReference) {
|
||||
// view queries can take up space in data and allocation happens earlier (in the "viewQuery"
|
||||
// function)
|
||||
this._dataIndex = viewQueries.length;
|
||||
this._bindingScope =
|
||||
parentBindingScope.nestedScope(level, (lhsVar: o.ReadVarExpr, rhsExpr: o.Expression) => {
|
||||
this._variableCode.push(
|
||||
lhsVar.set(rhsExpr).toDeclStmt(o.INFERRED_TYPE, [o.StmtModifier.Final]));
|
||||
});
|
||||
|
||||
// TODO(kara): generate restore instruction in listener to replace creation scope
|
||||
this._creationScope = parentBindingScope.nestedScope(level);
|
||||
this._updateScope = parentBindingScope.nestedScope(level);
|
||||
|
||||
this._valueConverter = new ValueConverter(
|
||||
constantPool, () => this.allocateDataSlot(),
|
||||
(numSlots: number): number => this._pureFunctionSlots += numSlots,
|
||||
@ -96,12 +116,33 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
||||
if (pipeType) {
|
||||
this.pipes.add(pipeType);
|
||||
}
|
||||
this._bindingScope.set(localName, value);
|
||||
this._updateScope.set(this.level, localName, value);
|
||||
this._creationCode.push(
|
||||
o.importExpr(R3.pipe).callFn([o.literal(slot), o.literal(name)]).toStmt());
|
||||
});
|
||||
}
|
||||
|
||||
registerContextVariables(variable: t.Variable, retrievalScope: BindingScope) {
|
||||
const scopedName = retrievalScope.freshReferenceName();
|
||||
const retrievalLevel = this.level;
|
||||
const lhs = o.variable(variable.name + scopedName);
|
||||
retrievalScope.set(
|
||||
retrievalLevel, variable.name, lhs, DeclarationPriority.CONTEXT,
|
||||
(scope: BindingScope, relativeLevel: number) => {
|
||||
let rhs: o.Expression;
|
||||
if (scope.bindingLevel === retrievalLevel) {
|
||||
// e.g. ctx
|
||||
rhs = o.variable(CONTEXT_NAME);
|
||||
} else {
|
||||
const sharedCtxVar = scope.getSharedContextName(retrievalLevel);
|
||||
// e.g. ctx_r0 OR x(2);
|
||||
rhs = sharedCtxVar ? sharedCtxVar : generateNextContextExpr(relativeLevel);
|
||||
}
|
||||
// e.g. const $item$ = x(2).$implicit;
|
||||
return [lhs.set(rhs.prop(variable.value || IMPLICIT_REFERENCE)).toConstDecl()];
|
||||
});
|
||||
}
|
||||
|
||||
buildTemplateFunction(
|
||||
nodes: t.Node[], variables: t.Variable[], hasNgContent: boolean = false,
|
||||
ngContentSelectors: string[] = []): o.FunctionExpr {
|
||||
@ -111,12 +152,9 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
||||
|
||||
// Create variable bindings
|
||||
for (const variable of variables) {
|
||||
const variableName = variable.name;
|
||||
const expression =
|
||||
o.variable(this.contextParameter).prop(variable.value || IMPLICIT_REFERENCE);
|
||||
const scopedName = this._bindingScope.freshReferenceName();
|
||||
// Add the reference to the local scope.
|
||||
this._bindingScope.set(variableName, o.variable(variableName + scopedName), expression);
|
||||
this.registerContextVariables(variable, this._creationScope);
|
||||
this.registerContextVariables(variable, this._updateScope);
|
||||
}
|
||||
|
||||
// Output a `ProjectionDef` instruction when some `<ng-content>` are present
|
||||
@ -135,42 +173,50 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
||||
this.creationInstruction(null, R3.projectionDef, ...parameters);
|
||||
}
|
||||
|
||||
// This is the initial pass through the nodes of this template. In this pass, we
|
||||
// generate all creation mode instructions & queue all update mode instructions for
|
||||
// generation in the second pass. It's necessary to separate the passes to ensure
|
||||
// local refs are defined before resolving bindings.
|
||||
t.visitAll(this, nodes);
|
||||
|
||||
const creationCode = this._creationCode.length > 0 ?
|
||||
[renderFlagCheckIfStmt(core.RenderFlags.Create, this._creationCode)] :
|
||||
[];
|
||||
|
||||
const updateCode = this._bindingCode.length > 0 ?
|
||||
[renderFlagCheckIfStmt(core.RenderFlags.Update, this._variableCode.concat(
|
||||
this._variableCode.concat(this._bindingCode.map((fn: () => o.Statement) => fn()))))] :
|
||||
[];
|
||||
// Generate all the update mode instructions as the second pass (e.g. resolve bindings)
|
||||
const updateStatements = this._updateCodeFns.map((fn: () => o.Statement) => fn());
|
||||
|
||||
// To count slots for the reserveSlots() instruction, all bindings must have been visited.
|
||||
if (this._pureFunctionSlots > 0) {
|
||||
this.creationInstruction(null, R3.reserveSlots, o.literal(this._pureFunctionSlots));
|
||||
}
|
||||
|
||||
const creationCode = this._creationCode.length > 0 ?
|
||||
[renderFlagCheckIfStmt(
|
||||
core.RenderFlags.Create,
|
||||
this._creationScope.variableDeclarations().concat(this._creationCode))] :
|
||||
[];
|
||||
|
||||
// This must occur after binding resolution so we can generate context instructions that
|
||||
// build on each other. e.g. const row = x().$implicit; const table = x().$implicit();
|
||||
const updateVariables = this._updateScope.variableDeclarations().concat(this._tempVariables);
|
||||
|
||||
const updateCode = this._updateCodeFns.length > 0 ?
|
||||
[renderFlagCheckIfStmt(core.RenderFlags.Update, updateVariables.concat(updateStatements))] :
|
||||
[];
|
||||
|
||||
// Generate maps of placeholder name to node indexes
|
||||
// TODO(vicb): This is a WIP, not fully supported yet
|
||||
for (const phToNodeIdx of this._phToNodeIdxes) {
|
||||
if (Object.keys(phToNodeIdx).length > 0) {
|
||||
const scopedName = this._bindingScope.freshReferenceName();
|
||||
const phMap = o.variable(scopedName)
|
||||
.set(mapToExpression(phToNodeIdx, true))
|
||||
.toDeclStmt(o.INFERRED_TYPE, [o.StmtModifier.Final]);
|
||||
const scopedName = this._updateScope.freshReferenceName();
|
||||
const phMap = o.variable(scopedName).set(mapToExpression(phToNodeIdx, true)).toConstDecl();
|
||||
|
||||
this._prefixCode.push(phMap);
|
||||
}
|
||||
}
|
||||
|
||||
this._nestedTemplates.forEach(buildTemplateFn => buildTemplateFn());
|
||||
this._nestedTemplateFns.forEach(buildTemplateFn => buildTemplateFn());
|
||||
|
||||
return o.fn(
|
||||
// i.e. (rf: RenderFlags, ctx0: any, ctx: any)
|
||||
[
|
||||
new o.FnParam(RENDER_FLAGS, o.NUMBER_TYPE), ...this.getNestedContexts(),
|
||||
new o.FnParam(CONTEXT_NAME, null)
|
||||
],
|
||||
// i.e. (rf: RenderFlags, ctx: any)
|
||||
[new o.FnParam(RENDER_FLAGS, o.NUMBER_TYPE), new o.FnParam(CONTEXT_NAME, null)],
|
||||
[
|
||||
// Temporary variable declarations for query refresh (i.e. let _t: any;)
|
||||
...this._prefixCode,
|
||||
@ -183,7 +229,7 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
||||
}
|
||||
|
||||
// LocalResolver
|
||||
getLocal(name: string): o.Expression|null { return this._bindingScope.get(name); }
|
||||
getLocal(name: string): o.Expression|null { return this._updateScope.get(name); }
|
||||
|
||||
visitContent(ngContent: t.Content) {
|
||||
const slot = this.allocateDataSlot();
|
||||
@ -225,18 +271,6 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
||||
this.creationInstruction(element.sourceSpan, nsInstruction);
|
||||
}
|
||||
|
||||
getNestedContexts(): o.FnParam[] {
|
||||
const nestedContexts = [];
|
||||
let nestingLevel = this.level - 1;
|
||||
|
||||
while (nestingLevel >= 0) {
|
||||
nestedContexts.push(new o.FnParam(`ctx${nestingLevel}`, null));
|
||||
nestingLevel--;
|
||||
}
|
||||
|
||||
return nestedContexts;
|
||||
}
|
||||
|
||||
visitElement(element: t.Element) {
|
||||
const elementIndex = this.allocateDataSlot();
|
||||
const wasInI18nSection = this._inI18nSection;
|
||||
@ -426,17 +460,19 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
||||
const references = flatten(element.references.map(reference => {
|
||||
const slot = this.allocateDataSlot();
|
||||
// Generate the update temporary.
|
||||
const variableName = this._bindingScope.freshReferenceName();
|
||||
// When the ref's binding is processed, we'll either generate a load() or a reference()
|
||||
// instruction depending on the nesting level of the binding relative to the reference def.
|
||||
const refLevel = this.level;
|
||||
this._bindingScope.set(
|
||||
reference.name, o.variable(variableName), undefined, (bindingLevel: number) => {
|
||||
return bindingLevel === refLevel ?
|
||||
o.importExpr(R3.load).callFn([o.literal(slot)]) :
|
||||
o.importExpr(R3.reference).callFn([
|
||||
o.literal(bindingLevel - refLevel), o.literal(slot)
|
||||
]);
|
||||
const variableName = this._updateScope.freshReferenceName();
|
||||
const retrievalLevel = this.level;
|
||||
const lhs = o.variable(variableName);
|
||||
this._updateScope.set(
|
||||
retrievalLevel, reference.name, lhs, DeclarationPriority.DEFAULT,
|
||||
(scope: BindingScope, relativeLevel: number) => {
|
||||
// e.g. x(2);
|
||||
const nextContextStmt =
|
||||
relativeLevel > 0 ? [generateNextContextExpr(relativeLevel).toStmt()] : [];
|
||||
|
||||
// e.g. const $foo$ = r(1);
|
||||
const refExpr = lhs.set(o.importExpr(R3.reference).callFn([o.literal(slot)]));
|
||||
return nextContextStmt.concat(refExpr.toConstDecl());
|
||||
});
|
||||
return [reference.name, reference.value];
|
||||
}));
|
||||
@ -515,17 +551,11 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
||||
const elName = sanitizeIdentifier(element.name);
|
||||
const evName = sanitizeIdentifier(outputAst.name);
|
||||
const functionName = `${this.templateName}_${elName}_${evName}_listener`;
|
||||
const localVars: o.Statement[] = [];
|
||||
const bindingScope = this._bindingScope.nestedScope(
|
||||
this.level + 1, (lhsVar: o.ReadVarExpr, rhsExpression: o.Expression) => {
|
||||
localVars.push(
|
||||
lhsVar.set(rhsExpression).toDeclStmt(o.INFERRED_TYPE, [o.StmtModifier.Final]));
|
||||
});
|
||||
const bindingExpr = convertActionBinding(
|
||||
bindingScope, implicit, outputAst.handler, 'b',
|
||||
this._creationScope, implicit, outputAst.handler, 'b',
|
||||
() => error('Unexpected interpolation'));
|
||||
const handler = o.fn(
|
||||
[new o.FnParam('$event', o.DYNAMIC_TYPE)], [...localVars, ...bindingExpr.render3Stmts],
|
||||
[new o.FnParam('$event', o.DYNAMIC_TYPE)], [...bindingExpr.render3Stmts],
|
||||
o.INFERRED_TYPE, null, functionName);
|
||||
this.creationInstruction(
|
||||
outputAst.sourceSpan, R3.listener, o.literal(outputAst.name), handler);
|
||||
@ -668,8 +698,6 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
||||
const templateName =
|
||||
contextName ? `${contextName}_Template_${templateIndex}` : `Template_${templateIndex}`;
|
||||
|
||||
const templateContext = `ctx${this.level}`;
|
||||
|
||||
const parameters: o.Expression[] = [
|
||||
o.literal(templateIndex),
|
||||
o.variable(templateName),
|
||||
@ -713,15 +741,14 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
||||
|
||||
// Create the template function
|
||||
const templateVisitor = new TemplateDefinitionBuilder(
|
||||
this.constantPool, templateContext, this._bindingScope, this.level + 1, contextName,
|
||||
templateName, [], this.directiveMatcher, this.directives, this.pipeTypeByName, this.pipes,
|
||||
this._namespace);
|
||||
this.constantPool, this._updateScope, this.level + 1, contextName, templateName, [],
|
||||
this.directiveMatcher, this.directives, this.pipeTypeByName, this.pipes, this._namespace);
|
||||
|
||||
// Nested templates must not be visited until after their parent templates have completed
|
||||
// processing, so they are queued here until after the initial pass. Otherwise, we wouldn't
|
||||
// be able to support bindings in nested templates to local refs that occur after the
|
||||
// template definition. e.g. <div *ngIf="showing"> {{ foo }} </div> <div #foo></div>
|
||||
this._nestedTemplates.push(() => {
|
||||
this._nestedTemplateFns.push(() => {
|
||||
const templateFunctionExpr =
|
||||
templateVisitor.buildTemplateFunction(template.children, template.variables);
|
||||
this.constantPool.statements.push(templateFunctionExpr.toDeclStmt(templateName, null));
|
||||
@ -790,7 +817,7 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
||||
// bindings. e.g. {{ foo }} <div #foo></div>
|
||||
private updateInstruction(
|
||||
span: ParseSourceSpan|null, reference: o.ExternalReference, paramsFn: () => o.Expression[]) {
|
||||
this._bindingCode.push(() => { return this.instruction(span, reference, paramsFn()); });
|
||||
this._updateCodeFns.push(() => { return this.instruction(span, reference, paramsFn()); });
|
||||
}
|
||||
|
||||
private convertPropertyBinding(implicit: o.Expression, value: AST, skipBindFn?: boolean):
|
||||
@ -800,7 +827,7 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
|
||||
|
||||
const convertedPropertyBinding = convertPropertyBinding(
|
||||
this, implicit, value, this.bindingContext(), BindingForm.TrySimple, interpolationFn);
|
||||
this._variableCode.push(...convertedPropertyBinding.stmts);
|
||||
this._tempVariables.push(...convertedPropertyBinding.stmts);
|
||||
|
||||
const valExpr = convertedPropertyBinding.currValExpr;
|
||||
return value instanceof Interpolation || skipBindFn ? valExpr :
|
||||
@ -888,6 +915,12 @@ function pureFunctionCallInfo(args: o.Expression[]) {
|
||||
};
|
||||
}
|
||||
|
||||
// e.g. x(2);
|
||||
function generateNextContextExpr(relativeLevelDiff: number): o.Expression {
|
||||
return o.importExpr(R3.nextContext)
|
||||
.callFn(relativeLevelDiff > 1 ? [o.literal(relativeLevelDiff)] : []);
|
||||
}
|
||||
|
||||
function getLiteralFactory(
|
||||
constantPool: ConstantPool, literal: o.LiteralArrayExpr | o.LiteralMapExpr,
|
||||
allocateSlots: (numSlots: number) => number): o.Expression {
|
||||
@ -919,33 +952,44 @@ function getLiteralFactory(
|
||||
*
|
||||
* It is expected that the function creates the `const localName = expression`; statement.
|
||||
*/
|
||||
export type DeclareLocalVarCallback = (lhsVar: o.ReadVarExpr, rhsExpression: o.Expression) => void;
|
||||
export type DeclareLocalVarCallback = (scope: BindingScope, relativeLevel: number) => o.Statement[];
|
||||
|
||||
/** The prefix used to get a shared context in BindingScope's map. */
|
||||
const SHARED_CONTEXT_KEY = '$$shared_ctx$$';
|
||||
|
||||
/**
|
||||
* This is used when one refers to variable such as: 'let abc = x(2).$implicit`.
|
||||
* - key to the map is the string literal `"abc"`.
|
||||
* - value `retrievalLevel` is the level from which this value can be retrieved, which is 2 levels
|
||||
* up in example.
|
||||
* - value `lhs` is the left hand side which is an AST representing `abc`.
|
||||
* - value `declareLocalCallback` is a callback that is invoked when declaring the local.
|
||||
* - value `declare` is true if this value needs to be declared.
|
||||
* - value `priority` dictates the sorting priority of this var declaration compared
|
||||
* to other var declarations on the same retrieval level. For example, if there is a
|
||||
* context variable and a local ref accessing the same parent view, the context var
|
||||
* declaration should always come before the local ref declaration.
|
||||
*/
|
||||
type BindingData = {
|
||||
retrievalLevel: number; lhs: o.ReadVarExpr; declareLocalCallback?: DeclareLocalVarCallback;
|
||||
declare: boolean;
|
||||
priority: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* The sorting priority of a local variable declaration. Higher numbers
|
||||
* mean the declaration will appear first in the generated code.
|
||||
*/
|
||||
const enum DeclarationPriority { DEFAULT = 0, CONTEXT = 1, SHARED_CONTEXT = 2 }
|
||||
|
||||
export class BindingScope implements LocalResolver {
|
||||
/**
|
||||
* Keeps a map from local variables to their expressions.
|
||||
*
|
||||
* This is used when one refers to variable such as: 'let abc = a.b.c`.
|
||||
* - key to the map is the string literal `"abc"`.
|
||||
* - value `lhs` is the left hand side which is an AST representing `abc`.
|
||||
* - value `rhs` is the right hand side which is an AST representing `a.b.c`.
|
||||
* - value `declared` is true if the `declareLocalVarCallback` has been called for this scope
|
||||
* already.
|
||||
*/
|
||||
private map = new Map < string, {
|
||||
lhs: o.ReadVarExpr;
|
||||
rhs: o.Expression|undefined;
|
||||
declared: boolean;
|
||||
rhsCallback?: (level: number) => o.Expression;
|
||||
}
|
||||
> ();
|
||||
/** Keeps a map from local variables to their BindingData. */
|
||||
private map = new Map<string, BindingData>();
|
||||
private referenceNameIndex = 0;
|
||||
|
||||
static ROOT_SCOPE = new BindingScope().set('$event', o.variable('$event'));
|
||||
static ROOT_SCOPE = new BindingScope().set(-1, '$event', o.variable('$event'));
|
||||
|
||||
private constructor(
|
||||
private level: number = 0, private parent: BindingScope|null = null,
|
||||
private declareLocalVarCallback: DeclareLocalVarCallback = noop) {}
|
||||
private constructor(public bindingLevel: number = 0, private parent: BindingScope|null = null) {}
|
||||
|
||||
get(name: string): o.Expression|null {
|
||||
let current: BindingScope|null = this;
|
||||
@ -953,48 +997,118 @@ export class BindingScope implements LocalResolver {
|
||||
let value = current.map.get(name);
|
||||
if (value != null) {
|
||||
if (current !== this) {
|
||||
// make a local copy and reset the `declared` state.
|
||||
value = {lhs: value.lhs, rhs: value.rhs, rhsCallback: value.rhsCallback, declared: false};
|
||||
// make a local copy and reset the `declare` state
|
||||
value = {
|
||||
retrievalLevel: value.retrievalLevel,
|
||||
lhs: value.lhs,
|
||||
declareLocalCallback: value.declareLocalCallback,
|
||||
declare: false,
|
||||
priority: value.priority
|
||||
};
|
||||
|
||||
// Cache the value locally.
|
||||
this.map.set(name, value);
|
||||
// Possibly generate a shared context var
|
||||
this.maybeGenerateSharedContextVar(value);
|
||||
}
|
||||
const rhs = value.rhs || value.rhsCallback && value.rhsCallback(this.level);
|
||||
if (rhs && !value.declared) {
|
||||
// if it is first time we are referencing the variable in the scope
|
||||
// then invoke the callback to insert variable declaration.
|
||||
this.declareLocalVarCallback(value.lhs, rhs);
|
||||
value.declared = true;
|
||||
|
||||
if (value.declareLocalCallback && !value.declare) {
|
||||
value.declare = true;
|
||||
}
|
||||
return value.lhs;
|
||||
}
|
||||
current = current.parent;
|
||||
}
|
||||
return null;
|
||||
|
||||
// If we get to this point, we are looking for a property on the top level component
|
||||
// - If level === 0, we are on the top and don't need to re-declare `ctx`.
|
||||
// - If level > 0, we are in an embedded view. We need to retrieve the name of the
|
||||
// local var we used to store the component context, e.g. const $comp$ = x();
|
||||
return this.bindingLevel === 0 ? null : this.getComponentProperty(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a local variable for later reference.
|
||||
*
|
||||
* @param retrievalLevel The level from which this value can be retrieved
|
||||
* @param name Name of the variable.
|
||||
* @param lhs AST representing the left hand side of the `let lhs = rhs;`.
|
||||
* @param rhs AST representing the right hand side of the `let lhs = rhs;`. The `rhs` can be
|
||||
* `undefined` for variable that are ambient such as `$event` and which don't have `rhs`
|
||||
* declaration.
|
||||
* @param priority The sorting priority of this var
|
||||
* @param declareLocalCallback The callback to invoke when declaring this local var
|
||||
*/
|
||||
set(name: string, lhs: o.ReadVarExpr, rhs?: o.Expression,
|
||||
rhsCallback?: (level: number) => o.Expression): BindingScope {
|
||||
set(retrievalLevel: number, name: string, lhs: o.ReadVarExpr,
|
||||
priority: number = DeclarationPriority.DEFAULT,
|
||||
declareLocalCallback?: DeclareLocalVarCallback): BindingScope {
|
||||
!this.map.has(name) ||
|
||||
error(`The name ${name} is already defined in scope to be ${this.map.get(name)}`);
|
||||
this.map.set(name, {lhs: lhs, rhs: rhs, declared: false, rhsCallback: rhsCallback});
|
||||
this.map.set(name, {
|
||||
retrievalLevel: retrievalLevel,
|
||||
lhs: lhs,
|
||||
declare: false,
|
||||
declareLocalCallback: declareLocalCallback,
|
||||
priority: priority
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
getLocal(name: string): (o.Expression|null) { return this.get(name); }
|
||||
|
||||
nestedScope(level: number, declareCallback: DeclareLocalVarCallback): BindingScope {
|
||||
return new BindingScope(level, this, declareCallback);
|
||||
nestedScope(level: number): BindingScope {
|
||||
const newScope = new BindingScope(level, this);
|
||||
if (level > 0) newScope.generateSharedContextVar(0);
|
||||
return newScope;
|
||||
}
|
||||
|
||||
getSharedContextName(retrievalLevel: number): o.ReadVarExpr|null {
|
||||
const sharedCtxObj = this.map.get(SHARED_CONTEXT_KEY + retrievalLevel);
|
||||
return sharedCtxObj && sharedCtxObj.declare ? sharedCtxObj.lhs : null;
|
||||
}
|
||||
|
||||
maybeGenerateSharedContextVar(value: BindingData) {
|
||||
if (value.priority === DeclarationPriority.CONTEXT) {
|
||||
const sharedCtxObj = this.map.get(SHARED_CONTEXT_KEY + value.retrievalLevel);
|
||||
if (sharedCtxObj) {
|
||||
sharedCtxObj.declare = true;
|
||||
} else {
|
||||
this.generateSharedContextVar(value.retrievalLevel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
generateSharedContextVar(retrievalLevel: number) {
|
||||
const lhs = o.variable(CONTEXT_NAME + this.freshReferenceName());
|
||||
this.map.set(SHARED_CONTEXT_KEY + retrievalLevel, {
|
||||
retrievalLevel: retrievalLevel,
|
||||
lhs: lhs,
|
||||
declareLocalCallback: (scope: BindingScope, relativeLevel: number) => {
|
||||
// const ctx_r0 = x(2);
|
||||
return [lhs.set(generateNextContextExpr(relativeLevel)).toConstDecl()];
|
||||
},
|
||||
declare: false,
|
||||
priority: DeclarationPriority.SHARED_CONTEXT
|
||||
});
|
||||
}
|
||||
|
||||
getComponentProperty(name: string): o.Expression {
|
||||
const componentValue = this.map.get(SHARED_CONTEXT_KEY + 0) !;
|
||||
componentValue.declare = true;
|
||||
return componentValue.lhs.prop(name);
|
||||
}
|
||||
|
||||
variableDeclarations(): o.Statement[] {
|
||||
let currentContextLevel = 0;
|
||||
return Array.from(this.map.values())
|
||||
.filter(value => value.declare)
|
||||
.sort((a, b) => b.retrievalLevel - a.retrievalLevel || b.priority - a.priority)
|
||||
.reduce((stmts: o.Statement[], value: BindingData) => {
|
||||
const levelDiff = this.bindingLevel - value.retrievalLevel;
|
||||
const currStmts = value.declareLocalCallback !(this, levelDiff - currentContextLevel);
|
||||
currentContextLevel = levelDiff;
|
||||
return stmts.concat(currStmts);
|
||||
}, []) as o.Statement[];
|
||||
}
|
||||
|
||||
|
||||
freshReferenceName(): string {
|
||||
let current: BindingScope = this;
|
||||
// Find the top scope as it maintains the global reference count
|
||||
|
Reference in New Issue
Block a user