fix(ivy): type-checking should infer string type for interpolations (#30177)
Previously, interpolations were generated into TCBs as a comma-separated list of expressions, letting TypeScript infer the type of the expression as the type of the last expression in the chain. This is undesirable, as interpolations always result in a string type at runtime. Therefore, type-checking of bindings such as `<img src="{{ link }}"/>` where `link` is an object would incorrectly report a type-error. This commit adjusts the emitted TCB code for interpolations, where a chain of string concatenations is emitted, starting with the empty string. This ensures that the inferred type of the interpolation is of type string. PR Close #30177
This commit is contained in:
parent
6f073885b0
commit
0937062a64
|
@ -93,7 +93,12 @@ class AstTranslator implements AstVisitor {
|
|||
}
|
||||
|
||||
visitInterpolation(ast: Interpolation): ts.Expression {
|
||||
return this.astArrayToExpression(ast.expressions);
|
||||
// Build up a chain of binary + operations to simulate the string concatenation of the
|
||||
// interpolation's expressions. The chain is started using an actual string literal to ensure
|
||||
// the type is inferred as 'string'.
|
||||
return ast.expressions.reduce(
|
||||
(lhs, ast) => ts.createBinary(lhs, ts.SyntaxKind.PlusToken, this.translate(ast)),
|
||||
ts.createLiteral(''));
|
||||
}
|
||||
|
||||
visitKeyedRead(ast: KeyedRead): ts.Expression {
|
||||
|
@ -177,20 +182,6 @@ class AstTranslator implements AstVisitor {
|
|||
const whenNull = this.config.strictSafeNavigationTypes ? UNDEFINED : NULL_AS_ANY;
|
||||
return safeTernary(receiver, expr, whenNull);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an array of `AST` expressions into a single `ts.Expression`, by converting them all
|
||||
* and separating them with commas.
|
||||
*/
|
||||
private astArrayToExpression(astArray: AST[]): ts.Expression {
|
||||
// Reduce the `asts` array into a `ts.Expression`. Multiple expressions are combined into a
|
||||
// `ts.BinaryExpression` with a comma separator. First make a copy of the input array, as
|
||||
// it will be modified during the reduction.
|
||||
const asts = astArray.slice();
|
||||
return asts.reduce(
|
||||
(lhs, ast) => ts.createBinary(lhs, ts.SyntaxKind.CommaToken, this.translate(ast)),
|
||||
this.translate(asts.pop() !));
|
||||
}
|
||||
}
|
||||
|
||||
function safeTernary(
|
||||
|
|
|
@ -18,7 +18,7 @@ import {generateTypeCheckBlock} from '../src/type_check_block';
|
|||
|
||||
describe('type check blocks', () => {
|
||||
it('should generate a basic block for a binding',
|
||||
() => { expect(tcb('{{hello}}')).toContain('ctx.hello;'); });
|
||||
() => { expect(tcb('{{hello}} {{world}}')).toContain('"" + ctx.hello + ctx.world;'); });
|
||||
|
||||
it('should generate literal map expressions', () => {
|
||||
const TEMPLATE = '{{ method({foo: a, bar: b}) }}';
|
||||
|
@ -32,7 +32,7 @@ describe('type check blocks', () => {
|
|||
|
||||
it('should handle non-null assertions', () => {
|
||||
const TEMPLATE = `{{a!}}`;
|
||||
expect(tcb(TEMPLATE)).toContain('ctx.a!;');
|
||||
expect(tcb(TEMPLATE)).toContain('(ctx.a!);');
|
||||
});
|
||||
|
||||
it('should handle keyed property access', () => {
|
||||
|
@ -45,7 +45,7 @@ describe('type check blocks', () => {
|
|||
{{ i.value }}
|
||||
<input #i>
|
||||
`;
|
||||
expect(tcb(TEMPLATE)).toContain('var _t1 = document.createElement("input"); _t1.value;');
|
||||
expect(tcb(TEMPLATE)).toContain('var _t1 = document.createElement("input"); "" + _t1.value;');
|
||||
});
|
||||
|
||||
it('should generate a forward directive reference correctly', () => {
|
||||
|
@ -61,7 +61,7 @@ describe('type check blocks', () => {
|
|||
}];
|
||||
expect(tcb(TEMPLATE, DIRECTIVES))
|
||||
.toContain(
|
||||
'var _t1 = Dir.ngTypeCtor({}); _t1.value; var _t2 = document.createElement("div");');
|
||||
'var _t1 = Dir.ngTypeCtor({}); "" + _t1.value; var _t2 = document.createElement("div");');
|
||||
});
|
||||
|
||||
it('should handle style and class bindings specially', () => {
|
||||
|
|
Loading…
Reference in New Issue