Skip to main content

rusteron_code_gen/
generator.rs

1use crate::get_possible_wrappers;
2#[allow(unused_imports)]
3use crate::snake_to_pascal_case;
4use itertools::Itertools;
5use proc_macro2::{Ident, TokenStream};
6use quote::{ToTokens, format_ident, quote};
7use std::collections::{BTreeMap, BTreeSet, HashMap};
8use std::ops::Deref;
9use std::str::FromStr;
10use syn::{ImplItem, Item, Type, parse_str};
11
12pub const COMMON_CODE: &str = include_str!("common.rs");
13pub const CLIENT_BINDINGS: &str = include_str!("../bindings/client.rs");
14pub const ARCHIVE_BINDINGS: &str = include_str!("../bindings/archive.rs");
15pub const MEDIA_DRIVER_BINDINGS: &str = include_str!("../bindings/media-driver.rs");
16
17#[derive(Debug, Clone, Default)]
18pub struct CBinding {
19    pub wrappers: BTreeMap<String, CWrapper>,
20    pub methods: Vec<Method>,
21    pub handlers: Vec<CHandler>,
22}
23
24#[derive(Debug, Clone, Eq, PartialEq)]
25pub struct Method {
26    pub fn_name: String,
27    pub struct_method_name: String,
28    pub return_type: Arg,
29    pub arguments: Vec<Arg>,
30    pub docs: BTreeSet<String>,
31}
32
33#[derive(Debug, Clone, Eq, PartialEq)]
34pub enum ArgProcessing {
35    Handler(Vec<Arg>),
36    StringWithLength(Vec<Arg>),
37    ByteArrayWithLength(Vec<Arg>),
38    Default,
39}
40
41/// Callbacks Aeron invokes only during the FFI call (e.g. `poll`'s fragment handler).
42/// The generator emits a `_fn` stack-closure variant for these.
43///
44/// Callbacks NOT listed are retained by Aeron and invoked later (conductor thread);
45/// they need a heap `Handler` registered as a dependency, and get no `_fn` variant.
46const SYNC_HANDLER_TYPES: &[&str] = &[
47    "aeron_fragment_handler_t",
48    "aeron_controlled_fragment_handler_t",
49    "aeron_block_handler_t",
50    "aeron_error_log_reader_func_t",
51    "aeron_loss_reporter_read_entry_func_t",
52    "aeron_uri_parse_callback_t",
53    "aeron_reserved_value_supplier_t",
54    "aeron_counters_reader_foreach_counter_func_t",
55    "aeron_counters_reader_foreach_metadata_func_t",
56    "aeron_str_to_ptr_hash_map_for_each_func_t",
57    "aeron_rb_handler_t",
58    "aeron_rb_controlled_handler_t",
59    "aeron_queue_drain_func_t",
60    "aeron_term_gap_scanner_on_gap_detected_func_t",
61    "aeron_archive_recording_descriptor_consumer_func_t",
62    "aeron_archive_recording_subscription_descriptor_consumer_func_t",
63];
64
65pub fn is_sync_handler_type(c_type: &str) -> bool {
66    SYNC_HANDLER_TYPES.iter().any(|t| c_type.contains(t))
67}
68
69#[derive(Debug, Clone, Eq, PartialEq)]
70pub struct Arg {
71    pub name: String,
72    pub c_type: String,
73    pub processing: ArgProcessing,
74}
75
76impl Arg {
77    pub fn is_primitive(&self) -> bool {
78        static PRIMITIVE_TYPES: &[&str] = &[
79            "i64", "u64", "f32", "f64", "i32", "i16", "u32", "u16", "bool", "usize", "isize", "c_int",
80        ];
81        PRIMITIVE_TYPES.iter().any(|&f| self.c_type.ends_with(f))
82    }
83}
84
85impl Arg {
86    const C_INT_RETURN_TYPE_STR: &'static str = ":: std :: os :: raw :: c_int";
87    const C_CHAR_STR: &'static str = "* const :: std :: os :: raw :: c_char";
88    const C_MUT_CHAR_STR: &'static str = "* mut :: std :: os :: raw :: c_char";
89    const C_BYTE_ARRAY: &'static str = "* const u8";
90    const C_BYTE_MUT_ARRAY: &'static str = "* mut u8";
91    const STAR_MUT: &'static str = "* mut";
92    const DOUBLE_STAR_MUT: &'static str = "* mut * mut";
93    const C_VOID: &'static str = "* mut :: std :: os :: raw :: c_void";
94
95    pub fn is_any_pointer(&self) -> bool {
96        self.c_type.starts_with("* const") || self.c_type.starts_with("* mut")
97    }
98
99    pub fn is_c_string(&self) -> bool {
100        self.c_type == Self::C_CHAR_STR
101    }
102
103    pub fn is_c_string_any(&self) -> bool {
104        self.is_c_string() || self.is_mut_c_string()
105    }
106
107    pub fn is_mut_c_string(&self) -> bool {
108        self.c_type == Self::C_MUT_CHAR_STR
109    }
110
111    pub fn is_usize(&self) -> bool {
112        self.c_type == "usize"
113    }
114
115    pub fn is_byte_array(&self) -> bool {
116        self.c_type == Self::C_BYTE_ARRAY || self.c_type == Self::C_BYTE_MUT_ARRAY
117    }
118
119    pub fn is_mut_byte_array(&self) -> bool {
120        self.c_type == Self::C_BYTE_MUT_ARRAY
121    }
122
123    pub fn is_c_raw_int(&self) -> bool {
124        self.c_type == Self::C_INT_RETURN_TYPE_STR
125    }
126
127    pub fn is_mut_pointer(&self) -> bool {
128        self.c_type.starts_with(Self::STAR_MUT)
129    }
130
131    pub fn is_double_mut_pointer(&self) -> bool {
132        self.c_type.starts_with(Self::DOUBLE_STAR_MUT)
133    }
134
135    pub fn is_single_mut_pointer(&self) -> bool {
136        self.is_mut_pointer() && !self.is_double_mut_pointer()
137    }
138
139    pub fn is_c_void(&self) -> bool {
140        self.c_type == Self::C_VOID
141    }
142}
143
144impl Deref for Arg {
145    type Target = str;
146
147    fn deref(&self) -> &Self::Target {
148        &self.c_type
149    }
150}
151
152impl Arg {
153    pub fn as_ident(&self) -> Ident {
154        Ident::new(&self.name, proc_macro2::Span::call_site())
155    }
156
157    pub fn as_type(&self) -> Type {
158        parse_str(&self.c_type).expect("Invalid argument type")
159    }
160}
161
162#[derive(Debug, Clone)]
163pub struct CHandler {
164    pub type_name: String,
165    pub args: Vec<Arg>,
166    pub return_type: Arg,
167    pub docs: BTreeSet<String>,
168    pub fn_mut_signature: TokenStream,
169    pub closure_type_name: TokenStream,
170}
171
172#[derive(Debug, Clone)]
173pub struct ReturnType {
174    original: Arg,
175    wrappers: BTreeMap<String, CWrapper>,
176}
177
178impl ReturnType {
179    pub fn new(original_c_type: Arg, wrappers: BTreeMap<String, CWrapper>) -> Self {
180        ReturnType {
181            original: original_c_type,
182            wrappers,
183        }
184    }
185
186    pub fn get_new_return_type(&self, convert_errors: bool, use_ref_for_cwrapper: bool) -> TokenStream {
187        if let ArgProcessing::Handler(_) = self.original.processing {
188            if self.original.name.len() > 0 {
189                if !self.original.is_mut_pointer() {
190                    let new_type =
191                        parse_str::<Type>(&format!("{}HandlerImpl", snake_to_pascal_case(&self.original.c_type)))
192                            .expect("Invalid class name in wrapper");
193                    return quote! { Option<&Handler<#new_type>> };
194                } else {
195                    return quote! {};
196                }
197            }
198        } else if let ArgProcessing::StringWithLength(_) = self.original.processing {
199            if self.original.name.len() > 0 {
200                if self.original.is_c_string() {
201                    return quote! { &str };
202                } else if self.original.is_mut_c_string() {
203                    // snprintf-style fill buffer: `&mut [u8]` as arg, `&str` as field.
204                    if use_ref_for_cwrapper {
205                        return quote! { &mut [u8] };
206                    } else {
207                        return quote! { &str };
208                    }
209                } else {
210                    return quote! {};
211                }
212            }
213        } else if let ArgProcessing::ByteArrayWithLength(_) = self.original.processing {
214            if self.original.name.len() > 0 {
215                if self.original.is_byte_array() {
216                    if self.original.is_mut_byte_array() {
217                        return quote! { &mut [u8] };
218                    } else {
219                        return quote! { &[u8] };
220                    }
221                } else {
222                    return quote! {};
223                }
224            }
225        }
226
227        if self.original.is_single_mut_pointer() {
228            let type_name = self.original.split(" ").last().unwrap();
229            if let Some(wrapper) = self.wrappers.get(type_name) {
230                let new_type = parse_str::<Type>(&wrapper.class_name).expect("Invalid class name in wrapper");
231                if use_ref_for_cwrapper {
232                    return quote! { &#new_type };
233                } else {
234                    return quote! { #new_type };
235                }
236            }
237        }
238        if let Some(wrapper) = self.wrappers.get(&self.original.c_type) {
239            let new_type = parse_str::<Type>(&wrapper.class_name).expect("Invalid class name in wrapper");
240            return quote! { #new_type };
241        }
242        if convert_errors && self.original.is_c_raw_int() {
243            return quote! { Result<i32, AeronCError> };
244        }
245        if self.original.is_c_string() {
246            // if incoming argument use &CString
247            if !convert_errors && use_ref_for_cwrapper {
248                return quote! { &std::ffi::CStr };
249            } else {
250                return quote! { &str };
251            }
252        }
253        let return_type: Type = parse_str(&self.original).expect("Invalid return type");
254        if self.original.is_single_mut_pointer() && self.original.is_primitive() {
255            let mut_type: Type =
256                parse_str(&return_type.to_token_stream().to_string().replace("* mut ", "&mut ")).unwrap();
257            return quote! { #mut_type };
258        }
259        quote! { #return_type }
260    }
261
262    pub fn handle_c_to_rs_return(&self, result: TokenStream, convert_errors: bool, use_self: bool) -> TokenStream {
263        if let ArgProcessing::StringWithLength(args) = &self.original.processing {
264            if !self.original.is_c_string_any() {
265                return quote! {};
266            }
267            // Field read: text + stored length (buffer need not be NUL-terminated).
268            let string_ptr = &args[0].as_ident();
269            let length = &args[1].as_ident();
270            let me = if use_self {
271                quote! {self.}
272            } else {
273                quote! {}
274            };
275            return quote! {
276                if #me #string_ptr.is_null() { "" } else { unsafe { std::str::from_utf8_unchecked(std::slice::from_raw_parts(#me #string_ptr as *const u8, #me #length.try_into().unwrap())) } }
277            };
278        }
279        if let ArgProcessing::ByteArrayWithLength(args) = &self.original.processing {
280            if !self.original.is_byte_array() {
281                return quote! {};
282            } else {
283                let star_const = &args[0].as_ident();
284                let length = &args[1].as_ident();
285                let me = if use_self {
286                    quote! {self.}
287                } else {
288                    quote! {}
289                };
290                if self.original.is_mut_byte_array() {
291                    return quote! {
292                        unsafe { if #me #star_const.is_null() { &mut [] as &mut [_]  } else {std::slice::from_raw_parts_mut(#me #star_const, #me #length.try_into().unwrap()) } }
293                    };
294                } else {
295                    return quote! {
296                        if #me #star_const.is_null() { &[] as &[_]  } else { std::slice::from_raw_parts(#me #star_const, #me #length.try_into().unwrap()) }
297                    };
298                }
299            }
300        }
301
302        if convert_errors && self.original.is_c_raw_int() {
303            quote! {
304                if result < 0 {
305                    return Err(AeronCError::from_code(result));
306                } else {
307                    return Ok(result)
308                }
309            }
310        } else if self.original.is_c_string() {
311            if let ArgProcessing::StringWithLength(args) = &self.original.processing {
312                let length = &args[1].as_ident();
313                return quote! { if #result.is_null() { ""} else { std::str::from_utf8_unchecked(std::slice::from_raw_parts(#result as *const u8, #length.try_into().unwrap()))}};
314            } else {
315                return quote! { if #result.is_null() { ""} else { unsafe { std::ffi::CStr::from_ptr(#result).to_str().unwrap_or("") } } };
316            }
317        } else if self.original.is_single_mut_pointer() && self.original.is_primitive() {
318            return quote! {
319                unsafe { &mut *#result }
320            };
321        } else {
322            quote! { #result.into() }
323        }
324    }
325
326    /// Generic bounds for a handler argument. `force_static` is used by constructors,
327    /// where the handler is always cloned into the created resource's dependencies
328    /// (requiring `'static` for the `dyn Any` storage). For plain methods, only
329    /// *retained* handlers (see [`SYNC_HANDLER_TYPES`]) get the `'static` bound —
330    /// synchronous handlers may borrow local state, and their `_fn` closure variants
331    /// rely on that.
332    pub fn method_generics_for_where(&self, force_static: bool) -> Option<TokenStream> {
333        if let ArgProcessing::Handler(handler_client) = &self.original.processing {
334            if !self.original.is_mut_pointer() {
335                let handler = handler_client.get(0).unwrap();
336                let new_type = parse_str::<Type>(&format!("{}HandlerImpl", snake_to_pascal_case(&handler.c_type)))
337                    .expect("Invalid class name in wrapper");
338                let new_handler = parse_str::<Type>(&format!("{}Callback", snake_to_pascal_case(&handler.c_type)))
339                    .expect("Invalid class name in wrapper");
340                let needs_static = force_static || !is_sync_handler_type(&handler.c_type);
341                return if needs_static {
342                    Some(quote! {
343                        #new_type: #new_handler + 'static
344                    })
345                } else {
346                    Some(quote! {
347                        #new_type: #new_handler
348                    })
349                };
350            }
351        }
352        None
353    }
354
355    pub fn method_generics_for_method(&self) -> Option<TokenStream> {
356        if let ArgProcessing::Handler(handler_client) = &self.original.processing {
357            if !self.original.is_mut_pointer() {
358                let handler = handler_client.get(0).unwrap();
359                let new_type = parse_str::<Type>(&format!("{}HandlerImpl", snake_to_pascal_case(&handler.c_type)))
360                    .expect("Invalid class name in wrapper");
361                return Some(quote! {
362                    #new_type
363                });
364            }
365        }
366        None
367    }
368
369    pub fn handle_rs_to_c_return(&self, result: TokenStream, include_field_name: bool) -> TokenStream {
370        if let ArgProcessing::Handler(handler_client) = &self.original.processing {
371            if !self.original.is_mut_pointer() {
372                let handler = handler_client.get(0).unwrap();
373                let handler_name = handler.as_ident();
374                let handler_type = handler.as_type();
375                let clientd_name = handler_client.get(1).unwrap().as_ident();
376                let method_name = format_ident!("{}_callback", handler.c_type);
377                let new_type =
378                    parse_str::<Type>(&format!("{}HandlerImpl", snake_to_pascal_case(&self.original.c_type)))
379                        .expect("Invalid class name in wrapper");
380                if include_field_name {
381                    return quote! {
382                        #handler_name: { let callback: #handler_type = if #handler_name.is_none() { None } else { Some(#method_name::<#new_type>) }; callback },
383                        #clientd_name: #handler_name.map(|m|m.as_raw()).unwrap_or_else(|| std::ptr::null_mut())
384                    };
385                } else {
386                    return quote! {
387                        { let callback: #handler_type = if #handler_name.is_none() { None } else { Some(#method_name::<#new_type>) }; callback },
388                        #handler_name.map(|m|m.as_raw()).unwrap_or_else(|| std::ptr::null_mut())
389                    };
390                }
391            } else {
392                return quote! {};
393            }
394        }
395        if let ArgProcessing::StringWithLength(handler_client) = &self.original.processing {
396            // Emit the (pointer, length) pair once, on the length argument.
397            if !self.original.is_c_string_any() {
398                let array = handler_client.get(0).unwrap();
399                let array_name = array.as_ident();
400                let length_name = handler_client.get(1).unwrap().as_ident();
401                // `&str` in-param for const buffers; `&mut [u8]` fill-buffer for mut ones.
402                let ptr = if array.is_mut_c_string() {
403                    quote! { #array_name.as_mut_ptr() as *mut _ }
404                } else {
405                    quote! { #array_name.as_ptr() as *const _ }
406                };
407                if include_field_name {
408                    return quote! {
409                        #array_name: #ptr,
410                        #length_name: #array_name.len()
411                    };
412                } else {
413                    return quote! {
414                        #ptr,
415                        #array_name.len()
416                    };
417                }
418            } else {
419                return quote! {};
420            }
421        }
422        if let ArgProcessing::ByteArrayWithLength(handler_client) = &self.original.processing {
423            if !self.original.is_byte_array() {
424                let array = handler_client.get(0).unwrap();
425                let array_name = array.as_ident();
426                let length_name = handler_client.get(1).unwrap().as_ident();
427                if include_field_name {
428                    return quote! {
429                        #array_name: #array_name.as_ptr() as *mut _,
430                        #length_name: #array_name.len()
431                    };
432                } else {
433                    return quote! {
434                        #array_name.as_ptr() as *mut _,
435                        #array_name.len()
436                    };
437                }
438            } else {
439                return quote! {};
440            }
441        }
442
443        if include_field_name {
444            let arg_name = self.original.as_ident();
445            return if self.original.is_c_string() {
446                quote! {
447                    #arg_name: #result.as_ptr()
448                }
449            } else {
450                if self.original.is_single_mut_pointer() && self.original.is_primitive() {
451                    return quote! {
452                        #arg_name: #result as *mut _
453                    };
454                }
455
456                quote! { #arg_name: #result.into() }
457            };
458        }
459
460        if self.original.is_single_mut_pointer() && self.original.is_primitive() {
461            return quote! {
462                #result as *mut _
463            };
464        }
465
466        if self.original.is_c_string() {
467            quote! {
468                #result.as_ptr()
469            }
470        } else {
471            quote! { #result.into() }
472        }
473    }
474}
475
476#[derive(Debug, Clone, Default, Eq, PartialEq)]
477pub struct CWrapper {
478    pub class_name: String,
479    pub type_name: String,
480    pub without_name: String,
481    pub fields: Vec<Arg>,
482    pub methods: Vec<Method>,
483    pub docs: BTreeSet<String>,
484    /// Method names manually implemented in aeron_custom.rs — the generator skips these
485    /// to avoid duplicate definitions.
486    pub skipped_methods: BTreeSet<String>,
487}
488
489/// Parse `aeron_custom.rs` source and return a map of `ClassName -> {method_names}`
490/// for every `impl ClassName { fn method_name ... }` block found.
491/// This lets the generator skip auto-generating methods that have hand-written overrides.
492pub fn parse_custom_methods(src: &str) -> HashMap<String, BTreeSet<String>> {
493    let mut result: HashMap<String, BTreeSet<String>> = HashMap::new();
494    let file = syn::parse_file(src).unwrap_or_else(|e| {
495        panic!(
496            "rusteron codegen: failed to parse aeron_custom.rs while extracting custom method \
497             names (this would silently empty the generator skip-list and emit duplicate \
498             methods): {e}"
499        )
500    });
501    for item in &file.items {
502        if let Item::Impl(impl_block) = item {
503            // Only plain `impl TypeName { ... }` — no trait impls
504            if impl_block.trait_.is_some() {
505                continue;
506            }
507            let type_name = impl_block.self_ty.to_token_stream().to_string();
508            let type_name = type_name.trim().to_string();
509            let entry = result.entry(type_name).or_default();
510            for impl_item in &impl_block.items {
511                if let ImplItem::Fn(method) = impl_item {
512                    entry.insert(method.sig.ident.to_string());
513                }
514            }
515        }
516    }
517    result
518}
519
520impl CWrapper {
521    pub fn find_methods(&self, name: &str) -> Vec<Method> {
522        self.methods
523            .iter()
524            .filter(|m| m.struct_method_name == name)
525            .cloned()
526            .collect_vec()
527    }
528
529    pub fn find_unique_method(&self, name: &str) -> Option<Method> {
530        let results = self.find_methods(name);
531        if results.len() == 1 {
532            results.into_iter().next()
533        } else {
534            None
535        }
536    }
537
538    fn get_close_method(&self) -> Option<Method> {
539        self.find_unique_method("close")
540    }
541
542    /// Generate logging expressions for method arguments
543    fn generate_arg_logging(arguments: &[Arg], arg_names: &[TokenStream]) -> TokenStream {
544        let mut arg_names_idx = 0;
545        let mut arg_names_for_logging = vec![];
546
547        for (arg_idx, arg) in arguments.iter().enumerate() {
548            if arg_names_idx >= arg_names.len() {
549                break;
550            }
551
552            let arg_name_str = &arg.name;
553            let arg_type = arg.as_type();
554            let arg_ident = arg.as_ident();
555
556            // Determine how to log this argument
557            match &arg.processing {
558                ArgProcessing::Handler(_) if !arg.is_mut_pointer() => {
559                    // Handlers - just show type
560                    arg_names_for_logging.push(quote! {
561                        concat!(#arg_name_str, ": ", stringify!(#arg_type)).to_string()
562                    });
563                    arg_names_idx += 2; // Skip BOTH expanded values (callback + clientd)
564                }
565                ArgProcessing::StringWithLength(_args) => {
566                    // Check if this is the length argument (second in pair) - skip it
567                    if arg_idx > 0 && arguments[arg_idx - 1].processing == arg.processing {
568                        continue;
569                    }
570                    // This is the string argument - show the actual string value
571                    arg_names_for_logging.push(quote! {
572                        format!("{} = {:?}", #arg_name_str, #arg_ident)
573                    });
574                    arg_names_idx += 2;
575                }
576                ArgProcessing::ByteArrayWithLength(_args) => {
577                    // Check if this is the length argument (second in pair) - skip it
578                    if arg_idx > 0 && arguments[arg_idx - 1].processing == arg.processing {
579                        continue;
580                    }
581                    // This is the byte array argument - show name, type, and length
582                    arg_names_for_logging.push(quote! {
583                        format!("{}: {} (len={})", #arg_name_str, stringify!(#arg_type), #arg_ident.len())
584                    });
585                    arg_names_idx += 2;
586                }
587                _ => {
588                    // For primitive types, show value. For pointers/structs, just show name:type
589                    if arg.is_primitive() && !arg.is_any_pointer() {
590                        arg_names_for_logging.push(quote! {
591                            format!("{} = {:?}", #arg_name_str, #arg_ident)
592                        });
593                    } else {
594                        arg_names_for_logging.push(quote! {
595                            concat!(#arg_name_str, ": ", stringify!(#arg_type)).to_string()
596                        });
597                    }
598                    arg_names_idx += 1;
599                }
600            }
601        }
602
603        // For logging - need explicit type when array is empty
604        if arg_names_for_logging.is_empty() {
605            quote! { [""; 0].join(", ") }
606        } else {
607            quote! { [#(#arg_names_for_logging),*].join(", ") }
608        }
609    }
610
611    /// Generate methods for the struct
612    fn generate_methods(
613        &self,
614        wrappers: &BTreeMap<String, CWrapper>,
615        closure_handlers: &Vec<CHandler>,
616        additional_outer_impls: &mut Vec<TokenStream>,
617        debug_fields: &mut Vec<TokenStream>,
618    ) -> Vec<TokenStream> {
619        self.methods
620            .iter()
621            .filter(|m| !m.arguments.iter().any(|arg| arg.is_double_mut_pointer()))
622            .filter(|m| !self.skipped_methods.contains(&m.struct_method_name))
623            .filter(|m| m.struct_method_name != "close")
624            .map(|method| {
625
626                let fn_name =
627                    Ident::new(&method.struct_method_name, proc_macro2::Span::call_site());
628                let return_type_helper =
629                    ReturnType::new(method.return_type.clone(), wrappers.clone());
630                let mut return_type = return_type_helper.get_new_return_type(true, false);
631                let ffi_call = Ident::new(&method.fn_name, proc_macro2::Span::call_site());
632
633                // One classification pass: every argument becomes a ClassifiedArg carrying its
634                // signature/call/generic/registration/_once fragments (see arg_classifier.rs). All
635                // emitters below consume the plan — no parallel loops to keep in sync.
636                let classified = crate::arg_classifier::classify_method_args(method, &self.type_name, wrappers, closure_handlers);
637                let uses_self = classified.uses_self;
638                let owned_retained_handler = classified.owned_retained.clone();
639
640                let generic_types: Vec<TokenStream> = classified.generics();
641                let where_clause = if generic_types.is_empty() {
642                    quote! {}
643                } else {
644                    quote! { <#(#generic_types),*> }
645                };
646
647                let fn_arguments: Vec<TokenStream> = classified.signatures();
648                let mut arg_names: Vec<TokenStream> = classified.calls();
649                let retained_handler_registrations: Vec<TokenStream> = classified.registrations();
650
651                let mut converter = return_type_helper.handle_c_to_rs_return(quote! { result }, true, false);
652
653                // Heap-allocate the owned callback and capture its raw clientd before the
654                // FFI call, so the Handler itself stays available for registration/return.
655                let handler_prelude = if let Some(arg) = &owned_retained_handler {
656                    let name = arg.as_ident();
657                    let raw_name = format_ident!("{}_raw", arg.name);
658                    quote! {
659                        let #name = #name.map(Handler::new);
660                        let #raw_name = #name
661                            .as_ref()
662                            .map(|m| m.as_raw())
663                            .unwrap_or_else(|| std::ptr::null_mut());
664                    }
665                } else {
666                    quote! {}
667                };
668
669                if let Some(arg) = &owned_retained_handler {
670                    let name = arg.as_ident();
671                    let new_type = parse_str::<Type>(&format!(
672                        "{}HandlerImpl",
673                        snake_to_pascal_case(&arg.c_type)
674                    ))
675                    .expect("Invalid class name in wrapper");
676                    return_type = quote! { Result<Option<Handler<#new_type>>, AeronCError> };
677                    converter = quote! {
678                        if result < 0 {
679                            return Err(AeronCError::from_code(result));
680                        } else {
681                            return Ok(#name);
682                        }
683                    };
684                }
685
686                let mut method_docs: Vec<TokenStream> = get_docs(&method.docs, wrappers, Some(&fn_arguments) );
687
688                if owned_retained_handler.is_some() {
689                    method_docs.push(quote! { #[doc = ""] });
690                    method_docs.push(quote! { #[doc = " The callback is retained by the C client; this resource keeps it alive automatically."] });
691                    method_docs.push(quote! { #[doc = " Returns the created [`Handler`] for optional state access — safe to ignore. Closures with a matching signature are accepted directly."] });
692                }
693
694                // Generate logging expression for arguments
695                let args_log_expr = Self::generate_arg_logging(&method.arguments, &arg_names);
696
697                if uses_self && method.return_type.is_c_string_any() && method.arguments.len() == 1 {
698                    let name = format_ident!("{}", method.struct_method_name);
699                    debug_fields.push( quote! {
700                            .field(stringify!(#name), &self.#name() )
701                        } );
702                }
703
704                let possible_self = if uses_self  {
705                    quote! { &self, }
706                } else {
707                    if return_type.to_string().eq("& str") {
708                        return_type = quote! { &'static str  };
709                        method_docs.push(quote! {#[doc = "SAFETY: this is static for performance reasons, so you should not store this without copying it!!"]});
710                    }
711                    quote! {}
712                };
713
714
715
716                let register_handlers = if uses_self && !retained_handler_registrations.is_empty() {
717                    quote! { #(#retained_handler_registrations)* }
718                } else {
719                    quote! {}
720                };
721
722                let mut additional_methods = vec![];
723                let set_closed = quote! {};
724
725                Self::add_mut_string_methods_if_applicable(method, &fn_name, uses_self, &method_docs, &mut additional_methods);
726
727                // getter methods
728                Self::add_getter_instead_of_mut_arg_if_applicable(wrappers, method, &fn_name, &where_clause, &possible_self, &method_docs, &mut additional_methods, debug_fields);
729
730                // `_fn` stack-closure variant, emitted straight from the plan (sync-only:
731                // a stack closure handed to a retained callback would dangle). Skipped when
732                // aeron_custom.rs hand-writes a method of the same `<name>_fn` name, so
733                // custom code can override/deprecate a generated `_fn` variant.
734                if classified.once_capable
735                    && !self
736                        .skipped_methods
737                        .contains(&format!("{}_fn", method.struct_method_name))
738                {
739                    let once_fn_name = format_ident!("{}_fn", fn_name);
740                    let once_generics = classified.once_generics();
741                    let once_where = if once_generics.is_empty() {
742                        quote! {}
743                    } else {
744                        quote! { <#(#once_generics),*> }
745                    };
746                    let once_args = classified.once_signatures();
747                    let once_calls = classified.once_calls();
748                    let once_log_expr = Self::generate_arg_logging(&method.arguments, &once_calls);
749                    additional_methods.push(quote! {
750                        #[inline]
751                        #(#method_docs)*
752                        ///
753                        ///
754                        /// **Stack-borrowed closure** (`_fn` variant): the `FnMut` closure lives on the
755                        /// caller's stack and is borrowed for this call only — the callback fires
756                        /// synchronously inside the call, so nothing is heap-allocated, nothing is stored,
757                        /// and the closure may borrow local state. Prefer this over the retained
758                        /// [`Handler`]-based form on the hot path; only generated for callbacks the C
759                        /// client does not retain (i.e. not stored for later firing).
760                        ///
761                        /// # Panics
762                        ///
763                        /// A panic inside the closure cannot unwind across the `extern "C"` callback
764                        /// boundary and **aborts the process** (since Rust 1.81). Return early instead
765                        /// of panicking in production fragment handlers.
766                        pub fn #once_fn_name #once_where(#possible_self #(#once_args),*) -> #return_type {
767                            #set_closed
768                            unsafe {
769                                #[cfg(feature = "log-c-bindings")]
770                                log::info!(
771                                    "{}({})",
772                                    stringify!(#ffi_call),
773                                    #once_log_expr
774                                );
775
776                                let result = #ffi_call(#(#once_calls),*);
777
778                                #[cfg(feature = "log-c-bindings")]
779                                log::info!("  -> {:?}", result);
780
781                                #converter
782                            }
783                        }
784                    });
785                }
786
787                let mut_primitivies = method.arguments.iter()
788                    .filter(|a| a.is_mut_pointer() && a.is_primitive())
789                    .collect_vec();
790                let single_mut_field = method.return_type.is_c_raw_int() && mut_primitivies.len() == 1;
791
792                // in aeron some methods return error code but have &mut primitive
793                // ideally we should return that primitive instead of forcing user to pass it in
794                let __method_tokens = if single_mut_field {
795                    let mut_field = mut_primitivies.first().unwrap();
796                    // keep the qualified path (`c_int` is only valid qualified), not just the last segment
797                    let rt: Type = parse_str(mut_field.c_type.trim_start_matches("* mut").trim()).unwrap();
798                    let return_type = quote! { Result<#rt, AeronCError> };
799
800                    let fn_arguments= fn_arguments.into_iter().filter(|arg| {!arg.to_string().contains("& mut ")})
801                        .collect_vec();
802
803                    let idx = arg_names.iter().enumerate()
804                        .filter(|(_, arg)| arg.to_string().ends_with("* mut _"))
805                        .map(|(i, _)| i)
806                        .next().unwrap();
807
808                    arg_names[idx] = quote! { &mut mut_result };
809
810                    let mut first = true;
811                    let mut method_docs = method_docs.iter()
812                        .filter(|d| !d.to_string().contains("# Return"))
813                        .map(|d| {
814                            let mut string = d.to_string();
815                            string = string.replace("# Parameters", "");
816                            if string.contains("out param") {
817                                TokenStream::from_str(&string.replace("- `", "\n# Return\n`")).unwrap()
818                            } else {
819                                if string.contains("- `") && first {
820                                    first = false;
821                                    string = string.replacen("- `","# Parameters\n- `", 1);
822                                }
823                                TokenStream::from_str(&string).unwrap()
824                            }
825                        })
826                        .collect_vec();
827
828                    let filter_param_title = !method_docs.iter().any(|d| d.to_string().contains("- `"));
829
830                    if filter_param_title {
831                        method_docs = method_docs.into_iter()
832                            .map(|s| TokenStream::from_str(s.to_string().replace("# Parameters\n", "").as_str()).unwrap())
833                            .collect_vec();
834                    }
835
836
837                    quote! {
838                        #[inline]
839                        #(#method_docs)*
840                        pub fn #fn_name #where_clause(#possible_self #(#fn_arguments),*) -> #return_type {
841                            #set_closed
842                            unsafe {
843                                let mut mut_result: #rt = Default::default();
844
845                                #[cfg(feature = "log-c-bindings")]
846                                log::info!(
847                                    "{}({})",
848                                    stringify!(#ffi_call),
849                                    #args_log_expr
850                                );
851
852                                let err_code = #ffi_call(#(#arg_names),*);
853                                #register_handlers
854
855                                #[cfg(feature = "log-c-bindings")]
856                                log::info!("  -> err_code = {:?}, result = {:?}", err_code, mut_result);
857
858                                if err_code < 0 {
859                                    return Err(AeronCError::from_code(err_code));
860                                } else {
861                                    return Ok(mut_result);
862                                }
863                            }
864                        }
865
866                        #(#additional_methods)*
867                    }
868                } else {
869                    quote! {
870                        #[inline]
871                        #(#method_docs)*
872                        pub fn #fn_name #where_clause(#possible_self #(#fn_arguments),*) -> #return_type {
873                            #set_closed
874                            #handler_prelude
875                            unsafe {
876                                #[cfg(feature = "log-c-bindings")]
877                                log::info!(
878                                    "{}({})",
879                                    stringify!(#ffi_call),
880                                    #args_log_expr
881                                );
882
883                                let result = #ffi_call(#(#arg_names),*);
884                                #register_handlers
885
886                                #[cfg(feature = "log-c-bindings")]
887                                log::info!("  -> {:?}", result);
888
889                                #converter
890                            }
891                        }
892
893                        #(#additional_methods)*
894                    }
895                };
896
897                // Archive control operations surface typed errors (AeronArchiveError) so
898                // callers can match on the structured code. Apply uniformly at the token
899                // level to every method on `aeron_archive_t` — the main body plus the
900                // `additional_methods` (_fn variants, out-param getters, string getters)
901                // which all live in this same closure scope.
902                if self.type_name == "aeron_archive_t" {
903                    __method_tokens
904                        .to_string()
905                        .replace("AeronCError", "AeronArchiveError")
906                        .parse()
907                        .expect("AeronArchiveError token substitution must yield valid tokens")
908                } else {
909                    __method_tokens
910                }
911            })
912            .collect()
913    }
914
915    /// Registration statements that clone each `Handler` argument into `result`'s
916    /// dependencies. Used by constructors: the created C struct stores the callback and
917    /// clientd pointers, so the Handler must stay alive until the C resource is closed.
918    fn handler_dependency_registrations(arguments: &[Arg]) -> Vec<TokenStream> {
919        arguments
920            .iter()
921            .filter_map(|arg| {
922                if let ArgProcessing::Handler(_) = &arg.processing {
923                    if !arg.is_mut_pointer() {
924                        let name = arg.as_ident();
925                        return Some(quote! {
926                            if let Some(__handler) = #name {
927                                if let Some(__inner) = result.inner.as_owned() {
928                                    __inner.add_dependency(__handler.clone());
929                                }
930                            }
931                        });
932                    }
933                }
934                None
935            })
936            .collect()
937    }
938
939    fn add_getter_instead_of_mut_arg_if_applicable(
940        wrappers: &BTreeMap<String, CWrapper>,
941        method: &Method,
942        fn_name: &Ident,
943        where_clause: &TokenStream,
944        possible_self: &TokenStream,
945        method_docs: &Vec<TokenStream>,
946        additional_methods: &mut Vec<TokenStream>,
947        debug_fields: &mut Vec<TokenStream>,
948    ) {
949        if ["constants", "buffers", "values"]
950            .iter()
951            .any(|name| method.struct_method_name == *name)
952            && method.arguments.len() == 2
953        {
954            let rt = ReturnType::new(method.arguments[1].clone(), wrappers.clone());
955            let return_type = rt.get_new_return_type(false, false);
956            let getter_method = format_ident!("get_{}", fn_name);
957            let method_docs = method_docs
958                .iter()
959                .cloned()
960                .take_while(|t| !t.to_string().contains(" Parameter"))
961                .collect_vec();
962            additional_methods.push(quote! {
963                #[inline]
964                #(#method_docs)*
965                pub fn #getter_method #where_clause(#possible_self) -> Result<#return_type, AeronCError> {
966                    let result = #return_type::new_zeroed_on_stack();
967                    self.#fn_name(&result)?;
968                    Ok(result)
969                }
970            });
971            debug_fields.push(quote! {
972                .field(stringify!(#fn_name), &self.#getter_method() )
973            });
974        }
975    }
976
977    fn add_mut_string_methods_if_applicable(
978        method: &Method,
979        fn_name: &Ident,
980        uses_self: bool,
981        method_docs: &Vec<TokenStream>,
982        additional_methods: &mut Vec<TokenStream>,
983    ) {
984        if method.arguments.len() == 3 && uses_self {
985            let method_docs = method_docs.clone();
986            let into_method = format_ident!("{}_into", fn_name);
987            if method.arguments[1].is_mut_c_string() && method.arguments[2].is_usize() {
988                let string_method = format_ident!("{}_as_string", fn_name);
989                additional_methods.push(quote! {
990    #[inline]
991    #(#method_docs)*
992    pub fn #string_method(
993        &self,
994        max_length: usize,
995    ) -> Result<String, AeronCError> {
996        let mut result = String::with_capacity(max_length);
997        self.#into_method(&mut result)?;
998        Ok(result)
999    }
1000
1001    #[inline]
1002    #(#method_docs)*
1003    #[doc = "NOTE: allocation friendly method, the string capacity must be set as it will truncate string to capacity it will never grow the string. So if you pass String::new() it will write 0 chars"]
1004    pub fn #into_method(
1005        &self,
1006        dst_truncate_to_capacity: &mut String,
1007    ) -> Result<i32, AeronCError> {
1008        unsafe {
1009            let capacity = dst_truncate_to_capacity.capacity();
1010            let vec = dst_truncate_to_capacity.as_mut_vec();
1011            vec.set_len(capacity);
1012            let result = self.#fn_name(&mut vec[..])?;
1013            let mut len = 0;
1014            loop {
1015                if len == capacity {
1016                    break;
1017                }
1018                let val = vec[len];
1019                if val == 0 {
1020                    break;
1021                }
1022                len += 1;
1023            }
1024            vec.set_len(len);
1025            Ok(result)
1026        }
1027    }
1028                        });
1029            }
1030        }
1031    }
1032
1033    /// Generate the fields / getters
1034    fn generate_fields(
1035        &self,
1036        cwrappers: &BTreeMap<String, CWrapper>,
1037        debug_fields: &mut Vec<TokenStream>,
1038    ) -> Vec<TokenStream> {
1039        self.fields
1040            .iter()
1041            .filter(|arg| {
1042                !arg.name.starts_with("_") && !self.methods.iter().any(|m| m.struct_method_name.as_str() == arg.name)
1043            })
1044            .map(|arg| {
1045                let field_name = &arg.name;
1046                let fn_name = Ident::new(field_name, proc_macro2::Span::call_site());
1047
1048                let mut arg = arg.clone();
1049                // for mut strings return just &str not &mut str
1050                if arg.is_mut_c_string() {
1051                    arg.c_type = arg.c_type.replace(" mut ", " const ");
1052                }
1053                let mut rt = ReturnType::new(arg.clone(), cwrappers.clone());
1054                let mut return_type = rt.get_new_return_type(false, false);
1055                let handler = if let ArgProcessing::Handler(_) = &arg.processing {
1056                    true
1057                } else {
1058                    false
1059                };
1060                if return_type.is_empty() || handler {
1061                    rt = ReturnType::new(
1062                        Arg {
1063                            processing: ArgProcessing::Default,
1064                            ..arg.clone()
1065                        },
1066                        cwrappers.clone(),
1067                    );
1068                    return_type = rt.get_new_return_type(false, false);
1069                }
1070                let converter = rt.handle_c_to_rs_return(quote! { self.#fn_name }, false, true);
1071
1072                if rt.original.is_primitive()
1073                    || rt.original.is_c_string_any()
1074                    || rt.original.is_byte_array()
1075                    || cwrappers.contains_key(&rt.original.c_type)
1076                {
1077                    if !rt.original.is_any_pointer() || rt.original.is_c_string_any() {
1078                        debug_fields.push(quote! { .field(stringify!(#fn_name), &self.#fn_name()) });
1079                    }
1080                }
1081
1082                quote! {
1083                    #[inline]
1084                    pub fn #fn_name(&self) -> #return_type {
1085                        #converter
1086                    }
1087                }
1088            })
1089            .filter(|t| !t.is_empty())
1090            .collect()
1091    }
1092
1093    /// Generate the constructor for the struct
1094    fn generate_constructor(
1095        &self,
1096        wrappers: &BTreeMap<String, CWrapper>,
1097        constructor_fields: &mut Vec<TokenStream>,
1098        new_ref_set_none: &mut Vec<TokenStream>,
1099    ) -> Vec<TokenStream> {
1100        let constructors = self
1101            .methods
1102            .iter()
1103            .filter(|m| m.arguments.iter().any(|arg| arg.is_double_mut_pointer()))
1104            .map(|method| {
1105                let init_fn = format_ident!("{}", method.fn_name);
1106
1107                // `_add_destination`/`_remove_destination[_by_id]` are constructor-style
1108                // methods (double-mut-pointer output) that don't have a real C "close"/
1109                // "destroy" counterpart to pair with — each is a standalone async op polled
1110                // to completion via `..._destination_poll`, and (per the doc comment on
1111                // `cleanup_tokens` below) must NOT auto-invoke its sibling on drop. Treat
1112                // them as trivially "having a close method" (of themselves, unused — see
1113                // `cleanup_tokens` forcing `None` for any `_destination` method) so they are
1114                // still eligible for constructor generation without requiring a matching
1115                // pair to exist
1116                let is_destination_add_or_remove = method.fn_name.contains("_async_add_destination")
1117                    || method.fn_name.contains("_async_remove_destination");
1118                let close_method = self.find_close_method(method).or(if is_destination_add_or_remove {
1119                    Some(method)
1120                } else {
1121                    None
1122                });
1123                let found_close = is_destination_add_or_remove
1124                    || (close_method.is_some()
1125                        && close_method.unwrap().return_type.is_c_raw_int()
1126                        && close_method.unwrap() != method
1127                        && close_method
1128                            .unwrap()
1129                            .arguments
1130                            .iter()
1131                            .skip(1)
1132                            .all(|a| method.arguments.iter().any(|a2| a.name == a2.name)));
1133                if found_close {
1134                    let close_fn = format_ident!("{}", close_method.unwrap().fn_name);
1135                    let init_args: Vec<TokenStream> = method
1136                        .arguments
1137                        .iter()
1138                        .enumerate()
1139                        .map(|(idx, arg)| {
1140                            if idx == 0 {
1141                                quote! { ctx_field }
1142                            } else {
1143                                let arg_name = arg.as_ident();
1144                                quote! { #arg_name }
1145                            }
1146                        })
1147                        .filter(|t| !t.is_empty())
1148                        .collect();
1149                    let close_args: Vec<TokenStream> = close_method
1150                        .unwrap_or(method)
1151                        .arguments
1152                        .iter()
1153                        .enumerate()
1154                        .map(|(idx, arg)| {
1155                            if idx == 0 {
1156                                if arg.is_double_mut_pointer() {
1157                                    quote! { ctx_field }
1158                                } else {
1159                                    quote! { *ctx_field }
1160                                }
1161                            } else {
1162                                let arg_name = arg.as_ident();
1163                                quote! { #arg_name.into() }
1164                            }
1165                        })
1166                        .filter(|t| !t.is_empty())
1167                        .collect();
1168                    let lets: Vec<TokenStream> = Self::lets_for_copying_arguments(wrappers, &method.arguments, true);
1169
1170                    constructor_fields.clear();
1171                    constructor_fields.extend(Self::constructor_fields(wrappers, &method.arguments, &self.class_name));
1172
1173                    let new_ref_args = Self::new_args(wrappers, &method.arguments, &self.class_name, false);
1174
1175                    new_ref_set_none.clear();
1176                    new_ref_set_none.extend(Self::new_args(wrappers, &method.arguments, &self.class_name, true));
1177
1178                    let new_args: Vec<TokenStream> = method
1179                        .arguments
1180                        .iter()
1181                        .enumerate()
1182                        .filter_map(|(_idx, arg)| {
1183                            if arg.is_double_mut_pointer() {
1184                                None
1185                            } else {
1186                                let arg_name = arg.as_ident();
1187                                let arg_type =
1188                                    ReturnType::new(arg.clone(), wrappers.clone()).get_new_return_type(false, true);
1189                                if arg_type.clone().into_token_stream().is_empty() {
1190                                    None
1191                                } else {
1192                                    Some(quote! { #arg_name: #arg_type })
1193                                }
1194                            }
1195                        })
1196                        .filter(|t| !t.is_empty())
1197                        .collect();
1198
1199                    let fn_name = format_ident!(
1200                        "{}",
1201                        method
1202                            .struct_method_name
1203                            .replace("init", "new")
1204                            .replace("create", "new")
1205                    );
1206
1207                    let generic_types: Vec<TokenStream> = method
1208                        .arguments
1209                        .iter()
1210                        .flat_map(|arg| {
1211                            ReturnType::new(arg.clone(), wrappers.clone())
1212                                .method_generics_for_where(true)
1213                                .into_iter()
1214                        })
1215                        .collect_vec();
1216                    let where_clause = if generic_types.is_empty() {
1217                        quote! {}
1218                    } else {
1219                        quote! { <#(#generic_types),*> }
1220                    };
1221
1222                    let method_docs: Vec<TokenStream> = get_docs(&method.docs, wrappers, Some(&new_args));
1223
1224                    // The C struct created here stores every callback/clientd pair it is
1225                    // given (e.g. fragment assemblers keep their delegate), so clone each
1226                    // Handler into the new resource's dependencies to keep it alive until
1227                    // the C resource is closed.
1228                    let handler_deps: Vec<TokenStream> = Self::handler_dependency_registrations(&method.arguments);
1229
1230                    // Generate logging expression token stream (will be evaluated in closure)
1231                    let init_log_expr_tokens = Self::generate_arg_logging(&method.arguments, &init_args);
1232                    // Generate logging for close method arguments
1233                    let close_log_expr_tokens = if let Some(close_m) = close_method {
1234                        Self::generate_arg_logging(&close_m.arguments, &close_args)
1235                    } else {
1236                        quote! { "" }
1237                    };
1238
1239                    // `async_add_destination` handles must NOT run `async_remove_destination`
1240                    // on drop: removing the destination is a separate, destructive operation —
1241                    // not the destructor of the async-add handle (the C client frees the async
1242                    // struct itself once its poll completes). Pairing them made dropping the
1243                    // poll handle silently tear the destination down again, with a dangling
1244                    // uri pointer to boot.
1245                    let cleanup_tokens = if method.fn_name.contains("_destination") {
1246                        quote! { None }
1247                    } else {
1248                        quote! {
1249                            Some(Box::new(move |ctx_field| unsafe {
1250                                #[cfg(feature = "log-c-bindings")]
1251                                {
1252                                    let log_args = #close_log_expr_tokens;
1253                                    log::info!("{}({})", stringify!(#close_fn), log_args);
1254                                }
1255                                #close_fn(#(#close_args),*)
1256                            }))
1257                        }
1258                    };
1259
1260                    quote! {
1261                        #[inline]
1262                        #(#method_docs)*
1263                        pub fn #fn_name #where_clause(#(#new_args),*) -> Result<Self, AeronCError> {
1264                            #(#lets)*
1265                            // new by using constructor
1266                            let resource_constructor = ManagedCResource::new(
1267                                move |ctx_field| unsafe {
1268                                    #[cfg(feature = "log-c-bindings")]
1269                                    {
1270                                        let log_args = #init_log_expr_tokens;
1271                                        log::info!("{}({})", stringify!(#init_fn), log_args);
1272                                    }
1273                                    #init_fn(#(#init_args),*)
1274                                },
1275                                #cleanup_tokens,
1276                                false,
1277                            )?;
1278
1279                            let result = Self {
1280                                inner: CResource::OwnedOnHeap(RcOrArc::new(resource_constructor)),
1281                                #(#new_ref_args)*
1282                            };
1283                            #(#handler_deps)*
1284                            Ok(result)
1285                        }
1286                    }
1287                } else {
1288                    quote! {}
1289                }
1290            })
1291            .collect_vec();
1292
1293        let no_constructor = constructors.iter().map(|x| x.to_string()).join("").trim().is_empty();
1294        if no_constructor {
1295            let type_name = format_ident!("{}", self.type_name);
1296            let zeroed_impl = quote! {
1297                #[inline]
1298                /// creates zeroed struct where the underlying c struct is on the heap
1299                pub fn new_zeroed_on_heap() -> Self {
1300                    let resource = ManagedCResource::new(
1301                        move |ctx_field| {
1302                            #[cfg(feature = "extra-logging")]
1303                            log::info!("creating zeroed empty resource on heap {}", stringify!(#type_name));
1304                            let inst: #type_name = unsafe { std::mem::zeroed() };
1305                            let inner_ptr: *mut #type_name = Box::into_raw(Box::new(inst));
1306                            unsafe { *ctx_field = inner_ptr };
1307                            0
1308                        },
1309                        None,
1310                        true,
1311                    ).unwrap();
1312
1313                    Self {
1314                        inner: CResource::OwnedOnHeap(RcOrArc::new(resource)),
1315                    }
1316                }
1317
1318                #[inline]
1319                /// creates zeroed struct where the underlying c struct is on the stack
1320                /// _(Use with care)_
1321                pub fn new_zeroed_on_stack() -> Self {
1322                    #[cfg(feature = "extra-logging")]
1323                    log::debug!("creating zeroed empty resource on stack {}", stringify!(#type_name));
1324
1325                    Self {
1326                        inner: CResource::OwnedOnStack(std::mem::MaybeUninit::zeroed()),
1327                    }
1328                }
1329            };
1330            if self.has_default_method() {
1331                let type_name = format_ident!("{}", self.type_name);
1332                let new_args: Vec<TokenStream> = self
1333                    .fields
1334                    .iter()
1335                    .filter_map(|arg| {
1336                        let arg_name = arg.as_ident();
1337                        let arg_type = ReturnType::new(arg.clone(), wrappers.clone()).get_new_return_type(false, true);
1338                        if arg_type.is_empty() {
1339                            None
1340                        } else {
1341                            Some(quote! { #arg_name: #arg_type })
1342                        }
1343                    })
1344                    .filter(|t| !t.is_empty())
1345                    .collect();
1346                let init_args: Vec<TokenStream> = self
1347                    .fields
1348                    .iter()
1349                    .map(|arg| {
1350                        let arg_name = arg.as_ident();
1351                        let value = ReturnType::new(arg.clone(), wrappers.clone())
1352                            .handle_rs_to_c_return(quote! { #arg_name }, true);
1353                        quote! { #value }
1354                    })
1355                    .filter(|t| !t.is_empty())
1356                    .collect();
1357
1358                let generic_types: Vec<TokenStream> = self
1359                    .fields
1360                    .iter()
1361                    .flat_map(|arg| {
1362                        ReturnType::new(arg.clone(), wrappers.clone())
1363                            .method_generics_for_where(true)
1364                            .into_iter()
1365                    })
1366                    .collect_vec();
1367                let where_clause = if generic_types.is_empty() {
1368                    quote! {}
1369                } else {
1370                    quote! { <#(#generic_types),*> }
1371                };
1372
1373                let cloned_fields = self
1374                    .fields
1375                    .iter()
1376                    .filter(|a| a.processing == ArgProcessing::Default)
1377                    .cloned()
1378                    .collect_vec();
1379                let lets: Vec<TokenStream> = Self::lets_for_copying_arguments(wrappers, &cloned_fields, false);
1380
1381                // The C struct stores these callback/clientd fields directly, so keep the
1382                // Handler values alive alongside the resource.
1383                let field_handler_deps: Vec<TokenStream> = Self::handler_dependency_registrations(&self.fields);
1384
1385                vec![quote! {
1386                    #[inline]
1387                    pub fn new #where_clause(#(#new_args),*) -> Result<Self, AeronCError> {
1388                        #(#lets)*
1389                        // no constructor in c bindings
1390                        let r_constructor = ManagedCResource::new(
1391                            move |ctx_field| {
1392                                let inst = #type_name { #(#init_args),* };
1393                                let inner_ptr: *mut #type_name = Box::into_raw(Box::new(inst));
1394                                unsafe { *ctx_field = inner_ptr };
1395                                0
1396                            },
1397                            None,
1398                            true,
1399                        )?;
1400
1401                        let result = Self {
1402                            inner: CResource::OwnedOnHeap(RcOrArc::new(r_constructor)),
1403                        };
1404                        #(#field_handler_deps)*
1405                        Ok(result)
1406                    }
1407
1408                    #zeroed_impl
1409                }]
1410            } else {
1411                vec![zeroed_impl]
1412            }
1413        } else {
1414            constructors
1415        }
1416    }
1417
1418    fn lets_for_copying_arguments(
1419        wrappers: &BTreeMap<String, CWrapper>,
1420        arguments: &Vec<Arg>,
1421        include_let_statements: bool,
1422    ) -> Vec<TokenStream> {
1423        arguments
1424            .iter()
1425            .enumerate()
1426            .filter_map(|(_idx, arg)| {
1427                if arg.is_double_mut_pointer() {
1428                    None
1429                } else {
1430                    let arg_name = arg.as_ident();
1431                    let rtype = arg.as_type();
1432
1433                    // check if I need to make copy of object for reference counting
1434                    let fields = if arg.is_single_mut_pointer()
1435                        && wrappers.contains_key(arg.c_type.split_whitespace().last().unwrap())
1436                    {
1437                        let arg_copy = format_ident!("{}_copy", arg.name);
1438                        quote! {
1439                            let #arg_copy = #arg_name.clone();
1440                        }
1441                    } else {
1442                        quote! {}
1443                    };
1444
1445                    let return_type = ReturnType::new(arg.clone(), wrappers.clone());
1446
1447                    if let ArgProcessing::StringWithLength(_args) | ArgProcessing::ByteArrayWithLength(_args) =
1448                        &return_type.original.processing
1449                    {
1450                        return None;
1451                    }
1452                    if let ArgProcessing::Handler(args) = &return_type.original.processing {
1453                        let arg1 = args[0].as_ident();
1454                        let arg2 = args[1].as_ident();
1455                        let value = return_type.handle_rs_to_c_return(quote! { #arg_name }, false);
1456
1457                        if value.is_empty() {
1458                            return None;
1459                        }
1460
1461                        if include_let_statements {
1462                            return Some(quote! { #fields let (#arg1, #arg2)= (#value); });
1463                        } else {
1464                            return Some(fields);
1465                        }
1466                    }
1467
1468                    let value = return_type.handle_rs_to_c_return(quote! { #arg_name }, false);
1469                    if value.is_empty() {
1470                        None
1471                    } else {
1472                        if include_let_statements {
1473                            Some(quote! { #fields let #arg_name: #rtype = #value; })
1474                        } else {
1475                            return Some(fields);
1476                        }
1477                    }
1478                }
1479            })
1480            .filter(|t| !t.is_empty())
1481            .collect()
1482    }
1483
1484    fn constructor_fields(
1485        wrappers: &BTreeMap<String, CWrapper>,
1486        arguments: &Vec<Arg>,
1487        class_name: &String,
1488    ) -> Vec<TokenStream> {
1489        if class_name == "AeronAsyncDestination" {
1490            return vec![];
1491        }
1492
1493        arguments
1494            .iter()
1495            .enumerate()
1496            .filter_map(|(_idx, arg)| {
1497                if arg.is_double_mut_pointer() {
1498                    None
1499                } else {
1500                    let arg_name = arg.as_ident();
1501                    let rtype = arg.as_type();
1502                    if arg.is_single_mut_pointer()
1503                        && wrappers.contains_key(arg.c_type.split_whitespace().last().unwrap())
1504                    {
1505                        let return_type = ReturnType::new(arg.clone(), wrappers.clone());
1506                        let return_type = return_type.get_new_return_type(false, false);
1507
1508                        let arg_copy = format_ident!("_{}", arg.name);
1509                        Some(quote! {
1510                            #arg_copy: Option<#return_type>,
1511                        })
1512                    } else {
1513                        None
1514                    }
1515                }
1516            })
1517            .collect()
1518    }
1519
1520    fn new_args(
1521        wrappers: &BTreeMap<String, CWrapper>,
1522        arguments: &Vec<Arg>,
1523        class_name: &String,
1524        set_none: bool,
1525    ) -> Vec<TokenStream> {
1526        if class_name == "AeronAsyncDestination" {
1527            return vec![];
1528        }
1529
1530        arguments
1531            .iter()
1532            .enumerate()
1533            .filter_map(|(_idx, arg)| {
1534                if arg.is_double_mut_pointer() {
1535                    None
1536                } else {
1537                    let arg_name = arg.as_ident();
1538                    let rtype = arg.as_type();
1539                    if arg.is_single_mut_pointer()
1540                        && wrappers.contains_key(arg.c_type.split_whitespace().last().unwrap())
1541                    {
1542                        let arg_f = format_ident!("_{}", &arg.name);
1543                        let arg_copy = format_ident!("{}_copy", &arg.name);
1544                        if set_none {
1545                            Some(quote! {
1546                                #arg_f: None,
1547                            })
1548                        } else {
1549                            Some(quote! {
1550                                #arg_f: Some(#arg_copy),
1551                            })
1552                        }
1553                    } else {
1554                        None
1555                    }
1556                }
1557            })
1558            .collect()
1559    }
1560
1561    fn find_close_method(&self, method: &Method) -> Option<&Method> {
1562        let mut close_method = None;
1563
1564        // must have init, create, add or remove method name. `_remove` is needed so
1565        // that e.g. `aeron_publication_async_remove_destination`
1566        if ["_init", "_create", "_add", "_remove"]
1567            .iter()
1568            .all(|find| !method.fn_name.contains(find))
1569        {
1570            return None;
1571        }
1572
1573        for name in ["_destroy", "_delete"] {
1574            let mut candidate = method.fn_name.replace("_init", "_close").replace("_create", name);
1575            if method.fn_name.contains("_remove_") {
1576                // symmetric case: a `_remove_` method (e.g. `..._async_remove_destination`,
1577                // or its `..._remove_destination_by_id` sibling — which has no `_add_..._by_id`
1578                // counterpart, so drop the `_by_id` suffix before pairing) looks for its
1579                // paired `_add_` method (e.g. `..._async_add_destination`) so `found_close`
1580                // can be satisfied for it too. This is purely a "does a plausible pair
1581                // exist" heuristic gate — it does NOT wire remove_destination as
1582                // add_destination's cleanup closure or vice versa; `cleanup_tokens` below
1583                // always forces `None` for any `_destination` method regardless of what
1584                // this resolves to.
1585                candidate = candidate.trim_end_matches("_by_id").replace("_remove_", "_add_");
1586            } else {
1587                candidate = candidate.replace("_add_", "_remove_");
1588            }
1589            let close_fn = format_ident!("{}", candidate);
1590            let method = self.methods.iter().find(|m| close_fn.to_string().contains(&m.fn_name));
1591            if method.is_some() {
1592                close_method = method;
1593                break;
1594            }
1595        }
1596        close_method
1597    }
1598
1599    fn has_default_method(&self) -> bool {
1600        // AeronUriStringBuilder does not follow the normal convention so have additional check arg.is_single_mut_pointer() && m.fn_name.contains("_init_")
1601        let no_init_method = !self.methods.iter().any(|m| {
1602            m.arguments
1603                .iter()
1604                .any(|arg| arg.is_double_mut_pointer() || (arg.is_single_mut_pointer() && m.fn_name.contains("_init_")))
1605        });
1606
1607        no_init_method && !self.fields.iter().any(|arg| arg.name.starts_with("_")) && !self.fields.is_empty()
1608    }
1609
1610    fn generate_allocation_test(&self) -> TokenStream {
1611        let class_name = format_ident!("{}", self.class_name);
1612
1613        let has_c_constructor = self
1614            .methods
1615            .iter()
1616            .any(|m| m.arguments.iter().any(|arg| arg.is_double_mut_pointer()));
1617        let has_empty_new_constructor = self.methods.iter().any(|m| {
1618            m.fn_name.contains("_init_")
1619                && m.arguments.iter().any(|arg| arg.is_double_mut_pointer())
1620                && m.arguments.len() == 1
1621        });
1622
1623        let mut tests = vec![];
1624
1625        if !has_c_constructor {
1626            tests.push(quote! {
1627                #[test]
1628                #[file_serial(global)]
1629                fn test_new_on_stack() {
1630                    crate::test_alloc::assert_no_allocation(|| {
1631                        for _ in 0..100 {
1632                            let _ = #class_name::new_zeroed_on_stack();
1633                        }
1634                    });
1635                }
1636            });
1637        }
1638
1639        if has_empty_new_constructor {
1640            tests.push(quote! {
1641                #[test]
1642                #[file_serial(global)]
1643                fn test_new() {
1644                    crate::test_alloc::assert_no_allocation(|| {
1645                        for _ in 0..100 {
1646                            let _ = #class_name::new();
1647                        }
1648                    });
1649                }
1650            });
1651        }
1652
1653        if self.has_default_method() {
1654            tests.push(quote! {
1655                #[test]
1656                #[file_serial(global)]
1657                fn test_default() {
1658                    crate::test_alloc::assert_no_allocation(|| {
1659                        for _ in 0..100 {
1660                            let _ = #class_name::default();
1661                        }
1662                    });
1663                }
1664            });
1665        }
1666
1667        let mod_name = format_ident!("{}_allocation_tests", self.type_name);
1668
1669        if tests.is_empty() {
1670            quote! {}
1671        } else {
1672            quote! {
1673                #[cfg(test)]
1674                mod #mod_name {
1675                    use super::*;
1676                    use serial_test::file_serial;
1677                    #(#tests)*
1678                }
1679            }
1680        }
1681    }
1682}
1683
1684#[cfg(test)]
1685mod parse_custom_methods_tests {
1686    use super::parse_custom_methods;
1687    use std::collections::BTreeSet;
1688
1689    #[test]
1690    fn extracts_method_names_from_inherent_impls() {
1691        let src = r#"
1692            impl AeronPublication {
1693                pub fn offer_result(&self) -> i64 { 0 }
1694                pub fn status(&self) -> u8 { 0 }
1695            }
1696            impl AeronSubscription {
1697                pub fn status(&self) -> u8 { 0 }
1698            }
1699        "#;
1700        let map = parse_custom_methods(src);
1701        assert_eq!(
1702            map.get("AeronPublication"),
1703            Some(
1704                &["offer_result", "status"]
1705                    .iter()
1706                    .map(|s| s.to_string())
1707                    .collect::<BTreeSet<_>>()
1708            )
1709        );
1710        assert!(map.get("AeronSubscription").map_or(false, |s| s.contains("status")));
1711    }
1712
1713    #[test]
1714    fn ignores_trait_impls() {
1715        // `impl Display for X` and `impl<T> Trait for Y` must not be treated as
1716        // inherent method sources (they'd add bogus skip entries).
1717        let src = r#"
1718            impl std::fmt::Display for AeronCError {
1719                fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { Ok(()) }
1720            }
1721        "#;
1722        let map = parse_custom_methods(src);
1723        assert!(
1724            !map.contains_key("AeronCError"),
1725            "trait impls must not appear in the skip-list"
1726        );
1727    }
1728
1729    #[test]
1730    #[should_panic(expected = "failed to parse aeron_custom.rs")]
1731    fn panics_loudly_on_malformed_input() {
1732        // Previously this returned an empty map → duplicate-method emission.
1733        let _ = parse_custom_methods("impl { not valid rust }}}");
1734    }
1735
1736    #[test]
1737    fn empty_input_yields_empty_map() {
1738        assert!(parse_custom_methods("").is_empty());
1739    }
1740
1741    #[test]
1742    fn self_type_keyed_by_plain_token_stream() {
1743        let src = r#"
1744            impl AeronPublication { pub fn offer_result(&self) -> i64 { 0 } }
1745        "#;
1746        let map = parse_custom_methods(src);
1747        // Exact plain-name key — no surrounding whitespace, no path qualifiers.
1748        assert_eq!(map.len(), 1);
1749        assert!(map.contains_key("AeronPublication"));
1750    }
1751}
1752
1753#[cfg(test)]
1754mod arg_classification_tests {
1755    use super::{Arg, ArgProcessing};
1756
1757    fn arg(c_type: &str) -> Arg {
1758        Arg {
1759            name: "x".to_string(),
1760            c_type: c_type.to_string(),
1761            processing: ArgProcessing::Default,
1762        }
1763    }
1764
1765    #[test]
1766    fn classifies_c_strings() {
1767        assert!(arg("* const :: std :: os :: raw :: c_char").is_c_string());
1768        assert!(arg("* const :: std :: os :: raw :: c_char").is_c_string_any());
1769        assert!(arg("* mut :: std :: os :: raw :: c_char").is_mut_c_string());
1770        assert!(arg("* mut :: std :: os :: raw :: c_char").is_c_string_any());
1771        // Not a C string:
1772        assert!(!arg("* const u8").is_c_string());
1773        assert!(!arg("i32").is_c_string_any());
1774    }
1775
1776    #[test]
1777    fn classifies_byte_arrays() {
1778        assert!(arg("* const u8").is_byte_array());
1779        assert!(arg("* mut u8").is_byte_array());
1780        assert!(arg("* mut u8").is_mut_byte_array());
1781        assert!(!arg("* const u8").is_mut_byte_array());
1782        assert!(!arg("* const :: std :: os :: raw :: c_char").is_byte_array());
1783    }
1784
1785    #[test]
1786    fn classifies_raw_int_return() {
1787        assert!(arg(":: std :: os :: raw :: c_int").is_c_raw_int());
1788        assert!(!arg("i32").is_c_raw_int());
1789    }
1790
1791    #[test]
1792    fn classifies_void_pointer() {
1793        assert!(arg("* mut :: std :: os :: raw :: c_void").is_c_void());
1794        assert!(!arg("* mut u8").is_c_void());
1795    }
1796
1797    #[test]
1798    fn classifies_mut_pointers_single_vs_double() {
1799        // Single `*mut`:
1800        assert!(arg("* mut i32").is_mut_pointer());
1801        assert!(arg("* mut i32").is_single_mut_pointer());
1802        assert!(!arg("* mut i32").is_double_mut_pointer());
1803        // Double `*mut *mut` (output param / handle out):
1804        assert!(arg("* mut * mut aeron_foo_t").is_mut_pointer());
1805        assert!(arg("* mut * mut aeron_foo_t").is_double_mut_pointer());
1806        assert!(!arg("* mut * mut aeron_foo_t").is_single_mut_pointer());
1807        // `*const` is not a mut pointer:
1808        assert!(!arg("* const i32").is_mut_pointer());
1809    }
1810
1811    #[test]
1812    fn classifies_primitives() {
1813        for ty in [
1814            "i64", "u64", "f32", "f64", "i32", "i16", "u32", "u16", "bool", "usize", "isize",
1815        ] {
1816            assert!(arg(ty).is_primitive(), "{ty} should be primitive");
1817        }
1818        // NOTE: `is_primitive` is a SUFFIX match (`c_type.ends_with(primitive)`),
1819        // so a pointer-to-primitive like `* mut i32` also returns true. This is
1820        // the contract the generator relies on (it composes this with the
1821        // `is_single_mut_pointer` check elsewhere), so pin it here.
1822        assert!(arg("* mut i32").is_primitive());
1823        // But a pointer to a non-primitive (a C struct) is not primitive:
1824        assert!(!arg("* mut aeron_foo_t").is_primitive());
1825    }
1826
1827    #[test]
1828    fn classifies_any_pointer() {
1829        assert!(arg("* const i32").is_any_pointer());
1830        assert!(arg("* mut i32").is_any_pointer());
1831        assert!(!arg("i32").is_any_pointer());
1832    }
1833}
1834
1835fn get_docs(
1836    docs: &BTreeSet<String>,
1837    wrappers: &BTreeMap<String, CWrapper>,
1838    arguments: Option<&Vec<TokenStream>>,
1839) -> Vec<TokenStream> {
1840    let mut first_param = true;
1841    docs.iter()
1842        .flat_map(|d| d.lines())
1843        .filter(|s| {
1844            arguments.is_none()
1845                || !s.contains("@param")
1846                || (s.contains("@param")
1847                    && arguments
1848                        .unwrap()
1849                        .iter()
1850                        .any(|a| s.contains(format!(" {}", a.to_string().split_whitespace().next().unwrap()).as_str())))
1851        })
1852        .map(|doc| {
1853            let mut doc = doc.to_string();
1854
1855            if first_param && doc.contains("@param") {
1856                doc = format!("# Parameters\n{}", doc);
1857                first_param = false;
1858            }
1859
1860            if doc.contains("@param") {
1861                doc = regex::Regex::new("@param\\s+([^ ]+)")
1862                    .unwrap()
1863                    .replace(doc.as_str(), "\n - `$1`")
1864                    .to_string();
1865            }
1866
1867            doc = doc
1868                .replace("@return", "\n# Return\n")
1869                .replace("<p>", "\n")
1870                .replace("</p>", "\n");
1871
1872            doc = wrappers
1873                .values()
1874                .fold(doc, |acc, v| acc.replace(&v.type_name, &format!("`{}`", v.class_name)));
1875
1876            if doc.contains("@deprecated") {
1877                quote! {
1878                    #[deprecated]
1879                    #[doc = #doc]
1880                }
1881            } else {
1882                quote! {
1883                    #[doc = #doc]
1884                }
1885            }
1886        })
1887        .collect()
1888}
1889
1890fn generate_stateless_handler_code(handler: &CHandler) -> TokenStream {
1891    let type_name_ident = format_ident!("{}", handler.type_name);
1892    let callback_fn_name = format_ident!("{}_callback", handler.type_name);
1893    let closure_type_name = format_ident!("{}Callback", snake_to_pascal_case(&handler.type_name));
1894    let logger_type_name = format_ident!("{}Logger", snake_to_pascal_case(&handler.type_name));
1895    let handle_method_name = format_ident!("handle_{}", &handler.type_name[..handler.type_name.len() - 2]);
1896    let closure_return_type = handler.return_type.as_type();
1897
1898    // For stateless callbacks, the FnMut signature takes no args (other than &mut self)
1899    let (fn_mut_args, trait_args): (Vec<TokenStream>, Vec<TokenStream>) = handler
1900        .args
1901        .iter()
1902        .enumerate()
1903        .map(|(i, arg)| {
1904            let return_type = ReturnType::new(arg.clone(), BTreeMap::new());
1905            let type_name = return_type.get_new_return_type(false, false);
1906            let arg_name = format_ident!("arg_{}", i);
1907
1908            let fn_mut_arg = if arg.is_single_mut_pointer() && arg.is_primitive() {
1909                let owned_type: Type = parse_str(arg.c_type.split_whitespace().last().unwrap()).unwrap();
1910                quote! { #owned_type }
1911            } else {
1912                quote! { #type_name }
1913            };
1914
1915            let trait_arg = if type_name.is_empty() {
1916                quote! {}
1917            } else if arg.is_single_mut_pointer() && arg.is_primitive() {
1918                let owned_type: Type = parse_str(arg.c_type.split_whitespace().last().unwrap()).unwrap();
1919                quote! { #arg_name: #owned_type }
1920            } else {
1921                quote! { #arg_name: #type_name }
1922            };
1923
1924            (fn_mut_arg, trait_arg)
1925        })
1926        .filter(|(fn_mut, trait_)| !fn_mut.is_empty() && !trait_.is_empty())
1927        .unzip();
1928
1929    // Store the FnMut signature for use in wrapper code
1930    let fn_mut_sig = quote! {
1931       FnMut(#(#fn_mut_args),*) -> #closure_return_type
1932    };
1933
1934    quote! {
1935        pub trait #closure_type_name {
1936            fn #handle_method_name(&mut self, #(#trait_args),*) -> #closure_return_type;
1937        }
1938
1939        pub struct #logger_type_name;
1940        impl #closure_type_name for #logger_type_name {
1941            fn #handle_method_name(&mut self, #(#trait_args),*) -> #closure_return_type {
1942                log::info!(
1943                    "{}({}\n)",
1944                    stringify!(#handle_method_name),
1945                    ""
1946                );
1947                unimplemented!()
1948            }
1949        }
1950
1951        // Stub callback function for stateless handlers - panics when called
1952        #[allow(dead_code)]
1953        unsafe extern "C" fn #callback_fn_name<F: #closure_type_name>(#(#trait_args),*) -> #closure_return_type {
1954            unimplemented!("Stateless handler callback called - not supported")
1955        }
1956    }
1957}
1958
1959pub fn generate_handlers(handler: &mut CHandler, bindings: &CBinding) -> TokenStream {
1960    if handler
1961        .args
1962        .iter()
1963        .any(|arg| arg.is_primitive() && arg.is_mut_pointer())
1964    {
1965        return quote! {};
1966    }
1967
1968    // Check for stateless callbacks (no c_void parameter)
1969    // These cannot use closure pattern as there's no clientd to pass the closure through
1970    // Generate only the trait and logger, not the closure infrastructure
1971    let has_c_void_param = handler.args.iter().any(|a| a.is_c_void());
1972    let is_stateless = !has_c_void_param;
1973
1974    if is_stateless {
1975        let closure_type_name = format_ident!("{}Callback", snake_to_pascal_case(&handler.type_name));
1976        // Set the FnMut signature for stateless handlers (needed by wrapper code generation)
1977        let fn_mut_args: Vec<TokenStream> = handler
1978            .args
1979            .iter()
1980            .map(|arg| {
1981                let return_type = ReturnType::new(arg.clone(), bindings.wrappers.clone());
1982                let type_name = return_type.get_new_return_type(false, false);
1983                if arg.is_single_mut_pointer() && arg.is_primitive() {
1984                    let owned_type: Type = parse_str(arg.c_type.split_whitespace().last().unwrap()).unwrap();
1985                    quote! { #owned_type }
1986                } else {
1987                    quote! { #type_name }
1988                }
1989            })
1990            .filter(|t| !t.is_empty())
1991            .collect();
1992        let closure_return_type = handler.return_type.as_type();
1993        handler.fn_mut_signature = quote! {
1994           FnMut(#(#fn_mut_args),*) -> #closure_return_type
1995        };
1996        handler.closure_type_name = quote! {
1997           #closure_type_name
1998        };
1999        return generate_stateless_handler_code(handler);
2000    }
2001
2002    let fn_name = format_ident!("{}_callback", handler.type_name);
2003    let closure_fn_name = format_ident!("{}_callback_for_once_closure", handler.type_name);
2004    let doc_comments: Vec<TokenStream> = handler
2005        .docs
2006        .iter()
2007        .flat_map(|doc| doc.lines())
2008        .map(|line| quote! { #[doc = #line] })
2009        .collect();
2010
2011    let closure = handler
2012        .args
2013        .iter()
2014        .find(|a| a.is_c_void())
2015        .map(|a| a.name.clone())
2016        .unwrap_or_else(|| "state".to_string());
2017    let closure_name = format_ident!("{}", closure);
2018    let closure_type_name = format_ident!("{}Callback", snake_to_pascal_case(&handler.type_name));
2019    let closure_return_type = handler.return_type.as_type();
2020
2021    let logger_type_name = format_ident!("{}Logger", snake_to_pascal_case(&handler.type_name));
2022
2023    let handle_method_name = format_ident!("handle_{}", &handler.type_name[..handler.type_name.len() - 2]);
2024
2025    let no_method_name = format_ident!(
2026        "no_{}_handler",
2027        &handler.type_name[..handler.type_name.len() - 2]
2028            .replace("_on_", "_")
2029            .replace("aeron_", "")
2030    );
2031
2032    let args: Vec<TokenStream> = handler
2033        .args
2034        .iter()
2035        .map(|arg| {
2036            let arg_name = arg.as_ident();
2037            // do not need to convert as its calling hour handler
2038            let arg_type: Type = arg.as_type();
2039            quote! { #arg_name: #arg_type }
2040        })
2041        .filter(|t| !t.is_empty())
2042        .collect();
2043
2044    let arg_names_for_logging: Vec<TokenStream> = handler
2045        .args
2046        .iter()
2047        .map(|arg| {
2048            let arg_name = arg.as_ident();
2049            quote! { format!("{} = {:?}", stringify!(#arg_name), #arg_name) }
2050        })
2051        .collect();
2052
2053    let converted_args: Vec<TokenStream> = handler
2054        .args
2055        .iter()
2056        .filter_map(|arg| {
2057            let name = &arg.name;
2058            let arg_name = arg.as_ident();
2059            // Skip closure argument - it's only used to get the closure reference
2060            // All other args (including other c_void args) are passed to the handler
2061            if name == &closure {
2062                None
2063            } else {
2064                let return_type = ReturnType::new(arg.clone(), bindings.wrappers.clone());
2065                Some(return_type.handle_c_to_rs_return(quote! {#arg_name}, false, false))
2066            }
2067        })
2068        .filter(|t| !t.is_empty())
2069        .collect();
2070
2071    let closure_args: Vec<TokenStream> = handler
2072        .args
2073        .iter()
2074        .filter_map(|arg| {
2075            let name = &arg.name;
2076            if name == &closure {
2077                return None;
2078            }
2079
2080            let return_type = ReturnType::new(arg.clone(), bindings.wrappers.clone());
2081            let type_name = return_type.get_new_return_type(false, false);
2082            let field_name = format_ident!("{}", name);
2083            if type_name.is_empty() {
2084                None
2085            } else {
2086                Some(quote! {
2087                    #field_name: #type_name
2088                })
2089            }
2090        })
2091        .filter(|t| !t.is_empty())
2092        .collect();
2093
2094    let mut log_field_names = vec![];
2095    let closure_args_in_logger: Vec<TokenStream> = handler
2096        .args
2097        .iter()
2098        .filter_map(|arg| {
2099            let name = &arg.name;
2100            if name == &closure {
2101                return None;
2102            }
2103
2104            let return_type = ReturnType::new(arg.clone(), bindings.wrappers.clone());
2105            let type_name = return_type.get_new_return_type(false, false);
2106            let field_name = format_ident!("{}", name);
2107            if type_name.is_empty() {
2108                None
2109            } else {
2110                log_field_names.push(Some(
2111                    quote! { format!("{} : {:?}", stringify!(#field_name), #field_name) },
2112                ));
2113
2114                Some(quote! {
2115                    #field_name: #type_name
2116                })
2117            }
2118        })
2119        .filter(|t| !t.is_empty())
2120        .collect();
2121
2122    if log_field_names.is_empty() {
2123        log_field_names.push(Some(quote! { "" }));
2124    }
2125
2126    let fn_mut_args: Vec<TokenStream> = handler
2127        .args
2128        .iter()
2129        .filter_map(|arg| {
2130            let name = &arg.name;
2131            if name == &closure {
2132                return None;
2133            }
2134
2135            let return_type = ReturnType::new(arg.clone(), bindings.wrappers.clone());
2136            let type_name = return_type.get_new_return_type(false, false);
2137            if arg.is_single_mut_pointer() && arg.is_primitive() {
2138                let owned_type: Type = parse_str(arg.c_type.split_whitespace().last().unwrap()).unwrap();
2139                return Some(quote! { #owned_type });
2140            } else {
2141                return Some(quote! {
2142                    #type_name
2143                });
2144            }
2145        })
2146        .filter(|t| !t.is_empty())
2147        .collect();
2148
2149    handler.fn_mut_signature = quote! {
2150       FnMut(#(#fn_mut_args),*) -> #closure_return_type
2151    };
2152    handler.closure_type_name = quote! {
2153       #closure_type_name
2154    };
2155
2156    let logger_return_type = if closure_return_type.to_token_stream().to_string().eq("()") {
2157        closure_return_type.clone().to_token_stream()
2158    } else {
2159        quote! {
2160            unimplemented!()
2161        }
2162    };
2163
2164    let wrapper_closure_args: Vec<TokenStream> = handler
2165        .args
2166        .iter()
2167        .filter_map(|arg| {
2168            let name = &arg.name;
2169            if name == &closure {
2170                return None;
2171            }
2172
2173            let field_name = format_ident!("{}", name);
2174            let return_type = ReturnType::new(arg.clone(), bindings.wrappers.clone()).get_new_return_type(false, false);
2175            if return_type.is_empty() {
2176                None
2177            } else {
2178                Some(quote! { #field_name })
2179            }
2180        })
2181        .filter(|t| !t.is_empty())
2182        .collect();
2183
2184    quote! {
2185        #(#doc_comments)*
2186        ///
2187        ///
2188        /// _(note you must copy any arguments that you use afterwards even those with static lifetimes)_
2189        pub trait #closure_type_name {
2190            fn #handle_method_name(&mut self, #(#closure_args),*) -> #closure_return_type;
2191        }
2192
2193        pub struct #logger_type_name;
2194        impl #closure_type_name for #logger_type_name {
2195            fn #handle_method_name(&mut self, #(#closure_args_in_logger),*) -> #closure_return_type {
2196                log::info!("{}({}\n)",
2197                    stringify!(#handle_method_name),
2198                    [#(#log_field_names),*].join(", "),
2199                );
2200                #logger_return_type
2201            }
2202        }
2203
2204        unsafe impl Send for #logger_type_name {}
2205        unsafe impl Sync for #logger_type_name {}
2206
2207        /// Any closure with the matching signature is a callback: methods that retain it
2208        /// heap-allocate it into a [`Handler`] owned by the registering resource.
2209        impl<F: FnMut(#(#fn_mut_args),*) -> #closure_return_type> #closure_type_name for F {
2210            #[inline]
2211            fn #handle_method_name(&mut self, #(#closure_args),*) -> #closure_return_type {
2212                self(#(#wrapper_closure_args),*)
2213            }
2214        }
2215
2216        /// Pass an existing [`Handler`] clone anywhere a callback value is expected, e.g.
2217        /// to share one callback instance across several registrations.
2218        impl<T: #closure_type_name> #closure_type_name for Handler<T> {
2219            #[inline]
2220            fn #handle_method_name(&mut self, #(#closure_args),*) -> #closure_return_type {
2221                let inner = unsafe { &mut *self.inner.get() };
2222                inner.#handle_method_name(#(#wrapper_closure_args),*)
2223            }
2224        }
2225
2226        // `NoHandler` implements every generated callback trait so that
2227        // `Handlers::NONE` (= `None::<&Handler<NoHandler>>`) can pin the callback
2228        // generic at any call site. Its body is unreachable: the C side receives a
2229        // null callback + null clientd, so this method can never be invoked.
2230        impl #closure_type_name for NoHandler {
2231            fn #handle_method_name(&mut self, #(#closure_args),*) -> #closure_return_type {
2232                unreachable!("NoHandler is a None sentinel; its callback must never be invoked")
2233            }
2234        }
2235
2236        // #[no_mangle]
2237        #[allow(dead_code)]
2238        #(#doc_comments)*
2239        unsafe extern "C" fn #fn_name<F: #closure_type_name>(
2240            #(#args),*
2241        ) -> #closure_return_type
2242        {
2243            #[cfg(debug_assertions)]
2244            if #closure_name.is_null() {
2245                unimplemented!("closure should not be null")
2246            }
2247            #[cfg(feature = "extra-logging")]
2248            {
2249                log::debug!("calling {}", stringify!(#handle_method_name));
2250            }
2251            #[cfg(feature = "log-c-bindings")]
2252            log::debug!(
2253                "{}({}\n)",
2254                stringify!(#fn_name),
2255                [#(#arg_names_for_logging),*].join(", ")
2256            );
2257            let closure: &mut F = unsafe { &mut *(#closure_name as *mut F) };
2258            closure.#handle_method_name(#(#converted_args),*)
2259        }
2260
2261        // #[no_mangle]
2262        #[allow(dead_code)]
2263        #(#doc_comments)*
2264        unsafe extern "C" fn #closure_fn_name<F: FnMut(#(#fn_mut_args),*) -> #closure_return_type>(
2265            #(#args),*
2266        ) -> #closure_return_type
2267        {
2268            #[cfg(debug_assertions)]
2269            if #closure_name.is_null() {
2270                unimplemented!("closure should not be null")
2271            }
2272            #[cfg(feature = "extra-logging")]
2273            {
2274                log::debug!("calling {}", stringify!(#closure_fn_name));
2275            }
2276            #[cfg(feature = "log-c-bindings")]
2277            log::debug!(
2278                "{}({}\n)",
2279                stringify!(#closure_fn_name),
2280                [#(#arg_names_for_logging),*].join(", ")
2281            );
2282            let closure: &mut F = unsafe { &mut *(#closure_name as *mut F) };
2283            closure(#(#converted_args),*)
2284        }
2285
2286    }
2287}
2288
2289pub fn generate_rust_code(
2290    wrapper: &CWrapper,
2291    wrappers: &BTreeMap<String, CWrapper>,
2292    include_common_code: bool,
2293    include_clippy: bool,
2294    include_aeron_client_registering_resource_t: bool,
2295    closure_handlers: &Vec<CHandler>,
2296) -> TokenStream {
2297    let class_name = Ident::new(&wrapper.class_name, proc_macro2::Span::call_site());
2298    let type_name = Ident::new(&wrapper.type_name, proc_macro2::Span::call_site());
2299
2300    let mut additional_outer_impls = vec![];
2301    let mut debug_fields = vec![];
2302
2303    let methods = wrapper.generate_methods(
2304        wrappers,
2305        closure_handlers,
2306        &mut additional_outer_impls,
2307        &mut debug_fields,
2308    );
2309    let tests = wrapper.generate_allocation_test();
2310    let mut constructor_fields = vec![];
2311    let mut new_ref_set_none = vec![];
2312    let constructor = wrapper.generate_constructor(wrappers, &mut constructor_fields, &mut new_ref_set_none);
2313
2314    let async_impls = if wrapper.type_name.starts_with("aeron_async_")
2315        || wrapper.type_name.starts_with("aeron_archive_async_")
2316    {
2317        let new_method = wrapper.methods.iter().find(|m| m.fn_name == wrapper.without_name);
2318
2319        if let Some(new_method) = new_method {
2320            let main_type = &wrapper.type_name.replace("_async_", "_").replace("_add_", "_");
2321            let main = get_possible_wrappers(main_type)
2322                .iter()
2323                .filter_map(|f| wrappers.get(f))
2324                .next()
2325                .expect(&format!("failed to find main type {}", main_type));
2326
2327            let poll_method = main
2328                .methods
2329                .iter()
2330                .find(|m| m.fn_name == format!("{}_poll", wrapper.without_name))
2331                .unwrap();
2332
2333            let main_class_name = format_ident!("{}", main.class_name);
2334            let async_class_name = format_ident!("{}", wrapper.class_name);
2335            let poll_method_name = format_ident!("{}_poll", wrapper.without_name);
2336            let new_method_name = format_ident!("{}", new_method.fn_name);
2337
2338            let client_class = wrappers
2339                .get(
2340                    new_method
2341                        .arguments
2342                        .iter()
2343                        .skip(1)
2344                        .next()
2345                        .unwrap()
2346                        .c_type
2347                        .split_whitespace()
2348                        .last()
2349                        .unwrap(),
2350                )
2351                .unwrap();
2352            let client_type = format_ident!("{}", client_class.class_name);
2353            let client_type_method_name = format_ident!(
2354                "{}",
2355                new_method
2356                    .fn_name
2357                    .replace(&format!("{}_", client_class.without_name), "")
2358            );
2359            let client_type_method_name_without_async = format_ident!(
2360                "{}",
2361                new_method
2362                    .fn_name
2363                    .replace(&format!("{}_", client_class.without_name), "")
2364                    .replace("async_", "")
2365            );
2366
2367            let init_args: Vec<TokenStream> = poll_method
2368                .arguments
2369                .iter()
2370                .enumerate()
2371                .filter_map(|(idx, arg)| {
2372                    if idx == 0 {
2373                        Some(quote! { ctx_field })
2374                    } else {
2375                        let arg_name = arg.as_ident();
2376                        let arg_name = ReturnType::new(arg.clone(), wrappers.clone())
2377                            .handle_rs_to_c_return(quote! { #arg_name }, false);
2378                        Some(quote! { #arg_name })
2379                    }
2380                })
2381                .filter(|t| !t.is_empty())
2382                .collect();
2383
2384            let new_args: Vec<TokenStream> = poll_method
2385                .arguments
2386                .iter()
2387                .enumerate()
2388                .filter_map(|(idx, arg)| {
2389                    if idx == 0 {
2390                        None
2391                    } else {
2392                        let arg_name = arg.as_ident();
2393                        let arg_type = ReturnType::new(arg.clone(), wrappers.clone()).get_new_return_type(false, true);
2394                        if arg_type.clone().into_token_stream().is_empty() {
2395                            None
2396                        } else {
2397                            Some(quote! { #arg_name: #arg_type })
2398                        }
2399                    }
2400                })
2401                .filter(|t| !t.is_empty())
2402                .collect();
2403
2404            let async_init_args: Vec<TokenStream> = new_method
2405                .arguments
2406                .iter()
2407                .enumerate()
2408                .filter_map(|(idx, arg)| {
2409                    if idx == 0 {
2410                        Some(quote! { ctx_field })
2411                    } else {
2412                        let arg_name = arg.as_ident();
2413                        let arg_name = ReturnType::new(arg.clone(), wrappers.clone())
2414                            .handle_rs_to_c_return(quote! { #arg_name }, false);
2415                        Some(quote! { #arg_name })
2416                    }
2417                })
2418                .filter(|t| !t.is_empty())
2419                .collect();
2420
2421            // Generate logging for async new method arguments (as token stream)
2422            let async_log_expr_tokens = CWrapper::generate_arg_logging(&new_method.arguments, &async_init_args);
2423
2424            let generic_types: Vec<TokenStream> = new_method
2425                .arguments
2426                .iter()
2427                .flat_map(|arg| {
2428                    ReturnType::new(arg.clone(), wrappers.clone())
2429                        .method_generics_for_where(true)
2430                        .into_iter()
2431                })
2432                .collect_vec();
2433            let where_clause_async = if generic_types.is_empty() {
2434                quote! {}
2435            } else {
2436                quote! { <#(#generic_types),*> }
2437            };
2438            let generic_types: Vec<TokenStream> = poll_method
2439                .arguments
2440                .iter()
2441                .flat_map(|arg| {
2442                    ReturnType::new(arg.clone(), wrappers.clone())
2443                        .method_generics_for_where(true)
2444                        .into_iter()
2445                })
2446                .collect_vec();
2447            let where_clause_main = if generic_types.is_empty() {
2448                quote! {}
2449            } else {
2450                quote! { <#(#generic_types),*> }
2451            };
2452            let async_new_args: Vec<TokenStream> = new_method
2453                .arguments
2454                .iter()
2455                .enumerate()
2456                .filter_map(|(idx, arg)| {
2457                    if idx == 0 {
2458                        None
2459                    } else {
2460                        let arg_name = arg.as_ident();
2461                        let arg_type = ReturnType::new(arg.clone(), wrappers.clone()).get_new_return_type(false, true);
2462                        if arg_type.clone().into_token_stream().is_empty() {
2463                            None
2464                        } else {
2465                            Some(quote! { #arg_name: #arg_type })
2466                        }
2467                    }
2468                })
2469                .filter(|t| !t.is_empty())
2470                .collect();
2471
2472            let async_dependancies = async_new_args
2473                .iter()
2474                .filter(|a| a.to_string().contains(" : Aeron") || a.to_string().contains(" : & Aeron"))
2475                .map(|e| {
2476                    let var_name = format_ident!("{}", e.to_string().split_whitespace().next().unwrap());
2477                    quote! {
2478                        result.inner.add_dependency(#var_name.clone());
2479                    }
2480                })
2481                .collect_vec();
2482
2483            // The `Aeron` client argument, if any — needed both to keep retained handlers
2484            // alive (above) and to call `..._cancel(client, async)` on drop/`.cancel()` below.
2485            let async_client_var: Option<Ident> = async_new_args
2486                .iter()
2487                .find(|a| a.to_string().contains(" : Aeron") || a.to_string().contains(" : & Aeron"))
2488                .map(|a| format_ident!("{}", a.to_string().split_whitespace().next().unwrap()));
2489
2490            let mut async_handler_deps: Vec<TokenStream> =
2491                CWrapper::handler_dependency_registrations(&new_method.arguments);
2492
2493            // The conductor thread can invoke retained callbacks (e.g. image lifecycle
2494            // handlers) for as long as the *client* lives — even if this async poller is
2495            // dropped without ever being polled. Anchor each handler to the client too, so
2496            // dropping an unpolled poller cannot free a callback C still points at.
2497            if let Some(client_arg) = async_new_args
2498                .iter()
2499                .find(|a| a.to_string().contains(" : Aeron") || a.to_string().contains(" : & Aeron"))
2500            {
2501                let client_var = format_ident!("{}", client_arg.to_string().split_whitespace().next().unwrap());
2502                let client_anchored: Vec<TokenStream> = new_method
2503                    .arguments
2504                    .iter()
2505                    .filter_map(|arg| {
2506                        if let ArgProcessing::Handler(_) = &arg.processing {
2507                            if !arg.is_mut_pointer() {
2508                                let name = arg.as_ident();
2509                                return Some(quote! {
2510                                    if let Some(__handler) = #name {
2511                                        if let Some(__client_inner) = #client_var.inner.as_owned() {
2512                                            __client_inner.add_dependency(__handler.clone());
2513                                        }
2514                                    }
2515                                });
2516                            }
2517                        }
2518                        None
2519                    })
2520                    .collect();
2521                async_handler_deps.extend(client_anchored);
2522            }
2523
2524            let async_new_args_for_client = async_new_args.iter().skip(1).cloned().collect_vec();
2525
2526            let async_new_args_name_only: Vec<TokenStream> = new_method
2527                .arguments
2528                .iter()
2529                .enumerate()
2530                .filter_map(|(idx, arg)| {
2531                    if idx < 2 {
2532                        None
2533                    } else {
2534                        let arg_name = arg.as_ident();
2535                        let arg_type = ReturnType::new(arg.clone(), wrappers.clone()).get_new_return_type(false, false);
2536                        if arg_type.clone().into_token_stream().is_empty() {
2537                            None
2538                        } else {
2539                            Some(quote! { #arg_name })
2540                        }
2541                    }
2542                })
2543                .filter(|t| !t.is_empty())
2544                .collect();
2545
2546            // Generate logging for poll method arguments (as token stream)
2547            let poll_log_expr_tokens = CWrapper::generate_arg_logging(&poll_method.arguments, &init_args);
2548
2549            let close_cleanup = if let Some(close_method) = main.get_close_method() {
2550                let close_fn = format_ident!("{}", close_method.fn_name);
2551                // Aeron C close functions now take optional notification
2552                // callbacks and a clientd pointer.  Use Default::default()
2553                // for any arg beyond the resource pointer — it resolves to
2554                // `None` / `null_mut()` via type inference at the call site.
2555                let extra_args: Vec<TokenStream> = close_method
2556                    .arguments
2557                    .iter()
2558                    .skip(1)
2559                    .map(|_| quote! { Default::default() })
2560                    .collect();
2561                quote! {
2562                    Some(Box::new(move |ptr| unsafe {
2563                        #close_fn(*ptr, #(#extra_args),*)
2564                    }))
2565                }
2566            } else {
2567                quote! { None }
2568            };
2569
2570            // Safety: `ManagedCResource::mark_resource_released()` (called by `poll()` on every
2571            // terminal outcome — success or a real error) already nulls the stored pointer, and
2572            // `close_shared()` only invokes the cleanup closure when the pointer is non-null. So
2573            // once `poll()` has ever returned `Ok(Some(_))` or `Err(_)`, this cleanup can no
2574            // longer fire — cancel() is never called on an already-resolved/consumed pointer,
2575            // whether that happens via `Drop` or an explicit `.cancel()` call afterwards.
2576            let cancel_method_name = format!("{}_cancel", new_method.fn_name);
2577            let async_cancel_cleanup = if let (Some(cancel_method), Some(client_var)) = (
2578                client_class.methods.iter().find(|m| m.fn_name == cancel_method_name),
2579                async_client_var.clone(),
2580            ) {
2581                let cancel_fn = format_ident!("{}", cancel_method.fn_name);
2582                let client_owned = format_ident!("{}_for_cancel", client_var);
2583                Some(quote! {
2584                    {
2585                        let #client_owned = #client_var.clone();
2586                        Some(Box::new(move |ptr| unsafe {
2587                            log::warn!(
2588                                "auto-cancelling {} (poll() never resolved it before drop/cancel) to avoid leaking the pending Aeron registration",
2589                                stringify!(#async_class_name)
2590                            );
2591                            #cancel_fn(#client_owned.get_inner(), *ptr)
2592                        }))
2593                    }
2594                })
2595            } else {
2596                None
2597            };
2598            let async_new_cleanup_tokens = async_cancel_cleanup.clone().unwrap_or(quote! { None });
2599            let has_cancel_method = async_cancel_cleanup.is_some();
2600
2601            let cancel_method_impl = if has_cancel_method {
2602                quote! {
2603                    #[doc = r"Cancels this in-progress operation, releasing the pending Aeron"]
2604                    #[doc = r"registration instead of waiting for `poll()`/`poll_blocking()` to"]
2605                    #[doc = r"resolve it or for this value to be dropped (dropping an unresolved"]
2606                    #[doc = r"poller does this automatically — this is only needed to give up"]
2607                    #[doc = r"earlier than the drop would happen naturally)."]
2608                    #[doc = r""]
2609                    #[doc = r"A no-op if `poll()` has already returned a terminal result (`Some`"]
2610                    #[doc = r"or an `Err`) — cancelling after that point would either be invalid"]
2611                    #[doc = r"(the C struct is no longer valid) or unnecessary (nothing pending"]
2612                    #[doc = r"left to cancel)."]
2613                    #[inline]
2614                    pub fn cancel(&self) -> Result<(), AeronCError> {
2615                        if let Some(inner) = self.inner.as_owned() {
2616                            inner.close_shared()
2617                        } else {
2618                            Ok(())
2619                        }
2620                    }
2621                }
2622            } else {
2623                quote! {}
2624            };
2625
2626            quote! {
2627                    impl #main_class_name {
2628                        #[inline]
2629                        pub fn new #where_clause_main (#(#new_args),*) -> Result<Self, AeronCError> {
2630                            let resource = ManagedCResource::new(
2631                                move |ctx_field| unsafe {
2632                                    #[cfg(feature = "log-c-bindings")]
2633                                    {
2634                                        let log_args = #poll_log_expr_tokens;
2635                                        log::info!("{}({})", stringify!(#poll_method_name), log_args);
2636                                    }
2637                                    #poll_method_name(#(#init_args),*)
2638                                },
2639                                #close_cleanup,
2640                                false,
2641                            )?;
2642                            Ok(Self {
2643                                inner: CResource::OwnedOnHeap(RcOrArc::new(resource)),
2644                            })
2645                        }
2646                    }
2647
2648                    impl #client_type {
2649                        #[doc = r"# Handler lifetime and async close"]
2650                        #[doc = r""]
2651                        #[doc = r"If this call takes a [`Handler`], the C close for the resulting resource"]
2652                        #[doc = r"(e.g. `aeron_subscription_close`) is **asynchronous** — the conductor thread"]
2653                        #[doc = r"may still invoke the handler's callback (e.g. `on_available_image`) after"]
2654                        #[doc = r"`close()`/`drop` has already returned on the calling thread. Releasing the"]
2655                        #[doc = r"handler's value immediately on close would risk a use-after-free from that"]
2656                        #[doc = r"still-in-flight callback."]
2657                        #[doc = r""]
2658                        #[doc = r"To make this safe without requiring the caller to track it, this method"]
2659                        #[doc = r"stores a clone of the owning [`Aeron`] client as a *dependency* on the"]
2660                        #[doc = r"resource being created — which transitively keeps every [`Handler`] clone"]
2661                        #[doc = r"already registered as a dependency of that resource alive for as long as"]
2662                        #[doc = r"the client itself lives, regardless of when the resource closes. No manual"]
2663                        #[doc = r"`release()` call is needed."]
2664                        #[doc = r""]
2665                        #[doc = r"This differs from the Aeron C++ wrapper, which instead frees the handler"]
2666                        #[doc = r"as the final step of `on_cmd_close_subscription` on the conductor thread —"]
2667                        #[doc = r"i.e. it ties the handler's lifetime to the close completing, not to the"]
2668                        #[doc = r"client. Rusteron's approach is simpler and avoids needing a conductor-side"]
2669                        #[doc = r"hook, but it means handler dependencies accumulate on the client's"]
2670                        #[doc = r"dependency list for the client's lifetime (they are small `Arc` clones, one"]
2671                        #[doc = r"per call, and are only dropped when the client itself drops)."]
2672                        #[inline]
2673                        pub fn #client_type_method_name #where_clause_async(&self, #(#async_new_args_for_client),*) -> Result<#async_class_name, AeronCError> {
2674                            let mut result =  #async_class_name::new(self, #(#async_new_args_name_only),*);
2675                            if let Ok(result) = &mut result {
2676                                result.inner.add_dependency(self.clone());
2677                            }
2678
2679                            result
2680                        }
2681                    }
2682
2683                    impl #client_type {
2684                        #[doc = r"Blocking convenience wrapper around the async add operation."]
2685                        #[doc = r""]
2686                        #[doc = r"**Convenience for examples and tests only.** It blocks the calling thread"]
2687                        #[doc = r"in a busy-poll loop until the resource is available or `timeout` elapses. In"]
2688                        #[doc = r"production, prefer the `async_add_*` variant and drive its `poll()` from your"]
2689                        #[doc = r"own event loop rather than blocking on a single operation."]
2690                        #[doc = r""]
2691                        #[doc = r"# Production pattern (pseudo code)"]
2692                        #[doc = r"```text"]
2693                        #[doc = r"// Don't block — drive the async poller from your loop"]
2694                        #[doc = r"let poller = client.async_add_*(...)?;"]
2695                        #[doc = r"loop {"]
2696                        #[doc = r"    if let Some(resource) = poller.poll()? {"]
2697                        #[doc = r"        break; // ready"]
2698                        #[doc = r"    }"]
2699                        #[doc = r"    do_other_work(); // service other subscriptions, timers, etc."]
2700                        #[doc = r"}"]
2701                        #[doc = r"```"]
2702                        #[doc = r""]
2703                        #[doc = r"See `poll_blocking` on the async poller for the same caveat."]
2704                        #[inline]
2705                        pub fn #client_type_method_name_without_async #where_clause_async(&self #(
2706                    , #async_new_args_for_client)*,  timeout: std::time::Duration) -> Result<#main_class_name, AeronCError> {
2707                            let start = std::time::Instant::now();
2708                            loop {
2709                                if let Ok(poller) = #async_class_name::new(self, #(#async_new_args_name_only),*) {
2710                                    while start.elapsed() <= timeout  {
2711                                      if let Some(result) = poller.poll()? {
2712                                          return Ok(result);
2713                                      }
2714                                    #[cfg(debug_assertions)]
2715                                    std::thread::sleep(std::time::Duration::from_millis(10));
2716                                  }
2717                                }
2718                            if start.elapsed() > timeout {
2719                                log::error!("failed async poll for {:?}", self);
2720                                return Err(AeronErrorType::TimedOut.into());
2721                            }
2722                            #[cfg(debug_assertions)]
2723                            std::thread::sleep(std::time::Duration::from_millis(10));
2724                          }
2725            }
2726                    }
2727
2728                    impl #async_class_name {
2729                        #[inline]
2730                        pub fn new #where_clause_async (#(#async_new_args),*) -> Result<Self, AeronCError> {
2731                            let resource_async = ManagedCResource::new(
2732                                move |ctx_field| unsafe {
2733                                    #[cfg(feature = "log-c-bindings")]
2734                                    {
2735                                        let log_args = #async_log_expr_tokens;
2736                                        log::info!("{}({})", stringify!(#new_method_name), log_args);
2737                                    }
2738                                    #new_method_name(#(#async_init_args),*)
2739                                },
2740                                #async_new_cleanup_tokens,
2741                                false,
2742                            )?;
2743                            let result = Self {
2744                                inner: CResource::OwnedOnHeap(RcOrArc::new(resource_async)),
2745                            };
2746                            #(#async_dependancies)*
2747                            // Retained callbacks (e.g. image lifecycle handlers) are stored by
2748                            // the C client; the dependency clones below are propagated to the
2749                            // final resource when poll() succeeds, keeping them alive until it
2750                            // closes.
2751                            #(#async_handler_deps)*
2752                            Ok(result)
2753                        }
2754
2755                        #[inline]
2756                        pub fn poll(&self) -> Result<Option<#main_class_name>, AeronCError> {
2757
2758                            if let Some(inner) = self.inner.as_owned() {
2759                                if inner.is_resource_released() {
2760                                    return Ok(None);
2761                                }
2762                            }
2763
2764                            let mut result = #main_class_name::new(self);
2765                            if let Ok(result) = &mut result {
2766                                unsafe {
2767                                    #[cfg(feature = "multi-threaded")]
2768                                    for d in (&mut *self.inner.as_owned().unwrap().dependencies.lock().unwrap()).iter_mut() {
2769                                      result.inner.add_dependency(d.clone());
2770                                    }
2771                                    #[cfg(not(feature = "multi-threaded"))]
2772                                    for d in (&mut *self.inner.as_owned().unwrap().dependencies.get()).iter_mut() {
2773                                      result.inner.add_dependency(d.clone());
2774                                    }
2775                                }
2776                            }
2777
2778                            match result {
2779                                Ok(result) => {
2780                                    if let Some(inner) = self.inner.as_owned() {
2781                                        inner.mark_resource_released();
2782                                    }
2783                                    Ok(Some(result))
2784                                }
2785                                Err(e) if e.code == 0 => {
2786                                  Ok(None) // try again
2787                                }
2788                                Err(e) => {
2789                                    if let Some(inner) = self.inner.as_owned() {
2790                                        inner.mark_resource_released();
2791                                    }
2792                                    Err(e)
2793                                }
2794                            }
2795                        }
2796
2797                        #cancel_method_impl
2798
2799                        #[doc = r"Polls synchronously until the async operation completes or `timeout` elapses."]
2800                        #[doc = r""]
2801                        #[doc = r"**Convenience for examples and tests only.** It blocks the calling thread"]
2802                        #[doc = r"in a busy-poll loop. In production, drive `poll()` from your own event loop"]
2803                        #[doc = r"(between other work, on a timer, or in a dedicated duty cycle) rather than"]
2804                        #[doc = r"blocking on a single operation."]
2805                        #[doc = r""]
2806                        #[doc = r"# Production pattern (pseudo code)"]
2807                        #[doc = r"```text"]
2808                        #[doc = r"// Don't block — integrate poll() into your existing loop"]
2809                        #[doc = r"loop {"]
2810                        #[doc = r"    if let Some(resource) = poller.poll()? {"]
2811                        #[doc = r"        break; // ready"]
2812                        #[doc = r"    }"]
2813                        #[doc = r"    do_other_work(); // service other subscriptions, timers, etc."]
2814                        #[doc = r"}"]
2815                        #[doc = r"```"]
2816                        #[doc = r""]
2817                        #[doc = r"See the blocking `add_*(.., timeout)` helpers for the same caveat."]
2818                        #[inline]
2819                        pub fn poll_blocking(&self, timeout: std::time::Duration) -> Result<#main_class_name, AeronCError> {
2820                            if let Some(result) = self.poll()? {
2821                                return Ok(result);
2822                            }
2823
2824                            let time = std::time::Instant::now();
2825                            while time.elapsed() < timeout {
2826                                if let Some(result) = self.poll()? {
2827                                    return Ok(result);
2828                                }
2829                                #[cfg(debug_assertions)]
2830                                std::thread::sleep(std::time::Duration::from_millis(10));
2831                            }
2832                            log::error!("failed async poll for {:?}", self);
2833                            Err(AeronErrorType::TimedOut.into())
2834                        }
2835                    }
2836                                }
2837        } else {
2838            quote! {}
2839        }
2840    } else {
2841        quote! {}
2842    };
2843
2844    let mut additional_impls = vec![];
2845
2846    if let Some(close_method) = wrapper.get_close_method() {
2847        let close_deferred_if_shared = wrapper.type_name == "aeron_t" || wrapper.type_name == "aeron_archive_t";
2848        let skip_generated_close =
2849            wrapper.methods.iter().any(|m| m.fn_name.contains("_init")) && !close_deferred_if_shared;
2850        if !skip_generated_close {
2851            let close_fn = format_ident!("{}", close_method.fn_name);
2852            let close_resource_call = if close_deferred_if_shared {
2853                quote! { self.inner.close_resource_deferred_if_shared() }
2854            } else {
2855                quote! { self.inner.close_resource() }
2856            };
2857            // Consuming close(self): run the FFI close through the shared
2858            // ManagedCResource state, then drop this handle.  The cleanup
2859            // closure is taken exactly once across clones, and after
2860            // close(self) this binding is gone.
2861            if close_deferred_if_shared {
2862                additional_impls.push(quote! {
2863                    impl #class_name {
2864                        /// Releases this handle and closes the C client **once the last reference drops**.
2865                        ///
2866                        /// The C client owns and frees every child resource (publications,
2867                        /// subscriptions, counters) when it closes, so while children or clones
2868                        /// are alive this is deferred — equivalent to `drop` — and the surviving
2869                        /// handles remain fully usable. The C close runs when the final
2870                        /// reference (child or clone) is released.
2871                        pub fn close(self) -> Result<(), AeronCError> {
2872                            let result = #close_resource_call;
2873                            // Drop this handle (decrements Rc, releases deps).
2874                            drop(self);
2875                            result
2876                        }
2877
2878                        /// Closes the C client **immediately**, even if children or clones are alive.
2879                        ///
2880                        /// Clones of *this* handle are safe afterwards (their shared pointer is
2881                        /// nulled). Child handles are not:
2882                        ///
2883                        /// # Safety
2884                        /// The C client frees every child resource (publications, subscriptions,
2885                        /// counters) during this call, so surviving child handles dangle. Any use
2886                        /// is use-after-free — **including their `Drop`**, which calls the C close
2887                        /// on the freed pointer (double free). You must `std::mem::forget` every
2888                        /// surviving child, or never return (e.g. `std::process::exit`). Prefer
2889                        /// [`Self::close`], which defers until the last reference drops.
2890                        pub unsafe fn close_now(self) -> Result<(), AeronCError> {
2891                            let result = self.inner.close_resource();
2892                            drop(self);
2893                            result
2894                        }
2895                    }
2896                });
2897            } else {
2898                additional_impls.push(quote! {
2899                    impl #class_name {
2900                        /// Closes this resource in the C client immediately; all clones of this
2901                        /// handle become closed (their pointer is nulled) and must not be used.
2902                        pub fn close(self) -> Result<(), AeronCError> {
2903                            let result = #close_resource_call;
2904                            // Drop this handle (decrements Rc, releases deps).
2905                            drop(self);
2906                            result
2907                        }
2908                    }
2909                });
2910            }
2911
2912            let close_has_notification_handler = !close_deferred_if_shared
2913                && close_method
2914                    .arguments
2915                    .iter()
2916                    .any(|arg| matches!(arg.processing, ArgProcessing::Handler(_)))
2917                && close_method.arguments.iter().any(|arg| arg.is_c_void());
2918
2919            if close_has_notification_handler {
2920                additional_impls.push(quote! {
2921                    impl #class_name {
2922                        /// Like [`Self::close`], but notifies `on_close_complete` when the close
2923                        /// finishes. The handler must outlive the notification (keep your
2924                        /// `Handler` alive until it has fired).
2925                        pub fn close_with_handler<AeronNotificationHandlerImpl: AeronNotificationCallback>(
2926                            self,
2927                            on_close_complete: Option<&Handler<AeronNotificationHandlerImpl>>,
2928                        ) -> Result<(), AeronCError> {
2929                            let result = self.inner.close_resource_with(move |ptr| unsafe {
2930                                #close_fn(
2931                                    *ptr,
2932                                    {
2933                                        let callback: aeron_notification_t = if on_close_complete.is_none() {
2934                                            None
2935                                        } else {
2936                                            Some(aeron_notification_t_callback::<AeronNotificationHandlerImpl>)
2937                                        };
2938                                        callback
2939                                    },
2940                                    on_close_complete
2941                                        .map(|m| m.as_raw())
2942                                        .unwrap_or_else(|| std::ptr::null_mut()),
2943                                )
2944                            });
2945                            drop(self);
2946                            result
2947                        }
2948                    }
2949                });
2950            }
2951
2952            // Generate additional methods for specific types
2953            if wrapper.type_name == "aeron_counter_t" {
2954                additional_impls.push(quote! {
2955                    impl #class_name {
2956                        #[inline]
2957                        pub fn addr_atomic(&self) -> &std::sync::atomic::AtomicI64 {
2958                            unsafe { std::sync::atomic::AtomicI64::from_ptr(self.addr()) }
2959                        }
2960                    }
2961                });
2962            } else if wrapper.type_name == "aeron_publication_t" || wrapper.type_name == "aeron_exclusive_publication_t"
2963            {
2964                additional_impls.push(quote! {
2965                    impl #class_name {
2966                        #[inline]
2967                        pub fn is_ready(&self) -> bool {
2968                            self.is_connected() && self.position_limit() != 0
2969                        }
2970                    }
2971                });
2972            }
2973        }
2974    }
2975
2976    let common_code = if !include_common_code {
2977        quote! {}
2978    } else {
2979        TokenStream::from_str(COMMON_CODE).unwrap()
2980    };
2981    let warning_code = if !include_common_code {
2982        quote! {}
2983    } else {
2984        let mut code = String::new();
2985
2986        if include_clippy {
2987            code.push_str(
2988                "        #![allow(non_upper_case_globals)]
2989        #![allow(non_camel_case_types)]
2990        #![allow(non_snake_case)]
2991        #![allow(clippy::all)]
2992        #![allow(unused_variables)]
2993        #![allow(unused_unsafe)]
2994",
2995            );
2996        }
2997
2998        if include_aeron_client_registering_resource_t {
2999            code.push_str(
3000                "
3001                type aeron_client_registering_resource_t = aeron_client_registering_resource_stct;
3002",
3003            );
3004        }
3005
3006        TokenStream::from_str(code.as_str()).unwrap()
3007    };
3008    let class_docs: Vec<TokenStream> = wrapper
3009        .docs
3010        .iter()
3011        .map(|doc| {
3012            quote! {
3013                #[doc = #doc]
3014            }
3015        })
3016        .collect();
3017
3018    let fields = wrapper.generate_fields(&wrappers, &mut debug_fields);
3019
3020    let default_impl =
3021        if wrapper.has_default_method() && !constructor.iter().map(|x| x.to_string()).join("").trim().is_empty() {
3022            // let default_method_call = if wrapper.has_any_methods() {
3023            //     quote! {
3024            //         #class_name::new_zeroed_on_heap()
3025            //     }
3026            //  } else {
3027            //     quote! {
3028            //         #class_name::new_zeroed_on_stack()
3029            //     }
3030            // };
3031
3032            quote! {
3033                /// This will create an instance where the struct is zeroed, use with care
3034                impl Default for #class_name {
3035                    fn default() -> Self {
3036                        #class_name::new_zeroed_on_heap()
3037                    }
3038                }
3039
3040                impl #class_name {
3041                    /// Regular clone just increases the reference count of underlying count.
3042                    /// `clone_struct` shallow copies the content of the underlying struct on heap.
3043                    ///
3044                    /// NOTE: if the struct has references to other structs these will not be copied
3045                    ///
3046                    /// Must be only used on structs which has no init/clean up methods.
3047                    /// So its dangerous to use with Aeron/AeronContext/AeronPublication/AeronSubscription
3048                    /// More intended for AeronArchiveRecordingDescriptor (note strings will not work as its a shallow copy)
3049                    pub fn clone_struct(&self) -> Self {
3050                        let copy = Self::default();
3051                        // SAFETY: `copy` was created just above and is not aliased yet.
3052                        unsafe { copy.get_inner_mut().clone_from(self.deref()) };
3053                        copy
3054                    }
3055                }
3056            }
3057        } else {
3058            quote! {}
3059        };
3060
3061    quote! {
3062        #warning_code
3063
3064        #(#class_docs)*
3065        #[derive(Clone)]
3066        pub struct #class_name {
3067            inner: CResource<#type_name>,
3068            #(#constructor_fields)*
3069        }
3070
3071        impl core::fmt::Debug for  #class_name {
3072            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3073                if self.inner.get().is_null() {
3074                    f.debug_struct(stringify!(#class_name))
3075                    .field("inner", &"null")
3076                    .finish()
3077                } else {
3078                    f.debug_struct(stringify!(#class_name))
3079                      .field("inner", &self.inner)
3080                      #(#debug_fields)*
3081                      .finish()
3082                }
3083            }
3084        }
3085
3086        impl #class_name {
3087            #(#constructor)*
3088            #(#fields)*
3089            #(#methods)*
3090
3091            #[inline(always)]
3092            pub fn get_inner(&self) -> *mut #type_name {
3093                self.inner.get()
3094            }
3095
3096            /// Mutable access to the underlying C struct, minted from `&self`: nothing
3097            /// prevents two live `&mut` at once, so the caller must ensure exclusive
3098            /// access for the lifetime of the returned reference.
3099            ///
3100            /// # Safety
3101            /// No other reference (`&` or `&mut`) to the underlying struct may be
3102            /// alive while the returned `&mut` is in use.
3103            #[inline(always)]
3104            pub unsafe fn get_inner_mut(&self) -> &mut #type_name {
3105                &mut *self.inner.get()
3106            }
3107
3108            #[inline(always)]
3109            pub fn get_inner_ref(&self) -> & #type_name {
3110                unsafe { &*self.inner.get() }
3111            }
3112        }
3113
3114        impl std::ops::Deref for #class_name {
3115            type Target = #type_name;
3116
3117            fn deref(&self) -> &Self::Target {
3118                self.get_inner_ref()
3119            }
3120        }
3121
3122        impl From<*mut #type_name> for #class_name {
3123            #[inline]
3124            fn from(value: *mut #type_name) -> Self {
3125                #class_name {
3126                    inner: CResource::Borrowed(value),
3127                    #(#new_ref_set_none)*
3128                }
3129            }
3130        }
3131
3132        impl From<#class_name> for *mut #type_name {
3133            #[inline]
3134            fn from(value: #class_name) -> Self {
3135                value.get_inner()
3136            }
3137        }
3138
3139        impl From<&#class_name> for *mut #type_name {
3140            #[inline]
3141            fn from(value: &#class_name) -> Self {
3142                value.get_inner()
3143            }
3144        }
3145
3146        impl From<#class_name> for #type_name {
3147            #[inline]
3148            fn from(value: #class_name) -> Self {
3149                unsafe { *value.get_inner().clone() }
3150            }
3151        }
3152
3153        impl From<*const #type_name> for #class_name {
3154            #[inline]
3155            fn from(value: *const #type_name) -> Self {
3156                #class_name {
3157                    inner: CResource::Borrowed(value as *mut #type_name),
3158                    #(#new_ref_set_none)*
3159                }
3160            }
3161        }
3162
3163        impl From<#type_name> for #class_name {
3164            #[inline]
3165            fn from(value: #type_name) -> Self {
3166                #class_name {
3167                    inner: CResource::OwnedOnStack(MaybeUninit::new(value)),
3168                    #(#new_ref_set_none)*
3169                }
3170            }
3171        }
3172
3173        #(#additional_impls)*
3174
3175        #async_impls
3176        #default_impl
3177        #tests
3178        #common_code
3179    }
3180}