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:
@ -33,7 +33,7 @@ export class Identifiers {
|
|||||||
|
|
||||||
static text: o.ExternalReference = {name: 'ɵT', moduleName: CORE};
|
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};
|
static bind: o.ExternalReference = {name: 'ɵb', moduleName: CORE};
|
||||||
|
|
||||||
|
@ -11,7 +11,7 @@ import {CompileReflector} from '../../compile_reflector';
|
|||||||
import {BindingForm, BuiltinFunctionCall, LocalResolver, convertActionBinding, convertPropertyBinding} from '../../compiler_util/expression_converter';
|
import {BindingForm, BuiltinFunctionCall, LocalResolver, convertActionBinding, convertPropertyBinding} from '../../compiler_util/expression_converter';
|
||||||
import {ConstantPool} from '../../constant_pool';
|
import {ConstantPool} from '../../constant_pool';
|
||||||
import * as core from '../../core';
|
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 {Lexer} from '../../expression_parser/lexer';
|
||||||
import {Parser} from '../../expression_parser/parser';
|
import {Parser} from '../../expression_parser/parser';
|
||||||
import * as html from '../../ml_parser/ast';
|
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];
|
const instruction = BINDING_INSTRUCTION_MAP[input.type];
|
||||||
if (instruction) {
|
if (instruction) {
|
||||||
// TODO(chuckj): runtime: security context?
|
// TODO(chuckj): runtime: security context?
|
||||||
const value = o.importExpr(R3.bind).callFn([convertedBinding]);
|
|
||||||
this.instruction(
|
this.instruction(
|
||||||
this._bindingCode, input.sourceSpan, instruction, o.literal(elementIndex),
|
this._bindingCode, input.sourceSpan, instruction, o.literal(elementIndex),
|
||||||
o.literal(input.name), value);
|
o.literal(input.name), convertedBinding);
|
||||||
} else {
|
} else {
|
||||||
this._unsupported(`binding type ${input.type}`);
|
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);
|
const convertedBinding = this.convertPropertyBinding(context, input.value);
|
||||||
this.instruction(
|
this.instruction(
|
||||||
this._bindingCode, template.sourceSpan, R3.elementProperty, o.literal(templateIndex),
|
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
|
// 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._creationCode, text.sourceSpan, R3.text, o.literal(nodeIndex));
|
||||||
|
|
||||||
this.instruction(
|
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));
|
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 {
|
private convertPropertyBinding(implicit: o.Expression, value: AST): o.Expression {
|
||||||
const pipesConvertedValue = value.visit(this._valueConverter);
|
const pipesConvertedValue = value.visit(this._valueConverter);
|
||||||
const convertedPropertyBinding = convertPropertyBinding(
|
if (pipesConvertedValue instanceof Interpolation) {
|
||||||
this, implicit, pipesConvertedValue, this.bindingContext(), BindingForm.TrySimple,
|
const convertedPropertyBinding = convertPropertyBinding(
|
||||||
interpolate);
|
this, implicit, pipesConvertedValue, this.bindingContext(), BindingForm.TrySimple,
|
||||||
this._bindingCode.push(...convertedPropertyBinding.stmts);
|
interpolate);
|
||||||
return convertedPropertyBinding.currValExpr;
|
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]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
123
packages/compiler/test/render3/r3_view_compiler_binding_spec.ts
Normal file
123
packages/compiler/test/render3/r3_view_compiler_binding_spec.ts
Normal 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');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
Reference in New Issue
Block a user