Skip to content

Commit 9e2f304

Browse files
committed
fix #3416, fix #3425: better enum constant folding
1 parent 8f1faf7 commit 9e2f304

File tree

4 files changed

+81
-19
lines changed

4 files changed

+81
-19
lines changed

CHANGELOG.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,35 @@
7777
}
7878
```
7979
80+
* Do additional constant folding after cross-module enum inlining ([#3416](https://github.com/evanw/esbuild/issues/3416), [#3425](https://github.com/evanw/esbuild/issues/3425))
81+
82+
This release adds a few more cases where esbuild does constant folding after cross-module enum inlining.
83+
84+
```ts
85+
// Original code: enum.ts
86+
export enum Platform {
87+
WINDOWS = 'windows',
88+
MACOS = 'macos',
89+
LINUX = 'linux',
90+
}
91+
92+
// Original code: main.ts
93+
import { Platform } from './enum';
94+
declare const PLATFORM: string;
95+
export function logPlatform() {
96+
if (PLATFORM == Platform.WINDOWS) console.log('Windows');
97+
else if (PLATFORM == Platform.MACOS) console.log('macOS');
98+
else if (PLATFORM == Platform.LINUX) console.log('Linux');
99+
else console.log('Other');
100+
}
101+
102+
// Old output (with --bundle '--define:PLATFORM="macos"' --minify --format=esm)
103+
function n(){"windows"=="macos"?console.log("Windows"):"macos"=="macos"?console.log("macOS"):"linux"=="macos"?console.log("Linux"):console.log("Other")}export{n as logPlatform};
104+
105+
// New output (with --bundle '--define:PLATFORM="macos"' --minify --format=esm)
106+
function n(){console.log("macOS")}export{n as logPlatform};
107+
```
108+
80109
* Formatting support for the `@position-try` rule ([#3773](https://github.com/evanw/esbuild/issues/3773))
81110
82111
Chrome shipped this new CSS at-rule in version 125 as part of the [CSS anchor positioning API](https://developer.chrome.com/blog/anchor-positioning-api). With this release, esbuild now knows to expect a declaration list inside of the `@position-try` body block and will format it appropriately.

internal/bundler_tests/bundler_dce_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3183,6 +3183,8 @@ func TestCrossModuleConstantFoldingNumber(t *testing.T) {
31833183
x.a && x.b,
31843184
x.a || x.b,
31853185
x.a ?? x.b,
3186+
x.a ? 'y' : 'n',
3187+
!x.b ? 'y' : 'n',
31863188
])
31873189
`,
31883190

@@ -3226,6 +3228,8 @@ func TestCrossModuleConstantFoldingNumber(t *testing.T) {
32263228
a && b,
32273229
a || b,
32283230
a ?? b,
3231+
a ? 'y' : 'n',
3232+
!b ? 'y' : 'n',
32293233
])
32303234
`,
32313235

@@ -3288,6 +3292,8 @@ func TestCrossModuleConstantFoldingString(t *testing.T) {
32883292
x.a && x.b,
32893293
x.a || x.b,
32903294
x.a ?? x.b,
3295+
x.a ? 'y' : 'n',
3296+
!x.b ? 'y' : 'n',
32913297
])
32923298
`,
32933299

