Merge branch 'plugins' into plugins_dev
This commit is contained in:
commit
40f71320f3
@ -1,5 +1,5 @@
|
|||||||
use quote::{quote, ToTokens};
|
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::function::{ExportedFn, ExportedFnParams};
|
||||||
use crate::rhai_module::ExportedConst;
|
use crate::rhai_module::ExportedConst;
|
||||||
@ -12,6 +12,7 @@ use std::vec as new_vec;
|
|||||||
#[cfg(no_std)]
|
#[cfg(no_std)]
|
||||||
use core::mem;
|
use core::mem;
|
||||||
|
|
||||||
|
use std::borrow::Cow;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
fn inner_fn_attributes(f: &mut syn::ItemFn) -> syn::Result<ExportedFnParams> {
|
fn inner_fn_attributes(f: &mut syn::ItemFn) -> syn::Result<ExportedFnParams> {
|
||||||
@ -72,11 +73,113 @@ fn check_rename_collisions(fns: &Vec<ExportedFn>) -> Result<(), syn::Error> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn inner_mod_attributes(f: &mut syn::ItemMod) -> syn::Result<ExportedModParams> {
|
||||||
|
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<String>,
|
||||||
|
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<Self> {
|
||||||
|
if args.is_empty() {
|
||||||
|
return Ok(ExportedModParams::default());
|
||||||
|
}
|
||||||
|
|
||||||
|
let arg_list = args.call(
|
||||||
|
syn::punctuated::Punctuated::<syn::Expr, syn::Token![,]>::parse_separated_nonempty,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let mut attrs: HashMap<syn::Ident, Option<syn::LitStr>> = 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)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct Module {
|
pub(crate) struct Module {
|
||||||
mod_all: Option<syn::ItemMod>,
|
mod_all: Option<syn::ItemMod>,
|
||||||
fns: Vec<ExportedFn>,
|
fns: Vec<ExportedFn>,
|
||||||
consts: Vec<ExportedConst>,
|
consts: Vec<ExportedConst>,
|
||||||
|
submodules: Vec<Module>,
|
||||||
|
params: ExportedModParams,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Parse for Module {
|
impl Parse for Module {
|
||||||
@ -84,7 +187,9 @@ impl Parse for Module {
|
|||||||
let mut mod_all: syn::ItemMod = input.parse()?;
|
let mut mod_all: syn::ItemMod = input.parse()?;
|
||||||
let fns: Vec<_>;
|
let fns: Vec<_>;
|
||||||
let consts: Vec<_>;
|
let consts: Vec<_>;
|
||||||
|
let mut submodules: Vec<_> = Vec::new();
|
||||||
if let Some((_, ref mut content)) = mod_all.content {
|
if let Some((_, ref mut content)) = mod_all.content {
|
||||||
|
// Gather and parse functions.
|
||||||
fns = content
|
fns = content
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
.filter_map(|item| match item {
|
.filter_map(|item| match item {
|
||||||
@ -104,6 +209,7 @@ impl Parse for Module {
|
|||||||
.map(|f| if !f.params.skip { vec.push(f) })
|
.map(|f| if !f.params.skip { vec.push(f) })
|
||||||
.map(|_| vec)
|
.map(|_| vec)
|
||||||
})?;
|
})?;
|
||||||
|
// Gather and parse constants definitions.
|
||||||
consts = content
|
consts = content
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|item| match item {
|
.filter_map(|item| match item {
|
||||||
@ -122,6 +228,34 @@ impl Parse for Module {
|
|||||||
_ => None,
|
_ => None,
|
||||||
})
|
})
|
||||||
.collect();
|
.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::<Module>(itemmod.to_token_stream())
|
||||||
|
.map(|mut f| {
|
||||||
|
f.params = params;
|
||||||
|
f
|
||||||
|
})?;
|
||||||
|
if !module.params.skip {
|
||||||
|
submodules.push(module);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
consts = new_vec![];
|
consts = new_vec![];
|
||||||
fns = new_vec![];
|
fns = new_vec![];
|
||||||
@ -130,19 +264,49 @@ impl Parse for Module {
|
|||||||
mod_all: Some(mod_all),
|
mod_all: Some(mod_all),
|
||||||
fns,
|
fns,
|
||||||
consts,
|
consts,
|
||||||
|
submodules,
|
||||||
|
params: ExportedModParams::default(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Module {
|
impl Module {
|
||||||
pub fn generate(self) -> proc_macro2::TokenStream {
|
pub fn module_name(&self) -> Option<&syn::Ident> {
|
||||||
// Check for collisions if the "name" attribute was used on inner functions.
|
self.mod_all.as_ref().map(|m| &m.ident)
|
||||||
if let Err(e) = check_rename_collisions(&self.fns) {
|
|
||||||
return e.to_compile_error();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Perform the generation of new module items.
|
pub fn exported_name(&self) -> Option<Cow<str>> {
|
||||||
let mod_gen = crate::rhai_module::generate_body(&self.fns, &self.consts);
|
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<proc_macro2::TokenStream, syn::Error> {
|
||||||
|
// 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<proc_macro2::TokenStream> = self.submodules.drain(..)
|
||||||
|
.try_fold::<Vec<proc_macro2::TokenStream>, _,
|
||||||
|
Result<Vec<proc_macro2::TokenStream>, 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.
|
// Rebuild the structure of the module, with the new content added.
|
||||||
let Module { mod_all, .. } = self;
|
let Module { mod_all, .. } = self;
|
||||||
@ -150,11 +314,23 @@ impl Module {
|
|||||||
let mod_name = mod_all.ident.clone();
|
let mod_name = mod_all.ident.clone();
|
||||||
let (_, orig_content) = mod_all.content.take().unwrap();
|
let (_, orig_content) = mod_all.content.take().unwrap();
|
||||||
|
|
||||||
quote! {
|
Ok(quote! {
|
||||||
pub mod #mod_name {
|
pub mod #mod_name {
|
||||||
#(#orig_content)*
|
#(#orig_content)*
|
||||||
|
#(#inner_modules)*
|
||||||
#mod_gen
|
#mod_gen
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn name(&self) -> Option<&syn::Ident> {
|
||||||
|
self.mod_all.as_ref().map(|m| &m.ident)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn content(&self) -> Option<&Vec<syn::Item>> {
|
||||||
|
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::<Module>(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::<syn::Expr>(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::<Module>(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::<Module>(input_tokens).unwrap();
|
||||||
|
assert!(item_mod.fns.is_empty());
|
||||||
|
assert!(item_mod.consts.is_empty());
|
||||||
|
assert!(item_mod.submodules.is_empty());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn one_constant_module() {
|
fn one_constant_module() {
|
||||||
let input_tokens: TokenStream = quote! {
|
let input_tokens: TokenStream = quote! {
|
||||||
@ -801,4 +1039,312 @@ mod generate_tests {
|
|||||||
let item_mod = syn::parse2::<Module>(input_tokens).unwrap();
|
let item_mod = syn::parse2::<Module>(input_tokens).unwrap();
|
||||||
assert_streams_eq(item_mod.generate(), expected_tokens);
|
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::<FLOAT>()],
|
||||||
|
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<Dynamic, Box<EvalAltResult>> {
|
||||||
|
debug_assert_eq!(args.len(), 1usize,
|
||||||
|
"wrong arg count: {} != {}", args.len(), 1usize);
|
||||||
|
let arg0: &mut _ = &mut args[0usize].write_lock::<FLOAT>().unwrap();
|
||||||
|
Ok(Dynamic::from(increment(arg0)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_method_call(&self) -> bool { true }
|
||||||
|
fn is_varadic(&self) -> bool { false }
|
||||||
|
fn clone_boxed(&self) -> Box<dyn PluginFunction> {
|
||||||
|
Box::new(increment_token())
|
||||||
|
}
|
||||||
|
fn input_types(&self) -> Box<[TypeId]> {
|
||||||
|
new_vec![TypeId::of::<FLOAT>()].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::<Module>(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::<Module>(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::<Module>(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::<Module>(input_tokens).unwrap();
|
||||||
|
assert_streams_eq(item_mod.generate(), expected_tokens);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,18 @@
|
|||||||
use quote::quote;
|
use quote::quote;
|
||||||
|
|
||||||
use crate::function::ExportedFn;
|
use crate::function::ExportedFn;
|
||||||
|
use crate::module::Module;
|
||||||
|
|
||||||
pub(crate) type ExportedConst = (String, syn::Expr);
|
pub(crate) type ExportedConst = (String, syn::Expr);
|
||||||
|
|
||||||
pub(crate) fn generate_body(
|
pub(crate) fn generate_body(
|
||||||
fns: &Vec<ExportedFn>,
|
fns: &Vec<ExportedFn>,
|
||||||
consts: &Vec<ExportedConst>,
|
consts: &Vec<ExportedConst>,
|
||||||
|
submodules: &Vec<Module>,
|
||||||
) -> proc_macro2::TokenStream {
|
) -> proc_macro2::TokenStream {
|
||||||
let mut set_fn_stmts: Vec<syn::Stmt> = Vec::new();
|
let mut set_fn_stmts: Vec<syn::Stmt> = Vec::new();
|
||||||
let mut set_const_stmts: Vec<syn::Stmt> = Vec::new();
|
let mut set_const_stmts: Vec<syn::Stmt> = Vec::new();
|
||||||
|
let mut add_mod_stmts: Vec<syn::Stmt> = Vec::new();
|
||||||
let str_type_path = syn::parse2::<syn::Path>(quote! { str }).unwrap();
|
let str_type_path = syn::parse2::<syn::Path>(quote! { str }).unwrap();
|
||||||
|
|
||||||
for (const_name, const_expr) in consts {
|
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::<syn::Stmt>(quote! {
|
||||||
|
m.set_sub_module(#exported_name, self::#module_name::rhai_module_generate());
|
||||||
|
})
|
||||||
|
.unwrap(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// NB: these are token streams, because reparsing messes up "> >" vs ">>"
|
// NB: these are token streams, because reparsing messes up "> >" vs ">>"
|
||||||
let mut gen_fn_tokens: Vec<proc_macro2::TokenStream> = Vec::new();
|
let mut gen_fn_tokens: Vec<proc_macro2::TokenStream> = Vec::new();
|
||||||
for function in fns {
|
for function in fns {
|
||||||
@ -98,6 +117,7 @@ pub(crate) fn generate_body(
|
|||||||
let mut m = Module::new();
|
let mut m = Module::new();
|
||||||
#(#set_fn_stmts)*
|
#(#set_fn_stmts)*
|
||||||
#(#set_const_stmts)*
|
#(#set_const_stmts)*
|
||||||
|
#(#add_mod_stmts)*
|
||||||
m
|
m
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,3 +34,39 @@ fn one_fn_module_nested_attr_test() -> Result<(), Box<EvalAltResult>> {
|
|||||||
);
|
);
|
||||||
Ok(())
|
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<Dynamic, Box<EvalAltResult>> {
|
||||||
|
Ok(Dynamic::from(42.0 as FLOAT))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn one_fn_submodule_nested_attr_test() -> Result<(), Box<EvalAltResult>> {
|
||||||
|
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::<FLOAT>(
|
||||||
|
r#"import "Math::Advanced" as math;
|
||||||
|
let m = math::constants::get_mystic_number();
|
||||||
|
m"#
|
||||||
|
)?,
|
||||||
|
42.0
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
29
codegen/ui_tests/rhai_mod_bad_attr.rs
Normal file
29
codegen/ui_tests/rhai_mod_bad_attr.rs
Normal file
@ -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");
|
||||||
|
}
|
||||||
|
}
|
11
codegen/ui_tests/rhai_mod_bad_attr.stderr
Normal file
11
codegen/ui_tests/rhai_mod_bad_attr.stderr
Normal file
@ -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`
|
29
codegen/ui_tests/rhai_mod_bad_value.rs
Normal file
29
codegen/ui_tests/rhai_mod_bad_value.rs
Normal file
@ -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");
|
||||||
|
}
|
||||||
|
}
|
11
codegen/ui_tests/rhai_mod_bad_value.stderr
Normal file
11
codegen/ui_tests/rhai_mod_bad_value.stderr
Normal file
@ -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`
|
29
codegen/ui_tests/rhai_mod_junk_arg.rs
Normal file
29
codegen/ui_tests/rhai_mod_junk_arg.rs
Normal file
@ -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");
|
||||||
|
}
|
||||||
|
}
|
11
codegen/ui_tests/rhai_mod_junk_arg.stderr
Normal file
11
codegen/ui_tests/rhai_mod_junk_arg.stderr
Normal file
@ -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`
|
29
codegen/ui_tests/rhai_mod_missing_value.rs
Normal file
29
codegen/ui_tests/rhai_mod_missing_value.rs
Normal file
@ -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");
|
||||||
|
}
|
||||||
|
}
|
11
codegen/ui_tests/rhai_mod_missing_value.stderr
Normal file
11
codegen/ui_tests/rhai_mod_missing_value.stderr
Normal file
@ -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`
|
29
codegen/ui_tests/rhai_mod_path_attr.rs
Normal file
29
codegen/ui_tests/rhai_mod_path_attr.rs
Normal file
@ -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");
|
||||||
|
}
|
||||||
|
}
|
11
codegen/ui_tests/rhai_mod_path_attr.stderr
Normal file
11
codegen/ui_tests/rhai_mod_path_attr.stderr
Normal file
@ -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`
|
29
codegen/ui_tests/rhai_mod_return_raw.rs
Normal file
29
codegen/ui_tests/rhai_mod_return_raw.rs
Normal file
@ -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");
|
||||||
|
}
|
||||||
|
}
|
11
codegen/ui_tests/rhai_mod_return_raw.stderr
Normal file
11
codegen/ui_tests/rhai_mod_return_raw.stderr
Normal file
@ -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`
|
Loading…
Reference in New Issue
Block a user