diff --git a/codegen/Cargo.toml b/codegen/Cargo.toml index 79a179b1..67baf653 100644 --- a/codegen/Cargo.toml +++ b/codegen/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rhai_codegen" -version = "1.3.0" +version = "1.4.0" edition = "2018" authors = ["jhwgh1968", "Stephen Chung"] description = "Procedural macros support package for Rhai, a scripting language and engine for Rust" diff --git a/codegen/src/module.rs b/codegen/src/module.rs index c25a102d..bafd1b33 100644 --- a/codegen/src/module.rs +++ b/codegen/src/module.rs @@ -10,7 +10,7 @@ use std::borrow::Cow; use crate::attrs::{AttrItem, ExportInfo, ExportScope, ExportedParams}; use crate::function::ExportedFn; -use crate::rhai_module::ExportedConst; +use crate::rhai_module::{ExportedConst, ExportedType}; #[derive(Debug, Clone, Eq, PartialEq, Hash, Default)] pub struct ExportedModParams { @@ -89,8 +89,9 @@ impl ExportedParams for ExportedModParams { #[derive(Debug)] pub struct Module { mod_all: syn::ItemMod, - fns: Vec, consts: Vec, + custom_types: Vec, + fns: Vec, sub_modules: Vec, params: ExportedModParams, } @@ -107,6 +108,7 @@ impl Parse for Module { let mut mod_all: syn::ItemMod = input.parse()?; let fns: Vec<_>; let mut consts = Vec::new(); + let mut custom_types = Vec::new(); let mut sub_modules = Vec::new(); if let Some((.., ref mut content)) = mod_all.content { @@ -155,6 +157,25 @@ impl Parse for Module { _ => {} } } + // Gather and parse type definitions. + for item in content.iter() { + match item { + syn::Item::Type(syn::ItemType { + vis, + ident, + attrs, + ty, + .. + }) if matches!(vis, syn::Visibility::Public(..)) => { + custom_types.push(ExportedType { + name: ident.to_string(), + typ: ty.clone(), + cfg_attrs: crate::attrs::collect_cfg_attr(&attrs), + }) + } + _ => {} + } + } // Gather and parse sub-module definitions. // // They are actually removed from the module's body, because they will need @@ -188,6 +209,7 @@ impl Parse for Module { mod_all, fns, consts, + custom_types, sub_modules, params: ExportedModParams::default(), }) @@ -241,6 +263,7 @@ impl Module { mut mod_all, mut fns, consts, + custom_types, mut sub_modules, params, .. @@ -257,6 +280,7 @@ impl Module { let mod_gen = crate::rhai_module::generate_body( &mut fns, &consts, + &custom_types, &mut sub_modules, ¶ms.scope, ); @@ -300,6 +324,11 @@ impl Module { &self.consts } + #[allow(dead_code)] + pub fn custom_types(&self) -> &[ExportedType] { + &self.custom_types + } + #[allow(dead_code)] pub fn fns(&self) -> &[ExportedFn] { &self.fns diff --git a/codegen/src/rhai_module.rs b/codegen/src/rhai_module.rs index e3fd07af..ccd40601 100644 --- a/codegen/src/rhai_module.rs +++ b/codegen/src/rhai_module.rs @@ -18,9 +18,17 @@ pub struct ExportedConst { pub cfg_attrs: Vec, } +#[derive(Debug)] +pub struct ExportedType { + pub name: String, + pub typ: Box, + pub cfg_attrs: Vec, +} + pub fn generate_body( fns: &mut [ExportedFn], consts: &[ExportedConst], + custom_types: &[ExportedType], sub_modules: &mut [Module], parent_scope: &ExportScope, ) -> TokenStream { @@ -54,6 +62,29 @@ pub fn generate_body( ); } + for ExportedType { + name, + typ, + cfg_attrs, + .. + } in custom_types + { + let const_literal = syn::LitStr::new(&name, Span::call_site()); + + let cfg_attrs: Vec<_> = cfg_attrs + .iter() + .map(syn::Attribute::to_token_stream) + .collect(); + + set_const_statements.push( + syn::parse2::(quote! { + #(#cfg_attrs)* + m.set_custom_type::<#typ>(#const_literal); + }) + .unwrap(), + ); + } + for item_mod in sub_modules { item_mod.update_scope(&parent_scope); if item_mod.skipped() { diff --git a/codegen/src/test/module.rs b/codegen/src/test/module.rs index f4700e83..be9b93e5 100644 --- a/codegen/src/test/module.rs +++ b/codegen/src/test/module.rs @@ -37,6 +37,31 @@ mod module_tests { ); } + #[test] + fn one_factory_fn_with_custom_type_module() { + let input_tokens: TokenStream = quote! { + pub mod one_fn { + pub type Hello = (); + + pub fn get_mystic_number() -> INT { + 42 + } + } + }; + + let item_mod = syn::parse2::(input_tokens).unwrap(); + assert!(item_mod.consts().is_empty()); + assert_eq!(item_mod.custom_types().len(), 1); + assert_eq!(item_mod.custom_types()[0].name.to_string(), "Hello"); + assert_eq!(item_mod.fns().len(), 1); + assert_eq!(item_mod.fns()[0].name().to_string(), "get_mystic_number"); + assert_eq!(item_mod.fns()[0].arg_count(), 0); + assert_eq!( + item_mod.fns()[0].return_type().unwrap(), + &syn::parse2::(quote! { INT }).unwrap() + ); + } + #[test] #[cfg(feature = "metadata")] fn one_factory_fn_with_comments_module() { @@ -753,7 +778,13 @@ mod generate_tests { #[derive(Debug, Clone)] pub struct Foo(pub INT); + pub type Hello = Foo; + pub const MYSTIC_NUMBER: Foo = Foo(42); + + pub fn get_mystic_number(x: &mut Hello) -> INT { + x.0 + } } }; @@ -762,7 +793,13 @@ mod generate_tests { #[derive(Debug, Clone)] pub struct Foo(pub INT); + pub type Hello = Foo; + pub const MYSTIC_NUMBER: Foo = Foo(42); + + pub fn get_mystic_number(x: &mut Hello) -> INT { + x.0 + } #[allow(unused_imports)] use super::*; @@ -774,9 +811,32 @@ mod generate_tests { } #[allow(unused_mut)] pub fn rhai_generate_into_module(m: &mut Module, flatten: bool) { + m.set_fn("get_mystic_number", FnNamespace::Internal, FnAccess::Public, + Some(get_mystic_number_token::PARAM_NAMES), &[TypeId::of::()], + get_mystic_number_token().into()); m.set_var("MYSTIC_NUMBER", MYSTIC_NUMBER); + m.set_custom_type::("Hello"); if flatten {} else {} } + + #[allow(non_camel_case_types)] + pub struct get_mystic_number_token(); + impl get_mystic_number_token { + pub const PARAM_NAMES: &'static [&'static str] = &["x: &mut Hello", "INT"]; + #[inline(always)] pub fn param_types() -> [TypeId; 1usize] { [TypeId::of::()] } + } + impl PluginFunction for get_mystic_number_token { + #[inline(always)] + fn call(&self, context: NativeCallContext, args: &mut [&mut Dynamic]) -> RhaiResult { + if args[0usize].is_read_only() { + return Err(EvalAltResult::ErrorAssignmentToConstant("x".to_string(), Position::NONE).into()); + } + let arg0 = &mut args[0usize].write_lock::().unwrap(); + Ok(Dynamic::from(get_mystic_number(arg0))) + } + + #[inline(always)] fn is_method_call(&self) -> bool { true } + } } }; diff --git a/src/api/register.rs b/src/api/register.rs index 72720c7a..1d5220d5 100644 --- a/src/api/register.rs +++ b/src/api/register.rs @@ -10,7 +10,7 @@ use std::any::{type_name, TypeId}; use std::prelude::v1::*; impl Engine { - /// Get the global namespace module (which is the last module in `global_modules`). + /// Get the global namespace module (which is the fist module in `global_modules`). #[inline(always)] #[allow(dead_code)] pub(crate) fn global_namespace(&self) -> &Module { @@ -273,7 +273,8 @@ impl Engine { /// ``` #[inline(always)] pub fn register_type_with_name(&mut self, name: &str) -> &mut Self { - self.register_type_with_name_raw(type_name::(), name) + self.custom_types.add_type::(name); + self } /// Register a custom type for use with the [`Engine`], with a pretty-print name /// for the `type_of` function. The type must implement [`Clone`]. @@ -288,8 +289,7 @@ impl Engine { name: impl Into, ) -> &mut Self { // Add the pretty-print type name into the map - self.type_names - .insert(fully_qualified_type_path.into(), name.into()); + self.custom_types.add(fully_qualified_type_path, name); self } /// Register an type iterator for an iterable type with the [`Engine`]. diff --git a/src/api/type_names.rs b/src/api/type_names.rs index 3edb1c3b..bf9ac7da 100644 --- a/src/api/type_names.rs +++ b/src/api/type_names.rs @@ -85,9 +85,15 @@ impl Engine { #[inline] #[must_use] pub fn map_type_name<'a>(&'a self, name: &'a str) -> &'a str { - self.type_names - .get(name) - .map(|s| s.as_str()) + self.global_modules + .iter() + .find_map(|m| m.get_custom_type(name)) + .or_else(|| { + self.global_sub_modules + .iter() + .find_map(|(_, m)| m.get_custom_type(name)) + }) + .or_else(|| self.custom_types.get(name)) .unwrap_or_else(|| map_std_type_name(name, true)) } @@ -109,9 +115,8 @@ impl Engine { }; } - self.type_names + self.custom_types .get(name) - .map(|s| s.as_str()) .unwrap_or_else(|| match name { "INT" => return type_name::(), #[cfg(not(feature = "no_float"))] diff --git a/src/engine.rs b/src/engine.rs index eaefc5dd..f180f624 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -6,7 +6,7 @@ use crate::func::native::{ }; use crate::packages::{Package, StandardPackage}; use crate::tokenizer::Token; -use crate::types::dynamic::Union; +use crate::types::{dynamic::Union, CustomTypesCollection}; use crate::{ Dynamic, Identifier, ImmutableString, Module, OptimizationLevel, Position, RhaiResult, Shared, StaticVec, @@ -105,7 +105,7 @@ pub struct Engine { pub(crate) module_resolver: Option>, /// A map mapping type names to pretty-print names. - pub(crate) type_names: BTreeMap, + pub(crate) custom_types: CustomTypesCollection, /// An empty [`ImmutableString`] for cloning purposes. pub(crate) empty_string: ImmutableString, @@ -160,7 +160,7 @@ impl fmt::Debug for Engine { f.field("global_sub_modules", &self.global_sub_modules) .field("module_resolver", &self.module_resolver.is_some()); - f.field("type_names", &self.type_names) + f.field("type_names", &self.custom_types) .field("disabled_symbols", &self.disabled_symbols) .field("custom_keywords", &self.custom_keywords) .field("custom_syntax", &(!self.custom_syntax.is_empty())) @@ -271,7 +271,7 @@ impl Engine { #[cfg(not(feature = "no_module"))] module_resolver: None, - type_names: BTreeMap::new(), + custom_types: CustomTypesCollection::new(), empty_string: ImmutableString::new(), disabled_symbols: BTreeSet::new(), custom_keywords: BTreeMap::new(), diff --git a/src/module/mod.rs b/src/module/mod.rs index 0491e3e9..a70ee8e9 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -5,7 +5,7 @@ use crate::func::{ shared_take_or_clone, CallableFunction, FnCallArgs, IteratorFn, RegisterNativeFunction, SendSync, }; -use crate::types::dynamic::Variant; +use crate::types::{dynamic::Variant, CustomTypesCollection}; use crate::{ calc_fn_params_hash, calc_qualified_fn_hash, combine_hashes, Dynamic, Identifier, ImmutableString, NativeCallContext, RhaiResultOf, Shared, StaticVec, @@ -232,6 +232,8 @@ pub struct Module { pub(crate) internal: bool, /// Is this module part of a standard library? pub(crate) standard: bool, + /// Custom types. + custom_types: CustomTypesCollection, /// Sub-modules. modules: BTreeMap>, /// [`Module`] variables. @@ -339,6 +341,7 @@ impl Module { id: Identifier::new_const(), internal: false, standard: false, + custom_types: CustomTypesCollection::new(), modules: BTreeMap::new(), variables: BTreeMap::new(), all_variables: BTreeMap::new(), @@ -413,6 +416,17 @@ impl Module { self } + /// Map a custom type to a friendly display name. + #[inline(always)] + pub fn set_custom_type(&mut self, name: &str) { + self.custom_types.add_type::(name) + } + /// Get the display name of a registered custom type. + #[inline(always)] + pub fn get_custom_type(&self, key: &str) -> Option<&str> { + self.custom_types.get(key) + } + /// Is the [`Module`] empty? /// /// # Example diff --git a/src/types/custom_types.rs b/src/types/custom_types.rs new file mode 100644 index 00000000..e46aaddc --- /dev/null +++ b/src/types/custom_types.rs @@ -0,0 +1,43 @@ +//! Collection of custom types. + +use crate::Identifier; +use std::{any::type_name, collections::BTreeMap, fmt}; + +/// _(internals)_ A custom type. +/// Exported under the `internals` feature only. +pub type CustomType = Identifier; + +/// _(internals)_ A collection of custom types. +/// Exported under the `internals` feature only. +#[derive(Clone, Hash, Default)] +pub struct CustomTypesCollection(BTreeMap); + +impl fmt::Debug for CustomTypesCollection { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("CustomTypesCollection ")?; + f.debug_map().entries(self.0.iter()).finish() + } +} + +impl CustomTypesCollection { + /// Create a new [`CustomTypesCollection`]. + #[inline(always)] + pub fn new() -> Self { + Self(BTreeMap::new()) + } + /// Register a custom type. + #[inline(always)] + pub fn add(&mut self, key: impl Into, name: impl Into) { + self.0.insert(key.into(), name.into()); + } + /// Register a custom type. + #[inline(always)] + pub fn add_type(&mut self, name: &str) { + self.0.insert(type_name::().into(), name.into()); + } + /// Find a custom type. + #[inline(always)] + pub fn get(&self, key: &str) -> Option<&str> { + self.0.get(key).map(CustomType::as_str) + } +} diff --git a/src/types/dynamic.rs b/src/types/dynamic.rs index 30c05a52..afeb3d7c 100644 --- a/src/types/dynamic.rs +++ b/src/types/dynamic.rs @@ -1,4 +1,5 @@ -//! Helper module which defines the [`Any`] trait to to allow dynamic value handling. +//! Helper module which defines the [`Dynamic`] data type and the +//! [`Any`] trait to to allow custom type handling. use crate::func::native::SendSync; use crate::{reify, ExclusiveRange, FnPtr, ImmutableString, InclusiveRange, INT}; diff --git a/src/types/mod.rs b/src/types/mod.rs index 66da654c..6fad549e 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -1,5 +1,6 @@ //! Module defining Rhai data types. +pub mod custom_types; pub mod dynamic; pub mod error; pub mod fn_ptr; @@ -8,6 +9,7 @@ pub mod interner; pub mod parse_error; pub mod scope; +pub use custom_types::{CustomType, CustomTypesCollection}; pub use dynamic::Dynamic; #[cfg(not(feature = "no_std"))] pub use dynamic::Instant; diff --git a/tests/modules.rs b/tests/modules.rs index 25e17117..2b952a8d 100644 --- a/tests/modules.rs +++ b/tests/modules.rs @@ -53,9 +53,13 @@ fn test_module_sub_module() -> Result<(), Box> { assert_eq!(m2.get_var_value::("answer").unwrap(), 41); + module.set_custom_type::<()>("Don't Panic"); + let mut engine = Engine::new(); engine.register_static_module("question", module.into()); + assert_eq!(engine.eval::("type_of(())")?, "Don't Panic"); + assert_eq!(engine.eval::("question::MYSTIC_NUMBER")?, 42); assert!(engine.eval::("MYSTIC_NUMBER").is_err()); assert_eq!(engine.eval::("question::life::universe::answer")?, 41);