{"record":{"id":"75c45ddd77909be1","repo":"PyO3/pyo3","slug":"empty-enum-75c45d","errorCode":null,"errorMessage":"Empty enum","messagePattern":"Empty enum","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pyo3-macros-backend/src/intopyobject.rs","lineNumber":475,"sourceCode":"                target: quote!(#pyo3_path::types::PyAny),\n                output: quote!(#pyo3_path::Bound<'py, <Self as #pyo3_path::conversion::IntoPyObject<'py>>::Target>),\n                error: quote!(#pyo3_path::PyErr),\n            },\n            body: quote! {\n                match self {\n                    #variants\n                }\n            },\n        }\n    }\n\n    #[cfg(feature = \"experimental-inspect\")]\n    fn output_type(&self) -> PyExpr {\n        self.variants\n            .iter()\n            .map(|var| var.output_type())\n            .reduce(PyExpr::union)\n            .expect(\"Empty enum\")\n    }\n}\n\n// if there is a `'py` lifetime, we treat it as the `Python<'py>` lifetime\nfn verify_and_get_lifetime(generics: &syn::Generics) -> Option<&syn::LifetimeParam> {\n    let mut lifetimes = generics.lifetimes();\n    lifetimes.find(|l| l.lifetime.ident == \"py\")\n}\n\npub fn build_derive_into_pyobject<const REF: bool>(tokens: &DeriveInput) -> Result<TokenStream> {\n    let options = ContainerAttributes::from_attrs(&tokens.attrs)?;\n    let ctx = &Ctx::new(&options.krate, None);\n    let Ctx { pyo3_path, .. } = &ctx;\n\n    let (_, ty_generics, _) = tokens.generics.split_for_impl();\n    let mut trait_generics = tokens.generics.clone();\n    if REF {\n        trait_generics.params.push(parse_quote!('_a));","sourceCodeStart":457,"sourceCodeEnd":493,"githubUrl":"https://github.com/PyO3/pyo3/blob/ac9b6899d348be4d54614d060dea53a645a12e36/pyo3-macros-backend/src/intopyobject.rs#L457-L493","documentation":"The #[pyclass] macro computes the output type of an enum by unioning the output types of all its variants with Iterator::reduce, which returns None for an empty iterator; the expect then panics. It guards the assumption that a pyclass enum has at least one variant. An empty enum (enum E {}) has no representable value, so PyO3 refuses to generate code for it.","triggerScenarios":"Applying #[pyclass] (or the IntoPyObject/derive machinery) to an enum with zero variants, e.g. `#[pyclass] enum Never {}`.","commonSituations":"Users porting never-type-like empty enums from Rust-only code into pyo3-exposed types, or code-generated enums that ended up with no variants after cfg-stripping (e.g. all variants behind #[cfg] features that are off).","solutions":["Add at least one variant to the enum.","If the enum is a never type, do not expose it to Python; keep it out of #[pyclass].","Check feature flags: ensure at least one variant is not compiled out by cfg when the crate builds.","Wrap the type differently (e.g. expose a unit variant placeholder) if a placeholder value is acceptable."],"exampleFix":"// before\n#[pyclass]\nenum Never {}\n// after\n#[pyclass]\nenum Never {\n    Unreachable,\n}","handlingStrategy":"validation","validationCode":"// Before adding #[pyclass] to an enum, ensure it has variants\nassert!(variants > 0, \"pyclass enum must have at least one variant\");","typeGuard":"fn has_variants(e: &syn::ItemEnum) -> bool { !e.variants.is_empty() }","tryCatchPattern":null,"preventionTips":["Never declare pyclass enums with zero variants.","Audit cfg-gated variants: keep at least one variant enabled in every feature combination.","Treat empty (never-type) enums as non-Python types."],"tags":["pyo3","proc-macro","enum","compile-time-panic"],"backgroundTag":"empty-enum-not-constructible","analyzedSha":"ac9b6899d348be4d54614d060dea53a645a12e36","analyzedAt":"2026-09-05T09:20:35.319Z","contentChangedAt":"2026-09-05T09:20:35.319Z","schemaVersion":2},"datasetVersion":"2026-09-12T12:17:11.808Z"}