From c4831967be8cf1d4b350f10938e352da338c344d Mon Sep 17 00:00:00 2001 From: Franz Dietrich Date: Sat, 19 Sep 2026 20:55:00 +0200 Subject: [PATCH] Parameter Type Validation Helper: Added validate_parameter_type(&syn::Type): Verifies the type is a reference (syn::Type::Reference). Ensures the reference is mutable (type_ref.mutability.is_some()), reporting: #[turtle_main] parameter must be a mutable reference: '&mut TurtlePlan'. Checks that the target type (type_ref.elem) is a path whose trailing identifier is TurtlePlan, allowing both &mut TurtlePlan and qualified paths like &mut turtle_lib::TurtlePlan. Rejects other types (like value: i32 or owned t: TurtlePlan), reporting: Integration into validate_input: Called validate_parameter_type(&pat_type.ty)?; directly within syn::FnArg::Typed(pat_type). Unit Tests Added: test_valid_qualified_type: verifies &mut turtle_lib::TurtlePlan is accepted. test_rejects_wrong_type: verifies value: i32 is rejected with an informative error. test_rejects_immutable_reference: verifies t: &TurtlePlan is rejected. test_rejects_owned_type: verifies t: TurtlePlan is rejected. test_rejects_wrong_reference_type: verifies t: &mut i32 is rejected. --- turtle-lib-macros/src/lib.rs | 80 ++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/turtle-lib-macros/src/lib.rs b/turtle-lib-macros/src/lib.rs index 45d32be..b9faed1 100644 --- a/turtle-lib-macros/src/lib.rs +++ b/turtle-lib-macros/src/lib.rs @@ -102,6 +102,40 @@ use syn::ItemFn; /// }); /// } /// ``` +fn validate_parameter_type(ty: &syn::Type) -> Result<(), syn::Error> { + match ty { + syn::Type::Reference(type_ref) => { + if type_ref.mutability.is_none() { + return Err(syn::Error::new_spanned( + type_ref, + "#[turtle_main] parameter must be a mutable reference: `&mut TurtlePlan`", + )); + } + + if let syn::Type::Path(type_path) = &*type_ref.elem { + let is_turtle_plan = type_path + .path + .segments + .last() + .is_some_and(|seg| seg.ident == "TurtlePlan"); + + if is_turtle_plan { + return Ok(()); + } + } + + Err(syn::Error::new_spanned( + &type_ref.elem, + "#[turtle_main] expected reference to `TurtlePlan`, e.g. `&mut TurtlePlan`", + )) + } + _ => Err(syn::Error::new_spanned( + ty, + "#[turtle_main] parameter must be of type `&mut TurtlePlan`", + )), + } +} + fn validate_input(input_fn: &ItemFn) -> Result<(), syn::Error> { if input_fn.sig.asyncness.is_some() { return Err(syn::Error::new_spanned( @@ -137,6 +171,8 @@ fn validate_input(input_fn: &ItemFn) -> Result<(), syn::Error> { )); } } + + validate_parameter_type(&pat_type.ty)?; } } } @@ -356,6 +392,50 @@ mod tests { assert!(err.to_string().contains("cannot have a return type")); } + #[test] + fn test_valid_qualified_type() { + let input: ItemFn = parse_quote! { + fn my_draw(t: &mut turtle_lib::TurtlePlan) {} + }; + assert!(validate_input(&input).is_ok()); + } + + #[test] + fn test_rejects_wrong_type() { + let input: ItemFn = parse_quote! { + fn my_draw(value: i32) {} + }; + let err = validate_input(&input).unwrap_err(); + assert!(err.to_string().contains("parameter must be of type `&mut TurtlePlan`")); + } + + #[test] + fn test_rejects_immutable_reference() { + let input: ItemFn = parse_quote! { + fn my_draw(t: &TurtlePlan) {} + }; + let err = validate_input(&input).unwrap_err(); + assert!(err.to_string().contains("parameter must be a mutable reference: `&mut TurtlePlan`")); + } + + #[test] + fn test_rejects_owned_type() { + let input: ItemFn = parse_quote! { + fn my_draw(t: TurtlePlan) {} + }; + let err = validate_input(&input).unwrap_err(); + assert!(err.to_string().contains("parameter must be of type `&mut TurtlePlan`")); + } + + #[test] + fn test_rejects_wrong_reference_type() { + let input: ItemFn = parse_quote! { + fn my_draw(t: &mut i32) {} + }; + let err = validate_input(&input).unwrap_err(); + assert!(err.to_string().contains("expected reference to `TurtlePlan`")); + } + #[test] fn test_expansion_preserves_custom_param_name() { let input = quote! {