Skip to main content

rusteron_code_gen/
arg_classifier.rs

1//! Argument classification for generated wrapper methods.
2//!
3//! `classify_method_args` classifies each C argument once into a [`ClassifiedArg`] carrying all token
4//! fragments the emitters need (signature, FFI call, generics, retained-handler registration,
5//! and the `_fn` stack-closure variant). `generate_methods` then emits purely from the classification.
6
7use crate::generator::{is_sync_handler_type, Arg, ArgProcessing, CHandler, CWrapper, Method, ReturnType};
8use crate::snake_to_pascal_case;
9use proc_macro2::TokenStream;
10use quote::{format_ident, quote};
11use std::collections::BTreeMap;
12use syn::{parse_str, Type};
13
14/// Role of a C argument in the generated Rust wrapper method.
15///
16/// Each argument is classified once: is it `&self`? A `&OtherWrapper` param? A
17/// callback that must be retained? etc. The classification drives all emission
18/// (signature, FFI call, handler registration, and the `_fn` variant).
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum ArgRole {
21    /// `*mut <own type>` — becomes `&self` / `self.get_inner()`.
22    SelfPointer,
23    /// `*mut <other wrapper>` — `&OtherWrapper` param, `name.get_inner()` call.
24    WrapperPointer,
25    /// Callback invoked only during the FFI call — `Option<&Handler<T>>`, `_fn` capable.
26    HandlerSync,
27    /// Callback the C client retains — borrow style (`Option<&Handler<T>>`), registered
28    /// as a dependency of `self`.
29    HandlerRetainedBorrowed,
30    /// Callback the C client retains — owned setter style (`Option<T>`), heap-allocated in
31    /// a prelude, registered, and returned to the caller.
32    HandlerRetainedOwned,
33    /// clientd / length / merged arguments that emit nothing of their own.
34    Absorbed,
35    /// Everything else — handled by the existing `ReturnType` conversions.
36    Plain,
37}
38
39/// One argument, classified once, with every emission fragment precomputed.
40pub struct ClassifiedArg {
41    pub kind: ArgRole,
42    /// `name: Type` in the generated signature (None: not part of the signature).
43    pub signature: Option<TokenStream>,
44    /// Argument(s) for the FFI call (None: emits nothing).
45    pub call: Option<TokenStream>,
46    /// Generic bound contributed to the where clause.
47    pub generic: Option<TokenStream>,
48    /// Post-call dependency registration for retained handlers.
49    pub registration: Option<TokenStream>,
50    /// Statement(s) emitted before the FFI call (e.g. the `IntoCStr` shadow for
51    /// C-string args so the `Cow` outlives the call).
52    pub prelude: Option<TokenStream>,
53    /// `_fn` variant fragments (sync handlers swap Handler for a stack closure).
54    pub once_signature: Option<TokenStream>,
55    pub once_call: Option<TokenStream>,
56    pub once_generic: Option<TokenStream>,
57}
58
59pub struct ClassifiedMethodArgs {
60    pub uses_self: bool,
61    pub args: Vec<ClassifiedArg>,
62    /// The owned retained-handler argument, when this method is an owned setter.
63    pub owned_retained: Option<Arg>,
64    /// Every handler argument is sync — a `_fn` stack-closure variant is sound.
65    pub once_capable: bool,
66}
67
68impl ClassifiedMethodArgs {
69    pub fn signatures(&self) -> Vec<TokenStream> {
70        self.args.iter().filter_map(|a| a.signature.clone()).collect()
71    }
72
73    pub fn calls(&self) -> Vec<TokenStream> {
74        self.args.iter().filter_map(|a| a.call.clone()).collect()
75    }
76
77    pub fn generics(&self) -> Vec<TokenStream> {
78        self.args.iter().filter_map(|a| a.generic.clone()).collect()
79    }
80
81    pub fn registrations(&self) -> Vec<TokenStream> {
82        self.args.iter().filter_map(|a| a.registration.clone()).collect()
83    }
84
85    /// Statements emitted before the FFI call (e.g. `IntoCStr` shadows).
86    pub fn preludes(&self) -> Vec<TokenStream> {
87        self.args.iter().filter_map(|a| a.prelude.clone()).collect()
88    }
89
90    pub fn once_signatures(&self) -> Vec<TokenStream> {
91        self.args
92            .iter()
93            .filter_map(|a| a.once_signature.clone().or_else(|| a.signature.clone()))
94            .collect()
95    }
96
97    pub fn once_calls(&self) -> Vec<TokenStream> {
98        self.args
99            .iter()
100            .filter_map(|a| a.once_call.clone().or_else(|| a.call.clone()))
101            .collect()
102    }
103
104    pub fn once_generics(&self) -> Vec<TokenStream> {
105        self.args
106            .iter()
107            .filter_map(|a| a.once_generic.clone().or_else(|| a.generic.clone()))
108            .collect()
109    }
110}
111
112/// Classify every argument of `method` once. `owned_retained` mirrors the owned-setter
113/// rule: `&self` method, int return, exactly one retained handler, no `&mut` primitives.
114pub fn classify_method_args(
115    method: &Method,
116    own_type_name: &str,
117    wrappers: &BTreeMap<String, CWrapper>,
118    closure_handlers: &[CHandler],
119) -> ClassifiedMethodArgs {
120    let uses_self_precheck = method
121        .arguments
122        .iter()
123        .any(|arg| arg.is_single_mut_pointer() && arg.c_type.ends_with(own_type_name));
124    let handler_value_args: Vec<&Arg> = method
125        .arguments
126        .iter()
127        .filter(|a| matches!(a.processing, ArgProcessing::Handler(_)) && !a.is_mut_pointer())
128        .collect();
129    let has_mut_primitive = method.arguments.iter().any(|a| a.is_mut_pointer() && a.is_primitive());
130    let owned_retained: Option<Arg> = if uses_self_precheck
131        && !has_mut_primitive
132        && method.return_type.is_c_raw_int()
133        && handler_value_args.len() == 1
134    {
135        let a = handler_value_args[0];
136        match &a.processing {
137            ArgProcessing::Handler(hc) if !is_sync_handler_type(&hc[0].c_type) => Some(a.clone()),
138            _ => None,
139        }
140    } else {
141        None
142    };
143    let once_capable = !handler_value_args.is_empty()
144        && handler_value_args.iter().all(|arg| match &arg.processing {
145            ArgProcessing::Handler(hc) => is_sync_handler_type(&hc[0].c_type),
146            _ => false,
147        });
148
149    let mut uses_self = false;
150    let args = method
151        .arguments
152        .iter()
153        .map(|arg| {
154            classify_arg(
155                arg,
156                own_type_name,
157                wrappers,
158                closure_handlers,
159                &owned_retained,
160                &mut uses_self,
161            )
162        })
163        .collect();
164
165    ClassifiedMethodArgs {
166        uses_self,
167        args,
168        owned_retained,
169        once_capable,
170    }
171}
172
173fn classify_arg(
174    arg: &Arg,
175    own_type_name: &str,
176    wrappers: &BTreeMap<String, CWrapper>,
177    closure_handlers: &[CHandler],
178    owned_retained: &Option<Arg>,
179    uses_self: &mut bool,
180) -> ClassifiedArg {
181    let none = ClassifiedArg {
182        kind: ArgRole::Absorbed,
183        signature: None,
184        call: None,
185        generic: None,
186        registration: None,
187        prelude: None,
188        once_signature: None,
189        once_call: None,
190        once_generic: None,
191    };
192
193    // wrapper pointers (incl. self)
194    let pointee = if arg.is_single_mut_pointer() {
195        arg.c_type.split(' ').last().unwrap_or("notfound")
196    } else {
197        "notfound"
198    };
199    if let Some(matching_wrapper) = wrappers.get(pointee) {
200        let name = arg.as_ident();
201        if arg.c_type.ends_with(own_type_name) && matching_wrapper.type_name == own_type_name {
202            *uses_self = true;
203            return ClassifiedArg {
204                kind: ArgRole::SelfPointer,
205                call: Some(quote! { self.get_inner() }),
206                ..none
207            };
208        }
209        let arg_type = ReturnType::new(arg.clone(), wrappers.clone()).get_new_return_type(false, true);
210        return ClassifiedArg {
211            kind: ArgRole::WrapperPointer,
212            signature: if arg_type.is_empty() {
213                None
214            } else {
215                Some(quote! { #name: #arg_type })
216            },
217            call: Some(quote! { #name.get_inner() }),
218            ..none
219        };
220    }
221
222    // handler arguments (the callback side; the clientd side is Absorbed below)
223    if let ArgProcessing::Handler(handler_client) = &arg.processing {
224        if !arg.is_mut_pointer() {
225            return classify_handler_arg(arg, handler_client, wrappers, closure_handlers, owned_retained, none);
226        }
227        // clientd pointer: merged into the callback argument
228        return none;
229    }
230
231    // plain arguments (strings, byte arrays, primitives, out-params) — the existing
232    // ReturnType conversions already concentrate this knowledge
233    let name = arg.as_ident();
234    let rt = ReturnType::new(arg.clone(), wrappers.clone());
235    let arg_type = rt.get_new_return_type(false, true);
236    let call = rt.handle_rs_to_c_return(quote! { #name }, false);
237    ClassifiedArg {
238        kind: ArgRole::Plain,
239        signature: if arg_type.is_empty() {
240            None
241        } else {
242            Some(quote! { #name: #arg_type })
243        },
244        call: if call.is_empty() { None } else { Some(quote! { #call }) },
245        generic: rt.method_generics_for_where(false),
246        ..none
247    }
248}
249
250fn classify_handler_arg(
251    arg: &Arg,
252    handler_client: &[Arg],
253    wrappers: &BTreeMap<String, CWrapper>,
254    closure_handlers: &[CHandler],
255    owned_retained: &Option<Arg>,
256    none: ClassifiedArg,
257) -> ClassifiedArg {
258    let handler = handler_client.first().unwrap();
259    let name = arg.as_ident();
260    let raw_name = format_ident!("{}_raw", arg.name);
261    let shim = format_ident!("{}_callback", handler.c_type);
262    let once_shim = format_ident!("{}_callback_for_once_closure", handler.c_type);
263    let handler_type = handler.as_type();
264    let new_type =
265        parse_str::<Type>(&format!("{}HandlerImpl", snake_to_pascal_case(&arg.c_type))).expect("Invalid class name");
266    let sync = is_sync_handler_type(&handler.c_type);
267    let is_owned = owned_retained.as_ref().map(|o| o.name == arg.name).unwrap_or(false);
268    let rt = ReturnType::new(arg.clone(), wrappers.clone());
269
270    if is_owned {
271        // owned setter: Option<T> in, prelude heap-allocates, registration keeps it alive
272        return ClassifiedArg {
273            kind: ArgRole::HandlerRetainedOwned,
274            signature: Some(quote! { #name: Option<#new_type> }),
275            call: Some(quote! {
276                {
277                    let callback: #handler_type = if #raw_name.is_null() {
278                        None
279                    } else {
280                        Some(#shim::<#new_type>)
281                    };
282                    callback
283                },
284                #raw_name
285            }),
286            generic: rt.method_generics_for_where(false),
287            registration: Some(quote! {
288                if let Some(__handler) = &#name {
289                    if let Some(__inner) = self.inner.as_owned() {
290                        __inner.add_dependency(__handler.clone());
291                    }
292                }
293            }),
294            ..none
295        };
296    }
297
298    // borrow style: Option<&Handler<T>>
299    let borrow_call = quote! {
300        {
301            let callback: #handler_type = if #name.is_none() {
302                None
303            } else {
304                Some(#shim::<#new_type>)
305            };
306            callback
307        },
308        #name.map(|m| m.as_raw()).unwrap_or_else(|| std::ptr::null_mut())
309    };
310    if sync {
311        // the `_fn` variant swaps the Handler for a stack closure with the matching
312        // FnMut signature (from the handler's CHandler definition)
313        let fn_mut_sig = closure_handlers
314            .iter()
315            .find(|c| c.type_name == handler.c_type)
316            .map(|c| c.fn_mut_signature.clone())
317            .unwrap_or_else(|| quote! { FnMut() -> () });
318        ClassifiedArg {
319            kind: ArgRole::HandlerSync,
320            signature: Some(quote! { #name: Option<&Handler<#new_type>> }),
321            call: Some(borrow_call),
322            generic: rt.method_generics_for_where(false),
323            once_signature: Some(quote! { mut #name: #new_type }),
324            once_call: Some(quote! {
325                Some(#once_shim::<#new_type>),
326                &mut #name as *mut _ as *mut std::os::raw::c_void
327            }),
328            once_generic: Some(quote! { #new_type: #fn_mut_sig }),
329            ..none
330        }
331    } else {
332        ClassifiedArg {
333            kind: ArgRole::HandlerRetainedBorrowed,
334            signature: Some(quote! { #name: Option<&Handler<#new_type>> }),
335            call: Some(borrow_call),
336            generic: rt.method_generics_for_where(false),
337            registration: Some(quote! {
338                if let Some(__handler) = #name {
339                    if let Some(__inner) = self.inner.as_owned() {
340                        __inner.add_dependency(__handler.clone());
341                    }
342                }
343            }),
344            ..none
345        }
346    }
347}
348
349#[cfg(test)]
350mod tests {
351    use super::*;
352
353    fn arg(name: &str, c_type: &str, processing: ArgProcessing) -> Arg {
354        Arg {
355            name: name.to_string(),
356            c_type: c_type.to_string(),
357            processing,
358        }
359    }
360
361    fn handler_pair(name: &str, c_type: &str) -> Arg {
362        let cb = arg(name, c_type, ArgProcessing::Default);
363        let clientd = arg("clientd", "* mut :: std :: os :: raw :: c_void", ArgProcessing::Default);
364        arg(name, c_type, ArgProcessing::Handler(vec![cb, clientd]))
365    }
366
367    fn method(name: &str, ret: &str, args: Vec<Arg>) -> Method {
368        Method {
369            fn_name: format!("aeron_x_{name}"),
370            struct_method_name: name.to_string(),
371            return_type: arg("", ret, ArgProcessing::Default),
372            arguments: args,
373            docs: Default::default(),
374        }
375    }
376
377    #[test]
378    fn retained_handler_on_int_setter_is_owned() {
379        let m = method(
380            "set_error_handler",
381            ":: std :: os :: raw :: c_int",
382            vec![
383                arg("ctx", "* mut aeron_context_t", ArgProcessing::Default),
384                handler_pair("handler", "aeron_error_handler_t"),
385            ],
386        );
387        let mut wrappers = BTreeMap::new();
388        wrappers.insert(
389            "aeron_context_t".to_string(),
390            CWrapper {
391                type_name: "aeron_context_t".to_string(),
392                class_name: "AeronContext".to_string(),
393                ..Default::default()
394            },
395        );
396        let classified = classify_method_args(&m, "aeron_context_t", &wrappers, &[]);
397        assert!(classified.uses_self);
398        assert!(
399            classified.owned_retained.is_some(),
400            "error handler must be an owned setter"
401        );
402        assert!(
403            !classified.once_capable,
404            "retained callbacks must not get a _fn variant"
405        );
406        assert_eq!(classified.args[1].kind, ArgRole::HandlerRetainedOwned);
407        assert!(classified.args[1].registration.is_some());
408    }
409
410    #[test]
411    fn sync_handler_is_once_capable_and_not_registered() {
412        let m = method(
413            "poll",
414            ":: std :: os :: raw :: c_int",
415            vec![
416                arg("subscription", "* mut aeron_subscription_t", ArgProcessing::Default),
417                handler_pair("handler", "aeron_fragment_handler_t"),
418            ],
419        );
420        let mut wrappers = BTreeMap::new();
421        wrappers.insert(
422            "aeron_subscription_t".to_string(),
423            CWrapper {
424                type_name: "aeron_subscription_t".to_string(),
425                class_name: "AeronSubscription".to_string(),
426                ..Default::default()
427            },
428        );
429        let classified = classify_method_args(&m, "aeron_subscription_t", &wrappers, &[]);
430        assert!(classified.once_capable, "sync callbacks get a _fn variant");
431        assert!(classified.owned_retained.is_none());
432        assert_eq!(classified.args[1].kind, ArgRole::HandlerSync);
433        assert!(
434            classified.args[1].registration.is_none(),
435            "sync handlers are never retained"
436        );
437        assert!(classified.args[1].once_call.is_some());
438    }
439
440    #[test]
441    fn clientd_and_self_absorb_correctly() {
442        let m = method(
443            "poll",
444            ":: std :: os :: raw :: c_int",
445            vec![arg(
446                "subscription",
447                "* mut aeron_subscription_t",
448                ArgProcessing::Default,
449            )],
450        );
451        let mut wrappers = BTreeMap::new();
452        wrappers.insert(
453            "aeron_subscription_t".to_string(),
454            CWrapper {
455                type_name: "aeron_subscription_t".to_string(),
456                class_name: "AeronSubscription".to_string(),
457                ..Default::default()
458            },
459        );
460        let classified = classify_method_args(&m, "aeron_subscription_t", &wrappers, &[]);
461        assert_eq!(classified.args[0].kind, ArgRole::SelfPointer);
462        assert!(classified.args[0].signature.is_none());
463        assert_eq!(classified.signatures().len(), 0);
464        assert_eq!(classified.calls().len(), 1);
465    }
466}