rusteron-code-gen 0.2.0

Code generator for Aeron C bindings
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
//! Argument classification for generated wrapper methods.
//!
//! `classify_method_args` classifies each C argument once into a [`ClassifiedArg`] carrying all token
//! fragments the emitters need (signature, FFI call, generics, retained-handler registration,
//! and the `_fn` stack-closure variant). `generate_methods` then emits purely from the classification.

use crate::generator::{is_sync_handler_type, Arg, ArgProcessing, CHandler, CWrapper, Method, ReturnType};
use crate::snake_to_pascal_case;
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use std::collections::BTreeMap;
use syn::{parse_str, Type};

/// Role of a C argument in the generated Rust wrapper method.
///
/// Each argument is classified once: is it `&self`? A `&OtherWrapper` param? A
/// callback that must be retained? etc. The classification drives all emission
/// (signature, FFI call, handler registration, and the `_fn` variant).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ArgRole {
    /// `*mut <own type>` — becomes `&self` / `self.get_inner()`.
    SelfPointer,
    /// `*mut <other wrapper>` — `&OtherWrapper` param, `name.get_inner()` call.
    WrapperPointer,
    /// Callback invoked only during the FFI call — `Option<&Handler<T>>`, `_fn` capable.
    HandlerSync,
    /// Callback the C client retains — borrow style (`Option<&Handler<T>>`), registered
    /// as a dependency of `self`.
    HandlerRetainedBorrowed,
    /// Callback the C client retains — owned setter style (`Option<T>`), heap-allocated in
    /// a prelude, registered, and returned to the caller.
    HandlerRetainedOwned,
    /// clientd / length / merged arguments that emit nothing of their own.
    Absorbed,
    /// Everything else — handled by the existing `ReturnType` conversions.
    Plain,
}

/// One argument, classified once, with every emission fragment precomputed.
pub struct ClassifiedArg {
    pub kind: ArgRole,
    /// `name: Type` in the generated signature (None: not part of the signature).
    pub signature: Option<TokenStream>,
    /// Argument(s) for the FFI call (None: emits nothing).
    pub call: Option<TokenStream>,
    /// Generic bound contributed to the where clause.
    pub generic: Option<TokenStream>,
    /// Post-call dependency registration for retained handlers.
    pub registration: Option<TokenStream>,
    /// Statement(s) emitted before the FFI call (e.g. the `IntoCStr` shadow for
    /// C-string args so the `Cow` outlives the call).
    pub prelude: Option<TokenStream>,
    /// `_fn` variant fragments (sync handlers swap Handler for a stack closure).
    pub once_signature: Option<TokenStream>,
    pub once_call: Option<TokenStream>,
    pub once_generic: Option<TokenStream>,
}

pub struct ClassifiedMethodArgs {
    pub uses_self: bool,
    pub args: Vec<ClassifiedArg>,
    /// The owned retained-handler argument, when this method is an owned setter.
    pub owned_retained: Option<Arg>,
    /// Every handler argument is sync — a `_fn` stack-closure variant is sound.
    pub once_capable: bool,
}

impl ClassifiedMethodArgs {
    pub fn signatures(&self) -> Vec<TokenStream> {
        self.args.iter().filter_map(|a| a.signature.clone()).collect()
    }

    pub fn calls(&self) -> Vec<TokenStream> {
        self.args.iter().filter_map(|a| a.call.clone()).collect()
    }

    pub fn generics(&self) -> Vec<TokenStream> {
        self.args.iter().filter_map(|a| a.generic.clone()).collect()
    }

    pub fn registrations(&self) -> Vec<TokenStream> {
        self.args.iter().filter_map(|a| a.registration.clone()).collect()
    }

    /// Statements emitted before the FFI call (e.g. `IntoCStr` shadows).
    pub fn preludes(&self) -> Vec<TokenStream> {
        self.args.iter().filter_map(|a| a.prelude.clone()).collect()
    }

    pub fn once_signatures(&self) -> Vec<TokenStream> {
        self.args
            .iter()
            .filter_map(|a| a.once_signature.clone().or_else(|| a.signature.clone()))
            .collect()
    }

