Validates that when a base class is specified, the struct has the base type as its first field. This ensures proper memory layout for subclassing (required for #[repr(transparent)] to work correctly).
(item: &Item, base_path: &syn::Path)
| 321 | /// Validates that when a base class is specified, the struct has the base type as its first field. |
| 322 | /// This ensures proper memory layout for subclassing (required for #[repr(transparent)] to work correctly). |
| 323 | fn validate_base_field(item: &Item, base_path: &syn::Path) -> Result<()> { |
| 324 | let Item::Struct(item_struct) = item else { |
| 325 | // Only validate structs - enums with base are already an error elsewhere |
| 326 | return Ok(()); |
| 327 | }; |
| 328 | |
| 329 | // Get the base type name for error messages |
| 330 | let base_name = base_path |
| 331 | .segments |
| 332 | .last() |
| 333 | .map(|s| s.ident.to_string()) |
| 334 | .unwrap_or_else(|| quote!(#base_path).to_string()); |
| 335 | |
| 336 | match &item_struct.fields { |
| 337 | syn::Fields::Named(fields) => { |
| 338 | let Some(first_field) = fields.named.first() else { |
| 339 | bail_span!( |
| 340 | item_struct, |
| 341 | "#[pyclass] with base = {base_name} requires the first field to be of type {base_name}, but the struct has no fields" |
| 342 | ); |
| 343 | }; |
| 344 | if !type_matches_path(&first_field.ty, base_path) { |
| 345 | bail_span!( |
| 346 | first_field, |
| 347 | "#[pyclass] with base = {base_name} requires the first field to be of type {base_name}" |
| 348 | ); |
| 349 | } |
| 350 | } |
| 351 | syn::Fields::Unnamed(fields) => { |
| 352 | let Some(first_field) = fields.unnamed.first() else { |
| 353 | bail_span!( |
| 354 | item_struct, |
| 355 | "#[pyclass] with base = {base_name} requires the first field to be of type {base_name}, but the struct has no fields" |
| 356 | ); |
| 357 | }; |
| 358 | if !type_matches_path(&first_field.ty, base_path) { |
| 359 | bail_span!( |
| 360 | first_field, |
| 361 | "#[pyclass] with base = {base_name} requires the first field to be of type {base_name}" |
| 362 | ); |
| 363 | } |
| 364 | } |
| 365 | syn::Fields::Unit => { |
| 366 | bail_span!( |
| 367 | item_struct, |
| 368 | "#[pyclass] with base = {base_name} requires the first field to be of type {base_name}, but the struct is a unit struct" |
| 369 | ); |
| 370 | } |
| 371 | } |
| 372 | |
| 373 | Ok(()) |
| 374 | } |
| 375 | |
| 376 | /// Check if a type matches a given path (handles simple cases like `Foo` or `path::to::Foo`) |
| 377 | fn type_matches_path(ty: &syn::Type, path: &syn::Path) -> bool { |
no test coverage detected