Skip to content

fix: late constant folding for cross-module if-statement DCE#4413

Open
Felipeness wants to merge 2 commits intoevanw:mainfrom
Felipeness:fix/cross-module-dce-if-stmt
Open

fix: late constant folding for cross-module if-statement DCE#4413
Felipeness wants to merge 2 commits intoevanw:mainfrom
Felipeness:fix/cross-module-dce-if-stmt

Conversation

@Felipeness
Copy link
Copy Markdown

Summary

When bundling with --minify-syntax, imported constants like export const DEV = false are inlined at print time via ConstValues. However, if statements (SIf) with these inlined constants were not folded — only if expressions (EIf/ternary) were. This left dead branches in the output:

// constants.js
export const DEV = false

// entry.js
import { DEV } from './constants'
if (DEV) {
  let detail = "detailed: " + msg
  throw new Error(detail)
} else {
  throw new Error("ERROR_CODE")
}

Before:

// Both branches kept, condition becomes !1 but if-statement stays
if (!1) { let e = "detailed: " + msg; throw new Error(e); } else throw new Error("ERROR_CODE");

After:

// Dead branch eliminated, only live branch remains
throw new Error("ERROR_CODE");

Approach

Added late constant folding for SIf in printStmt() (mirroring how EIf is already handled in lateConstantFoldUnaryOrBinaryOrIfExpr()):

  1. When MinifySyntax is enabled, fold the test expression through lateConstantFoldUnaryOrBinaryOrIfExpr() (which resolves EImportIdentifier refs via ConstValues)
  2. Use ToBooleanWithSideEffects() to determine if the condition is statically known
  3. If true → print only the "yes" branch; if false → print only the "no" branch (or nothing)

Also added isLateConstantFoldedSideEffectFree() to drop residual expression statements like !1; that result from logical expressions with inlined constants (e.g., DEV && console.log("debug")!1;).

Test

go test ./... -count=1
ok  github.com/evanw/esbuild/internal/bundler_tests  2.210s
ok  github.com/evanw/esbuild/internal/js_printer      0.733s
... (all packages pass)

Three new snapshot tests added:

  • TestCrossModuleConstantFoldingIfStmt — basic if (DEV) elimination
  • TestCrossModuleConstantFoldingIfStmtThrow — if/else with throw in both branches
  • TestCrossModuleConstantFoldingIfStmtMultiple — multiple independent if statements

Fixes #2589, fixes #3964.

When bundling with --minify-syntax, imported constants like
`export const DEV = false` are inlined at print time via ConstValues.
However, `if` statements with these inlined constants were not folded
because mangleIf() runs at parse time (before cross-module resolution)
and the printer's late constant folding only handled expressions (EIf),
not statements (SIf).

This adds late constant folding for SIf in printStmt(): when the test
expression resolves to a known boolean after ConstValues substitution,
the dead branch is eliminated entirely.

Also drops residual side-effect-free expression statements (like `!1;`)
that result from cross-module constant inlining in logical expressions.

Fixes evanw#2589, fixes evanw#3964.
@Felipeness
Copy link
Copy Markdown
Author

Hey @evanw, just a gentle ping on this and the related #4412. Both PRs have passing tests (\FAIL ./... [setup failed]
FAIL) and address longstanding issues (#2589, #3964, #2605). Happy to adjust anything if the approach doesn't align with your vision for esbuild. No rush — I know you review in batches!

Copy link
Copy Markdown

@care44cubs-max care44cubs-max left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes

Copy link
Copy Markdown

@care44cubs-max care44cubs-max left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

missing JS minification imported constant false not used in dead code elimination

2 participants