swc-project/swc · error
Multiple identifiers equivalent up to span hygiene found: {:
Error message
Multiple identifiers equivalent up to span hygiene found: {:#?}\nFirst = {:#?}\nSecond = {:#?} What it means
swc_bundler's inliner builds equivalence classes of identifiers that are identical up to span hygiene (symbol + SyntaxContext). store() panics if the same Id is inserted twice with different targets, meaning analysis found two bindings it considers the same identifier that must inline to different places — an invariant break almost always caused by hygiene marks being lost, reused, or duplicated by earlier transforms.
Source
Thrown at crates/swc_bundler/src/inline.rs:51
module.par_visit_mut_with(&mut v);
module.retain_mut(|_, s| !matches!(s, ModuleItem::Stmt(Stmt::Empty(..))));
}
#[derive(Debug)]
#[cfg_attr(feature = "concurrent", derive(Clone))]
struct Inliner {
data: Readonly<InlineData>,
}
struct Analyzer<'a> {
injected_ctxt: SyntaxContext,
data: &'a mut InlineData,
}
impl Analyzer<'_> {
fn store(&mut self, from: Id, to: Id) {
if let Some(prev) = self.data.ids.insert(from.clone(), to.clone()) {
unreachable!(
"Multiple identifiers equivalent up to span hygiene found: {:#?}\nFirst = \
{:#?}\nSecond = {:#?}",
from, prev, to
)
}
}
}
impl Visit for Analyzer<'_> {
noop_visit_type!();
/// Noop
fn visit_module_decl(&mut self, _: &ModuleDecl) {}
/// Noop. We don't inline variables declared in subscopes.
fn visit_function(&mut self, _: &Function) {}
/// Noop. We don't inline variables declared in subscopes.View on GitHub (pinned to 5176682b65)
Solutions
- Create fresh Globals (swc_common::globals::Globals::new()) for every compilation so SyntaxContexts never repeat
- Audit custom passes for node cloning that duplicates identifiers without fresh hygiene marks (use Mark/resolver correctly)
- Confirm the inliner is the trigger by disabling inlining; if so, reduce the two colliding modules and report upstream
Defensive patterns
Strategy: fallback
Try / catch
let out = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| bundler.bundle(&entries)?));
match out {
Ok(v) => v,
Err(p) if panic_message(&p).contains("span hygiene") => {
// fallback: rerun the whole build with fresh Globals and inlining disabled
swc_common::globals::Globals::new().set(|| { /* rebuild without inline */ });
}
Err(p) => std::panic::resume_unwind(p),
} Prevention
- Create fresh swc_common::Globals (and Marks) per compilation, never share them across builds
- Audit custom transforms for AST cloning that duplicates identifiers without fresh hygiene marks
- Provide a build option to disable bundler inlining so there is always a fallback path
- Run the bundler in a worker process to contain hygiene panics
When it happens
Trigger: Bundling with the inlining step enabled where two modules (or two transforms of the same module) produce bindings with identical symbol and SyntaxContext that inline differently; custom passes that clone AST nodes without assigning fresh marks; reusing one Globals/Mark context across sequential builds in the same process.
Common situations: Custom swc transforms executed before bundling that clone/copy bindings; long-lived build servers that reuse swc_common::Globals between compilations; heavy macro- or codemod-generated code entering the graph.
Related errors
- Modules reexported with `export * as foo from './foo'` shoul
- Plan does not contain bundle kind for {:?}
- Decl in ExportDecl: {:?}
- internal error: entered unreachable code
- Non-computed member expression with property other than iden
AI-assisted analysis of swc-project/swc@5176682b65 (2026-08-17).
Data as JSON: /api/errors/4ae78e02915dea9c.
Report an issue: GitHub.