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

  1. Create fresh Globals (swc_common::globals::Globals::new()) for every compilation so SyntaxContexts never repeat
  2. Audit custom passes for node cloning that duplicates identifiers without fresh hygiene marks (use Mark/resolver correctly)
  3. 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

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


AI-assisted analysis of swc-project/swc@5176682b65 (2026-08-17). Data as JSON: /api/errors/4ae78e02915dea9c. Report an issue: GitHub.