parcel-bundler/parcel · error · napi::Error

GenericFailure

GenericFailure

Error message

Could not convert value returned from macro to AST.

What it means

Returned (as a napi::Error of Status::GenericFailure) by @parcel/macros when napi_to_js_value receives a value whose ValueType is Symbol, External, or Unknown. The macro system can round-trip undefined/null/number/boolean/string/array/object/regex/function, but has no AST representation for these three types, so conversion fails and the error propagates to the JS caller.

Source

Thrown at crates/macros/src/napi.rs:165

        let names = obj.get_property_names()?;
        let len = names.get_array_length()?;
        let mut props = IndexMap::with_capacity(len as usize);
        for i in 0..len {
          let prop = names.get_element::<JsString>(i)?;
          let name = prop.into_utf8()?.into_owned()?;
          let value = napi_to_js_value(obj.get_property(prop)?, env)?;
          props.insert(name, value);
        }
        Ok(JsValue::Object(props))
      }
    }
    ValueType::Function => {
      let f = unsafe { value.cast::<JsFunction>() };
      let source = f.coerce_to_string()?.into_utf8()?.into_owned()?;
      Ok(JsValue::Function(source))
    }
    ValueType::Symbol | ValueType::External | ValueType::Unknown => Err(napi::Error::new(
      napi::Status::GenericFailure,
      "Could not convert value returned from macro to AST.",
    )),
  }
}

fn await_promise(
  env: Env,
  result: JsUnknown,
  tx: Sender<Result<JsValue, MacroError>>,
) -> napi::Result<()> {
  // If the result is a promise, wait for it to resolve, and send the result to the channel.
  // Otherwise, send the result immediately.
  if result.is_promise()? {
    let result: JsObject = result.try_into()?;
    let then: JsFunction = result.get_named_property("then")?;
    let tx2 = tx.clone();
    let cb = env.create_function_from_closure("callback", move |ctx| {

View on GitHub (pinned to 59484858a1)

Solutions

  1. Ensure the macro returns only plain JSON-representable data (strings, numbers, booleans, arrays, plain objects, regexes, functions-as-source).
  2. Strip or stringify any Symbol-keyed/valued properties before returning.
  3. Avoid returning native/external handles; serialize the data you need first.

Example fix

// before
export default function macro() {
  return { id: Symbol('x') };
}
// after
export default function macro() {
  return { id: 'x' };
}
Defensive patterns

Strategy: type-guard

Validate before calling

function sanitizeForAst(v) {
  if (typeof v === 'symbol') throw new Error('Cannot return Symbol from macro');
  if (Array.isArray(v)) return v.map(sanitizeForAst);
  if (v && typeof v === 'object') {
    const out = {};
    for (const [k, val] of Object.entries(v)) out[k] = sanitizeForAst(val);
    return out;
  }
  return v;
}

Type guard

function isMacroSafeValue(v, seen = new WeakSet()) {
  if (typeof v === 'symbol') return false;
  if (v === null || typeof v !== 'object') return true;
  if (seen.has(v)) return false;
  seen.add(v);
  return Object.getOwnPropertySymbols(v).length === 0 &&
    Object.values(v).every(x => isMacroSafeValue(x, seen));
}

Try / catch

try {
  result = macro(...args);
} catch (e) {
  if (/Could not convert value returned from macro/.test(e?.message)) {
    throw new Error('Macro returned a Symbol/External/Unknown value; serialize it first.');
  }
  throw e;
}

Prevention

When it happens

Trigger: A Parcel macro (JS function invoked during transform) returns a value that contains, at any depth, a Symbol, a napi External (native pointer), or a value Node reports as Unknown.

Common situations: Macro returns an object with a Symbol-keyed property or a Symbol value (e.g. Symbol.for('x')), returns a class instance whose prototype chain exposes a Symbol, or returns an object embedding a native handle (Buffer internals, FFI).

Related errors


AI-assisted analysis of parcel-bundler/parcel@59484858a1 (2026-08-13). Data as JSON: /api/errors/4ee87079b49f439a. Report an issue: GitHub.