feat(ivy): support array and object literals in binding expressions (#22336)
PR Close #22336
This commit is contained in:

committed by
Alex Eagle

parent
ec445b5c73
commit
49f074f61d
@ -13,6 +13,14 @@ const CONSTANT_PREFIX = '_c';
|
||||
|
||||
export const enum DefinitionKind {Injector, Directive, Component, Pipe}
|
||||
|
||||
/**
|
||||
* Context to use when producing a key.
|
||||
*
|
||||
* This ensures we see the constant not the reference variable when producing
|
||||
* a key.
|
||||
*/
|
||||
const KEY_CONTEXT = {};
|
||||
|
||||
/**
|
||||
* A node that is a place-holder that allows the node to be replaced when the actual
|
||||
* node is known.
|
||||
@ -22,18 +30,31 @@ export const enum DefinitionKind {Injector, Directive, Component, Pipe}
|
||||
* change the referenced expression.
|
||||
*/
|
||||
class FixupExpression extends o.Expression {
|
||||
constructor(public resolved: o.Expression) { super(resolved.type); }
|
||||
private original: o.Expression;
|
||||
|
||||
shared: boolean;
|
||||
|
||||
constructor(public resolved: o.Expression) {
|
||||
super(resolved.type);
|
||||
this.original = resolved;
|
||||
}
|
||||
|
||||
visitExpression(visitor: o.ExpressionVisitor, context: any): any {
|
||||
return this.resolved.visitExpression(visitor, context);
|
||||
if (context === KEY_CONTEXT) {
|
||||
// When producing a key we want to traverse the constant not the
|
||||
// variable used to refer to it.
|
||||
return this.original.visitExpression(visitor, context);
|
||||
} else {
|
||||
return this.resolved.visitExpression(visitor, context);
|
||||
}
|
||||
}
|
||||
|
||||
isEquivalent(e: o.Expression): boolean {
|
||||
return e instanceof FixupExpression && this.resolved.isEquivalent(e.resolved);
|
||||
}
|
||||
|
||||
isConstant() { return true; }
|
||||
|
||||
fixup(expression: o.Expression) {
|
||||
this.resolved = expression;
|
||||
this.shared = true;
|
||||
@ -48,6 +69,7 @@ class FixupExpression extends o.Expression {
|
||||
export class ConstantPool {
|
||||
statements: o.Statement[] = [];
|
||||
private literals = new Map<string, FixupExpression>();
|
||||
private literalFactories = new Map<string, o.Expression>();
|
||||
private injectorDefinitions = new Map<any, FixupExpression>();
|
||||
private directiveDefinitions = new Map<any, FixupExpression>();
|
||||
private componentDefinitions = new Map<any, FixupExpression>();
|
||||
@ -56,6 +78,11 @@ export class ConstantPool {
|
||||
private nextNameIndex = 0;
|
||||
|
||||
getConstLiteral(literal: o.Expression, forceShared?: boolean): o.Expression {
|
||||
if (literal instanceof o.LiteralExpr || literal instanceof FixupExpression) {
|
||||
// Do no put simple literals into the constant pool or try to produce a constant for a
|
||||
// reference to a constant.
|
||||
return literal;
|
||||
}
|
||||
const key = this.keyOf(literal);
|
||||
let fixup = this.literals.get(key);
|
||||
let newValue = false;
|
||||
@ -97,6 +124,54 @@ export class ConstantPool {
|
||||
return fixup;
|
||||
}
|
||||
|
||||
getLiteralFactory(literal: o.LiteralArrayExpr|o.LiteralMapExpr):
|
||||
{literalFactory: o.Expression, literalFactoryArguments: o.Expression[]} {
|
||||
// Create a pure function that builds an array of a mix of constant and variable expressions
|
||||
if (literal instanceof o.LiteralArrayExpr) {
|
||||
const argumentsForKey = literal.entries.map(e => e.isConstant() ? e : o.literal(null));
|
||||
const key = this.keyOf(o.literalArr(argumentsForKey));
|
||||
return this._getLiteralFactory(key, literal.entries, entries => o.literalArr(entries));
|
||||
} else {
|
||||
const expressionForKey = o.literalMap(
|
||||
literal.entries.map(e => ({
|
||||
key: e.key,
|
||||
value: e.value.isConstant() ? e.value : o.literal(null),
|
||||
quoted: e.quoted
|
||||
})));
|
||||
const key = this.keyOf(expressionForKey);
|
||||
return this._getLiteralFactory(
|
||||
key, literal.entries.map(e => e.value),
|
||||
entries => o.literalMap(entries.map((value, index) => ({
|
||||
key: literal.entries[index].key,
|
||||
value,
|
||||
quoted: literal.entries[index].quoted
|
||||
}))));
|
||||
}
|
||||
}
|
||||
|
||||
private _getLiteralFactory(
|
||||
key: string, values: o.Expression[], resultMap: (parameters: o.Expression[]) => o.Expression):
|
||||
{literalFactory: o.Expression, literalFactoryArguments: o.Expression[]} {
|
||||
let literalFactory = this.literalFactories.get(key);
|
||||
const literalFactoryArguments = values.filter((e => !e.isConstant()));
|
||||
if (!literalFactory) {
|
||||
const resultExpressions = values.map(
|
||||
(e, index) => e.isConstant() ? this.getConstLiteral(e, true) : o.variable(`a${index}`));
|
||||
const parameters =
|
||||
resultExpressions.filter(isVariable).map(e => new o.FnParam(e.name !, o.DYNAMIC_TYPE));
|
||||
const pureFunctionDeclaration =
|
||||
o.fn(parameters, [new o.ReturnStatement(resultMap(resultExpressions))], o.INFERRED_TYPE);
|
||||
const name = this.freshName();
|
||||
this.statements.push(
|
||||
o.variable(name).set(pureFunctionDeclaration).toDeclStmt(o.INFERRED_TYPE, [
|
||||
o.StmtModifier.Final
|
||||
]));
|
||||
literalFactory = o.variable(name);
|
||||
this.literalFactories.set(key, literalFactory);
|
||||
}
|
||||
return {literalFactory, literalFactoryArguments};
|
||||
}
|
||||
|
||||
/**
|
||||
* Produce a unique name.
|
||||
*
|
||||
@ -139,7 +214,7 @@ export class ConstantPool {
|
||||
private freshName(): string { return this.uniqueName(CONSTANT_PREFIX); }
|
||||
|
||||
private keyOf(expression: o.Expression) {
|
||||
return expression.visitExpression(new KeyVisitor(), null);
|
||||
return expression.visitExpression(new KeyVisitor(), KEY_CONTEXT);
|
||||
}
|
||||
}
|
||||
|
||||
@ -147,13 +222,13 @@ class KeyVisitor implements o.ExpressionVisitor {
|
||||
visitLiteralExpr(ast: o.LiteralExpr): string {
|
||||
return `${typeof ast.value === 'string' ? '"' + ast.value + '"' : ast.value}`;
|
||||
}
|
||||
visitLiteralArrayExpr(ast: o.LiteralArrayExpr): string {
|
||||
return `[${ast.entries.map(entry => entry.visitExpression(this, null)).join(',')}]`;
|
||||
visitLiteralArrayExpr(ast: o.LiteralArrayExpr, context: object): string {
|
||||
return `[${ast.entries.map(entry => entry.visitExpression(this, context)).join(',')}]`;
|
||||
}
|
||||
|
||||
visitLiteralMapExpr(ast: o.LiteralMapExpr): string {
|
||||
visitLiteralMapExpr(ast: o.LiteralMapExpr, context: object): string {
|
||||
const mapEntry = (entry: o.LiteralMapEntry) =>
|
||||
`${entry.key}:${entry.value.visitExpression(this, null)}`;
|
||||
`${entry.key}:${entry.value.visitExpression(this, context)}`;
|
||||
return `{${ast.entries.map(mapEntry).join(',')}`;
|
||||
}
|
||||
|
||||
@ -184,3 +259,7 @@ function invalid<T>(arg: o.Expression | o.Statement): never {
|
||||
throw new Error(
|
||||
`Invalid state: Visitor ${this.constructor.name} doesn't handle ${o.constructor.name}`);
|
||||
}
|
||||
|
||||
function isVariable(e: o.Expression): e is o.ReadVarExpr {
|
||||
return e instanceof o.ReadVarExpr;
|
||||
}
|
Reference in New Issue
Block a user