{"record":{"id":"2c371088e3e1da4f","repo":"bevyengine/bevy","slug":"meta-only-supports-structs-not-unions","errorCode":null,"errorMessage":"#[{meta}] only supports structs, not unions","messagePattern":"#\\[(.+?)\\] only supports structs, not unions","errorType":"validation","errorClass":"syn::Error","httpStatus":null,"severity":"error","filePath":"crates/bevy_macro_utils/src/shape.rs","lineNumber":17,"sourceCode":"use syn::{\n    punctuated::Punctuated, spanned::Spanned, token::Comma, Data, DataEnum, DataUnion, Error,\n    Field, Fields,\n};\n\n/// Get the fields of a data structure if that structure is a struct;\n/// otherwise, return a compile error that points to the site of the macro invocation.\n///\n/// `meta` should be the name of the macro calling this function.\npub fn get_struct_fields<'a>(data: &'a Data, meta: &str) -> Result<&'a Fields, Error> {\n    match data {\n        Data::Struct(data_struct) => Ok(&data_struct.fields),\n        Data::Enum(DataEnum { enum_token, .. }) => Err(Error::new(\n            enum_token.span(),\n            format!(\"#[{meta}] only supports structs, not enums\"),\n        )),\n        Data::Union(DataUnion { union_token, .. }) => Err(Error::new(\n            union_token.span(),\n            format!(\"#[{meta}] only supports structs, not unions\"),\n        )),\n    }\n}\n\n/// Return an error if `Fields` is not `Fields::Named`\npub fn require_named<'a>(fields: &'a Fields) -> Result<&'a Punctuated<Field, Comma>, Error> {\n    if let Fields::Named(fields) = fields {\n        Ok(&fields.named)\n    } else {\n        Err(Error::new(\n            fields.span(),\n            \"Unnamed fields are not supported here\",\n        ))\n    }\n}\n","sourceCodeStart":1,"sourceCodeEnd":35,"githubUrl":"https://github.com/bevyengine/bevy/blob/396ca727080776bd313bb892423b7d94e03b81b4/crates/bevy_macro_utils/src/shape.rs#L1-L35","documentation":"Companion of the enum case: get_struct_fields rejects unions when a struct-only Bevy derive macro is applied to a `union` item. The error span points at the `union` keyword. Bevy derives need per-field access to generate code (component storage, param fetching), which unions cannot provide safely.","triggerScenarios":"Applying #[derive(Bundle)], #[derive(SystemParam)], #[derive(QueryData)], #[derive(WorldQuery)], or the render Specializer derive to a Rust `union` item.","commonSituations":"FFI or zero-copy code where a union was declared in the same module as Bevy types and a derive was attached by copy-paste or IDE auto-complete.","solutions":["Move the derive off the union onto a plain struct wrapper.","Replace the union with a struct for ECS data; keep unions in FFI boundary code only.","Delete the stale derive if it was attached accidentally."],"exampleFix":"// before\n#[derive(Bundle)]\nunion RawInput {\n    bytes: [u8; 8],\n    fields: (u32, u32),\n}\n\n// after\n#[derive(Bundle)]\nstruct InputBundle {\n    raw: RawInput, // union kept as a plain field, no derive on it\n}","handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never attach Bevy derives to `union` items; wrap unions in a struct field instead.","Keep FFI union declarations derive-free and in a separate module from ECS types.","When copy-pasting derives, check the item kind (`struct` vs `enum` vs `union`) matches what the macro supports."],"tags":["rust","bevy","proc-macro","derive","compile-time","union"],"backgroundTag":"derive-macro-invalid-input","analyzedSha":"396ca727080776bd313bb892423b7d94e03b81b4","analyzedAt":"2026-08-20T16:12:39.808Z","contentChangedAt":"2026-08-20T16:12:39.808Z","schemaVersion":2},"datasetVersion":"2026-09-08T20:17:18.057Z"}