From 07a454194942b75bd7170df2e0b19609e54306ad Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Sun, 9 Aug 2020 14:19:39 -0500 Subject: [PATCH] export_fn: add return_raw attribute --- codegen/src/function.rs | 94 +++++++++++++++---- codegen/tests/test_functions.rs | 41 ++++++++ codegen/ui_tests/export_fn_bad_attr.rs | 2 +- codegen/ui_tests/export_fn_bad_attr.stderr | 2 +- codegen/ui_tests/export_fn_bad_value.stderr | 2 +- codegen/ui_tests/export_fn_extra_value.rs | 24 +++++ codegen/ui_tests/export_fn_extra_value.stderr | 11 +++ codegen/ui_tests/export_fn_junk_arg.stderr | 2 +- codegen/ui_tests/export_fn_missing_value.rs | 24 +++++ .../ui_tests/export_fn_missing_value.stderr | 11 +++ codegen/ui_tests/export_fn_path_attr.rs | 24 +++++ codegen/ui_tests/export_fn_path_attr.stderr | 11 +++ 12 files changed, 227 insertions(+), 21 deletions(-) create mode 100644 codegen/ui_tests/export_fn_extra_value.rs create mode 100644 codegen/ui_tests/export_fn_extra_value.stderr create mode 100644 codegen/ui_tests/export_fn_missing_value.rs create mode 100644 codegen/ui_tests/export_fn_missing_value.stderr create mode 100644 codegen/ui_tests/export_fn_path_attr.rs create mode 100644 codegen/ui_tests/export_fn_path_attr.stderr diff --git a/codegen/src/function.rs b/codegen/src/function.rs index f2e0ab11..7b28eeeb 100644 --- a/codegen/src/function.rs +++ b/codegen/src/function.rs @@ -1,10 +1,14 @@ #![allow(unused)] + +use std::collections::HashMap; + use quote::{quote, quote_spanned}; -use syn::{parse::Parse, parse::ParseStream, spanned::Spanned}; +use syn::{parse::Parse, parse::ParseStream, parse::Parser, spanned::Spanned}; #[derive(Debug, Default)] pub(crate) struct ExportedFnParams { pub name: Option, + pub return_raw: bool, } impl Parse for ExportedFnParams { @@ -12,25 +16,68 @@ impl Parse for ExportedFnParams { if args.is_empty() { return Ok(ExportedFnParams::default()); } - let assignment: syn::ExprAssign = args.parse()?; - let attr_name: syn::Ident = match assignment.left.as_ref() { - syn::Expr::Path(syn::ExprPath { path: attr_path, .. }) => attr_path.get_ident().cloned() - .ok_or_else(|| syn::Error::new(attr_path.span(), "expecting attribute name"))?, - x => return Err(syn::Error::new(x.span(), "expecting attribute name")), - }; - if &attr_name != "name" { - return Err(syn::Error::new(attr_name.span(), format!("unknown attribute '{}'", &attr_name))); + let arg_list = args.call( + syn::punctuated::Punctuated::::parse_separated_nonempty, + )?; + + let mut attrs: HashMap> = HashMap::new(); + for arg in arg_list { + let (left, right) = match arg { + syn::Expr::Assign(syn::ExprAssign { + ref left, + ref right, + .. + }) => { + let attr_name: syn::Ident = match left.as_ref() { + syn::Expr::Path(syn::ExprPath { + path: attr_path, .. + }) => attr_path.get_ident().cloned().ok_or_else(|| { + syn::Error::new(attr_path.span(), "expecting attribute name") + })?, + x => return Err(syn::Error::new(x.span(), "expecting attribute name")), + }; + let attr_value = match right.as_ref() { + syn::Expr::Lit(syn::ExprLit { + lit: syn::Lit::Str(string), + .. + }) => string.clone(), + x => return Err(syn::Error::new(x.span(), "expecting string literal")), + }; + (attr_name, Some(attr_value)) + } + syn::Expr::Path(syn::ExprPath { + path: attr_path, .. + }) => attr_path + .get_ident() + .cloned() + .map(|a| (a, None)) + .ok_or_else(|| syn::Error::new(attr_path.span(), "expecting attribute name"))?, + x => return Err(syn::Error::new(x.span(), "expecting identifier")), + }; + attrs.insert(left, right); } - let attr_value: String = match assignment.right.as_ref() { - syn::Expr::Lit(syn::ExprLit { lit: syn::Lit::Str(string), .. }) => string.value(), - x => return Err(syn::Error::new(x.span(), "expecting string literal value")), - }; + let mut name = None; + let mut return_raw = false; + for (ident, value) in attrs.drain() { + match (ident.to_string().as_ref(), value) { + ("name", Some(s)) => name = Some(s.value()), + ("name", None) => return Err(syn::Error::new(ident.span(), "requires value")), + ("return_raw", None) => return_raw = true, + ("return_raw", Some(s)) => { + return Err(syn::Error::new(s.span(), "extraneous value")) + } + (attr, _) => { + return Err(syn::Error::new( + ident.span(), + format!("unknown attribute '{}'", attr), + )) + } + } + } - Ok(ExportedFnParams { - name: Some(attr_value), - }) + Ok(ExportedFnParams { name, return_raw }) } } @@ -361,6 +408,19 @@ impl ExportedFn { unpack_stmts.push(arg0); } + // Handle "raw returns", aka cases where the result is a dynamic or an error. + // + // This allows skipping the Dynamic::from wrap. + let return_expr = if !self.params.return_raw { + quote! { + Ok(Dynamic::from(#name(#(#unpack_exprs),*))) + } + } else { + quote! { + #name(#(#unpack_exprs),*) + } + }; + let type_name = syn::Ident::new(on_type_name, proc_macro2::Span::call_site()); quote! { impl PluginFunction for #type_name { @@ -373,7 +433,7 @@ impl ExportedFn { args.len(), #arg_count), Position::none()))); } #(#unpack_stmts)* - Ok(Dynamic::from(#name(#(#unpack_exprs),*))) + #return_expr } fn is_method_call(&self) -> bool { #is_method_call } diff --git a/codegen/tests/test_functions.rs b/codegen/tests/test_functions.rs index 85fcede7..876fccf5 100644 --- a/codegen/tests/test_functions.rs +++ b/codegen/tests/test_functions.rs @@ -230,3 +230,44 @@ fn duplicate_fn_rename_test() -> Result<(), Box> { assert_eq!(&output_array[1].as_int().unwrap(), &43); Ok(()) } + +pub mod raw_returning_fn { + use rhai::plugin::*; + use rhai::FLOAT; + + #[export_fn(return_raw)] + pub fn distance_function( + x1: FLOAT, + y1: FLOAT, + x2: FLOAT, + y2: FLOAT, + ) -> Result> { + Ok(Dynamic::from( + ((y2 - y1).abs().powf(2.0) + (x2 - x1).abs().powf(2.0)).sqrt(), + )) + } +} + +#[test] +fn raw_returning_fn_test() -> Result<(), Box> { + let mut engine = Engine::new(); + engine.register_fn("get_mystic_number", || 42 as FLOAT); + let mut m = Module::new(); + rhai::register_exported_fn!( + m, + "euclidean_distance".to_string(), + raw_returning_fn::distance_function + ); + let mut r = StaticModuleResolver::new(); + r.insert("Math::Advanced".to_string(), m); + engine.set_module_resolver(Some(r)); + + assert_eq!( + engine.eval::( + r#"import "Math::Advanced" as math; + let x = math::euclidean_distance(0.0, 1.0, 0.0, get_mystic_number()); x"# + )?, + 41.0 + ); + Ok(()) +} diff --git a/codegen/ui_tests/export_fn_bad_attr.rs b/codegen/ui_tests/export_fn_bad_attr.rs index 7a268f01..685b8e88 100644 --- a/codegen/ui_tests/export_fn_bad_attr.rs +++ b/codegen/ui_tests/export_fn_bad_attr.rs @@ -6,7 +6,7 @@ struct Point { y: f32, } -#[export_fn(unknown = true)] +#[export_fn(unknown = "thing")] pub fn test_fn(input: Point) -> bool { input.x > input.y } diff --git a/codegen/ui_tests/export_fn_bad_attr.stderr b/codegen/ui_tests/export_fn_bad_attr.stderr index f08dd188..12dd2a82 100644 --- a/codegen/ui_tests/export_fn_bad_attr.stderr +++ b/codegen/ui_tests/export_fn_bad_attr.stderr @@ -1,7 +1,7 @@ error: unknown attribute 'unknown' --> $DIR/export_fn_bad_attr.rs:9:13 | -9 | #[export_fn(unknown = true)] +9 | #[export_fn(unknown = "thing")] | ^^^^^^^ error[E0425]: cannot find function `test_fn` in this scope diff --git a/codegen/ui_tests/export_fn_bad_value.stderr b/codegen/ui_tests/export_fn_bad_value.stderr index 0db1969f..4695abb0 100644 --- a/codegen/ui_tests/export_fn_bad_value.stderr +++ b/codegen/ui_tests/export_fn_bad_value.stderr @@ -1,4 +1,4 @@ -error: expecting string literal value +error: expecting string literal --> $DIR/export_fn_bad_value.rs:9:20 | 9 | #[export_fn(name = true)] diff --git a/codegen/ui_tests/export_fn_extra_value.rs b/codegen/ui_tests/export_fn_extra_value.rs new file mode 100644 index 00000000..d8cb9623 --- /dev/null +++ b/codegen/ui_tests/export_fn_extra_value.rs @@ -0,0 +1,24 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Point { + x: f32, + y: f32, +} + +#[export_fn(return_raw = "yes")] +pub fn test_fn(input: Point) -> bool { + input.x > input.y +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/export_fn_extra_value.stderr b/codegen/ui_tests/export_fn_extra_value.stderr new file mode 100644 index 00000000..6ebc2763 --- /dev/null +++ b/codegen/ui_tests/export_fn_extra_value.stderr @@ -0,0 +1,11 @@ +error: extraneous value + --> $DIR/export_fn_extra_value.rs:9:26 + | +9 | #[export_fn(return_raw = "yes")] + | ^^^^^ + +error[E0425]: cannot find function `test_fn` in this scope + --> $DIR/export_fn_extra_value.rs:19:8 + | +19 | if test_fn(n) { + | ^^^^^^^ not found in this scope diff --git a/codegen/ui_tests/export_fn_junk_arg.stderr b/codegen/ui_tests/export_fn_junk_arg.stderr index d6003354..04cf996b 100644 --- a/codegen/ui_tests/export_fn_junk_arg.stderr +++ b/codegen/ui_tests/export_fn_junk_arg.stderr @@ -1,4 +1,4 @@ -error: expected assignment expression +error: expecting identifier --> $DIR/export_fn_junk_arg.rs:9:13 | 9 | #[export_fn("wheeeee")] diff --git a/codegen/ui_tests/export_fn_missing_value.rs b/codegen/ui_tests/export_fn_missing_value.rs new file mode 100644 index 00000000..7497a518 --- /dev/null +++ b/codegen/ui_tests/export_fn_missing_value.rs @@ -0,0 +1,24 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Point { + x: f32, + y: f32, +} + +#[export_fn(name)] +pub fn test_fn(input: Point) -> bool { + input.x > input.y +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/export_fn_missing_value.stderr b/codegen/ui_tests/export_fn_missing_value.stderr new file mode 100644 index 00000000..978798e5 --- /dev/null +++ b/codegen/ui_tests/export_fn_missing_value.stderr @@ -0,0 +1,11 @@ +error: requires value + --> $DIR/export_fn_missing_value.rs:9:13 + | +9 | #[export_fn(name)] + | ^^^^ + +error[E0425]: cannot find function `test_fn` in this scope + --> $DIR/export_fn_missing_value.rs:19:8 + | +19 | if test_fn(n) { + | ^^^^^^^ not found in this scope diff --git a/codegen/ui_tests/export_fn_path_attr.rs b/codegen/ui_tests/export_fn_path_attr.rs new file mode 100644 index 00000000..a9fed9e9 --- /dev/null +++ b/codegen/ui_tests/export_fn_path_attr.rs @@ -0,0 +1,24 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Point { + x: f32, + y: f32, +} + +#[export_fn(rhai::name = "thing")] +pub fn test_fn(input: Point) -> bool { + input.x > input.y +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/export_fn_path_attr.stderr b/codegen/ui_tests/export_fn_path_attr.stderr new file mode 100644 index 00000000..baf499cb --- /dev/null +++ b/codegen/ui_tests/export_fn_path_attr.stderr @@ -0,0 +1,11 @@ +error: expecting attribute name + --> $DIR/export_fn_path_attr.rs:9:13 + | +9 | #[export_fn(rhai::name = "thing")] + | ^^^^^^^^^^ + +error[E0425]: cannot find function `test_fn` in this scope + --> $DIR/export_fn_path_attr.rs:19:8 + | +19 | if test_fn(n) { + | ^^^^^^^ not found in this scope