From 71f7137d8df824323439912af7477634907c5229 Mon Sep 17 00:00:00 2001 From: Xenira <1288524+Xenira@users.noreply.github.com> Date: Mon, 14 Jul 2025 18:43:58 +0200 Subject: [PATCH] feat(stubs): add stubs for `RustClosure` Fixes: #373 --- src/args.rs | 1 + src/describe/mod.rs | 69 +++++++++++++++++++++++++++++++++++++++----- src/describe/stub.rs | 4 +++ 3 files changed, 67 insertions(+), 7 deletions(-) diff --git a/src/args.rs b/src/args.rs index 187e6fbade..b6bebb5ff1 100644 --- a/src/args.rs +++ b/src/args.rs @@ -203,6 +203,7 @@ impl From> for Parameter { name: val.name.into(), ty: Some(val.r#type).into(), nullable: val.allow_null, + variadic: val.variadic, default: val.default_value.map(abi::RString::from).into(), } } diff --git a/src/describe/mod.rs b/src/describe/mod.rs index 8abf2592e1..fe22acd9bb 100644 --- a/src/describe/mod.rs +++ b/src/describe/mod.rs @@ -76,6 +76,17 @@ pub struct Module { impl From> for Module { fn from(builder: ModuleBuilder) -> Self { let functions = builder.functions; + + #[allow(unused_mut)] + let mut classes = builder + .classes + .into_iter() + .map(|c| c().into()) + .collect::>(); + + #[cfg(feature = "closure")] + classes.push(Class::closure()); + Self { name: builder.name.into(), functions: functions @@ -83,12 +94,7 @@ impl From> for Module { .map(Function::from) .collect::>() .into(), - classes: builder - .classes - .into_iter() - .map(|c| c().into()) - .collect::>() - .into(), + classes: classes.into(), constants: builder .constants .into_iter() @@ -151,6 +157,8 @@ pub struct Parameter { pub ty: Option, /// Whether the parameter is nullable. pub nullable: bool, + /// Whether the parameter is variadic. + pub variadic: bool, /// Default value of the parameter. pub default: Option, } @@ -175,6 +183,43 @@ pub struct Class { pub constants: Vec, } +#[cfg(feature = "closure")] +impl Class { + /// Creates a new class representing a Rust closure used for generating + /// the stubs if the `closure` feature is enabled. + #[must_use] + pub fn closure() -> Self { + Self { + name: "RustClosure".into(), + docs: DocBlock(StdVec::new().into()), + extends: Option::None, + implements: StdVec::new().into(), + properties: StdVec::new().into(), + methods: vec![Method { + name: "__invoke".into(), + docs: DocBlock(StdVec::new().into()), + ty: MethodType::Member, + params: vec![Parameter { + name: "args".into(), + ty: Option::Some(DataType::Mixed), + nullable: false, + variadic: true, + default: Option::None, + }] + .into(), + retval: Option::Some(Retval { + ty: DataType::Mixed, + nullable: false, + }), + r#static: false, + visibility: Visibility::Public, + }] + .into(), + constants: StdVec::new().into(), + } + } +} + impl From for Class { fn from(val: ClassBuilder) -> Self { Self { @@ -426,6 +471,8 @@ impl From<(String, Box, DocComments)> for Constant { #[cfg(test)] mod tests { #![cfg_attr(windows, feature(abi_vectorcall))] + use cfg_if::cfg_if; + use super::*; use crate::{args::Arg, test::test_function}; @@ -460,7 +507,13 @@ mod tests { let module: Module = builder.into(); assert_eq!(module.name, "test".into()); assert_eq!(module.functions.len(), 1); - assert_eq!(module.classes.len(), 0); + cfg_if! { + if #[cfg(feature = "closure")] { + assert_eq!(module.classes.len(), 1); + } else { + assert_eq!(module.classes.len(), 0); + } + } assert_eq!(module.constants.len(), 0); } @@ -479,6 +532,7 @@ mod tests { name: "foo".into(), ty: Option::Some(DataType::Long), nullable: false, + variadic: false, default: Option::None, }] .into() @@ -568,6 +622,7 @@ mod tests { name: "foo".into(), ty: Option::Some(DataType::Long), nullable: false, + variadic: false, default: Option::None, }] .into() diff --git a/src/describe/stub.rs b/src/describe/stub.rs index 58c7428743..d0bff00d9b 100644 --- a/src/describe/stub.rs +++ b/src/describe/stub.rs @@ -154,6 +154,10 @@ impl ToStub for Parameter { write!(buf, " ")?; } + if self.variadic { + write!(buf, "...")?; + } + write!(buf, "${}", self.name) } }