PyO3/pyo3 · error
complex enum has a non-unit variant
Error message
complex enum has a non-unit variant
What it means
PyClassComplexEnum::new is only called after the enum has been determined to be a 'complex' enum (one containing at least one non-unit variant). It uses find + expect to grab the first non-unit variant as a witness, so the panic fires if this constructor is invoked on an enum whose variants are all unit variants — a caller-side invariant violation.
Source
Thrown at pyo3-macros-backend/src/pyclass.rs:676
ident,
repr_type,
variants,
})
}
}
struct PyClassComplexEnum<'a> {
ident: &'a syn::Ident,
variants: Vec<PyClassEnumVariant<'a>>,
}
impl<'a> PyClassComplexEnum<'a> {
fn new(enum_: &'a mut syn::ItemEnum) -> syn::Result<Self> {
let witness = enum_
.variants
.iter()
.find(|variant| !matches!(variant.fields, syn::Fields::Unit))
.expect("complex enum has a non-unit variant")
.ident
.to_owned();
let extract_variant_data =
|variant: &'a mut syn::Variant| -> syn::Result<PyClassEnumVariant<'a>> {
use syn::Fields;
let ident = &variant.ident;
let options = EnumVariantPyO3Options::take_pyo3_options(&mut variant.attrs)?;
let variant = match &variant.fields {
Fields::Unit => {
bail_spanned!(variant.span() => format!(
"Unit variant `{ident}` is not yet supported in a complex enum\n\
= help: change to an empty tuple variant instead: `{ident}()`\n\
= note: the enum is complex because of non-unit variant `{witness}`",
ident=ident, witness=witness))
}
Fields::Named(fields) => {View on GitHub (pinned to ac9b6899d3)
Solutions
- Route unit-only enums to the simple-enum constructor instead of PyClassComplexEnum::new.
- Check the caller's classification logic (e.g. the variants.any(!unit) check) before choosing the complex path.
- Report to pyo3 if stock #[pyclass] triggers it on a mixed enum.
Example fix
// before (caller dispatch)
PyClassComplexEnum::new(&mut item_enum) // for enum { A, B }
// after
if has_non_unit_variant { PyClassComplexEnum::new(...) } else { PyClassSimpleEnum::new(...) } Defensive patterns
Strategy: validation
Validate before calling
// Dispatch correctly: only complex enums go to PyClassComplexEnum let is_complex = item_enum.variants.iter().any(|v| !matches!(v.fields, syn::Fields::Unit));
Type guard
fn is_complex_enum(e: &syn::ItemEnum) -> bool {
e.variants.iter().any(|v| !matches!(v.fields, syn::Fields::Unit))
} Prevention
- Check enum shape before choosing the simple vs complex codegen path.
- Keep dispatch logic and constructors' expectations together in one place.
- Add debug_asserts mirroring the constructor invariants.
When it happens
Trigger: Internal misuse: calling PyClassComplexEnum::new on an enum with only unit variants (the simple-enum path should have been taken instead).
Common situations: Only when forking/patching pyo3-macros-backend or routing ASTs to the wrong enum representation (e.g. after changing how #[pyclass(enum)] discriminates simple vs complex enums); invisible to regular users.
Related errors
- named field has an identifier
- Named fields should have identifiers
- Empty enum
- no class given for Fn with a "self" receiver
- no class given for a class method
AI-assisted analysis of PyO3/pyo3@ac9b6899d3 (2026-09-05).
Data as JSON: /api/errors/bdbe1b9209c0423b.
Report an issue: GitHub.