swc-project/swc · error

illegal conversion: Cannot convert {:?} to Expression - babe

Error message

illegal conversion: Cannot convert {:?} to Expression - babel has no equivalent

What it means

babelify of expressions returns ExprOutput — either a normal Expression or a PrivateName (`#foo`). Babel's Expression union has no PrivateName member, so `From<ExprOutput> for Expression` (expr.rs:180-189) panics when a PrivateName lands in a slot typed as a plain Expression (object property values, call arguments, etc.). Babel only permits private names in two expression contexts: the left side of `#x in obj` and member properties like `this.#x`.

Source

Thrown at crates/swc_estree_compat/src/babelify/expr.rs:184

            Expr::OptChain(_) => panic!(
                "illegal conversion: Cannot convert {:?} to ExprOutput - babel has no equivalent",
                &self
            ),
            Expr::Invalid(_) => panic!(
                "illegal conversion: Cannot convert {:?} to ExprOutput - babel has no equivalent",
                &self
            ),
            #[cfg(swc_ast_unknown)]
            _ => panic!("unable to access unknown nodes"),
        }
    }
}

impl From<ExprOutput> for Expression {
    fn from(o: ExprOutput) -> Self {
        match o {
            ExprOutput::Expr(expr) => *expr,
            ExprOutput::Private(_) => panic!(
                "illegal conversion: Cannot convert {:?} to Expression - babel has no equivalent",
                &o
            ),
        }
    }
}

impl From<ExprOutput> for BinaryExprLeft {
    fn from(o: ExprOutput) -> Self {
        match o {
            ExprOutput::Expr(e) => BinaryExprLeft::Expr(e),
            ExprOutput::Private(p) => BinaryExprLeft::Private(p),
        }
    }
}

impl From<ExprOutput> for ObjectKey {
    fn from(o: ExprOutput) -> Self {

View on GitHub (pinned to 5176682b65)

Solutions

  1. Keep private names only in `#x in obj` left-side or member-property positions.
  2. Fix the producer (plugin/builder) that placed a PrivateName into a plain expression slot.
  3. Pre-scan the AST for PrivateName in Expression-typed positions and reject with a span.

Example fix

// before: private name in a value slot (invalid)
Prop::KeyValue(KeyValueProp { key: k, value: Expr::PrivateName(p) })

// after: keep it in a supported position
Expr::Bin(BinExpr { op: op!(in), left: Box::new(Expr::PrivateName(p)), right, .. })
Defensive patterns

Strategy: type-guard

Type guard

// PrivateName is only legal as `#x in obj` left side or member property.
fn has_private_name_in_value(program: &Program) -> bool {
    struct Scan(bool);
    impl Visit for Scan {
        fn visit_expr(&mut self, e: &Expr) {
            if let Expr::PrivateName(_) = e {
                // reaching generic expression traversal at all means it is
                // outside the supported positions
                self.0 = true;
            }
            e.visit_children_with(self);
        }
        fn visit_member_expr(&mut self, m: &MemberExpr) {
            m.obj.visit_with(self); // PrivateName member props handled elsewhere
        }
    }
    let mut s = Scan(false);
    program.visit_with(&mut s);
    s.0
}

Prevention

When it happens

Trigger: Babelifying an AST where a PrivateName appears as a general expression value — practically only from plugins/codemods or hand-built ASTs, since the parser rejects `#x` in value positions (BinaryExprLeft, in contrast, is converted losslessly at expr.rs:192+).

Common situations: Codemods moving private names around; AST fixtures exercising private class members.

Related errors


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