    pub fn once_calls(&self) -> Vec<TokenStream> {
        self.args
            .iter()
            .filter_map(|a| a.once_call.clone().or_else(|| a.call.clone()))
            .collect()
    }

    pub fn once_generics(&self) -> Vec<TokenStream> {
        self.args
            .iter()
            .filter_map(|a| a.once_generic.clone().or_else(|| a.generic.clone()))
            .collect()
    }
}

/// Classify every argument of `method` once. `owned_retained` mirrors the owned-setter
/// rule: `&self` method, int return, exactly one retained handler, no `&mut` primitives.
pub fn classify_method_args(
    method: &Method,
    own_type_name: &str,
    wrappers: &BTreeMap<String, CWrapper>,
    closure_handlers: &[CHandler],
) -> ClassifiedMethodArgs {
    let uses_self_precheck = method
        .arguments
        .iter()
        .any(|arg| arg.is_single_mut_pointer() && arg.c_type.ends_with(own_type_name));
    let handler_value_args: Vec<&Arg> = method
        .arguments
        .iter()
        .filter(|a| matches!(a.processing, ArgProcessing::Handler(_)) && !a.is_mut_pointer())
        .collect();
    let has_mut_primitive = method.arguments.iter().any(|a| a.is_mut_pointer() && a.is_primitive());
    let owned_retained: Option<Arg> = if uses_self_precheck
        && !has_mut_primitive
        && method.return_type.is_c_raw_int()
        && handler_value_args.len() == 1
    {
        let a = handler_value_args[0];
        match &a.processing {
            ArgProcessing::Handler(hc) if !is_sync_handler_type(&hc[0].c_type) => Some(a.clone()),
            _ => None,
        }
    } else {
        None
    };
    let once_capable = !handler_value_args.is_empty()
        && handler_value_args.iter().all(|arg| match &arg.processing {
            ArgProcessing::Handler(hc) => is_sync_handler_type(&hc[0].c_type),
            _ => false,
        });

    let mut uses_self = false;
    let args = method
        .arguments
        .iter()
        .map(|arg| {
            classify_arg(
                arg,
                own_type_name,
                wrappers,
                closure_handlers,
                &owned_retained,
                &mut uses_self,
            )
        })
        .collect();

    ClassifiedMethodArgs {
        uses_self,
        args,
        owned_retained,
        once_capable,
    }
}

