From 7962c6dc521c7b94cf6a0ecd74d68d24932cab89 Mon Sep 17 00:00:00 2001 From: J Henry Waugh Date: Thu, 20 Aug 2020 22:20:12 -0500 Subject: [PATCH] codegen: add rhai_mod and submodule support --- codegen/src/module.rs | 566 +++++++++++++++++- codegen/src/rhai_module.rs | 20 + codegen/tests/test_nested.rs | 36 ++ codegen/ui_tests/rhai_mod_bad_attr.rs | 29 + codegen/ui_tests/rhai_mod_bad_attr.stderr | 11 + codegen/ui_tests/rhai_mod_bad_value.rs | 29 + codegen/ui_tests/rhai_mod_bad_value.stderr | 11 + codegen/ui_tests/rhai_mod_junk_arg.rs | 29 + codegen/ui_tests/rhai_mod_junk_arg.stderr | 11 + codegen/ui_tests/rhai_mod_missing_value.rs | 29 + .../ui_tests/rhai_mod_missing_value.stderr | 11 + codegen/ui_tests/rhai_mod_path_attr.rs | 29 + codegen/ui_tests/rhai_mod_path_attr.stderr | 11 + codegen/ui_tests/rhai_mod_return_raw.rs | 29 + codegen/ui_tests/rhai_mod_return_raw.stderr | 11 + 15 files changed, 852 insertions(+), 10 deletions(-) create mode 100644 codegen/ui_tests/rhai_mod_bad_attr.rs create mode 100644 codegen/ui_tests/rhai_mod_bad_attr.stderr create mode 100644 codegen/ui_tests/rhai_mod_bad_value.rs create mode 100644 codegen/ui_tests/rhai_mod_bad_value.stderr create mode 100644 codegen/ui_tests/rhai_mod_junk_arg.rs create mode 100644 codegen/ui_tests/rhai_mod_junk_arg.stderr create mode 100644 codegen/ui_tests/rhai_mod_missing_value.rs create mode 100644 codegen/ui_tests/rhai_mod_missing_value.stderr create mode 100644 codegen/ui_tests/rhai_mod_path_attr.rs create mode 100644 codegen/ui_tests/rhai_mod_path_attr.stderr create mode 100644 codegen/ui_tests/rhai_mod_return_raw.rs create mode 100644 codegen/ui_tests/rhai_mod_return_raw.stderr diff --git a/codegen/src/module.rs b/codegen/src/module.rs index bba97c2a..c6bd6469 100644 --- a/codegen/src/module.rs +++ b/codegen/src/module.rs @@ -1,5 +1,5 @@ use quote::{quote, ToTokens}; -use syn::{parse::Parse, parse::ParseStream}; +use syn::{parse::Parse, parse::ParseStream, spanned::Spanned}; use crate::function::{ExportedFn, ExportedFnParams}; use crate::rhai_module::ExportedConst; @@ -12,6 +12,7 @@ use std::vec as new_vec; #[cfg(no_std)] use core::mem; +use std::borrow::Cow; use std::collections::HashMap; fn inner_fn_attributes(f: &mut syn::ItemFn) -> syn::Result { @@ -72,11 +73,113 @@ fn check_rename_collisions(fns: &Vec) -> Result<(), syn::Error> { Ok(()) } +fn inner_mod_attributes(f: &mut syn::ItemMod) -> syn::Result { + if let Some(rhai_mod_idx) = f.attrs.iter().position(|a| { + a.path + .get_ident() + .map(|i| i.to_string() == "rhai_mod") + .unwrap_or(false) + }) { + let rhai_mod_attr = f.attrs.remove(rhai_mod_idx); + rhai_mod_attr.parse_args() + } else if let syn::Visibility::Public(_) = f.vis { + Ok(ExportedModParams::default()) + } else { + Ok(ExportedModParams::skip()) + } +} + +#[derive(Debug, Default)] +pub(crate) struct ExportedModParams { + pub name: Option, + pub skip: bool, +} + +impl ExportedModParams { + pub fn skip() -> ExportedModParams { + let mut skip = ExportedModParams::default(); + skip.skip = true; + skip + } +} + +impl Parse for ExportedModParams { + fn parse(args: ParseStream) -> syn::Result { + if args.is_empty() { + return Ok(ExportedModParams::default()); + } + + 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 mut name = None; + let mut skip = 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")), + ("skip", None) => skip = true, + ("skip", Some(s)) => { + return Err(syn::Error::new(s.span(), "extraneous value")) + } + (attr, _) => { + return Err(syn::Error::new( + ident.span(), + format!("unknown attribute '{}'", attr), + )) + } + } + } + + Ok(ExportedModParams { name, skip, ..Default::default() }) + } +} + #[derive(Debug)] pub(crate) struct Module { mod_all: Option, fns: Vec, consts: Vec, + submodules: Vec, + params: ExportedModParams, } impl Parse for Module { @@ -84,7 +187,9 @@ impl Parse for Module { let mut mod_all: syn::ItemMod = input.parse()?; let fns: Vec<_>; let consts: Vec<_>; + let mut submodules: Vec<_> = Vec::new(); if let Some((_, ref mut content)) = mod_all.content { + // Gather and parse functions. fns = content .iter_mut() .filter_map(|item| match item { @@ -104,6 +209,7 @@ impl Parse for Module { .map(|f| if !f.params.skip { vec.push(f) }) .map(|_| vec) })?; + // Gather and parse constants definitions. consts = content .iter() .filter_map(|item| match item { @@ -122,6 +228,34 @@ impl Parse for Module { _ => None, }) .collect(); + // Gather and parse submodule definitions. + // + // They are actually removed from the module's body, because they will need + // re-generating later when generated code is added. + submodules.reserve(content.len() - fns.len() - consts.len()); + let mut i = 0; + while i < content.len() { + if let syn::Item::Mod(_) = &content[i] { + let mut itemmod = match content.remove(i) { + syn::Item::Mod(m) => m, + _ => unreachable!(), + }; + let params = match inner_mod_attributes(&mut itemmod) { + Ok(p) => p, + Err(e) => return Err(e), + }; + let module = syn::parse2::(itemmod.to_token_stream()) + .map(|mut f| { + f.params = params; + f + })?; + if !module.params.skip { + submodules.push(module); + } + } else { + i += 1; + } + } } else { consts = new_vec![]; fns = new_vec![]; @@ -130,19 +264,49 @@ impl Parse for Module { mod_all: Some(mod_all), fns, consts, + submodules, + params: ExportedModParams::default(), }) } } impl Module { - pub fn generate(self) -> proc_macro2::TokenStream { - // Check for collisions if the "name" attribute was used on inner functions. - if let Err(e) = check_rename_collisions(&self.fns) { - return e.to_compile_error(); - } + pub fn module_name(&self) -> Option<&syn::Ident> { + self.mod_all.as_ref().map(|m| &m.ident) + } - // Perform the generation of new module items. - let mod_gen = crate::rhai_module::generate_body(&self.fns, &self.consts); + pub fn exported_name(&self) -> Option> { + if let Some(ref s) = self.params.name { + Some(Cow::Borrowed(s)) + } else { + self.module_name().map(|m| Cow::Owned(m.to_string())) + } + } + + pub fn generate(self) -> proc_macro2::TokenStream { + match self.generate_inner() { + Ok(tokens) => tokens, + Err(e) => e.to_compile_error(), + } + } + + fn generate_inner(mut self) -> Result { + // Check for collisions if the "name" attribute was used on inner functions. + check_rename_collisions(&self.fns)?; + + // Generate new module items. + // + // This is done before inner module recursive generation, because that is destructive. + let mod_gen = crate::rhai_module::generate_body(&self.fns, &self.consts, &self.submodules); + + // NB: submodules must have their new items for exporting generated in depth-first order to + // avoid issues with reparsing them. + let inner_modules: Vec = self.submodules.drain(..) + .try_fold::, _, + Result, syn::Error>>( + Vec::new(), |mut acc, m| { acc.push(m.generate_inner()?); Ok(acc) })?; + + // Generate new module items for exporting functions and constant. // Rebuild the structure of the module, with the new content added. let Module { mod_all, .. } = self; @@ -150,11 +314,23 @@ impl Module { let mod_name = mod_all.ident.clone(); let (_, orig_content) = mod_all.content.take().unwrap(); - quote! { + Ok(quote! { pub mod #mod_name { #(#orig_content)* + #(#inner_modules)* #mod_gen } + }) + } + + pub fn name(&self) -> Option<&syn::Ident> { + self.mod_all.as_ref().map(|m| &m.ident) + } + + pub fn content(&self) -> Option<&Vec> { + match self.mod_all { + Some(syn::ItemMod { content: Some((_, ref vec)), .. }) => Some(vec), + _ => None, } } } @@ -254,6 +430,68 @@ mod module_tests { ); } + #[test] + fn one_constant_nested_module() { + let input_tokens: TokenStream = quote! { + pub mod one_constant { + pub mod it_is { + pub const MYSTIC_NUMBER: INT = 42; + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert!(item_mod.fns.is_empty()); + assert!(item_mod.consts.is_empty()); + assert_eq!(item_mod.submodules.len(), 1); + assert_eq!(&item_mod.submodules[0].consts[0].0, "MYSTIC_NUMBER"); + assert_eq!( + item_mod.submodules[0].consts[0].1, + syn::parse2::(quote! { 42 }).unwrap() + ); + } + + #[test] + fn one_skipped_fn_nested_module() { + let input_tokens: TokenStream = quote! { + pub mod one_fn { + pub mod skip_this { + #[rhai_fn(skip)] + pub fn get_mystic_number() -> INT { + 42 + } + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert!(item_mod.fns.is_empty()); + assert!(item_mod.consts.is_empty()); + assert_eq!(item_mod.submodules.len(), 1); + assert!(item_mod.submodules[0].fns.is_empty()); + assert!(item_mod.submodules[0].consts.is_empty()); + assert!(item_mod.submodules[0].submodules.is_empty()); + } + + #[test] + fn one_skipped_nested_module() { + let input_tokens: TokenStream = quote! { + pub mod one_fn { + #[rhai_mod(skip)] + pub mod skip_this { + pub fn get_mystic_number() -> INT { + 42 + } + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert!(item_mod.fns.is_empty()); + assert!(item_mod.consts.is_empty()); + assert!(item_mod.submodules.is_empty()); + } + #[test] fn one_constant_module() { let input_tokens: TokenStream = quote! { @@ -795,7 +1033,315 @@ mod generate_tests { pub fn increment_token_input_types() -> Box<[TypeId]> { increment_token().input_types() } - } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + + #[test] + fn one_fn_nested_module() { + let input_tokens: TokenStream = quote! { + pub mod one_fn { + pub mod it_is { + pub fn increment(x: &mut FLOAT) { + *x += 1.0 as FLOAT; + } + } + } + }; + + let expected_tokens = quote! { + pub mod one_fn { + pub mod it_is { + pub fn increment(x: &mut FLOAT) { + *x += 1.0 as FLOAT; + } + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_fn("increment", FnAccess::Public, + &[core::any::TypeId::of::()], + CallableFunction::from_plugin(increment_token())); + m + } + #[allow(non_camel_case_types)] + struct increment_token(); + impl PluginFunction for increment_token { + fn call(&self, + args: &mut [&mut Dynamic], pos: Position + ) -> Result> { + debug_assert_eq!(args.len(), 1usize, + "wrong arg count: {} != {}", args.len(), 1usize); + let arg0: &mut _ = &mut args[0usize].write_lock::().unwrap(); + Ok(Dynamic::from(increment(arg0))) + } + + fn is_method_call(&self) -> bool { true } + fn is_varadic(&self) -> bool { false } + fn clone_boxed(&self) -> Box { + Box::new(increment_token()) + } + fn input_types(&self) -> Box<[TypeId]> { + new_vec![TypeId::of::()].into_boxed_slice() + } + } + pub fn increment_token_callable() -> CallableFunction { + CallableFunction::from_plugin(increment_token()) + } + pub fn increment_token_input_types() -> Box<[TypeId]> { + increment_token().input_types() + } + } + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_sub_module("it_is", self::it_is::rhai_module_generate()); + m + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + + #[test] + fn one_constant_nested_module() { + let input_tokens: TokenStream = quote! { + pub mod one_constant { + pub mod it_is { + pub const MYSTIC_NUMBER: INT = 42; + } + } + }; + + let expected_tokens = quote! { + pub mod one_constant { + pub mod it_is { + pub const MYSTIC_NUMBER: INT = 42; + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_var("MYSTIC_NUMBER", 42); + m + } + } + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_sub_module("it_is", self::it_is::rhai_module_generate()); + m + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + + #[test] + fn dual_constant_nested_module() { + let input_tokens: TokenStream = quote! { + pub mod two_constants { + pub mod first_is { + pub const MYSTIC_NUMBER: INT = 42; + } + pub mod second_is { + pub const SPECIAL_CPU_NUMBER: INT = 68000; + } + } + }; + + let expected_tokens = quote! { + pub mod two_constants { + pub mod first_is { + pub const MYSTIC_NUMBER: INT = 42; + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_var("MYSTIC_NUMBER", 42); + m + } + } + pub mod second_is { + pub const SPECIAL_CPU_NUMBER: INT = 68000; + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_var("SPECIAL_CPU_NUMBER", 68000); + m + } + } + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_sub_module("first_is", self::first_is::rhai_module_generate()); + m.set_sub_module("second_is", self::second_is::rhai_module_generate()); + m + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert_streams_eq(item_mod.generate(), expected_tokens); + } + + #[test] + fn deep_tree_nested_module() { + let input_tokens: TokenStream = quote! { + pub mod heap_root { + pub const VALUE: INT = 100; + pub mod left { + pub const VALUE: INT = 19; + pub mod left { + pub const VALUE: INT = 17; + pub mod left { + pub const VALUE: INT = 2; + } + pub mod right { + pub const VALUE: INT = 7; + } + } + pub mod right { + pub const VALUE: INT = 3; + } + } + pub mod right { + pub const VALUE: INT = 36; + pub mod left { + pub const VALUE: INT = 25; + } + pub mod right { + pub const VALUE: INT = 1; + } + } + } + }; + + let expected_tokens = quote! { + pub mod heap_root { + pub const VALUE: INT = 100; + pub mod left { + pub const VALUE: INT = 19; + pub mod left { + pub const VALUE: INT = 17; + pub mod left { + pub const VALUE: INT = 2; + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_var("VALUE", 2); + m + } + } + pub mod right { + pub const VALUE: INT = 7; + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_var("VALUE", 7); + m + } + } + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_var("VALUE", 17); + m.set_sub_module("left", self::left::rhai_module_generate()); + m.set_sub_module("right", self::right::rhai_module_generate()); + m + } + } + pub mod right { + pub const VALUE: INT = 3; + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_var("VALUE", 3); + m + } + } + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_var("VALUE", 19); + m.set_sub_module("left", self::left::rhai_module_generate()); + m.set_sub_module("right", self::right::rhai_module_generate()); + m + } + } + pub mod right { + pub const VALUE: INT = 36; + pub mod left { + pub const VALUE: INT = 25; + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_var("VALUE", 25); + m + } + } + pub mod right { + pub const VALUE: INT = 1; + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_var("VALUE", 1); + m + } + } + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_var("VALUE", 36); + m.set_sub_module("left", self::left::rhai_module_generate()); + m.set_sub_module("right", self::right::rhai_module_generate()); + m + } + } + #[allow(unused_imports)] + use super::*; + #[allow(unused_mut)] + pub fn rhai_module_generate() -> Module { + let mut m = Module::new(); + m.set_var("VALUE", 100); + m.set_sub_module("left", self::left::rhai_module_generate()); + m.set_sub_module("right", self::right::rhai_module_generate()); + m + } + } }; let item_mod = syn::parse2::(input_tokens).unwrap(); diff --git a/codegen/src/rhai_module.rs b/codegen/src/rhai_module.rs index dea8a421..f4f7369e 100644 --- a/codegen/src/rhai_module.rs +++ b/codegen/src/rhai_module.rs @@ -1,15 +1,18 @@ use quote::quote; use crate::function::ExportedFn; +use crate::module::Module; pub(crate) type ExportedConst = (String, syn::Expr); pub(crate) fn generate_body( fns: &Vec, consts: &Vec, + submodules: &Vec, ) -> proc_macro2::TokenStream { let mut set_fn_stmts: Vec = Vec::new(); let mut set_const_stmts: Vec = Vec::new(); + let mut add_mod_stmts: Vec = Vec::new(); let str_type_path = syn::parse2::(quote! { str }).unwrap(); for (const_name, const_expr) in consts { @@ -22,6 +25,22 @@ pub(crate) fn generate_body( ); } + for itemmod in submodules { + let module_name: &syn::Ident = itemmod.module_name().unwrap(); + let exported_name: syn::LitStr = if let Some(name) = itemmod.exported_name() { + syn::LitStr::new(&name, proc_macro2::Span::call_site()) + } else { + syn::LitStr::new(&module_name.to_string(), proc_macro2::Span::call_site()) + }; + add_mod_stmts.push( + syn::parse2::(quote! { + m.set_sub_module(#exported_name, self::#module_name::rhai_module_generate()); + }) + .unwrap(), + ); + } + + // NB: these are token streams, because reparsing messes up "> >" vs ">>" let mut gen_fn_tokens: Vec = Vec::new(); for function in fns { @@ -98,6 +117,7 @@ pub(crate) fn generate_body( let mut m = Module::new(); #(#set_fn_stmts)* #(#set_const_stmts)* + #(#add_mod_stmts)* m } } diff --git a/codegen/tests/test_nested.rs b/codegen/tests/test_nested.rs index 4881b927..bf0c0030 100644 --- a/codegen/tests/test_nested.rs +++ b/codegen/tests/test_nested.rs @@ -34,3 +34,39 @@ fn one_fn_module_nested_attr_test() -> Result<(), Box> { ); Ok(()) } + +pub mod one_fn_submodule_nested_attr { + use rhai::plugin::*; + + #[export_module] + pub mod advanced_math { + #[rhai_mod(name = "constants")] + pub mod my_module { + use rhai::plugin::*; + use rhai::FLOAT; + #[rhai_fn(return_raw)] + pub fn get_mystic_number() -> Result> { + Ok(Dynamic::from(42.0 as FLOAT)) + } + } + } +} + +#[test] +fn one_fn_submodule_nested_attr_test() -> Result<(), Box> { + let mut engine = Engine::new(); + let m = rhai::exported_module!(crate::one_fn_submodule_nested_attr::advanced_math); + 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 m = math::constants::get_mystic_number(); + m"# + )?, + 42.0 + ); + Ok(()) +} diff --git a/codegen/ui_tests/rhai_mod_bad_attr.rs b/codegen/ui_tests/rhai_mod_bad_attr.rs new file mode 100644 index 00000000..09319ce6 --- /dev/null +++ b/codegen/ui_tests/rhai_mod_bad_attr.rs @@ -0,0 +1,29 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_module { +#[rhai_mod(unknown = "thing")] +pub mod test_mod { +pub fn test_fn(input: Point) -> bool { + input.x > input.y +} +} +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_module::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/rhai_mod_bad_attr.stderr b/codegen/ui_tests/rhai_mod_bad_attr.stderr new file mode 100644 index 00000000..87209038 --- /dev/null +++ b/codegen/ui_tests/rhai_mod_bad_attr.stderr @@ -0,0 +1,11 @@ +error: unknown attribute 'unknown' + --> $DIR/rhai_mod_bad_attr.rs:11:12 + | +11 | #[rhai_mod(unknown = "thing")] + | ^^^^^^^ + +error[E0433]: failed to resolve: use of undeclared type or module `test_module` + --> $DIR/rhai_mod_bad_attr.rs:24:8 + | +24 | if test_module::test_fn(n) { + | ^^^^^^^^^^^ use of undeclared type or module `test_module` diff --git a/codegen/ui_tests/rhai_mod_bad_value.rs b/codegen/ui_tests/rhai_mod_bad_value.rs new file mode 100644 index 00000000..3d30fdcc --- /dev/null +++ b/codegen/ui_tests/rhai_mod_bad_value.rs @@ -0,0 +1,29 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_module { +#[rhai_mod(name = true)] +pub mod test_mod { +pub fn test_fn(input: Point) -> bool { + input.x > input.y +} +} +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_module::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/rhai_mod_bad_value.stderr b/codegen/ui_tests/rhai_mod_bad_value.stderr new file mode 100644 index 00000000..765f8a1f --- /dev/null +++ b/codegen/ui_tests/rhai_mod_bad_value.stderr @@ -0,0 +1,11 @@ +error: expecting string literal + --> $DIR/rhai_mod_bad_value.rs:11:19 + | +11 | #[rhai_mod(name = true)] + | ^^^^ + +error[E0433]: failed to resolve: use of undeclared type or module `test_module` + --> $DIR/rhai_mod_bad_value.rs:24:8 + | +24 | if test_module::test_fn(n) { + | ^^^^^^^^^^^ use of undeclared type or module `test_module` diff --git a/codegen/ui_tests/rhai_mod_junk_arg.rs b/codegen/ui_tests/rhai_mod_junk_arg.rs new file mode 100644 index 00000000..842c0bad --- /dev/null +++ b/codegen/ui_tests/rhai_mod_junk_arg.rs @@ -0,0 +1,29 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_module { +#[rhai_mod("wheeeee")] +pub mod test_mod { +pub fn test_fn(input: Point) -> bool { + input.x > input.y +} +} +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_module::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/rhai_mod_junk_arg.stderr b/codegen/ui_tests/rhai_mod_junk_arg.stderr new file mode 100644 index 00000000..c2dcc28a --- /dev/null +++ b/codegen/ui_tests/rhai_mod_junk_arg.stderr @@ -0,0 +1,11 @@ +error: expecting identifier + --> $DIR/rhai_mod_junk_arg.rs:11:12 + | +11 | #[rhai_mod("wheeeee")] + | ^^^^^^^^^ + +error[E0433]: failed to resolve: use of undeclared type or module `test_module` + --> $DIR/rhai_mod_junk_arg.rs:24:8 + | +24 | if test_module::test_fn(n) { + | ^^^^^^^^^^^ use of undeclared type or module `test_module` diff --git a/codegen/ui_tests/rhai_mod_missing_value.rs b/codegen/ui_tests/rhai_mod_missing_value.rs new file mode 100644 index 00000000..f231b65d --- /dev/null +++ b/codegen/ui_tests/rhai_mod_missing_value.rs @@ -0,0 +1,29 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_module { +#[rhai_mod(name)] +pub mod test_mod { +pub fn test_fn(input: Point) -> bool { + input.x > input.y +} +} +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_module::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/rhai_mod_missing_value.stderr b/codegen/ui_tests/rhai_mod_missing_value.stderr new file mode 100644 index 00000000..62e0502f --- /dev/null +++ b/codegen/ui_tests/rhai_mod_missing_value.stderr @@ -0,0 +1,11 @@ +error: requires value + --> $DIR/rhai_mod_missing_value.rs:11:12 + | +11 | #[rhai_mod(name)] + | ^^^^ + +error[E0433]: failed to resolve: use of undeclared type or module `test_module` + --> $DIR/rhai_mod_missing_value.rs:24:8 + | +24 | if test_module::test_fn(n) { + | ^^^^^^^^^^^ use of undeclared type or module `test_module` diff --git a/codegen/ui_tests/rhai_mod_path_attr.rs b/codegen/ui_tests/rhai_mod_path_attr.rs new file mode 100644 index 00000000..43be9604 --- /dev/null +++ b/codegen/ui_tests/rhai_mod_path_attr.rs @@ -0,0 +1,29 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_module { +#[rhai_mod(rhai::name = "thing")] +pub mod test_mod { +pub fn test_fn(input: Point) -> bool { + input.x > input.y +} +} +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_module::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/rhai_mod_path_attr.stderr b/codegen/ui_tests/rhai_mod_path_attr.stderr new file mode 100644 index 00000000..bd165324 --- /dev/null +++ b/codegen/ui_tests/rhai_mod_path_attr.stderr @@ -0,0 +1,11 @@ +error: expecting attribute name + --> $DIR/rhai_mod_path_attr.rs:11:12 + | +11 | #[rhai_mod(rhai::name = "thing")] + | ^^^^^^^^^^ + +error[E0433]: failed to resolve: use of undeclared type or module `test_module` + --> $DIR/rhai_mod_path_attr.rs:24:8 + | +24 | if test_module::test_fn(n) { + | ^^^^^^^^^^^ use of undeclared type or module `test_module` diff --git a/codegen/ui_tests/rhai_mod_return_raw.rs b/codegen/ui_tests/rhai_mod_return_raw.rs new file mode 100644 index 00000000..e5fed7e0 --- /dev/null +++ b/codegen/ui_tests/rhai_mod_return_raw.rs @@ -0,0 +1,29 @@ +use rhai::plugin::*; + +#[derive(Clone)] +struct Point { + x: f32, + y: f32, +} + +#[export_module] +pub mod test_module { +#[rhai_mod(return_raw = "yes")] +pub mod test_mod { +pub fn test_fn(input: Point) -> bool { + input.x > input.y +} +} +} + +fn main() { + let n = Point { + x: 0.0, + y: 10.0, + }; + if test_module::test_fn(n) { + println!("yes"); + } else { + println!("no"); + } +} diff --git a/codegen/ui_tests/rhai_mod_return_raw.stderr b/codegen/ui_tests/rhai_mod_return_raw.stderr new file mode 100644 index 00000000..f767dce0 --- /dev/null +++ b/codegen/ui_tests/rhai_mod_return_raw.stderr @@ -0,0 +1,11 @@ +error: unknown attribute 'return_raw' + --> $DIR/rhai_mod_return_raw.rs:11:12 + | +11 | #[rhai_mod(return_raw = "yes")] + | ^^^^^^^^^^ + +error[E0433]: failed to resolve: use of undeclared type or module `test_module` + --> $DIR/rhai_mod_return_raw.rs:24:8 + | +24 | if test_module::test_fn(n) { + | ^^^^^^^^^^^ use of undeclared type or module `test_module`