Add type alias support for plugin modules.

This commit is contained in:
Stephen Chung 2022-03-19 09:43:18 +08:00
parent 6546eae95f
commit fefa633cf0
12 changed files with 207 additions and 18 deletions

View File

@ -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"

View File

@ -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<ExportedFn>,
consts: Vec<ExportedConst>,
custom_types: Vec<ExportedType>,
fns: Vec<ExportedFn>,
sub_modules: Vec<Module>,
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,
&params.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

View File

@ -18,9 +18,17 @@ pub struct ExportedConst {
pub cfg_attrs: Vec<syn::Attribute>,
}
#[derive(Debug)]
pub struct ExportedType {
pub name: String,
pub typ: Box<syn::Type>,
pub cfg_attrs: Vec<syn::Attribute>,
}
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::<syn::Stmt>(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() {

View File

@ -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::<Module>(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::<syn::Type>(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::<Hello>()],
get_mystic_number_token().into());
m.set_var("MYSTIC_NUMBER", MYSTIC_NUMBER);
m.set_custom_type::<Foo>("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::<Hello>()] }
}
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::<Hello>().unwrap();
Ok(Dynamic::from(get_mystic_number(arg0)))
}
#[inline(always)] fn is_method_call(&self) -> bool { true }
}
}
};

View File

@ -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<T: Variant + Clone>(&mut self, name: &str) -> &mut Self {
self.register_type_with_name_raw(type_name::<T>(), name)
self.custom_types.add_type::<T>(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<Identifier>,
) -> &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`].

View File

@ -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::<crate::INT>(),
#[cfg(not(feature = "no_float"))]

View File

@ -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<Box<dyn crate::ModuleResolver>>,
/// A map mapping type names to pretty-print names.
pub(crate) type_names: BTreeMap<Identifier, Identifier>,
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(),

View File

@ -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<Identifier, Shared<Module>>,
/// [`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<T>(&mut self, name: &str) {
self.custom_types.add_type::<T>(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

43
src/types/custom_types.rs Normal file
View File

@ -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<Identifier, CustomType>);
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<Identifier>, name: impl Into<Identifier>) {
self.0.insert(key.into(), name.into());
}
/// Register a custom type.
#[inline(always)]
pub fn add_type<T>(&mut self, name: &str) {
self.0.insert(type_name::<T>().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)
}
}

View File

@ -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};

View File

@ -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;

View File

@ -53,9 +53,13 @@ fn test_module_sub_module() -> Result<(), Box<EvalAltResult>> {
assert_eq!(m2.get_var_value::<INT>("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::<String>("type_of(())")?, "Don't Panic");
assert_eq!(engine.eval::<INT>("question::MYSTIC_NUMBER")?, 42);
assert!(engine.eval::<INT>("MYSTIC_NUMBER").is_err());
assert_eq!(engine.eval::<INT>("question::life::universe::answer")?, 41);