feat(ivy): support array and object literals in binding expressions (#22336)
PR Close #22336
This commit is contained in:
parent
ec445b5c73
commit
49f074f61d
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -209,6 +209,273 @@ describe('compiler compliance', () => {
|
|||
expectEmit(source, MyComponentDefinition, 'Incorrect MyComponent.ngComponentDef');
|
||||
});
|
||||
|
||||
describe('value composition', () => {
|
||||
|
||||
it('should support array literals', () => {
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
import {Component, Input, NgModule} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'my-comp',
|
||||
template: \`
|
||||
<p>{{ names[0] }}</p>
|
||||
<p>{{ names[1] }}</p>
|
||||
\`
|
||||
})
|
||||
export class MyComp {
|
||||
@Input() names: string[];
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'my-app',
|
||||
template: \`
|
||||
<my-comp [names]="['Nancy', customName]"></my-comp>
|
||||
\`
|
||||
})
|
||||
export class MyApp {
|
||||
customName = 'Bess';
|
||||
}
|
||||
|
||||
@NgModule({declarations: [MyComp, MyApp]})
|
||||
export class MyModule { }
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
const MyAppDeclaration = `
|
||||
const $e0_ff$ = ($v$: any) => { return ['Nancy', $v$]; };
|
||||
…
|
||||
static ngComponentDef = $r3$.ɵdefineComponent({
|
||||
type: MyApp,
|
||||
tag: 'my-app',
|
||||
factory: function MyApp_Factory() { return new MyApp(); },
|
||||
template: function MyApp_Template(ctx: $MyApp$, cm: $boolean$) {
|
||||
if (cm) {
|
||||
$r3$.ɵE(0, MyComp);
|
||||
$r3$.ɵe();
|
||||
}
|
||||
$r3$.ɵp(0, 'names', $r3$.ɵb($r3$.ɵf1($e0_ff$, ctx.customName)));
|
||||
MyComp.ngComponentDef.h(1, 0);
|
||||
$r3$.ɵr(1, 0);
|
||||
}
|
||||
});
|
||||
`;
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
const source = result.source;
|
||||
|
||||
expectEmit(source, MyAppDeclaration, 'Invalid array emit');
|
||||
});
|
||||
|
||||
it('should support 9+ bindings in array literals', () => {
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
import {Component, Input, NgModule} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'my-comp',
|
||||
template: \`
|
||||
{{ names[0] }}
|
||||
{{ names[1] }}
|
||||
{{ names[3] }}
|
||||
{{ names[4] }}
|
||||
{{ names[5] }}
|
||||
{{ names[6] }}
|
||||
{{ names[7] }}
|
||||
{{ names[8] }}
|
||||
{{ names[9] }}
|
||||
{{ names[10] }}
|
||||
{{ names[11] }}
|
||||
\`
|
||||
})
|
||||
export class MyComp {
|
||||
@Input() names: string[];
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'my-app',
|
||||
template: \`
|
||||
<my-comp [names]="['start-', n0, n1, n2, n3, n4, '-middle-', n5, n6, n7, n8, '-end']">
|
||||
</my-comp>
|
||||
\`
|
||||
})
|
||||
export class MyApp {
|
||||
n0 = 'a';
|
||||
n1 = 'b';
|
||||
n2 = 'c';
|
||||
n3 = 'd';
|
||||
n4 = 'e';
|
||||
n5 = 'f';
|
||||
n6 = 'g';
|
||||
n7 = 'h';
|
||||
n8 = 'i';
|
||||
}
|
||||
|
||||
@NgModule({declarations: [MyComp, MyApp]})
|
||||
export class MyModule {}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
const MyAppDefinition = `
|
||||
const $e0_ff$ = ($v0$: $any$, $v1$: $any$, $v2$: $any$, $v3$: $any$, $v4$: $any$, $v5$: $any$, $v6$: $any$, $v7$: $any$, $v8$: $any$) => {
|
||||
return ['start-', $v0$, $v1$, $v2$, $v3$, $v4$, '-middle-', $v5$, $v6$, $v7$, $v8$, '-end'];
|
||||
}
|
||||
…
|
||||
static ngComponentDef = $r3$.ɵdefineComponent({
|
||||
type: MyApp,
|
||||
tag: 'my-app',
|
||||
factory: function MyApp_Factory() { return new MyApp(); },
|
||||
template: function MyApp_Template(ctx: $MyApp$, cm: $boolean$) {
|
||||
if (cm) {
|
||||
$r3$.ɵE(0, MyComp);
|
||||
$r3$.ɵe();
|
||||
}
|
||||
$r3$.ɵp(
|
||||
0, 'names',
|
||||
$r3$.ɵb($r3$.ɵfV($e0_ff$, ctx.n0, ctx.n1, ctx.n2, ctx.n3, ctx.n4, ctx.n5, ctx.n6, ctx.n7, ctx.n8)));
|
||||
MyComp.ngComponentDef.h(1, 0);
|
||||
$r3$.ɵr(1, 0);
|
||||
}
|
||||
});
|
||||
`;
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
const source = result.source;
|
||||
|
||||
expectEmit(source, MyAppDefinition, 'Invalid array binding');
|
||||
});
|
||||
|
||||
it('should support object literals', () => {
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
import {Component, Input, NgModule} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'object-comp',
|
||||
template: \`
|
||||
<p> {{ config['duration'] }} </p>
|
||||
<p> {{ config.animation }} </p>
|
||||
\`
|
||||
})
|
||||
export class ObjectComp {
|
||||
@Input() config: {[key: string]: any};
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'my-app',
|
||||
template: \`
|
||||
<object-comp [config]="{'duration': 500, animation: name}"></object-comp>
|
||||
\`
|
||||
})
|
||||
export class MyApp {
|
||||
name = 'slide';
|
||||
}
|
||||
|
||||
@NgModule({declarations: [ObjectComp, MyApp]})
|
||||
export class MyModule {}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
const MyAppDefinition = `
|
||||
const $e0_ff$ = ($v$: any) => { return {'duration': 500, animation: $v$}; };
|
||||
…
|
||||
static ngComponentDef = $r3$.ɵdefineComponent({
|
||||
type: MyApp,
|
||||
tag: 'my-app',
|
||||
factory: function MyApp_Factory() { return new MyApp(); },
|
||||
template: function MyApp_Template(ctx: $MyApp$, cm: $boolean$) {
|
||||
if (cm) {
|
||||
$r3$.ɵE(0, ObjectComp);
|
||||
$r3$.ɵe();
|
||||
}
|
||||
$r3$.ɵp(0, 'config', $r3$.ɵb($r3$.ɵf1($e0_ff$, ctx.name)));
|
||||
ObjectComp.ngComponentDef.h(1, 0);
|
||||
$r3$.ɵr(1, 0);
|
||||
}
|
||||
});
|
||||
`;
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
const source = result.source;
|
||||
|
||||
expectEmit(source, MyAppDefinition, 'Invalid object literal binding');
|
||||
});
|
||||
|
||||
it('should support expressions nested deeply in object/array literals', () => {
|
||||
const files = {
|
||||
app: {
|
||||
'spec.ts': `
|
||||
import {Component, Input, NgModule} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'nested-comp',
|
||||
template: \`
|
||||
<p> {{ config.animation }} </p>
|
||||
<p> {{config.actions[0].opacity }} </p>
|
||||
<p> {{config.actions[1].duration }} </p>
|
||||
\`
|
||||
})
|
||||
export class NestedComp {
|
||||
@Input() config: {[key: string]: any};
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'my-app',
|
||||
template: \`
|
||||
<nested-comp [config]="{animation: name, actions: [{ opacity: 0, duration: 0}, {opacity: 1, duration: duration }]}">
|
||||
</nested-comp>
|
||||
\`
|
||||
})
|
||||
export class MyApp {
|
||||
name = 'slide';
|
||||
duration = 100;
|
||||
}
|
||||
|
||||
@NgModule({declarations: [NestedComp, MyApp]})
|
||||
export class MyModule {}
|
||||
`
|
||||
}
|
||||
};
|
||||
|
||||
const MyAppDefinition = `
|
||||
const $c0$ = {opacity: 0, duration: 0};
|
||||
const $e0_ff$ = ($v$: any) => { return {opacity: 1, duration: $v$}; };
|
||||
const $e0_ff_1$ = ($v$: any) => { return [$c0$, $v$]; };
|
||||
const $e0_ff_2$ = ($v1$: any, $v2$: any) => { return {animation: $v1$, actions: $v2$}; };
|
||||
…
|
||||
static ngComponentDef = $r3$.ɵdefineComponent({
|
||||
type: MyApp,
|
||||
tag: 'my-app',
|
||||
factory: function MyApp_Factory() { return new MyApp(); },
|
||||
template: function MyApp_Template(ctx: $MyApp$, cm: $boolean$) {
|
||||
if (cm) {
|
||||
$r3$.ɵE(0, NestedComp);
|
||||
$r3$.ɵe();
|
||||
}
|
||||
$r3$.ɵp(
|
||||
0, 'config',
|
||||
$r3$.ɵb($r3$.ɵf2(
|
||||
$e0_ff_2$, ctx.name, $r3$.ɵf1($e0_ff_1$, $r3$.ɵf1($e0_ff$, ctx.duration)))));
|
||||
NestedComp.ngComponentDef.h(1, 0);
|
||||
$r3$.ɵr(1, 0);
|
||||
}
|
||||
});
|
||||
`;
|
||||
|
||||
|
||||
const result = compile(files, angularFiles);
|
||||
const source = result.source;
|
||||
|
||||
expectEmit(source, MyAppDefinition, 'Invalid array/object literal binding');
|
||||
});
|
||||
});
|
||||
|
||||
it('should support content projection', () => {
|
||||
const files = {
|
||||
app: {
|
||||
|
|
Loading…
Reference in New Issue