@@ -3314,6 +3320,8 @@ func TestCrossModuleConstantFoldingString(t *testing.T) {
33143320
a && b,
33153321
a || b,
33163322
a ?? b,
3323+
a ? 'y' : 'n',
3324+
!b ? 'y' : 'n',
33173325
])
33183326
`,
33193327

internal/bundler_tests/snapshots/snapshots_dce.txt

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,9 @@ console.log([
293293
], [
294294
6 /* b */,
295295
3 /* a */,
296-
3 /* a */
296+
3 /* a */,
297+
"y",
298+
"n"
297299
]);
298300

299301
---------- /out/const-entry.js ----------
@@ -331,7 +333,9 @@ console.log([
331333
], [
332334
6,
333335
3,
334-
3
336+
3,
337+
"y",
338+
"n"
335339
]);
336340

337341
---------- /out/nested-entry.js ----------
@@ -361,7 +365,9 @@ console.log([
361365
], [
362366
"bar" /* b */,
363367
"foo" /* a */,
364-
"foo" /* a */
368+
"foo" /* a */,
369+
"y",
370+
"n"
365371
]);
366372

367373
---------- /out/const-entry.js ----------
@@ -385,7 +391,9 @@ console.log([
385391
], [
386392
a && b,
387393
a || b,
388-
a ?? b
394+
a ?? b,
395+
a ? "y" : "n",
396+
b ? "n" : "y"
389397
]);
390398

391399
---------- /out/nested-entry.js ----------

internal/js_printer/js_printer.go

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1749,7 +1749,7 @@ func (p *printer) guardAgainstBehaviorChangeDueToSubstitution(expr js_ast.Expr,
17491749
// module numeric constants and bitwise operations. This is not an general-
17501750
// purpose/optimal approach and never will be. For example, we can't affect
17511751
// tree shaking at this stage because it has already happened.
1752-
func (p *printer) lateConstantFoldUnaryOrBinaryExpr(expr js_ast.Expr) js_ast.Expr {
1752+
func (p *printer) lateConstantFoldUnaryOrBinaryOrIfExpr(expr js_ast.Expr) js_ast.Expr {
17531753
switch e := expr.Data.(type) {
17541754
case *js_ast.EImportIdentifier:
17551755
ref := ast.FollowSymbols(p.symbols, e.Ref)
@@ -1779,7 +1779,7 @@ func (p *printer) lateConstantFoldUnaryOrBinaryExpr(expr js_ast.Expr) js_ast.Exp
17791779
}
17801780

17811781
case *js_ast.EUnary:
1782-
value := p.lateConstantFoldUnaryOrBinaryExpr(e.Value)
1782+
value := p.lateConstantFoldUnaryOrBinaryOrIfExpr(e.Value)
17831783

17841784
// Only fold again if something chained
17851785
if value.Data != e.Value.Data {
@@ -1802,8 +1802,8 @@ func (p *printer) lateConstantFoldUnaryOrBinaryExpr(expr js_ast.Expr) js_ast.Exp
18021802
}
18031803

18041804
case *js_ast.EBinary:
1805-
left := p.lateConstantFoldUnaryOrBinaryExpr(e.Left)
1806-
right := p.lateConstantFoldUnaryOrBinaryExpr(e.Right)
1805+
left := p.lateConstantFoldUnaryOrBinaryOrIfExpr(e.Left)
1806+
right := p.lateConstantFoldUnaryOrBinaryOrIfExpr(e.Right)
18071807

18081808
// Only fold again if something changed
18091809
if left.Data != e.Left.Data || right.Data != e.Right.Data {
@@ -1819,6 +1819,23 @@ func (p *printer) lateConstantFoldUnaryOrBinaryExpr(expr js_ast.Expr) js_ast.Exp
18191819
// Don't mutate the original AST
18201820
expr.Data = binary
18211821
}
1822+
1823+
case *js_ast.EIf:
1824+
test := p.lateConstantFoldUnaryOrBinaryOrIfExpr(e.Test)
1825+
1826+
// Only fold again if something changed
1827+
if test.Data != e.Test.Data {
1828+
if boolean, sideEffects, ok := js_ast.ToBooleanWithSideEffects(test.Data); ok && sideEffects == js_ast.NoSideEffects {
1829+
if boolean {
1830+
return p.lateConstantFoldUnaryOrBinaryOrIfExpr(e.Yes)
1831+
} else {
1832+
return p.lateConstantFoldUnaryOrBinaryOrIfExpr(e.No)
1833+
}
1834+
}
1835+
1836+
// Don't mutate the original AST
1837+
expr.Data = &js_ast.EIf{Test: test, Yes: e.Yes, No: e.No}
1838+
}
18221839
}
18231840

18241841
return expr
@@ -1969,7 +1986,7 @@ const (
19691986
isDeleteTarget
19701987
isCallTargetOrTemplateTag
19711988
isPropertyAccessTarget
1972-
parentWasUnaryOrBinary
1989+
parentWasUnaryOrBinaryOrIfTest
19731990
)
19741991

19751992
func (p *printer) printExpr(expr js_ast.Expr, level js_ast.L, flags printExprFlags) {
@@ -1983,10 +2000,10 @@ func (p *printer) printExpr(expr js_ast.Expr, level js_ast.L, flags printExprFla
19832000
// This sets a flag to avoid doing this when the parent is a unary or binary
19842001
// operator so that we don't trigger O(n^2) behavior when traversing over a
19852002
// large expression tree.
1986-
if p.options.MinifySyntax && (flags&parentWasUnaryOrBinary) == 0 {
2003+
if p.options.MinifySyntax && (flags&parentWasUnaryOrBinaryOrIfTest) == 0 {
19872004
switch expr.Data.(type) {
1988-
case *js_ast.EUnary, *js_ast.EBinary:
1989-
expr = p.lateConstantFoldUnaryOrBinaryExpr(expr)
2005+
case *js_ast.EUnary, *js_ast.EBinary, *js_ast.EIf:
2006+
expr = p.lateConstantFoldUnaryOrBinaryOrIfExpr(expr)
19902007
}
19912008
}
19922009

@@ -2655,7 +2672,7 @@ func (p *printer) printExpr(expr js_ast.Expr, level js_ast.L, flags printExprFla
26552672
p.print("(")
26562673
flags &= ^forbidIn
26572674
}
2658-
p.printExpr(e.Test, js_ast.LConditional, flags&forbidIn)
2675+
p.printExpr(e.Test, js_ast.LConditional, (flags&forbidIn)|parentWasUnaryOrBinaryOrIfTest)
26592676
p.printSpace()
26602677
p.print("?")
26612678
if p.options.LineLimit <= 0 || !p.printNewlinePastLineLimit() {
@@ -3141,7 +3158,7 @@ func (p *printer) printExpr(expr js_ast.Expr, level js_ast.L, flags printExprFla
31413158
}
31423159

31433160
if !e.Op.IsPrefix() {
3144-
p.printExpr(e.Value, js_ast.LPostfix-1, parentWasUnaryOrBinary)
3161+
p.printExpr(e.Value, js_ast.LPostfix-1, parentWasUnaryOrBinaryOrIfTest)
31453162
}
31463163

31473164
if entry.IsKeyword {
@@ -3162,7 +3179,7 @@ func (p *printer) printExpr(expr js_ast.Expr, level js_ast.L, flags printExprFla
31623179
}
31633180

31643181
if e.Op.IsPrefix() {
3165-
valueFlags := parentWasUnaryOrBinary
3182+
valueFlags := parentWasUnaryOrBinaryOrIfTest
31663183
if e.Op == js_ast.UnOpDelete {
31673184
valueFlags |= isDeleteTarget
31683185
}
@@ -3352,9 +3369,9 @@ func (v *binaryExprVisitor) checkAndPrepare(p *printer) bool {
33523369

33533370
if e.Op == js_ast.BinOpComma {
33543371
// The result of the left operand of the comma operator is unused
3355-
v.leftFlags = (v.flags & forbidIn) | exprResultIsUnused | parentWasUnaryOrBinary
3372+
v.leftFlags = (v.flags & forbidIn) | exprResultIsUnused | parentWasUnaryOrBinaryOrIfTest
33563373
} else {
3357-
v.leftFlags = (v.flags & forbidIn) | parentWasUnaryOrBinary
3374+
v.leftFlags = (v.flags & forbidIn) | parentWasUnaryOrBinaryOrIfTest
33583375
}
33593376
return true
33603377
}
@@ -3382,9 +3399,9 @@ func (v *binaryExprVisitor) visitRightAndFinish(p *printer) {
33823399

33833400
if e.Op == js_ast.BinOpComma {
33843401
// The result of the right operand of the comma operator is unused if the caller doesn't use it
3385-
p.printExpr(e.Right, v.rightLevel, (v.flags&(forbidIn|exprResultIsUnused))|parentWasUnaryOrBinary)
3402+
p.printExpr(e.Right, v.rightLevel, (v.flags&(forbidIn|exprResultIsUnused))|parentWasUnaryOrBinaryOrIfTest)
33863403
} else {
3387-
p.printExpr(e.Right, v.rightLevel, (v.flags&forbidIn)|parentWasUnaryOrBinary)
3404+
p.printExpr(e.Right, v.rightLevel, (v.flags&forbidIn)|parentWasUnaryOrBinaryOrIfTest)
33883405
}
33893406

33903407
if v.wrap {

0 commit comments

Comments
 (0)