Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion crates/pine-builtin-macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -757,6 +757,22 @@ fn is_field_lazy(field: &Field) -> bool {
false
}

/// The `T` of an `Option<T>` field type, as tokens (`_` if it can't be read).
fn option_inner_type(ty: &syn::Type) -> proc_macro2::TokenStream {
if let syn::Type::Path(path) = ty {
if let Some(segment) = path.path.segments.last() {
if segment.ident == "Option" {
if let syn::PathArguments::AngleBracketed(args) = &segment.arguments {
if let Some(syn::GenericArgument::Type(inner)) = args.args.first() {
return quote! { #inner };
}
}
}
}
}
quote! { _ }
}

fn generate_value_conversion(
field_name: &syn::Ident,
field_type: &syn::Type,
Expand Down Expand Up @@ -810,14 +826,24 @@ fn generate_value_conversion(
Some(arg_value.as_bool()?)
}
}
} else {
} else if type_str.contains("Value") {
quote! {
if matches!(arg_value, Value::Na) {
None
} else {
Some(arg_value)
}
}
} else {
// `Option<enum>` and other custom inners: convert from the argument.
let inner = option_inner_type(field_type);
quote! {
if matches!(arg_value, Value::Na) {
None
} else {
Some(<#inner as ::pine_interpreter::FromArg<O>>::from_arg(&arg_value)?)
}
}
}
} else if type_str.contains("Num") {
// A `Num` field keeps int-vs-float, so the builtin can apply Pine's
Expand Down
64 changes: 64 additions & 0 deletions crates/pine-builtins/src/label/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -637,3 +637,67 @@ pub fn register<O: PineOutput + LabelOutput>() -> Value<O> {
value: None,
}
}

#[cfg(test)]
mod tests {
use super::*;
use pine_core::DefaultPineOutput;
use pine_interpreter::{EvaluatedArg, FunctionCallArgs};

#[derive(BuiltinFunction)]
#[builtin(name = "test.opt_style")]
struct OptStyle {
#[arg(default = None)]
style: Option<LabelStyle>,
}

impl OptStyle {
fn execute<O: PineOutput>(
&self,
_ctx: &mut Interpreter<O>,
) -> Result<Value<O>, RuntimeError> {
Ok(match self.style {
Some(LabelStyle::StyleCircle) => Value::String("circle".to_string()),
Some(_) => Value::String("other".to_string()),
None => Value::Na,
})
}
}

fn call(
args: Vec<EvaluatedArg<DefaultPineOutput>>,
) -> Result<Value<DefaultPineOutput>, RuntimeError> {
let mut interp = Interpreter::<DefaultPineOutput>::new();
OptStyle::builtin_fn(
&mut interp,
FunctionCallArgs {
type_args: vec![],
args,
call_id: 0,
},
)
}

#[test]
fn test_optional_enum_in_builtin() {
// present_enum_argument_is_converted
let value = call(vec![EvaluatedArg::Positional(Value::String(
"style_circle".to_string(),
))])
.unwrap();
assert!(matches!(value, Value::String(s) if s == "circle"));

// absent_and_na_map_to_none
assert!(matches!(call(vec![]).unwrap(), Value::Na));
assert!(matches!(
call(vec![EvaluatedArg::Positional(Value::Na)]).unwrap(),
Value::Na
));

// unknown_variant_is_rejected
assert!(call(vec![EvaluatedArg::Positional(Value::String(
"nope".to_string()
))])
.is_err());
}
}
Loading