feat(ivy): support array and object literals in binding expressions (#22336)

PR Close #22336
This commit is contained in:
Chuck Jazdzewski
2018-02-14 17:12:05 -08:00
committed by Alex Eagle
parent ec445b5c73
commit 49f074f61d
6 changed files with 503 additions and 18 deletions

View File

@ -97,7 +97,7 @@ export enum BindingForm {
General,
// Try to generate a simple binding (no temporaries or statements)
// otherise generate a general binding
// otherwise generate a general binding
TrySimple,
}
@ -341,7 +341,7 @@ class _AstToIrVisitor implements cdAst.AstVisitor {
}
visitLiteralPrimitive(ast: cdAst.LiteralPrimitive, mode: _Mode): any {
// For literal values of null, undefined, true, or false allow type inteference
// For literal values of null, undefined, true, or false allow type interference
// to infer the type.
const type =
ast.value === null || ast.value === undefined || ast.value === true || ast.value === true ?
@ -648,7 +648,7 @@ function convertStmtIntoExpression(stmt: o.Statement): o.Expression|null {
return null;
}
class BuiltinFunctionCall extends cdAst.FunctionCall {
export class BuiltinFunctionCall extends cdAst.FunctionCall {
constructor(span: cdAst.ParseSpan, public args: cdAst.AST[], public converter: BuiltinConverter) {
super(span, null, args);
}

View File

@ -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;
}

View File

@ -144,6 +144,11 @@ export abstract class Expression {
*/
abstract isEquivalent(e: Expression): boolean;
/**
* Return true if the expression is constant.
*/
abstract isConstant(): boolean;
prop(name: string, sourceSpan?: ParseSourceSpan|null): ReadPropExpr {
return new ReadPropExpr(this, name, null, sourceSpan);
}
@ -250,10 +255,13 @@ export class ReadVarExpr extends Expression {
this.builtin = <BuiltinVar>name;
}
}
isEquivalent(e: Expression): boolean {
return e instanceof ReadVarExpr && this.name === e.name && this.builtin === e.builtin;
}
isConstant() { return false; }
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitReadVarExpr(this, context);
}
@ -274,10 +282,13 @@ export class WriteVarExpr extends Expression {
super(type || value.type, sourceSpan);
this.value = value;
}
isEquivalent(e: Expression): boolean {
return e instanceof WriteVarExpr && this.name === e.name && this.value.isEquivalent(e.value);
}
isConstant() { return false; }
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitWriteVarExpr(this, context);
}
@ -296,10 +307,14 @@ export class WriteKeyExpr extends Expression {
super(type || value.type, sourceSpan);
this.value = value;
}
isEquivalent(e: Expression): boolean {
return e instanceof WriteKeyExpr && this.receiver.isEquivalent(e.receiver) &&
this.index.isEquivalent(e.index) && this.value.isEquivalent(e.value);
}
isConstant() { return false; }
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitWriteKeyExpr(this, context);
}
@ -314,10 +329,14 @@ export class WritePropExpr extends Expression {
super(type || value.type, sourceSpan);
this.value = value;
}
isEquivalent(e: Expression): boolean {
return e instanceof WritePropExpr && this.receiver.isEquivalent(e.receiver) &&
this.name === e.name && this.value.isEquivalent(e.value);
}
isConstant() { return false; }
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitWritePropExpr(this, context);
}
@ -344,10 +363,14 @@ export class InvokeMethodExpr extends Expression {
this.builtin = <BuiltinMethod>method;
}
}
isEquivalent(e: Expression): boolean {
return e instanceof InvokeMethodExpr && this.receiver.isEquivalent(e.receiver) &&
this.name === e.name && this.builtin === e.builtin && areAllEquivalent(this.args, e.args);
}
isConstant() { return false; }
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitInvokeMethodExpr(this, context);
}
@ -360,10 +383,14 @@ export class InvokeFunctionExpr extends Expression {
sourceSpan?: ParseSourceSpan|null) {
super(type, sourceSpan);
}
isEquivalent(e: Expression): boolean {
return e instanceof InvokeFunctionExpr && this.fn.isEquivalent(e.fn) &&
areAllEquivalent(this.args, e.args);
}
isConstant() { return false; }
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitInvokeFunctionExpr(this, context);
}
@ -376,10 +403,14 @@ export class InstantiateExpr extends Expression {
sourceSpan?: ParseSourceSpan|null) {
super(type, sourceSpan);
}
isEquivalent(e: Expression): boolean {
return e instanceof InstantiateExpr && this.classExpr.isEquivalent(e.classExpr) &&
areAllEquivalent(this.args, e.args);
}
isConstant() { return false; }
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitInstantiateExpr(this, context);
}
@ -392,9 +423,13 @@ export class LiteralExpr extends Expression {
sourceSpan?: ParseSourceSpan|null) {
super(type, sourceSpan);
}
isEquivalent(e: Expression): boolean {
return e instanceof LiteralExpr && this.value === e.value;
}
isConstant() { return true; }
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitLiteralExpr(this, context);
}
@ -407,10 +442,14 @@ export class ExternalExpr extends Expression {
sourceSpan?: ParseSourceSpan|null) {
super(type, sourceSpan);
}
isEquivalent(e: Expression): boolean {
return e instanceof ExternalExpr && this.value.name === e.value.name &&
this.value.moduleName === e.value.moduleName && this.value.runtime === e.value.runtime;
}
isConstant() { return false; }
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitExternalExpr(this, context);
}
@ -424,16 +463,21 @@ export class ExternalReference {
export class ConditionalExpr extends Expression {
public trueCase: Expression;
constructor(
public condition: Expression, trueCase: Expression, public falseCase: Expression|null = null,
type?: Type|null, sourceSpan?: ParseSourceSpan|null) {
super(type || trueCase.type, sourceSpan);
this.trueCase = trueCase;
}
isEquivalent(e: Expression): boolean {
return e instanceof ConditionalExpr && this.condition.isEquivalent(e.condition) &&
this.trueCase.isEquivalent(e.trueCase) && nullSafeIsEquivalent(this.falseCase, e.falseCase);
}
isConstant() { return false; }
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitConditionalExpr(this, context);
}
@ -444,9 +488,13 @@ export class NotExpr extends Expression {
constructor(public condition: Expression, sourceSpan?: ParseSourceSpan|null) {
super(BOOL_TYPE, sourceSpan);
}
isEquivalent(e: Expression): boolean {
return e instanceof NotExpr && this.condition.isEquivalent(e.condition);
}
isConstant() { return false; }
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitNotExpr(this, context);
}
@ -456,9 +504,13 @@ export class AssertNotNull extends Expression {
constructor(public condition: Expression, sourceSpan?: ParseSourceSpan|null) {
super(condition.type, sourceSpan);
}
isEquivalent(e: Expression): boolean {
return e instanceof AssertNotNull && this.condition.isEquivalent(e.condition);
}
isConstant() { return false; }
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitAssertNotNullExpr(this, context);
}
@ -468,9 +520,13 @@ export class CastExpr extends Expression {
constructor(public value: Expression, type?: Type|null, sourceSpan?: ParseSourceSpan|null) {
super(type, sourceSpan);
}
isEquivalent(e: Expression): boolean {
return e instanceof CastExpr && this.value.isEquivalent(e.value);
}
isConstant() { return false; }
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitCastExpr(this, context);
}
@ -490,10 +546,14 @@ export class FunctionExpr extends Expression {
sourceSpan?: ParseSourceSpan|null, public name?: string|null) {
super(type, sourceSpan);
}
isEquivalent(e: Expression): boolean {
return e instanceof FunctionExpr && areAllEquivalent(this.params, e.params) &&
areAllEquivalent(this.statements, e.statements);
}
isConstant() { return false; }
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitFunctionExpr(this, context);
}
@ -513,10 +573,14 @@ export class BinaryOperatorExpr extends Expression {
super(type || lhs.type, sourceSpan);
this.lhs = lhs;
}
isEquivalent(e: Expression): boolean {
return e instanceof BinaryOperatorExpr && this.operator === e.operator &&
this.lhs.isEquivalent(e.lhs) && this.rhs.isEquivalent(e.rhs);
}
isConstant() { return false; }
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitBinaryOperatorExpr(this, context);
}
@ -529,13 +593,18 @@ export class ReadPropExpr extends Expression {
sourceSpan?: ParseSourceSpan|null) {
super(type, sourceSpan);
}
isEquivalent(e: Expression): boolean {
return e instanceof ReadPropExpr && this.receiver.isEquivalent(e.receiver) &&
this.name === e.name;
}
isConstant() { return false; }
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitReadPropExpr(this, context);
}
set(value: Expression): WritePropExpr {
return new WritePropExpr(this.receiver, this.name, value, null, this.sourceSpan);
}
@ -548,13 +617,18 @@ export class ReadKeyExpr extends Expression {
sourceSpan?: ParseSourceSpan|null) {
super(type, sourceSpan);
}
isEquivalent(e: Expression): boolean {
return e instanceof ReadKeyExpr && this.receiver.isEquivalent(e.receiver) &&
this.index.isEquivalent(e.index);
}
isConstant() { return false; }
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitReadKeyExpr(this, context);
}
set(value: Expression): WriteKeyExpr {
return new WriteKeyExpr(this.receiver, this.index, value, null, this.sourceSpan);
}
@ -567,6 +641,9 @@ export class LiteralArrayExpr extends Expression {
super(type, sourceSpan);
this.entries = entries;
}
isConstant() { return this.entries.every(e => e.isConstant()); }
isEquivalent(e: Expression): boolean {
return e instanceof LiteralArrayExpr && areAllEquivalent(this.entries, e.entries);
}
@ -591,9 +668,13 @@ export class LiteralMapExpr extends Expression {
this.valueType = type.valueType;
}
}
isEquivalent(e: Expression): boolean {
return e instanceof LiteralMapExpr && areAllEquivalent(this.entries, e.entries);
}
isConstant() { return this.entries.every(e => e.value.isConstant()); }
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitLiteralMapExpr(this, context);
}
@ -603,9 +684,13 @@ export class CommaExpr extends Expression {
constructor(public parts: Expression[], sourceSpan?: ParseSourceSpan|null) {
super(parts[parts.length - 1].type, sourceSpan);
}
isEquivalent(e: Expression): boolean {
return e instanceof CommaExpr && areAllEquivalent(this.parts, e.parts);
}
isConstant() { return false; }
visitExpression(visitor: ExpressionVisitor, context: any): any {
return visitor.visitCommaExpr(this, context);
}

View File

@ -64,6 +64,17 @@ export class Identifiers {
static interpolation8: o.ExternalReference = {name: 'ɵi8', moduleName: CORE};
static interpolationV: o.ExternalReference = {name: 'ɵiV', moduleName: CORE};
static pureFunction0: o.ExternalReference = {name: 'ɵf0', moduleName: CORE};
static pureFunction1: o.ExternalReference = {name: 'ɵf1', moduleName: CORE};
static pureFunction2: o.ExternalReference = {name: 'ɵf2', moduleName: CORE};
static pureFunction3: o.ExternalReference = {name: 'ɵf3', moduleName: CORE};
static pureFunction4: o.ExternalReference = {name: 'ɵf4', moduleName: CORE};
static pureFunction5: o.ExternalReference = {name: 'ɵf5', moduleName: CORE};
static pureFunction6: o.ExternalReference = {name: 'ɵf6', moduleName: CORE};
static pureFunction7: o.ExternalReference = {name: 'ɵf7', moduleName: CORE};
static pureFunction8: o.ExternalReference = {name: 'ɵf8', moduleName: CORE};
static pureFunctionV: o.ExternalReference = {name: 'ɵfV', moduleName: CORE};
static pipeBind1: o.ExternalReference = {name: 'ɵpb1', moduleName: CORE};
static pipeBind2: o.ExternalReference = {name: 'ɵpb2', moduleName: CORE};
static pipeBind3: o.ExternalReference = {name: 'ɵpb3', moduleName: CORE};

View File

@ -8,9 +8,9 @@
import {CompileDirectiveMetadata, CompilePipeSummary, CompileTokenMetadata, CompileTypeMetadata, flatten, identifierName, rendererTypeName, tokenReference, viewClassName} from '../compile_metadata';
import {CompileReflector} from '../compile_reflector';
import {BindingForm, BuiltinConverter, ConvertPropertyBindingResult, EventHandlerVars, LocalResolver, convertActionBinding, convertPropertyBinding, convertPropertyBindingBuiltins} from '../compiler_util/expression_converter';
import {BindingForm, BuiltinConverter, BuiltinFunctionCall, ConvertPropertyBindingResult, EventHandlerVars, LocalResolver, convertActionBinding, convertPropertyBinding, convertPropertyBindingBuiltins} from '../compiler_util/expression_converter';
import {ConstantPool, DefinitionKind} from '../constant_pool';
import {AST, AstMemoryEfficientTransformer, AstTransformer, BindingPipe, FunctionCall, ImplicitReceiver, LiteralPrimitive, MethodCall, ParseSpan, PropertyRead} from '../expression_parser/ast';
import {AST, AstMemoryEfficientTransformer, AstTransformer, BindingPipe, FunctionCall, ImplicitReceiver, LiteralArray, LiteralMap, LiteralPrimitive, MethodCall, ParseSpan, PropertyRead} from '../expression_parser/ast';
import {Identifiers} from '../identifiers';
import {LifecycleHooks} from '../lifecycle_reflector';
import * as o from '../output/output_ast';
@ -22,6 +22,7 @@ import {OutputContext, error} from '../util';
import {Identifiers as R3} from './r3_identifiers';
/** Name of the context parameter passed into a template function */
const CONTEXT_NAME = 'ctx';
@ -217,6 +218,23 @@ function pipeBinding(args: o.Expression[]): o.ExternalReference {
}
}
const pureFunctionIdentifiers = [
R3.pureFunction0, R3.pureFunction1, R3.pureFunction2, R3.pureFunction3, R3.pureFunction4,
R3.pureFunction5, R3.pureFunction6, R3.pureFunction7, R3.pureFunction8
];
function getLiteralFactory(
outputContext: OutputContext, literal: o.LiteralArrayExpr | o.LiteralMapExpr): o.Expression {
const {literalFactory, literalFactoryArguments} =
outputContext.constantPool.getLiteralFactory(literal);
literalFactoryArguments.length > 0 || error(`Expected arguments to a literal factory function`);
let pureFunctionIdent =
pureFunctionIdentifiers[literalFactoryArguments.length] || R3.pureFunctionV;
// Literal factories are pure functions that only need to be re-invoked when the parameters
// change.
return o.importExpr(pureFunctionIdent).callFn([literalFactory, ...literalFactoryArguments]);
}
class BindingScope {
private map = new Map<string, o.Expression>();
private referenceNameIndex = 0;
@ -269,7 +287,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
private _postfix: o.Statement[] = [];
private _contentProjections: Map<NgContentAst, NgContentInfo>;
private _projectionDefinitionIndex = 0;
private _pipeConverter: PipeConverter;
private _valueConverter: ValueConverter;
private unsupported = unsupported;
private invalid = invalid;
@ -279,8 +297,8 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
private bindingScope: BindingScope, private level = 0, private ngContentSelectors: string[],
private contextName: string|null, private templateName: string|null,
private pipes: Map<string, CompilePipeSummary>) {
this._pipeConverter =
new PipeConverter(() => this.allocateDataSlot(), (name, localName, slot, value) => {
this._valueConverter = new ValueConverter(
outputCtx, () => this.allocateDataSlot(), (name, localName, slot, value) => {
bindingScope.set(localName, value);
const pipe = pipes.get(name) !;
pipe || error(`Could not find pipe ${name}`);
@ -634,7 +652,7 @@ class TemplateDefinitionBuilder implements TemplateAstVisitor, LocalResolver {
}
private convertPropertyBinding(implicit: o.Expression, value: AST): o.Expression {
const pipesConvertedValue = value.visit(this._pipeConverter);
const pipesConvertedValue = value.visit(this._valueConverter);
const convertedPropertyBinding = convertPropertyBinding(
this, implicit, pipesConvertedValue, this.bindingContext(), BindingForm.TrySimple,
interpolate);
@ -688,10 +706,10 @@ export function createFactory(
o.INFERRED_TYPE, null, type.reference.name ? `${type.reference.name}_Factory` : null);
}
class PipeConverter extends AstMemoryEfficientTransformer {
class ValueConverter extends AstMemoryEfficientTransformer {
private pipeSlots = new Map<string, number>();
constructor(
private allocateSlot: () => number,
private outputCtx: OutputContext, private allocateSlot: () => number,
private definePipe:
(name: string, localName: string, slot: number, value: o.Expression) => void) {
super();
@ -715,6 +733,31 @@ class PipeConverter extends AstMemoryEfficientTransformer {
return new FunctionCall(
ast.span, target, [new LiteralPrimitive(ast.span, slot), value, ...args]);
}
visitLiteralArray(ast: LiteralArray, context: any): AST {
return new BuiltinFunctionCall(ast.span, this.visitAll(ast.expressions), values => {
// If the literal has calculated (non-literal) elements transform it into
// calls to literal factories that compose the literal and will cache intermediate
// values. Otherwise, just return an literal array that contains the values.
const literal = o.literalArr(values);
return values.every(a => a.isConstant()) ?
this.outputCtx.constantPool.getConstLiteral(literal, true) :
getLiteralFactory(this.outputCtx, literal);
});
}
visitLiteralMap(ast: LiteralMap, context: any): AST {
return new BuiltinFunctionCall(ast.span, this.visitAll(ast.values), values => {
// If the literal has calculated (non-literal) elements transform it into
// calls to literal factories that compose the literal and will cache intermediate
// values. Otherwise, just return an literal array that contains the values.
const literal = o.literalMap(values.map(
(value, index) => ({key: ast.keys[index].key, value, quoted: ast.keys[index].quoted})));
return values.every(a => a.isConstant()) ?
this.outputCtx.constantPool.getConstLiteral(literal, true) :
getLiteralFactory(this.outputCtx, literal);
});
}
}
function invalid<T>(arg: o.Expression | o.Statement | TemplateAst): never {