fn classify_arg(
    arg: &Arg,
    own_type_name: &str,
    wrappers: &BTreeMap<String, CWrapper>,
    closure_handlers: &[CHandler],
    owned_retained: &Option<Arg>,
    uses_self: &mut bool,
) -> ClassifiedArg {
    let none = ClassifiedArg {
        kind: ArgRole::Absorbed,
        signature: None,
        call: None,
        generic: None,
        registration: None,
        prelude: None,
        once_signature: None,
        once_call: None,
        once_generic: None,
    };

    // wrapper pointers (incl. self)
    let pointee = if arg.is_single_mut_pointer() {
        arg.c_type.split(' ').last().unwrap_or("notfound")
    } else {
        "notfound"
    };
    if let Some(matching_wrapper) = wrappers.get(pointee) {
        let name = arg.as_ident();
        if arg.c_type.ends_with(own_type_name) && matching_wrapper.type_name == own_type_name {
            *uses_self = true;
            return ClassifiedArg {
                kind: ArgRole::SelfPointer,
                call: Some(quote! { self.get_inner() }),
                ..none
            };
        }
        let arg_type = ReturnType::new(arg.clone(), wrappers.clone()).get_new_return_type(false, true);
        return ClassifiedArg {
            kind: ArgRole::WrapperPointer,
            signature: if arg_type.is_empty() {
                None
            } else {
                Some(quote! { #name: #arg_type })
            },
            call: Some(quote! { #name.get_inner() }),
            ..none
        };
    }

    // handler arguments (the callback side; the clientd side is Absorbed below)
    if let ArgProcessing::Handler(handler_client) = &arg.processing {
        if !arg.is_mut_pointer() {
            return classify_handler_arg(arg, handler_client, wrappers, closure_handlers, owned_retained, none);
        }
        // clientd pointer: merged into the callback argument
        return none;
    }

    // plain arguments (strings, byte arrays, primitives, out-params) — the existing
    // ReturnType conversions already concentrate this knowledge
    let name = arg.as_ident();
    let rt = ReturnType::new(arg.clone(), wrappers.clone());
    let arg_type = rt.get_new_return_type(false, true);
    let call = rt.handle_rs_to_c_return(quote! { #name }, false);
    ClassifiedArg {
        kind: ArgRole::Plain,
        signature: if arg_type.is_empty() {
            None
        } else {
            Some(quote! { #name: #arg_type })
        },
        call: if call.is_empty() { None } else { Some(quote! { #call }) },
        generic: rt.method_generics_for_where(false),
        ..none
    }
}

fn classify_handler_arg(
    arg: &Arg,
    handler_client: &[Arg],
    wrappers: &BTreeMap<String, CWrapper>,
    closure_handlers: &[CHandler],
    owned_retained: &Option<Arg>,
    none: ClassifiedArg,
) -> ClassifiedArg {
    let handler = handler_client.first().unwrap();
    let name = arg.as_ident();
    let raw_name = format_ident!("{}_raw", arg.name);
    let shim = format_ident!("{}_callback", handler.c_type);
    let once_shim = format_ident!("{}_callback_for_once_closure", handler.c_type);
    let handler_type = handler.as_type();
    let new_type =
        parse_str::<Type>(&format!("{}HandlerImpl", snake_to_pascal_case(&arg.c_type))).expect("Invalid class name");
    let sync = is_sync_handler_type(&handler.c_type);
    let is_owned = owned_retained.as_ref().map(|o| o.name == arg.name).unwrap_or(false);
    let rt = ReturnType::new(arg.clone(), wrappers.clone());

    if is_owned {
        // owned setter: Option<T> in, prelude heap-allocates, registration keeps it alive
        return ClassifiedArg {
            kind: ArgRole::HandlerRetainedOwned,
            signature: Some(quote! { #name: Option<#new_type> }),
            call: Some(quote! {
                {
                    let callback: #handler_type = if #raw_name.is_null() {
                        None
                    } else {
                        Some(#shim::<#new_type>)
                    };
                    callback
                },
                #raw_name
            }),
            generic: rt.method_generics_for_where(false),
            registration: Some(quote! {
                if let Some(__handler) = &#name {
                    if let Some(__inner) = self.inner.as_owned() {
                        __inner.add_dependency(__handler.clone());
                    }
                }
            }),
            ..none
        };
    }

    // borrow style: Option<&Handler<T>>
    let borrow_call = quote! {
        {
            let callback: #handler_type = if #name.is_none() {
                None
            } else {
                Some(#shim::<#new_type>)
            };
            callback
        },
        #name.map(|m| m.as_raw()).unwrap_or_else(|| std::ptr::null_mut())
    };
    if sync {
        // the `_fn` variant swaps the Handler for a stack closure with the matching
        // FnMut signature (from the handler's CHandler definition)
        let fn_mut_sig = closure_handlers
            .iter()
            .find(|c| c.type_name == handler.c_type)
            .map(|c| c.fn_mut_signature.clone())
            .unwrap_or_else(|| quote! { FnMut() -> () });
        ClassifiedArg {
            kind: ArgRole::HandlerSync,
            signature: Some(quote! { #name: Option<&Handler<#new_type>> }),
            call: Some(borrow_call),
            generic: rt.method_generics_for_where(false),
            once_signature: Some(quote! { mut #name: #new_type }),
            once_call: Some(quote! {
                Some(#once_shim::<#new_type>),
                &mut #name as *mut _ as *mut std::os::raw::c_void
            }),
            once_generic: Some(quote! { #new_type: #fn_mut_sig }),
            ..none
        }
    } else {
        ClassifiedArg {
            kind: ArgRole::HandlerRetainedBorrowed,
            signature: Some(quote! { #name: Option<&Handler<#new_type>> }),
            call: Some(borrow_call),
            generic: rt.method_generics_for_where(false),
            registration: Some(quote! {
                if let Some(__handler) = #name {
                    if let Some(__inner) = self.inner.as_owned() {
                        __inner.add_dependency(__handler.clone());
                    }
                }
            }),
            ..none
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn arg(name: &str, c_type: &str, processing: ArgProcessing) -> Arg {
        Arg {
            name: name.to_string(),
            c_type: c_type.to_string(),
            processing,
        }
    }

    fn handler_pair(name: &str, c_type: &str) -> Arg {
        let cb = arg(name, c_type, ArgProcessing::Default);
        let clientd = arg("clientd", "* mut :: std :: os :: raw :: c_void", ArgProcessing::Default);
        arg(name, c_type, ArgProcessing::Handler(vec![cb, clientd]))
    }

    fn method(name: &str, ret: &str, args: Vec<Arg>) -> Method {
        Method {
            fn_name: format!("aeron_x_{name}"),
            struct_method_name: name.to_string(),
            return_type: arg("", ret, ArgProcessing::Default),
            arguments: args,
            docs: Default::default(),
        }
    }

    #[test]
    fn retained_handler_on_int_setter_is_owned() {
        let m = method(
            "set_error_handler",
            ":: std :: os :: raw :: c_int",
            vec![
                arg("ctx", "* mut aeron_context_t", ArgProcessing::Default),
                handler_pair("handler", "aeron_error_handler_t"),
            ],
        );
        let mut wrappers = BTreeMap::new();
        wrappers.insert(
            "aeron_context_t".to_string(),
            CWrapper {
                type_name: "aeron_context_t".to_string(),
                class_name: "AeronContext".to_string(),
                ..Default::default()
            },
        );
        let classified = classify_method_args(&m, "aeron_context_t", &wrappers, &[]);
        assert!(classified.uses_self);
        assert!(
            classified.owned_retained.is_some(),
            "error handler must be an owned setter"
        );
        assert!(
            !classified.once_capable,
            "retained callbacks must not get a _fn variant"
        );
        assert_eq!(classified.args[1].kind, ArgRole::HandlerRetainedOwned);
        assert!(classified.args[1].registration.is_some());
    }

    #[test]
    fn sync_handler_is_once_capable_and_not_registered() {
        let m = method(
            "poll",
            ":: std :: os :: raw :: c_int",
            vec![
                arg("subscription", "* mut aeron_subscription_t", ArgProcessing::Default),
                handler_pair("handler", "aeron_fragment_handler_t"),
            ],
        );
        let mut wrappers = BTreeMap::new();
        wrappers.insert(
            "aeron_subscription_t".to_string(),
            CWrapper {
                type_name: "aeron_subscription_t".to_string(),
                class_name: "AeronSubscription".to_string(),
                ..Default::default()
            },
        );
        let classified = classify_method_args(&m, "aeron_subscription_t", &wrappers, &[]);
        assert!(classified.once_capable, "sync callbacks get a _fn variant");
        assert!(classified.owned_retained.is_none());
        assert_eq!(classified.args[1].kind, ArgRole::HandlerSync);
        assert!(
            classified.args[1].registration.is_none(),
            "sync handlers are never retained"
        );
        assert!(classified.args[1].once_call.is_some());
    }

    #[test]
    fn clientd_and_self_absorb_correctly() {
        let m = method(
            "poll",
            ":: std :: os :: raw :: c_int",
            vec![arg(
                "subscription",
                "* mut aeron_subscription_t",
                ArgProcessing::Default,
            )],
        );
        let mut wrappers = BTreeMap::new();
        wrappers.insert(
            "aeron_subscription_t".to_string(),
            CWrapper {
                type_name: "aeron_subscription_t".to_string(),
                class_name: "AeronSubscription".to_string(),
                ..Default::default()
            },
        );
        let classified = classify_method_args(&m, "aeron_subscription_t", &wrappers, &[]);
        assert_eq!(classified.args[0].kind, ArgRole::SelfPointer);
        assert!(classified.args[0].signature.is_none());
        assert_eq!(classified.signatures().len(), 0);
        assert_eq!(classified.calls().len(), 1);
    }
}