fix(ivy): compile interpolated bindings without superfluous bind instruction (#23923)

This fixes the case where the compiler would generate a bind(interpolation#())
instruction.

PR Close #23923
This commit is contained in:
JoostK 2018-05-15 21:07:59 +02:00 committed by Matias Niemelä
parent fb906a87e8
commit bd149e5d67
3 changed files with 141 additions and 11 deletions

View File

@ -33,7 +33,7 @@ export class Identifiers {
static text: o.ExternalReference = {name: 'ɵT', moduleName: CORE};
static textCreateBound: o.ExternalReference = {name: 'ɵt', moduleName: CORE};
static textBinding: o.ExternalReference = {name: 'ɵt', moduleName: CORE};
static bind: o.ExternalReference = {name: 'ɵb', moduleName: CORE};

View File

@ -11,7 +11,7 @@ import {CompileReflector} from '../../compile_reflector';
import {BindingForm, BuiltinFunctionCall, LocalResolver, convertActionBinding, convertPropertyBinding} from '../../compiler_util/expression_converter';
import {ConstantPool} from '../../constant_pool';
import * as core from '../../core';
import {AST, AstMemoryEfficientTransformer, BindingPipe, BindingType, FunctionCall, ImplicitReceiver, LiteralArray, LiteralMap, LiteralPrimitive, PropertyRead} from '../../expression_parser/ast';
import {AST, AstMemoryEfficientTransformer, BindingPipe, BindingType, FunctionCall, ImplicitReceiver, Interpolation, LiteralArray, LiteralMap, LiteralPrimitive, PropertyRead} from '../../expression_parser/ast';
import {Lexer} from '../../expression_parser/lexer';
import {Parser} from '../../expression_parser/parser';
import * as html from '../../ml_parser/ast';
@ -340,10 +340,9 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
const instruction = BINDING_INSTRUCTION_MAP[input.type];
if (instruction) {
// TODO(chuckj): runtime: security context?
const value = o.importExpr(R3.bind).callFn([convertedBinding]);
this.instruction(
this._bindingCode, input.sourceSpan, instruction, o.literal(elementIndex),
o.literal(input.name), value);
o.literal(input.name), convertedBinding);
} else {
this._unsupported(`binding type ${input.type}`);
}
@ -418,7 +417,7 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
const convertedBinding = this.convertPropertyBinding(context, input.value);
this.instruction(
this._bindingCode, template.sourceSpan, R3.elementProperty, o.literal(templateIndex),
o.literal(input.name), o.importExpr(R3.bind).callFn([convertedBinding]));
o.literal(input.name), convertedBinding);
});
// Create the template function
@ -443,7 +442,7 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
this.instruction(this._creationCode, text.sourceSpan, R3.text, o.literal(nodeIndex));
this.instruction(
this._bindingCode, text.sourceSpan, R3.textCreateBound, o.literal(nodeIndex),
this._bindingCode, text.sourceSpan, R3.textBinding, o.literal(nodeIndex),
this.convertPropertyBinding(o.variable(CONTEXT_NAME), text.value));
}
@ -483,11 +482,19 @@ export class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver
private convertPropertyBinding(implicit: o.Expression, value: AST): o.Expression {
const pipesConvertedValue = value.visit(this._valueConverter);
const convertedPropertyBinding = convertPropertyBinding(
this, implicit, pipesConvertedValue, this.bindingContext(), BindingForm.TrySimple,
interpolate);
this._bindingCode.push(...convertedPropertyBinding.stmts);
return convertedPropertyBinding.currValExpr;
if (pipesConvertedValue instanceof Interpolation) {
const convertedPropertyBinding = convertPropertyBinding(
this, implicit, pipesConvertedValue, this.bindingContext(), BindingForm.TrySimple,
interpolate);
this._bindingCode.push(...convertedPropertyBinding.stmts);
return convertedPropertyBinding.currValExpr;
} else {
const convertedPropertyBinding = convertPropertyBinding(
this, implicit, pipesConvertedValue, this.bindingContext(), BindingForm.TrySimple,
() => error('Unexpected interpolation'));
this._bindingCode.push(...convertedPropertyBinding.stmts);
return o.importExpr(R3.bind).callFn([convertedPropertyBinding.currValExpr]);
}
}
}

View File

@ -0,0 +1,123 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {MockDirectory, setup} from '../aot/test_util';
import {compile, expectEmit} from './mock_compile';
describe('compiler compliance: bindings', () => {
const angularFiles = setup({
compileAngular: true,
compileAnimations: false,
compileCommon: false,
});
describe('text bindings', () => {
it('should generate interpolation instruction', () => {
const files: MockDirectory = {
app: {
'example.ts': `
import {Component, NgModule} from '@angular/core';
@Component({
selector: 'my-component',
template: \`
<div>Hello {{ name }}</div>\`
})
export class MyComponent {
name = 'World';
}
@NgModule({declarations: [MyComponent]})
export class MyModule {}
`
}
};
const template = `
template:function MyComponent_Template(rf: IDENT, $ctx$: IDENT){
if (rf & 1) {
$i0$.ɵE(0, 'div');
$i0$.ɵT(1);
$i0$.ɵe();
}
if (rf & 2) {
$i0$.ɵt(1, $i0$.ɵi1('Hello ', $ctx$.name, ''));
}
}`;
const result = compile(files, angularFiles);
expectEmit(result.source, template, 'Incorrect interpolated text binding');
});
});
describe('property bindings', () => {
it('should generate bind instruction', () => {
const files: MockDirectory = {
app: {
'example.ts': `
import {Component, NgModule} from '@angular/core';
@Component({
selector: 'my-app',
template: '<a [title]="title"></a>'
})
export class MyComponent {
title = 'Hello World';
}
@NgModule({declarations: [MyComponent]})
export class MyModule {}`
}
};
const template = `
template:function MyComponent_Template(rf: IDENT, $ctx$: IDENT){
if (rf & 1) {
$i0$.ɵE(0, 'a');
$i0$.ɵe();
}
if (rf & 2) {
$i0$.ɵp(0, 'title', $i0$.ɵb($ctx$.title));
}
}`;
const result = compile(files, angularFiles);
expectEmit(result.source, template, 'Incorrect property binding');
});
it('should generate interpolation instruction for {{...}} bindings', () => {
const files: MockDirectory = {
app: {
'example.ts': `
import {Component, NgModule} from '@angular/core';
@Component({
selector: 'my-component',
template: \`
<a title="Hello {{name}}"></a>\`
})
export class MyComponent {
name = 'World';
}
@NgModule({declarations: [MyComponent]})
export class MyModule {}
`
}
};
const template = `
template:function MyComponent_Template(rf: IDENT, $ctx$: IDENT){
if (rf & 1) {
$i0$.ɵE(0, 'a');
$i0$.ɵe();
}
if (rf & 2) {
$i0$.ɵp(0, 'title', $i0$.ɵi1('Hello ', $ctx$.name, ''));
}
}`;
const result = compile(files, angularFiles);
expectEmit(result.source, template, 'Incorrect interpolated property binding');
});
});
});