swc-project/swc · error

enum member types must be consistent: {:?}

Error message

enum member types must be consistent: {:?}

What it means

Encode's derive folds all non-unknown variants into one EnumType (Unit/One/Struct) and requires uniformity. Named-field variants cannot coexist with single-field tuple ('newtype') variants — the (Struct, One) combination falls into the catch-all and panics with the conflicting pair; a Unit+One mix is upgraded to Struct, so Unit+One+One also ends here. The {:?} payload prints the colliding EnumTypes, e.g. (Struct, One).

Source

Thrown at crates/ast_node/src/encoding/encode.rs:73

                None,
                |mut sum, next| {
                    let ty = match &next.fields {
                        syn::Fields::Named(_) => EnumType::Struct,
                        syn::Fields::Unnamed(fields) if fields.unnamed.len() == 1 => EnumType::One,
                        syn::Fields::Unit => EnumType::Unit,
                        syn::Fields::Unnamed(_) => {
                            panic!("more than 1 unnamed member field are not allowed")
                        }
                    };
                    match (*sum.get_or_insert(ty), ty) {
                        (EnumType::Struct, EnumType::Struct)
                        | (EnumType::Struct, EnumType::Unit)
                        | (EnumType::Unit, EnumType::Unit)
                        | (EnumType::One, EnumType::One) => (),
                        (EnumType::Unit, EnumType::One)
                        | (EnumType::One, EnumType::Unit)
                        | (_, EnumType::Struct) => sum = Some(EnumType::Struct),
                        _ => panic!("enum member types must be consistent: {:?}", (sum, ty)),
                    }
                    sum
                },
            );
            let enum_type = enum_type.expect("enum cannot be empty");
            let mut iter = data.variants.iter().peekable();

            let unknown_arm: Option<syn::Arm> = iter.next_if(|variant| is_unknown(&variant.attrs))
                .map(|unknown| {
                    let name = &unknown.ident;
                    assert!(
                        unknown.discriminant.is_none(),
                        "unknown member is not allowed custom discriminant"
                    );
                    assert!(
                        is_with(&unknown.attrs).is_none(),
                        "unknown member is not allowed with type"
                    );

View on GitHub (pinned to 5176682b65)

Solutions

  1. Make all variants the same shape — usually convert newtype variants to named ones.
  2. Alternatively wrap named payloads in a struct so every variant is single-field.
  3. Read the (X, Y) pair in the panic message to find the disagreeing variant shapes.

Example fix

// before
#[derive(Encode, Decode)]
enum Value {
    Num(f64),                 // One
    Obj { props: Vec<Prop> }, // Struct — inconsistent
}

// after — uniform named-struct variants
#[derive(Encode, Decode)]
enum Value {
    Num { value: f64 },
    Obj { props: Vec<Prop> },
}
Defensive patterns

Strategy: validation

Prevention

When it happens

Trigger: An enum mixing `A { x: u32 }` (Struct) with `B(u32)` (One) under derive(Encode)/#[ast_node]; or Unit plus multiple One variants, which folds to Struct and then clashes with a later One.

Common situations: Incrementally growing an AST enum where later variants use a different style; merging branches that each added variants in different shapes.

Related errors


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