swc-project/swc · error

unable to access unknown nodes

Error message

unable to access unknown nodes

What it means

infer_type_from_expr (crates/swc_typescript/src/fast_dts/inferrer.rs) infers a .d.ts type from expressions such as parameter defaults and exported initializers; for `Expr::Lit` it maps literal kinds to keyword types (string/bool/number/bigint/null). Under `--cfg swc_ast_unknown`, a literal kind this build does not recognize decodes to `Unknown(tag, value)` and the catch-all arm panics, since no keyword type can be derived from an opaque node. The root cause is always an AST produced by a different swc_ecma_ast version crossing a serialization boundary.

Source

Thrown at crates/swc_typescript/src/fast_dts/inferrer.rs:28

    util::types::{ts_keyword_type, type_ann},
    FastDts,
};

impl FastDts {
    pub(crate) fn infer_type_from_expr(&mut self, e: &Expr) -> Option<Box<TsType>> {
        match e {
            Expr::Ident(ident) if ident.sym.as_str() == "undefined" => {
                Some(ts_keyword_type(TsKeywordTypeKind::TsUndefinedKeyword))
            }
            Expr::Lit(lit) => match lit {
                Lit::Str(_) => Some(ts_keyword_type(TsKeywordTypeKind::TsStringKeyword)),
                Lit::Bool(_) => Some(ts_keyword_type(TsKeywordTypeKind::TsBooleanKeyword)),
                Lit::Num(_) => Some(ts_keyword_type(TsKeywordTypeKind::TsNumberKeyword)),
                Lit::BigInt(_) => Some(ts_keyword_type(TsKeywordTypeKind::TsBigIntKeyword)),
                Lit::Null(_) => Some(ts_keyword_type(TsKeywordTypeKind::TsNullKeyword)),
                Lit::Regex(_) | Lit::JSXText(_) => None,
                #[cfg(swc_ast_unknown)]
                _ => panic!("unable to access unknown nodes"),
            },
            Expr::Tpl(_) => Some(ts_keyword_type(TsKeywordTypeKind::TsStringKeyword)),
            Expr::Fn(fn_expr) => self.transform_fn_to_ts_type(
                &fn_expr.function,
                fn_expr.ident.as_ref().map(|ident| ident.span),
            ),
            Expr::Arrow(arrow_expr) => self.transform_arrow_expr_to_ts_type(arrow_expr),
            Expr::Array(arr) => {
                self.array_inferred(arr.span);
                Some(ts_keyword_type(TsKeywordTypeKind::TsUnknownKeyword))
            }
            Expr::Object(obj) => self.transform_object_to_ts_type(obj, false),
            Expr::Class(class) => {
                self.inferred_type_of_class_expression(class.span());
                Some(ts_keyword_type(TsKeywordTypeKind::TsUnknownKeyword))
            }
            Expr::Paren(expr) => self.infer_type_from_expr(&expr.expr),
            Expr::TsNonNull(non_null) => self.infer_type_from_expr(&non_null.expr),

View on GitHub (pinned to 5176682b65)

Solutions

  1. Unify the swc_core/swc_ecma_ast version between the serializer and the swc_typescript build, then rebuild both.
  2. Upgrade swc_typescript/swc_core so the new literal kind is known and maps to a real TsType instead of Unknown.
  3. Re-parse source text in the same process before FastDts::transform rather than reusing a foreign AST.
  4. Treat --cfg swc_ast_unknown builds as version-sensitive: rebuild/re-deploy them whenever the producing side is upgraded.

Example fix

// before: two crates resolve different swc_ecma_ast versions
# Cargo.toml
swc_typescript = "9"
swc_ecma_ast = "0.118"   # newer than what swc_typescript was built for

// after: let swc_core drive every AST crate version
swc_core = { version = "=16.0.0", features = ["ecma_ast", "typescript"] }
Defensive patterns

Strategy: type-guard

Validate before calling

// Before inferring types from deserialized default values:
#[cfg(swc_ast_unknown)]
fn default_value_lits_known(params: &[Param]) -> bool {
    params.iter().all(|p| match &p.pat {
        Pat::Assign(a) => expr_lits_known(&a.right),
        _ => true,
    })
}
#[cfg(swc_ast_unknown)]
fn expr_lits_known(e: &Expr) -> bool {
    !matches!(e, Expr::Lit(l) if matches!(l, Lit::Unknown(..)))
}

Type guard

#[cfg(swc_ast_unknown)]
fn lit_is_known(l: &Lit) -> bool {
    !matches!(l, Lit::Unknown(..))
}

Try / catch

use std::panic::{catch_unwind, AssertUnwindSafe};
let issues = catch_unwind(AssertUnwindSafe(|| dts.transform(&mut program)))
    .map_err(|p| format!("fast_dts: {}", p.downcast_ref::<&str>().unwrap_or(&"panic")))?;

Prevention

When it happens

Trigger: FastDts infers a type from a default value or exported expression (e.g. `export const x = <newLiteralKind>`) whose literal node tag is unknown to this build — a Lit variant added in a newer swc_ecma_ast and deserialized into this older binary.

Common situations: Wasm plugins built against an older swc_core receiving AST from a newer @swc/core host; CI checks compiled with RUSTFLAGS="--cfg swc_ast_unknown" exercising newer recorded fixtures; multi-process pipelines that hand serialized ASTs between binaries built at different times.

Related errors


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