Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 0093869

Browse files
mmalerbaAndrewKushnir
authored andcommitted
refactor(compiler-cli): handle parentheses in the template pipeline (#60169)
Now that the expression AST contains parenthesized expressions, this refactors the template pipeline to strip out the ones we don't need. PR Close #60169
1 parent d4cfeb0 commit 0093869

File tree

13 files changed

+134
-100
lines changed

13 files changed

+134
-100
lines changed

‎packages/compiler-cli/src/ngtsc/translator/src/translator.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -463,7 +463,8 @@ export class ExpressionTranslatorVisitor<TFile, TStatement, TExpression>
463463
}
464464

465465
visitParenthesizedExpr(ast: o.ParenthesizedExpr, context: any) {
466-
return this.factory.createParenthesizedExpression(ast.expr.visitExpression(this, context));
466+
const result = ast.expr.visitExpression(this, context);
467+
return this.factory.createParenthesizedExpression(result);
467468
}
468469

469470
private visitStatements(statements: o.Statement[], context: Context): TStatement[] {

‎packages/compiler-cli/src/ngtsc/typecheck/src/expression.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import {
2424
LiteralMap,
2525
LiteralPrimitive,
2626
NonNullAssert,
27-
Parenthesized,
27+
ParenthesizedExpression,
2828
PrefixNot,
2929
PropertyRead,
3030
PropertyWrite,
@@ -491,7 +491,7 @@ class AstTranslator implements AstVisitor {
491491
);
492492
}
493493

494-
visitParenthesized(ast: Parenthesized): ts.ParenthesizedExpression {
494+
visitParenthesizedExpression(ast: ParenthesizedExpression): ts.ParenthesizedExpression {
495495
return ts.factory.createParenthesizedExpression(this.translate(ast.expression));
496496
}
497497

@@ -629,7 +629,7 @@ class VeSafeLhsInferenceBugDetector implements AstVisitor {
629629
visitTaggedTemplateLiteral(ast: TaggedTemplateLiteral, context: any) {
630630
return false;
631631
}
632-
visitParenthesized(ast: Parenthesized, context: any) {
632+
visitParenthesizedExpression(ast: ParenthesizedExpression, context: any) {
633633
return ast.expression.visit(this);
634634
}
635635
}

‎packages/compiler/src/expression_parser/ast.ts‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -486,7 +486,7 @@ export class TemplateLiteralElement extends AST {
486486
}
487487
}
488488

489-
export class Parenthesized extends AST {
489+
export class ParenthesizedExpression extends AST {
490490
constructor(
491491
span: ParseSpan,
492492
sourceSpan: AbsoluteSourceSpan,
@@ -496,7 +496,7 @@ export class Parenthesized extends AST {
496496
}
497497

498498
override visit(visitor: AstVisitor, context?: any) {
499-
return visitor.visitParenthesized(this, context);
499+
return visitor.visitParenthesizedExpression(this, context);
500500
}
501501
}
502502

@@ -630,7 +630,7 @@ export interface AstVisitor {
630630
visitTemplateLiteral(ast: TemplateLiteral, context: any): any;
631631
visitTemplateLiteralElement(ast: TemplateLiteralElement, context: any): any;
632632
visitTaggedTemplateLiteral(ast: TaggedTemplateLiteral, context: any): any;
633-
visitParenthesized(ast: Parenthesized, context: any): any;
633+
visitParenthesizedExpression(ast: ParenthesizedExpression, context: any): any;
634634
visitASTWithSource?(ast: ASTWithSource, context: any): any;
635635
/**
636636
* This function is optionally defined to allow classes that implement this
@@ -739,7 +739,7 @@ export class RecursiveAstVisitor implements AstVisitor {
739739
this.visit(ast.tag, context);
740740
this.visit(ast.template, context);
741741
}
742-
visitParenthesized(ast: Parenthesized, context: any) {
742+
visitParenthesizedExpression(ast: ParenthesizedExpression, context: any) {
743743
this.visit(ast.expression, context);
744744
}
745745
// This is not part of the AstVisitor interface, just a helper method

‎packages/compiler/src/expression_parser/parser.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ import {
3434
LiteralMapKey,
3535
LiteralPrimitive,
3636
NonNullAssert,
37-
Parenthesized,
37+
ParenthesizedExpression,
3838
ParserError,
3939
ParseSpan,
4040
PrefixNot,
@@ -1041,7 +1041,7 @@ class _ParseAST {
10411041
const result = this.parsePipe();
10421042
this.rparensExpected--;
10431043
this.expectCharacter(chars.$RPAREN);
1044-
return new Parenthesized(this.span(start), this.sourceSpan(start), result);
1044+
return new ParenthesizedExpression(this.span(start), this.sourceSpan(start), result);
10451045
} else if (this.next.isKeywordNull()) {
10461046
this.advance();
10471047
return new LiteralPrimitive(this.span(start), this.sourceSpan(start), null);

‎packages/compiler/src/expression_parser/serializer.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ class SerializeExpressionVisitor implements expr.AstVisitor {
168168
return ast.tag.visit(this, context) + ast.template.visit(this, context);
169169
}
170170

171-
visitParenthesized(ast: expr.Parenthesized, context: any) {
171+
visitParenthesizedExpression(ast: expr.ParenthesizedExpression, context: any) {
172172
return '(' + ast.expression.visit(this, context) + ')';
173173
}
174174
}

‎packages/compiler/src/template/pipeline/src/emit.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,6 @@ import {removeEmptyBindings} from './phases/remove_empty_bindings';
6464
import {removeI18nContexts} from './phases/remove_i18n_contexts';
6565
import {removeIllegalLetReferences} from './phases/remove_illegal_let_references';
6666
import {removeUnusedI18nAttributesOps} from './phases/remove_unused_i18n_attrs';
67-
import {requiredParentheses} from './phases/required_parentheses';
6867
import {resolveContexts} from './phases/resolve_contexts';
6968
import {resolveDeferDepsFns} from './phases/resolve_defer_deps_fns';
7069
import {resolveDollarEvent} from './phases/resolve_dollar_event';
@@ -75,6 +74,7 @@ import {resolveSanitizers} from './phases/resolve_sanitizers';
7574
import {saveAndRestoreView} from './phases/save_restore_view';
7675
import {allocateSlots} from './phases/slot_allocation';
7776
import {optimizeStoreLet} from './phases/store_let_optimization';
77+
import {stripNonrequiredParentheses} from './phases/strip_nonrequired_parentheses';
7878
import {specializeStyleBindings} from './phases/style_binding_specialization';
7979
import {generateTemporaryVariables} from './phases/temporary_variables';
8080
import {optimizeTrackFns} from './phases/track_fn_optimization';
@@ -139,7 +139,7 @@ const phases: Phase[] = [
139139
{kind: Kind.Both, fn: resolveSanitizers},
140140
{kind: Kind.Tmpl, fn: liftLocalRefs},
141141
{kind: Kind.Both, fn: expandSafeReads},
142-
{kind: Kind.Both, fn: requiredParentheses},
142+
{kind: Kind.Both, fn: stripNonrequiredParentheses},
143143
{kind: Kind.Both, fn: generateTemporaryVariables},
144144
{kind: Kind.Both, fn: optimizeVariables},
145145
{kind: Kind.Both, fn: optimizeStoreLet},

‎packages/compiler/src/template/pipeline/src/ingest.ts‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1179,9 +1179,12 @@ function convertAst(
11791179
undefined,
11801180
convertSourceSpan(ast.span, baseSourceSpan),
11811181
);
1182-
} else if (ast instanceof e.Parenthesized) {
1183-
// TODO: refactor so we can translate the expression AST parens into output AST parens
1184-
return convertAst(ast.expression, job, baseSourceSpan);
1182+
} else if (ast instanceof e.ParenthesizedExpression) {
1183+
return new o.ParenthesizedExpr(
1184+
convertAst(ast.expression, job, baseSourceSpan),
1185+
undefined,
1186+
convertSourceSpan(ast.span, baseSourceSpan),
1187+
);
11851188
} else {
11861189
throw new Error(
11871190
`Unhandled expression type "${ast.constructor.name}" in file "${baseSourceSpan?.start.file.url}"`,

‎packages/compiler/src/template/pipeline/src/phases/expand_safe_reads.ts‎

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ const requiresTemporary = [
3535
o.InvokeFunctionExpr,
3636
o.LiteralArrayExpr,
3737
o.LiteralMapExpr,
38+
o.ParenthesizedExpr,
3839
ir.SafeInvokeFunctionExpr,
3940
ir.PipeBindingExpr,
4041
].map((e) => e.constructor.name);
@@ -58,6 +59,8 @@ function needsTemporaryInSafeAccess(e: o.Expression): boolean {
5859
return needsTemporaryInSafeAccess(e.receiver);
5960
} else if (e instanceof o.ReadKeyExpr) {
6061
return needsTemporaryInSafeAccess(e.receiver) || needsTemporaryInSafeAccess(e.index);
62+
} else if (e instanceof o.ParenthesizedExpr) {
63+
return needsTemporaryInSafeAccess(e.expr);
6164
}
6265
// TODO: Switch to a method which is exhaustive of newly added expression subtypes.
6366
return (
@@ -233,9 +236,11 @@ function ternaryTransform(e: o.Expression): o.Expression {
233236
if (!(e instanceof ir.SafeTernaryExpr)) {
234237
return e;
235238
}
236-
return new o.ConditionalExpr(
237-
new o.BinaryOperatorExpr(o.BinaryOperator.Equals, e.guard, o.NULL_EXPR),
238-
o.NULL_EXPR,
239-
e.expr,
239+
return new o.ParenthesizedExpr(
240+
new o.ConditionalExpr(
241+
new o.BinaryOperatorExpr(o.BinaryOperator.Equals, e.guard, o.NULL_EXPR),
242+
o.NULL_EXPR,
243+
e.expr,
244+
),
240245
);
241246
}

‎packages/compiler/src/template/pipeline/src/phases/required_parentheses.ts‎

Lines changed: 0 additions & 73 deletions
This file was deleted.
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.dev/license
7+
*/
8+
9+
import * as o from '../../../../output/output_ast';
10+
import * as ir from '../../ir';
11+
import type {CompilationJob} from '../compilation';
12+
13+
/**
14+
* In most cases we can drop user added parentheses from expressions. However, in some cases
15+
* parentheses are needed for the expression to be considered valid JavaScript or for Typescript to
16+
* generate the correct output. This phases strips all parentheses except in the following
17+
* siturations where they are required:
18+
*
19+
* 1. Unary operators in the base of an exponentiation expression. For example, `-2 ** 3` is not
20+
* valid JavaScript, but `(-2) ** 3` is.
21+
* 2. When mixing nullish coalescing (`??`) and logical and/or operators (`&&`, `||`), we need
22+
* parentheses. For example, `a ?? b && c` is not valid JavaScript, but `a ?? (b && c)` is.
23+
* 3. Ternary expression used as an operand for nullish coalescing. Typescript generates incorrect
24+
* code if the parentheses are missing. For example when `(a ? b : c) ?? d` is translated to
25+
* typescript AST, the parentheses node is removed, and then the remaining AST is printed, it
26+
* incorrectly prints `a ? b : c ?? d`. This is different from how it handles the same situation
27+
* with `||` and `&&` where it prints the parentheses even if they are not present in the AST.
28+
*/
29+
export function stripNonrequiredParentheses(job: CompilationJob): void {
30+
// Check which parentheses are required.
31+
const requiredParens = new Set<o.ParenthesizedExpr>();
32+
for (const unit of job.units) {
33+
for (const op of unit.ops()) {
34+
ir.visitExpressionsInOp(op, (expr) => {
35+
if (expr instanceof o.BinaryOperatorExpr) {
36+
switch (expr.operator) {
37+
case o.BinaryOperator.Exponentiation:
38+
checkExponentiationParens(expr, requiredParens);
39+
break;
40+
case o.BinaryOperator.NullishCoalesce:
41+
checkNullishCoalescingParens(expr, requiredParens);
42+
break;
43+
}
44+
}
45+
});
46+
}
47+
}
48+
49+
// Remove any non-required parentheses.
50+
for (const unit of job.units) {
51+
for (const op of unit.ops()) {
52+
ir.transformExpressionsInOp(
53+
op,
54+
(expr) => {
55+
if (expr instanceof o.ParenthesizedExpr) {
56+
return requiredParens.has(expr) ? expr : expr.expr;
57+
}
58+
return expr;
59+
},
60+
ir.VisitorContextFlag.None,
61+
);
62+
}
63+
}
64+
}
65+
66+
function checkExponentiationParens(
67+
expr: o.BinaryOperatorExpr,
68+
requiredParens: Set<o.ParenthesizedExpr>,
69+
) {
70+
if (expr.lhs instanceof o.ParenthesizedExpr && expr.lhs.expr instanceof o.UnaryOperatorExpr) {
71+
requiredParens.add(expr.lhs);
72+
}
73+
}
74+
75+
function checkNullishCoalescingParens(
76+
expr: o.BinaryOperatorExpr,
77+
requiredParens: Set<o.ParenthesizedExpr>,
78+
) {
79+
if (
80+
expr.lhs instanceof o.ParenthesizedExpr &&
81+
(isLogicalAndOr(expr.lhs.expr) || expr.lhs.expr instanceof o.ConditionalExpr)
82+
) {
83+
requiredParens.add(expr.lhs);
84+
}
85+
if (
86+
expr.rhs instanceof o.ParenthesizedExpr &&
87+
(isLogicalAndOr(expr.rhs.expr) || expr.rhs.expr instanceof o.ConditionalExpr)
88+
) {
89+
requiredParens.add(expr.rhs);
90+
}
91+
}
92+
93+
function isLogicalAndOr(expr: o.Expression) {
94+
return (
95+
expr instanceof o.BinaryOperatorExpr &&
96+
(expr.operator === o.BinaryOperator.And || expr.operator === o.BinaryOperator.Or)
97+
);
98+
}

0 commit comments

Comments
 (0)