Skip to content

Commit

Permalink
Format function signatures.
Browse files Browse the repository at this point in the history
  • Loading branch information
schungx committed Jan 30, 2024
1 parent 31b0760 commit 8359732
Show file tree
Hide file tree
Showing 8 changed files with 88 additions and 70 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
-------------------------------
Expand Down Expand Up @@ -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
Expand Down
23 changes: 10 additions & 13 deletions examples/custom_types_and_methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<EvalAltResult>> {
#[derive(Debug, Clone)]
#[derive(Debug, Clone, CustomType)]
#[rhai_type(extra = Self::build_extra)]
struct TestStruct {
x: i64,
}
Expand All @@ -24,22 +25,18 @@ fn main() -> Result<(), Box<EvalAltResult>> {
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<Self>) {
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>("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::<TestStruct>();

#[cfg(feature = "metadata")]
{
Expand Down
2 changes: 1 addition & 1 deletion src/api/definitions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
18 changes: 17 additions & 1 deletion src/api/deprecated.rs
Original file line number Diff line number Diff line change
Expand Up @@ -551,7 +551,7 @@ impl Position {
}

#[allow(deprecated)]
impl<T: Variant + Clone> TypeBuilder<'_, '_, T> {
impl<T: Variant + Clone> TypeBuilder<'_, T> {
/// Register a custom fallible function.
///
/// # Deprecated
Expand Down Expand Up @@ -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<Item = String> + '_ {
self.gen_fn_signatures_with_mapper(|s| s.into())
}
}

#[cfg(not(feature = "no_index"))]
Expand Down
58 changes: 22 additions & 36 deletions src/api/formatting.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<crate::INT>(),
#[cfg(not(feature = "no_float"))]
"FLOAT" => return type_name::<crate::FLOAT>(),
_ => (),
}

if name == type_name::<String>() {
return if shorthands { "string" } else { "String" };
}
Expand Down Expand Up @@ -111,15 +118,15 @@ 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.
/// * `&mut` is cleared.
/// * `INT` and `FLOAT` are expanded.
/// * [`RhaiResult`][crate::RhaiResult] and [`RhaiResultOf<T>`][crate::RhaiResultOf] are expanded.
#[cfg(feature = "metadata")]
pub fn format_type(typ: &str, is_return_type: bool) -> std::borrow::Cow<str> {
pub fn format_param_type_for_display(typ: &str, is_return_type: bool) -> std::borrow::Cow<str> {
const RESULT_TYPE: &str = "Result<";
const ERROR_TYPE: &str = ",Box<EvalAltResult>>";
const RHAI_RESULT_TYPE: &str = "RhaiResult";
Expand All @@ -136,17 +143,17 @@ pub fn format_type(typ: &str, is_return_type: bool) -> std::borrow::Cow<str> {
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 {
format!("&mut {r}").into()
};
} 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();
}

Expand All @@ -165,25 +172,25 @@ pub fn format_type(typ: &str, is_return_type: bool) -> std::borrow::Cow<str> {
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(),
Expand All @@ -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 {
Expand All @@ -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::<crate::INT>(),
#[cfg(not(feature = "no_float"))]
"FLOAT" => type_name::<crate::FLOAT>(),
_ => map_std_type_name(name, false),
})
.into()
self.map_type_name(name).into()
}

/// Make a `Box<`[`EvalAltResult<ErrorMismatchDataType>`][ERR::ErrorMismatchDataType]`>`.
Expand Down
15 changes: 10 additions & 5 deletions src/api/register.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<crate::FnArgsVec<_>>();

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
Expand Down Expand Up @@ -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 {
Expand All @@ -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
Expand Down
32 changes: 22 additions & 10 deletions src/module/mod.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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 {
Expand All @@ -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<str> = 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
})
Expand Down Expand Up @@ -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::<Vec<_>>(),
)
Expand Down Expand Up @@ -1052,14 +1061,17 @@ impl Module {
/// Exported under the `metadata` feature only.
#[cfg(feature = "metadata")]
#[inline]
pub fn gen_fn_signatures(&self) -> impl Iterator<Item = String> + '_ {
pub fn gen_fn_signatures_with_mapper<'a>(
&'a self,
type_mapper: impl Fn(&'a str) -> std::borrow::Cow<'a, str> + 'a,
) -> impl Iterator<Item = String> + '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`]?
Expand Down
8 changes: 4 additions & 4 deletions src/serde/metadata.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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");
Expand Down

0 comments on commit 8359732

Please sign in to comment.