Skip to content

[Bug]: Cross-module enum inlining mis-folds auto-increment member to 0 #9494

@lukasIO

Description

@lukasIO

Reproduction link or steps

https://repl.rolldown.rs/#eNp1UstuwjAQ/JWVLwE1Cm2PqXoA1Ftv7QGkXCyzoS5mHcUOUEX5925sCI/SXGLvzI5ntdOKUuSt0LTCQ+ZdfyaRn++pUP11W9naQwtvB481SQMdlLXdQpJN8FhKXgoqCKnZwrtVTGkLApjC69CULdK+NLssLUNpnsLFN5kAP2i00j6HCmv4/EhhJ02DoB23P8ATSA91Q15vsaCuf7hsSHltCZSRzunyZ7TLo5FxdOL22qsvGO2OdwAlHUZKNs2hRt/UBMm0H+QGnp3h2R14fobnEWZLMNQa2pDdU48Eq8qSswYzY9ej5KgAr0l6Uhsz8YozjHQiBPaf6v99zzcdz8zl1aLIS2kcdnw8LeScgctSjAEeQgzCjocktLDghT6msOTfUz+5YL3aGrPioTM2VOr1hewd5CZkKyw14TzAQ9BObUlIWTTCRNmY8B8aRmG5mqqGw1MMQS4EB60bR3OVVBu5xuzbWXYUbV3VoqGgVIgVVsgqpDSyTA4tjximZKUd04z06LzofgGdKxRC

What is expected?

Local.C = 2
classify(Local.C) = C
classify(2) = C

What is actually happening?

Local.C = 0
classify(Local.C) = A
classify(2) = unknown

rolldown output:

var Local = /* @__PURE__ */ function(Local) {
    Local[Local["A"] = 0] = "A";               // External.X folded ✓
    Local[Local["B"] = 1] = "B";               // External.Y folded ✓
    Local[Local["C"] = 1 + Local["B"]] = "C";  // runtime value 2 ✓
    return Local;
}(Local || {});
function classify(v) {
    switch (v) {
        case Local.A: return "A";
        case Local.B: return "B";
        case 0: return "C";          // ← should be `case Local.C` / `case 2`
    }
    return "unknown";
}
console.log("Local.C =", 0);                    // ← should be Local.C / 2
console.log("classify(Local.C) =", classify(0));// ← should pass 2
console.log("classify(2) =", classify(2));

In a switch, the inlined case 0: is unreachable because case Local.A: (also runtime 0) catches first. Anything that actually passes the runtime value 2 matches no case and falls through.

System Info

reproducible in the REPL as linked above

Any additional comments?

Running [email protected] on the same input.ts leaves call-site references as Local.C (no broken inlining). The IIFE comes out identical. The buggy substitution only appears once rolldown does cross-module folding (External.X → 0, External.Y → 1) on top.

It looks like the IIFE-rewriting pass folds the imported initializers to literals but stops short of folding 1 + Local["B"] to 2, while a separate consumer-inlining pass tries to substitute Local.C at call sites, can't recover a literal, and silently emits 0.

Workaround

Give the trailing member an explicit initializer:

enum Local {
  A = External.X,
  B = External.Y,
  C = External.Y + 1,
}

Metadata

Metadata

Assignees

Type

Priority

None yet

Effort

None yet

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions