swc-project/swc · error · TypeError

A class descriptor's .kind property must be "class", but a d

Error message

A class descriptor's .kind property must be "class", but a decorator created a class descriptor with .kind "${kind}"

What it means

While expanding #[derive(DeserializeEnum)] (used by swc's #[ast_node] enums), the macro reads each variant's #[tag(..)] attribute and parses its token stream with syn::parse2::<VariantAttr>().expect("failed to parse #[tag] attribute"). VariantAttr only accepts a comma-separated list of literals (Punctuated<Lit, Comma>), so any non-literal token inside #[tag(..)] makes parse2 fail and panics the proc macro at compile time.

Source

Thrown at crates/swc_ecma_transforms_base/src/helpers/generated/_decorate.rs:239

}
function _toElementFinisherExtras(elementObject) {
    var element = _toElementDescriptor(elementObject);
    var finisher = _optionalCallableProperty(elementObject, "finisher");
    var extras = _toElementDescriptors(elementObject.extras);

    return { element: element, finisher: finisher, extras: extras };
}
function _fromClassDescriptor(elements) {
    var obj = { kind: "class", elements: elements.map(_fromElementDescriptor) };
    var desc = { value: "Descriptor", configurable: true };
    Object.defineProperty(obj, Symbol.toStringTag, desc);

    return obj;
}
function _toClassDescriptor(obj) {
    var kind = String(obj.kind);
    if (kind !== "class") {
        throw new TypeError("A class descriptor's .kind property must be \"class\", but a decorator" + " created a class descriptor with .kind \"" + kind + "\"");
    }
    _disallowProperty(obj, "key", "A class descriptor");
    _disallowProperty(obj, "placement", "A class descriptor");
    _disallowProperty(obj, "descriptor", "A class descriptor");
    _disallowProperty(obj, "initializer", "A class descriptor");
    _disallowProperty(obj, "extras", "A class descriptor");
    var finisher = _optionalCallableProperty(obj, "finisher");
    var elements = _toElementDescriptors(obj.elements);

    return { elements: elements, finisher: finisher };
}
function _disallowProperty(obj, name, objectType) {
    if (obj[name] !== undefined) throw new TypeError(objectType + " can't have a ." + name + " property.");
}
function _optionalCallableProperty(obj, name) {
    var value = obj[name];
    if (value !== undefined && typeof value !== "function") {
        throw new TypeError("Expected '" + name + "' to be a function");

View on GitHub (pinned to 5176682b65)

Solutions

  1. Write the attribute as a comma-separated list of string literals: #[tag("TagName")] or #[tag("A", "B")].
  2. Quote every tag - identifiers, numbers-as-names, and constants are not accepted, only literals.
  3. Separate multiple tags with commas; a single string "*" is the reserved wildcard.
  4. Keep the outer form as a list (#[tag(..)]), never #[tag = ..] or #[tag(..) = ..].

Example fix

// before: bare identifier -> panic 'failed to parse #[tag] attribute'
#[ast_node("Meta")]
enum Node {
    #[tag(Ident)]
    Ident(Box<Ident>),
}

// after: quoted string literal
#[ast_node("Meta")]
enum Node {
    #[tag("Ident")]
    Ident(Box<Ident>),
}
Defensive patterns

Strategy: validation

Validate before calling

// Authoring-time pattern: tags are comma-separated string literals only.
// OK:   #[tag("Ident")]
// OK:   #[tag("A", "B")]
// OK:   #[tag("*")]            (wildcard)
// BAD:  #[tag(Ident)]  #[tag("a" "b")]  #[tag = "x"]
// If you generate enums, validate before emitting:
fn valid_tag(tag: &str) -> bool {
    tag.chars().all(|c| !c.is_whitespace()) && !tag.is_empty()
}
let attr = format!("#[tag({})]", tags.iter().map(|t| format!("{:?}", t)).collect::<Vec<_>>().join(", "));

Prevention

When it happens

Trigger: Writing #[tag(Name)] (a bare identifier instead of the string literal "Name"), #[tag("a" "b")] without a comma, function-call syntax like #[tag(tag("x"))], or expressions inside the attribute. Note the separate earlier panic for the wrong meta form (#[tag = "x"] gives '#[tag] attribute must be in form of #[tag(..)]'), and the later assert requiring at least one tag per variant.

Common situations: First-time authors of swc AST node enums copying serde's #[serde(tag = "type")] habits; renaming tags with unquoted identifiers; typos like missing quotes after refactoring; version changes that moved from one accepted syntax to strictly literal lists.

Related errors


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