diff --git a/CHANGELOG.md b/CHANGELOG.md index 228ee0d1..ac587db8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,6 +53,7 @@ Enhancements * `is_empty` method is added to arrays, BLOB's, object maps, strings and ranges. * `StaticModuleResolver` now stores the path in the module's `id` field. * `Engine::module_resolver` is added to grant access to the `Engine`'s module resolver. +* Constants and variables now have types in generated definition files. Version 1.9.1 diff --git a/Cargo.toml b/Cargo.toml index 80ab0cf2..56c8abef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,7 +64,7 @@ debugging = ["internals"] # enable debugging serde = ["dep:serde", "smartstring/serde", "smallvec/serde"] # implement serde for rhai types # compiling for no-std -no_std = ["no-std-compat", "num-traits/libm", "core-error", "libm", "ahash/compile-time-rng", "hashbrown"] +no_std = ["no-std-compat", "num-traits/libm", "core-error", "libm", "ahash/compile-time-rng", "hashbrown/ahash-compile-time-rng"] # compiling for WASM wasm-bindgen = ["instant/wasm-bindgen"] diff --git a/examples/definitions/.rhai/all_in_one.d.rhai b/examples/definitions/.rhai/all_in_one.d.rhai index 9cf5fa41..c1025aa8 100644 --- a/examples/definitions/.rhai/all_in_one.d.rhai +++ b/examples/definitions/.rhai/all_in_one.d.rhai @@ -1211,7 +1211,7 @@ fn all(array: Array, filter: FnPtr) -> bool; /// let x = [1, 2, 3]; /// let y = [true, 'x']; /// -/// x.push(y); +/// x.append(y); /// /// print(x); // prints "[1, 2, 3, true, 'x']" /// ``` @@ -6480,8 +6480,12 @@ op |(u64, u64) -> u64; op |(u8, u8) -> u8; module general_kenobi { +const CONSTANT: int; + /// Returns a string where "hello there" is repeated `n` times. fn hello_there(n: int) -> String; } -let hello_there; +let hello_there: string; + +const HELLO: string; diff --git a/examples/definitions/.rhai/all_in_one_without_standard.d.rhai b/examples/definitions/.rhai/all_in_one_without_standard.d.rhai index aae73596..729b64fc 100644 --- a/examples/definitions/.rhai/all_in_one_without_standard.d.rhai +++ b/examples/definitions/.rhai/all_in_one_without_standard.d.rhai @@ -3,8 +3,12 @@ module static; op minus(int, int) -> int; module general_kenobi { +const CONSTANT: int; + /// Returns a string where "hello there" is repeated `n` times. fn hello_there(n: int) -> String; } -let hello_there; +let hello_there: string; + +const HELLO: string; diff --git a/examples/definitions/.rhai/definitions/__scope__.d.rhai b/examples/definitions/.rhai/definitions/__scope__.d.rhai index d66567c1..96d874f3 100644 --- a/examples/definitions/.rhai/definitions/__scope__.d.rhai +++ b/examples/definitions/.rhai/definitions/__scope__.d.rhai @@ -1,3 +1,5 @@ module static; -let hello_there; \ No newline at end of file +let hello_there: string; + +const HELLO: string; \ No newline at end of file diff --git a/examples/definitions/.rhai/definitions/__static__.d.rhai b/examples/definitions/.rhai/definitions/__static__.d.rhai index f9c3867f..a1876713 100644 --- a/examples/definitions/.rhai/definitions/__static__.d.rhai +++ b/examples/definitions/.rhai/definitions/__static__.d.rhai @@ -693,7 +693,7 @@ fn all(array: Array, filter: FnPtr) -> bool; /// let x = [1, 2, 3]; /// let y = [true, 'x']; /// -/// x.push(y); +/// x.append(y); /// /// print(x); // prints "[1, 2, 3, true, 'x']" /// ``` diff --git a/examples/definitions/.rhai/definitions/general_kenobi.d.rhai b/examples/definitions/.rhai/definitions/general_kenobi.d.rhai index 9e077d8e..6257c2a6 100644 --- a/examples/definitions/.rhai/definitions/general_kenobi.d.rhai +++ b/examples/definitions/.rhai/definitions/general_kenobi.d.rhai @@ -1,4 +1,6 @@ module general_kenobi; +const CONSTANT: int; + /// Returns a string where "hello there" is repeated `n` times. fn hello_there(n: int) -> String; \ No newline at end of file diff --git a/examples/definitions/.rhai/defs.json b/examples/definitions/.rhai/defs.json index 4f007907..bb25ab3e 100644 --- a/examples/definitions/.rhai/defs.json +++ b/examples/definitions/.rhai/defs.json @@ -3,8 +3,8 @@ "general_kenobi": { "functions": [ { - "baseHash": 14798413363692662073, - "fullHash": 2039416761244929762, + "baseHash": 727795846011184342, + "fullHash": 5101524478338862216, "namespace": "internal", "access": "public", "name": "hello_there", @@ -27,8 +27,8 @@ }, "functions": [ { - "baseHash": 17487674894006547092, - "fullHash": 13058993152904417424, + "baseHash": 17133166385977770750, + "fullHash": 11299449021188202345, "namespace": "global", "access": "public", "name": "minus", diff --git a/examples/definitions/main.rs b/examples/definitions/main.rs index 3d603f32..7648ec89 100644 --- a/examples/definitions/main.rs +++ b/examples/definitions/main.rs @@ -3,6 +3,9 @@ use rhai::{Engine, EvalAltResult, Scope}; #[export_module] pub mod general_kenobi { + /// General Kenobi's Constant. + pub const CONSTANT: i64 = 42; + /// Returns a string where "hello there" is repeated `n` times. pub fn hello_there(n: i64) -> String { use std::convert::TryInto; @@ -17,6 +20,9 @@ fn main() -> Result<(), Box> { // This variable will also show up in the definitions, since it will be part of the scope. scope.push("hello_there", "hello there"); + // This constant will also show up in the definitions, since it will be part of the scope. + scope.push_constant("HELLO", "hello there"); + #[cfg(not(feature = "no_module"))] engine.register_static_module("general_kenobi", exported_module!(general_kenobi).into()); diff --git a/src/api/definitions/mod.rs b/src/api/definitions/mod.rs index 8cb3b3ee..5bbd42ac 100644 --- a/src/api/definitions/mod.rs +++ b/src/api/definitions/mod.rs @@ -339,7 +339,7 @@ impl Definitions<'_> { }; if let Some(scope) = self.scope { - scope.write_definition(&mut s).unwrap(); + scope.write_definition(&mut s, self).unwrap(); } s @@ -412,13 +412,15 @@ impl Module { let mut vars = self.iter_var().collect::>(); vars.sort_by(|(a, _), (b, _)| a.cmp(b)); - for (name, _) in vars { + for (name, value) in vars { if !first { writer.write_str("\n\n")?; } first = false; - write!(writer, "const {name}: ?;")?; + let ty = def_type_name(value.type_name(), def.engine); + + write!(writer, "const {name}: {ty};")?; } let mut func_infos = self.iter_fn().collect::>(); @@ -554,17 +556,18 @@ fn def_type_name<'a>(ty: &'a str, engine: &'a Engine) -> Cow<'a, str> { impl Scope<'_> { /// _(metadata, internals)_ Return definitions for all items inside the [`Scope`]. - fn write_definition(&self, writer: &mut dyn fmt::Write) -> fmt::Result { + fn write_definition(&self, writer: &mut dyn fmt::Write, def: &Definitions) -> fmt::Result { let mut first = true; - for (name, constant, _) in self.iter_raw() { + for (name, constant, value) in self.iter_raw() { if !first { writer.write_str("\n\n")?; } first = false; let kw = if constant { Token::Const } else { Token::Let }; + let ty = def_type_name(value.type_name(), def.engine); - write!(writer, "{kw} {name};")?; + write!(writer, "{kw} {name}: {ty};")?; } Ok(()) diff --git a/src/api/type_names.rs b/src/api/type_names.rs index de9120ac..495916ed 100644 --- a/src/api/type_names.rs +++ b/src/api/type_names.rs @@ -106,6 +106,71 @@ fn map_std_type_name(name: &str, shorthands: bool) -> &str { .map_or(name, |s| map_std_type_name(s, shorthands)) } +/// Format a Rust type to be display-friendly. +/// +/// * `()` is cleared. +/// * `INT` and `FLOAT` are expanded. +/// * [`RhaiResult`][crate::RhaiResult] and [`RhaiResultOf`][crate::RhaiResultOf] are expanded. +#[cfg(feature = "metadata")] +pub fn format_type(typ: &str, is_return_type: bool) -> std::borrow::Cow { + const RHAI_RESULT_TYPE: &str = "RhaiResult"; + const RHAI_RESULT_TYPE_EXPAND: &str = "Result>"; + const RHAI_RESULT_OF_TYPE: &str = "RhaiResultOf<"; + const RHAI_RESULT_OF_TYPE_EXPAND: &str = "Result<{}, Box>"; + const RHAI_RANGE: &str = "ExclusiveRange"; + const RHAI_RANGE_TYPE: &str = "Range<"; + const RHAI_RANGE_EXPAND: &str = "Range<{}>"; + const RHAI_INCLUSIVE_RANGE: &str = "InclusiveRange"; + const RHAI_INCLUSIVE_RANGE_TYPE: &str = "RangeInclusive<"; + const RHAI_INCLUSIVE_RANGE_EXPAND: &str = "RangeInclusive<{}>"; + + let typ = typ.trim(); + + if let Some(x) = typ.strip_prefix("rhai::") { + return format_type(x, is_return_type); + } else if let Some(x) = typ.strip_prefix("&mut ") { + let r = format_type(x, false); + return if r == x { + typ.into() + } else { + format!("&mut {r}").into() + }; + } + + match typ { + "" | "()" if is_return_type => "".into(), + "INT" => std::any::type_name::().into(), + #[cfg(not(feature = "no_float"))] + "FLOAT" => std::any::type_name::().into(), + RHAI_RANGE => RHAI_RANGE_EXPAND + .replace("{}", std::any::type_name::()) + .into(), + RHAI_INCLUSIVE_RANGE => RHAI_INCLUSIVE_RANGE_EXPAND + .replace("{}", std::any::type_name::()) + .into(), + RHAI_RESULT_TYPE => RHAI_RESULT_TYPE_EXPAND.into(), + ty if ty.starts_with(RHAI_RANGE_TYPE) && ty.ends_with('>') => { + let inner = &ty[RHAI_RANGE_TYPE.len()..ty.len() - 1]; + RHAI_RANGE_EXPAND + .replace("{}", format_type(inner, false).trim()) + .into() + } + ty if ty.starts_with(RHAI_INCLUSIVE_RANGE_TYPE) && ty.ends_with('>') => { + let inner = &ty[RHAI_INCLUSIVE_RANGE_TYPE.len()..ty.len() - 1]; + RHAI_INCLUSIVE_RANGE_EXPAND + .replace("{}", format_type(inner, false).trim()) + .into() + } + ty if ty.starts_with(RHAI_RESULT_OF_TYPE) && ty.ends_with('>') => { + let inner = &ty[RHAI_RESULT_OF_TYPE.len()..ty.len() - 1]; + RHAI_RESULT_OF_TYPE_EXPAND + .replace("{}", format_type(inner, false).trim()) + .into() + } + ty => ty.into(), + } +} + impl Engine { /// Pretty-print a type name. /// diff --git a/src/func/call.rs b/src/func/call.rs index 17be540f..dc76eb4d 100644 --- a/src/func/call.rs +++ b/src/func/call.rs @@ -15,6 +15,10 @@ use crate::{ Scope, ERR, }; #[cfg(feature = "no_std")] +use hashbrown::hash_map::Entry; +#[cfg(not(feature = "no_std"))] +use std::collections::hash_map::Entry; +#[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{ any::{type_name, TypeId}, @@ -199,10 +203,9 @@ impl Engine { ) }); - let result = caches - .fn_resolution_cache_mut() - .entry(hash) - .or_insert_with(|| { + match caches.fn_resolution_cache_mut().entry(hash) { + Entry::Occupied(entry) => entry.into_mut().as_ref(), + Entry::Vacant(entry) => { let num_args = args.as_ref().map_or(0, |a| a.len()); let mut max_bitmask = 0; // One above maximum bitmask based on number of parameters. // Set later when a specific matching function is not found. @@ -211,45 +214,27 @@ impl Engine { loop { let func = lib .iter() - .find_map(|&m| { - m.get_fn(hash).cloned().map(|func| FnResolutionCacheEntry { - func, - source: m.id().map(|s| Box::new(s.into())), - }) - }) + .find_map(|&m| m.get_fn(hash).map(|f| (f, m.id()))) .or_else(|| { - self.global_modules.iter().find_map(|m| { - m.get_fn(hash).cloned().map(|func| FnResolutionCacheEntry { - func, - source: m.id().map(|s| Box::new(s.into())), - }) - }) + self.global_modules + .iter() + .find_map(|m| m.get_fn(hash).map(|f| (f, m.id()))) }); #[cfg(not(feature = "no_module"))] - let func = func - .or_else(|| { - _global.get_qualified_fn(hash).map(|(func, source)| { - FnResolutionCacheEntry { - func: func.clone(), - source: source.map(|s| Box::new(s.into())), - } - }) - }) - .or_else(|| { - self.global_sub_modules.values().find_map(|m| { - m.get_qualified_fn(hash).cloned().map(|func| { - FnResolutionCacheEntry { - func, - source: m.id().map(|s| Box::new(s.into())), - } - }) - }) - }); + let func = func.or_else(|| _global.get_qualified_fn(hash)).or_else(|| { + self.global_sub_modules + .values() + .find_map(|m| m.get_qualified_fn(hash).map(|f| (f, m.id()))) + }); - // Specific version found - if let Some(f) = func { - return Some(f); + if let Some((f, s)) = func { + // Specific version found - insert into cache and return it + let new_entry = FnResolutionCacheEntry { + func: f.clone(), + source: s.map(|s| Box::new(s.into())), + }; + return entry.insert(Some(new_entry)).as_ref(); } // Check `Dynamic` parameters for functions with parameters @@ -282,7 +267,8 @@ impl Engine { return None; } - return args.and_then(|args| { + // Try to find a built-in version + let builtin = args.and_then(|args| { if is_op_assignment { let (first_arg, rest_args) = args.split_first().unwrap(); @@ -301,6 +287,8 @@ impl Engine { }) } }); + + return entry.insert(builtin).as_ref(); } // Try all permutations with `Dynamic` wildcards @@ -323,9 +311,8 @@ impl Engine { bitmask += 1; } - }); - - result.as_ref() + } + } } /// # Main Entry-Point diff --git a/src/func/hashing.rs b/src/func/hashing.rs index 1a79a684..2a5f5dcc 100644 --- a/src/func/hashing.rs +++ b/src/func/hashing.rs @@ -47,11 +47,11 @@ impl Hasher for StraightHasher { fn finish(&self) -> u64 { self.0 } - #[inline] + #[inline(always)] fn write(&mut self, _bytes: &[u8]) { panic!("StraightHasher can only hash u64 values"); } - + #[inline(always)] fn write_u64(&mut self, i: u64) { if i == 0 { self.0 = ALT_ZERO_HASH; diff --git a/src/module/mod.rs b/src/module/mod.rs index dc4b7140..d97e8ec1 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -1,5 +1,7 @@ //! Module defining external-loaded modules for Rhai. +#[cfg(feature = "metadata")] +use crate::api::type_names::format_type; use crate::ast::FnAccess; use crate::func::{ shared_take_or_clone, CallableFunction, FnCallArgs, IteratorFn, RegisterNativeFunction, @@ -116,69 +118,6 @@ pub struct FuncInfo { } impl FuncInfo { - /// Format a return type to be display-friendly. - /// - /// `()` is cleared. - /// [`RhaiResult`][crate::RhaiResult] and [`RhaiResultOf`] are expanded. - #[cfg(feature = "metadata")] - pub fn format_type(typ: &str, is_return_type: bool) -> std::borrow::Cow { - const RHAI_RESULT_TYPE: &str = "RhaiResult"; - const RHAI_RESULT_TYPE_EXPAND: &str = "Result>"; - const RHAI_RESULT_OF_TYPE: &str = "RhaiResultOf<"; - const RHAI_RESULT_OF_TYPE_EXPAND: &str = "Result<{}, Box>"; - const RHAI_RANGE: &str = "ExclusiveRange"; - const RHAI_RANGE_TYPE: &str = "Range<"; - const RHAI_RANGE_EXPAND: &str = "Range<{}>"; - const RHAI_INCLUSIVE_RANGE: &str = "InclusiveRange"; - const RHAI_INCLUSIVE_RANGE_TYPE: &str = "RangeInclusive<"; - const RHAI_INCLUSIVE_RANGE_EXPAND: &str = "RangeInclusive<{}>"; - - let typ = typ.trim(); - - if let Some(x) = typ.strip_prefix("rhai::") { - return Self::format_type(x, is_return_type); - } else if let Some(x) = typ.strip_prefix("&mut ") { - let r = Self::format_type(x, false); - return if r == x { - typ.into() - } else { - format!("&mut {r}").into() - }; - } - - match typ { - "" | "()" if is_return_type => "".into(), - "INT" => std::any::type_name::().into(), - #[cfg(not(feature = "no_float"))] - "FLOAT" => std::any::type_name::().into(), - RHAI_RANGE => RHAI_RANGE_EXPAND - .replace("{}", std::any::type_name::()) - .into(), - RHAI_INCLUSIVE_RANGE => RHAI_INCLUSIVE_RANGE_EXPAND - .replace("{}", std::any::type_name::()) - .into(), - RHAI_RESULT_TYPE => RHAI_RESULT_TYPE_EXPAND.into(), - ty if ty.starts_with(RHAI_RANGE_TYPE) && ty.ends_with('>') => { - let inner = &ty[RHAI_RANGE_TYPE.len()..ty.len() - 1]; - RHAI_RANGE_EXPAND - .replace("{}", Self::format_type(inner, false).trim()) - .into() - } - ty if ty.starts_with(RHAI_INCLUSIVE_RANGE_TYPE) && ty.ends_with('>') => { - let inner = &ty[RHAI_INCLUSIVE_RANGE_TYPE.len()..ty.len() - 1]; - RHAI_INCLUSIVE_RANGE_EXPAND - .replace("{}", Self::format_type(inner, false).trim()) - .into() - } - ty if ty.starts_with(RHAI_RESULT_OF_TYPE) && ty.ends_with('>') => { - let inner = &ty[RHAI_RESULT_OF_TYPE.len()..ty.len() - 1]; - RHAI_RESULT_OF_TYPE_EXPAND - .replace("{}", Self::format_type(inner, false).trim()) - .into() - } - ty => ty.into(), - } - } /// _(metadata)_ Generate a signature of the function. /// Exported under the `metadata` feature only. #[cfg(feature = "metadata")] @@ -186,7 +125,7 @@ impl FuncInfo { pub fn gen_signature(&self) -> String { let mut sig = format!("{}(", self.metadata.name); - let return_type = Self::format_type(&self.metadata.return_type, true); + let return_type = format_type(&self.metadata.return_type, true); if self.metadata.params_info.is_empty() { for x in 0..self.metadata.params { @@ -207,9 +146,7 @@ impl FuncInfo { s => s, }; let result: std::borrow::Cow = match seg.next() { - Some(typ) => { - format!("{name}: {}", FuncInfo::format_type(typ, false)).into() - } + Some(typ) => format!("{name}: {}", format_type(typ, false)).into(), None => name.into(), }; result @@ -278,9 +215,9 @@ pub struct Module { /// Native Rust functions (in scripted hash format) that contain [`Dynamic`] parameters. dynamic_functions: StraightHashSet, /// Iterator functions, keyed by the type producing the iterator. - type_iterators: StraightHashMap>, + type_iterators: BTreeMap>, /// Flattened collection of iterator functions, including those in sub-modules. - all_type_iterators: StraightHashMap>, + all_type_iterators: BTreeMap>, /// Is the [`Module`] indexed? indexed: bool, /// Does the [`Module`] contain indexed functions that have been exposed to the global namespace? @@ -378,8 +315,8 @@ impl Module { functions: StraightHashMap::default(), all_functions: StraightHashMap::default(), dynamic_functions: StraightHashSet::default(), - type_iterators: StraightHashMap::default(), - all_type_iterators: StraightHashMap::default(), + type_iterators: BTreeMap::new(), + all_type_iterators: BTreeMap::new(), indexed: true, contains_indexed_global_functions: false, } @@ -2141,7 +2078,7 @@ impl Module { path: &mut Vec<&'a str>, variables: &mut StraightHashMap, functions: &mut StraightHashMap, - type_iterators: &mut StraightHashMap>, + type_iterators: &mut BTreeMap>, ) -> bool { let mut contains_indexed_global_functions = false; @@ -2205,7 +2142,7 @@ impl Module { let mut path = Vec::with_capacity(4); let mut variables = StraightHashMap::default(); let mut functions = StraightHashMap::default(); - let mut type_iterators = StraightHashMap::default(); + let mut type_iterators = BTreeMap::new(); path.push(""); diff --git a/src/serde/metadata.rs b/src/serde/metadata.rs index 7a562d5c..b693c858 100644 --- a/src/serde/metadata.rs +++ b/src/serde/metadata.rs @@ -1,6 +1,7 @@ //! Serialization of functions metadata. #![cfg(feature = "metadata")] +use crate::api::type_names::format_type; use crate::module::{calc_native_fn_hash, FuncInfo}; use crate::{calc_fn_hash, Engine, FnAccess, SmartString, StaticVec, AST}; use serde::Serialize; @@ -94,12 +95,12 @@ impl<'a> From<&'a FuncInfo> for FnMetadata<'a> { "_" => None, s => Some(s), }; - let typ = seg.next().map(|s| FuncInfo::format_type(s, false)); + let typ = seg.next().map(|s| format_type(s, false)); FnParam { name, typ } }) .collect(), _dummy: None, - return_type: FuncInfo::format_type(&info.metadata.return_type, true), + return_type: format_type(&info.metadata.return_type, true), signature: info.gen_signature().into(), doc_comments: if info.func.is_script() { #[cfg(feature = "no_function")]