Classifies how a generic type parameter appears in a type. Strips `Option<...>`, `Result<..., E>`, and `ExcludeNull<...>` wrappers before inspecting the inner type. Any generic type wrapping `T` that isn't `Option`, `Result`, or `ExcludeNull` is treated as a container. If the container doesn't implement `SqlContainerType`, the generated code won't compile (a clear error).
(ty: &syn::Type, generic_name: &Ident)
| 384 | /// `Result`, or `ExcludeNull` is treated as a container. If the container doesn't |
| 385 | /// implement `SqlContainerType`, the generated code won't compile (a clear error). |
| 386 | fn classify_generic_usage(ty: &syn::Type, generic_name: &Ident) -> GenericUsage { |
| 387 | match ty { |
| 388 | syn::Type::Path(type_path) => { |
| 389 | if type_path.path.is_ident(generic_name) { |
| 390 | return GenericUsage::Bare; |
| 391 | } |
| 392 | if let Some(last) = type_path.path.segments.last() { |
| 393 | let ident_str = last.ident.to_string(); |
| 394 | // Unwrap Option, Result, ExcludeNull wrappers |
| 395 | if ident_str == "Option" || ident_str == "Result" || ident_str == "ExcludeNull" { |
| 396 | if let syn::PathArguments::AngleBracketed(args) = &last.arguments { |
| 397 | if let Some(syn::GenericArgument::Type(inner)) = args.args.first() { |
| 398 | return classify_generic_usage(inner, generic_name); |
| 399 | } |
| 400 | } |
| 401 | } |
| 402 | // Check if any angle-bracketed arg contains the generic param. |
| 403 | // If so, treat this type as a container. |
| 404 | if let syn::PathArguments::AngleBracketed(args) = &last.arguments { |
| 405 | let has_generic_arg = args.args.iter().any(|arg| { |
| 406 | if let syn::GenericArgument::Type(inner) = arg { |
| 407 | type_contains_ident(inner, generic_name) |
| 408 | } else { |
| 409 | false |
| 410 | } |
| 411 | }); |
| 412 | if has_generic_arg { |
| 413 | // Build the erased container type path (T → Datum<'a>). |
| 414 | let erased = erase_generic_param(ty, generic_name); |
| 415 | if let syn::Type::Path(erased_path) = erased { |
| 416 | return GenericUsage::InContainer(erased_path); |
| 417 | } |
| 418 | } |
| 419 | // Recurse into args for nested containers |
| 420 | // (e.g., Option<DatumList<'a, T>> was already handled by |
| 421 | // the Option unwrapping above, but handle other nestings) |
| 422 | for arg in &args.args { |
| 423 | if let syn::GenericArgument::Type(inner) = arg { |
| 424 | let inner_usage = classify_generic_usage(inner, generic_name); |
| 425 | if inner_usage != GenericUsage::Absent { |
| 426 | return inner_usage; |
| 427 | } |
| 428 | } |
| 429 | } |
| 430 | } |
| 431 | } |
| 432 | GenericUsage::Absent |
| 433 | } |
| 434 | syn::Type::Reference(r) => classify_generic_usage(&r.elem, generic_name), |
| 435 | syn::Type::Tuple(t) => { |
| 436 | // Prefer container usages over bare. For example, `(T, DatumList<'_, T>)` |
| 437 | // should classify as `InDatumList`, not `Bare`. |
| 438 | let mut best = GenericUsage::Absent; |
| 439 | for elem in &t.elems { |
| 440 | let usage = classify_generic_usage(elem, generic_name); |
| 441 | match (&best, &usage) { |
| 442 | (GenericUsage::Absent, _) => best = usage, |
| 443 | (GenericUsage::Bare, u) if *u != GenericUsage::Absent => best = usage.clone(), |
no test coverage detected