From 14b0757a45ae96115c768edff51642ad7a3cc8bb Mon Sep 17 00:00:00 2001 From: Ferran Date: Thu, 3 Sep 2026 19:10:29 +0200 Subject: [PATCH] Improve error message when custom arg decode fails --- crates/pine-builtin-macro/src/lib.rs | 6 ++++-- crates/pine-interpreter/src/lib.rs | 21 ++++++++++++++------- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/crates/pine-builtin-macro/src/lib.rs b/crates/pine-builtin-macro/src/lib.rs index 95c2eb6..0d368ac 100644 --- a/crates/pine-builtin-macro/src/lib.rs +++ b/crates/pine-builtin-macro/src/lib.rs @@ -779,6 +779,8 @@ fn generate_value_conversion( has_default: bool, ) -> proc_macro2::TokenStream { let type_str = quote! { #field_type }.to_string(); + let field_name_str = field_name.to_string(); + let field_name_str = field_name_str.trim_start_matches("r#"); // Check if this is an Option type let is_option = type_str.contains("Option"); @@ -841,7 +843,7 @@ fn generate_value_conversion( if matches!(arg_value, Value::Na) { None } else { - Some(<#inner as ::pine_interpreter::FromArg>::from_arg(&arg_value)?) + Some(<#inner as ::pine_interpreter::FromArg>::from_arg(#field_name_str, &arg_value)?) } } } @@ -866,7 +868,7 @@ fn generate_value_conversion( } else { // Any other type is converted from the argument — e.g. a string-constant // enum deserialized from a `Value::String`. - quote! { <#field_type as ::pine_interpreter::FromArg>::from_arg(&arg_value)? } + quote! { <#field_type as ::pine_interpreter::FromArg>::from_arg(#field_name_str, &arg_value)? } }; if has_default { diff --git a/crates/pine-interpreter/src/lib.rs b/crates/pine-interpreter/src/lib.rs index 11c1f30..b7dfb5c 100644 --- a/crates/pine-interpreter/src/lib.rs +++ b/crates/pine-interpreter/src/lib.rs @@ -14,18 +14,25 @@ use thiserror::Error; pub use pine_core::LibraryLoader; -/// Convert a call argument into a builtin parameter type. Blanket-implemented for -/// any `serde`-deserializable type by deserializing the argument's string, so a -/// string-constant enum (`LabelStyle`, …) can be a builtin parameter directly. +/// Convert a builtin call argument into a custom parameter type — one that isn't +/// a macro primitive (`String`, `f64`, `bool`, `Color`, `Value`, `Num`). +/// +/// The rule: the type implements [`serde::Deserialize`] and is deserialized from +/// the argument's `Value::String`, so a fieldless enum with +/// `#[serde(rename_all = "snake_case")]` accepts the lowercase constants scripts +/// write (`"close"`, `"signed"`, …). On mismatch the error names the argument and +/// — for an enum — lists the accepted variants. pub trait FromArg: Sized { - fn from_arg(value: &Value) -> Result; + fn from_arg(arg: &str, value: &Value) -> Result; } impl FromArg for T { - fn from_arg(value: &Value) -> Result { - let s = value.as_string()?; + fn from_arg(arg: &str, value: &Value) -> Result { + let s = value + .as_string() + .map_err(|_| RuntimeError::TypeError(format!("`{arg}` expects a string constant")))?; let de = serde::de::value::StrDeserializer::::new(s.as_str()); - T::deserialize(de).map_err(|e| RuntimeError::TypeError(format!("invalid value `{s}`: {e}"))) + T::deserialize(de).map_err(|e| RuntimeError::TypeError(format!("`{arg}`: {e}"))) } }