From 83597328815eb2e1713113a00825cf41e7045c1a Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 30 Jan 2024 18:09:07 +0800 Subject: [PATCH] Format function signatures. --- CHANGELOG.md | 2 + examples/custom_types_and_methods.rs | 23 +++++------ src/api/definitions/mod.rs | 2 +- src/api/deprecated.rs | 18 ++++++++- src/api/formatting.rs | 58 +++++++++++----------------- src/api/register.rs | 15 ++++--- src/module/mod.rs | 32 ++++++++++----- src/serde/metadata.rs | 8 ++-- 8 files changed, 88 insertions(+), 70 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d4e8fe9ef..9fe366a56 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ Deprecated API's * `rhai::config::hashing::set_ahash_seed`, `rhai::config::hashing::get_ahash_seed` and the `RHAI_AHASH_SEED` environment variable are deprecated in favor of `rhai::config::hashing::set_hashing_seed`, `rhai::config::hashing::get_hashing_seed` and `RHAI_HASHING_SEED`. * `AST::clear_doc` is deprecated. * Much of the `Module::update_XXX` API is deprecated in favor of using the `FuncRegistration` API. +* `Module::gen_fn_signatures` is deprecated in favor of `Module::gen_fn_signatures_with_mapper`. Fixes to bugs found via fuzzing ------------------------------- @@ -75,6 +76,7 @@ Enhancements * `Dynamic::is_fnptr` is made a public API. * `Scope::get_value_ref` and `Scope::get_value_mut` are added. * `TypeBuilder::with_name` now takes any `&str` instead of just `&'static str`. +* `Engine::gen_fn_signatures` now formats the function signatures using pretty-print names of custom types. Version 1.16.3 diff --git a/examples/custom_types_and_methods.rs b/examples/custom_types_and_methods.rs index fecbccfec..4dd0f5618 100644 --- a/examples/custom_types_and_methods.rs +++ b/examples/custom_types_and_methods.rs @@ -5,11 +5,12 @@ fn main() { panic!("This example does not run under 'no_object'."); } -use rhai::{Engine, EvalAltResult}; +use rhai::{CustomType, Engine, EvalAltResult, TypeBuilder}; #[cfg(not(feature = "no_object"))] fn main() -> Result<(), Box> { - #[derive(Debug, Clone)] + #[derive(Debug, Clone, CustomType)] + #[rhai_type(extra = Self::build_extra)] struct TestStruct { x: i64, } @@ -24,22 +25,18 @@ fn main() -> Result<(), Box> { pub fn calculate(&mut self, data: i64) -> i64 { self.x * data } - pub fn get_x(&mut self) -> i64 { - self.x - } - pub fn set_x(&mut self, value: i64) { - self.x = value; + + fn build_extra(builder: &mut TypeBuilder) { + builder + .with_fn("new_ts", TestStruct::new) + .with_fn("update", TestStruct::update) + .with_fn("calc", TestStruct::calculate); } } let mut engine = Engine::new(); - engine - .register_type_with_name::("TestStruct") - .register_fn("new_ts", TestStruct::new) - .register_fn("update", TestStruct::update) - .register_fn("calc", TestStruct::calculate) - .register_get_set("x", TestStruct::get_x, TestStruct::set_x); + engine.build_type::(); #[cfg(feature = "metadata")] { diff --git a/src/api/definitions/mod.rs b/src/api/definitions/mod.rs index da5147c1d..a2c19d868 100644 --- a/src/api/definitions/mod.rs +++ b/src/api/definitions/mod.rs @@ -533,7 +533,7 @@ impl FuncMetadata { /// Associated generic types are also rewritten into regular generic type parameters. #[must_use] fn def_type_name<'a>(ty: &'a str, engine: &'a Engine) -> Cow<'a, str> { - let ty = engine.format_type_name(ty).replace("crate::", ""); + let ty = engine.format_param_type(ty).replace("crate::", ""); let ty = ty.strip_prefix("&mut").unwrap_or(&*ty).trim(); let ty = ty.split("::").last().unwrap(); diff --git a/src/api/deprecated.rs b/src/api/deprecated.rs index 17248b62c..554800213 100644 --- a/src/api/deprecated.rs +++ b/src/api/deprecated.rs @@ -551,7 +551,7 @@ impl Position { } #[allow(deprecated)] -impl TypeBuilder<'_, '_, T> { +impl TypeBuilder<'_, T> { /// Register a custom fallible function. /// /// # Deprecated @@ -788,6 +788,22 @@ impl Module { ) -> &mut Self { self.update_fn_metadata_with_comments(hash_fn, arg_names, [""; 0]) } + + /// _(metadata)_ Generate signatures for all the non-private functions in the [`Module`]. + /// Exported under the `metadata` feature only. + /// + /// # Deprecated + /// + /// This method is deprecated. + /// Use [`gen_fn_signatures_with_mapper`][`Module::gen_fn_signatures_with_mapper`] instead. + /// + /// This method will be removed in the next major version. + #[deprecated(since = "1.17.0", note = "use `gen_fn_signatures_with_mapper` instead")] + #[cfg(feature = "metadata")] + #[inline(always)] + pub fn gen_fn_signatures(&self) -> impl Iterator + '_ { + self.gen_fn_signatures_with_mapper(|s| s.into()) + } } #[cfg(not(feature = "no_index"))] diff --git a/src/api/formatting.rs b/src/api/formatting.rs index d06e34b55..0fd630735 100644 --- a/src/api/formatting.rs +++ b/src/api/formatting.rs @@ -17,6 +17,13 @@ use std::prelude::v1::*; pub fn map_std_type_name(name: &str, shorthands: bool) -> &str { let name = name.trim(); + match name { + "INT" => return type_name::(), + #[cfg(not(feature = "no_float"))] + "FLOAT" => return type_name::(), + _ => (), + } + if name == type_name::() { return if shorthands { "string" } else { "String" }; } @@ -111,7 +118,7 @@ pub 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. +/// Format a Rust parameter type to be display-friendly. /// /// * `rhai::` prefix is cleared. /// * `()` is cleared. @@ -119,7 +126,7 @@ pub fn map_std_type_name(name: &str, shorthands: bool) -> &str { /// * `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 { +pub fn format_param_type_for_display(typ: &str, is_return_type: bool) -> std::borrow::Cow { const RESULT_TYPE: &str = "Result<"; const ERROR_TYPE: &str = ",Box>"; const RHAI_RESULT_TYPE: &str = "RhaiResult"; @@ -136,9 +143,9 @@ pub fn format_type(typ: &str, is_return_type: bool) -> std::borrow::Cow { let typ = typ.trim(); if let Some(x) = typ.strip_prefix("rhai::") { - return format_type(x, is_return_type); + return format_param_type_for_display(x, is_return_type); } else if let Some(x) = typ.strip_prefix("&mut ") { - let r = format_type(x, false); + let r = format_param_type_for_display(x, false); return if r == x { typ.into() } else { @@ -146,7 +153,7 @@ pub fn format_type(typ: &str, is_return_type: bool) -> std::borrow::Cow { }; } else if typ.contains(' ') { let typ = typ.replace(' ', ""); - let r = format_type(&typ, is_return_type); + let r = format_param_type_for_display(&typ, is_return_type); return r.into_owned().into(); } @@ -165,25 +172,25 @@ pub fn format_type(typ: &str, is_return_type: bool) -> std::borrow::Cow { 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()) + .replace("{}", format_param_type_for_display(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()) + .replace("{}", format_param_type_for_display(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()) + .replace("{}", format_param_type_for_display(inner, false).trim()) .into() } ty if ty.starts_with(RESULT_TYPE) && ty.ends_with(ERROR_TYPE) => { let inner = &ty[RESULT_TYPE.len()..ty.len() - ERROR_TYPE.len()]; RHAI_RESULT_OF_TYPE_EXPAND - .replace("{}", format_type(inner, false).trim()) + .replace("{}", format_param_type_for_display(inner, false).trim()) .into() } ty => ty.into(), @@ -195,10 +202,6 @@ impl Engine { /// /// If a type is registered via [`register_type_with_name`][Engine::register_type_with_name], /// the type name provided for the registration will be used. - /// - /// # Panics - /// - /// Panics if the type name is `&mut`. #[inline] #[must_use] pub fn map_type_name<'a>(&'a self, name: &'a str) -> &'a str { @@ -218,41 +221,24 @@ impl Engine { .unwrap_or_else(|| map_std_type_name(name, true)) } - /// Format a type name. + /// Format a Rust parameter type. /// /// If a type is registered via [`register_type_with_name`][Engine::register_type_with_name], /// the type name provided for the registration will be used. + /// + /// This method properly handles type names beginning with `&mut`. #[cfg(feature = "metadata")] #[inline] #[must_use] - pub(crate) fn format_type_name<'a>(&'a self, name: &'a str) -> std::borrow::Cow<'a, str> { + pub(crate) fn format_param_type<'a>(&'a self, name: &'a str) -> std::borrow::Cow<'a, str> { if let Some(x) = name.strip_prefix("&mut ") { - return match self.format_type_name(x) { + return match self.format_param_type(x) { r if r == x => name.into(), r => format!("&mut {r}").into(), }; } - self.global_modules - .iter() - .find_map(|m| m.get_custom_type_display_by_name(name)) - .or_else(|| { - #[cfg(not(feature = "no_module"))] - return self - .global_sub_modules - .values() - .find_map(|m| m.get_custom_type_display_by_name(name)); - - #[cfg(feature = "no_module")] - return None; - }) - .unwrap_or_else(|| match name { - "INT" => type_name::(), - #[cfg(not(feature = "no_float"))] - "FLOAT" => type_name::(), - _ => map_std_type_name(name, false), - }) - .into() + self.map_type_name(name).into() } /// Make a `Box<`[`EvalAltResult`][ERR::ErrorMismatchDataType]`>`. diff --git a/src/api/register.rs b/src/api/register.rs index 3b02a41b3..42882d8fa 100644 --- a/src/api/register.rs +++ b/src/api/register.rs @@ -84,11 +84,11 @@ impl Engine { { let mut param_type_names = FUNC::param_names() .iter() - .map(|ty| format!("_: {}", self.format_type_name(ty))) + .map(|ty| format!("_: {}", self.format_param_type(ty))) .collect::>(); if FUNC::return_type() != TypeId::of::<()>() { - param_type_names.push(self.format_type_name(FUNC::return_type_name()).into()); + param_type_names.push(self.format_param_type(FUNC::return_type_name()).into()); } let param_type_names = param_type_names @@ -754,12 +754,17 @@ impl Engine { let mut signatures = Vec::with_capacity(64); if let Some(global_namespace) = self.global_modules.first() { - signatures.extend(global_namespace.gen_fn_signatures()); + signatures.extend( + global_namespace.gen_fn_signatures_with_mapper(|s| self.format_param_type(s)), + ); } #[cfg(not(feature = "no_module"))] for (name, m) in &self.global_sub_modules { - signatures.extend(m.gen_fn_signatures().map(|f| format!("{name}::{f}"))); + signatures.extend( + m.gen_fn_signatures_with_mapper(|s| self.format_param_type(s)) + .map(|f| format!("{name}::{f}")), + ); } let exclude_flags = if include_packages { @@ -773,7 +778,7 @@ impl Engine { .iter() .skip(1) .filter(|m| !m.flags.intersects(exclude_flags)) - .flat_map(|m| m.gen_fn_signatures()), + .flat_map(|m| m.gen_fn_signatures_with_mapper(|s| self.format_param_type(s))), ); signatures diff --git a/src/module/mod.rs b/src/module/mod.rs index 3d479d225..860bc29e8 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -1,7 +1,7 @@ //! Module defining external-loaded modules for Rhai. #[cfg(feature = "metadata")] -use crate::api::formatting::format_type; +use crate::api::formatting::format_param_type_for_display; use crate::ast::FnAccess; use crate::func::{ shared_take_or_clone, FnIterator, RhaiFunc, RhaiNativeFunc, SendSync, StraightHashMap, @@ -101,10 +101,13 @@ impl FuncMetadata { /// Exported under the `metadata` feature only. #[cfg(feature = "metadata")] #[must_use] - pub fn gen_signature(&self) -> String { + pub fn gen_signature<'a>( + &'a self, + type_mapper: impl Fn(&'a str) -> std::borrow::Cow<'a, str>, + ) -> String { let mut signature = format!("{}(", self.name); - let return_type = format_type(&self.return_type, true); + let return_type = format_param_type_for_display(&self.return_type, true); if self.params_info.is_empty() { for x in 0..self.num_params { @@ -120,12 +123,18 @@ impl FuncMetadata { .map(|param| { let mut segment = param.splitn(2, ':'); let name = match segment.next().unwrap().trim() { - "" => "_", + "" => "_".into(), s => s, }; - let result: std::borrow::Cow = segment.next().map_or_else( + let result: std::borrow::Cow<_> = segment.next().map_or_else( || name.into(), - |typ| format!("{name}: {}", format_type(typ, false)).into(), + |typ| { + format!( + "{name}: {}", + format_param_type_for_display(&type_mapper(typ), false) + ) + .into() + }, ); result }) @@ -601,7 +610,7 @@ impl fmt::Debug for Module { #[cfg(not(feature = "metadata"))] return _f.to_string(); #[cfg(feature = "metadata")] - return _m.gen_signature(); + return _m.gen_signature(|s| s.into()); }) .collect::>(), ) @@ -1052,14 +1061,17 @@ impl Module { /// Exported under the `metadata` feature only. #[cfg(feature = "metadata")] #[inline] - pub fn gen_fn_signatures(&self) -> impl Iterator + '_ { + pub fn gen_fn_signatures_with_mapper<'a>( + &'a self, + type_mapper: impl Fn(&'a str) -> std::borrow::Cow<'a, str> + 'a, + ) -> impl Iterator + 'a { self.iter_fn() - .map(|(_, f)| f) + .map(|(_, m)| m) .filter(|&f| match f.access { FnAccess::Public => true, FnAccess::Private => false, }) - .map(FuncMetadata::gen_signature) + .map(move |m| m.gen_signature(&type_mapper)) } /// Does a variable exist in the [`Module`]? diff --git a/src/serde/metadata.rs b/src/serde/metadata.rs index 3f950b1b8..23d079657 100644 --- a/src/serde/metadata.rs +++ b/src/serde/metadata.rs @@ -1,7 +1,7 @@ //! Serialization of functions metadata. #![cfg(feature = "metadata")] -use crate::api::formatting::format_type; +use crate::api::formatting::format_param_type_for_display; use crate::func::RhaiFunc; use crate::module::{calc_native_fn_hash, FuncMetadata, ModuleFlags}; use crate::types::custom_types::CustomTypeInfo; @@ -151,12 +151,12 @@ impl<'a> From<(&'a RhaiFunc, &'a FuncMetadata)> for FnMetadata<'a> { "_" => None, s => Some(s), }; - let typ = seg.next().map(|s| format_type(s, false)); + let typ = seg.next().map(|s| format_param_type_for_display(s, false)); FnParam { name, typ } }) .collect(), - return_type: format_type(&m.return_type, true), - signature: m.gen_signature().into(), + return_type: format_param_type_for_display(&m.return_type, true), + signature: m.gen_signature(|s| s.into()).into(), doc_comments: if f.is_script() { #[cfg(feature = "no_function")] unreachable!("script-defined functions should not exist under no_function");