Skip to main content

miniextendr_macros/
miniextendr_impl.rs

1//! # Impl-block Parsing and Wrapper Generation
2//!
3//! This module handles `#[miniextendr]` applied to inherent impl blocks,
4//! generating R wrappers for different class systems.
5//!
6//! ## Architecture Overview
7//!
8//! ```text
9//! ┌─────────────────────────────────────────────────────────────────────────┐
10//! │                         #[miniextendr(r6)]                              │
11//! │                         impl MyType { ... }                             │
12//! └─────────────────────────────────────────────────────────────────────────┘
13//!                                    │
14//!                                    ▼
15//! ┌─────────────────────────────────────────────────────────────────────────┐
16//! │                           PARSING PHASE                                 │
17//! │  ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────────┐ │
18//! │  │   ImplAttrs     │    │  ParsedMethod   │    │    ParsedImpl       │ │
19//! │  │ - class_system  │    │ - ident         │───▶│ - type_ident        │ │
20//! │  │ - class_name    │    │ - receiver      │    │ - class_system      │ │
21//! │  └─────────────────┘    │ - sig           │    │ - methods[]         │ │
22//! │                         │ - doc_tags      │    │ - doc_tags          │ │
23//! │                         │ - method_attrs  │    └─────────────────────┘ │
24//! │                         └─────────────────┘                             │
25//! └─────────────────────────────────────────────────────────────────────────┘
26//!                                    │
27//!                                    ▼
28//! ┌─────────────────────────────────────────────────────────────────────────┐
29//! │                        CODE GENERATION PHASE                            │
30//! │                                                                         │
31//! │  For each method:                                                       │
32//! │  ┌─────────────────────────────────────────────────────────────────┐   │
33//! │  │ generate_c_wrapper_for_method()                                 │   │
34//! │  │   └─▶ CWrapperContext (shared builder from c_wrapper_builder)   │   │
35//! │  │         - thread strategy (main vs worker)                      │   │
36//! │  │         - SEXP→Rust conversion                                  │   │
37//! │  │         - return handling                                       │   │
38//! │  └─────────────────────────────────────────────────────────────────┘   │
39//! │                                                                         │
40//! │  For the whole impl:                                                    │
41//! │  ┌─────────────────────────────────────────────────────────────────┐   │
42//! │  │ generate_{class_system}_r_wrapper()                             │   │
43//! │  │   - generate_env_r_wrapper()   → Type$method(self, ...)         │   │
44//! │  │   - generate_r6_r_wrapper()    → R6Class with methods           │   │
45//! │  │   - generate_s3_r_wrapper()    → generic + method.Type          │   │
46//! │  │   - generate_s4_r_wrapper()    → setClass + setMethod           │   │
47//! │  │   - generate_s7_r_wrapper()    → new_class + method<-           │   │
48//! │  └─────────────────────────────────────────────────────────────────┘   │
49//! └─────────────────────────────────────────────────────────────────────────┘
50//!                                    │
51//!                                    ▼
52//! ┌─────────────────────────────────────────────────────────────────────────┐
53//! │                              OUTPUTS                                    │
54//! │  ┌─────────────────┐  ┌─────────────────┐  ┌─────────────────────────┐ │
55//! │  │ C wrapper fns   │  │ R wrapper code  │  │  R_CallMethodDef       │ │
56//! │  │ C_*_Type__method│  │ (as const str)  │  │  registration entries  │ │
57//! │  └─────────────────┘  └─────────────────┘  └─────────────────────────┘ │
58//! └─────────────────────────────────────────────────────────────────────────┘
59//! ```
60//!
61//! ## Supported Class Systems
62//!
63//! | System | Syntax | R Pattern | Use Case |
64//! |--------|--------|-----------|----------|
65//! | **Env** | `#[miniextendr]` | `obj$method()` | Simple, environment-based dispatch |
66//! | **R6** | `#[miniextendr(r6)]` | `R6Class` with `$new()` | OOP with encapsulation |
67//! | **S3** | `#[miniextendr(s3)]` | `generic(obj)` dispatch | Idiomatic R generics |
68//! | **S4** | `#[miniextendr(s4)]` | `setClass`/`setMethod` | Formal OOP, multiple dispatch |
69//! | **S7** | `#[miniextendr(s7)]` | `new_class`/`new_generic` | Modern R OOP |
70//! | **vctrs** | `#[miniextendr(vctrs)]` | `new_vctr`/`new_rcrd`/`new_list_of` | vctrs-compatible vectors |
71//!
72//! ## Method Categorization
73//!
74//! Methods are categorized by their receiver type:
75//!
76//! | Receiver | [`ReceiverKind`] | Generated as |
77//! |----------|------------------|--------------|
78//! | `&self` | `Ref` | Instance method (immutable) |
79//! | `&mut self` | `RefMut` | Instance method (mutable, chainable) |
80//! | `self: ExternalPtrRef<'_, Self>` | `ExternalPtrRef` | Instance method (immutable, with the R object: sidecar fields) |
81//! | `self: ExternalPtrMut<'_, Self>` | `ExternalPtrMut` | Instance method (mutable, with the R object: sidecar fields) |
82//! | `self: ExternalPtr<Self>` | `ExternalPtrValue` | Instance method (the handle itself, unborrowed) |
83//! | `self` | `Value` | Consuming method: `-> Self` writes the result back into the same handle; `-> Result<Self, E>` / `Option<Self>` run on a clone (`T: Clone`) and overwrite on success; any other return consumes the handle (later use errors) |
84//! | (none) | `None` | Static method or constructor |
85//!
86//! Special methods:
87//! - **Constructor**: Returns `Self` with no receiver, marked with `#[miniextendr(constructor)]` or named `new`
88//! - **Finalizer**: R6 only, marked with `#[miniextendr(r6(finalize))]` (never inferred from the receiver)
89//! - **Private**: R6 only, marked with `#[miniextendr(r6(private))]`
90//!
91//! ## Shared Builders
92//!
93//! This module uses shared infrastructure from:
94//! - [`crate::c_wrapper_builder`]: C wrapper generation with thread strategy
95//! - [`crate::r_wrapper_builder`]: R function signatures and `.Call()` args
96//! - [`crate::method_return_builder`]: Return value handling per class system
97//! - [`crate::roxygen`]: Documentation extraction from Rust doc comments
98//!
99//! ## Example
100//!
101//! ```ignore
102//! #[miniextendr(r6)]
103//! impl Counter {
104//!     fn new(value: i32) -> Self { Counter { value } }
105//!     fn get(&self) -> i32 { self.value }
106//!     fn increment(&mut self) { self.value += 1; }
107//! }
108//! ```
109//!
110//! Generates:
111//! - C wrappers: `C_<crate>_Counter__new`, `C_<crate>_Counter__get`, `C_<crate>_Counter__increment`
112//! - R6Class with `initialize`, `get`, `increment` methods
113//! - Registration entries for R's `.Call()` interface
114
115use proc_macro2::TokenStream;
116use quote::quote;
117
118/// Check if a type is `ExternalPtr<...>` (possibly fully qualified).
119fn is_external_ptr_type(ty: &syn::Type) -> bool {
120    if let syn::Type::Path(type_path) = ty {
121        type_path
122            .path
123            .segments
124            .last()
125            .map(|seg| seg.ident == "ExternalPtr")
126            .unwrap_or(false)
127    } else {
128        false
129    }
130}
131
132/// Refuse the receivers `self: &ExternalPtr<Self>` / `self: &mut
133/// ExternalPtr<Self>`, which #2106 replaced by the views: a handle behind a
134/// reference reads its value unchecked, while a view holds the borrow.
135fn old_handle_receiver(sig: &syn::Signature) -> syn::Result<()> {
136    let Some(syn::FnArg::Receiver(receiver)) = sig.inputs.first() else {
137        return Ok(());
138    };
139    let syn::Type::Reference(reference) = receiver.ty.as_ref() else {
140        return Ok(());
141    };
142    if receiver.colon_token.is_none() || !is_external_ptr_type(&reference.elem) {
143        return Ok(());
144    }
145    let (view, binding) = if reference.mutability.is_some() {
146        ("ExternalPtrMut", "mut self")
147    } else {
148        ("ExternalPtrRef", "self")
149    };
150    Err(syn::Error::new_spanned(
151        &receiver.ty,
152        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("a method takes its object as `{0}: {1}<\'_, Self>`: the wrapper borrows the value for the call, and the view carries the R object for the sidecar accessors (`Self::field(&self)`); use `&self` / `&mut self` when the body needs only the value, or `self: ExternalPtr<Self>` for the unborrowed handle",
                binding, view))
    })format!(
153            "a method takes its object as `{binding}: {view}<'_, Self>`: the wrapper borrows the \
154             value for the call, and the view carries the R object for the sidecar accessors \
155             (`Self::field(&self)`); use `&self` / `&mut self` when the body needs only the \
156             value, or `self: ExternalPtr<Self>` for the unborrowed handle"
157        ),
158    ))
159}
160
161/// Rewrite methods with handle receivers so they compile on stable Rust
162/// (which lacks `arbitrary_self_types`).
163///
164/// Handles:
165/// - `self: ExternalPtrRef<'_, Self>` → `__miniextendr_self: ExternalPtrRef<'_, Self>`
166/// - `self: ExternalPtrMut<'_, Self>` → `__miniextendr_self: ExternalPtrMut<'_, Self>`
167/// - `self: ExternalPtr<Self>` → `__miniextendr_self: ExternalPtr<Self>`
168///
169/// Also replaces all `self` references in the method body with `__miniextendr_self`.
170fn rewrite_external_ptr_receivers(mut item_impl: syn::ItemImpl) -> syn::ItemImpl {
171    for item in &mut item_impl.items {
172        let syn::ImplItem::Fn(method) = item else {
173            continue;
174        };
175        if crate::trait_receiver::owner_receiver(&method.sig).is_some() {
176            crate::trait_receiver::rewrite(&mut method.sig, Some(&mut method.block));
177            continue;
178        }
179        let Some(syn::FnArg::Receiver(receiver)) = method.sig.inputs.first() else {
180            continue;
181        };
182        if receiver.colon_token.is_none() || !is_external_ptr_type(receiver.ty.as_ref()) {
183            continue;
184        }
185
186        // self: ExternalPtr<Self> (by value)
187        let receiver_ident =
188            proc_macro2::Ident::new("__miniextendr_self", proc_macro2::Span::mixed_site());
189        let binding_mutability = receiver.mutability;
190        let inner_ty = &receiver.ty;
191        let new_param: syn::FnArg = ::syn::__private::parse_quote({
        let mut _s = ::quote::__private::TokenStream::new();
        ::quote::ToTokens::to_tokens(&binding_mutability, &mut _s);
        ::quote::ToTokens::to_tokens(&receiver_ident, &mut _s);
        ::quote::__private::push_colon(&mut _s);
        ::quote::ToTokens::to_tokens(&inner_ty, &mut _s);
        _s
    })syn::parse_quote! {
192            #binding_mutability #receiver_ident: #inner_ty
193        };
194
195        // Replace first parameter
196        let inputs: Vec<syn::FnArg> = method.sig.inputs.iter().cloned().collect();
197        let mut new_inputs: Vec<syn::FnArg> = Vec::with_capacity(inputs.len());
198        new_inputs.push(new_param);
199        new_inputs.extend(inputs.into_iter().skip(1));
200        method.sig.inputs = new_inputs.into_iter().collect();
201
202        crate::receiver_body::rewrite(&mut method.block, &receiver_ident);
203    }
204    item_impl
205}
206
207/// Strip `#[miniextendr(...)]` attributes and roxygen doc tags from an impl block and
208/// all of its items (functions, constants, types, macros).
209///
210/// Called before re-emitting the original impl block so that proc-macro attributes
211/// do not appear in the compiler output. Returns the cleaned impl block.
212fn strip_miniextendr_attrs_from_impl(mut item_impl: syn::ItemImpl) -> syn::ItemImpl {
213    item_impl.attrs = crate::roxygen::strip_roxygen_from_attrs(&item_impl.attrs);
214    item_impl
215        .attrs
216        .retain(|attr| !attr.path().is_ident("miniextendr"));
217    for item in &mut item_impl.items {
218        match item {
219            syn::ImplItem::Fn(fn_item) => {
220                fn_item.attrs = crate::roxygen::strip_roxygen_from_attrs(&fn_item.attrs);
221                fn_item
222                    .attrs
223                    .retain(|attr| !attr.path().is_ident("miniextendr"));
224            }
225            syn::ImplItem::Const(const_item) => {
226                const_item.attrs = crate::roxygen::strip_roxygen_from_attrs(&const_item.attrs);
227                const_item
228                    .attrs
229                    .retain(|attr| !attr.path().is_ident("miniextendr"));
230            }
231            syn::ImplItem::Type(type_item) => {
232                type_item.attrs = crate::roxygen::strip_roxygen_from_attrs(&type_item.attrs);
233                type_item
234                    .attrs
235                    .retain(|attr| !attr.path().is_ident("miniextendr"));
236            }
237            syn::ImplItem::Macro(macro_item) => {
238                macro_item.attrs = crate::roxygen::strip_roxygen_from_attrs(&macro_item.attrs);
239                macro_item
240                    .attrs
241                    .retain(|attr| !attr.path().is_ident("miniextendr"));
242            }
243            _ => {}
244        }
245    }
246    item_impl
247}
248
249/// Class system flavor for wrapper generation.
250#[derive(#[automatically_derived]
impl ::core::fmt::Debug for ClassSystem {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::write_str(f,
            match self {
                ClassSystem::Env => "Env",
                ClassSystem::R6 => "R6",
                ClassSystem::S7 => "S7",
                ClassSystem::S3 => "S3",
                ClassSystem::S4 => "S4",
                ClassSystem::Vctrs => "Vctrs",
            })
    }
}Debug, #[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for ClassSystem { }
#[automatically_derived]
impl ::core::clone::Clone for ClassSystem {
    #[inline]
    fn clone(&self) -> Self { *self }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for ClassSystem { }Copy, #[automatically_derived]
impl ::core::marker::StructuralPartialEq for ClassSystem { }
#[automatically_derived]
impl ::core::cmp::PartialEq for ClassSystem {
    #[inline]
    fn eq(&self, other: &Self) -> bool {
        ::core::intrinsics::discriminant_value(self) ==
            ::core::intrinsics::discriminant_value(other)
    }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for ClassSystem { }Eq)]
251pub enum ClassSystem {
252    /// Environment-style with `$`/`[[` dispatch
253    Env,
254    /// R6::R6Class
255    R6,
256    /// S7::new_class
257    S7,
258    /// S3 structure() with class attribute
259    S3,
260    /// S4 setClass
261    S4,
262    /// vctrs-compatible S3 class (vctr, rcrd, or list_of)
263    Vctrs,
264}
265
266impl ClassSystem {
267    /// Convert to an identifier for token transport (e.g., in macro_rules! expansion).
268    pub fn to_ident(self) -> syn::Ident {
269        let name = match self {
270            ClassSystem::Env => "env",
271            ClassSystem::R6 => "r6",
272            ClassSystem::S7 => "s7",
273            ClassSystem::S3 => "s3",
274            ClassSystem::S4 => "s4",
275            ClassSystem::Vctrs => "vctrs",
276        };
277        syn::Ident::new(name, proc_macro2::Span::call_site())
278    }
279
280    /// Parse from an identifier (inverse of `to_ident`).
281    pub fn from_ident(ident: &syn::Ident) -> Option<Self> {
282        match ident.to_string().as_str() {
283            "env" => Some(ClassSystem::Env),
284            "r6" => Some(ClassSystem::R6),
285            "s7" => Some(ClassSystem::S7),
286            "s3" => Some(ClassSystem::S3),
287            "s4" => Some(ClassSystem::S4),
288            "vctrs" => Some(ClassSystem::Vctrs),
289            _ => None,
290        }
291    }
292}
293
294/// Case-insensitive parsing of class system names from strings.
295///
296/// Accepts: `"env"`, `"r6"`, `"s3"`, `"s4"`, `"s7"`, `"vctrs"` (any casing).
297impl std::str::FromStr for ClassSystem {
298    type Err = String;
299
300    fn from_str(s: &str) -> Result<Self, Self::Err> {
301        match s.to_lowercase().as_str() {
302            "env" => Ok(ClassSystem::Env),
303            "r6" => Ok(ClassSystem::R6),
304            "s7" => Ok(ClassSystem::S7),
305            "s3" => Ok(ClassSystem::S3),
306            "s4" => Ok(ClassSystem::S4),
307            "vctrs" => Ok(ClassSystem::Vctrs),
308            _ => Err(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("unknown class system: {0}", s))
    })format!("unknown class system: {}", s)),
309        }
310    }
311}
312
313/// What the impl option `r_data_accessors` asks for: the class-level form of
314/// the `pub` `#[r_data]` fields of `#[derive(ExternalPtr)]`, next to the
315/// standalone `Type_get_f()` / `Type_set_f()` every class gets.
316///
317/// R6 gets active bindings, S7 properties, and S3, S4 and env classes `$` /
318/// `[[` field methods (#1848, `field_syntax.rs`).
319#[derive(#[automatically_derived]
impl ::core::fmt::Debug for RDataAccessors {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::write_str(f,
            match self {
                RDataAccessors::Off => "Off",
                RDataAccessors::GetSet => "GetSet",
                RDataAccessors::GetOnly => "GetOnly",
            })
    }
}Debug, #[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for RDataAccessors { }
#[automatically_derived]
impl ::core::clone::Clone for RDataAccessors {
    #[inline]
    fn clone(&self) -> Self { *self }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for RDataAccessors { }Copy, #[automatically_derived]
impl ::core::marker::StructuralPartialEq for RDataAccessors { }
#[automatically_derived]
impl ::core::cmp::PartialEq for RDataAccessors {
    #[inline]
    fn eq(&self, other: &Self) -> bool {
        ::core::intrinsics::discriminant_value(self) ==
            ::core::intrinsics::discriminant_value(other)
    }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for RDataAccessors { }Eq, #[automatically_derived]
impl ::core::default::Default for RDataAccessors {
    #[inline]
    fn default() -> Self { Self::Off }
}Default)]
320pub enum RDataAccessors {
321    /// No option: only the standalone accessors.
322    #[default]
323    Off,
324    /// `r_data_accessors`: a getter and a setter per field.
325    GetSet,
326    /// `r_data_accessors = "get"` (S3, S4, env): `$` / `[[` read the fields
327    /// and no `$<-` / `[[<-` is generated, for a class whose own replacement
328    /// methods write them (for example copy-on-modify setters that return a
329    /// new object).
330    GetOnly,
331}
332
333impl RDataAccessors {
334    /// Whether the class wires its fields in at all.
335    pub fn enabled(self) -> bool {
336        self != RDataAccessors::Off
337    }
338
339    /// Whether the class gets field setters (`$<-` / `[[<-`).
340    pub fn setters(self) -> bool {
341        self == RDataAccessors::GetSet
342    }
343}
344
345/// How the S3 readers of `s3(r_data_accessors)` look a field up on a bare
346/// pointer (`field_syntax.rs`): `s3(r_data_accessors, lookup = "list")`.
347/// Neither lookup matches a prefix of a name (#1922).
348#[derive(#[automatically_derived]
impl ::core::fmt::Debug for FieldLookup {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::write_str(f,
            match self {
                FieldLookup::Strict => "Strict",
                FieldLookup::List => "List",
            })
    }
}Debug, #[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for FieldLookup { }
#[automatically_derived]
impl ::core::clone::Clone for FieldLookup {
    #[inline]
    fn clone(&self) -> Self { *self }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for FieldLookup { }Copy, #[automatically_derived]
impl ::core::marker::StructuralPartialEq for FieldLookup { }
#[automatically_derived]
impl ::core::cmp::PartialEq for FieldLookup {
    #[inline]
    fn eq(&self, other: &Self) -> bool {
        ::core::intrinsics::discriminant_value(self) ==
            ::core::intrinsics::discriminant_value(other)
    }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for FieldLookup { }Eq, #[automatically_derived]
impl ::core::default::Default for FieldLookup {
    #[inline]
    fn default() -> Self { Self::Strict }
}Default)]
349pub enum FieldLookup {
350    /// The default (#1921): exact names and whole-number positions only; an
351    /// unknown name raises `miniextendr_no_field` and any other index the
352    /// bad-index error.
353    #[default]
354    Strict,
355    /// `lookup = "list"` (#1891): a list's lenience without its prefix
356    /// match. An unknown name or `NA` reads `NULL`, and `[[` takes a logical
357    /// or a fractional position.
358    List,
359}
360
361/// The fallback of the field readers of `s3(r_data_accessors)`,
362/// `s4(r_data_accessors)` and `env(r_data_accessors)` (#2150):
363/// `fallback = "Self::column"`, and optionally
364/// `fallback_names = "Self::column_names"`, in the path style of
365/// `#[r_data(get = "Self::f")]`.
366///
367/// `fallback` names a `fn(&Self, name: &str) -> Option<T>`, `T: IntoR`,
368/// which the readers call on the live Rust value for a name no field has, on
369/// the bare pointer (an S3 or env object's classed pointer, an S4 object's
370/// `ptr`); a list or environment carrying the pointer in `.ptr` keeps its own
371/// elements for those names. Fields win, a `[[` position counts the fields
372/// only, `names()` / `as.list()` stay the fields, and an assignment to a name
373/// no field has is refused as before. `None` reads what an unknown name
374/// reads without the fallback: `miniextendr_no_field` on S3 (default lookup)
375/// and S4, `NULL` under `lookup = "list"` and on an env class (whose `$`
376/// tries the methods before the fallback). The macro handles the `Option`
377/// itself, so `Some(x)` reads `x` and a scalar `T` is never `NA` for `None`.
378///
379/// `fallback_names` names a `fn(&Self) -> Vec<String>`: `.DollarNames()`
380/// offers its names after the fields (after the methods on an env class).
381/// Without it, completion offers the fields only.
382///
383/// The impl expansion emits one `.Call` entry for each
384/// (`field_syntax::fallback_c_entries`), so only one impl block of a type
385/// carries the option: it is not a class setting the blocks repeat
386/// ([`ParsedImpl::class_settings`]) but part of the class that block defines
387/// ([`ParsedImpl::class_definitions`]), next to the constructor.
388#[derive(#[automatically_derived]
impl ::core::fmt::Debug for FieldFallback {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field2_finish(f, "FieldFallback",
            "get", &self.get, "names", &&self.names)
    }
}Debug, #[automatically_derived]
impl ::core::clone::Clone for FieldFallback {
    #[inline]
    fn clone(&self) -> Self {
        FieldFallback {
            get: ::core::clone::Clone::clone(&self.get),
            names: ::core::clone::Clone::clone(&self.names),
        }
    }
}Clone)]
389pub struct FieldFallback {
390    /// `fallback = "Self::column"`: the path, as written.
391    pub get: syn::LitStr,
392    /// `fallback_names = "Self::column_names"`, as written.
393    pub names: Option<syn::LitStr>,
394}
395
396/// Parses the value of a `fallback` / `fallback_names` key inside `s3(...)` /
397/// `s4(...)` / `env(...)`, after the key: `= "path"`, where the path names a
398/// function (a leading `Self` is the impl's type).
399fn parse_fallback_value(
400    content: syn::parse::ParseStream,
401    key: &syn::Ident,
402) -> syn::Result<syn::LitStr> {
403    let example = if key == "fallback" {
404        "Self::column"
405    } else {
406        "Self::column_names"
407    };
408    if !content.peek(::syn::token::Eqsyn::Token![=]) {
409        return Err(syn::Error::new(
410            key.span(),
411            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("`{0}` takes a path to a function in a string: `{0} = \"{1}\"`",
                key, example))
    })format!("`{key}` takes a path to a function in a string: `{key} = \"{example}\"`"),
412        ));
413    }
414    let _: ::syn::token::Eqsyn::Token![=] = content.parse()?;
415    if !content.peek(syn::LitStr) {
416        return Err(content.error(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("`{0}` takes a path to a function in a string: `{0} = \"{1}\"`",
                key, example))
    })format!(
417            "`{key}` takes a path to a function in a string: `{key} = \"{example}\"`"
418        )));
419    }
420    let value: syn::LitStr = content.parse()?;
421    if let Err(err) = value.parse::<syn::Path>() {
422        return Err(syn::Error::new_spanned(
423            &value,
424            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("`{0}` must be a path to a function, e.g. `{1}`: {2}",
                key, example, err))
    })format!("`{key}` must be a path to a function, e.g. `{example}`: {err}"),
425        ));
426    }
427    Ok(value)
428}
429
430/// The error for `fallback` / `fallback_names` under a class system without
431/// field syntax: R6 active bindings and S7 properties are a fixed set of
432/// names, and a vctr has no pointer.
433fn no_field_fallback_error(key: &syn::Ident, system: &str) -> syn::Error {
434    let why = match system {
435        "r6" => "an R6 object's `$` reads its environment, whose active bindings are fixed",
436        "s7" => "S7 properties are a fixed set, read with `@`",
437        _ => "a vctr is an R vector with no external pointer",
438    };
439    syn::Error::new(
440        key.span(),
441        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("`{0}` applies to the `$` / `[[` readers of `s3(r_data_accessors)`, `s4(r_data_accessors)` and `env(r_data_accessors)`; {1}",
                key, why))
    })format!(
442            "`{key}` applies to the `$` / `[[` readers of `s3(r_data_accessors)`, \
443             `s4(r_data_accessors)` and `env(r_data_accessors)`; {why}"
444        ),
445    )
446}
447
448/// Parses an `r_data_accessors` key inside `s3(...)` / `s4(...)` / `env(...)`
449/// / `r6(...)` / `s7(...)`, after the key: nothing, or `= "get"` (S3, S4 and
450/// env only).
451fn parse_r_data_accessors_value(
452    content: syn::parse::ParseStream,
453    key: &syn::Ident,
454    system: ClassSystem,
455) -> syn::Result<RDataAccessors> {
456    if !content.peek(::syn::token::Eqsyn::Token![=]) {
457        return Ok(RDataAccessors::GetSet);
458    }
459    let _: ::syn::token::Eqsyn::Token![=] = content.parse()?;
460    let value: syn::LitStr = content.parse()?;
461    if !#[allow(non_exhaustive_omitted_patterns)] match system {
    ClassSystem::S3 | ClassSystem::S4 | ClassSystem::Env => true,
    _ => false,
}matches!(system, ClassSystem::S3 | ClassSystem::S4 | ClassSystem::Env) {
462        return Err(syn::Error::new(
463            key.span(),
464            "the getters-only form `r_data_accessors = \"get\"` applies to `s3`, `s4` and \
465             `env` classes; R6 active bindings and S7 properties take the bare \
466             `r_data_accessors`",
467        ));
468    }
469    if value.value() != "get" {
470        return Err(syn::Error::new(
471            value.span(),
472            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("unknown `r_data_accessors` value \"{0}\"; expected `r_data_accessors` (getters and setters) or `r_data_accessors = \"get\"` (getters only)",
                value.value()))
    })format!(
473                "unknown `r_data_accessors` value \"{}\"; expected `r_data_accessors` (getters \
474                 and setters) or `r_data_accessors = \"get\"` (getters only)",
475                value.value()
476            ),
477        ));
478    }
479    Ok(RDataAccessors::GetOnly)
480}
481
482/// Kind of vctrs class being created.
483#[derive(#[automatically_derived]
impl ::core::fmt::Debug for VctrsKind {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::write_str(f,
            match self {
                VctrsKind::Vctr => "Vctr",
                VctrsKind::Rcrd => "Rcrd",
                VctrsKind::ListOf => "ListOf",
            })
    }
}Debug, #[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for VctrsKind { }
#[automatically_derived]
impl ::core::clone::Clone for VctrsKind {
    #[inline]
    fn clone(&self) -> Self { *self }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for VctrsKind { }Copy, #[automatically_derived]
impl ::core::marker::StructuralPartialEq for VctrsKind { }
#[automatically_derived]
impl ::core::cmp::PartialEq for VctrsKind {
    #[inline]
    fn eq(&self, other: &Self) -> bool {
        ::core::intrinsics::discriminant_value(self) ==
            ::core::intrinsics::discriminant_value(other)
    }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for VctrsKind { }Eq, #[automatically_derived]
impl ::core::default::Default for VctrsKind {
    #[inline]
    fn default() -> Self { Self::Vctr }
}Default)]
484pub enum VctrsKind {
485    /// Simple vctr backed by a base vector (new_vctr)
486    #[default]
487    Vctr,
488    /// Record type with named fields (new_rcrd)
489    Rcrd,
490    /// Homogeneous list with ptype (new_list_of)
491    ListOf,
492}
493
494/// Case-insensitive parsing of vctrs kind names from strings.
495///
496/// Accepts: `"vctr"`, `"rcrd"` (or `"record"`), `"list_of"` (or `"listof"`).
497impl std::str::FromStr for VctrsKind {
498    type Err = String;
499
500    fn from_str(s: &str) -> Result<Self, Self::Err> {
501        match s.to_lowercase().as_str() {
502            "vctr" => Ok(VctrsKind::Vctr),
503            "rcrd" | "record" => Ok(VctrsKind::Rcrd),
504            "list_of" | "listof" => Ok(VctrsKind::ListOf),
505            _ => Err(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("unknown vctrs kind: {0} (expected vctr, rcrd, or list_of)",
                s))
    })format!(
506                "unknown vctrs kind: {} (expected vctr, rcrd, or list_of)",
507                s
508            )),
509        }
510    }
511}
512
513/// Attributes for vctrs class generation.
514#[derive(#[automatically_derived]
impl ::core::fmt::Debug for VctrsAttrs {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field5_finish(f, "VctrsAttrs",
            "kind", &self.kind, "base", &self.base, "inherit_base_type",
            &self.inherit_base_type, "ptype", &self.ptype, "abbr",
            &&self.abbr)
    }
}Debug, #[automatically_derived]
impl ::core::clone::Clone for VctrsAttrs {
    #[inline]
    fn clone(&self) -> Self {
        VctrsAttrs {
            kind: ::core::clone::Clone::clone(&self.kind),
            base: ::core::clone::Clone::clone(&self.base),
            inherit_base_type: ::core::clone::Clone::clone(&self.inherit_base_type),
            ptype: ::core::clone::Clone::clone(&self.ptype),
            abbr: ::core::clone::Clone::clone(&self.abbr),
        }
    }
}Clone, #[automatically_derived]
impl ::core::default::Default for VctrsAttrs {
    #[inline]
    fn default() -> Self {
        VctrsAttrs {
            kind: ::core::default::Default::default(),
            base: ::core::default::Default::default(),
            inherit_base_type: ::core::default::Default::default(),
            ptype: ::core::default::Default::default(),
            abbr: ::core::default::Default::default(),
        }
    }
}Default)]
515pub struct VctrsAttrs {
516    /// The vctrs kind (vctr, rcrd, list_of)
517    pub kind: VctrsKind,
518    /// Base type for vctr (e.g., "double", "integer", "character")
519    pub base: Option<String>,
520    /// Whether to inherit base type in class vector
521    pub inherit_base_type: Option<bool>,
522    /// Prototype type for list_of (R expression)
523    pub ptype: Option<String>,
524    /// Abbreviation for vec_ptype_abbr (for printing)
525    pub abbr: Option<String>,
526}
527
528/// Receiver kind for methods.
529#[derive(#[automatically_derived]
impl ::core::fmt::Debug for ReceiverKind {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::write_str(f,
            match self {
                ReceiverKind::None => "None",
                ReceiverKind::Ref => "Ref",
                ReceiverKind::RefMut => "RefMut",
                ReceiverKind::Value => "Value",
                ReceiverKind::ExternalPtrRef => "ExternalPtrRef",
                ReceiverKind::ExternalPtrMut => "ExternalPtrMut",
                ReceiverKind::ExternalPtrValue => "ExternalPtrValue",
            })
    }
}Debug, #[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for ReceiverKind { }
#[automatically_derived]
impl ::core::clone::Clone for ReceiverKind {
    #[inline]
    fn clone(&self) -> Self { *self }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for ReceiverKind { }Copy, #[automatically_derived]
impl ::core::marker::StructuralPartialEq for ReceiverKind { }
#[automatically_derived]
impl ::core::cmp::PartialEq for ReceiverKind {
    #[inline]
    fn eq(&self, other: &Self) -> bool {
        ::core::intrinsics::discriminant_value(self) ==
            ::core::intrinsics::discriminant_value(other)
    }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for ReceiverKind { }Eq)]
530pub enum ReceiverKind {
531    /// No env - static/associated function
532    None,
533    /// `&self` - immutable borrow
534    Ref,
535    /// `&mut self` - mutable borrow
536    RefMut,
537    /// `self` (or `self: Self`) - consuming. The C wrapper moves the value out
538    /// of the R handle: `-> Self` writes the result back (same handle, in-place
539    /// semantics), `-> Result<Self, E>` / `-> Option<Self>` run on a clone and
540    /// overwrite on success, anything else leaves the handle consumed.
541    Value,
542    /// `self: ExternalPtrRef<'_, Self>` — shared borrow, with the R object
543    ExternalPtrRef,
544    /// `self: ExternalPtrMut<'_, Self>` — exclusive borrow, with the R object
545    ExternalPtrMut,
546    /// `self: ExternalPtr<Self>` — the handle, unborrowed (not consuming the inner T)
547    ExternalPtrValue,
548}
549
550impl ReceiverKind {
551    /// Returns true if this is an instance method (has self), including the
552    /// consuming `self` receiver.
553    pub fn is_instance(&self) -> bool {
554        #[allow(non_exhaustive_omitted_patterns)] match self {
    ReceiverKind::Ref | ReceiverKind::RefMut | ReceiverKind::Value |
        ReceiverKind::ExternalPtrRef | ReceiverKind::ExternalPtrMut |
        ReceiverKind::ExternalPtrValue => true,
    _ => false,
}matches!(
555            self,
556            ReceiverKind::Ref
557                | ReceiverKind::RefMut
558                | ReceiverKind::Value
559                | ReceiverKind::ExternalPtrRef
560                | ReceiverKind::ExternalPtrMut
561                | ReceiverKind::ExternalPtrValue
562        )
563    }
564
565    /// Returns true if this is a mutable instance receiver.
566    pub fn is_mut(&self) -> bool {
567        #[allow(non_exhaustive_omitted_patterns)] match self {
    ReceiverKind::RefMut | ReceiverKind::ExternalPtrMut => true,
    _ => false,
}matches!(self, ReceiverKind::RefMut | ReceiverKind::ExternalPtrMut)
568    }
569}
570
571/// Parsed method from an impl block.
572///
573/// # Default Parameters
574///
575/// Default parameters are specified using method-level syntax:
576/// - `#[miniextendr(defaults(param = "value", ...))]` on the method
577///
578/// Note: Parameter-level `#[miniextendr(default = "...")]` syntax is only supported
579/// for standalone functions, not impl methods (Rust language limitation).
580///
581/// Defaults cannot be specified for `self` parameters (compile error).
582#[derive(#[automatically_derived]
impl ::core::fmt::Debug for ParsedMethod {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        let names: &'static _ =
            &["visibility_marker", "return_wrap", "ident", "env", "sig",
                        "param_markers", "vis", "doc_tags", "method_attrs",
                        "param_defaults", "has_dots", "rng_guard"];
        let values: &[&dyn ::core::fmt::Debug] =
            &[&self.visibility_marker, &self.return_wrap, &self.ident,
                        &self.env, &self.sig, &self.param_markers, &self.vis,
                        &self.doc_tags, &self.method_attrs, &self.param_defaults,
                        &self.has_dots, &&self.rng_guard];
        ::core::fmt::Formatter::debug_struct_fields_finish(f, "ParsedMethod",
            names, values)
    }
}Debug)]
583pub struct ParsedMethod {
584    /// Return-type visibility marker (#1213): `Some(true)` for
585    /// `-> Invisible<T>`, `Some(false)` for `-> Visible<T>`. The marker is
586    /// peeled from [`Self::sig`] at parse time, so every return-shape
587    /// predicate sees `T`; the C wrapper unwraps the newtype before conversion.
588    pub visibility_marker: Option<bool>,
589    /// Explicit target class wrapping, shared by marker and attribute spellings.
590    pub return_wrap: Option<crate::return_wrap::ReturnWrap>,
591    /// The method's name (e.g., `new`, `get`, `set_value`).
592    pub ident: syn::Ident,
593    /// How this method receives `self`: `&self`, `&mut self`, by value, or not at all (static).
594    pub env: ReceiverKind,
595    /// Method signature with the `self` receiver stripped. Used for C wrapper generation
596    /// where `self` is handled separately as a SEXP parameter. The parameter
597    /// markers (`Checked<T>` / `Unchecked<T>`, #1566) are peeled from it, as
598    /// the visibility marker is from the output: every R-side consumer and
599    /// the conversion see the inner type.
600    pub sig: syn::Signature,
601    /// The parameter markers peeled from [`Self::sig`] (Rust name →
602    /// markers, outermost first): the C wrapper wraps the converted value in
603    /// them before the call.
604    pub param_markers: Vec<(String, Vec<crate::type_inspect::ParamMarker>)>,
605    /// Rust visibility of the method. Non-`pub` methods become private in R6;
606    /// only `pub` methods get `@export` in R wrappers.
607    pub vis: syn::Visibility,
608    /// Roxygen tag lines extracted from Rust doc comments
609    pub doc_tags: Vec<String>,
610    /// Per-method attributes for class system overrides. Also carries the
611    /// `match_arg(...)` / `choices(...)` / `several_ok` parameter annotations
612    /// via its `per_param_match_arg` / `per_param_choices` / `per_param_several_ok`
613    /// fields — see [`MethodAttrs`] for the parsing surface.
614    pub method_attrs: MethodAttrs,
615    /// Parameter default values from `#[miniextendr(defaults(...))]`, keyed
616    /// by Rust name; the R wrapper keys them by formal
617    /// ([`Formals::rekey`](crate::r_wrapper_builder::Formals::rekey)).
618    pub param_defaults: std::collections::HashMap<String, String>,
619    /// Whether this method accepts dots, either from raw `...` rewritten to
620    /// `&Dots` or from an explicit `&Dots` parameter, anywhere.
621    pub has_dots: bool,
622    /// Where the `&RngGuard` parameter (#2002) sat among the parameters of
623    /// [`Self::sig`], which no longer holds it: it is no R formal, and the C
624    /// wrapper passes the `rng` wrapper's guard at that position.
625    pub rng_guard: Option<usize>,
626}
627
628/// R6-specific per-method markers, separated from [`MethodAttrs`] so the
629/// `r6` parser branch and R6 class generator own a self-contained bag.
630///
631/// All R6 boolean flags live here.  Using any of these markers under a
632/// non-R6 class system (`#[miniextendr(s3)]`, `s4`, `s7`, `env`) is a
633/// compile-time error caught by [`ParsedMethod::validate_method_attrs`].
634#[derive(#[automatically_derived]
impl ::core::fmt::Debug for R6MethodAttrs {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        let names: &'static _ =
            &["active", "active_span", "setter", "prop", "private",
                        "private_span", "finalize", "finalize_span", "deep_clone",
                        "deep_clone_span"];
        let values: &[&dyn ::core::fmt::Debug] =
            &[&self.active, &self.active_span, &self.setter, &self.prop,
                        &self.private, &self.private_span, &self.finalize,
                        &self.finalize_span, &self.deep_clone,
                        &&self.deep_clone_span];
        ::core::fmt::Formatter::debug_struct_fields_finish(f, "R6MethodAttrs",
            names, values)
    }
}Debug, #[automatically_derived]
impl ::core::default::Default for R6MethodAttrs {
    #[inline]
    fn default() -> Self {
        R6MethodAttrs {
            active: ::core::default::Default::default(),
            active_span: ::core::default::Default::default(),
            setter: ::core::default::Default::default(),
            prop: ::core::default::Default::default(),
            private: ::core::default::Default::default(),
            private_span: ::core::default::Default::default(),
            finalize: ::core::default::Default::default(),
            finalize_span: ::core::default::Default::default(),
            deep_clone: ::core::default::Default::default(),
            deep_clone_span: ::core::default::Default::default(),
        }
    }
}Default)]
635pub struct R6MethodAttrs {
636    /// Mark as active binding getter (`#[miniextendr(r6(active))]`).
637    pub active: bool,
638    /// Span of the `r6(active)` marker — used for error reporting when the
639    /// marker is misused in a non-R6 class generator.
640    pub active_span: Option<proc_macro2::Span>,
641    /// R6 active-binding *setter* (paired with an `active` getter by `prop`).
642    pub setter: bool,
643    /// R6 active-binding property name (defaults to the method name).
644    pub prop: Option<String>,
645    /// Mark as private method (`#[miniextendr(r6(private))]`).
646    /// Also inferred from non-`pub` Rust visibility.
647    pub private: bool,
648    /// Span of the `r6(private)` marker — points the validator's diagnostic
649    /// at the offending marker rather than the method ident.
650    pub private_span: Option<proc_macro2::Span>,
651    /// Mark as finalizer (`#[miniextendr(r6(finalize))]`).
652    /// Also inferred when the method consumes `self` and does not return `Self`.
653    pub finalize: bool,
654    /// Span of the `r6(finalize)` marker — see `private_span`.
655    pub finalize_span: Option<proc_macro2::Span>,
656    /// Mark as R6 deep-clone handler (`#[miniextendr(r6(deep_clone))]`).
657    /// This method is wired into `private$deep_clone` in the R6Class definition.
658    pub deep_clone: bool,
659    /// Span of the `r6(deep_clone)` marker — see `private_span`.
660    pub deep_clone_span: Option<proc_macro2::Span>,
661}
662
663/// S7-specific per-method markers, separated from [`MethodAttrs`] so the S7
664/// class generator has a self-contained bag of its own state (property
665/// getters/setters, generic-dispatch controls, convert() wiring) and the other
666/// class generators don't have to look past them.
667///
668/// # Mapping from `s7(...)` attribute keys
669///
670/// | Attribute | Field |
671/// |-----------|-------|
672/// | `s7(getter)` | `getter: true` |
673/// | `s7(setter)` | `setter: true` |
674/// | `s7(prop = "name")` | `prop: Some("name")` |
675/// | `s7(default = "expr")` | `default: Some("expr")` |
676/// | `s7(validate)` | `validate: true` |
677/// | `s7(required)` | `required: true` |
678/// | `s7(frozen)` | `frozen: true` |
679/// | `s7(deprecated = "msg")` | `deprecated: Some("msg")` |
680/// | `s7(no_dots)` | `no_dots: true` |
681/// | `s7(dispatch = "x,y")` | `dispatch: Some("x,y")` |
682/// | `s7(fallback)` | `fallback: true` |
683/// | `s7(convert_from = "T")` | `convert_from: Some("T")` |
684/// | `s7(convert_to = "T")` | `convert_to: Some("T")` |
685#[derive(#[automatically_derived]
impl ::core::fmt::Debug for S7MethodAttrs {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        let names: &'static _ =
            &["getter", "setter", "prop", "default", "validate", "required",
                        "frozen", "deprecated", "no_dots", "dispatch", "fallback",
                        "convert_from", "convert_to", "no_shortcut"];
        let values: &[&dyn ::core::fmt::Debug] =
            &[&self.getter, &self.setter, &self.prop, &self.default,
                        &self.validate, &self.required, &self.frozen,
                        &self.deprecated, &self.no_dots, &self.dispatch,
                        &self.fallback, &self.convert_from, &self.convert_to,
                        &&self.no_shortcut];
        ::core::fmt::Formatter::debug_struct_fields_finish(f, "S7MethodAttrs",
            names, values)
    }
}Debug, #[automatically_derived]
impl ::core::default::Default for S7MethodAttrs {
    #[inline]
    fn default() -> Self {
        S7MethodAttrs {
            getter: ::core::default::Default::default(),
            setter: ::core::default::Default::default(),
            prop: ::core::default::Default::default(),
            default: ::core::default::Default::default(),
            validate: ::core::default::Default::default(),
            required: ::core::default::Default::default(),
            frozen: ::core::default::Default::default(),
            deprecated: ::core::default::Default::default(),
            no_dots: ::core::default::Default::default(),
            dispatch: ::core::default::Default::default(),
            fallback: ::core::default::Default::default(),
            convert_from: ::core::default::Default::default(),
            convert_to: ::core::default::Default::default(),
            no_shortcut: ::core::default::Default::default(),
        }
    }
}Default)]
686pub struct S7MethodAttrs {
687    pub getter: bool,
688    pub setter: bool,
689    pub prop: Option<String>,
690    pub default: Option<String>,
691    pub validate: bool,
692    pub required: bool,
693    pub frozen: bool,
694    pub deprecated: Option<String>,
695    pub no_dots: bool,
696    pub dispatch: Option<String>,
697    pub fallback: bool,
698    pub convert_from: Option<String>,
699    pub convert_to: Option<String>,
700    /// Opt out of the per-class `<ClassName>_<method>` fast-dispatch shortcut
701    /// (set via `#[miniextendr(s7(no_shortcut))]`). The S7 generic + method are
702    /// still emitted; only the standalone shortcut function is suppressed.
703    pub no_shortcut: bool,
704}
705
706/// Per-method attributes for class system customization.
707#[derive(#[automatically_derived]
impl ::core::fmt::Debug for MethodAttrs {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        let names: &'static _ =
            &["ignore", "constructor", "force_invisible", "r6", "as_coercion",
                        "as_coercion_span", "generic", "receiver",
                        "no_dispatch_dots", "class", "worker", "unsafe_main_thread",
                        "check_interrupt", "coerce", "preconditions", "rng",
                        "dots_spec", "dots_policy", "unwrap_in_r", "serde_error",
                        "serialize", "wrap", "defaults", "defaults_span",
                        "per_param", "match_arg_span", "s7", "lifecycle",
                        "vctrs_protocol", "r_name", "postfix", "r_entry",
                        "r_post_checks", "r_on_exit", "internal", "noexport"];
        let values: &[&dyn ::core::fmt::Debug] =
            &[&self.ignore, &self.constructor, &self.force_invisible,
                        &self.r6, &self.as_coercion, &self.as_coercion_span,
                        &self.generic, &self.receiver, &self.no_dispatch_dots,
                        &self.class, &self.worker, &self.unsafe_main_thread,
                        &self.check_interrupt, &self.coerce, &self.preconditions,
                        &self.rng, &self.dots_spec, &self.dots_policy,
                        &self.unwrap_in_r, &self.serde_error, &self.serialize,
                        &self.wrap, &self.defaults, &self.defaults_span,
                        &self.per_param, &self.match_arg_span, &self.s7,
                        &self.lifecycle, &self.vctrs_protocol, &self.r_name,
                        &self.postfix, &self.r_entry, &self.r_post_checks,
                        &self.r_on_exit, &self.internal, &&self.noexport];
        ::core::fmt::Formatter::debug_struct_fields_finish(f, "MethodAttrs",
            names, values)
    }
}Debug, #[automatically_derived]
impl ::core::default::Default for MethodAttrs {
    #[inline]
    fn default() -> Self {
        MethodAttrs {
            ignore: ::core::default::Default::default(),
            constructor: ::core::default::Default::default(),
            force_invisible: ::core::default::Default::default(),
            r6: ::core::default::Default::default(),
            as_coercion: ::core::default::Default::default(),
            as_coercion_span: ::core::default::Default::default(),
            generic: ::core::default::Default::default(),
            receiver: ::core::default::Default::default(),
            no_dispatch_dots: ::core::default::Default::default(),
            class: ::core::default::Default::default(),
            worker: ::core::default::Default::default(),
            unsafe_main_thread: ::core::default::Default::default(),
            check_interrupt: ::core::default::Default::default(),
            coerce: ::core::default::Default::default(),
            preconditions: ::core::default::Default::default(),
            rng: ::core::default::Default::default(),
            dots_spec: ::core::default::Default::default(),
            dots_policy: ::core::default::Default::default(),
            unwrap_in_r: ::core::default::Default::default(),
            serde_error: ::core::default::Default::default(),
            serialize: ::core::default::Default::default(),
            wrap: ::core::default::Default::default(),
            defaults: ::core::default::Default::default(),
            defaults_span: ::core::default::Default::default(),
            per_param: ::core::default::Default::default(),
            match_arg_span: ::core::default::Default::default(),
            s7: ::core::default::Default::default(),
            lifecycle: ::core::default::Default::default(),
            vctrs_protocol: ::core::default::Default::default(),
            r_name: ::core::default::Default::default(),
            postfix: ::core::default::Default::default(),
            r_entry: ::core::default::Default::default(),
            r_post_checks: ::core::default::Default::default(),
            r_on_exit: ::core::default::Default::default(),
            internal: ::core::default::Default::default(),
            noexport: ::core::default::Default::default(),
        }
    }
}Default)]
708pub struct MethodAttrs {
709    /// Skip this method
710    pub ignore: bool,
711    /// Mark as constructor
712    pub constructor: bool,
713    /// `invisible` / `visible` method option (#1213): `Some(true)` makes the R
714    /// wrapper return `invisible(...)`, `Some(false)` forces a visible return.
715    /// Same decision as an `Invisible<T>` / `Visible<T>` return-type marker;
716    /// the two must not disagree. Nothing is invisible by default.
717    pub force_invisible: Option<bool>,
718    /// R6-specific method markers. All R6 boolean flags live here.
719    /// Only consumed by the R6 class generator and R6-aware accessor methods
720    /// (`ParsedMethod::is_active`, `is_private`, `is_finalizer`).
721    pub r6: R6MethodAttrs,
722    /// Generate as `as.<class>()` S3 method (e.g., "data.frame", "list", "character").
723    ///
724    /// When set, generates an S3 method for R's `as.<class>()` generic:
725    /// ```r
726    /// as.data.frame.MyType <- function(x, ...) {
727    ///     .Call(C_MyType__as_data_frame, .call = sys.call(), x)
728    /// }
729    /// ```
730    ///
731    /// Valid values: data.frame, list, character, numeric, double, integer,
732    /// logical, matrix, vector, factor, Date, POSIXct, complex, raw,
733    /// environment, function
734    pub as_coercion: Option<String>,
735    /// Span of `as = "..."` for error reporting.
736    pub as_coercion_span: Option<proc_macro2::Span>,
737    /// Override generic name for S3/S4/S7 methods.
738    ///
739    /// Use this to implement methods for existing generics (like `print`, `format`, `length`)
740    /// without creating a new generic. When set, the generated code:
741    /// - Uses the specified generic name instead of the method name
742    /// - Skips creating a new generic (assumes it already exists)
743    /// - Creates only the method implementation (e.g., `print.MyClass`)
744    ///
745    /// # Example
746    /// ```ignore
747    /// #[miniextendr(s3)]
748    /// impl MyType {
749    ///     #[miniextendr(generic = "print")]
750    ///     fn show(&self) -> String {
751    ///         format!("MyType: {}", self.value)
752    ///     }
753    /// }
754    /// ```
755    /// This generates `print.MyType` that calls the `show` method.
756    pub generic: Option<String>,
757    /// First formal of an existing S3/S4 generic (for example `object` for show).
758    pub receiver: Option<String>,
759    /// Omit the automatic dispatch dots for an existing S3/S4 generic.
760    pub no_dispatch_dots: bool,
761    /// Override class suffix for S3 methods.
762    ///
763    /// Use this to implement double-dispatch methods (like vctrs coercion)
764    /// where the class suffix differs from the type name or contains multiple classes.
765    ///
766    /// # Example
767    /// ```ignore
768    /// #[miniextendr(s3(generic = "vec_ptype2", class = "my_vctr.my_vctr"))]
769    /// fn ptype2_self(x: Robj, y: Robj, dots: ...) -> Robj {
770    ///     // Return prototype
771    /// }
772    /// ```
773    /// This generates `vec_ptype2.my_vctr.my_vctr` for vctrs double-dispatch.
774    pub class: Option<String>,
775    /// Worker thread execution (default: auto-detect based on types)
776    pub worker: bool,
777    /// Force main thread execution (unsafe)
778    pub unsafe_main_thread: bool,
779    /// Enable R interrupt checking
780    pub check_interrupt: bool,
781    /// Enable coercion for this method's parameters
782    pub coerce: bool,
783    /// The method's own `preconditions` / `no_preconditions` (bare; #1017,
784    /// #1566): `Some(true)` keeps the type-derived R-side checks of the
785    /// parameters that do not decide for themselves, `Some(false)` drops
786    /// them; the last one written wins. `None` defers to the impl block,
787    /// the crate default and the feature. The list forms
788    /// `preconditions(p)` / `no_preconditions(p)` land in
789    /// [`Self::per_param`].
790    pub preconditions: Option<bool>,
791    /// Enable RNG state management (GetRNGstate/PutRNGstate)
792    pub rng: bool,
793    /// `typed_list!(...)` spec from `#[miniextendr(dots = typed_list!(...))]` on
794    /// this method, mirroring `MiniextendrFnAttrs.dots_spec`. When set, a
795    /// `dots_typed` binding is injected at the top of the method body. Per-method
796    /// (dots apply to an individual signature), not an impl-block-level option.
797    pub dots_spec: Option<proc_macro2::TokenStream>,
798    /// What the method's `...` takes: `dots = empty | empty(allow = [..]) |
799    /// forward(allow = [..]) | data` (#2157, [`crate::dots_policy`]), checked
800    /// in the R wrapper's prelude
801    /// ([`emit_method_prelude`](crate::r_class_formatter::MethodContext::emit_method_prelude)).
802    pub dots_policy: Option<crate::dots_policy::DotsPolicy>,
803    /// Return `Result<T, E>` to R without unwrapping.
804    pub unwrap_in_r: bool,
805    /// Build the `Err` arm's condition from the error's serde output
806    /// (`#[miniextendr(serde_error(shape = .., tag = .., prefix = .., ..))]`).
807    pub serde_error: Option<crate::miniextendr_fn::SerdeErrorSpec>,
808    /// Serialize the complete return value through `AsSerialize<T>`.
809    pub serialize: bool,
810    /// Explicit return class system (`wrap = "r6"` and siblings).
811    pub wrap: Option<ClassSystem>,
812    /// Parameter defaults from `#[miniextendr(defaults(param = "value", ...))]`
813    pub defaults: std::collections::HashMap<String, String>,
814    /// Span of `defaults(...)` for error reporting.
815    pub defaults_span: Option<proc_macro2::Span>,
816    /// Per-parameter `match_arg` / `several_ok` / `choices` state for this
817    /// method, keyed by the Rust parameter name.
818    ///
819    /// Method-level (not parameter-level) because Rust's parser rejects
820    /// attribute macros on fn parameters inside impl items. Standalone
821    /// functions take the per-param syntax directly; impl methods spell the
822    /// same data through `#[miniextendr(match_arg(p1, p2))]`,
823    /// `#[miniextendr(match_arg_several_ok(p))]`,
824    /// `#[miniextendr(choices(p = "a, b"))]`,
825    /// `#[miniextendr(no_default(p))]` and
826    /// `#[miniextendr(formal(p = "p.name"))]` (#1842) on the method attribute.
827    ///
828    /// Uses the shared [`ParamAttrs`](crate::miniextendr_fn::ParamAttrs)
829    /// struct — the `coerce` / `default` fields are unused on the impl path.
830    pub per_param: std::collections::HashMap<String, crate::miniextendr_fn::ParamAttrs>,
831    /// Span of `match_arg(...)` / `choices(...)` for error reporting.
832    pub match_arg_span: Option<proc_macro2::Span>,
833    /// S7-specific method markers. Only consumed by the S7 class generator;
834    /// all other generators ignore this field.
835    pub s7: S7MethodAttrs,
836    // region: Lifecycle support
837    /// Lifecycle specification for deprecation/experimental status on methods.
838    ///
839    /// Use `#[miniextendr(lifecycle = "deprecated")]` or
840    /// `#[miniextendr(lifecycle(stage = "deprecated", when = "0.4.0", with = "new_method()"))]`
841    /// on methods in impl blocks.
842    ///
843    /// # Example
844    /// ```ignore
845    /// #[miniextendr(r6)]
846    /// impl MyType {
847    ///     #[miniextendr(lifecycle = "deprecated")]
848    ///     pub fn old_method(&self) -> i32 { 0 }
849    /// }
850    /// ```
851    pub lifecycle: Option<crate::lifecycle::LifecycleSpec>,
852    /// vctrs protocol method override.
853    ///
854    /// Use `#[miniextendr(vctrs(format))]` to mark a method as implementing a vctrs
855    /// protocol S3 generic. The method will be generated as `format.<class>` instead
856    /// of the default Rust method name.
857    ///
858    /// Supported protocols: format, vec_proxy, vec_proxy_equal, vec_proxy_compare,
859    /// vec_proxy_order, vec_restore, obj_print_data, obj_print_header, obj_print_footer.
860    pub vctrs_protocol: Option<String>,
861    /// Override R method name.
862    ///
863    /// Use `#[miniextendr(r_name = "add_one")]` to give the R method a different name
864    /// than the Rust method. The C symbol is still derived from the Rust name.
865    /// Cannot be combined with `generic = "..."` on the same method.
866    pub r_name: Option<String>,
867    /// Append a fixed suffix to the Rust method name for the R-facing name
868    /// (`#[miniextendr(postfix = "_impl")]` on `fn bump` yields `bump_impl`).
869    /// Exclusive with `r_name` and `generic`; the C symbol is unchanged.
870    pub postfix: Option<String>,
871    /// R code to inject at the very top of the method body (before all built-in checks).
872    pub r_entry: Option<String>,
873    /// R code to inject after all built-in checks, immediately before `.Call()`.
874    pub r_post_checks: Option<String>,
875    /// Register `on.exit()` cleanup code in the R method wrapper.
876    ///
877    /// Short form: `#[miniextendr(r_on_exit = "close(con)")]`
878    /// Long form: `#[miniextendr(r_on_exit(expr = "close(con)", add = false))]`
879    pub r_on_exit: Option<crate::miniextendr_fn::ROnExit>,
880    /// Mark this method as internal: adds `@keywords internal`, suppresses export.
881    ///
882    /// For R6 active bindings this emits a class-level
883    /// `#' @field name (internal)` so the binding stays satisfied for roxygen2
884    /// (which warns on undocumented R6 bindings even when `@field name NULL` is
885    /// present) but is clearly marked internal in the docs.
886    pub internal: bool,
887    /// Suppress export for this method without adding `@keywords internal`.
888    ///
889    /// For R6 active bindings this emits a class-level
890    /// `#' @field name (internal)` (see `internal` above for why we don't use
891    /// roxygen2's `@field name NULL` opt-out).
892    pub noexport: bool,
893}
894
895/// Fully parsed `#[miniextendr]` impl block, ready for code generation.
896///
897/// Contains the type identity, chosen class system, all parsed methods, the original
898/// impl block (with miniextendr attrs stripped for re-emission), and all class-system-specific
899/// configuration options. Created by [`ParsedImpl::parse`] and consumed by the per-class-system
900/// R wrapper generators and [`generate_method_c_wrapper`].
901#[derive(#[automatically_derived]
impl ::core::fmt::Debug for ParsedImpl {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        let names: &'static _ =
            &["type_ident", "class_system", "class_name", "doc_tags",
                        "methods", "original_impl", "cfg_attrs", "vctrs_attrs",
                        "r6_inherit", "r6_portable", "r6_cloneable",
                        "r6_lock_objects", "r6_lock_class", "s7_parent",
                        "s7_abstract", "r_data_accessors", "field_lookup",
                        "field_fallback", "strict", "preconditions", "internal",
                        "noexport", "constructor_demoted", "param_warnings",
                        "class_param_names"];
        let values: &[&dyn ::core::fmt::Debug] =
            &[&self.type_ident, &self.class_system, &self.class_name,
                        &self.doc_tags, &self.methods, &self.original_impl,
                        &self.cfg_attrs, &self.vctrs_attrs, &self.r6_inherit,
                        &self.r6_portable, &self.r6_cloneable,
                        &self.r6_lock_objects, &self.r6_lock_class, &self.s7_parent,
                        &self.s7_abstract, &self.r_data_accessors,
                        &self.field_lookup, &self.field_fallback, &self.strict,
                        &self.preconditions, &self.internal, &self.noexport,
                        &self.constructor_demoted, &self.param_warnings,
                        &&self.class_param_names];
        ::core::fmt::Formatter::debug_struct_fields_finish(f, "ParsedImpl",
            names, values)
    }
}Debug)]
902pub struct ParsedImpl {
903    /// The Rust type name being implemented (e.g., `Counter`).
904    pub type_ident: syn::Ident,
905    /// Which R class system to generate wrappers for.
906    pub class_system: ClassSystem,
907    /// Optional override for the R class name. When `None`, uses `type_ident` as the class name.
908    pub class_name: Option<String>,
909    /// Roxygen tag lines extracted from `///` doc comments on the impl block.
910    /// Used for class-level documentation (e.g., the R6 class docstring or S3 type description).
911    pub doc_tags: Vec<String>,
912    /// All parsed methods in this impl block, in source order.
913    pub methods: Vec<ParsedMethod>,
914    /// The original impl block with `#[miniextendr]` and roxygen attrs stripped.
915    /// Re-emitted as-is so the Rust compiler sees the actual method implementations.
916    pub original_impl: syn::ItemImpl,
917    /// `#[cfg(...)]` attributes from the impl block, propagated to all generated items
918    /// (C wrappers, R wrapper constants, call def arrays) for conditional compilation.
919    pub cfg_attrs: Vec<syn::Attribute>,
920    /// vctrs-specific attributes (only used when class_system is Vctrs)
921    pub vctrs_attrs: VctrsAttrs,
922    /// R6 parent class name for inheritance (e.g., `"ParentClass"`).
923    /// Propagated from [`ImplAttrs::r6_inherit`].
924    pub r6_inherit: Option<String>,
925    /// R6 portable flag. When `Some(true)`, generates a portable R6 class.
926    /// Propagated from [`ImplAttrs::r6_portable`].
927    pub r6_portable: Option<bool>,
928    /// R6 cloneable flag. Controls whether `$clone()` is available on instances.
929    /// Propagated from [`ImplAttrs::r6_cloneable`].
930    pub r6_cloneable: Option<bool>,
931    /// R6 lock_objects flag. When `Some(true)`, prevents adding new fields after creation.
932    /// Propagated from [`ImplAttrs::r6_lock_objects`].
933    pub r6_lock_objects: Option<bool>,
934    /// R6 lock_class flag. When `Some(true)`, prevents modifying the class definition.
935    /// Propagated from [`ImplAttrs::r6_lock_class`].
936    pub r6_lock_class: Option<bool>,
937    /// S7 parent class name for inheritance (e.g., `"ParentClass"`).
938    /// Propagated from [`ImplAttrs::s7_parent`].
939    pub s7_parent: Option<String>,
940    /// When true, marks this as an abstract S7 class that cannot be instantiated.
941    /// Propagated from [`ImplAttrs::s7_abstract`].
942    pub s7_abstract: bool,
943    /// The class-level form of the sidecar `#[r_data]` fields
944    /// (`r_data_accessors`, see [`RDataAccessors`]).
945    /// For R6: active bindings are added via `$set("active", ...)` after class creation.
946    /// For S7: properties are spliced from `.rdata_properties_{Type}` into `new_class()`.
947    /// For S3, S4 and env: `$` / `[[` field methods (`field_syntax.rs`, #1848).
948    pub r_data_accessors: RDataAccessors,
949    /// `s3(r_data_accessors, lookup = "list")`: how the S3 readers look a
950    /// field up (see [`FieldLookup`]).
951    pub field_lookup: FieldLookup,
952    /// `fallback = "Self::f"` (and `fallback_names`) on an S3, S4 or env
953    /// class with `r_data_accessors`: what the readers call for a name no
954    /// field has (see [`FieldFallback`], #2150).
955    pub field_fallback: Option<FieldFallback>,
956    /// Strict conversion mode: methods returning lossy types use checked conversions.
957    pub strict: bool,
958    /// The impl block's `preconditions` / `no_preconditions`, inherited by
959    /// every method wrapper. See [`ImplAttrs::preconditions`].
960    pub preconditions: Option<bool>,
961    /// Mark class as internal: adds `@keywords internal`, suppresses `@export`.
962    pub internal: bool,
963    /// Suppress `@export` without adding `@keywords internal`.
964    pub noexport: bool,
965    /// Generate the block as it reads when another impl block of the type
966    /// has the constructor: the constructor this block would infer stays a
967    /// static method. `expand_impl` sets it for the second rendering of a
968    /// block that infers one ([`ParsedImpl::inferred_constructor`]).
969    pub constructor_demoted: bool,
970    /// Deprecation warnings for `@param` tags found on the impl block.
971    /// Appended to the final TokenStream output.
972    pub param_warnings: proc_macro2::TokenStream,
973    /// Parameter names declared via `@param` in the impl-block doc comments.
974    ///
975    /// For R6 classes, roxygen2 8.0.0 inherits class-level `@param` tags into
976    /// all methods. These are the names that appeared in impl-block-level `@param`
977    /// tags (extracted before stripping). The R6 generator uses this set to suppress
978    /// `(no documentation available)` placeholders for covered params and to emit
979    /// class-level `@param` lines in the class header.
980    pub class_param_names: std::collections::HashSet<String>,
981}
982
983/// Attributes parsed from `#[miniextendr(...)]` on an impl block.
984///
985/// These control which R class system to use, class naming, and
986/// class-system-specific options (R6 inheritance, S7 parent, vctrs kind, etc.).
987///
988/// Parsed by the [`syn::parse::Parse`] implementation which handles all supported
989/// attribute formats like `#[miniextendr(r6, class = "Custom")]`.
990#[derive(#[automatically_derived]
impl ::core::fmt::Debug for ImplAttrs {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        let names: &'static _ =
            &["class_system", "class_name", "vctrs_attrs", "r6_inherit",
                        "r6_portable", "r6_cloneable", "r6_lock_objects",
                        "r6_lock_class", "s7_parent", "s7_abstract",
                        "r_data_accessors", "field_lookup", "field_fallback",
                        "strict", "preconditions", "internal", "noexport",
                        "blanket"];
        let values: &[&dyn ::core::fmt::Debug] =
            &[&self.class_system, &self.class_name, &self.vctrs_attrs,
                        &self.r6_inherit, &self.r6_portable, &self.r6_cloneable,
                        &self.r6_lock_objects, &self.r6_lock_class, &self.s7_parent,
                        &self.s7_abstract, &self.r_data_accessors,
                        &self.field_lookup, &self.field_fallback, &self.strict,
                        &self.preconditions, &self.internal, &self.noexport,
                        &&self.blanket];
        ::core::fmt::Formatter::debug_struct_fields_finish(f, "ImplAttrs",
            names, values)
    }
}Debug)]
991pub struct ImplAttrs {
992    /// Which R class system to generate wrappers for.
993    /// Defaults to `Env` unless overridden by feature flags (`r6-default`, `s7-default`).
994    pub class_system: ClassSystem,
995    /// Optional override for the R class name. When `None`, the Rust type name is used.
996    pub class_name: Option<String>,
997    /// vctrs-specific attributes (only used when class_system is Vctrs)
998    pub vctrs_attrs: VctrsAttrs,
999    // endregion
1000    // region: R6-specific configuration
1001    /// R6 parent class for inheritance.
1002    /// Use `#[miniextendr(r6(inherit = "ParentClass"))]` to specify the parent.
1003    pub r6_inherit: Option<String>,
1004    /// R6 portable flag. Default TRUE. Set to false for non-portable R6 classes.
1005    pub r6_portable: Option<bool>,
1006    /// R6 cloneable flag. Controls whether `$clone()` is available.
1007    pub r6_cloneable: Option<bool>,
1008    /// R6 lock_objects flag. Controls whether fields can be added after creation.
1009    pub r6_lock_objects: Option<bool>,
1010    /// R6 lock_class flag. Controls whether the class definition can be modified.
1011    pub r6_lock_class: Option<bool>,
1012    // endregion
1013    // region: S7-specific configuration
1014    /// S7 parent class for inheritance.
1015    /// Use `#[miniextendr(s7(parent = "ParentClass"))]` to specify the parent.
1016    pub s7_parent: Option<String>,
1017    /// S7 abstract class flag. Abstract classes cannot be instantiated.
1018    pub s7_abstract: bool,
1019    // endregion
1020    // region: Sidecar integration
1021    /// The class-level form of the `#[r_data]` field accessors
1022    /// (`r6(r_data_accessors)`, `s3(r_data_accessors = "get")`, ...).
1023    /// For R6: active bindings via `$set("active", ...)` post-creation.
1024    /// For S7: properties spliced from `.rdata_properties_{Type}`.
1025    /// For S3, S4 and env: `$` / `[[` field methods (#1848).
1026    pub r_data_accessors: RDataAccessors,
1027    /// How the S3 readers look a field up (`lookup = "list"`, #1921).
1028    pub field_lookup: FieldLookup,
1029    /// What the S3, S4 and env readers call for a name no field has
1030    /// (`fallback = "Self::f"`, #2150).
1031    pub field_fallback: Option<FieldFallback>,
1032    // endregion
1033    // region: Strict conversion mode
1034    /// When true, methods returning lossy types (i64/u64/isize/usize + Vec variants)
1035    /// use `strict::checked_*()` instead of `IntoR::into_sexp()`, panicking on overflow.
1036    pub strict: bool,
1037    // endregion
1038    // region: Preconditions
1039    /// `Some(false)` drops the R-side type-check guards from the generated
1040    /// method wrappers, `Some(true)` keeps them: those of an inherent impl,
1041    /// including the setter branch of an R6 active binding, and those of a
1042    /// trait impl, including the empty-body (TPIE) form. TryFromSexp still
1043    /// raises on bad input, with the same argument-error condition; the
1044    /// message comes from the conversion. Set by the bare
1045    /// `#[miniextendr(no_preconditions)]` / `preconditions` (the last one
1046    /// written wins). A method's own `preconditions` / `no_preconditions` and
1047    /// each parameter's spelling come first; `None` defers to the crate
1048    /// default, then the `no-preconditions-default` feature
1049    /// (`crate::r_preconditions::resolve_type_checks`).
1050    pub preconditions: Option<bool>,
1051    // endregion
1052    /// Mark class as internal: adds `@keywords internal`, suppresses `@export`.
1053    pub internal: bool,
1054    /// Suppress `@export` without adding `@keywords internal`.
1055    pub noexport: bool,
1056    /// When true on a trait impl (`impl Trait for Type`), the impl block is NOT
1057    /// emitted (a blanket impl already provides it), but C wrappers and R wrappers
1058    /// ARE generated from the method signatures in the body.
1059    pub blanket: bool,
1060}
1061
1062/// The impl doc tags that are class settings rather than class documentation:
1063/// they decide whether every block's members are documented and exported, so
1064/// each impl block of a type repeats them ([`ParsedImpl::class_settings`]).
1065const CLASS_SETTING_TAGS: [&str; 2] = ["noRd", "keywords internal"];
1066
1067/// Every impl-block option, for the "unknown impl block option" errors of the
1068/// bare and the `key = value` forms.
1069const IMPL_OPTIONS_HELP: &str = "env, env(...), r6, r6(...), s3, s3(...), s4, s4(...), s7, \
1070     s7(...), vctrs, vctrs(...) (class system), class = \"...\" (R class name), \
1071     blanket, strict, no_strict, preconditions, no_preconditions, internal, noexport";
1072
1073impl syn::parse::Parse for ImplAttrs {
1074    /// Parses `#[miniextendr(...)]` impl-level options.
1075    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
1076        let mut class_system = if falsecfg!(feature = "r6-default") {
1077            ClassSystem::R6
1078        } else if falsecfg!(feature = "s7-default") {
1079            ClassSystem::S7
1080        } else {
1081            ClassSystem::Env
1082        };
1083        let mut class_system_span: Option<(&str, proc_macro2::Span)> = None;
1084        let mut class_name = None;
1085        let mut vctrs_attrs = VctrsAttrs::default();
1086        let mut r6_inherit = None;
1087        let mut r6_portable = None;
1088        let mut r6_cloneable = None;
1089        let mut r6_lock_objects = None;
1090        let mut r6_lock_class = None;
1091        let mut s7_parent = None;
1092        let mut s7_abstract = false;
1093        let mut r_data_accessors = RDataAccessors::Off;
1094        let mut field_lookup: Option<(FieldLookup, proc_macro2::Span)> = None;
1095        let mut fallback: Option<(syn::LitStr, proc_macro2::Span)> = None;
1096        let mut fallback_names: Option<(syn::LitStr, proc_macro2::Span)> = None;
1097        let mut strict: Option<bool> = None;
1098        let mut preconditions: Option<bool> = None;
1099        let mut internal = false;
1100        let mut noexport = false;
1101        let mut blanket = false;
1102
1103        // Parse attributes. The first identifier can be either:
1104        // - A class system (env, r6, s3, s4, s7, vctrs)
1105        // - A key in a key=value pair (class)
1106        //
1107        // Valid formats:
1108        // - #[miniextendr]
1109        // - #[miniextendr(r6)]
1110        // - #[miniextendr(r6, class = "CustomName")]
1111        // - #[miniextendr(vctrs)]
1112        // - #[miniextendr(vctrs(kind = "rcrd", base = "double", abbr = "my_abbr"))]
1113        while !input.is_empty() {
1114            let ident: syn::Ident = input.parse()?;
1115            let ident_str = ident.to_string();
1116
1117            // Check if this is a key=value pair
1118            if input.peek(::syn::token::Eqsyn::Token![=]) {
1119                let _: ::syn::token::Eqsyn::Token![=] = input.parse()?;
1120                match ident_str.as_str() {
1121                    "class" => {
1122                        let value: syn::LitStr = input.parse()?;
1123                        class_name = Some(value.value());
1124                    }
1125                    _ => {
1126                        return Err(syn::Error::new(
1127                            ident.span(),
1128                            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("unknown impl block option `{0}`; expected one of: {1}",
                ident_str, IMPL_OPTIONS_HELP))
    })format!(
1129                                "unknown impl block option `{ident_str}`; expected one of: \
1130                                 {IMPL_OPTIONS_HELP}"
1131                            ),
1132                        ));
1133                    }
1134                }
1135            } else if ident_str == "vctrs" {
1136                // vctrs class system with optional nested attributes
1137                if let Some((prev_name, _prev_span)) = class_system_span {
1138                    return Err(syn::Error::new(
1139                        ident.span(),
1140                        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("multiple class systems specified (`{0}` and `{1}`); use only one of: env, r6, s3, s4, s7, vctrs",
                prev_name, ident_str))
    })format!(
1141                            "multiple class systems specified (`{}` and `{}`); use only one of: env, r6, s3, s4, s7, vctrs",
1142                            prev_name, ident_str
1143                        ),
1144                    ));
1145                }
1146                class_system_span = Some(("vctrs", ident.span()));
1147                class_system = ClassSystem::Vctrs;
1148
1149                // Check for nested vctrs options: vctrs(kind = "rcrd", base = "double", ...)
1150                if input.peek(syn::token::Paren) {
1151                    let content;
1152                    match ::syn::__private::parse_parens(&input) {
    ::syn::__private::Ok(parens) => {
        content = parens.content;
        _ = content;
        parens.token
    }
    ::syn::__private::Err(error) => { return ::syn::__private::Err(error); }
};syn::parenthesized!(content in input);
1153
1154                    while !content.is_empty() {
1155                        let key: syn::Ident = content.parse()?;
1156                        if key == "r_data_accessors" {
1157                            return Err(syn::Error::new(
1158                                key.span(),
1159                                "vctrs classes have no `r_data_accessors`: a vctr is an R \
1160                                 vector with no external pointer, and vctrs keeps `$` on a \
1161                                 record for its own fields. Use the standalone \
1162                                 `Type_get_<field>()` / `Type_set_<field>()`",
1163                            ));
1164                        }
1165                        if key == "fallback" || key == "fallback_names" {
1166                            return Err(no_field_fallback_error(&key, "vctrs"));
1167                        }
1168                        let _: ::syn::token::Eqsyn::Token![=] = content.parse()?;
1169                        let key_str = key.to_string();
1170
1171                        match key_str.as_str() {
1172                            "kind" => {
1173                                let value: syn::LitStr = content.parse()?;
1174                                vctrs_attrs.kind = value
1175                                    .value()
1176                                    .parse()
1177                                    .map_err(|e| syn::Error::new(value.span(), e))?;
1178                            }
1179                            "base" => {
1180                                let value: syn::LitStr = content.parse()?;
1181                                vctrs_attrs.base = Some(value.value());
1182                            }
1183                            "inherit_base_type" => {
1184                                let value: syn::LitBool = content.parse()?;
1185                                vctrs_attrs.inherit_base_type = Some(value.value());
1186                            }
1187                            "ptype" => {
1188                                let value: syn::LitStr = content.parse()?;
1189                                vctrs_attrs.ptype = Some(value.value());
1190                            }
1191                            "abbr" => {
1192                                let value: syn::LitStr = content.parse()?;
1193                                vctrs_attrs.abbr = Some(value.value());
1194                            }
1195                            _ => {
1196                                return Err(syn::Error::new(
1197                                    key.span(),
1198                                    ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("unknown vctrs option: {0} (expected kind, base, inherit_base_type, ptype, abbr)",
                key_str))
    })format!(
1199                                        "unknown vctrs option: {} (expected kind, base, inherit_base_type, ptype, abbr)",
1200                                        key_str
1201                                    ),
1202                                ));
1203                            }
1204                        }
1205
1206                        // Consume trailing comma if present
1207                        if content.peek(::syn::token::Commasyn::Token![,]) {
1208                            let _: ::syn::token::Commasyn::Token![,] = content.parse()?;
1209                        }
1210                    }
1211                }
1212            } else if ident_str == "r6" {
1213                // R6 class system with optional nested attributes
1214                // r6 or r6(inherit = "Parent", portable = false, cloneable, lock_class)
1215                if let Some((prev_name, _prev_span)) = class_system_span {
1216                    return Err(syn::Error::new(
1217                        ident.span(),
1218                        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("multiple class systems specified (`{0}` and `{1}`); use only one of: env, r6, s3, s4, s7, vctrs",
                prev_name, ident_str))
    })format!(
1219                            "multiple class systems specified (`{}` and `{}`); use only one of: env, r6, s3, s4, s7, vctrs",
1220                            prev_name, ident_str
1221                        ),
1222                    ));
1223                }
1224                class_system_span = Some(("r6", ident.span()));
1225                class_system = ClassSystem::R6;
1226
1227                if input.peek(syn::token::Paren) {
1228                    let content;
1229                    match ::syn::__private::parse_parens(&input) {
    ::syn::__private::Ok(parens) => {
        content = parens.content;
        _ = content;
        parens.token
    }
    ::syn::__private::Err(error) => { return ::syn::__private::Err(error); }
};syn::parenthesized!(content in input);
1230
1231                    while !content.is_empty() {
1232                        let key: syn::Ident = content.parse()?;
1233                        let key_str = key.to_string();
1234
1235                        match key_str.as_str() {
1236                            "inherit" => {
1237                                let _: ::syn::token::Eqsyn::Token![=] = content.parse()?;
1238                                let value: syn::LitStr = content.parse()?;
1239                                r6_inherit = Some(value.value());
1240                            }
1241                            "portable" => {
1242                                if content.peek(::syn::token::Eqsyn::Token![=]) {
1243                                    let _: ::syn::token::Eqsyn::Token![=] = content.parse()?;
1244                                    let value: syn::LitBool = content.parse()?;
1245                                    r6_portable = Some(value.value());
1246                                } else {
1247                                    r6_portable = Some(true);
1248                                }
1249                            }
1250                            "cloneable" => {
1251                                if content.peek(::syn::token::Eqsyn::Token![=]) {
1252                                    let _: ::syn::token::Eqsyn::Token![=] = content.parse()?;
1253                                    let value: syn::LitBool = content.parse()?;
1254                                    r6_cloneable = Some(value.value());
1255                                } else {
1256                                    r6_cloneable = Some(true);
1257                                }
1258                            }
1259                            "lock_objects" => {
1260                                if content.peek(::syn::token::Eqsyn::Token![=]) {
1261                                    let _: ::syn::token::Eqsyn::Token![=] = content.parse()?;
1262                                    let value: syn::LitBool = content.parse()?;
1263                                    r6_lock_objects = Some(value.value());
1264                                } else {
1265                                    r6_lock_objects = Some(true);
1266                                }
1267                            }
1268                            "lock_class" => {
1269                                if content.peek(::syn::token::Eqsyn::Token![=]) {
1270                                    let _: ::syn::token::Eqsyn::Token![=] = content.parse()?;
1271                                    let value: syn::LitBool = content.parse()?;
1272                                    r6_lock_class = Some(value.value());
1273                                } else {
1274                                    r6_lock_class = Some(true);
1275                                }
1276                            }
1277                            "r_data_accessors" => {
1278                                r_data_accessors =
1279                                    parse_r_data_accessors_value(&content, &key, ClassSystem::R6)?;
1280                            }
1281                            "fallback" | "fallback_names" => {
1282                                return Err(no_field_fallback_error(&key, "r6"));
1283                            }
1284                            _ => {
1285                                return Err(syn::Error::new(
1286                                    key.span(),
1287                                    ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("unknown r6 option: {0} (expected inherit, portable, cloneable, lock_objects, lock_class, r_data_accessors)",
                key_str))
    })format!(
1288                                        "unknown r6 option: {} (expected inherit, portable, cloneable, lock_objects, lock_class, r_data_accessors)",
1289                                        key_str
1290                                    ),
1291                                ));
1292                            }
1293                        }
1294
1295                        // Consume trailing comma if present
1296                        if content.peek(::syn::token::Commasyn::Token![,]) {
1297                            let _: ::syn::token::Commasyn::Token![,] = content.parse()?;
1298                        }
1299                    }
1300                }
1301            } else if ident_str == "s7" {
1302                // S7 class system with optional nested attributes
1303                // s7 or s7(parent = "Parent", abstract)
1304                if let Some((prev_name, _prev_span)) = class_system_span {
1305                    return Err(syn::Error::new(
1306                        ident.span(),
1307                        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("multiple class systems specified (`{0}` and `{1}`); use only one of: env, r6, s3, s4, s7, vctrs",
                prev_name, ident_str))
    })format!(
1308                            "multiple class systems specified (`{}` and `{}`); use only one of: env, r6, s3, s4, s7, vctrs",
1309                            prev_name, ident_str
1310                        ),
1311                    ));
1312                }
1313                class_system_span = Some(("s7", ident.span()));
1314                class_system = ClassSystem::S7;
1315
1316                if input.peek(syn::token::Paren) {
1317                    let content;
1318                    match ::syn::__private::parse_parens(&input) {
    ::syn::__private::Ok(parens) => {
        content = parens.content;
        _ = content;
        parens.token
    }
    ::syn::__private::Err(error) => { return ::syn::__private::Err(error); }
};syn::parenthesized!(content in input);
1319
1320                    while !content.is_empty() {
1321                        // Use parse_any to accept `abstract` (a reserved keyword)
1322                        use syn::ext::IdentExt;
1323                        let key = syn::Ident::parse_any(&content)?;
1324                        let key_str = key.to_string();
1325
1326                        match key_str.as_str() {
1327                            "parent" => {
1328                                let _: ::syn::token::Eqsyn::Token![=] = content.parse()?;
1329                                let value: syn::LitStr = content.parse()?;
1330                                s7_parent = Some(value.value());
1331                            }
1332                            "abstract" => {
1333                                if content.peek(::syn::token::Eqsyn::Token![=]) {
1334                                    let _: ::syn::token::Eqsyn::Token![=] = content.parse()?;
1335                                    let value: syn::LitBool = content.parse()?;
1336                                    s7_abstract = value.value();
1337                                } else {
1338                                    s7_abstract = true;
1339                                }
1340                            }
1341                            "r_data_accessors" => {
1342                                r_data_accessors =
1343                                    parse_r_data_accessors_value(&content, &key, ClassSystem::S7)?;
1344                            }
1345                            "fallback" | "fallback_names" => {
1346                                return Err(no_field_fallback_error(&key, "s7"));
1347                            }
1348                            _ => {
1349                                return Err(syn::Error::new(
1350                                    key.span(),
1351                                    ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("unknown s7 option: {0} (expected parent, abstract, r_data_accessors)",
                key_str))
    })format!(
1352                                        "unknown s7 option: {} (expected parent, abstract, r_data_accessors)",
1353                                        key_str
1354                                    ),
1355                                ));
1356                            }
1357                        }
1358
1359                        // Consume trailing comma if present
1360                        if content.peek(::syn::token::Commasyn::Token![,]) {
1361                            let _: ::syn::token::Commasyn::Token![,] = content.parse()?;
1362                        }
1363                    }
1364                }
1365            } else if ident_str == "blanket" {
1366                blanket = true;
1367            } else if ident_str == "strict" {
1368                strict = Some(true);
1369            } else if ident_str == "no_strict" {
1370                strict = Some(false);
1371            } else if ident_str == "preconditions" {
1372                preconditions = Some(true);
1373            } else if ident_str == "no_preconditions" {
1374                preconditions = Some(false);
1375            } else if ident_str == "internal" {
1376                internal = true;
1377            } else if ident_str == "noexport" {
1378                noexport = true;
1379            } else {
1380                // A class system identifier, or no option at all.
1381                let parsed_system: ClassSystem = ident_str.parse().map_err(|_| {
1382                    syn::Error::new(
1383                        ident.span(),
1384                        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("unknown impl block option `{0}`; expected one of: {1}",
                ident_str, IMPL_OPTIONS_HELP))
    })format!(
1385                            "unknown impl block option `{ident_str}`; expected one of: \
1386                             {IMPL_OPTIONS_HELP}"
1387                        ),
1388                    )
1389                })?;
1390                if let Some((prev_name, _prev_span)) = class_system_span {
1391                    return Err(syn::Error::new(
1392                        ident.span(),
1393                        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("multiple class systems specified (`{0}` and `{1}`); use only one of: env, r6, s3, s4, s7, vctrs",
                prev_name, ident_str))
    })format!(
1394                            "multiple class systems specified (`{}` and `{}`); use only one of: env, r6, s3, s4, s7, vctrs",
1395                            prev_name, ident_str
1396                        ),
1397                    ));
1398                }
1399                class_system_span = Some((
1400                    match parsed_system {
1401                        ClassSystem::Env => "env",
1402                        ClassSystem::R6 => "r6",
1403                        ClassSystem::S3 => "s3",
1404                        ClassSystem::S4 => "s4",
1405                        ClassSystem::S7 => "s7",
1406                        ClassSystem::Vctrs => "vctrs",
1407                    },
1408                    ident.span(),
1409                ));
1410                class_system = parsed_system;
1411
1412                // `s3(...)`, `s4(...)`, `env(...)`: the field syntax option.
1413                if input.peek(syn::token::Paren) {
1414                    let content;
1415                    match ::syn::__private::parse_parens(&input) {
    ::syn::__private::Ok(parens) => {
        content = parens.content;
        _ = content;
        parens.token
    }
    ::syn::__private::Err(error) => { return ::syn::__private::Err(error); }
};syn::parenthesized!(content in input);
1416                    while !content.is_empty() {
1417                        let key: syn::Ident = content.parse()?;
1418                        if key == "lookup" && parsed_system == ClassSystem::S3 {
1419                            if field_lookup.is_some() {
1420                                return Err(syn::Error::new(key.span(), "`lookup` is given twice"));
1421                            }
1422                            let _: ::syn::token::Eqsyn::Token![=] = content.parse()?;
1423                            let value: syn::LitStr = content.parse()?;
1424                            let lookup = match value.value().as_str() {
1425                                "strict" => FieldLookup::Strict,
1426                                "list" => FieldLookup::List,
1427                                other => {
1428                                    return Err(syn::Error::new(
1429                                        value.span(),
1430                                        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("unknown `lookup` value \"{0}\"; expected \"strict\" (exact names and whole-number positions, the default) or \"list\" (an unknown name or `NA` reads `NULL`)",
                other))
    })format!(
1431                                            "unknown `lookup` value \"{other}\"; expected \
1432                                             \"strict\" (exact names and whole-number \
1433                                             positions, the default) or \"list\" (an unknown \
1434                                             name or `NA` reads `NULL`)"
1435                                        ),
1436                                    ));
1437                                }
1438                            };
1439                            field_lookup = Some((lookup, key.span()));
1440                        } else if key == "r_data_accessors" {
1441                            r_data_accessors =
1442                                parse_r_data_accessors_value(&content, &key, parsed_system)?;
1443                        } else if key == "fallback" || key == "fallback_names" {
1444                            let slot = if key == "fallback" {
1445                                &mut fallback
1446                            } else {
1447                                &mut fallback_names
1448                            };
1449                            if slot.is_some() {
1450                                return Err(syn::Error::new(
1451                                    key.span(),
1452                                    ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("`{0}` is given twice", key))
    })format!("`{key}` is given twice"),
1453                                ));
1454                            }
1455                            *slot = Some((parse_fallback_value(&content, &key)?, key.span()));
1456                        } else {
1457                            let lookup = if parsed_system == ClassSystem::S3 {
1458                                ", lookup = \"list\""
1459                            } else {
1460                                ""
1461                            };
1462                            return Err(syn::Error::new(
1463                                key.span(),
1464                                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("unknown {0} option: {1} (expected r_data_accessors, or r_data_accessors = \"get\" for getters only{2}, fallback = \"Self::f\", fallback_names = \"Self::g\")",
                ident_str, key, lookup))
    })format!(
1465                                    "unknown {ident_str} option: {key} (expected \
1466                                     r_data_accessors, or r_data_accessors = \"get\" for \
1467                                     getters only{lookup}, fallback = \"Self::f\", \
1468                                     fallback_names = \"Self::g\")"
1469                                ),
1470                            ));
1471                        }
1472                        if content.peek(::syn::token::Commasyn::Token![,]) {
1473                            let _: ::syn::token::Commasyn::Token![,] = content.parse()?;
1474                        }
1475                    }
1476                    if let Some((_, span)) = field_lookup
1477                        && !r_data_accessors.enabled()
1478                    {
1479                        return Err(syn::Error::new(
1480                            span,
1481                            "`lookup` sets how the readers of `s3(r_data_accessors)` look a \
1482                             field up; add `r_data_accessors` (or `r_data_accessors = \"get\"`)",
1483                        ));
1484                    }
1485                    if let Some((_, span)) = &fallback
1486                        && !r_data_accessors.enabled()
1487                    {
1488                        return Err(syn::Error::new(
1489                            *span,
1490                            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("`fallback` answers the names no field has for the `$` / `[[` readers of `{0}(r_data_accessors)`; add `r_data_accessors` (or `r_data_accessors = \"get\"`)",
                ident_str))
    })format!(
1491                                "`fallback` answers the names no field has for the `$` / `[[` \
1492                                 readers of `{ident_str}(r_data_accessors)`; add \
1493                                 `r_data_accessors` (or `r_data_accessors = \"get\"`)"
1494                            ),
1495                        ));
1496                    }
1497                    if let Some((_, span)) = &fallback_names
1498                        && fallback.is_none()
1499                    {
1500                        return Err(syn::Error::new(
1501                            *span,
1502                            "`fallback_names` lists the names `fallback` answers, for \
1503                             `.DollarNames()`; add `fallback = \"Self::f\"`",
1504                        ));
1505                    }
1506                }
1507            }
1508
1509            // Consume trailing comma if present
1510            if input.peek(::syn::token::Commasyn::Token![,]) {
1511                let _: ::syn::token::Commasyn::Token![,] = input.parse()?;
1512            }
1513        }
1514
1515        Ok(ImplAttrs {
1516            class_system,
1517            class_name,
1518            vctrs_attrs,
1519            r6_inherit,
1520            r6_portable,
1521            r6_cloneable,
1522            r6_lock_objects,
1523            r6_lock_class,
1524            s7_parent,
1525            s7_abstract,
1526            r_data_accessors,
1527            field_lookup: field_lookup.map(|(lookup, _)| lookup).unwrap_or_default(),
1528            field_fallback: fallback.map(|(get, _)| FieldFallback {
1529                get,
1530                names: fallback_names.map(|(names, _)| names),
1531            }),
1532            strict: strict.unwrap_or(falsecfg!(feature = "strict-default")),
1533            preconditions,
1534            internal,
1535            noexport,
1536            blanket,
1537        })
1538    }
1539}
1540
1541impl ParsedMethod {
1542    /// Validate method attributes for the given class system.
1543    /// Returns an error if unsupported attributes are used.
1544    fn validate_method_attrs(
1545        attrs: &MethodAttrs,
1546        class_system: ClassSystem,
1547        span: proc_macro2::Span,
1548    ) -> syn::Result<()> {
1549        if (attrs.receiver.is_some() || attrs.no_dispatch_dots)
1550            && (!#[allow(non_exhaustive_omitted_patterns)] match class_system {
    ClassSystem::S3 | ClassSystem::S4 => true,
    _ => false,
}matches!(class_system, ClassSystem::S3 | ClassSystem::S4)
1551                || attrs.generic.is_none())
1552        {
1553            return Err(syn::Error::new(
1554                span,
1555                "`receiver` and `no_dots` require an explicit S3/S4 `generic = \"...\"`",
1556            ));
1557        }
1558
1559        // R6-only boolean markers must not appear under any other class system.
1560        if class_system != ClassSystem::R6 {
1561            if attrs.r6.active {
1562                return Err(syn::Error::new(
1563                    attrs.r6.active_span.unwrap_or(span),
1564                    "`active` is only valid for R6 class systems",
1565                ));
1566            }
1567            if attrs.r6.private {
1568                return Err(syn::Error::new(
1569                    attrs.r6.private_span.unwrap_or(span),
1570                    "`private` is only valid for R6 class systems",
1571                ));
1572            }
1573            if attrs.r6.finalize {
1574                return Err(syn::Error::new(
1575                    attrs.r6.finalize_span.unwrap_or(span),
1576                    "`finalize` is only valid for R6 class systems",
1577                ));
1578            }
1579            if attrs.r6.deep_clone {
1580                return Err(syn::Error::new(
1581                    attrs.r6.deep_clone_span.unwrap_or(span),
1582                    "`deep_clone` is only valid for R6 class systems",
1583                ));
1584            }
1585        }
1586
1587        // convert_from and convert_to are mutually exclusive on the same method
1588        // - convert_from expects a static method (no &self, takes source type)
1589        // - convert_to expects an instance method (&self, returns target type)
1590        if attrs.s7.convert_from.is_some() && attrs.s7.convert_to.is_some() {
1591            return Err(syn::Error::new(
1592                span,
1593                "cannot specify both `convert_from` and `convert_to` on the same method; \
1594                 convert_from is for static methods, convert_to is for instance methods",
1595            ));
1596        }
1597
1598        // postfix derives the R name from the Rust name; it cannot combine
1599        // with another naming source.
1600        if attrs.postfix.is_some() && attrs.r_name.is_some() {
1601            return Err(syn::Error::new(
1602                span,
1603                "`postfix` and `r_name` both set the R method name; use one of them.",
1604            ));
1605        }
1606        if attrs.postfix.is_some() && attrs.generic.is_some() {
1607            return Err(syn::Error::new(
1608                span,
1609                "`postfix` and `generic` cannot be used on the same method. \
1610                 Generic dispatch names the R method after the generic.",
1611            ));
1612        }
1613
1614        if attrs.serialize && (attrs.unwrap_in_r || attrs.serde_error.is_some()) {
1615            return Err(syn::Error::new(
1616                proc_macro2::Span::call_site(),
1617                "`serialize` cannot be combined with `unwrap_in_r` or `serde_error`: it serializes the complete return value, including any Result variant",
1618            ));
1619        }
1620
1621        // serde_error classes the raised condition; unwrap_in_r never raises.
1622        if attrs.serde_error.is_some() && attrs.unwrap_in_r {
1623            return Err(syn::Error::new(
1624                span,
1625                "`serde_error` cannot be used with `unwrap_in_r`: `unwrap_in_r` returns the \
1626                 `Result` to R as a value, so there is no raised condition to class.",
1627            ));
1628        }
1629
1630        // r_name and generic are mutually exclusive
1631        if attrs.r_name.is_some() && attrs.generic.is_some() {
1632            return Err(syn::Error::new(
1633                span,
1634                "`r_name` and `generic` cannot be used on the same method. \
1635                 Use `r_name` for a simple rename, or `generic`/`class` for S3/S4/S7 generic dispatch.",
1636            ));
1637        }
1638
1639        // Static methods can select worker dispatch. Receiver and dots borrows
1640        // keep their wrappers on the main thread (see generate_method_c_wrapper).
1641
1642        Ok(())
1643    }
1644
1645    /// Parse method attributes in #[miniextendr(class_system(...))] format.
1646    ///
1647    /// Supported formats:
1648    /// - `#[miniextendr(r6(ignore, constructor, finalize, private, generic = "...")]`
1649    /// - `#[miniextendr(s3(ignore, constructor, generic = "..."))]`
1650    /// - `#[miniextendr(s7(ignore, constructor, generic = "..."))]`
1651    /// - etc.
1652    fn parse_method_attrs(attrs: &[syn::Attribute]) -> syn::Result<MethodAttrs> {
1653        use syn::spanned::Spanned;
1654        let mut method_attrs = MethodAttrs::default();
1655        // Use Option<bool> for fields that support feature defaults.
1656        let mut worker: Option<bool> = None;
1657        let mut unsafe_main_thread: Option<bool> = None;
1658        let mut coerce: Option<bool> = None;
1659
1660        for attr in attrs {
1661            // Parse new-style #[miniextendr(class_system(...))] attributes
1662            if !attr.path().is_ident("miniextendr") {
1663                continue;
1664            }
1665
1666            // Parse the nested content: miniextendr(class_system(options...)) or miniextendr(defaults(...))
1667            attr.parse_nested_meta(|meta| {
1668                // Note: "vctrs" is handled separately below for protocol method overrides
1669                let is_class_meta = meta.path.is_ident("env")
1670                    || meta.path.is_ident("r6")
1671                    || meta.path.is_ident("s7")
1672                    || meta.path.is_ident("s3")
1673                    || meta.path.is_ident("s4");
1674
1675                if is_class_meta {
1676                    // Parse the inner options: r6(ignore, constructor, ...)
1677                    meta.parse_nested_meta(|inner| {
1678                        if inner.path.is_ident("ignore") {
1679                            method_attrs.ignore = true;
1680                        } else if inner.path.is_ident("invisible") {
1681                            method_attrs.force_invisible = Some(true);
1682                        } else if inner.path.is_ident("visible") {
1683                            method_attrs.force_invisible = Some(false);
1684                        } else if inner.path.is_ident("constructor") {
1685                            method_attrs.constructor = true;
1686                        } else if inner.path.is_ident("finalize") {
1687                            method_attrs.r6.finalize = true;
1688                            method_attrs.r6.finalize_span = Some(inner.path.span());
1689                        } else if inner.path.is_ident("private") {
1690                            method_attrs.r6.private = true;
1691                            method_attrs.r6.private_span = Some(inner.path.span());
1692                        } else if inner.path.is_ident("active") {
1693                            method_attrs.r6.active = true;
1694                            method_attrs.r6.active_span = Some(inner.path.span());
1695                        } else if inner.path.is_ident("setter") {
1696                            // Active binding setter: works for both R6 and S7
1697                            method_attrs.r6.setter = true;
1698                            method_attrs.s7.setter = true;
1699                        } else if inner.path.is_ident("worker") {
1700                            worker = Some(true);
1701                            crate::deprecation::worker_option(inner.path.span());
1702                        } else if inner.path.is_ident("no_worker") {
1703                            worker = Some(false);
1704                        } else if inner.path.is_ident("main_thread") {
1705                            unsafe_main_thread = Some(true);
1706                        } else if inner.path.is_ident("no_main_thread") {
1707                            unsafe_main_thread = Some(false);
1708                        } else if inner.path.is_ident("check_interrupt") {
1709                            method_attrs.check_interrupt = true;
1710                        } else if inner.path.is_ident("coerce") {
1711                            coerce = Some(true);
1712                        } else if inner.path.is_ident("no_coerce") {
1713                            coerce = Some(false);
1714                        } else if inner.path.is_ident("rng") {
1715                            if inner.input.peek(syn::token::Paren) {
1716                                return crate::miniextendr_fn::refuse_rng_seed_on_method(
1717                                    &inner,
1718                                    "an impl method",
1719                                );
1720                            }
1721                            method_attrs.rng = true;
1722                        } else if inner.path.is_ident("unwrap_in_r") {
1723                            method_attrs.unwrap_in_r = true;
1724                        } else if inner.path.is_ident("wrap") {
1725                            let value: syn::LitStr = inner.value()?.parse()?;
1726                            method_attrs.wrap = Some(crate::return_wrap::parse_system(&value)?);
1727                        } else if inner.path.is_ident("serialize") {
1728                            method_attrs.serialize = true;
1729                        } else if inner.path.is_ident("serde_error") {
1730                            method_attrs.serde_error =
1731                                Some(crate::miniextendr_fn::parse_serde_error_nested(&inner)?);
1732                        } else if inner.path.is_ident("generic") {
1733                            let _: ::syn::token::Eqsyn::Token![=] = inner.input.parse()?;
1734                            let value: syn::LitStr = inner.input.parse()?;
1735                            method_attrs.generic = Some(crate::naming::parse_generic(&value)?);
1736                        } else if inner.path.is_ident("receiver") {
1737                            let value: syn::LitStr = inner.value()?.parse()?;
1738                            let name = crate::naming::parse_r_receiver(&value)?;
1739                            if method_attrs.receiver.replace(name).is_some() {
1740                                return Err(inner.error("receiver may be specified only once"));
1741                            }
1742                        } else if inner.path.is_ident("class") {
1743                            let _: ::syn::token::Eqsyn::Token![=] = inner.input.parse()?;
1744                            let value: syn::LitStr = inner.input.parse()?;
1745                            method_attrs.class = Some(value.value());
1746                        } else if inner.path.is_ident("getter") {
1747                            method_attrs.s7.getter = true;
1748                        } else if inner.path.is_ident("validate") {
1749                            method_attrs.s7.validate = true;
1750                        } else if inner.path.is_ident("prop") {
1751                            let _: ::syn::token::Eqsyn::Token![=] = inner.input.parse()?;
1752                            let value: syn::LitStr = inner.input.parse()?;
1753                            let prop_value = value.value();
1754                            // Set both S7 and R6 prop - the class system will use the appropriate one
1755                            method_attrs.s7.prop = Some(prop_value.clone());
1756                            method_attrs.r6.prop = Some(prop_value);
1757                        } else if inner.path.is_ident("default") {
1758                            let _: ::syn::token::Eqsyn::Token![=] = inner.input.parse()?;
1759                            let value: syn::LitStr = inner.input.parse()?;
1760                            method_attrs.s7.default = Some(value.value());
1761                        } else if inner.path.is_ident("required") {
1762                            method_attrs.s7.required = true;
1763                        } else if inner.path.is_ident("frozen") {
1764                            method_attrs.s7.frozen = true;
1765                        } else if inner.path.is_ident("deprecated") {
1766                            let _: ::syn::token::Eqsyn::Token![=] = inner.input.parse()?;
1767                            let value: syn::LitStr = inner.input.parse()?;
1768                            method_attrs.s7.deprecated = Some(value.value());
1769                        } else if inner.path.is_ident("no_dots") {
1770                            if meta.path.is_ident("s3") || meta.path.is_ident("s4") {
1771                                method_attrs.no_dispatch_dots = true;
1772                            } else {
1773                                method_attrs.s7.no_dots = true;
1774                            }
1775                        } else if inner.path.is_ident("dispatch") {
1776                            let _: ::syn::token::Eqsyn::Token![=] = inner.input.parse()?;
1777                            let value: syn::LitStr = inner.input.parse()?;
1778                            method_attrs.s7.dispatch = Some(value.value());
1779                        } else if inner.path.is_ident("fallback") {
1780                            method_attrs.s7.fallback = true;
1781                        } else if inner.path.is_ident("no_shortcut") {
1782                            method_attrs.s7.no_shortcut = true;
1783                        } else if inner.path.is_ident("convert_from") {
1784                            let _: ::syn::token::Eqsyn::Token![=] = inner.input.parse()?;
1785                            let value: syn::LitStr = inner.input.parse()?;
1786                            method_attrs.s7.convert_from = Some(value.value());
1787                        } else if inner.path.is_ident("convert_to") {
1788                            let _: ::syn::token::Eqsyn::Token![=] = inner.input.parse()?;
1789                            let value: syn::LitStr = inner.input.parse()?;
1790                            method_attrs.s7.convert_to = Some(value.value());
1791                        } else if inner.path.is_ident("deep_clone") {
1792                            method_attrs.r6.deep_clone = true;
1793                            method_attrs.r6.deep_clone_span = Some(inner.path.span());
1794                        } else if inner.path.is_ident("r_name") {
1795                            let _: ::syn::token::Eqsyn::Token![=] = inner.input.parse()?;
1796                            let value: syn::LitStr = inner.input.parse()?;
1797                            let val = value.value();
1798                            if val.is_empty() {
1799                                return Err(syn::Error::new_spanned(value, "r_name must not be empty"));
1800                            }
1801                            method_attrs.r_name = Some(val);
1802                        } else if inner.path.is_ident("postfix") {
1803                            let _: ::syn::token::Eqsyn::Token![=] = inner.input.parse()?;
1804                            let value: syn::LitStr = inner.input.parse()?;
1805                            let val = value.value();
1806                            crate::miniextendr_fn::validate_postfix(&val, &value)?;
1807                            method_attrs.postfix = Some(val);
1808                        } else if inner.path.is_ident("call") {
1809                            return Err(inner.error(
1810                                "`call = caller` is only supported on standalone `#[miniextendr]` \
1811                                 functions (the internal-entry-point pattern); class methods keep \
1812                                 their own call attribution",
1813                            ));
1814                        } else if inner.path.is_ident("r_entry") {
1815                            let _: ::syn::token::Eqsyn::Token![=] = inner.input.parse()?;
1816                            let value: syn::LitStr = inner.input.parse()?;
1817                            method_attrs.r_entry = Some(value.value());
1818                        } else if inner.path.is_ident("r_post_checks") {
1819                            let _: ::syn::token::Eqsyn::Token![=] = inner.input.parse()?;
1820                            let value: syn::LitStr = inner.input.parse()?;
1821                            method_attrs.r_post_checks = Some(value.value());
1822                        } else if inner.path.is_ident("r_on_exit") {
1823                            if inner.input.peek(::syn::token::Eqsyn::Token![=]) {
1824                                // Short form: r_on_exit = "expr"
1825                                let _: ::syn::token::Eqsyn::Token![=] = inner.input.parse()?;
1826                                let value: syn::LitStr = inner.input.parse()?;
1827                                method_attrs.r_on_exit = Some(crate::miniextendr_fn::ROnExit {
1828                                    expr: value.value(),
1829                                    add: true,
1830                                    after: true,
1831                                });
1832                            } else {
1833                                // Long form: r_on_exit(expr = "...", add = false, after = false)
1834                                let mut expr = None;
1835                                let mut add = true;
1836                                let mut after = true;
1837                                inner.parse_nested_meta(|meta| {
1838                                    if meta.path.is_ident("expr") {
1839                                        let _: ::syn::token::Eqsyn::Token![=] = meta.input.parse()?;
1840                                        let value: syn::LitStr = meta.input.parse()?;
1841                                        expr = Some(value.value());
1842                                    } else if meta.path.is_ident("add") {
1843                                        let _: ::syn::token::Eqsyn::Token![=] = meta.input.parse()?;
1844                                        let value: syn::LitBool = meta.input.parse()?;
1845                                        add = value.value;
1846                                    } else if meta.path.is_ident("after") {
1847                                        let _: ::syn::token::Eqsyn::Token![=] = meta.input.parse()?;
1848                                        let value: syn::LitBool = meta.input.parse()?;
1849                                        after = value.value;
1850                                    } else {
1851                                        return Err(meta.error(
1852                                            "unknown r_on_exit option; expected `expr`, `add`, or `after`",
1853                                        ));
1854                                    }
1855                                    Ok(())
1856                                })?;
1857                                let expr = expr.ok_or_else(|| {
1858                                    inner.error("r_on_exit(...) requires `expr = \"...\"` specifying the R expression")
1859                                })?;
1860                                method_attrs.r_on_exit = Some(crate::miniextendr_fn::ROnExit { expr, add, after });
1861                            }
1862                        } else {
1863                            return Err(inner.error(
1864                                "unknown method option; expected one of: ignore, constructor, finalize, private, active, worker, no_worker, main_thread, no_main_thread, check_interrupt, coerce, no_coerce, rng, unwrap_in_r, serialize, serde_error, generic, class, getter, setter, validate, prop, default, required, frozen, deprecated, no_dots, dispatch, fallback, no_shortcut, convert_from, convert_to, deep_clone, r_on_exit, r_name, postfix, invisible, visible"
1865                            ));
1866                        }
1867                        Ok(())
1868                    })?;
1869                } else if meta.path.is_ident("defaults") {
1870                    // Capture span for error reporting
1871                    method_attrs.defaults_span = Some(meta.path.span());
1872                    // Parse defaults(param = "value", param2 = "value2", ...)
1873                    meta.parse_nested_meta(|inner| {
1874                        // Get parameter name
1875                        let param_name = inner
1876                            .path
1877                            .get_ident()
1878                            .ok_or_else(|| inner.error("expected parameter name"))?
1879                            .to_string();
1880                        // Parse = "value"
1881                        let _: ::syn::token::Eqsyn::Token![=] = inner.input.parse()?;
1882                        let value: syn::LitStr = inner.input.parse()?;
1883                        method_attrs.defaults.insert(param_name, value.value());
1884                        Ok(())
1885                    })?;
1886                } else if meta.path.is_ident("preconditions")
1887                    || meta.path.is_ident("no_preconditions")
1888                {
1889                    // Bare: the whole method keeps / drops its type-derived
1890                    // R-side checks. `preconditions(p, q)` /
1891                    // `no_preconditions(p)`: those parameters. Last one wins.
1892                    let keep = meta.path.is_ident("preconditions");
1893                    if meta.input.peek(syn::token::Paren) {
1894                        method_attrs.match_arg_span.get_or_insert(meta.path.span());
1895                        crate::miniextendr_fn::parse_method_preconditions(
1896                            &meta,
1897                            &mut method_attrs.per_param,
1898                            keep,
1899                        )?;
1900                    } else {
1901                        method_attrs.preconditions = Some(keep);
1902                    }
1903                } else if meta.path.is_ident("no_na") {
1904                    // `no_na(p, q(message = "..."))` — R-side `!anyNA(p)` checks.
1905                    method_attrs.match_arg_span.get_or_insert(meta.path.span());
1906                    crate::miniextendr_fn::parse_method_no_na(&meta, &mut method_attrs.per_param)?;
1907                } else if let Some(check) = crate::miniextendr_fn::ClassCheck::of(&meta.path) {
1908                    // `inherits(p = "cls_a, cls_b", q(class = "cls", message = "..."))` —
1909                    // R-side `inherits(p, c(...))` checks; `not_inherits(...)` the
1910                    // same shape, `!inherits(p, c(...))`.
1911                    method_attrs.match_arg_span.get_or_insert(meta.path.span());
1912                    crate::miniextendr_fn::parse_method_class_check(
1913                        &meta,
1914                        &mut method_attrs.per_param,
1915                        check,
1916                    )?;
1917                } else if meta.path.is_ident("match_arg") {
1918                    // `match_arg(param1, param2, ...)` — scalar match_arg params.
1919                    method_attrs.match_arg_span.get_or_insert(meta.path.span());
1920                    meta.parse_nested_meta(|inner| {
1921                        let name = crate::miniextendr_fn::method_check_param(&inner)?;
1922                        method_attrs
1923                            .per_param
1924                            .entry(name)
1925                            .or_default()
1926                            .match_arg = true;
1927                        Ok(())
1928                    })?;
1929                } else if meta.path.is_ident("match_arg_several_ok") {
1930                    // `match_arg_several_ok(param1, param2, ...)` — match_arg + several_ok,
1931                    // for Vec/slice/array/Box<[_]>-typed parameters.
1932                    method_attrs.match_arg_span.get_or_insert(meta.path.span());
1933                    meta.parse_nested_meta(|inner| {
1934                        let name = crate::miniextendr_fn::method_check_param(&inner)?;
1935                        let entry = method_attrs.per_param.entry(name).or_default();
1936                        entry.match_arg = true;
1937                        entry.several_ok = true;
1938                        Ok(())
1939                    })?;
1940                } else if meta.path.is_ident("no_default") {
1941                    // `no_default(param1, param2, ...)` — choice params whose R
1942                    // formal has no default (#1828).
1943                    method_attrs.match_arg_span.get_or_insert(meta.path.span());
1944                    crate::miniextendr_fn::parse_method_no_default(
1945                        &meta,
1946                        &mut method_attrs.per_param,
1947                    )?;
1948                } else if meta.path.is_ident("formal") {
1949                    // `formal(conf_int = "conf.int", ...)` — the R formal of each
1950                    // named parameter (#1842).
1951                    method_attrs.match_arg_span.get_or_insert(meta.path.span());
1952                    crate::miniextendr_fn::parse_method_formal(&meta, &mut method_attrs.per_param)?;
1953                } else if meta.path.is_ident("choices") {
1954                    // `choices(param = "a, b, c", param2 = "x, y")` — explicit string choice lists.
1955                    method_attrs.match_arg_span.get_or_insert(meta.path.span());
1956                    meta.parse_nested_meta(|inner| {
1957                        let name = crate::miniextendr_fn::method_check_param(&inner)?;
1958                        let _: ::syn::token::Eqsyn::Token![=] = inner.input.parse()?;
1959                        let value: syn::LitStr = inner.input.parse()?;
1960                        let choices = crate::r_wrapper_builder::split_choice_list(&value.value());
1961                        method_attrs.per_param.entry(name).or_default().choices = Some(choices);
1962                        Ok(())
1963                    })?;
1964                } else if meta.path.is_ident("choices_several_ok") {
1965                    // `choices_several_ok(param = "a, b, c")` — choices + several_ok.
1966                    method_attrs.match_arg_span.get_or_insert(meta.path.span());
1967                    meta.parse_nested_meta(|inner| {
1968                        let name = crate::miniextendr_fn::method_check_param(&inner)?;
1969                        let _: ::syn::token::Eqsyn::Token![=] = inner.input.parse()?;
1970                        let value: syn::LitStr = inner.input.parse()?;
1971                        let choices = crate::r_wrapper_builder::split_choice_list(&value.value());
1972                        let entry = method_attrs.per_param.entry(name).or_default();
1973                        entry.choices = Some(choices);
1974                        entry.several_ok = true;
1975                        Ok(())
1976                    })?;
1977                } else if meta.path.is_ident("unsafe") {
1978                    // Parse unsafe(main_thread) - same syntax as standalone functions
1979                    meta.parse_nested_meta(|inner| {
1980                        if inner.path.is_ident("main_thread") {
1981                            unsafe_main_thread = Some(true);
1982                        } else {
1983                            return Err(inner.error(
1984                                "unknown `unsafe(...)` option; only `main_thread` is supported",
1985                            ));
1986                        }
1987                        Ok(())
1988                    })?;
1989                } else if meta.path.is_ident("check_interrupt") {
1990                    method_attrs.check_interrupt = true;
1991                } else if meta.path.is_ident("coerce") {
1992                    coerce = Some(true);
1993                } else if meta.path.is_ident("no_coerce") {
1994                    coerce = Some(false);
1995                } else if meta.path.is_ident("rng") {
1996                    if meta.input.peek(syn::token::Paren) {
1997                        return crate::miniextendr_fn::refuse_rng_seed_on_method(
1998                            &meta,
1999                            "an impl method",
2000                        );
2001                    }
2002                    method_attrs.rng = true;
2003                } else if meta.path.is_ident("unwrap_in_r") {
2004                    method_attrs.unwrap_in_r = true;
2005                } else if meta.path.is_ident("wrap") {
2006                    let value: syn::LitStr = meta.value()?.parse()?;
2007                    method_attrs.wrap = Some(crate::return_wrap::parse_system(&value)?);
2008                } else if meta.path.is_ident("serialize") {
2009                    method_attrs.serialize = true;
2010                } else if meta.path.is_ident("serde_error") {
2011                    method_attrs.serde_error =
2012                        Some(crate::miniextendr_fn::parse_serde_error_nested(&meta)?);
2013                } else if meta.path.is_ident("as") {
2014                    // Parse as = "data.frame", as = "list", etc.
2015                    method_attrs.as_coercion_span = Some(meta.path.span());
2016                    let _: ::syn::token::Eqsyn::Token![=] = meta.input.parse()?;
2017                    let value: syn::LitStr = meta.input.parse()?;
2018                    let coercion_type = value.value();
2019
2020                    // Validate the coercion type
2021                    const SUPPORTED_AS_TYPES: &[&str] = &[
2022                        "data.frame",
2023                        "list",
2024                        "character",
2025                        "numeric",
2026                        "double",
2027                        "integer",
2028                        "logical",
2029                        "matrix",
2030                        "vector",
2031                        "factor",
2032                        "Date",
2033                        "POSIXct",
2034                        "complex",
2035                        "raw",
2036                        "environment",
2037                        "function",
2038                        "tibble",
2039                        "data.table",
2040                        "array",
2041                        "ts",
2042                    ];
2043
2044                    if !SUPPORTED_AS_TYPES.contains(&coercion_type.as_str()) {
2045                        return Err(syn::Error::new(
2046                            value.span(),
2047                            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("unsupported `as` type: \"{0}\". Supported types: {1}",
                coercion_type, SUPPORTED_AS_TYPES.join(", ")))
    })format!(
2048                                "unsupported `as` type: \"{}\". Supported types: {}",
2049                                coercion_type,
2050                                SUPPORTED_AS_TYPES.join(", ")
2051                            ),
2052                        ));
2053                    }
2054
2055                    method_attrs.as_coercion = Some(coercion_type);
2056                } else if meta.path.is_ident("lifecycle") {
2057                    // lifecycle = "stage" or lifecycle(stage = "deprecated", when = "0.4.0", ...)
2058                    if meta.input.peek(::syn::token::Eqsyn::Token![=]) {
2059                        // lifecycle = "stage"
2060                        let _: ::syn::token::Eqsyn::Token![=] = meta.input.parse()?;
2061                        let value: syn::LitStr = meta.input.parse()?;
2062                        let stage = crate::lifecycle::LifecycleStage::from_str(&value.value())
2063                            .ok_or_else(|| {
2064                                syn::Error::new(
2065                                    value.span(),
2066                                    "invalid lifecycle stage; expected one of: experimental, stable, superseded, soft-deprecated, deprecated, defunct",
2067                                )
2068                            })?;
2069                        method_attrs.lifecycle = Some(crate::lifecycle::LifecycleSpec::new(stage));
2070                    } else {
2071                        // lifecycle(stage = "deprecated", when = "0.4.0", ...)
2072                        let mut spec = crate::lifecycle::LifecycleSpec::default();
2073                        meta.parse_nested_meta(|inner| {
2074                            let key = inner.path.get_ident()
2075                                .ok_or_else(|| inner.error("expected identifier"))?
2076                                .to_string();
2077                            let _: ::syn::token::Eqsyn::Token![=] = inner.input.parse()?;
2078                            let value: syn::LitStr = inner.input.parse()?;
2079                            match key.as_str() {
2080                                "stage" => {
2081                                    spec.stage = crate::lifecycle::LifecycleStage::from_str(&value.value())
2082                                        .ok_or_else(|| syn::Error::new(value.span(), "invalid lifecycle stage"))?;
2083                                }
2084                                "when" => spec.when = Some(value.value()),
2085                                "what" => spec.what = Some(value.value()),
2086                                "with" => spec.with = Some(value.value()),
2087                                "details" => spec.details = Some(value.value()),
2088                                "id" => spec.id = Some(value.value()),
2089                                _ => return Err(inner.error(
2090                                    "unknown lifecycle option; expected: stage, when, what, with, details, id"
2091                                )),
2092                            }
2093                            Ok(())
2094                        })?;
2095                        method_attrs.lifecycle = Some(spec);
2096                    }
2097                } else if meta.path.is_ident("vctrs") {
2098                    // vctrs protocol method: vctrs(format), vctrs(vec_proxy), etc.
2099                    meta.parse_nested_meta(|inner| {
2100                        let raw_name = inner.path.get_ident()
2101                            .ok_or_else(|| inner.error("expected protocol name"))?
2102                            .to_string();
2103
2104                        // Normalize short aliases to full protocol names
2105                        const PROTOCOL_ALIASES: &[(&str, &str)] = &[
2106                            ("print_data", "obj_print_data"),
2107                            ("print_header", "obj_print_header"),
2108                            ("print_footer", "obj_print_footer"),
2109                            ("proxy", "vec_proxy"),
2110                            ("proxy_equal", "vec_proxy_equal"),
2111                            ("proxy_compare", "vec_proxy_compare"),
2112                            ("proxy_order", "vec_proxy_order"),
2113                            ("restore", "vec_restore"),
2114                        ];
2115                        let protocol = PROTOCOL_ALIASES
2116                            .iter()
2117                            .find(|(alias, _)| *alias == raw_name)
2118                            .map(|(_, full)| full.to_string())
2119                            .unwrap_or_else(|| raw_name.to_string());
2120
2121                        const VALID_PROTOCOLS: &[&str] = &[
2122                            "format", "vec_proxy", "vec_proxy_equal", "vec_proxy_compare",
2123                            "vec_proxy_order", "vec_restore", "obj_print_data",
2124                            "obj_print_header", "obj_print_footer",
2125                        ];
2126                        if !VALID_PROTOCOLS.contains(&protocol.as_str()) {
2127                            return Err(inner.error(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("unknown vctrs protocol: {0}; expected one of: {1}",
                raw_name, VALID_PROTOCOLS.join(", ")))
    })format!(
2128                                "unknown vctrs protocol: {}; expected one of: {}",
2129                                raw_name,
2130                                VALID_PROTOCOLS.join(", ")
2131                            )));
2132                        }
2133                        method_attrs.vctrs_protocol = Some(protocol);
2134                        Ok(())
2135                    })?;
2136                } else if meta.path.is_ident("r_name") {
2137                    let _: ::syn::token::Eqsyn::Token![=] = meta.input.parse()?;
2138                    let value: syn::LitStr = meta.input.parse()?;
2139                    let val = value.value();
2140                    if val.is_empty() {
2141                        return Err(syn::Error::new_spanned(value, "r_name must not be empty"));
2142                    }
2143                    method_attrs.r_name = Some(val);
2144                } else if meta.path.is_ident("postfix") {
2145                    let _: ::syn::token::Eqsyn::Token![=] = meta.input.parse()?;
2146                    let value: syn::LitStr = meta.input.parse()?;
2147                    let val = value.value();
2148                    crate::miniextendr_fn::validate_postfix(&val, &value)?;
2149                    method_attrs.postfix = Some(val);
2150                } else if meta.path.is_ident("call") {
2151                    return Err(meta.error(
2152                        "`call = caller` is only supported on standalone `#[miniextendr]` \
2153                         functions (the internal-entry-point pattern); class methods keep \
2154                         their own call attribution",
2155                    ));
2156                } else if meta.path.is_ident("r_entry") {
2157                    let _: ::syn::token::Eqsyn::Token![=] = meta.input.parse()?;
2158                    let value: syn::LitStr = meta.input.parse()?;
2159                    method_attrs.r_entry = Some(value.value());
2160                } else if meta.path.is_ident("r_post_checks") {
2161                    let _: ::syn::token::Eqsyn::Token![=] = meta.input.parse()?;
2162                    let value: syn::LitStr = meta.input.parse()?;
2163                    method_attrs.r_post_checks = Some(value.value());
2164                } else if meta.path.is_ident("r_on_exit") {
2165                    if meta.input.peek(::syn::token::Eqsyn::Token![=]) {
2166                        // Short form: r_on_exit = "expr"
2167                        let _: ::syn::token::Eqsyn::Token![=] = meta.input.parse()?;
2168                        let value: syn::LitStr = meta.input.parse()?;
2169                        method_attrs.r_on_exit = Some(crate::miniextendr_fn::ROnExit {
2170                            expr: value.value(),
2171                            add: true,
2172                            after: true,
2173                        });
2174                    } else {
2175                        // Long form: r_on_exit(expr = "...", add = false, after = false)
2176                        let mut expr = None;
2177                        let mut add = true;
2178                        let mut after = true;
2179                        meta.parse_nested_meta(|inner| {
2180                            if inner.path.is_ident("expr") {
2181                                let _: ::syn::token::Eqsyn::Token![=] = inner.input.parse()?;
2182                                let value: syn::LitStr = inner.input.parse()?;
2183                                expr = Some(value.value());
2184                            } else if inner.path.is_ident("add") {
2185                                let _: ::syn::token::Eqsyn::Token![=] = inner.input.parse()?;
2186                                let value: syn::LitBool = inner.input.parse()?;
2187                                add = value.value;
2188                            } else if inner.path.is_ident("after") {
2189                                let _: ::syn::token::Eqsyn::Token![=] = inner.input.parse()?;
2190                                let value: syn::LitBool = inner.input.parse()?;
2191                                after = value.value;
2192                            } else {
2193                                return Err(inner.error(
2194                                    "unknown r_on_exit option; expected `expr`, `add`, or `after`",
2195                                ));
2196                            }
2197                            Ok(())
2198                        })?;
2199                        let expr = expr.ok_or_else(|| {
2200                            meta.error("r_on_exit(...) requires `expr = \"...\"` specifying the R expression")
2201                        })?;
2202                        method_attrs.r_on_exit = Some(crate::miniextendr_fn::ROnExit { expr, add, after });
2203                    }
2204                } else if meta.path.is_ident("noexport") {
2205                    method_attrs.noexport = true;
2206                } else if meta.path.is_ident("internal") {
2207                    method_attrs.internal = true;
2208                } else if meta.path.is_ident("invisible") {
2209                    method_attrs.force_invisible = Some(true);
2210                } else if meta.path.is_ident("visible") {
2211                    method_attrs.force_invisible = Some(false);
2212                } else if meta.path.is_ident("dots") {
2213                    // `dots = typed_list!(...)` — attribute sugar mirroring the
2214                    // standalone-fn path (`miniextendr_fn.rs`): capture the
2215                    // `typed_list!(...)` spec so a `dots_typed` binding can be
2216                    // injected at the top of the method body in `from_impl_item`.
2217                    // Or the role of `...` (#2157), checked in the R wrapper.
2218                    let value: syn::Expr = meta.value()?.parse()?;
2219                    match crate::dots_policy::parse_dots_attr(&value)? {
2220                        crate::dots_policy::DotsAttr::TypedList(spec) => {
2221                            method_attrs.dots_spec = Some(spec);
2222                        }
2223                        crate::dots_policy::DotsAttr::Policy(policy) => {
2224                            method_attrs.dots_policy = Some(policy);
2225                        }
2226                    }
2227                } else {
2228                    return Err(meta.error(
2229                        "unknown attribute; expected one of: env, r6, s3, s4, s7, vctrs, defaults, unsafe, check_interrupt, coerce, no_coerce, preconditions, no_preconditions, rng, unwrap_in_r, serialize, serde_error, as, lifecycle, r_name, postfix, r_entry, r_post_checks, r_on_exit, noexport, internal, invisible, visible, match_arg, match_arg_several_ok, choices, choices_several_ok, no_default, inherits, not_inherits, no_na, formal, dots = typed_list!(...) | empty | forward(allow = [...]) | data"
2230                    ));
2231                }
2232                Ok(())
2233            })?;
2234        }
2235
2236        // Resolve feature defaults for fields not explicitly set
2237        method_attrs.worker = worker.unwrap_or(falsecfg!(feature = "worker-default"));
2238        // Main-thread execution is already the fallback when worker is false.
2239        // Only an explicit override may take precedence over worker selection.
2240        method_attrs.unsafe_main_thread = unsafe_main_thread.unwrap_or(false);
2241        method_attrs.coerce = coerce.unwrap_or(falsecfg!(feature = "coerce-default"));
2242
2243        // Method-level `internal` / `noexport` are currently only honoured by the R6
2244        // active-binding doc path (emits a class-level
2245        // `#' @field <name> (internal)` — the documented roxygen2 8.0.0
2246        // `@field name NULL` opt-out doesn't actually silence
2247        // `r6_resolve_fields`'s "Undocumented R6 active binding" warning, so we
2248        // use a minimal real description instead). Accepting them silently
2249        // elsewhere would be a no-op surface: regular instance methods, static
2250        // methods, and trait methods don't currently consume these flags. Reject
2251        // them at parse time so the failure is loud rather than silent.
2252        // Class-level `#[miniextendr(internal)]` / `noexport` continue to work for whole
2253        // impl blocks via `parsed_impl.internal` / `parsed_impl.noexport`.
2254        if (method_attrs.internal || method_attrs.noexport) && !method_attrs.r6.active {
2255            return Err(syn::Error::new(
2256                proc_macro2::Span::call_site(),
2257                "method-level `internal` / `noexport` are currently only supported on R6 \
2258                 active bindings (use `#[miniextendr(r6(active), noexport)]`). For other \
2259                 method positions, set `internal` / `noexport` at the impl-block level \
2260                 (`#[miniextendr(s7, internal)] impl Foo { ... }`) instead.",
2261            ));
2262        }
2263
2264        Ok(method_attrs)
2265    }
2266
2267    /// Detect the [`ReceiverKind`] from a method's function signature.
2268    ///
2269    /// Inspects the first parameter to determine whether this is a static function
2270    /// (`None`), immutable borrow (`Ref`), mutable borrow (`RefMut`), consuming
2271    /// method (`Value`), or handle receiver (`ExternalPtrRef`, `ExternalPtrMut`,
2272    /// `ExternalPtrValue`). Handles both standard receivers (`&self`, `&mut self`) and
2273    /// typed receivers (`self: &Self`, `self: ExternalPtr<Self>`, etc.).
2274    fn detect_env(sig: &syn::Signature) -> ReceiverKind {
2275        match crate::trait_receiver::owner_receiver(sig) {
2276            Some(crate::trait_receiver::OwnerReceiver::Shared) => {
2277                return ReceiverKind::ExternalPtrRef;
2278            }
2279            Some(crate::trait_receiver::OwnerReceiver::Mutable) => {
2280                return ReceiverKind::ExternalPtrMut;
2281            }
2282            None => {}
2283        }
2284        match sig.inputs.first() {
2285            Some(syn::FnArg::Receiver(r)) => {
2286                // Check for standard &self / &mut self
2287                if r.reference.is_some() {
2288                    if r.mutability.is_some() {
2289                        ReceiverKind::RefMut
2290                    } else {
2291                        ReceiverKind::Ref
2292                    }
2293                } else if r.colon_token.is_some() {
2294                    // Check for typed receiver (self: &Self, self: &mut Self;
2295                    // `old_handle_receiver` refuses `self: &ExternalPtr<Self>`)
2296                    if let syn::Type::Reference(type_ref) = r.ty.as_ref() {
2297                        if type_ref.mutability.is_some() {
2298                            ReceiverKind::RefMut
2299                        } else {
2300                            ReceiverKind::Ref
2301                        }
2302                    } else if is_external_ptr_type(r.ty.as_ref()) {
2303                        // self: ExternalPtr<Self> — owned ExternalPtr
2304                        ReceiverKind::ExternalPtrValue
2305                    } else {
2306                        // self: Box<Self>, self: Rc<Self>, etc. - treat as by value
2307                        ReceiverKind::Value
2308                    }
2309                } else {
2310                    ReceiverKind::Value
2311                }
2312            }
2313            _ => ReceiverKind::None,
2314        }
2315    }
2316
2317    /// Create a copy of the method signature with the `self` receiver removed.
2318    ///
2319    /// The C wrapper receives `self` as a separate SEXP argument and extracts it
2320    /// from an `ErasedExternalPtr`, so the receiver must not appear in the
2321    /// parameter list used for SEXP-to-Rust conversion codegen.
2322    fn sig_without_env(sig: &syn::Signature) -> syn::Signature {
2323        let mut sig = sig.clone();
2324        if let Some(syn::FnArg::Receiver(_)) = sig.inputs.first() {
2325            sig.inputs = sig.inputs.into_iter().skip(1).collect();
2326        }
2327        sig
2328    }
2329
2330    /// Parse a method from an impl item.
2331    ///
2332    /// Regular doc comments are auto-converted to `@description` for all class
2333    /// systems. `class_page` is whether the class block renders a help page:
2334    /// the method's docs go nowhere else when it does not.
2335    pub fn from_impl_item(
2336        item: &mut syn::ImplItemFn,
2337        class_page: crate::roxygen::RdPage,
2338    ) -> syn::Result<Self> {
2339        use syn::spanned::Spanned;
2340        old_handle_receiver(&item.sig)?;
2341        let env = Self::detect_env(&item.sig);
2342        let mut method_attrs = Self::parse_method_attrs(&item.attrs)?;
2343
2344        // The `&Dots` parameter is R's `...` wherever it sits (Rust `...`
2345        // becomes a trailing one); at most one per method.
2346        crate::miniextendr_fn::rewrite_variadic_dots(&mut item.sig)?;
2347        let dots_ident = crate::miniextendr_fn::find_dots_param(&item.sig.inputs)?;
2348        let has_dots = dots_ident.is_some();
2349
2350        // Parameter markers (`Checked<T>` / `Unchecked<T>`, #1566): peeled
2351        // from the signature the codegen sees (the re-emitted impl keeps
2352        // them), and merged with the method's `preconditions(p)` /
2353        // `no_preconditions(p)`, which a marker must not contradict.
2354        let param_markers = crate::miniextendr_fn::collect_param_markers(&item.sig.inputs)?;
2355        for arg in &item.sig.inputs {
2356            let syn::FnArg::Typed(pt) = arg else {
2357                continue;
2358            };
2359            let syn::Pat::Ident(pat_ident) = pt.pat.as_ref() else {
2360                continue;
2361            };
2362            let name = crate::naming::ident_name(&pat_ident.ident);
2363            let Some((_, markers)) = param_markers.iter().find(|(n, _)| *n == name) else {
2364                continue;
2365            };
2366            let entry = method_attrs.per_param.entry(name.clone()).or_default();
2367            entry.preconditions = crate::miniextendr_fn::merge_param_policy(
2368                markers,
2369                entry.preconditions,
2370                &name,
2371                true,
2372                &pt.ty,
2373            )?;
2374        }
2375        let mut peeled_sig = item.sig.clone();
2376        peeled_sig.inputs = crate::miniextendr_fn::peeled_inputs(&item.sig.inputs, &param_markers)?;
2377
2378        // `dots = typed_list!(...)` sugar: inject the `dots_typed` binding at the
2379        // top of the method body, reusing the shared helper that the standalone-fn
2380        // path uses. This mutates `item.block`, which is the same node re-emitted
2381        // as `original_impl` (see `ParsedImpl::parse`), so it covers all six class
2382        // systems by construction (they emit only R/C wrappers, not the Rust body).
2383        if let Some(spec) = &method_attrs.dots_spec {
2384            let dots_ident = dots_ident.as_ref().ok_or_else(|| {
2385                syn::Error::new(
2386                    item.sig.ident.span(),
2387                    "dots = typed_list!(...) requires the method to take a dots parameter \
2388                     (`...` or a `&Dots` parameter)",
2389                )
2390            })?;
2391            if let Some(err) =
2392                crate::miniextendr_fn::typed_list_on_lazy_dots(&item.sig.inputs, None)
2393            {
2394                return Err(err);
2395            }
2396            let stmt = crate::build_dots_validation_stmt(dots_ident, spec);
2397            item.block.stmts.insert(0, stmt);
2398        }
2399
2400        // Validate: no defaults on self parameter (any kind: &self, &mut self, self)
2401        if env != ReceiverKind::None && method_attrs.defaults.contains_key("self") {
2402            return Err(syn::Error::new(
2403                method_attrs
2404                    .defaults_span
2405                    .unwrap_or_else(|| item.sig.ident.span()),
2406                "cannot specify default for self parameter in defaults(...)",
2407            ));
2408        }
2409
2410        // Validate: all defaults reference existing parameters
2411        let param_names: std::collections::HashSet<String> = item
2412            .sig
2413            .inputs
2414            .iter()
2415            .filter_map(|input| {
2416                if let syn::FnArg::Typed(pat_type) = input
2417                    && let syn::Pat::Ident(pat_ident) = pat_type.pat.as_ref()
2418                {
2419                    Some(crate::naming::ident_name(&pat_ident.ident))
2420                } else {
2421                    None
2422                }
2423            })
2424            .collect();
2425
2426        let mut invalid_params: Vec<String> = method_attrs
2427            .defaults
2428            .keys()
2429            .filter(|key| *key != "self" && !param_names.contains(*key))
2430            .cloned()
2431            .collect();
2432        invalid_params.sort();
2433
2434        if !invalid_params.is_empty() {
2435            return Err(syn::Error::new(
2436                method_attrs
2437                    .defaults_span
2438                    .unwrap_or_else(|| item.sig.ident.span()),
2439                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("defaults(...) references non-existent parameter(s): {0}",
                invalid_params.join(", ")))
    })format!(
2440                    "defaults(...) references non-existent parameter(s): {}",
2441                    invalid_params.join(", ")
2442                ),
2443            ));
2444        }
2445
2446        // Validate type-based constraints on each parameter
2447        for input in &peeled_sig.inputs {
2448            let syn::FnArg::Typed(pat_type) = input else {
2449                continue;
2450            };
2451            let syn::Pat::Ident(pat_ident) = pat_type.pat.as_ref() else {
2452                continue;
2453            };
2454            let param_name = crate::naming::ident_name(&pat_ident.ident);
2455
2456            // Validate Missing nesting and Missing<Dots>
2457            crate::miniextendr_fn::validate_param_type(pat_type.ty.as_ref(), pat_type.ty.span())?;
2458
2459            // Validate: no defaults on Dots-type parameters
2460            if crate::miniextendr_fn::is_dots_type(pat_type.ty.as_ref())
2461                && method_attrs.defaults.contains_key(&param_name)
2462            {
2463                return Err(syn::Error::new(
2464                    method_attrs
2465                        .defaults_span
2466                        .unwrap_or_else(|| pat_ident.ident.span()),
2467                    ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("variadic (...) parameter `{0}` cannot have a default value",
                param_name))
    })format!(
2468                        "variadic (...) parameter `{}` cannot have a default value",
2469                        param_name
2470                    ),
2471                ));
2472            }
2473        }
2474
2475        // Extract lifecycle from #[deprecated] attribute if not already set via #[miniextendr(lifecycle = ...)]
2476        if method_attrs.lifecycle.is_none() {
2477            method_attrs.lifecycle = item
2478                .attrs
2479                .iter()
2480                .find_map(crate::lifecycle::parse_rust_deprecated);
2481        }
2482
2483        // Auto-convert regular doc comments to @description for all class systems
2484        let mut doc_tags =
2485            crate::roxygen::roxygen_tags_from_attrs_for_r6_method(&item.attrs, class_page);
2486
2487        // Inject lifecycle badge into method roxygen tags if present
2488        if let Some(ref spec) = method_attrs.lifecycle {
2489            crate::lifecycle::inject_lifecycle_badge(&mut doc_tags, spec);
2490        }
2491
2492        // Get parameter defaults from method-level #[miniextendr(defaults(...))] attribute
2493        let param_defaults = method_attrs.defaults.clone();
2494
2495        // match_arg / choices / inherits / not_inherits / no_na on impl methods: unlike standalone
2496        // functions, Rust doesn't accept `#[miniextendr(...)]` on method parameters
2497        // inside an impl (attribute macros aren't allowed there — "expected
2498        // non-macro attribute"). The surface is instead method-level:
2499        // `#[miniextendr(match_arg(p), choices(q = "a, b"))]`. Every named param
2500        // must exist on the signature, so typos fail at compile time; `Option<T>`
2501        // scalar match_arg/choices params are the optional form (#1473) and
2502        // `Missing<..>` ones the omittable form (#1551), and neither takes a
2503        // default. Any other `Missing<T>` parameter does (#2161).
2504        crate::miniextendr_fn::finalize_method_param_attrs(
2505            &mut method_attrs.per_param,
2506            &peeled_sig.inputs,
2507            &param_markers,
2508            &param_defaults,
2509            method_attrs
2510                .match_arg_span
2511                .unwrap_or_else(|| item.sig.ident.span()),
2512        )?;
2513
2514        // The RNG guard (#2002): a `&RngGuard` parameter needs `rng` and takes
2515        // no option, which the method-level forms would name.
2516        crate::miniextendr_fn::check_rng_guard_params(
2517            &peeled_sig.inputs,
2518            method_attrs.rng,
2519            |name| method_attrs.per_param.contains_key(name) || param_defaults.contains_key(name),
2520        )?;
2521
2522        // Consuming `self` receivers (#1432): supported for every return shape
2523        // (see `ReceiverKind::Value`). Two things stay rejected: a receiver type
2524        // the wrapper cannot hand over (`self: Box<Self>` / `Rc<Self>` ...), and
2525        // the `constructor` marker, which describes receiverless `fn new()`.
2526        if env == ReceiverKind::Value {
2527            if let Some(syn::FnArg::Receiver(r)) = item.sig.inputs.first()
2528                && r.colon_token.is_some()
2529                && !#[allow(non_exhaustive_omitted_patterns)] match r.ty.as_ref() {
    syn::Type::Path(tp) if
        tp.path.segments.last().is_some_and(|seg| seg.ident == "Self") &&
            tp.path.segments.len() == 1 => true,
    _ => false,
}matches!(r.ty.as_ref(), syn::Type::Path(tp)
2530                    if tp.path.segments.last().is_some_and(|seg| seg.ident == "Self")
2531                        && tp.path.segments.len() == 1)
2532            {
2533                return Err(syn::Error::new_spanned(
2534                    &r.ty,
2535                    "unsupported receiver type: the R handle stores the value itself, so a \
2536                     method can take `self`, `self: Self`, `&self`, `&mut self`, \
2537                     `self: ExternalPtrRef<'_, Self>`, `self: ExternalPtrMut<'_, Self>` or \
2538                     `self: ExternalPtr<Self>` — not a smart pointer around `Self`",
2539                ));
2540            }
2541            if method_attrs.constructor {
2542                return Err(syn::Error::new(
2543                    item.sig.fn_token.span,
2544                    ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("method `{0}` takes `self` and is marked `constructor`; a constructor has no receiver (`fn new(...) -> Self`). A consuming builder step needs no marker: `fn {1}(self, ...) -> Self` writes its result back into the same R object.",
                item.sig.ident, item.sig.ident))
    })format!(
2545                        "method `{}` takes `self` and is marked `constructor`; a constructor has no \
2546                         receiver (`fn new(...) -> Self`). A consuming builder step needs no marker: \
2547                         `fn {}(self, ...) -> Self` writes its result back into the same R object.",
2548                        item.sig.ident, item.sig.ident
2549                    ),
2550                ));
2551            }
2552        }
2553
2554        // Return-visibility marker (#1213): validate, record, and strip it from
2555        // the signature the codegen sees (whose parameters are already peeled).
2556        let mut sig = Self::sig_without_env(&peeled_sig);
2557        // The RNG guard leaves the signature every R-side consumer and the
2558        // argument conversion see; the C wrapper puts the guard back in the
2559        // call at its position.
2560        let rng_guard = sig.inputs.iter().position(|arg| {
2561            #[allow(non_exhaustive_omitted_patterns)] match arg {
    syn::FnArg::Typed(pt) if crate::type_inspect::is_rng_guard_param(&pt.ty)
        => true,
    _ => false,
}matches!(arg, syn::FnArg::Typed(pt) if crate::type_inspect::is_rng_guard_param(&pt.ty))
2562        });
2563        if rng_guard.is_some() {
2564            sig.inputs = std::mem::take(&mut sig.inputs)
2565                .into_iter()
2566                .filter(|arg| {
2567                    !#[allow(non_exhaustive_omitted_patterns)] match arg {
    syn::FnArg::Typed(pt) if crate::type_inspect::is_rng_guard_param(&pt.ty)
        => true,
    _ => false,
}matches!(arg, syn::FnArg::Typed(pt)
2568                        if crate::type_inspect::is_rng_guard_param(&pt.ty))
2569                })
2570                .collect();
2571        }
2572        let visibility_marker = match &item.sig.output {
2573            syn::ReturnType::Type(_, ty) => {
2574                if let Some(err) = crate::type_inspect::visibility_marker_error(ty, "return") {
2575                    return Err(err);
2576                }
2577                let (marker, peeled) =
2578                    crate::type_inspect::peel_return_visibility(&item.sig.output);
2579                sig.output = peeled;
2580                marker
2581            }
2582            syn::ReturnType::Default => None,
2583        };
2584        if let (Some(marker), Some(attr)) = (visibility_marker, method_attrs.force_invisible)
2585            && marker != attr
2586        {
2587            return Err(syn::Error::new_spanned(
2588                &item.sig.output,
2589                "the return type's visibility marker and the `invisible` / `visible` method option disagree; keep one of them (or make them agree)",
2590            ));
2591        }
2592
2593        let (return_wrap, output) = crate::return_wrap::resolve(&sig.output, method_attrs.wrap)?;
2594        if return_wrap.is_some() && (method_attrs.serialize || method_attrs.unwrap_in_r) {
2595            return Err(syn::Error::new_spanned(
2596                &item.sig.output,
2597                "explicit class wrapping cannot be combined with serialize or unwrap_in_r",
2598            ));
2599        }
2600        sig.output = crate::type_inspect::serialize_return_type(&output, method_attrs.serialize);
2601        Ok(ParsedMethod {
2602            visibility_marker,
2603            return_wrap,
2604            ident: item.sig.ident.clone(),
2605            env,
2606            sig,
2607            param_markers,
2608            vis: item.vis.clone(),
2609            doc_tags,
2610            method_attrs,
2611            param_defaults,
2612            has_dots,
2613            rng_guard,
2614        })
2615    }
2616
2617    /// Returns true if this method should be included in the class.
2618    pub fn should_include(&self) -> bool {
2619        // Skip ignored methods
2620        !self.method_attrs.ignore
2621    }
2622
2623    /// Returns true if this method should be private in R6.
2624    /// Inferred from Rust visibility: anything not `pub` is private.
2625    pub fn is_private(&self) -> bool {
2626        // Explicit attribute takes precedence
2627        if self.method_attrs.r6.private {
2628            return true;
2629        }
2630        // Infer from visibility: anything not `pub` is private
2631        !#[allow(non_exhaustive_omitted_patterns)] match self.vis {
    syn::Visibility::Public(_) => true,
    _ => false,
}matches!(self.vis, syn::Visibility::Public(_))
2632    }
2633
2634    /// Returns true if this is likely a constructor.
2635    /// Inferred from: no env + named "new" + returns Self.
2636    pub fn is_constructor(&self) -> bool {
2637        self.method_attrs.constructor
2638            || (self.env == ReceiverKind::None && self.ident == "new" && self.returns_self())
2639    }
2640
2641    /// Returns true if this method is the R6 finalizer.
2642    ///
2643    /// Only the explicit `#[miniextendr(r6(finalize))]` marker qualifies. It
2644    /// used to be inferred from "takes `self` by value and does not return
2645    /// `Self`", which silently hid consuming methods on every class system
2646    /// (#1432).
2647    pub fn is_finalizer(&self) -> bool {
2648        self.method_attrs.r6.finalize
2649    }
2650
2651    /// Returns true if this method should be an R6 active binding.
2652    /// Active bindings provide property-like access (obj$name instead of obj$name()).
2653    pub fn is_active(&self) -> bool {
2654        self.method_attrs.r6.active
2655    }
2656
2657    /// R-facing method name.
2658    ///
2659    /// Returns `r_name` if set, otherwise the Rust ident with `postfix`
2660    /// appended when given, otherwise the Rust ident as a string.
2661    pub fn r_method_name(&self) -> String {
2662        if let Some(r_name) = &self.method_attrs.r_name {
2663            return r_name.clone();
2664        }
2665        match &self.method_attrs.postfix {
2666            Some(postfix) => ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0}{1}",
                crate::naming::ident_name(&self.ident), postfix))
    })format!("{}{postfix}", crate::naming::ident_name(&self.ident)),
2667            None => crate::naming::ident_name(&self.ident),
2668        }
2669    }
2670
2671    /// C wrapper identifier for this method.
2672    ///
2673    /// Format: `C_{crate}_{Type}__{method}` — crate-prefixed for webR
2674    /// cross-package symbol uniqueness (#1273). Unique per type: two impl
2675    /// blocks of one type can't both define a method (E0592).
2676    pub fn c_wrapper_ident(&self, type_ident: &syn::Ident) -> syn::Ident {
2677        crate::naming::impl_method_c_wrapper_ident(type_ident, &self.ident)
2678    }
2679
2680    /// The R formal of each parameter (#1842), from the method's
2681    /// `formal(p = "...")`.
2682    pub fn formals(&self) -> crate::r_wrapper_builder::Formals {
2683        crate::r_wrapper_builder::Formals::from_param_attrs(&self.method_attrs.per_param)
2684    }
2685
2686    /// Generate lifecycle prelude R code for this method, if lifecycle is specified.
2687    ///
2688    /// The `what` parameter describes the method in the format appropriate for the class system:
2689    /// - Env/R6: `"Type$method()"`
2690    /// - S3: `"method.Type()"`
2691    /// - S7: `"method()`" (dispatched generics)
2692    pub fn lifecycle_prelude(&self, what: &str) -> Option<String> {
2693        self.method_attrs
2694            .lifecycle
2695            .as_ref()
2696            .and_then(|spec| spec.r_prelude(what))
2697    }
2698
2699    /// Returns true if this method returns Self.
2700    pub fn returns_self(&self) -> bool {
2701        #[allow(non_exhaustive_omitted_patterns)] match &self.sig.output {
    syn::ReturnType::Type(_, ty) if
        #[allow(non_exhaustive_omitted_patterns)] match ty.as_ref() {
            syn::Type::Path(p) if
                p.path.segments.last().map(|s|
                            s.ident == "Self").unwrap_or(false) => true,
            _ => false,
        } => true,
    _ => false,
}matches!(&self.sig.output, syn::ReturnType::Type(_, ty)
2702            if matches!(ty.as_ref(), syn::Type::Path(p)
2703                if p.path.segments.last().map(|s| s.ident == "Self").unwrap_or(false)))
2704    }
2705
2706    /// Returns true if this method returns `Result<Self, E>` — a fallible
2707    /// constructor-shaped method (e.g. `from_r`, `try_new`). On the R side this
2708    /// is treated exactly like a bare `Self` return (wrapped class object via
2709    /// [`crate::ReturnStrategy::for_method`]); the C wrapper still raises on
2710    /// `Err` via the normal `Result` error path (see
2711    /// [`crate::c_wrapper_builder::ReturnHandling::ResultExternalPtr`]).
2712    pub fn returns_result_self(&self) -> bool {
2713        let syn::ReturnType::Type(_, ty) = &self.sig.output else {
2714            return false;
2715        };
2716        let syn::Type::Path(p) = ty.as_ref() else {
2717            return false;
2718        };
2719        let Some(seg) = p.path.segments.last() else {
2720            return false;
2721        };
2722        if seg.ident != "Result" {
2723            return false;
2724        }
2725        let syn::PathArguments::AngleBracketed(ab) = &seg.arguments else {
2726            return false;
2727        };
2728        let Some(syn::GenericArgument::Type(ok_ty)) = ab.args.first() else {
2729            return false;
2730        };
2731        #[allow(non_exhaustive_omitted_patterns)] match ok_ty {
    syn::Type::Path(ip) if
        ip.path.segments.last().map(|s| s.ident == "Self").unwrap_or(false) =>
        true,
    _ => false,
}matches!(ok_ty, syn::Type::Path(ip)
2732            if ip.path.segments.last().map(|s| s.ident == "Self").unwrap_or(false))
2733    }
2734
2735    /// Returns true if this method returns `Option<Self>` — a lookup-shaped
2736    /// fallible constructor (e.g. `try_find`). On the R side this is treated
2737    /// exactly like a bare `Self` return (wrapped class object via
2738    /// [`crate::ReturnStrategy::for_method`]); the C wrapper still raises on
2739    /// `None` via the normal `Option` error path (see
2740    /// [`crate::c_wrapper_builder::ReturnHandling::OptionExternalPtr`]).
2741    /// Symmetric with [`Self::returns_result_self`].
2742    pub fn returns_option_self(&self) -> bool {
2743        let syn::ReturnType::Type(_, ty) = &self.sig.output else {
2744            return false;
2745        };
2746        let syn::Type::Path(p) = ty.as_ref() else {
2747            return false;
2748        };
2749        let Some(seg) = p.path.segments.last() else {
2750            return false;
2751        };
2752        if seg.ident != "Option" {
2753            return false;
2754        }
2755        let syn::PathArguments::AngleBracketed(ab) = &seg.arguments else {
2756            return false;
2757        };
2758        let Some(syn::GenericArgument::Type(some_ty)) = ab.args.first() else {
2759            return false;
2760        };
2761        #[allow(non_exhaustive_omitted_patterns)] match some_ty {
    syn::Type::Path(ip) if
        ip.path.segments.last().map(|s| s.ident == "Self").unwrap_or(false) =>
        true,
    _ => false,
}matches!(some_ty, syn::Type::Path(ip)
2762            if ip.path.segments.last().map(|s| s.ident == "Self").unwrap_or(false))
2763    }
2764
2765    /// Returns the bare type name when this method returns a type that may be a
2766    /// different registered ExternalPtr-backed class.
2767    ///
2768    /// This is deliberately syntactic and conservative about primitives and
2769    /// common containers. The write-time wrapper resolver checks the complete
2770    /// class registry, so an unregistered capitalized type falls back to the
2771    /// direct `.val` return.
2772    ///
2773    /// Four shapes are recognized:
2774    /// - A bare capitalized path, e.g. `-> Board`.
2775    /// - `Option<T>` where `T` is a bare capitalized path. The C wrapper
2776    ///   converts the whole `Option` (`ReturnHandling::OptionIntoR`, as a
2777    ///   function's), so `.val` is a bare pointer for `Some`, wrapped as in the
2778    ///   bare-class case, and `NULL` for `None`, which the wrap passes through
2779    ///   ([`returns_option`](Self::returns_option)).
2780    /// - `Result<T, E>` where `T` is a bare capitalized path and `E` is not
2781    ///   `()`. `Result<T, ()>` is excluded: the unit-error sentinel maps to
2782    ///   `ReturnHandling::ResultNullOnErr`, where `.val` can be `NULL` on
2783    ///   `Ok`, and wrapping `NULL` in a class constructor would break.
2784    /// - `ExternalPtr<T>` (the explicit-handle spelling, #1375), including
2785    ///   through the `Option`/`Result` wrappers above (e.g.
2786    ///   `Option<ExternalPtr<Board>>`) — `IntoR for ExternalPtr<T>` already
2787    ///   produces a bare `EXTPTRSXP` `.val`, identical in shape to the bare
2788    ///   `T` case, so [`inner_class_ident`](Self::inner_class_ident) peels
2789    ///   through it to the same wrapped name.
2790    ///
2791    /// `Option<Self>` / `Result<Self, _>` are excluded here too (the inner
2792    /// ident is `Self`) — `ReturnStrategy::for_method` checks
2793    /// `returns_result_self()` / `returns_option_self()` before this method,
2794    /// so those already take the `ReturnSelf` path regardless.
2795    /// List-shaped returns (`Vec<Class>` and its `Option`/`Result` wrappers)
2796    /// are handled separately by
2797    /// [`returns_other_class_list`](Self::returns_other_class_list); any other
2798    /// nested container is not recognized: the inner type must be a bare path
2799    /// (or an `ExternalPtr<...>` wrapping one) with no other path arguments.
2800    pub fn returns_other_class(&self) -> Option<syn::Ident> {
2801        let syn::ReturnType::Type(_, ty) = &self.sig.output else {
2802            return None;
2803        };
2804        let syn::Type::Path(p) = ty.as_ref() else {
2805            return None;
2806        };
2807        let seg = p.path.segments.last()?;
2808
2809        match &seg.arguments {
2810            syn::PathArguments::None => Self::class_ident_from_ident(&seg.ident),
2811            syn::PathArguments::AngleBracketed(_) if seg.ident == "Option" => {
2812                Self::inner_class_ident(crate::first_type_argument(seg)?)
2813            }
2814            syn::PathArguments::AngleBracketed(_) if seg.ident == "Result" => {
2815                // `Result<T, ()>` is a deliberate NULL-on-Err sentinel
2816                // (`ResultNullOnErr`) rather than a fallible constructor —
2817                // `.val` may be `NULL`, which a class constructor can't wrap.
2818                let err_is_unit = crate::second_type_argument(seg)
2819                    .is_some_and(|ty| #[allow(non_exhaustive_omitted_patterns)] match ty {
    syn::Type::Tuple(t) if t.elems.is_empty() => true,
    _ => false,
}matches!(ty, syn::Type::Tuple(t) if t.elems.is_empty()));
2820                if err_is_unit {
2821                    return None;
2822                }
2823                Self::inner_class_ident(crate::first_type_argument(seg)?)
2824            }
2825            // Bare explicit-handle return (#1375): `-> ExternalPtr<Class>`
2826            // wraps identically to `-> Class` — delegate to
2827            // `inner_class_ident`, which peels the `ExternalPtr` segment.
2828            syn::PathArguments::AngleBracketed(_) if seg.ident == "ExternalPtr" => {
2829                Self::inner_class_ident(ty.as_ref())
2830            }
2831            _ => None,
2832        }
2833    }
2834
2835    /// Returns the bare element type name when this method returns a *list* of
2836    /// values that may be a different registered ExternalPtr-backed class.
2837    ///
2838    /// The list-shaped sibling of
2839    /// [`returns_other_class`](Self::returns_other_class) (#1284). Like the
2840    /// scalar detector it is deliberately syntactic: the write-time resolver
2841    /// checks the complete class registry, and an unregistered element type
2842    /// falls back to the bare list of external pointers.
2843    ///
2844    /// Four shapes are recognized (the element type must pass the same bare
2845    /// capitalized-path filter as the scalar case, optionally through a
2846    /// trailing `ExternalPtr<...>` segment):
2847    /// - `Vec<Class>` — the C wrapper converts via `IntoR` into a `VECSXP` of
2848    ///   external pointers (the `IntoRVecElement` impl emitted by
2849    ///   `#[derive(ExternalPtr)]`), so `.val` is a bare list.
2850    /// - `Option<Vec<Class>>` — the C wrapper converts the whole `Option`
2851    ///   (`ReturnHandling::OptionIntoR`), so `.val` is the same bare list for
2852    ///   `Some` and `NULL` for `None`, which the `lapply` wrap passes through
2853    ///   rather than turning it into `list()`.
2854    /// - `Result<Vec<Class>, E>` where `E` is not `()` — the C wrapper raises
2855    ///   on `Err` (`ReturnHandling::ResultIntoR`); same bare list on `Ok`.
2856    ///   `Result<Vec<Class>, ()>` is excluded: the unit-error sentinel maps to
2857    ///   `ReturnHandling::ResultNullOnErr`, where `.val` can be `NULL`, and
2858    ///   `lapply(NULL, ...)` would silently turn that `NULL` into `list()`.
2859    /// - `Vec<ExternalPtr<Class>>` (the explicit-handle spelling, #1375),
2860    ///   including through the `Option`/`Result` wrappers above — the element
2861    ///   filter is [`inner_class_ident`](Self::inner_class_ident), which peels
2862    ///   the `ExternalPtr` segment, so this resolves identically to
2863    ///   `Vec<Class>`.
2864    ///
2865    /// Not recognized (deliberately):
2866    /// - `Vec<Self>` — `ParsedMethod` doesn't carry the enclosing impl's type
2867    ///   ident, so this detector can't name the target class. Handled instead
2868    ///   by [`returns_self_list`](Self::returns_self_list), a boolean sibling
2869    ///   that `ReturnStrategy::for_method` treats the same as this method
2870    ///   returning `Some`; `MethodReturnBuilder::with_return_class_from_method`
2871    ///   substitutes the actual name using the `type_ident` every call site
2872    ///   already has in scope (#1375).
2873    /// - `Vec<Option<Class>>` — has no `IntoR` impl (the `Vec<Option<T>>`
2874    ///   blanket slot is occupied by the newtype funnel) and would need a
2875    ///   NULL-tolerant per-element wrap; tracked as a follow-up.
2876    pub fn returns_other_class_list(&self) -> Option<syn::Ident> {
2877        let syn::ReturnType::Type(_, ty) = &self.sig.output else {
2878            return None;
2879        };
2880        let syn::Type::Path(p) = ty.as_ref() else {
2881            return None;
2882        };
2883        let seg = p.path.segments.last()?;
2884
2885        match &seg.arguments {
2886            syn::PathArguments::AngleBracketed(_) if seg.ident == "Vec" => {
2887                Self::vec_inner_class_ident(ty)
2888            }
2889            syn::PathArguments::AngleBracketed(_) if seg.ident == "Option" => {
2890                Self::vec_inner_class_ident(crate::first_type_argument(seg)?)
2891            }
2892            syn::PathArguments::AngleBracketed(_) if seg.ident == "Result" => {
2893                // `Result<T, ()>` maps to `ResultNullOnErr` — `.val` may be
2894                // `NULL`, which `lapply` would silently coerce to `list()`.
2895                let err_is_unit = crate::second_type_argument(seg)
2896                    .is_some_and(|ty| #[allow(non_exhaustive_omitted_patterns)] match ty {
    syn::Type::Tuple(t) if t.elems.is_empty() => true,
    _ => false,
}matches!(ty, syn::Type::Tuple(t) if t.elems.is_empty()));
2897                if err_is_unit {
2898                    return None;
2899                }
2900                Self::vec_inner_class_ident(crate::first_type_argument(seg)?)
2901            }
2902            _ => None,
2903        }
2904    }
2905
2906    /// Applies [`inner_class_ident`](Self::inner_class_ident) to the element
2907    /// type of a `Vec<T>`: `ty` must be a `Vec` path whose single type
2908    /// argument is either a bare `syn::Type::Path` or an `ExternalPtr<...>`
2909    /// wrapping one (peeled by `inner_class_ident`, #1375).
2910    pub(crate) fn vec_inner_class_ident(ty: &syn::Type) -> Option<syn::Ident> {
2911        let syn::Type::Path(p) = ty else {
2912            return None;
2913        };
2914        let seg = p.path.segments.last()?;
2915        if seg.ident != "Vec" {
2916            return None;
2917        }
2918        Self::inner_class_ident(crate::first_type_argument(seg)?)
2919    }
2920
2921    /// Shared name filter for [`returns_other_class`](Self::returns_other_class):
2922    /// rejects `Self`, builtin scalar/string names, and known container names;
2923    /// accepts only idents starting with an ASCII-uppercase letter.
2924    fn class_ident_from_ident(ident: &syn::Ident) -> Option<syn::Ident> {
2925        let name = ident.to_string();
2926        if name == "Self"
2927            || is_builtin_return_type_name(&name)
2928            || is_known_return_container_name(&name)
2929        {
2930            return None;
2931        }
2932
2933        name.chars()
2934            .next()
2935            .is_some_and(|c| c.is_ascii_uppercase())
2936            .then(|| ident.clone())
2937    }
2938
2939    /// Applies [`class_ident_from_ident`](Self::class_ident_from_ident) to a
2940    /// container's inner type argument (e.g. the `T` in `Option<T>`).
2941    ///
2942    /// Two shapes qualify:
2943    /// - A bare `syn::Type::Path` with no path arguments of its own — this is
2944    ///   what excludes nested containers like `Option<Vec<T>>`, whose inner
2945    ///   `Vec<T>` carries `PathArguments::AngleBracketed`.
2946    /// - `ExternalPtr<T>` (#1375): the explicit-handle spelling wraps
2947    ///   identically to a bare `T` at the value level (`IntoR for
2948    ///   ExternalPtr<T>` already produces a bare `EXTPTRSXP`), so the handle
2949    ///   is peeled with the same helper the explicit `wrap` path uses.
2950    pub(crate) fn inner_class_ident(ty: &syn::Type) -> Option<syn::Ident> {
2951        let syn::Type::Path(p) = crate::type_inspect::peel_external_ptr(ty) else {
2952            return None;
2953        };
2954        let seg = p.path.segments.last()?;
2955        match &seg.arguments {
2956            syn::PathArguments::None => Self::class_ident_from_ident(&seg.ident),
2957            _ => None,
2958        }
2959    }
2960
2961    /// Returns `true` when this method returns `Vec<Self>` — the
2962    /// receiver-class sibling of
2963    /// [`returns_other_class_list`](Self::returns_other_class_list) that this
2964    /// syntactic detector can't resolve to a *name* on its own, because
2965    /// `ParsedMethod` doesn't carry the enclosing impl's type ident (#1375).
2966    ///
2967    /// `ReturnStrategy::for_method` treats a `true` result exactly like
2968    /// `returns_other_class_list()` returning `Some` (both select
2969    /// `ReturnOtherClassList`); `MethodReturnBuilder::with_return_class_from_method`
2970    /// does the actual name substitution, using the `type_ident` every
2971    /// class-system generator already has in scope — preferred over widening
2972    /// `ParsedMethod` to carry the impl ident (see that method's rustdoc for
2973    /// why: the substituted name must be the Rust type name, which is not
2974    /// always the same string as the R-facing class name).
2975    ///
2976    /// Same three container shapes as `returns_other_class_list`, with the
2977    /// element check inverted (the element must *be* `Self`, not be excluded
2978    /// for being `Self`): `Vec<Self>`, `Option<Vec<Self>>`,
2979    /// `Result<Vec<Self>, E>` (`E` not `()`).
2980    pub fn returns_self_list(&self) -> bool {
2981        let syn::ReturnType::Type(_, ty) = &self.sig.output else {
2982            return false;
2983        };
2984        let syn::Type::Path(p) = ty.as_ref() else {
2985            return false;
2986        };
2987        let Some(seg) = p.path.segments.last() else {
2988            return false;
2989        };
2990
2991        match &seg.arguments {
2992            syn::PathArguments::AngleBracketed(_) if seg.ident == "Vec" => Self::is_vec_of_self(ty),
2993            syn::PathArguments::AngleBracketed(_) if seg.ident == "Option" => {
2994                crate::first_type_argument(seg).is_some_and(Self::is_vec_of_self)
2995            }
2996            syn::PathArguments::AngleBracketed(_) if seg.ident == "Result" => {
2997                // Mirrors the `Result<T, ()>` exclusion in
2998                // `returns_other_class_list`: `ResultNullOnErr`'s `.val` may
2999                // be `NULL`, which `lapply` would silently coerce to `list()`.
3000                let err_is_unit = crate::second_type_argument(seg)
3001                    .is_some_and(|ty| #[allow(non_exhaustive_omitted_patterns)] match ty {
    syn::Type::Tuple(t) if t.elems.is_empty() => true,
    _ => false,
}matches!(ty, syn::Type::Tuple(t) if t.elems.is_empty()));
3002                !err_is_unit && crate::first_type_argument(seg).is_some_and(Self::is_vec_of_self)
3003            }
3004            _ => false,
3005        }
3006    }
3007
3008    /// True when `ty` is `Vec<Self>` — a bare `Self` ident, matching the
3009    /// looser ident-only check [`returns_self`](Self::returns_self) uses (no
3010    /// generic-argument check on `Self` itself, which cannot carry any in
3011    /// this position).
3012    fn is_vec_of_self(ty: &syn::Type) -> bool {
3013        let syn::Type::Path(p) = ty else {
3014            return false;
3015        };
3016        let Some(seg) = p.path.segments.last() else {
3017            return false;
3018        };
3019        if seg.ident != "Vec" {
3020            return false;
3021        }
3022        #[allow(non_exhaustive_omitted_patterns)] match crate::first_type_argument(seg)
    {
    Some(syn::Type::Path(ip)) if
        ip.path.segments.last().map(|s| s.ident == "Self").unwrap_or(false) =>
        true,
    _ => false,
}matches!(crate::first_type_argument(seg), Some(syn::Type::Path(ip))
3023            if ip.path.segments.last().map(|s| s.ident == "Self").unwrap_or(false))
3024    }
3025
3026    /// Returns true if this method returns a reference to `Self` (`&Self` or
3027    /// `&mut Self`) — the idiomatic Rust in-place builder signature.
3028    ///
3029    /// These methods mutate (`&mut self`) or read (`&self`) the receiver and
3030    /// return a borrow of the same value so calls can be chained in Rust
3031    /// (`b.set_a(1).set_b(2)`). On the R side this maps to a pipe-friendly free
3032    /// function (S3) / chainable instance method that returns the *same*
3033    /// ExternalPtr handle, so the R idiom `obj |> set_a(1) |> set_b(2)` works
3034    /// with in-place value semantics (no clone). See
3035    /// [`crate::c_wrapper_builder::ReturnHandling::SelfHandle`].
3036    pub fn returns_self_ref(&self) -> bool {
3037        #[allow(non_exhaustive_omitted_patterns)] match &self.sig.output {
    syn::ReturnType::Type(_, ty) if Self::is_self_ref_type(ty) => true,
    _ => false,
}matches!(&self.sig.output, syn::ReturnType::Type(_, ty) if Self::is_self_ref_type(ty))
3038    }
3039
3040    /// `&Self` / `&mut Self` test shared by the bare and wrapped forms.
3041    fn is_self_ref_type(ty: &syn::Type) -> bool {
3042        #[allow(non_exhaustive_omitted_patterns)] match ty {
    syn::Type::Reference(r) if
        #[allow(non_exhaustive_omitted_patterns)] match r.elem.as_ref() {
            syn::Type::Path(p) if
                p.path.segments.last().map(|s|
                            s.ident == "Self").unwrap_or(false) => true,
            _ => false,
        } => true,
    _ => false,
}matches!(ty, syn::Type::Reference(r)
3043            if matches!(r.elem.as_ref(), syn::Type::Path(p)
3044                if p.path.segments.last().map(|s| s.ident == "Self").unwrap_or(false)))
3045    }
3046
3047    /// First type argument of `Wrapper<..>` when the output's last path segment
3048    /// is `wrapper` (`Result` / `Option`).
3049    fn wrapped_output_arg(&self, wrapper: &str) -> Option<&syn::Type> {
3050        let syn::ReturnType::Type(_, ty) = &self.sig.output else {
3051            return None;
3052        };
3053        let syn::Type::Path(p) = ty.as_ref() else {
3054            return None;
3055        };
3056        let seg = p.path.segments.last()?;
3057        if seg.ident != wrapper {
3058            return None;
3059        }
3060        let syn::PathArguments::AngleBracketed(ab) = &seg.arguments else {
3061            return None;
3062        };
3063        match ab.args.first()? {
3064            syn::GenericArgument::Type(t) => Some(t),
3065            _ => None,
3066        }
3067    }
3068
3069    /// `-> Result<&Self, E>` / `-> Result<&mut Self, E>`: a fallible in-place
3070    /// builder step (#1433). `Ok` hands back the same handle like
3071    /// [`Self::returns_self_ref`]; `Err` raises through the normal `Result`
3072    /// error path.
3073    pub fn returns_result_self_ref(&self) -> bool {
3074        self.wrapped_output_arg("Result")
3075            .is_some_and(Self::is_self_ref_type)
3076    }
3077
3078    /// `-> Option<&Self>` / `-> Option<&mut Self>`: the `Option` sibling of
3079    /// [`Self::returns_result_self_ref`]; `None` returns `NULL` and leaves the
3080    /// object as it was.
3081    pub fn returns_option_self_ref(&self) -> bool {
3082        self.wrapped_output_arg("Option")
3083            .is_some_and(Self::is_self_ref_type)
3084    }
3085
3086    /// `-> Option<..>`: `.val` is `NULL` for `None`, so a cross-class wrap of
3087    /// the value has to let `NULL` through.
3088    pub fn returns_option(&self) -> bool {
3089        self.wrapped_output_arg("Option").is_some()
3090    }
3091
3092    /// Returns true if this method has no return type (returns unit `()`).
3093    pub fn returns_unit(&self) -> bool {
3094        match &self.sig.output {
3095            syn::ReturnType::Default => true,
3096            syn::ReturnType::Type(_, ty) => {
3097                #[allow(non_exhaustive_omitted_patterns)] match ty.as_ref() {
    syn::Type::Tuple(t) if t.elems.is_empty() => true,
    _ => false,
}matches!(ty.as_ref(), syn::Type::Tuple(t) if t.elems.is_empty())
3098            }
3099        }
3100    }
3101
3102    /// Whether the R wrapper returns this method's value with `invisible()`
3103    /// (#1213): an `Invisible<T>` return-type marker or the `invisible` method
3104    /// option. Methods are visible by default, including the receiver-returning
3105    /// chainable tails.
3106    pub fn is_invisible(&self) -> bool {
3107        self.visibility_marker
3108            .or(self.method_attrs.force_invisible)
3109            .unwrap_or(false)
3110    }
3111}
3112
3113fn is_builtin_return_type_name(name: &str) -> bool {
3114    #[allow(non_exhaustive_omitted_patterns)] match name {
    "i8" | "i16" | "i32" | "i64" | "i128" | "u8" | "u16" | "u32" | "u64" |
        "u128" | "f32" | "f64" | "bool" | "char" | "str" | "String" | "usize"
        | "isize" | "RLogical" | "Rboolean" | "Rcomplex" => true,
    _ => false,
}matches!(
3115        name,
3116        "i8" | "i16"
3117            | "i32"
3118            | "i64"
3119            | "i128"
3120            | "u8"
3121            | "u16"
3122            | "u32"
3123            | "u64"
3124            | "u128"
3125            | "f32"
3126            | "f64"
3127            | "bool"
3128            | "char"
3129            | "str"
3130            | "String"
3131            | "usize"
3132            | "isize"
3133            | "RLogical"
3134            | "Rboolean"
3135            | "Rcomplex"
3136    )
3137}
3138
3139fn is_known_return_container_name(name: &str) -> bool {
3140    #[allow(non_exhaustive_omitted_patterns)] match name {
    "Vec" | "Option" | "Result" | "Box" | "HashMap" | "BTreeMap" => true,
    _ => false,
}matches!(
3141        name,
3142        "Vec" | "Option" | "Result" | "Box" | "HashMap" | "BTreeMap"
3143    )
3144}
3145
3146impl ParsedImpl {
3147    /// Parse an impl block with class system attribute.
3148    ///
3149    /// Note: Trait impls (`impl Trait for Type`) are handled by `expand_impl`
3150    /// before this function is called, so we only handle inherent impls here.
3151    pub fn parse(attrs: ImplAttrs, item_impl: syn::ItemImpl) -> syn::Result<Self> {
3152        // Extract type identifier
3153        let type_ident =
3154            match item_impl.self_ty.as_ref() {
3155                syn::Type::Path(p) => p.path.segments.last().map(|s| s.ident.clone()).ok_or_else(
3156                    || {
3157                        syn::Error::new_spanned(
3158                            &item_impl.self_ty,
3159                            "#[miniextendr] impl blocks require a named type (e.g., `impl MyType`)",
3160                        )
3161                    },
3162                )?,
3163                _ => {
3164                    return Err(syn::Error::new_spanned(
3165                        &item_impl.self_ty,
3166                        "#[miniextendr] impl blocks require a named struct type. \
3167                     Found a non-path type. Use `impl MyStruct { ... }` with a concrete struct.",
3168                    ));
3169                }
3170            };
3171
3172        // Reject type/const generics — the generated #[no_mangle] C wrappers are
3173        // incompatible with type/const params (they require monomorphization → multiple
3174        // symbols). Lifetime params ARE allowed: lifetimes are erased at codegen and the
3175        // wrapper generation uses `type_ident` (the bare struct ident) without generic
3176        // args, which is correct because lifetime arguments are never needed in generated
3177        // code (they are inferred or elided).
3178        {
3179            let params = &item_impl.generics.params;
3180            let has_type_or_const = params
3181                .iter()
3182                .any(|p| #[allow(non_exhaustive_omitted_patterns)] match p {
    syn::GenericParam::Type(_) | syn::GenericParam::Const(_) => true,
    _ => false,
}matches!(p, syn::GenericParam::Type(_) | syn::GenericParam::Const(_)));
3183
3184            if has_type_or_const {
3185                return Err(syn::Error::new_spanned(
3186                    &item_impl.generics,
3187                    "generic impl blocks are not supported by #[miniextendr]. \
3188                     R's .Call interface requires monomorphic C symbols, so generic type \
3189                     parameters cannot be used. Remove the generic parameters and use a \
3190                     concrete type instead. \
3191                     Explicit lifetime parameters are allowed (lifetimes are erased at codegen).",
3192                ));
3193            }
3194        }
3195
3196        // Reject unsupported attributes on the impl block
3197        for attr in &item_impl.attrs {
3198            if attr.path().is_ident("export_name") {
3199                return Err(syn::Error::new_spanned(
3200                    attr,
3201                    "#[export_name] is not supported with #[miniextendr]; \
3202                     the macro generates its own C symbol names",
3203                ));
3204            }
3205        }
3206
3207        // The class block renders no page under `@noRd` or a plain `noexport`
3208        // (`ClassDocBuilder::build`'s gate: `internal`, as the attribute or the
3209        // `@keywords internal` tag, keeps the page). Its docs and every
3210        // method's (R6 folds its method blocks into the class block) then
3211        // keep no links (#1818).
3212        let class_page = crate::roxygen::RdPage::suppressed_if(
3213            crate::roxygen::attrs_have_roxygen_tag(&item_impl.attrs, "noRd")
3214                || (attrs.noexport
3215                    && !attrs.internal
3216                    && !crate::roxygen::attrs_have_roxygen_tag(
3217                        &item_impl.attrs,
3218                        "keywords internal",
3219                    )),
3220        );
3221
3222        // Parse methods and validate attributes. Method parsing also normalizes
3223        // raw variadic `...` syntax in the impl clone that gets re-emitted.
3224        let mut original_impl = item_impl.clone();
3225        let mut methods = Vec::new();
3226        for item in &mut original_impl.items {
3227            if let syn::ImplItem::Fn(fn_item) = item {
3228                let mut method = ParsedMethod::from_impl_item(fn_item, class_page)?;
3229                crate::s7_conversion::configure(
3230                    &mut method,
3231                    fn_item,
3232                    attrs.class_system,
3233                    &type_ident,
3234                )?;
3235                // Validate method attributes for this class system
3236                ParsedMethod::validate_method_attrs(
3237                    &method.method_attrs,
3238                    attrs.class_system,
3239                    fn_item.sig.ident.span(),
3240                )?;
3241                if method.method_attrs.serde_error.is_some()
3242                    && !crate::return_type_analysis::output_is_result(&fn_item.sig.output)
3243                {
3244                    return Err(syn::Error::new(
3245                        fn_item.sig.ident.span(),
3246                        "`#[miniextendr(serde_error(..))]` requires a `Result<T, E>` return type: it \
3247                         classes the condition raised from the `Err` arm",
3248                    ));
3249                }
3250                methods.push(method);
3251            }
3252        }
3253
3254        // MXL120: For vctrs impls, reject constructors that return Self or the named type,
3255        // and reject all instance-method receivers (&self, &mut self, self, self: ExternalPtr<Self>).
3256        //
3257        // The generated R wrapper passes the constructor result to vctrs::new_vctr() (or
3258        // new_rcrd/new_list_of), which requires a plain vector — not an ExternalPtr.
3259        // Returning Self produces an EXTPTRSXP which new_vctr rejects with
3260        // ".data must be a vector type".
3261        //
3262        // Instance-method receivers (&self, &mut self, etc.) are equally broken: the vctrs
3263        // S3 dispatch passes the R object (an S3-classed base vector — REALSXP, INTSXP, etc.)
3264        // as `self_sexp`. The C wrapper's receiver prelude (`resolve_receiver`, then
3265        // `ErasedExternalPtr::from_sexp`) panics because the base vector is not an ExternalPtr
3266        // and carries no `.ptr`.  There is no Rust `Self`
3267        // stored anywhere — the vector payload IS the R object.  Instance methods must be
3268        // expressed as static methods receiving the vector data by parameter.
3269        if attrs.class_system == ClassSystem::Vctrs {
3270            for method in &methods {
3271                // Check 1: constructor return type
3272                let is_ctor = (method.method_attrs.constructor
3273                    || (method.env == ReceiverKind::None && method.ident == "new"))
3274                    && method.env != ReceiverKind::Ref
3275                    && method.env != ReceiverKind::RefMut;
3276                if is_ctor && vctrs_ctor_returns_self_or_type(&method.sig.output, &type_ident) {
3277                    return Err(syn::Error::new_spanned(
3278                        &method.sig.output,
3279                        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("[MXL120] vctrs constructor `{0}` must not return `Self` or `{1}`.\n\nThe generated R wrapper passes the constructor result to `vctrs::new_vctr()` (or `new_rcrd`/`new_list_of`), which requires a plain vector payload — not an ExternalPtr (`EXTPTRSXP`).\n\nFix: return the vector payload directly instead of `Self`.\nFor example, return `Vec<f64>` (for vctr), a `std::collections::HashMap` / named-list struct (for rcrd), or a `Vec<Vec<T>>` (for list_of).",
                method.ident, type_ident))
    })format!(
3280                            "[MXL120] vctrs constructor `{}` must not return `Self` or `{}`.\n\
3281                             \n\
3282                             The generated R wrapper passes the constructor result to \
3283                             `vctrs::new_vctr()` (or `new_rcrd`/`new_list_of`), which requires a \
3284                             plain vector payload — not an ExternalPtr (`EXTPTRSXP`).\n\
3285                             \n\
3286                             Fix: return the vector payload directly instead of `Self`.\n\
3287                             For example, return `Vec<f64>` (for vctr), a `std::collections::HashMap` \
3288                             / named-list struct (for rcrd), or a `Vec<Vec<T>>` (for list_of).",
3289                            method.ident, type_ident
3290                        ),
3291                    ));
3292                }
3293
3294                // Check 2: instance-method receivers are not supported on vctrs impls
3295                if method.env.is_instance() {
3296                    let receiver_spelling = match method.env {
3297                        ReceiverKind::Ref => "&self",
3298                        ReceiverKind::RefMut => "&mut self",
3299                        ReceiverKind::Value => "self",
3300                        ReceiverKind::ExternalPtrRef => "self: ExternalPtrRef<'_, Self>",
3301                        ReceiverKind::ExternalPtrMut => "self: ExternalPtrMut<'_, Self>",
3302                        ReceiverKind::ExternalPtrValue => "self: ExternalPtr<Self>",
3303                        ReceiverKind::None => ::core::panicking::panic("internal error: entered unreachable code")unreachable!(),
3304                    };
3305                    return Err(syn::Error::new_spanned(
3306                        &method.ident,
3307                        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("[MXL120] vctrs impl method `{0}` uses a `{1}` receiver, which is not supported on `#[miniextendr(vctrs(...))]` impls.\n\nA vctrs object is an S3-classed base vector (REALSXP, INTSXP, etc.). There is no Rust `Self` stored inside the R SEXP — the vector payload IS the R object.  The C wrapper cannot reconstruct `Self` from a base vector, so calling an instance method would panic at runtime.\n\nFix: convert this method to a static method whose parameters receive the vector data directly.  For example:\n\n// Before (broken):\n// pub fn value(&self) -> f64 {{ ... }}\n\n// After (correct):\n// pub fn value(amounts: Vec<f64>) -> Vec<f64> {{ ... }}",
                method.ident, receiver_spelling))
    })format!(
3308                            "[MXL120] vctrs impl method `{}` uses a `{}` receiver, which is not \
3309                             supported on `#[miniextendr(vctrs(...))]` impls.\n\
3310                             \n\
3311                             A vctrs object is an S3-classed base vector (REALSXP, INTSXP, etc.). \
3312                             There is no Rust `Self` stored inside the R SEXP — the vector payload \
3313                             IS the R object.  The C wrapper cannot reconstruct `Self` from a base \
3314                             vector, so calling an instance method would panic at runtime.\n\
3315                             \n\
3316                             Fix: convert this method to a static method whose parameters receive \
3317                             the vector data directly.  For example:\n\
3318                             \n\
3319                             // Before (broken):\n\
3320                             // pub fn value(&self) -> f64 {{ ... }}\n\
3321                             \n\
3322                             // After (correct):\n\
3323                             // pub fn value(amounts: Vec<f64>) -> Vec<f64> {{ ... }}",
3324                            method.ident, receiver_spelling
3325                        ),
3326                    ));
3327                }
3328            }
3329        }
3330
3331        // Extract cfg attributes
3332        let cfg_attrs: Vec<_> = item_impl
3333            .attrs
3334            .iter()
3335            .filter(|attr| attr.path().is_ident("cfg"))
3336            .cloned()
3337            .collect();
3338        let raw_doc_tags = crate::roxygen::roxygen_tags_from_attrs(&item_impl.attrs, class_page);
3339        // For R6: keep class-level @param tags (roxygen2 8.0.0 inherits them into
3340        // all methods); for other class systems use the stricter filter. Each
3341        // stripped tag's warning lives in its own anonymous `const _: () = { .. };`
3342        // scope (#1118), so no per-block disambiguator is needed here.
3343        let (doc_tags, param_warnings) = if attrs.class_system == ClassSystem::R6 {
3344            crate::roxygen::strip_method_tags_r6(
3345                &raw_doc_tags,
3346                &type_ident.to_string(),
3347                item_impl.impl_token.span,
3348            )
3349        } else {
3350            crate::roxygen::strip_method_tags(
3351                &raw_doc_tags,
3352                &type_ident.to_string(),
3353                item_impl.impl_token.span,
3354            )
3355        };
3356        // Build the set of parameter names declared at class level so the R6
3357        // generator can suppress placeholder lines for covered params.
3358        let class_param_names = crate::roxygen::extract_param_names(&doc_tags);
3359
3360        let parsed = ParsedImpl {
3361            type_ident,
3362            class_system: attrs.class_system,
3363            class_name: attrs.class_name,
3364            doc_tags,
3365            methods,
3366            // Strip miniextendr attributes (and roxygen tags) before re-emitting,
3367            // then rewrite ExternalPtr receivers for stable Rust compatibility.
3368            original_impl: rewrite_external_ptr_receivers(strip_miniextendr_attrs_from_impl(
3369                original_impl,
3370            )),
3371            cfg_attrs,
3372            vctrs_attrs: attrs.vctrs_attrs,
3373            r6_inherit: attrs.r6_inherit,
3374            r6_portable: attrs.r6_portable,
3375            r6_cloneable: attrs.r6_cloneable,
3376            r6_lock_objects: attrs.r6_lock_objects,
3377            r6_lock_class: attrs.r6_lock_class,
3378            s7_parent: attrs.s7_parent,
3379            s7_abstract: attrs.s7_abstract,
3380            r_data_accessors: attrs.r_data_accessors,
3381            field_lookup: attrs.field_lookup,
3382            field_fallback: attrs.field_fallback,
3383            strict: attrs.strict,
3384            preconditions: attrs.preconditions,
3385            internal: attrs.internal,
3386            noexport: attrs.noexport,
3387            constructor_demoted: false,
3388            param_warnings,
3389            class_param_names,
3390        };
3391        parsed.check_constructor_markers()?;
3392        parsed.reject_unsupported_describe_in()?;
3393        field_syntax::check_collisions(&parsed)?;
3394        Ok(parsed)
3395    }
3396
3397    /// Reject a method-level `@describeIn` that roxygen2 cannot honour (#1590).
3398    ///
3399    /// roxygen2 lists a `@describeIn` block in its destination's "Functions"
3400    /// section through the R object the block documents, and rejects the tag
3401    /// next to `@name` / `@rdname`, so `MethodDocBuilder` drops both for it.
3402    /// That only works where the method's block sits on a plain R function or
3403    /// S3 method: S3 instance methods (`generic.Class`), the static methods of
3404    /// S3 / S4 / S7 / vctrs (`class_method()`), and the S4 constructor
3405    /// (`Class()`). Everywhere else the block has no object to list:
3406    ///
3407    /// - S4 / S7 instance methods are registered by `setMethod()` /
3408    ///   `S7::method<-` after the `if (!exists(...))` generic guard the block
3409    ///   is attached to;
3410    /// - Env and R6 methods are `Class$method <-` assignments or live inside
3411    ///   `R6Class()`;
3412    /// - the S3 / vctrs / S7 constructor is documented by the class block.
3413    ///
3414    /// There the tag would only earn a roxygen2 warning and a dropped block,
3415    /// so it is a compile error pointing at `@rdname` instead.
3416    fn reject_unsupported_describe_in(&self) -> syn::Result<()> {
3417        for method in self.included_methods() {
3418            if crate::roxygen::describe_in_topic(&method.doc_tags).is_none() {
3419                continue;
3420            }
3421            let is_ctor = self.is_method_constructor(method);
3422            let is_static = method.env == ReceiverKind::None && !is_ctor;
3423            let supported = match self.class_system {
3424                ClassSystem::S3 | ClassSystem::Vctrs => !is_ctor,
3425                ClassSystem::S4 => is_ctor || is_static,
3426                ClassSystem::S7 => is_static,
3427                ClassSystem::Env | ClassSystem::R6 => false,
3428            };
3429            if supported {
3430                continue;
3431            }
3432            let kind = if is_ctor {
3433                "constructor"
3434            } else if is_static {
3435                "static method"
3436            } else {
3437                "instance method"
3438            };
3439            let system = match self.class_system {
3440                ClassSystem::S3 => "S3",
3441                ClassSystem::Vctrs => "vctrs",
3442                ClassSystem::S4 => "S4",
3443                ClassSystem::S7 => "S7",
3444                ClassSystem::Env => "Env",
3445                ClassSystem::R6 => "R6",
3446            };
3447            return Err(syn::Error::new_spanned(
3448                &method.ident,
3449                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("`@describeIn` is not supported on the {1} {2} `{0}`: its R wrapper is not an R function roxygen2 can list in the destination\'s \"Functions\" section, so roxygen2 would drop the block. Use `@rdname <topic>` to document it on that page instead. `@describeIn` works on S3 instance methods, on the static methods of S3, S4, S7 and vctrs classes, and on S4 constructors.",
                method.ident, system, kind))
    })format!(
3450                    "`@describeIn` is not supported on the {system} {kind} `{}`: its R wrapper \
3451                     is not an R function roxygen2 can list in the destination's \"Functions\" \
3452                     section, so roxygen2 would drop the block. Use `@rdname <topic>` to \
3453                     document it on that page instead. `@describeIn` works on S3 instance \
3454                     methods, on the static methods of S3, S4, S7 and vctrs classes, and on \
3455                     S4 constructors.",
3456                    method.ident,
3457                ),
3458            ));
3459        }
3460        Ok(())
3461    }
3462
3463    /// Get the class name (override or type name).
3464    pub fn class_name(&self) -> String {
3465        self.class_name
3466            .clone()
3467            .unwrap_or_else(|| self.type_ident.to_string())
3468    }
3469
3470    /// The class-level settings, written like the attribute, which every impl
3471    /// block of a type must spell the same (#1776): the class system with its
3472    /// options, `class = "..."`, `internal` / `noexport`, and the `@noRd` /
3473    /// `@keywords internal` tags. They shape the members each block
3474    /// generates, so the wrapper writer refuses blocks that differ. The
3475    /// per-method defaults (`strict`, `preconditions`) may differ. A field
3476    /// fallback (`fallback = "..."`, #2150) is no setting: one block has it,
3477    /// as one has the constructor ([`class_definitions`](Self::class_definitions)).
3478    pub fn class_settings(&self) -> String {
3479        let mut options: Vec<String> = Vec::new();
3480        match self.class_system {
3481            ClassSystem::Vctrs => {
3482                let vctrs = &self.vctrs_attrs;
3483                let kind = match vctrs.kind {
3484                    VctrsKind::Vctr => "vctr",
3485                    VctrsKind::Rcrd => "rcrd",
3486                    VctrsKind::ListOf => "list_of",
3487                };
3488                options.push(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("kind = {0:?}", kind))
    })format!("kind = {kind:?}"));
3489                if let Some(base) = &vctrs.base {
3490                    options.push(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("base = {0:?}", base))
    })format!("base = {base:?}"));
3491                }
3492                if let Some(inherit) = vctrs.inherit_base_type {
3493                    options.push(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("inherit_base_type = {0}", inherit))
    })format!("inherit_base_type = {inherit}"));
3494                }
3495                if let Some(ptype) = &vctrs.ptype {
3496                    options.push(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("ptype = {0:?}", ptype))
    })format!("ptype = {ptype:?}"));
3497                }
3498                if let Some(abbr) = &vctrs.abbr {
3499                    options.push(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("abbr = {0:?}", abbr))
    })format!("abbr = {abbr:?}"));
3500                }
3501            }
3502            ClassSystem::R6 => {
3503                if let Some(inherit) = &self.r6_inherit {
3504                    options.push(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("inherit = {0:?}", inherit))
    })format!("inherit = {inherit:?}"));
3505                }
3506                for (name, value) in [
3507                    ("portable", self.r6_portable),
3508                    ("cloneable", self.r6_cloneable),
3509                    ("lock_objects", self.r6_lock_objects),
3510                    ("lock_class", self.r6_lock_class),
3511                ] {
3512                    if let Some(value) = value {
3513                        options.push(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0} = {1}", name, value))
    })format!("{name} = {value}"));
3514                    }
3515                }
3516            }
3517            ClassSystem::S7 => {
3518                if let Some(parent) = &self.s7_parent {
3519                    options.push(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("parent = {0:?}", parent))
    })format!("parent = {parent:?}"));
3520                }
3521                if self.s7_abstract {
3522                    options.push("abstract".to_string());
3523                }
3524            }
3525            ClassSystem::Env | ClassSystem::S3 | ClassSystem::S4 => {}
3526        }
3527        match self.r_data_accessors {
3528            RDataAccessors::Off => {}
3529            RDataAccessors::GetSet => options.push("r_data_accessors".to_string()),
3530            RDataAccessors::GetOnly => options.push("r_data_accessors = \"get\"".to_string()),
3531        }
3532        if self.field_lookup == FieldLookup::List {
3533            options.push("lookup = \"list\"".to_string());
3534        }
3535
3536        let system = self.class_system.to_ident().to_string();
3537        let mut settings = ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [if options.is_empty() {
                    system
                } else {
                    ::alloc::__export::must_use({
                            ::alloc::fmt::format(format_args!("{1}({0})",
                                    options.join(", "), system))
                        })
                }]))vec![if options.is_empty() {
3538            system
3539        } else {
3540            format!("{system}({})", options.join(", "))
3541        }];
3542        if let Some(class_name) = &self.class_name {
3543            settings.push(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("class = {0:?}", class_name))
    })format!("class = {class_name:?}"));
3544        }
3545        if self.internal {
3546            settings.push("internal".to_string());
3547        }
3548        if self.noexport {
3549            settings.push("noexport".to_string());
3550        }
3551        for tag in CLASS_SETTING_TAGS {
3552            if crate::roxygen::has_roxygen_tag(&self.doc_tags, tag) {
3553                settings.push(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("@{0}", tag))
    })format!("@{tag}"));
3554            }
3555        }
3556        settings.join(", ")
3557    }
3558
3559    /// What this impl block has that only one block of a type may: the
3560    /// constructor, S7 properties and the field fallback (#2150). The
3561    /// wrapper writer defines the class from the block that has them (#1776),
3562    /// so the field methods that call the fallback are that block's; the
3563    /// impl docs of every block merge into the class's roxygen block
3564    /// ([`ClassWrapper::class_docs`]).
3565    pub fn class_definitions(&self) -> Vec<String> {
3566        let mut definitions = Vec::new();
3567        if self.declared_constructor().is_some() {
3568            definitions.push("a constructor".to_string());
3569        } else if let Some(inferred) = self.inferred_constructor() {
3570            definitions.push(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("the inferred constructor `{0}`",
                inferred.ident))
    })format!("the inferred constructor `{}`", inferred.ident));
3571        }
3572        if self.class_system == ClassSystem::S7
3573            && self.included_methods().any(|m| {
3574                let s7 = &m.method_attrs.s7;
3575                s7.getter || s7.setter || s7.validate
3576            })
3577        {
3578            definitions.push("S7 properties".to_string());
3579        }
3580        if self.field_fallback.is_some() {
3581            definitions.push("the field fallback".to_string());
3582        }
3583        definitions
3584    }
3585
3586    /// Get methods that should be included.
3587    pub fn included_methods(&self) -> impl Iterator<Item = &ParsedMethod> {
3588        self.methods.iter().filter(|m| m.should_include())
3589    }
3590
3591    /// The class constructor of this impl block, if it has one: the
3592    /// [declared](Self::declared_constructor) constructor, else the
3593    /// [inferred](Self::inferred_constructor) one. Every other receiver-less
3594    /// method is a static method.
3595    pub fn constructor(&self) -> Option<&ParsedMethod> {
3596        self.declared_constructor()
3597            .or_else(|| self.inferred_constructor())
3598    }
3599
3600    /// The constructor the block declares: the method marked
3601    /// `#[miniextendr(<system>(constructor))]` (at most one,
3602    /// [`check_constructor_markers`](Self::check_constructor_markers)), else
3603    /// a receiver-less `new` returning `Self`. For vctrs, `new` may return
3604    /// anything: the canonical vctrs constructor returns the vector payload
3605    /// (e.g. `Vec<f64>`), which `vctrs::new_vctr()` then wraps; returning
3606    /// `Self` would produce an `ExternalPtr` that `new_vctr` can't accept as
3607    /// `.data`.
3608    pub fn declared_constructor(&self) -> Option<&ParsedMethod> {
3609        self.included_methods()
3610            .find(|m| m.method_attrs.constructor)
3611            .or_else(|| {
3612                self.included_methods().find(|m| {
3613                    m.env == ReceiverKind::None
3614                        && m.ident == "new"
3615                        && (self.class_system == ClassSystem::Vctrs || m.returns_self())
3616                })
3617            })
3618    }
3619
3620    /// The methods that could be the class constructor: the receiver-less
3621    /// methods returning `Self` (bare `Self`, as [`ParsedMethod::returns_self`]
3622    /// reads it, so not `Result<Self, E>` or `Option<Self>`), other than the
3623    /// declared constructor and an S7 `convert_from` method, in source order.
3624    /// Without a declared constructor, the one candidate of a type is its
3625    /// constructor; with several, the type needs a declared one. None for
3626    /// vctrs, whose constructor returns the vector payload rather than `Self`.
3627    pub fn constructor_candidates(&self) -> impl Iterator<Item = &ParsedMethod> {
3628        let declared = self.declared_constructor();
3629        self.included_methods().filter(move |m| {
3630            self.class_system != ClassSystem::Vctrs
3631                && m.env == ReceiverKind::None
3632                && m.returns_self()
3633                && m.method_attrs.s7.convert_from.is_none()
3634                && !declared.is_some_and(|d| std::ptr::eq(d, *m))
3635        })
3636    }
3637
3638    /// The constructor this block infers: its one
3639    /// [candidate](Self::constructor_candidates), whatever its name, when it
3640    /// declares no constructor. None in the rendering for a type whose
3641    /// constructor another impl block declares
3642    /// ([`constructor_demoted`](Self::constructor_demoted)). The wrapper
3643    /// writer decides per type which rendering it writes.
3644    pub fn inferred_constructor(&self) -> Option<&ParsedMethod> {
3645        if self.constructor_demoted || self.declared_constructor().is_some() {
3646            return None;
3647        }
3648        let mut candidates = self.constructor_candidates();
3649        let first = candidates.next()?;
3650        candidates.next().is_none().then_some(first)
3651    }
3652
3653    /// Whether `m` is the class [constructor](Self::constructor).
3654    fn is_method_constructor(&self, m: &ParsedMethod) -> bool {
3655        self.constructor().is_some_and(|c| std::ptr::eq(c, m))
3656    }
3657
3658    /// The roxygen tags of the static method `m`, called as `call` (e.g.
3659    /// `Counter$from_value()`). The constructor under its own name
3660    /// ([`constructor_keeps_own_name`](Self::constructor_keeps_own_name))
3661    /// points at the class constructor `constructor` (e.g. `Counter$new()`)
3662    /// instead of repeating its docs, keeping the method's `@noRd` /
3663    /// `@keywords`, and its `@param` tags when `params` (roxygen2 merges them
3664    /// with the class constructor's).
3665    fn static_doc_tags<'a>(
3666        &self,
3667        m: &'a ParsedMethod,
3668        call: &str,
3669        constructor: &str,
3670        params: bool,
3671    ) -> std::borrow::Cow<'a, [String]> {
3672        if !self.is_method_constructor(m) {
3673            return std::borrow::Cow::Borrowed(&m.doc_tags);
3674        }
3675        let kept = |tag: &&String| {
3676            let name = tag.split_whitespace().next().unwrap_or("");
3677            name == "@noRd" || name == "@keywords" || (params && name == "@param")
3678        };
3679        let mut tags = ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [::alloc::__export::must_use({
                        ::alloc::fmt::format(format_args!("@description `{0}` is another name for the constructor `{1}`.",
                                call, constructor))
                    })]))vec![format!(
3680            "@description `{call}` is another name for the constructor `{constructor}`."
3681        )];
3682        tags.extend(m.doc_tags.iter().filter(kept).cloned());
3683        std::borrow::Cow::Owned(tags)
3684    }
3685
3686    /// Refuse two methods marked `#[miniextendr(<system>(constructor))]`: a
3687    /// class has one constructor.
3688    fn check_constructor_markers(&self) -> syn::Result<()> {
3689        let marker = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("#[miniextendr({0}(constructor))]",
                self.class_system.to_ident()))
    })format!(
3690            "#[miniextendr({}(constructor))]",
3691            self.class_system.to_ident()
3692        );
3693        let mut marked = self
3694            .included_methods()
3695            .filter(|m| m.method_attrs.constructor);
3696        if let (Some(first), Some(second)) = (marked.next(), marked.next()) {
3697            return Err(syn::Error::new_spanned(
3698                &second.ident,
3699                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("`{0}` and `{1}` are both marked `{2}`; a class has one constructor. Drop the marker from one: it becomes a static method.",
                first.ident, second.ident, marker))
    })format!(
3700                    "`{}` and `{}` are both marked `{marker}`; a class has one constructor. \
3701                     Drop the marker from one: it becomes a static method.",
3702                    first.ident, second.ident,
3703                ),
3704            ));
3705        }
3706        Ok(())
3707    }
3708
3709    /// Get public instance methods (have env, not private, not active, not an
3710    /// R6 finalizer or `deep_clone` handler).
3711    pub fn public_instance_methods(&self) -> impl Iterator<Item = &ParsedMethod> {
3712        self.methods.iter().filter(|m| {
3713            m.should_include()
3714                && m.env.is_instance()
3715                && !m.is_constructor()
3716                && !m.is_finalizer()
3717                && !m.method_attrs.r6.deep_clone
3718                && !m.is_private()
3719                && !m.is_active()
3720        })
3721    }
3722
3723    /// Get private instance methods (have env, private visibility, not active).
3724    pub fn private_instance_methods(&self) -> impl Iterator<Item = &ParsedMethod> {
3725        self.methods.iter().filter(|m| {
3726            m.should_include()
3727                && m.env.is_instance()
3728                && !m.is_constructor()
3729                && !m.is_finalizer()
3730                && m.is_private()
3731                && !m.is_active()
3732        })
3733    }
3734
3735    /// Get active binding getter methods for R6 (have env, marked active, not setter).
3736    /// Active bindings provide property-like access (obj$name instead of obj$name()).
3737    pub fn active_instance_methods(&self) -> impl Iterator<Item = &ParsedMethod> {
3738        self.methods.iter().filter(|m| {
3739            m.should_include()
3740                && m.env.is_instance()
3741                && !m.is_constructor()
3742                && !m.is_finalizer()
3743                && m.is_active()
3744                && !m.method_attrs.r6.setter // Exclude setters
3745        })
3746    }
3747
3748    /// Get active binding setter methods for R6 (have env, marked as r6_setter).
3749    pub fn active_setter_methods(&self) -> impl Iterator<Item = &ParsedMethod> {
3750        self.methods
3751            .iter()
3752            .filter(|m| m.should_include() && m.env.is_instance() && m.method_attrs.r6.setter)
3753    }
3754
3755    /// Find the setter method for a given property name.
3756    pub fn find_setter_for_prop(&self, prop_name: &str) -> Option<&ParsedMethod> {
3757        self.active_setter_methods().find(|m| {
3758            // Match by explicit prop name or by method name with "set_" prefix removed
3759            if let Some(ref explicit_prop) = m.method_attrs.r6.prop {
3760                explicit_prop == prop_name
3761            } else {
3762                // Try to match by stripping "set_" prefix from method name
3763                let method_name = crate::naming::ident_name(&m.ident);
3764                method_name.strip_prefix("set_").unwrap_or(&method_name) == prop_name
3765            }
3766        })
3767    }
3768
3769    /// Get instance methods (have env) - includes both public and private.
3770    pub fn instance_methods(&self) -> impl Iterator<Item = &ParsedMethod> {
3771        self.methods.iter().filter(|m| {
3772            m.should_include() && m.env.is_instance() && !m.is_constructor() && !m.is_finalizer()
3773        })
3774    }
3775
3776    /// Get static methods (no env, not finalizer): the receiver-less methods
3777    /// other than the constructor, and the constructor too when its R name
3778    /// isn't `new` ([`constructor_keeps_own_name`](Self::constructor_keeps_own_name)).
3779    pub fn static_methods(&self) -> impl Iterator<Item = &ParsedMethod> {
3780        self.methods.iter().filter(|m| {
3781            m.should_include()
3782                && m.env == ReceiverKind::None
3783                && (!self.is_method_constructor(m) || self.constructor_keeps_own_name(m))
3784                && !m.is_finalizer()
3785        })
3786    }
3787
3788    /// Whether the constructor `m` is also a static method under its own
3789    /// name: `Type$from_value()` next to `Type$new()`, `Type_from_value()`
3790    /// next to `Type()`, `type_from_value()` next to `new_type()`. A
3791    /// constructor named `new` has no second name, and a vctrs constructor
3792    /// returns the vector payload, which only `new_<class>()` classes.
3793    fn constructor_keeps_own_name(&self, m: &ParsedMethod) -> bool {
3794        self.class_system != ClassSystem::Vctrs && m.r_method_name() != "new"
3795    }
3796
3797    /// Get methods with `#[miniextendr(as = "...")]` attribute.
3798    ///
3799    /// These generate S3 methods for R's `as.<class>()` generics like
3800    /// `as.data.frame.MyType`, `as.list.MyType`, etc.
3801    pub fn as_coercion_methods(&self) -> impl Iterator<Item = &ParsedMethod> {
3802        self.methods
3803            .iter()
3804            .filter(|m| m.should_include() && m.method_attrs.as_coercion.is_some())
3805    }
3806
3807    /// Get the finalizer method, if any.
3808    pub fn finalizer(&self) -> Option<&ParsedMethod> {
3809        self.methods
3810            .iter()
3811            .find(|m| m.should_include() && m.is_finalizer())
3812    }
3813}
3814
3815/// Generate a C-callable wrapper function for a single method in an impl block.
3816///
3817/// Produces a `#[no_mangle] extern "C-unwind"` function named `C_{crate}_{Type}__{method}` that:
3818/// 1. Accepts SEXP arguments (including `self_sexp` for instance methods)
3819/// 2. Extracts `&self` / `&mut self` from an `ErasedExternalPtr` for instance methods
3820/// 3. Converts SEXP arguments to Rust types
3821/// 4. Calls the actual Rust method
3822/// 5. Converts the return value back to SEXP
3823///
3824/// Thread strategy is determined automatically: instance methods always run on the main
3825/// thread (because `self_ref` is a non-Send borrow), as do methods taking `...`. Other
3826/// static methods use the main thread unless `worker` or `worker-default` selects
3827/// worker dispatch; `unsafe(main_thread)` keeps one on main regardless, and so do the
3828/// requirements that keep a standalone function there (R values in or out,
3829/// `check_interrupt`).
3830///
3831/// Also emits an `R_CallMethodDef` constant for R routine registration.
3832///
3833/// # Arguments
3834///
3835/// * `parsed_impl` - The parsed impl block providing type identity, cfg attrs, and options
3836/// * `method` - The parsed method to generate a wrapper for
3837pub fn generate_method_c_wrapper(parsed_impl: &ParsedImpl, method: &ParsedMethod) -> TokenStream {
3838    use crate::c_wrapper_builder::{CWrapperContext, ReturnHandling, ThreadStrategy};
3839
3840    let type_ident = &parsed_impl.type_ident;
3841    let method_ident = &method.ident;
3842    let c_ident = method.c_wrapper_ident(type_ident);
3843    let self_sexp = crate::naming::self_sexp_ident();
3844    let self_ptr = syn::Ident::new("self_ptr", proc_macro2::Span::mixed_site());
3845    let self_ref = syn::Ident::new("self_ref", proc_macro2::Span::mixed_site());
3846    let self_slot = syn::Ident::new("self_slot", proc_macro2::Span::mixed_site());
3847    let external_ptr = syn::Ident::new("__self_ptr", proc_macro2::Span::mixed_site());
3848    let taken = syn::Ident::new("__mx_taken", proc_macro2::Span::mixed_site());
3849
3850    // Determine thread strategy
3851    // Instance methods must use main thread because self_ref is a borrow that can't cross threads
3852    // Static methods use worker thread only when worker=true (set by explicit #[miniextendr(env(worker))]
3853    // or by the worker-default feature flag), and, as for standalone functions, only when
3854    // no R value crosses the call (a main-thread-bound input or return) and no
3855    // `check_interrupt` asks for R's interrupt check.
3856    let thread_strategy = if method.method_attrs.unsafe_main_thread
3857        || method.env.is_instance()
3858        || method.has_dots
3859        || method.method_attrs.check_interrupt
3860        || method.rng_guard.is_some()
3861        || crate::type_inspect::signature_holds_r_values(&method.sig)
3862    {
3863        ThreadStrategy::MainThread
3864    } else if method.method_attrs.worker {
3865        ThreadStrategy::WorkerThread
3866    } else {
3867        ThreadStrategy::MainThread
3868    };
3869
3870    // Build rust arguments from the signature, with the `rng` wrapper's guard
3871    // back at the `&RngGuard` parameter's position (#2002)
3872    let mut rust_args: Vec<TokenStream> = method
3873        .sig
3874        .inputs
3875        .iter()
3876        .filter_map(|arg| {
3877            if let syn::FnArg::Typed(pt) = arg
3878                && let syn::Pat::Ident(pat_ident) = pt.pat.as_ref()
3879            {
3880                let ident = &pat_ident.ident;
3881                Some({
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::ToTokens::to_tokens(&ident, &mut _s);
    _s
}quote! { #ident })
3882            } else {
3883                None
3884            }
3885        })
3886        .collect();
3887    if let Some(index) = method.rng_guard {
3888        rust_args.insert(index, crate::type_inspect::rng_guard_arg());
3889    }
3890
3891    /// How a consuming (`self` by value) receiver is handled (#1432).
3892    #[derive(#[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for ValueMode { }
#[automatically_derived]
impl ::core::clone::Clone for ValueMode {
    #[inline]
    fn clone(&self) -> Self { *self }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for ValueMode { }Copy, #[automatically_derived]
impl ::core::marker::StructuralPartialEq for ValueMode { }
#[automatically_derived]
impl ::core::cmp::PartialEq for ValueMode {
    #[inline]
    fn eq(&self, other: &Self) -> bool {
        ::core::intrinsics::discriminant_value(self) ==
            ::core::intrinsics::discriminant_value(other)
    }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for ValueMode { }Eq)]
3893    enum ValueMode {
3894        /// `-> Self`: move out, call, write the result back into the same slot.
3895        WriteBack,
3896        /// `-> Result<Self, E>` / `-> Option<Self>`: call on a clone, overwrite on success.
3897        Fallible,
3898        /// Any other return: move out, call, leave the slot consumed.
3899        Terminal,
3900    }
3901    let value_mode = if method.returns_self() {
3902        ValueMode::WriteBack
3903    } else if method.returns_result_self() || method.returns_option_self() {
3904        ValueMode::Fallible
3905    } else {
3906        ValueMode::Terminal
3907    };
3908
3909    // Generate self extraction for instance methods. Each receiver borrows
3910    // the value's RefCell for the whole call (shared for `&self`, exclusive
3911    // otherwise), so a callback into R that reaches the same object gets the
3912    // `miniextendr_borrow_conflict` error instead of a second reference. The
3913    // receiver is resolved before the arguments are converted and borrowed
3914    // (or moved out) after: an R error that longjmps out of an argument
3915    // conversion would skip the borrow's release and leave the object borrowed.
3916    let (pre_call, post_conversion) = if method.env.is_instance() {
3917        let owner = syn::Ident::new("__mx_owner", proc_macro2::Span::mixed_site());
3918        let cell = syn::Ident::new("__mx_cell", proc_macro2::Span::mixed_site());
3919        let guard = syn::Ident::new("__mx_guard", proc_macro2::Span::mixed_site());
3920        let resolve = {
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::__private::push_ident(&mut _s, "let");
    ::quote::ToTokens::to_tokens(&owner, &mut _s);
    ::quote::__private::push_eq(&mut _s);
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "miniextendr_api");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "externalptr");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "ReceiverOwner");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "resolve");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_lt(&mut _s);
    ::quote::ToTokens::to_tokens(&type_ident, &mut _s);
    ::quote::__private::push_gt(&mut _s);
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Parenthesis,
        {
            let mut _s = ::quote::__private::TokenStream::new();
            ::quote::ToTokens::to_tokens(&self_sexp, &mut _s);
            _s
        });
    ::quote::__private::push_semi(&mut _s);
    _s
}quote! {
3921            let #owner =
3922                ::miniextendr_api::externalptr::ReceiverOwner::resolve::<#type_ident>(#self_sexp);
3923        };
3924        // The receiver's RefCell; `handle_downcast_failed` refuses a consumed
3925        // value, a pointer restored from a saved session, or another type.
3926        let cell_lookup = {
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::ToTokens::to_tokens(&resolve, &mut _s);
    ::quote::__private::push_ident(&mut _s, "let");
    ::quote::ToTokens::to_tokens(&self_ptr, &mut _s);
    ::quote::__private::push_eq(&mut _s);
    ::quote::__private::push_ident(&mut _s, "unsafe");
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Brace,
        {
            let mut _s = ::quote::__private::TokenStream::new();
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "miniextendr_api");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "externalptr");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "ErasedExternalPtr");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "from_sexp");
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::ToTokens::to_tokens(&owner, &mut _s);
                    ::quote::__private::push_dot(&mut _s);
                    ::quote::__private::push_ident(&mut _s, "get");
                    ::quote::__private::push_group(&mut _s,
                        ::quote::__private::Delimiter::Parenthesis,
                        ::quote::__private::TokenStream::new());
                    _s
                });
            _s
        });
    ::quote::__private::push_semi(&mut _s);
    ::quote::__private::push_ident(&mut _s, "let");
    ::quote::ToTokens::to_tokens(&cell, &mut _s);
    ::quote::__private::push_eq(&mut _s);
    ::quote::__private::push_ident(&mut _s, "match");
    ::quote::ToTokens::to_tokens(&self_ptr, &mut _s);
    ::quote::__private::push_dot(&mut _s);
    ::quote::__private::push_ident(&mut _s, "downcast_cell");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_lt(&mut _s);
    ::quote::ToTokens::to_tokens(&type_ident, &mut _s);
    ::quote::__private::push_gt(&mut _s);
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Parenthesis,
        ::quote::__private::TokenStream::new());
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Brace,
        {
            let mut _s = ::quote::__private::TokenStream::new();
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "core");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "option");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "Option");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "Some");
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::__private::push_ident(&mut _s, "cell");
                    _s
                });
            ::quote::__private::push_fat_arrow(&mut _s);
            ::quote::__private::push_ident(&mut _s, "cell");
            ::quote::__private::push_comma(&mut _s);
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "core");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "option");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "Option");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "None");
            ::quote::__private::push_fat_arrow(&mut _s);
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "miniextendr_api");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "externalptr");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "handle_downcast_failed");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_lt(&mut _s);
            ::quote::ToTokens::to_tokens(&type_ident, &mut _s);
            ::quote::__private::push_gt(&mut _s);
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::__private::push_and(&mut _s);
                    ::quote::ToTokens::to_tokens(&self_ptr, &mut _s);
                    _s
                });
            ::quote::__private::push_comma(&mut _s);
            _s
        });
    ::quote::__private::push_semi(&mut _s);
    _s
}quote! {
3927            #resolve
3928            let #self_ptr = unsafe {
3929                ::miniextendr_api::externalptr::ErasedExternalPtr::from_sexp(#owner.get())
3930            };
3931            let #cell = match #self_ptr.downcast_cell::<#type_ident>() {
3932                ::core::option::Option::Some(cell) => cell,
3933                ::core::option::Option::None => ::miniextendr_api::externalptr::handle_downcast_failed::<#type_ident>(&#self_ptr),
3934            };
3935        };
3936        let (lookup, borrow) = match method.env {
3937            ReceiverKind::RefMut => (
3938                cell_lookup,
3939                {
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::__private::push_ident(&mut _s, "let");
    ::quote::__private::push_ident(&mut _s, "mut");
    ::quote::ToTokens::to_tokens(&guard, &mut _s);
    ::quote::__private::push_eq(&mut _s);
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "miniextendr_api");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "externalptr");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "borrow_value_mut");
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Parenthesis,
        {
            let mut _s = ::quote::__private::TokenStream::new();
            ::quote::ToTokens::to_tokens(&cell, &mut _s);
            _s
        });
    ::quote::__private::push_semi(&mut _s);
    ::quote::__private::push_ident(&mut _s, "let");
    ::quote::ToTokens::to_tokens(&self_ref, &mut _s);
    ::quote::__private::push_eq(&mut _s);
    ::quote::__private::push_and(&mut _s);
    ::quote::__private::push_ident(&mut _s, "mut");
    ::quote::__private::push_star(&mut _s);
    ::quote::ToTokens::to_tokens(&guard, &mut _s);
    ::quote::__private::push_semi(&mut _s);
    _s
}quote! {
3940                    let mut #guard = ::miniextendr_api::externalptr::borrow_value_mut(#cell);
3941                    let #self_ref = &mut *#guard;
3942                },
3943            ),
3944            ReceiverKind::Ref => (
3945                cell_lookup,
3946                {
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::__private::push_ident(&mut _s, "let");
    ::quote::ToTokens::to_tokens(&guard, &mut _s);
    ::quote::__private::push_eq(&mut _s);
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "miniextendr_api");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "externalptr");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "borrow_value");
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Parenthesis,
        {
            let mut _s = ::quote::__private::TokenStream::new();
            ::quote::ToTokens::to_tokens(&cell, &mut _s);
            _s
        });
    ::quote::__private::push_semi(&mut _s);
    ::quote::__private::push_ident(&mut _s, "let");
    ::quote::ToTokens::to_tokens(&self_ref, &mut _s);
    ::quote::__private::push_eq(&mut _s);
    ::quote::__private::push_and(&mut _s);
    ::quote::__private::push_star(&mut _s);
    ::quote::ToTokens::to_tokens(&guard, &mut _s);
    ::quote::__private::push_semi(&mut _s);
    _s
}quote! {
3947                    let #guard = ::miniextendr_api::externalptr::borrow_value(#cell);
3948                    let #self_ref = &*#guard;
3949                },
3950            ),
3951            ReceiverKind::Value => match value_mode {
3952                // `self -> Result<Self, E>` / `Option<Self>`: keep the stored
3953                // value; the call runs on a clone and the slot is overwritten
3954                // only on success (`T: Clone`, see `ConsumingFallible`).
3955                ValueMode::Fallible => (
3956                    cell_lookup,
3957                    {
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::__private::push_ident(&mut _s, "let");
    ::quote::__private::push_ident(&mut _s, "mut");
    ::quote::ToTokens::to_tokens(&self_slot, &mut _s);
    ::quote::__private::push_eq(&mut _s);
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "miniextendr_api");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "externalptr");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "borrow_value_mut");
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Parenthesis,
        {
            let mut _s = ::quote::__private::TokenStream::new();
            ::quote::ToTokens::to_tokens(&cell, &mut _s);
            _s
        });
    ::quote::__private::push_semi(&mut _s);
    _s
}quote! {
3958                        let mut #self_slot = ::miniextendr_api::externalptr::borrow_value_mut(#cell);
3959                    },
3960                ),
3961                // `self -> Self` and terminal `self -> T`: move the value out,
3962                // leaving the `ConsumedSlot` marker until (and unless) the
3963                // result is written back. A call that reaches the object
3964                // meanwhile finds it consumed.
3965                ValueMode::WriteBack | ValueMode::Terminal => (
3966                    {
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::ToTokens::to_tokens(&resolve, &mut _s);
    ::quote::__private::push_ident(&mut _s, "let");
    ::quote::__private::push_ident(&mut _s, "mut");
    ::quote::ToTokens::to_tokens(&self_ptr, &mut _s);
    ::quote::__private::push_eq(&mut _s);
    ::quote::__private::push_ident(&mut _s, "unsafe");
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Brace,
        {
            let mut _s = ::quote::__private::TokenStream::new();
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "miniextendr_api");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "externalptr");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "ErasedExternalPtr");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "from_sexp");
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::ToTokens::to_tokens(&owner, &mut _s);
                    ::quote::__private::push_dot(&mut _s);
                    ::quote::__private::push_ident(&mut _s, "get");
                    ::quote::__private::push_group(&mut _s,
                        ::quote::__private::Delimiter::Parenthesis,
                        ::quote::__private::TokenStream::new());
                    _s
                });
            _s
        });
    ::quote::__private::push_semi(&mut _s);
    _s
}quote! {
3967                        #resolve
3968                        let mut #self_ptr = unsafe {
3969                            ::miniextendr_api::externalptr::ErasedExternalPtr::from_sexp(#owner.get())
3970                        };
3971                    },
3972                    {
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::__private::push_ident(&mut _s, "let");
    ::quote::ToTokens::to_tokens(&taken, &mut _s);
    ::quote::__private::push_eq(&mut _s);
    ::quote::__private::push_ident(&mut _s, "match");
    ::quote::ToTokens::to_tokens(&self_ptr, &mut _s);
    ::quote::__private::push_dot(&mut _s);
    ::quote::__private::push_ident(&mut _s, "take_for_consuming");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_lt(&mut _s);
    ::quote::ToTokens::to_tokens(&type_ident, &mut _s);
    ::quote::__private::push_gt(&mut _s);
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Parenthesis,
        ::quote::__private::TokenStream::new());
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Brace,
        {
            let mut _s = ::quote::__private::TokenStream::new();
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "core");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "option");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "Option");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "Some");
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::__private::push_ident(&mut _s, "v");
                    _s
                });
            ::quote::__private::push_fat_arrow(&mut _s);
            ::quote::__private::push_ident(&mut _s, "v");
            ::quote::__private::push_comma(&mut _s);
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "core");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "option");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "Option");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "None");
            ::quote::__private::push_fat_arrow(&mut _s);
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "miniextendr_api");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "externalptr");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "handle_downcast_failed");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_lt(&mut _s);
            ::quote::ToTokens::to_tokens(&type_ident, &mut _s);
            ::quote::__private::push_gt(&mut _s);
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::__private::push_and(&mut _s);
                    ::quote::ToTokens::to_tokens(&self_ptr, &mut _s);
                    _s
                });
            ::quote::__private::push_comma(&mut _s);
            _s
        });
    ::quote::__private::push_semi(&mut _s);
    _s
}quote! {
3973                        let #taken = match #self_ptr.take_for_consuming::<#type_ident>() {
3974                            ::core::option::Option::Some(v) => v,
3975                            ::core::option::Option::None => ::miniextendr_api::externalptr::handle_downcast_failed::<#type_ident>(&#self_ptr),
3976                        };
3977                    },
3978                ),
3979            },
3980            // A view receiver holds the borrow itself, with the R object
3981            // the receiver resolved to (rooted by `#owner` for the call).
3982            ReceiverKind::ExternalPtrRef => (
3983                cell_lookup,
3984                {
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::__private::push_ident(&mut _s, "let");
    ::quote::ToTokens::to_tokens(&self_ref, &mut _s);
    ::quote::__private::push_eq(&mut _s);
    ::quote::__private::push_ident(&mut _s, "unsafe");
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Brace,
        {
            let mut _s = ::quote::__private::TokenStream::new();
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "miniextendr_api");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "ExternalPtrRef");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_lt(&mut _s);
            ::quote::ToTokens::to_tokens(&type_ident, &mut _s);
            ::quote::__private::push_gt(&mut _s);
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "from_raw_parts");
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::ToTokens::to_tokens(&owner, &mut _s);
                    ::quote::__private::push_dot(&mut _s);
                    ::quote::__private::push_ident(&mut _s, "owner");
                    ::quote::__private::push_group(&mut _s,
                        ::quote::__private::Delimiter::Parenthesis,
                        ::quote::__private::TokenStream::new());
                    ::quote::__private::push_comma(&mut _s);
                    ::quote::ToTokens::to_tokens(&cell, &mut _s);
                    ::quote::__private::push_comma(&mut _s);
                    _s
                });
            _s
        });
    ::quote::__private::push_semi(&mut _s);
    _s
}quote! {
3985                    let #self_ref = unsafe {
3986                        ::miniextendr_api::ExternalPtrRef::<#type_ident>::from_raw_parts(
3987                            #owner.owner(),
3988                            #cell,
3989                        )
3990                    };
3991                },
3992            ),
3993            ReceiverKind::ExternalPtrMut => (
3994                cell_lookup,
3995                {
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::__private::push_ident(&mut _s, "let");
    ::quote::ToTokens::to_tokens(&self_ref, &mut _s);
    ::quote::__private::push_eq(&mut _s);
    ::quote::__private::push_ident(&mut _s, "unsafe");
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Brace,
        {
            let mut _s = ::quote::__private::TokenStream::new();
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "miniextendr_api");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "ExternalPtrMut");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_lt(&mut _s);
            ::quote::ToTokens::to_tokens(&type_ident, &mut _s);
            ::quote::__private::push_gt(&mut _s);
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "from_raw_parts");
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::ToTokens::to_tokens(&owner, &mut _s);
                    ::quote::__private::push_dot(&mut _s);
                    ::quote::__private::push_ident(&mut _s, "owner");
                    ::quote::__private::push_group(&mut _s,
                        ::quote::__private::Delimiter::Parenthesis,
                        ::quote::__private::TokenStream::new());
                    ::quote::__private::push_comma(&mut _s);
                    ::quote::ToTokens::to_tokens(&cell, &mut _s);
                    ::quote::__private::push_comma(&mut _s);
                    _s
                });
            _s
        });
    ::quote::__private::push_semi(&mut _s);
    _s
}quote! {
3996                    let #self_ref = unsafe {
3997                        ::miniextendr_api::ExternalPtrMut::<#type_ident>::from_raw_parts(
3998                            #owner.owner(),
3999                            #cell,
4000                        )
4001                    };
4002                },
4003            ),
4004            // The handle receiver takes no borrow: the body reads through
4005            // `borrow()` / `borrow_mut()`, or consumes the handle.
4006            ReceiverKind::ExternalPtrValue => (
4007                {
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::ToTokens::to_tokens(&resolve, &mut _s);
    ::quote::__private::push_ident(&mut _s, "let");
    ::quote::ToTokens::to_tokens(&external_ptr, &mut _s);
    ::quote::__private::push_eq(&mut _s);
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "miniextendr_api");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "externalptr");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "wrap_receiver");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_lt(&mut _s);
    ::quote::ToTokens::to_tokens(&type_ident, &mut _s);
    ::quote::__private::push_gt(&mut _s);
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Parenthesis,
        {
            let mut _s = ::quote::__private::TokenStream::new();
            ::quote::ToTokens::to_tokens(&owner, &mut _s);
            ::quote::__private::push_dot(&mut _s);
            ::quote::__private::push_ident(&mut _s, "get");
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                ::quote::__private::TokenStream::new());
            _s
        });
    ::quote::__private::push_semi(&mut _s);
    _s
}quote! {
4008                    #resolve
4009                    let #external_ptr =
4010                        ::miniextendr_api::externalptr::wrap_receiver::<#type_ident>(#owner.get());
4011                },
4012                TokenStream::new(),
4013            ),
4014            _ => ::core::panicking::panic("internal error: entered unreachable code")unreachable!(),
4015        };
4016        (::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [lookup]))vec![lookup], ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [borrow]))vec![borrow])
4017    } else {
4018        (::alloc::vec::Vec::new()vec![], ::alloc::vec::Vec::new()vec![])
4019    };
4020
4021    // Fallible in-place builders (#1433): `&mut self -> Result<&mut Self, E>` /
4022    // `Option<&mut Self>` (and the `&self` forms). The returned borrow is
4023    // dropped and the wrapper returns the same handle on success.
4024    let fallible_self_ref = method.env.is_instance()
4025        && !#[allow(non_exhaustive_omitted_patterns)] match method.env {
    ReceiverKind::Value => true,
    _ => false,
}matches!(method.env, ReceiverKind::Value)
4026        && (method.returns_result_self_ref() || method.returns_option_self_ref());
4027
4028    // Generate call expression. A visibility marker (`Invisible<T>` /
4029    // `Visible<T>`, #1213) is a transparent newtype around the real return:
4030    // unwrap it (`.0`) before the conversion, which was analysed on the inner
4031    // type (`method.sig.output` is already peeled).
4032    let unwrap_marker = |call: TokenStream| -> TokenStream {
4033        let call = crate::type_inspect::prepare_return_value(
4034            call,
4035            method.visibility_marker,
4036            method.method_attrs.serialize,
4037        );
4038        match &method.return_wrap {
4039            Some(wrap) => wrap.prepare_value(call),
4040            None => call,
4041        }
4042    };
4043    let call_expr = match method.env {
4044        ReceiverKind::Ref | ReceiverKind::RefMut if fallible_self_ref => {
4045            let call = unwrap_marker({
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::ToTokens::to_tokens(&self_ref, &mut _s);
    ::quote::__private::push_dot(&mut _s);
    ::quote::ToTokens::to_tokens(&method_ident, &mut _s);
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Parenthesis,
        {
            let mut _s = ::quote::__private::TokenStream::new();
            {
                use ::quote::__private::ext::*;
                let mut _first = true;
                let has_iter = ::quote::__private::HasIterator::<false>;
                #[allow(unused_mut)]
                let (mut rust_args, i) = rust_args.quote_into_iter();
                let has_iter = has_iter | i;
                <_ as
                        ::quote::__private::CheckHasIterator<true>>::check(has_iter);
                while true {
                    let rust_args =
                        match rust_args.next() {
                            Some(_x) => ::quote::__private::RepInterp(_x),
                            None => break,
                        };
                    if !_first { ::quote::__private::push_comma(&mut _s); }
                    _first = false;
                    ::quote::ToTokens::to_tokens(&rust_args, &mut _s);
                }
            }
            _s
        });
    _s
}quote! { #self_ref.#method_ident(#(#rust_args),*) });
4046            {
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::ToTokens::to_tokens(&call, &mut _s);
    ::quote::__private::push_dot(&mut _s);
    ::quote::__private::push_ident(&mut _s, "map");
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Parenthesis,
        {
            let mut _s = ::quote::__private::TokenStream::new();
            ::quote::__private::push_or(&mut _s);
            ::quote::__private::push_underscore(&mut _s);
            ::quote::__private::push_or(&mut _s);
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                ::quote::__private::TokenStream::new());
            _s
        });
    _s
}quote! { #call.map(|_| ()) }
4047        }
4048        ReceiverKind::Ref | ReceiverKind::RefMut => {
4049            unwrap_marker({
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::ToTokens::to_tokens(&self_ref, &mut _s);
    ::quote::__private::push_dot(&mut _s);
    ::quote::ToTokens::to_tokens(&method_ident, &mut _s);
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Parenthesis,
        {
            let mut _s = ::quote::__private::TokenStream::new();
            {
                use ::quote::__private::ext::*;
                let mut _first = true;
                let has_iter = ::quote::__private::HasIterator::<false>;
                #[allow(unused_mut)]
                let (mut rust_args, i) = rust_args.quote_into_iter();
                let has_iter = has_iter | i;
                <_ as
                        ::quote::__private::CheckHasIterator<true>>::check(has_iter);
                while true {
                    let rust_args =
                        match rust_args.next() {
                            Some(_x) => ::quote::__private::RepInterp(_x),
                            None => break,
                        };
                    if !_first { ::quote::__private::push_comma(&mut _s); }
                    _first = false;
                    ::quote::ToTokens::to_tokens(&rust_args, &mut _s);
                }
            }
            _s
        });
    _s
}quote! { #self_ref.#method_ident(#(#rust_args),*) })
4050        }
4051        ReceiverKind::Value => match value_mode {
4052            ValueMode::WriteBack => {
4053                let call =
4054                    unwrap_marker({
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::ToTokens::to_tokens(&type_ident, &mut _s);
    ::quote::__private::push_colon2(&mut _s);
    ::quote::ToTokens::to_tokens(&method_ident, &mut _s);
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Parenthesis,
        {
            let mut _s = ::quote::__private::TokenStream::new();
            ::quote::ToTokens::to_tokens(&taken, &mut _s);
            ::quote::__private::push_comma(&mut _s);
            {
                use ::quote::__private::ext::*;
                let mut _first = true;
                let has_iter = ::quote::__private::HasIterator::<false>;
                #[allow(unused_mut)]
                let (mut rust_args, i) = rust_args.quote_into_iter();
                let has_iter = has_iter | i;
                <_ as
                        ::quote::__private::CheckHasIterator<true>>::check(has_iter);
                while true {
                    let rust_args =
                        match rust_args.next() {
                            Some(_x) => ::quote::__private::RepInterp(_x),
                            None => break,
                        };
                    if !_first { ::quote::__private::push_comma(&mut _s); }
                    _first = false;
                    ::quote::ToTokens::to_tokens(&rust_args, &mut _s);
                }
            }
            _s
        });
    _s
}quote! { #type_ident::#method_ident(#taken, #(#rust_args),*) });
4055                {
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Brace,
        {
            let mut _s = ::quote::__private::TokenStream::new();
            ::quote::__private::push_ident(&mut _s, "let");
            ::quote::__private::push_ident(&mut _s, "__mx_out");
            ::quote::__private::push_eq(&mut _s);
            ::quote::ToTokens::to_tokens(&call, &mut _s);
            ::quote::__private::push_semi(&mut _s);
            ::quote::ToTokens::to_tokens(&self_ptr, &mut _s);
            ::quote::__private::push_dot(&mut _s);
            ::quote::__private::push_ident(&mut _s,
                "restore_after_consuming");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_lt(&mut _s);
            ::quote::ToTokens::to_tokens(&type_ident, &mut _s);
            ::quote::__private::push_gt(&mut _s);
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::__private::push_ident(&mut _s, "__mx_out");
                    _s
                });
            ::quote::__private::push_semi(&mut _s);
            _s
        });
    _s
}quote! {{
4056                    let __mx_out = #call;
4057                    #self_ptr.restore_after_consuming::<#type_ident>(__mx_out);
4058                }}
4059            }
4060            ValueMode::Fallible => {
4061                let call = unwrap_marker({
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::ToTokens::to_tokens(&type_ident, &mut _s);
    ::quote::__private::push_colon2(&mut _s);
    ::quote::ToTokens::to_tokens(&method_ident, &mut _s);
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Parenthesis,
        {
            let mut _s = ::quote::__private::TokenStream::new();
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "miniextendr_api");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "externalptr");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "clone_for_consuming");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_lt(&mut _s);
            ::quote::ToTokens::to_tokens(&type_ident, &mut _s);
            ::quote::__private::push_gt(&mut _s);
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::__private::push_and(&mut _s);
                    ::quote::__private::push_star(&mut _s);
                    ::quote::ToTokens::to_tokens(&self_slot, &mut _s);
                    _s
                });
            ::quote::__private::push_comma(&mut _s);
            {
                use ::quote::__private::ext::*;
                let mut _first = true;
                let has_iter = ::quote::__private::HasIterator::<false>;
                #[allow(unused_mut)]
                let (mut rust_args, i) = rust_args.quote_into_iter();
                let has_iter = has_iter | i;
                <_ as
                        ::quote::__private::CheckHasIterator<true>>::check(has_iter);
                while true {
                    let rust_args =
                        match rust_args.next() {
                            Some(_x) => ::quote::__private::RepInterp(_x),
                            None => break,
                        };
                    if !_first { ::quote::__private::push_comma(&mut _s); }
                    _first = false;
                    ::quote::ToTokens::to_tokens(&rust_args, &mut _s);
                }
            }
            _s
        });
    _s
}quote! {
4062                    #type_ident::#method_ident(
4063                        ::miniextendr_api::externalptr::clone_for_consuming::<#type_ident>(&*#self_slot),
4064                        #(#rust_args),*
4065                    )
4066                });
4067                {
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::ToTokens::to_tokens(&call, &mut _s);
    ::quote::__private::push_dot(&mut _s);
    ::quote::__private::push_ident(&mut _s, "map");
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Parenthesis,
        {
            let mut _s = ::quote::__private::TokenStream::new();
            ::quote::__private::push_or(&mut _s);
            ::quote::__private::push_ident(&mut _s, "__mx_v");
            ::quote::__private::push_or(&mut _s);
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Brace,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::__private::push_star(&mut _s);
                    ::quote::ToTokens::to_tokens(&self_slot, &mut _s);
                    ::quote::__private::push_eq(&mut _s);
                    ::quote::__private::push_ident(&mut _s, "__mx_v");
                    ::quote::__private::push_semi(&mut _s);
                    _s
                });
            _s
        });
    _s
}quote! { #call.map(|__mx_v| { *#self_slot = __mx_v; }) }
4068            }
4069            ValueMode::Terminal => {
4070                unwrap_marker({
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::ToTokens::to_tokens(&type_ident, &mut _s);
    ::quote::__private::push_colon2(&mut _s);
    ::quote::ToTokens::to_tokens(&method_ident, &mut _s);
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Parenthesis,
        {
            let mut _s = ::quote::__private::TokenStream::new();
            ::quote::ToTokens::to_tokens(&taken, &mut _s);
            ::quote::__private::push_comma(&mut _s);
            {
                use ::quote::__private::ext::*;
                let mut _first = true;
                let has_iter = ::quote::__private::HasIterator::<false>;
                #[allow(unused_mut)]
                let (mut rust_args, i) = rust_args.quote_into_iter();
                let has_iter = has_iter | i;
                <_ as
                        ::quote::__private::CheckHasIterator<true>>::check(has_iter);
                while true {
                    let rust_args =
                        match rust_args.next() {
                            Some(_x) => ::quote::__private::RepInterp(_x),
                            None => break,
                        };
                    if !_first { ::quote::__private::push_comma(&mut _s); }
                    _first = false;
                    ::quote::ToTokens::to_tokens(&rust_args, &mut _s);
                }
            }
            _s
        });
    _s
}quote! { #type_ident::#method_ident(#taken, #(#rust_args),*) })
4071            }
4072        },
4073        ReceiverKind::ExternalPtrRef | ReceiverKind::ExternalPtrMut => {
4074            unwrap_marker({
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::ToTokens::to_tokens(&type_ident, &mut _s);
    ::quote::__private::push_colon2(&mut _s);
    ::quote::ToTokens::to_tokens(&method_ident, &mut _s);
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Parenthesis,
        {
            let mut _s = ::quote::__private::TokenStream::new();
            ::quote::ToTokens::to_tokens(&self_ref, &mut _s);
            ::quote::__private::push_comma(&mut _s);
            {
                use ::quote::__private::ext::*;
                let mut _first = true;
                let has_iter = ::quote::__private::HasIterator::<false>;
                #[allow(unused_mut)]
                let (mut rust_args, i) = rust_args.quote_into_iter();
                let has_iter = has_iter | i;
                <_ as
                        ::quote::__private::CheckHasIterator<true>>::check(has_iter);
                while true {
                    let rust_args =
                        match rust_args.next() {
                            Some(_x) => ::quote::__private::RepInterp(_x),
                            None => break,
                        };
                    if !_first { ::quote::__private::push_comma(&mut _s); }
                    _first = false;
                    ::quote::ToTokens::to_tokens(&rust_args, &mut _s);
                }
            }
            _s
        });
    _s
}quote! { #type_ident::#method_ident(#self_ref, #(#rust_args),*) })
4075        }
4076        ReceiverKind::ExternalPtrValue => {
4077            unwrap_marker({
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::ToTokens::to_tokens(&type_ident, &mut _s);
    ::quote::__private::push_colon2(&mut _s);
    ::quote::ToTokens::to_tokens(&method_ident, &mut _s);
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Parenthesis,
        {
            let mut _s = ::quote::__private::TokenStream::new();
            ::quote::ToTokens::to_tokens(&external_ptr, &mut _s);
            ::quote::__private::push_comma(&mut _s);
            {
                use ::quote::__private::ext::*;
                let mut _first = true;
                let has_iter = ::quote::__private::HasIterator::<false>;
                #[allow(unused_mut)]
                let (mut rust_args, i) = rust_args.quote_into_iter();
                let has_iter = has_iter | i;
                <_ as
                        ::quote::__private::CheckHasIterator<true>>::check(has_iter);
                while true {
                    let rust_args =
                        match rust_args.next() {
                            Some(_x) => ::quote::__private::RepInterp(_x),
                            None => break,
                        };
                    if !_first { ::quote::__private::push_comma(&mut _s); }
                    _first = false;
                    ::quote::ToTokens::to_tokens(&rust_args, &mut _s);
                }
            }
            _s
        });
    _s
}quote! { #type_ident::#method_ident(#external_ptr, #(#rust_args),*) })
4078        }
4079        ReceiverKind::None => unwrap_marker({
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::ToTokens::to_tokens(&type_ident, &mut _s);
    ::quote::__private::push_colon2(&mut _s);
    ::quote::ToTokens::to_tokens(&method_ident, &mut _s);
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Parenthesis,
        {
            let mut _s = ::quote::__private::TokenStream::new();
            {
                use ::quote::__private::ext::*;
                let mut _first = true;
                let has_iter = ::quote::__private::HasIterator::<false>;
                #[allow(unused_mut)]
                let (mut rust_args, i) = rust_args.quote_into_iter();
                let has_iter = has_iter | i;
                <_ as
                        ::quote::__private::CheckHasIterator<true>>::check(has_iter);
                while true {
                    let rust_args =
                        match rust_args.next() {
                            Some(_x) => ::quote::__private::RepInterp(_x),
                            None => break,
                        };
                    if !_first { ::quote::__private::push_comma(&mut _s); }
                    _first = false;
                    ::quote::ToTokens::to_tokens(&rust_args, &mut _s);
                }
            }
            _s
        });
    _s
}quote! { #type_ident::#method_ident(#(#rust_args),*) }),
4080    };
4081
4082    // Determine return handling strategy
4083    let return_handling = if method.env == ReceiverKind::Value && value_mode == ValueMode::WriteBack
4084    {
4085        // `self -> Self`: the call expression already wrote the result back;
4086        // hand back the same handle (in-place semantics, like `&mut Self`).
4087        ReturnHandling::SelfHandle
4088    } else if (method.env == ReceiverKind::Value && value_mode == ValueMode::Fallible)
4089        || fallible_self_ref
4090    {
4091        // Fallible in-place step: the call expression yields `Result<(), E>` /
4092        // `Option<()>`; raise on failure, same handle on success.
4093        if method.returns_result_self() || method.returns_result_self_ref() {
4094            ReturnHandling::SelfHandleResult
4095        } else {
4096            ReturnHandling::SelfHandleOption
4097        }
4098    } else if method.returns_self() {
4099        ReturnHandling::ExternalPtr
4100    } else if method.returns_self_ref() && method.env.is_instance() {
4101        // In-place builder (`&mut self -> &mut Self` / `&self -> Self`): the
4102        // method mutates/borrows the receiver and returns a borrow of it. Hand
4103        // back the same ExternalPtr handle (`self_sexp`) so chaining preserves
4104        // identity with no clone. See `ReturnHandling::SelfHandle`.
4105        ReturnHandling::SelfHandle
4106    } else if method.method_attrs.unwrap_in_r
4107        && crate::return_type_analysis::output_is_result(&method.sig.output)
4108    {
4109        ReturnHandling::IntoR
4110    } else {
4111        crate::c_wrapper_builder::detect_return_handling(
4112            &method.sig.output,
4113            method.return_wrap.as_ref(),
4114        )
4115    };
4116
4117    // Build the context using the builder
4118    let mut builder = CWrapperContext::builder(method_ident.clone(), c_ident)
4119        .r_name(method.r_method_name())
4120        .inputs(method.sig.inputs.clone())
4121        .output(method.sig.output.clone())
4122        .pre_call(pre_call)
4123        .post_conversion(post_conversion)
4124        .call_expr(call_expr)
4125        .thread_strategy(thread_strategy)
4126        .return_handling(return_handling)
4127        .err_parts(crate::c_wrapper_builder::ErrPartsMode::from_spec(
4128            method
4129                .method_attrs
4130                .serde_error
4131                .as_ref()
4132                .map(|spec| spec.with_self_type(type_ident))
4133                .as_ref(),
4134        ))
4135        .cfg_attrs(parsed_impl.cfg_attrs.clone())
4136        .type_context(type_ident.clone())
4137        .formals(method.formals());
4138
4139    if method.env.is_instance() {
4140        builder = builder.has_self();
4141    }
4142
4143    if method.method_attrs.coerce {
4144        builder = builder.coerce_all();
4145    }
4146
4147    if method.method_attrs.check_interrupt {
4148        builder = builder.check_interrupt();
4149    }
4150
4151    if method.method_attrs.rng {
4152        builder = builder.rng();
4153    }
4154
4155    if parsed_impl.strict {
4156        builder = builder.strict();
4157    }
4158
4159    // Forward match_arg + several_ok parameter names so `RustConversionBuilder` swaps
4160    // in `match_arg_vec_from_sexp` for the Vec/slice/array/Box<[_]> conversion path.
4161    // Scalar match_arg doesn't need this — R's match.arg() validated the choice and
4162    // `TryFromSexp for EnumType` (auto-generated by `#[derive(MatchArg)]`) decodes it.
4163    // A choice type under `Missing` / `Option` layers (#1473, #1551) likewise
4164    // bypasses `TryFromSexp`: no `Option<UserEnum>` impl can exist downstream
4165    // (orphan rule).
4166    // `no_na(..)` parameters also have their converted value checked, for what
4167    // the type reads as `NA` beyond the R guard's `anyNA()`.
4168    for (rust_name, attrs) in &method.method_attrs.per_param {
4169        if let Some(leaf) = attrs.layered_leaf() {
4170            builder = builder.layered_choice(rust_name.clone(), leaf);
4171        } else if attrs.match_arg && attrs.several_ok {
4172            builder = builder.match_arg_several_ok(rust_name.clone());
4173        }
4174        if attrs.checks.no_na {
4175            builder = builder.no_na(rust_name.clone(), attrs.checks.no_na_message.clone());
4176        }
4177    }
4178    // `Checked<T>` / `Unchecked<T>` parameters (#1566) convert as `T` and are
4179    // wrapped back before the call.
4180    for (rust_name, markers) in &method.param_markers {
4181        builder = builder.param_markers(rust_name.clone(), markers.clone());
4182    }
4183
4184    let c_wrapper_and_def = builder.build().generate();
4185
4186    let match_arg_entries = generate_method_match_arg_entries(parsed_impl, method);
4187
4188    {
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::ToTokens::to_tokens(&c_wrapper_and_def, &mut _s);
    ::quote::ToTokens::to_tokens(&match_arg_entries, &mut _s);
    _s
}quote! {
4189        #c_wrapper_and_def
4190        #match_arg_entries
4191    }
4192}
4193
4194/// Register enum choices, aliases, defaults and parameter documentation for
4195/// write-time placeholder substitution. R calls no choice-lookup C helper.
4196fn generate_method_match_arg_entries(
4197    parsed_impl: &ParsedImpl,
4198    method: &ParsedMethod,
4199) -> TokenStream {
4200    if !method.method_attrs.per_param.values().any(|a| a.match_arg) {
4201        return TokenStream::new();
4202    }
4203
4204    let type_ident = &parsed_impl.type_ident;
4205    let c_ident = method.c_wrapper_ident(type_ident);
4206    let c_ident_str = c_ident.to_string();
4207    let cfg_attrs = &parsed_impl.cfg_attrs;
4208
4209    let mut out = TokenStream::new();
4210
4211    // Signature order, so the emitted items don't follow the map's order.
4212    for (rust_name, attrs) in crate::miniextendr_fn::per_param_in_signature_order(
4213        &method.sig.inputs,
4214        &method.method_attrs.per_param,
4215    ) {
4216        if !attrs.match_arg {
4217            continue;
4218        }
4219        // Find the parameter type from the (already-normalized) signature.
4220        let Some(param_ty) = find_param_type(&method.sig.inputs, rust_name) else {
4221            continue;
4222        };
4223        // several_ok containers (Vec<Mode>, Box<[Mode]>, [Mode; N], &[Mode]) and the
4224        // `Option<Mode>` scalar form both wrap the enum; register the enum's
4225        // CHOICES, not the wrapper's.
4226        let several_ok = attrs.several_ok;
4227        let choices_ty = crate::match_arg_choices_ty(param_ty, several_ok).clone();
4228
4229        let key = crate::match_arg_keys::param_key(rust_name);
4230
4231        // Placeholder that the write-time substitution pass replaces with the
4232        // literal `c("a", "b", ...)` default. Shape matches the standalone-fn convention.
4233        let placeholder = crate::r_class_formatter::match_arg_placeholder(&c_ident_str, &key);
4234        let entry_ident = syn::Ident::new(
4235            &::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("match_arg_choices_entry_{0}",
                crate::match_arg_keys::placeholder_ident_suffix(&placeholder)))
    })format!(
4236                "match_arg_choices_entry_{}",
4237                crate::match_arg_keys::placeholder_ident_suffix(&placeholder)
4238            ),
4239            proc_macro2::Span::call_site(),
4240        );
4241        let doc_placeholder =
4242            crate::r_class_formatter::match_arg_param_doc_placeholder(&c_ident_str, &key);
4243        let doc_entry_ident = syn::Ident::new(
4244            &::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("match_arg_param_doc_entry_{0}",
                crate::match_arg_keys::placeholder_ident_suffix(&doc_placeholder)))
    })format!(
4245                "match_arg_param_doc_entry_{}",
4246                crate::match_arg_keys::placeholder_ident_suffix(&doc_placeholder)
4247            ),
4248            proc_macro2::Span::call_site(),
4249        );
4250
4251        let preferred_default = attrs
4252            .default
4253            .as_deref()
4254            .map(crate::match_arg_keys::extract_match_arg_default)
4255            .unwrap_or_default();
4256        let aliases_placeholder = crate::match_arg_keys::aliases_placeholder(&c_ident_str, &key);
4257        let choices_entry_tokens = crate::match_arg_keys::choices_entry_tokens(
4258            cfg_attrs,
4259            &entry_ident,
4260            &placeholder,
4261            &aliases_placeholder,
4262            &choices_ty,
4263            &preferred_default,
4264        );
4265        let param_doc_entry_tokens = crate::match_arg_keys::param_doc_entry_tokens(
4266            cfg_attrs,
4267            &doc_entry_ident,
4268            &doc_placeholder,
4269            several_ok,
4270            &attrs.choice_doc_suffix(),
4271            &choices_ty,
4272        );
4273
4274        out.extend({
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::ToTokens::to_tokens(&choices_entry_tokens, &mut _s);
    ::quote::ToTokens::to_tokens(&param_doc_entry_tokens, &mut _s);
    _s
}quote! {
4275            #choices_entry_tokens
4276            #param_doc_entry_tokens
4277        });
4278    }
4279
4280    out
4281}
4282
4283/// Find a parameter's Rust type from a stripped signature by identifier name.
4284fn find_param_type<'a>(
4285    inputs: &'a syn::punctuated::Punctuated<syn::FnArg, ::syn::token::Commasyn::Token![,]>,
4286    name: &str,
4287) -> Option<&'a syn::Type> {
4288    for arg in inputs {
4289        if let syn::FnArg::Typed(pt) = arg
4290            && let syn::Pat::Ident(pat_ident) = pt.pat.as_ref()
4291            && crate::naming::unraw(&pat_ident.ident) == name
4292        {
4293            return Some(pt.ty.as_ref());
4294        }
4295    }
4296    None
4297}
4298
4299/// Returns true if a vctrs constructor's return type is `Self`, `&Self`, `&mut Self`,
4300/// the named impl type, `Box<Self>`, `Result<Self, _>`, or `Result<NamedType, _>`.
4301///
4302/// These are all invalid for a vctrs constructor because the generated R wrapper
4303/// passes the return value to `vctrs::new_vctr()` / `new_rcrd()` / `new_list_of()`,
4304/// which require a plain vector payload — not an `ExternalPtr` (`EXTPTRSXP`).
4305fn vctrs_ctor_returns_self_or_type(output: &syn::ReturnType, type_ident: &syn::Ident) -> bool {
4306    let syn::ReturnType::Type(_, ty) = output else {
4307        return false;
4308    };
4309    ty_is_self_or_named(ty.as_ref(), type_ident)
4310}
4311
4312/// Recursively checks whether `ty` is `Self`, `&Self`, `&mut Self`, `Box<Self>`,
4313/// the named type, or `Result<(Self | NamedType), _>`.
4314fn ty_is_self_or_named(ty: &syn::Type, type_ident: &syn::Ident) -> bool {
4315    match ty {
4316        syn::Type::Path(p) => {
4317            let last = match p.path.segments.last() {
4318                Some(s) => s,
4319                None => return false,
4320            };
4321            // Plain `Self` or `TypeName`
4322            if last.ident == "Self" || last.ident == *type_ident {
4323                return true;
4324            }
4325            // `Result<Self, _>` or `Result<TypeName, _>`
4326            if last.ident == "Result"
4327                && let syn::PathArguments::AngleBracketed(ref args) = last.arguments
4328                && let Some(syn::GenericArgument::Type(first_ty)) = args.args.first()
4329            {
4330                return ty_is_self_or_named(first_ty, type_ident);
4331            }
4332            // `Box<Self>` or `Box<TypeName>`
4333            if last.ident == "Box"
4334                && let syn::PathArguments::AngleBracketed(ref args) = last.arguments
4335                && let Some(syn::GenericArgument::Type(inner)) = args.args.first()
4336            {
4337                return ty_is_self_or_named(inner, type_ident);
4338            }
4339            false
4340        }
4341        // `&Self` or `&mut Self`
4342        syn::Type::Reference(r) => ty_is_self_or_named(r.elem.as_ref(), type_ident),
4343        _ => false,
4344    }
4345}
4346
4347// region: Class-system R wrapper generators (sub-modules)
4348
4349/// Environment-based class wrapper generator (`obj$method()` dispatch).
4350mod env_class;
4351/// `$` / `[[` field methods of `r_data_accessors` for S3, S4 and env classes (#1848).
4352mod field_syntax;
4353/// R6 class wrapper generator (`R6Class` with `$new()`, active bindings, private methods).
4354mod r6_class;
4355/// S3 class wrapper generator (`structure()` + `generic.Class` dispatch).
4356mod s3_class;
4357/// S4 class wrapper generator (`setClass` / `setMethod` formal OOP).
4358pub(crate) mod s4_class;
4359/// S7 class wrapper generator (`new_class` / `new_generic` modern R OOP).
4360pub(crate) mod s7_class;
4361/// vctrs-compatible class wrapper generator (`new_vctr` / `new_rcrd` / `new_list_of`).
4362mod vctrs_class;
4363
4364pub(crate) use env_class::generate_env_class;
4365#[cfg(test)]
4366pub(crate) use env_class::generate_env_r_wrapper;
4367pub(crate) use r6_class::generate_r6_class;
4368#[cfg(test)]
4369pub(crate) use r6_class::generate_r6_r_wrapper;
4370pub(crate) use s3_class::generate_s3_class;
4371#[cfg(test)]
4372pub(crate) use s3_class::generate_s3_r_wrapper;
4373pub(crate) use s4_class::generate_s4_class;
4374#[cfg(test)]
4375pub(crate) use s4_class::generate_s4_r_wrapper;
4376pub(crate) use s7_class::generate_s7_class;
4377#[cfg(test)]
4378pub(crate) use s7_class::generate_s7_r_wrapper;
4379#[cfg(test)]
4380use s7_class::rust_type_to_s7_class;
4381pub(crate) use vctrs_class::generate_vctrs_class;
4382#[cfg(test)]
4383pub(crate) use vctrs_class::generate_vctrs_r_wrapper;
4384
4385/// The first line of R6's `initialize` and S7's `constructor` for a class
4386/// without a constructor: called without a `.ptr`, they raise an error
4387/// rather than build an object holding no pointer. The factories and other
4388/// classes' methods always pass the `.ptr`.
4389pub(crate) fn no_constructor_guard(class_name: &str) -> String {
4390    ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("if (is.null(.ptr)) stop(\"`{0}` has no constructor: its objects come from the functions that return one.\", call. = FALSE)",
                class_name))
    })format!(
4391        "if (is.null(.ptr)) stop(\"`{class_name}` has no constructor: its objects come from \
4392         the functions that return one.\", call. = FALSE)"
4393    )
4394}
4395
4396/// The line of [`ClassWrapper::head`] where the class's roxygen block takes
4397/// the [`ClassWrapper::class_doc_lines`] of every impl block of the type.
4398/// Spelled the same as `miniextendr_api::registry::CLASS_DOC_LINES_MARKER`,
4399/// which the wrapper writer replaces.
4400pub(crate) const CLASS_DOC_LINES_MARKER: &str = ".__MX_CLASS_DOC_LINES__";
4401
4402/// The line of [`ClassWrapper::head`] where the class's roxygen block takes
4403/// the [`ClassWrapper::class_docs`] of every impl block of the type, the
4404/// defining block's first. Spelled the same as
4405/// `miniextendr_api::registry::CLASS_DOCS_MARKER`.
4406pub(crate) const CLASS_DOCS_MARKER: &str = ".__MX_CLASS_DOCS__";
4407
4408/// Opens a roxygen line of a class's roxygen block that a doc tag written on
4409/// another impl block of the type replaces: a generated `@title`, `@name`,
4410/// `@rdname`, `@export`, constructor `@param` or R6 `@field`. The wrapper
4411/// writer drops the line when another block's [`ClassWrapper::class_docs`]
4412/// have the tag (for `@param` / `@field`, for the same name), and otherwise
4413/// keeps it without the prefix. Spelled the same as
4414/// `miniextendr_api::registry::CLASS_DEFAULT_PREFIX`.
4415pub(crate) const CLASS_DEFAULT_PREFIX: &str = ".__MX_CLASS_DEFAULT__";
4416
4417/// A generated member-documentation default resolved when the wrapper writer
4418/// knows the defining impl block's help topic (#2068). Unlike class defaults,
4419/// these belong to individual member blocks: an inherited author topic replaces
4420/// their page and drops generated parameter/structural prose, never author tags.
4421/// Spelled the same as `miniextendr_api::registry::CLASS_MEMBER_DEFAULT_PREFIX`.
4422pub(crate) const CLASS_MEMBER_DEFAULT_PREFIX: &str = ".__MX_CLASS_MEMBER_DEFAULT__";
4423
4424pub(crate) fn member_default(line: impl std::fmt::Display) -> String {
4425    ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0}{1}",
                CLASS_MEMBER_DEFAULT_PREFIX, line))
    })format!("{CLASS_MEMBER_DEFAULT_PREFIX}{line}")
4426}
4427
4428/// A companion block follows a method's explicit page; otherwise its page is
4429/// inherited from the defining impl at wrapper-write time.
4430pub(crate) fn push_member_rdname(lines: &mut Vec<String>, tags: &[String], class: &str) {
4431    if crate::roxygen::describe_in_topic(tags).is_some()
4432        || crate::roxygen::rdname_value(tags).is_some()
4433    {
4434        crate::roxygen::push_rdname_or_default(lines, tags, class);
4435    } else {
4436        lines.push(member_default(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("#\' @rdname {0}", class))
    })format!("#' @rdname {class}")));
4437    }
4438}
4439
4440/// `line` as a generated default of the class's roxygen block
4441/// ([`CLASS_DEFAULT_PREFIX`]).
4442pub(crate) fn class_default(line: impl std::fmt::Display) -> String {
4443    ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0}{1}", CLASS_DEFAULT_PREFIX,
                line))
    })format!("{CLASS_DEFAULT_PREFIX}{line}")
4444}
4445
4446/// Opens a class's roxygen block that only carries the impl docs
4447/// ([`docs_only_class_block`]). The wrapper writer writes the block, up to its
4448/// closing `NULL`, only when the type's impl docs or member-derived class docs
4449/// say something, so a readers-only impl adds no page (#1918). Spelled the
4450/// same as `miniextendr_api::registry::CLASS_DOCS_ONLY_MARKER`.
4451pub(crate) const CLASS_DOCS_ONLY_MARKER: &str = ".__MX_CLASS_DOCS_ONLY__";
4452
4453/// The roxygen block of an S3 or vctrs class without a constructor that the
4454/// package names and builds itself (`class = "..."`, #1918), documenting no
4455/// object: it carries the impl docs.
4456///
4457/// - On the impl's `@rdname` topic, a page the package documents itself, it
4458///   adds no alias and no title, and sorts after the topic's own blocks
4459///   ([`crate::roxygen::ORDER_AFTER_TOPIC_BLOCKS`]), so their name wins.
4460/// - Otherwise its page is named for the class, aliased by it, and titled by
4461///   default (`kind`, the class system in the title) unless the impl docs
4462///   give a `@title`.
4463///
4464/// Ends with `NULL`, before which [`ClassWrapper::mark_class_doc_lines`]
4465/// goes; the writer drops the block when there is nothing to say
4466/// ([`CLASS_DOCS_ONLY_MARKER`]).
4467pub(crate) fn docs_only_class_block(parsed_impl: &ParsedImpl, kind: &str) -> Vec<String> {
4468    let class_name = parsed_impl.class_name();
4469    let tags = &parsed_impl.doc_tags;
4470    let mut lines = ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [CLASS_DOCS_ONLY_MARKER.to_string()]))vec![CLASS_DOCS_ONLY_MARKER.to_string()];
4471    let no_rd = crate::roxygen::has_roxygen_tag(tags, "noRd")
4472        || (parsed_impl.noexport && !parsed_impl.internal);
4473    if no_rd {
4474        if !crate::roxygen::has_roxygen_tag(tags, "noRd") {
4475            lines.push("#' @noRd".to_string());
4476        }
4477    } else {
4478        if let Some(topic) = crate::roxygen::rdname_value(tags) {
4479            lines.push(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("#\' @name {0}", topic))
    })format!("#' @name {topic}"));
4480            lines.push("#' @aliases NULL".to_string());
4481            if !crate::roxygen::has_roxygen_tag(tags, "order") {
4482                lines.push(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("#\' {0}",
                crate::roxygen::ORDER_AFTER_TOPIC_BLOCKS))
    })format!("#' {}", crate::roxygen::ORDER_AFTER_TOPIC_BLOCKS));
4483            }
4484        } else {
4485            // The block's own `@title` / `@name` replace the defaults.
4486            if !crate::roxygen::has_roxygen_tag(tags, "title") {
4487                lines.push(class_default(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("#\' @title {0} {1} Class",
                class_name, kind))
    })format!(
4488                    "#' @title {class_name} {kind} Class"
4489                )));
4490            }
4491            if !crate::roxygen::has_roxygen_tag(tags, "name") {
4492                lines.push(class_default(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("#\' @name {0}", class_name))
    })format!("#' @name {class_name}")));
4493            }
4494        }
4495        if parsed_impl.internal && !crate::roxygen::has_roxygen_tag(tags, "keywords internal") {
4496            lines.push("#' @keywords internal".to_string());
4497        }
4498    }
4499    lines.push(CLASS_DOCS_MARKER.to_string());
4500    lines.push("NULL".to_string());
4501    lines
4502}
4503
4504/// Whether a type's class docs say something, beyond the page and export
4505/// tags: the wrapper writer keeps a [`CLASS_DOCS_ONLY_MARKER`] block then.
4506#[cfg(test)]
4507fn class_docs_say_something(class_docs: &[String], class_doc_lines: &[String]) -> bool {
4508    const PAGE_TAGS: &[&str] = &["rdname", "noRd", "keywords", "order", "export"];
4509    !class_doc_lines.is_empty()
4510        || class_docs.iter().any(|line| {
4511            line.strip_prefix("#' @")
4512                .and_then(|rest| rest.split_whitespace().next())
4513                .is_none_or(|tag| !PAGE_TAGS.contains(&tag))
4514        })
4515}
4516
4517/// One impl block's R wrapper, in the parts the wrapper writer merges per
4518/// Rust type (#1776): the class is defined once, from the defining block's
4519/// `head` and `tail`, and every block's `members` attach to it. See
4520/// `miniextendr_api::registry::ClassBlockEntry`.
4521#[derive(#[automatically_derived]
impl ::core::fmt::Debug for ClassWrapper {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        let names: &'static _ =
            &["head", "class_docs", "class_doc_lines", "members", "tail",
                        "member_names"];
        let values: &[&dyn ::core::fmt::Debug] =
            &[&self.head, &self.class_docs, &self.class_doc_lines,
                        &self.members, &self.tail, &&self.member_names];
        ::core::fmt::Formatter::debug_struct_fields_finish(f, "ClassWrapper",
            names, values)
    }
}Debug, #[automatically_derived]
impl ::core::default::Default for ClassWrapper {
    #[inline]
    fn default() -> Self {
        ClassWrapper {
            head: ::core::default::Default::default(),
            class_docs: ::core::default::Default::default(),
            class_doc_lines: ::core::default::Default::default(),
            members: ::core::default::Default::default(),
            tail: ::core::default::Default::default(),
            member_names: ::core::default::Default::default(),
        }
    }
}Default)]
4522pub struct ClassWrapper {
4523    /// The class definition: the class's roxygen block, with the
4524    /// [`CLASS_DOCS_MARKER`] line where the impl docs go and the
4525    /// [`CLASS_DOC_LINES_MARKER`] line where member-derived class docs go,
4526    /// the class object, the constructor and the class-level helpers.
4527    pub head: Vec<String>,
4528    /// The block's own doc comment as roxygen lines (with the constructor's,
4529    /// for S3 and vctrs, whose class block is the constructor's block).
4530    pub class_docs: Vec<String>,
4531    /// Roxygen lines the block's members add to the class's roxygen block.
4532    pub class_doc_lines: Vec<String>,
4533    /// The block's methods and static methods.
4534    pub members: Vec<String>,
4535    /// What follows the members of every block, such as R6's `$lock()`.
4536    pub tail: Vec<String>,
4537    /// The names the members bind in the class's own namespaces, worded for
4538    /// the writer's message when two blocks bind the same one.
4539    pub member_names: Vec<String>,
4540}
4541
4542impl ClassWrapper {
4543    /// Put the [`CLASS_DOC_LINES_MARKER`] before the last line of `head`, the
4544    /// tag that closes the class's roxygen block, and add the lifecycle
4545    /// imports of the block's methods to the class docs.
4546    fn mark_class_doc_lines(&mut self, head: &mut Vec<String>, parsed_impl: &ParsedImpl) {
4547        let pos = head.len().saturating_sub(1);
4548        head.insert(pos, CLASS_DOC_LINES_MARKER.to_string());
4549        if let Some(import) = crate::lifecycle::collect_lifecycle_imports(
4550            parsed_impl
4551                .methods
4552                .iter()
4553                .filter_map(|m| m.method_attrs.lifecycle.as_ref()),
4554        ) {
4555            self.class_doc_lines.push(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("#\' {0}", import))
    })format!("#' {import}"));
4556        }
4557    }
4558
4559    /// Local macro rendering for snapshots, with merge markers stripped.
4560    /// Write-time topic inheritance is covered by the registry and rpkg tests.
4561    #[cfg(test)]
4562    pub fn render(&self) -> String {
4563        let mut lines = Vec::new();
4564        let keep_docs_only = class_docs_say_something(&self.class_docs, &self.class_doc_lines);
4565        let mut skipping = false;
4566        for line in &self.head {
4567            if skipping {
4568                skipping = line != "NULL";
4569                continue;
4570            }
4571            if line == CLASS_DOCS_ONLY_MARKER {
4572                skipping = !keep_docs_only;
4573            } else if line == CLASS_DOCS_MARKER {
4574                lines.extend(self.class_docs.iter().cloned());
4575            } else if line == CLASS_DOC_LINES_MARKER {
4576                lines.extend(self.class_doc_lines.iter().map(|l| strip_class_default(l)));
4577            } else {
4578                lines.push(strip_class_default(line));
4579            }
4580        }
4581        lines.extend(self.members.iter().cloned());
4582        lines.extend(self.tail.iter().cloned());
4583        lines
4584            .join("\n")
4585            .split('\n')
4586            .map(|line| {
4587                line.strip_prefix(CLASS_MEMBER_DEFAULT_PREFIX)
4588                    .unwrap_or(line)
4589            })
4590            .collect::<Vec<_>>()
4591            .join("\n")
4592    }
4593}
4594
4595/// `line` without its [`CLASS_DEFAULT_PREFIX`], as the writer keeps a
4596/// default that no other block replaces.
4597#[cfg(test)]
4598fn strip_class_default(line: &str) -> String {
4599    line.strip_prefix(CLASS_DEFAULT_PREFIX)
4600        .unwrap_or(line)
4601        .to_string()
4602}
4603
4604/// Generate R S3 method wrappers for `as.<class>()` coercion methods.
4605///
4606/// For each method with `#[miniextendr(as = "...")]`, generates an S3 method like:
4607///
4608/// ```r
4609/// #' @export
4610/// #' @method as.data.frame MyType
4611/// as.data.frame.MyType <- function(x, ...) {
4612///     .Call(C_MyType__as_data_frame, .call = sys.call(), x)
4613/// }
4614/// ```
4615///
4616/// This function is called by each class system generator to append the
4617/// `as.*` methods to the R wrapper output.
4618pub fn generate_as_coercion_methods(parsed_impl: &ParsedImpl) -> String {
4619    use crate::r_class_formatter::MethodContext;
4620
4621    let class_name = parsed_impl.class_name();
4622    let type_ident = &parsed_impl.type_ident;
4623
4624    // Check if class has @noRd - if so, skip documentation
4625    let class_doc_tags = &parsed_impl.doc_tags;
4626    let class_has_no_rd = crate::roxygen::has_roxygen_tag(class_doc_tags, "noRd");
4627    let class_has_internal = crate::roxygen::has_roxygen_tag(class_doc_tags, "keywords internal")
4628        || parsed_impl.internal;
4629    let should_export = !class_has_no_rd && !class_has_internal && !parsed_impl.noexport;
4630
4631    let mut lines = Vec::new();
4632
4633    for method in parsed_impl.as_coercion_methods() {
4634        // Get the coercion target (e.g., "data.frame", "list", "character")
4635        let coercion_target = match &method.method_attrs.as_coercion {
4636            Some(target) => target.clone(),
4637            None => continue,
4638        };
4639
4640        // Build method context for .Call generation
4641        let mut ctx = MethodContext::new(method, type_ident)
4642            .with_impl_preconditions(parsed_impl.preconditions);
4643        // No choice formal and no prelude here (#1830), so a `Missing<..>`
4644        // choice parameter forwards only an omitted argument as absent, not
4645        // its choice vector (#2152).
4646        ctx.args = crate::r_wrapper_builder::RArgumentBuilder::new(&method.sig.inputs)
4647            .with_formals(method.formals())
4648            .build_call_args();
4649
4650        // Normalize coercion target for R generic name.
4651        // `as.numeric()` is a thin base-R wrapper that dispatches via the internal
4652        // generic `as.double` (not `as.numeric` itself) — an `as.numeric.<Class>`
4653        // S3 method is never consulted. Register under `as.double` for both
4654        // "numeric" and "double" targets so `as.double(x)` AND `as.numeric(x)`
4655        // both dispatch to it.
4656        // Some targets use non-standard S3 generic names (e.g., tibble uses as_tibble, not as.tibble)
4657        let r_generic = match coercion_target.as_str() {
4658            "numeric" | "double" => "as.double".to_string(),
4659            "tibble" => "as_tibble".to_string(),
4660            "ts" => "as.ts".to_string(),
4661            other => ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("as.{0}", other))
    })format!("as.{}", other),
4662        };
4663
4664        // S3 method name: as.data.frame.MyType
4665        let s3_method_name = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0}.{1}", r_generic, class_name))
    })format!("{}.{}", r_generic, class_name);
4666
4667        // Documentation
4668        if !class_has_no_rd {
4669            // Add documentation from the method
4670            if !method.doc_tags.is_empty() {
4671                crate::roxygen::push_roxygen_tags(&mut lines, &method.doc_tags);
4672            }
4673            // The method's own doc tags were pushed verbatim above, so only
4674            // inject the defaults a user did not supply (same rule as
4675            // `MethodDocBuilder`): a method-level `@rdname` splits the
4676            // coercion onto its own page instead of being duplicated, and a
4677            // `@describeIn` (which roxygen2 rejects next to `@name` /
4678            // `@rdname`) takes its page from the S3 method object.
4679            let describe_in = crate::roxygen::describe_in_topic(&method.doc_tags).is_some();
4680            if !describe_in && !crate::roxygen::has_roxygen_tag(&method.doc_tags, "name") {
4681                lines.push(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("#\' @name {0}", s3_method_name))
    })format!("#' @name {}", s3_method_name));
4682            }
4683            if !describe_in {
4684                if crate::roxygen::has_roxygen_tag(&method.doc_tags, "rdname") {
4685                    // Own page: needs a title (see `MethodDocBuilder::build`).
4686                    if !crate::roxygen::has_roxygen_tag(&method.doc_tags, "title") {
4687                        lines.push(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("#\' @title {0}", s3_method_name))
    })format!("#' @title {}", s3_method_name));
4688                    }
4689                } else {
4690                    lines.push(member_default(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("#\' @rdname {0}", class_name))
    })format!("#' @rdname {}", class_name)));
4691                }
4692            }
4693            crate::roxygen::push_order_after_topic_blocks(
4694                &mut lines,
4695                &method.doc_tags,
4696                &class_name,
4697            );
4698            lines.extend(crate::roxygen::method_source_tag(type_ident, &method.ident));
4699        }
4700
4701        // Export and method registration
4702        if should_export {
4703            lines.push("#' @export".to_string());
4704        }
4705        lines.push(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("#\' @method {0} {1}", r_generic,
                class_name))
    })format!("#' @method {} {}", r_generic, class_name));
4706
4707        // Function signature: always takes x and ... for S3 method compatibility
4708        // Additional parameters from the method are included
4709        let r_formals = method.formals();
4710        let method_params = crate::r_wrapper_builder::build_r_formals_from_sig(
4711            &method.sig,
4712            &r_formals,
4713            &r_formals.rekey(method.param_defaults.clone()),
4714        );
4715        let formals = if method_params.is_empty() {
4716            "x, ...".to_string()
4717        } else {
4718            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("x, {0}, ...", method_params))
    })format!("x, {}, ...", method_params)
4719        };
4720
4721        lines.push(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0} <- function({1}) {{",
                s3_method_name, formals))
    })format!("{} <- function({}) {{", s3_method_name, formals));
4722
4723        // Build the .Call() invocation
4724        let call = ctx.instance_call("x");
4725        let strategy = crate::ReturnStrategy::for_method(method);
4726        let return_builder = crate::MethodReturnBuilder::new(call)
4727            .with_strategy(strategy)
4728            .with_invisible(method.is_invisible())
4729            .with_class_name(class_name.clone())
4730            .with_return_class_from_method(method, &type_ident.to_string());
4731        lines.extend(return_builder.build_s3_body());
4732
4733        lines.push("}".to_string());
4734        lines.push(String::new());
4735    }
4736
4737    lines.join("\n")
4738}
4739
4740/// Generate `impl RCoerce*` trait impls for methods with `#[miniextendr(as = "...")]`.
4741///
4742/// For each `as` coercion method, generates a forwarding trait impl:
4743/// ```ignore
4744/// impl ::miniextendr_api::r_coerce::RCoerceDataFrame for MyType {
4745///     fn as_data_frame(&self) -> Result<::miniextendr_api::List, ::miniextendr_api::r_coerce::RCoerceError> {
4746///         self.as_data_frame()  // inherent method preferred over trait method
4747///     }
4748/// }
4749/// ```
4750///
4751/// Skips methods with extra parameters beyond `&self` (trait methods have fixed signatures)
4752/// and skips non-standard targets (like "tibble", "data.table") that don't have corresponding traits.
4753pub fn generate_as_coercion_trait_impls(parsed_impl: &ParsedImpl) -> TokenStream {
4754    let type_ident = &parsed_impl.type_ident;
4755    let cfg_attrs = &parsed_impl.cfg_attrs;
4756
4757    let mut impls = Vec::new();
4758
4759    for method in parsed_impl.as_coercion_methods() {
4760        let coercion_target = match &method.method_attrs.as_coercion {
4761            Some(target) => target.as_str(),
4762            None => continue,
4763        };
4764
4765        // Skip methods with extra params beyond &self — trait methods have fixed &self-only signatures.
4766        // `sig.inputs` already has self stripped, so non-empty means extra params.
4767        if !method.sig.inputs.is_empty() {
4768            continue;
4769        }
4770
4771        // Skip non-instance methods (trait requires &self)
4772        if method.env != ReceiverKind::Ref {
4773            continue;
4774        }
4775
4776        // Map coercion target to (trait name, trait method name, return type tokens).
4777        // Only the 15 standard targets that have corresponding traits in r_coerce.
4778        let (trait_name, trait_method): (&str, &str) = match coercion_target {
4779            "data.frame" => ("RCoerceDataFrame", "as_data_frame"),
4780            "list" => ("RCoerceList", "as_list"),
4781            "character" => ("RCoerceCharacter", "as_character"),
4782            "numeric" | "double" => ("RCoerceNumeric", "as_numeric"),
4783            "integer" => ("RCoerceInteger", "as_integer"),
4784            "logical" => ("RCoerceLogical", "as_logical"),
4785            "matrix" => ("RCoerceMatrix", "as_matrix"),
4786            "vector" => ("RCoerceVector", "as_vector"),
4787            "factor" => ("RCoerceFactor", "as_factor"),
4788            "Date" => ("RCoerceDate", "as_date"),
4789            "POSIXct" => ("RCoercePOSIXct", "as_posixct"),
4790            "complex" => ("RCoerceComplex", "as_complex"),
4791            "raw" => ("RCoerceRaw", "as_raw"),
4792            "environment" => ("RCoerceEnvironment", "as_environment"),
4793            "function" => ("RCoerceFunction", "as_function"),
4794            _ => continue, // Non-standard targets (tibble, data.table, etc.)
4795        };
4796
4797        let trait_ident = syn::Ident::new(trait_name, proc_macro2::Span::call_site());
4798        let trait_method_ident = syn::Ident::new(trait_method, proc_macro2::Span::call_site());
4799        let user_method_ident = &method.ident;
4800
4801        // Return type: data.frame and list return Result<List, RCoerceError>,
4802        // all others return Result<SEXP, RCoerceError>
4803        let return_type = match coercion_target {
4804            "data.frame" | "list" => {
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "core");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "result");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "Result");
    ::quote::__private::push_lt(&mut _s);
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "miniextendr_api");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "List");
    ::quote::__private::push_comma(&mut _s);
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "miniextendr_api");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "r_coerce");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "RCoerceError");
    ::quote::__private::push_gt(&mut _s);
    _s
}quote! {
4805                ::core::result::Result<::miniextendr_api::List, ::miniextendr_api::r_coerce::RCoerceError>
4806            },
4807            _ => {
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "core");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "result");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "Result");
    ::quote::__private::push_lt(&mut _s);
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "miniextendr_api");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "SEXP");
    ::quote::__private::push_comma(&mut _s);
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "miniextendr_api");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "r_coerce");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "RCoerceError");
    ::quote::__private::push_gt(&mut _s);
    _s
}quote! {
4808                ::core::result::Result<::miniextendr_api::SEXP, ::miniextendr_api::r_coerce::RCoerceError>
4809            },
4810        };
4811
4812        impls.push({
    let mut _s = ::quote::__private::TokenStream::new();
    {
        use ::quote::__private::ext::*;
        let has_iter = ::quote::__private::HasIterator::<false>;
        #[allow(unused_mut)]
        let (mut cfg_attrs, i) = cfg_attrs.quote_into_iter();
        let has_iter = has_iter | i;
        <_ as ::quote::__private::CheckHasIterator<true>>::check(has_iter);
        while true {
            let cfg_attrs =
                match cfg_attrs.next() {
                    Some(_x) => ::quote::__private::RepInterp(_x),
                    None => break,
                };
            ::quote::ToTokens::to_tokens(&cfg_attrs, &mut _s);
        }
    }
    ::quote::__private::push_ident(&mut _s, "impl");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "miniextendr_api");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "r_coerce");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::ToTokens::to_tokens(&trait_ident, &mut _s);
    ::quote::__private::push_ident(&mut _s, "for");
    ::quote::ToTokens::to_tokens(&type_ident, &mut _s);
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Brace,
        {
            let mut _s = ::quote::__private::TokenStream::new();
            ::quote::__private::push_ident(&mut _s, "fn");
            ::quote::ToTokens::to_tokens(&trait_method_ident, &mut _s);
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::__private::push_and(&mut _s);
                    ::quote::__private::push_ident(&mut _s, "self");
                    _s
                });
            ::quote::__private::push_rarrow(&mut _s);
            ::quote::ToTokens::to_tokens(&return_type, &mut _s);
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Brace,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::__private::push_ident(&mut _s, "self");
                    ::quote::__private::push_dot(&mut _s);
                    ::quote::ToTokens::to_tokens(&user_method_ident, &mut _s);
                    ::quote::__private::push_group(&mut _s,
                        ::quote::__private::Delimiter::Parenthesis,
                        ::quote::__private::TokenStream::new());
                    _s
                });
            _s
        });
    _s
}quote! {
4813            #(#cfg_attrs)*
4814            impl ::miniextendr_api::r_coerce::#trait_ident for #type_ident {
4815                fn #trait_method_ident(&self) -> #return_type {
4816                    self.#user_method_ident()
4817                }
4818            }
4819        });
4820    }
4821
4822    {
    let mut _s = ::quote::__private::TokenStream::new();
    {
        use ::quote::__private::ext::*;
        let has_iter = ::quote::__private::HasIterator::<false>;
        #[allow(unused_mut)]
        let (mut impls, i) = impls.quote_into_iter();
        let has_iter = has_iter | i;
        <_ as ::quote::__private::CheckHasIterator<true>>::check(has_iter);
        while true {
            let impls =
                match impls.next() {
                    Some(_x) => ::quote::__private::RepInterp(_x),
                    None => break,
                };
            ::quote::ToTokens::to_tokens(&impls, &mut _s);
        }
    }
    _s
}quote! { #(#impls)* }
4823}
4824
4825/// Detect S7 fast-path shortcut name collisions within an inherent impl block.
4826///
4827/// The S7 generator (`s7_class`) emits a standalone `<ClassName>_<method>`
4828/// shortcut function for each non-fallback, non-`no_shortcut` instance method,
4829/// and a `<ClassName>_<r_method_name>` function for each static method. These
4830/// share one R namespace, so two definitions with the same name silently
4831/// clobber each other (last write wins). This typically arises from an
4832/// `r_name` override that aliases an instance shortcut onto a static method
4833/// (or vice versa).
4834///
4835/// Returns an error pointing at the offending method, advising the user to
4836/// rename (via `r_name`) or opt the instance method out with
4837/// `#[miniextendr(s7(no_shortcut))]`. Non-S7 class systems are a no-op.
4838///
4839/// Note: collisions with `#[derive(ExternalPtr)]` sidecar accessors
4840/// (`<ClassName>_get_<field>` / `_set_<field>`) are *not* detected here — the
4841/// sidecar field names live in the derive's distributed-slice registry and are
4842/// not visible to this macro invocation. See #991 for the write-time check.
4843fn check_s7_shortcut_collisions(parsed: &ParsedImpl) -> syn::Result<()> {
4844    if parsed.class_system != ClassSystem::S7 {
4845        return Ok(());
4846    }
4847    let class_name = parsed.class_name();
4848
4849    // Map emitted standalone-function name -> the method ident span that produced
4850    // it, so a second producer of the same name can be reported against itself.
4851    let mut seen: std::collections::HashMap<String, proc_macro2::Span> =
4852        std::collections::HashMap::new();
4853
4854    // Static methods always emit `<ClassName>_<r_method_name>`.
4855    for m in parsed.static_methods() {
4856        let name = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0}_{1}", class_name,
                m.r_method_name()))
    })format!("{}_{}", class_name, m.r_method_name());
4857        seen.entry(name).or_insert_with(|| m.ident.span());
4858    }
4859
4860    // Instance methods (excluding fallback / no_shortcut / property accessors)
4861    // emit the `<ClassName>_<r_method_name>` shortcut, unless that name is not
4862    // syntactic R (an operator `r_name`), in which case none is emitted.
4863    for m in parsed.instance_methods() {
4864        if m.method_attrs.s7.fallback
4865            || m.method_attrs.s7.no_shortcut
4866            || m.method_attrs.s7.getter
4867            || m.method_attrs.s7.setter
4868            || m.method_attrs.s7.validate
4869        {
4870            continue;
4871        }
4872        let Some(name) = s7_class::s7_shortcut_name(&class_name, &m.r_method_name()) else {
4873            continue;
4874        };
4875        if seen.contains_key(&name) {
4876            return Err(syn::Error::new(
4877                m.ident.span(),
4878                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("S7 fast-path shortcut `{0}` collides with another generated function of the same name on `{1}`. Rename one (e.g. `#[miniextendr(s7(r_name = \"...\"))]`) or opt this method out of the shortcut with `#[miniextendr(s7(no_shortcut))]`.",
                name, class_name))
    })format!(
4879                    "S7 fast-path shortcut `{name}` collides with another generated function \
4880                     of the same name on `{class_name}`. Rename one (e.g. \
4881                     `#[miniextendr(s7(r_name = \"...\"))]`) or opt this method out of the \
4882                     shortcut with `#[miniextendr(s7(no_shortcut))]`."
4883                ),
4884            ));
4885        }
4886        seen.insert(name, m.ident.span());
4887    }
4888
4889    Ok(())
4890}
4891
4892/// Env constructors, instance methods and static methods all occupy the same
4893/// class environment. Check the emitted names before one overwrites another.
4894fn check_env_member_collisions(parsed: &ParsedImpl) -> syn::Result<()> {
4895    if parsed.class_system != ClassSystem::Env {
4896        return Ok(());
4897    }
4898    let mut seen = std::collections::HashMap::new();
4899    if let Some(constructor) = parsed.constructor() {
4900        seen.insert("new".to_owned(), constructor);
4901    }
4902    for method in parsed.instance_methods().chain(parsed.static_methods()) {
4903        let name = method.r_method_name();
4904        if let Some(previous) = seen.insert(name.clone(), method) {
4905            return Err(syn::Error::new_spanned(
4906                &method.ident,
4907                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("Env member `{0}${3}` is defined by both `{1}` and `{2}`. Constructors always occupy `new`; instance and static methods share the class environment. Rename a colliding method with `#[miniextendr(r_name = \"...\")]`.",
                parsed.class_name(), previous.ident, method.ident, name))
    })format!(
4908                    "Env member `{}${name}` is defined by both `{}` and `{}`. \
4909                     Constructors always occupy `new`; instance and static methods \
4910                     share the class environment. Rename a colliding method with \
4911                     `#[miniextendr(r_name = \"...\")]`.",
4912                    parsed.class_name(),
4913                    previous.ident,
4914                    method.ident,
4915                ),
4916            ));
4917        }
4918    }
4919    Ok(())
4920}
4921
4922/// R6 always has an `initialize` member, including classes without a Rust
4923/// constructor. R6 requires public, private and active names to be distinct;
4924/// reject collisions before the generated class fails during package loading.
4925fn check_r6_initialize_collision(parsed: &ParsedImpl) -> syn::Result<()> {
4926    if parsed.class_system != ClassSystem::R6 {
4927        return Ok(());
4928    }
4929    for method in parsed
4930        .public_instance_methods()
4931        .chain(parsed.private_instance_methods())
4932        .chain(parsed.active_instance_methods())
4933    {
4934        let name = if method.is_active() {
4935            method
4936                .method_attrs
4937                .r6
4938                .prop
4939                .clone()
4940                .unwrap_or_else(|| method.r_method_name())
4941        } else {
4942            method.r_method_name()
4943        };
4944        if name == "initialize" {
4945            return Err(syn::Error::new_spanned(
4946                &method.ident,
4947                "R6 member `initialize` conflicts with the generated constructor. \
4948                 Rename the method or use `#[miniextendr(r_name = \"...\")]`; \
4949                 for an active binding, choose a different `r6(prop = \"...\")`.",
4950            ));
4951        }
4952    }
4953    Ok(())
4954}
4955
4956/// The bindings of an `R6::R6Class()` generator object (R6 2.6). The R6
4957/// generator assigns static methods onto that object (`Class$name <- function`),
4958/// so a static method under one of these names silently replaces R6's own:
4959/// `new` stops creating objects, `set` and `lock` are the generated `$set()` /
4960/// `$lock()` calls, and `$new()` reads the data fields.
4961const R6_GENERATOR_BINDINGS: &[&str] = &[
4962    "active",
4963    "class",
4964    "classname",
4965    "clone_method",
4966    "cloneable",
4967    "debug",
4968    "debug_names",
4969    "get_inherit",
4970    "has_private",
4971    "inherit",
4972    "is_locked",
4973    "lock",
4974    "lock_class",
4975    "lock_objects",
4976    "new",
4977    "parent_env",
4978    "portable",
4979    "private_fields",
4980    "private_methods",
4981    "public_fields",
4982    "public_methods",
4983    "self",
4984    "set",
4985    "undebug",
4986    "unlock",
4987];
4988
4989/// Reject an R6 static method whose R name is one of the generator object's
4990/// own bindings ([`R6_GENERATOR_BINDINGS`]), which the static would replace
4991/// without a warning when the package loads.
4992fn check_r6_static_collisions(parsed: &ParsedImpl) -> syn::Result<()> {
4993    if parsed.class_system != ClassSystem::R6 {
4994        return Ok(());
4995    }
4996    for method in parsed.static_methods() {
4997        let name = method.r_method_name();
4998        if R6_GENERATOR_BINDINGS.contains(&name.as_str()) {
4999            return Err(syn::Error::new_spanned(
5000                &method.ident,
5001                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("R6 static method `{0}${1}` would replace the R6 generator\'s own `{1}`: static methods are assigned onto the generator object. Rename it with `#[miniextendr(r_name = \"...\")]`.",
                parsed.class_name(), name))
    })format!(
5002                    "R6 static method `{class}${name}` would replace the R6 generator's own \
5003                     `{name}`: static methods are assigned onto the generator object. Rename \
5004                     it with `#[miniextendr(r_name = \"...\")]`.",
5005                    class = parsed.class_name(),
5006                ),
5007            ));
5008        }
5009    }
5010    Ok(())
5011}
5012
5013/// Two instance methods of one S4 impl block that dispatch through the same
5014/// generic emit two `setMethod("<generic>", "<Class>", ...)` calls with the
5015/// same signature, and the second silently replaces the first. The default
5016/// generic follows the R method name (`s4_<r_name>`), so `r_name = "x"` next to
5017/// a method `x` is one way to get there; two equal `s4(generic = "...")`
5018/// overrides are another.
5019fn check_s4_generic_collisions(parsed: &ParsedImpl) -> syn::Result<()> {
5020    if parsed.class_system != ClassSystem::S4 {
5021        return Ok(());
5022    }
5023    let mut seen = std::collections::HashMap::new();
5024    for method in parsed.instance_methods() {
5025        let generic = s4_class::s4_generic_name(method);
5026        if let Some(previous) = seen.insert(generic.clone(), method) {
5027            return Err(syn::Error::new_spanned(
5028                &method.ident,
5029                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("S4 generic `{3}` gets a method for `{0}` from both `{1}` and `{2}`; the second `setMethod()` would replace the first. Rename one with `#[miniextendr(r_name = \"...\")]` or give it another `s4(generic = \"...\")`.",
                parsed.class_name(), previous.ident, method.ident, generic))
    })format!(
5030                    "S4 generic `{generic}` gets a method for `{}` from both `{}` and `{}`; \
5031                     the second `setMethod()` would replace the first. Rename one with \
5032                     `#[miniextendr(r_name = \"...\")]` or give it another \
5033                     `s4(generic = \"...\")`.",
5034                    parsed.class_name(),
5035                    previous.ident,
5036                    method.ident,
5037                ),
5038            ));
5039        }
5040    }
5041    Ok(())
5042}
5043
5044/// Check the R formals of every generated method
5045/// ([`check_r_formals`](crate::r_wrapper_builder::check_r_formals)), reserving
5046/// the names each class system's wrapper binds itself:
5047///
5048/// - the receiver of an instance method: `x` for S3 and S4,
5049///   the first dispatch argument (`x` unless `s7(dispatch)` or an operator
5050///   says otherwise) for S7; shortcuts use the collision-free `.mx_self`;
5051/// - `self` and `private`, which R6 binds inside `initialize` and every method
5052///   of the class: the wrapper reads the pointer from `private$.ptr` and
5053///   returns `self` from a chaining method.
5054///
5055/// Static methods and the other constructors are plain functions and reserve
5056/// nothing; S7 property accessors have fixed formals, so a `formal(...)` on
5057/// one is refused (#1842); vctrs impls take no instance methods (MXL120).
5058fn check_method_formals(parsed: &ParsedImpl) -> syn::Result<()> {
5059    let receiver = |name: &str, method: &str| {
5060        (
5061            name.to_string(),
5062            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("is the receiver of the generated {0}",
                method))
    })format!("is the receiver of the generated {method}"),
5063        )
5064    };
5065    for m in parsed.included_methods() {
5066        let is_ctor = parsed.is_method_constructor(m);
5067        let instance = m.env.is_instance() && !is_ctor && !m.is_finalizer();
5068        if (m.method_attrs.receiver.is_some() || m.method_attrs.no_dispatch_dots) && !instance {
5069            return Err(syn::Error::new_spanned(
5070                &m.ident,
5071                "`receiver` and `no_dots` apply only to instance methods",
5072            ));
5073        }
5074        let generic_receiver = m.method_attrs.receiver.as_deref().unwrap_or("x");
5075        let s7 = &m.method_attrs.s7;
5076        let formals = m.formals();
5077        if parsed.class_system == ClassSystem::S7
5078            && (s7.getter || s7.setter || s7.validate)
5079            && let Some(lit) = m
5080                .method_attrs
5081                .per_param
5082                .values()
5083                .find_map(|a| a.formal.as_ref())
5084        {
5085            return Err(syn::Error::new(
5086                lit.span(),
5087                "`formal(...)` on an S7 property getter, setter or validator: S7 calls it with \
5088                 fixed arguments (`self`, `value`), so a declared formal would name nothing",
5089            ));
5090        }
5091        let reserved = match parsed.class_system {
5092            ClassSystem::R6 if instance || is_ctor => ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [("self".to_string(),
                    "is R6's `self` binding inside the class, the object the generated \
                     method works on".to_string()),
                ("private".to_string(),
                    "is R6's `private` binding inside the class, where the generated method \
                     reads the object's pointer".to_string())]))vec![
5093                (
5094                    "self".to_string(),
5095                    "is R6's `self` binding inside the class, the object the generated \
5096                     method works on"
5097                        .to_string(),
5098                ),
5099                (
5100                    "private".to_string(),
5101                    "is R6's `private` binding inside the class, where the generated method \
5102                     reads the object's pointer"
5103                        .to_string(),
5104                ),
5105            ],
5106            ClassSystem::S3 if instance => ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [receiver(generic_receiver, "S3 method")]))vec![receiver(generic_receiver, "S3 method")],
5107            ClassSystem::S4 if instance => ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [receiver(generic_receiver, "S4 method")]))vec![receiver(generic_receiver, "S4 method")],
5108            ClassSystem::S7 if instance && !(s7.getter || s7.setter || s7.validate) => {
5109                let generic = m
5110                    .method_attrs
5111                    .generic
5112                    .clone()
5113                    .unwrap_or_else(|| m.r_method_name());
5114                let dispatch =
5115                    s7_class::s7_dispatch_args(m, &s7_class::s7_generic_target(&generic));
5116                ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [receiver(&dispatch[0], "S7 method")]))vec![receiver(&dispatch[0], "S7 method")]
5117            }
5118            _ => Vec::new(),
5119        };
5120        crate::r_wrapper_builder::check_r_formals(&m.sig.inputs, &formals, &reserved)?;
5121    }
5122    Ok(())
5123}
5124
5125/// Check that every S3 instance method for a replacement generic
5126/// (`s3(generic = "[[<-")`, or an `r_name` ending in `<-`) takes the new value
5127/// last, as `value`
5128/// ([`check_replacement_value_param`](crate::miniextendr_fn::check_replacement_value_param),
5129/// #1853). The generator puts the dispatch `...` before that formal
5130/// ([`s3_method_formals`](crate::r_class_formatter::MethodContext::s3_method_formals)).
5131/// Other class systems register their methods themselves, and static methods
5132/// are plain functions.
5133fn check_s3_replacement_methods(parsed: &ParsedImpl) -> syn::Result<()> {
5134    if !#[allow(non_exhaustive_omitted_patterns)] match parsed.class_system {
    ClassSystem::S3 | ClassSystem::Vctrs => true,
    _ => false,
}matches!(parsed.class_system, ClassSystem::S3 | ClassSystem::Vctrs) {
5135        return Ok(());
5136    }
5137    for m in parsed.instance_methods() {
5138        let generic = m
5139            .method_attrs
5140            .generic
5141            .clone()
5142            .unwrap_or_else(|| m.r_method_name());
5143        crate::miniextendr_fn::check_replacement_value_param(
5144            &generic,
5145            &m.sig.inputs,
5146            &m.formals(),
5147            m.ident.span(),
5148        )?;
5149    }
5150    Ok(())
5151}
5152
5153/// Refuse a `dots = empty | forward(..) | data` (#2157) that describes a
5154/// `...` the method's wrapper doesn't have
5155/// ([`DotsPolicy::check_signature`](crate::dots_policy::DotsPolicy::check_signature)):
5156/// `forward` and `data` need the method's own `...` parameter; `empty` the
5157/// wrapper's `...` formal, which is that parameter or the dispatch `...` a
5158/// method of a generic gets ([`has_dispatch_dots`]).
5159fn check_dots_policies(parsed: &ParsedImpl) -> syn::Result<()> {
5160    for m in &parsed.methods {
5161        if let Some(policy) = &m.method_attrs.dots_policy {
5162            policy.check_signature(
5163                m.has_dots,
5164                m.has_dots || has_dispatch_dots(parsed.class_system, m),
5165            )?;
5166        }
5167    }
5168    Ok(())
5169}
5170
5171/// Whether the class system gives `m`'s R wrapper a `...` formal the Rust
5172/// method doesn't take: the dispatch `...` of an S3, S4 or S7 instance method
5173/// (unless `no_dispatch_dots` / `s7(no_dots)`), and of a vctrs protocol
5174/// method. Env and R6 methods and every other static method have only the
5175/// formals of the Rust signature.
5176fn has_dispatch_dots(class_system: ClassSystem, m: &ParsedMethod) -> bool {
5177    match class_system {
5178        ClassSystem::Vctrs => m.method_attrs.vctrs_protocol.is_some(),
5179        _ if !m.env.is_instance() => false,
5180        ClassSystem::S3 | ClassSystem::S4 => !m.method_attrs.no_dispatch_dots,
5181        ClassSystem::S7 => !m.method_attrs.s7.no_dots,
5182        ClassSystem::Env | ClassSystem::R6 => false,
5183    }
5184}
5185
5186/// Validate the S7 dispatch arguments of every S7 instance method: an explicit
5187/// `s7(dispatch = "...")`, and the pair S7 fixes for an `Ops` / `%*%`
5188/// operator generic (`e1, e2` / `x, y`).
5189///
5190/// S7 registers a method only when its formals start with the generic's
5191/// dispatch arguments, in order and without defaults, and it rejects the
5192/// wrong number of classes in the signature; any violation stops the package
5193/// from loading. The generator names the receiver after the first dispatch
5194/// argument and takes the others from the method's leading parameters, so the
5195/// rules are checked here, where the error can name the fix. `!` is rejected
5196/// outright because S7 does not dispatch it. Non-S7 class systems, static
5197/// methods and property accessors are a no-op.
5198fn check_s7_dispatch(parsed: &ParsedImpl) -> syn::Result<()> {
5199    if parsed.class_system != ClassSystem::S7 {
5200        return Ok(());
5201    }
5202    for m in parsed.instance_methods() {
5203        if m.method_attrs.s7.getter || m.method_attrs.s7.setter || m.method_attrs.s7.validate {
5204            continue;
5205        }
5206        let ctx = crate::r_class_formatter::MethodContext::new(m, &parsed.type_ident);
5207        check_s7_method_dispatch(m, &ctx.params)
5208            .map_err(|message| syn::Error::new(m.ident.span(), message))?;
5209    }
5210    Ok(())
5211}
5212
5213/// [`check_s7_dispatch`] for one method; `params` is its R formals string
5214/// (normalized names, with defaults).
5215fn check_s7_method_dispatch(m: &ParsedMethod, params: &str) -> Result<(), String> {
5216    use s7_class::{S7GenericTarget, s7_dispatch_args, s7_generic_target};
5217
5218    let rust_name = crate::naming::ident_name(&m.ident);
5219    let generic = m
5220        .method_attrs
5221        .generic
5222        .clone()
5223        .unwrap_or_else(|| m.r_method_name());
5224    let target = s7_generic_target(&generic);
5225    let op = target.name();
5226    if op == "!" {
5227        return Err(
5228            "S7 methods cannot be generated for the `!` operator: S7 does not dispatch \
5229                    `!` (its Ops handling covers the binary operators only). Keep this method \
5230                    under an ordinary name and call it directly."
5231                .to_string(),
5232        );
5233    }
5234
5235    let dispatch = s7_dispatch_args(m, &target);
5236    let spec = m
5237        .method_attrs
5238        .s7
5239        .dispatch
5240        .clone()
5241        .unwrap_or_else(|| dispatch.join(", "));
5242    let formals = crate::roxygen::split_r_formals(params);
5243    let names: Vec<&str> = formals
5244        .iter()
5245        .map(|f| crate::roxygen::formal_name(f))
5246        .collect();
5247    let listed = if names.is_empty() {
5248        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("`{0}` has no parameters besides the receiver",
                rust_name))
    })format!("`{rust_name}` has no parameters besides the receiver")
5249    } else {
5250        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("the R parameters of `{1}` are {0}",
                names.iter().map(|n|
                                ::alloc::__export::must_use({
                                        ::alloc::fmt::format(format_args!("`{0}`", n))
                                    })).collect::<Vec<_>>().join(", "), rust_name))
    })format!(
5251            "the R parameters of `{rust_name}` are {}",
5252            names
5253                .iter()
5254                .map(|n| format!("`{n}`"))
5255                .collect::<Vec<_>>()
5256                .join(", ")
5257        )
5258    };
5259
5260    let mut seen = std::collections::HashSet::new();
5261    for d in &dispatch {
5262        // `...` / `..1` pass R's syntactic-name rule but are not arguments.
5263        if !crate::naming::is_syntactic_r_name(d) || d.starts_with("..") {
5264            return Err(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("`s7(dispatch = \"{0}\")` on `{1}`: `{2}` is not a syntactic R argument name (S7 dispatch arguments are plain names, and `...` cannot be one)",
                spec, rust_name, d))
    })format!(
5265                "`s7(dispatch = \"{spec}\")` on `{rust_name}`: `{d}` is not a syntactic R \
5266                 argument name (S7 dispatch arguments are plain names, and `...` cannot be one)"
5267            ));
5268        }
5269        if !seen.insert(d.as_str()) {
5270            return Err(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("`s7(dispatch = \"{0}\")` on `{1}` names `{2}` twice",
                spec, rust_name, d))
    })format!(
5271                "`s7(dispatch = \"{spec}\")` on `{rust_name}` names `{d}` twice"
5272            ));
5273        }
5274    }
5275
5276    let is_base_operator = #[allow(non_exhaustive_omitted_patterns)] match target {
    S7GenericTarget::BaseOperator(_) => true,
    _ => false,
}matches!(target, S7GenericTarget::BaseOperator(_));
5277    if let Some(pair) = s7_class::s7_operator_dispatch_args(op).filter(|_| is_base_operator) {
5278        let [left, right] = pair;
5279        if dispatch != pair {
5280            return Err(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("S7 dispatches `{0}` on `{1}` and `{2}`, so `s7(dispatch = \"{3}\")` cannot be used on `{4}`: drop it (the pair is the default for `{0}`)",
                op, left, right, spec, rust_name))
    })format!(
5281                "S7 dispatches `{op}` on `{left}` and `{right}`, so `s7(dispatch = \"{spec}\")` \
5282                 cannot be used on `{rust_name}`: drop it (the pair is the default for `{op}`)"
5283            ));
5284        }
5285        if m.method_attrs.s7.fallback {
5286            return Err(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("`s7(fallback)` cannot be combined with the `{0}` operator on `{1}`: it would register the method for every pair of operands, not for this class",
                op, rust_name))
    })format!(
5287                "`s7(fallback)` cannot be combined with the `{op}` operator on `{rust_name}`: \
5288                 it would register the method for every pair of operands, not for this class"
5289            ));
5290        }
5291        let operands: Vec<&str> = names.iter().copied().filter(|n| *n != "...").collect();
5292        if operands.len() != 1 {
5293            return Err(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("`{0}` implements the `{1}` operator, so it takes exactly one parameter besides the receiver: the right operand, named `{2}` (S7 calls the method as `{3} {1} {2}`); {4}",
                rust_name, op, right, left, listed))
    })format!(
5294                "`{rust_name}` implements the `{op}` operator, so it takes exactly one parameter \
5295                 besides the receiver: the right operand, named `{right}` (S7 calls the method \
5296                 as `{left} {op} {right}`); {listed}"
5297            ));
5298        }
5299        if operands[0] != right {
5300            return Err(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("S7 dispatches `{1}` on `{2}` and `{3}`: the receiver becomes `{2}` and the right operand of `{4}` must be named `{3}`. Rename the parameter `{0}` to `{3}`.",
                operands[0], op, left, right, rust_name))
    })format!(
5301                "S7 dispatches `{op}` on `{left}` and `{right}`: the receiver becomes `{left}` and \
5302                 the right operand of `{rust_name}` must be named `{right}`. Rename the parameter \
5303                 `{}` to `{right}`.",
5304                operands[0]
5305            ));
5306        }
5307    }
5308    if is_base_operator && s7_class::s7_operator_dispatch_args(op).is_none() && dispatch.len() > 1 {
5309        return Err(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("S7 dispatches `{0}` through the base primitive on its first argument only, so `s7(dispatch = \"{1}\")` cannot be used on `{2}`: drop it",
                op, spec, rust_name))
    })format!(
5310            "S7 dispatches `{op}` through the base primitive on its first argument only, so \
5311             `s7(dispatch = \"{spec}\")` cannot be used on `{rust_name}`: drop it"
5312        ));
5313    }
5314
5315    // A parameter named like the receiver (`dispatch[0]`) is rejected earlier,
5316    // by `check_method_formals`.
5317    for (i, d) in dispatch.iter().enumerate().skip(1) {
5318        let Some(&found) = names.get(i - 1) else {
5319            return Err(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("`s7(dispatch = \"{0}\")` on `{1}`: dispatch argument `{2}` must be parameter {3} of the method (S7 needs a method\'s formals to start with the generic\'s dispatch arguments, the first naming the receiver), but {4}",
                spec, rust_name, d, i, listed))
    })format!(
5320                "`s7(dispatch = \"{spec}\")` on `{rust_name}`: dispatch argument `{d}` must be \
5321                 parameter {i} of the method (S7 needs a method's formals to start with the \
5322                 generic's dispatch arguments, the first naming the receiver), but {listed}"
5323            ));
5324        };
5325        if found != d {
5326            return Err(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("`s7(dispatch = \"{0}\")` on `{1}`: dispatch argument `{2}` must be parameter {3} of the method, found `{4}` (S7 needs a method\'s formals to start with the generic\'s dispatch arguments, in order, the first naming the receiver); {5}",
                spec, rust_name, d, i, found, listed))
    })format!(
5327                "`s7(dispatch = \"{spec}\")` on `{rust_name}`: dispatch argument `{d}` must be \
5328                 parameter {i} of the method, found `{found}` (S7 needs a method's formals to \
5329                 start with the generic's dispatch arguments, in order, the first naming the \
5330                 receiver); {listed}"
5331            ));
5332        }
5333        if formals[i - 1].contains('=') {
5334            return Err(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("`s7(dispatch = \"{1}\")` on `{2}`: dispatch argument `{3}` has a default value (`{0}`), which S7 does not allow on dispatch arguments",
                formals[i - 1].trim(), spec, rust_name, d))
    })format!(
5335                "`s7(dispatch = \"{spec}\")` on `{rust_name}`: dispatch argument `{d}` has a \
5336                 default value (`{}`), which S7 does not allow on dispatch arguments",
5337                formals[i - 1].trim()
5338            ));
5339        }
5340    }
5341    if m.method_attrs.s7.no_dots
5342        && #[allow(non_exhaustive_omitted_patterns)] match target {
    S7GenericTarget::Local(_) => true,
    _ => false,
}matches!(target, S7GenericTarget::Local(_))
5343        && let Some(extra) = names.get(dispatch.len() - 1)
5344    {
5345        return Err(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("`s7(no_dots)` on `{0}` makes a generic without `...`, and S7 then requires every method\'s formals to equal its dispatch arguments, but `{1}` is not one. Add it to `s7(dispatch = \"...\")` or drop `no_dots`.",
                rust_name, extra))
    })format!(
5346            "`s7(no_dots)` on `{rust_name}` makes a generic without `...`, and S7 then requires \
5347             every method's formals to equal its dispatch arguments, but `{extra}` is not one. \
5348             Add it to `s7(dispatch = \"...\")` or drop `no_dots`."
5349        ));
5350    }
5351    Ok(())
5352}
5353
5354/// Top-level entry point for expanding `#[miniextendr]` on impl blocks.
5355///
5356/// Dispatches between two cases:
5357/// 1. **Inherent impls** (`impl Type { ... }`): Parses [`ImplAttrs`] and [`ParsedImpl`],
5358///    then generates C wrappers, R wrapper code, `R_CallMethodDef` arrays, and
5359///    `as.<class>()` trait impls for the chosen class system.
5360/// 2. **Trait impls** (`impl Trait for Type { ... }`): Generates trait ABI vtables,
5361///    cross-package shims, and R wrappers via
5362///    [`expand_miniextendr_impl_trait`](crate::miniextendr_impl_trait::expand_miniextendr_impl_trait).
5363///
5364/// # Arguments
5365///
5366/// * `attr` - The token stream inside `#[miniextendr(...)]` (class system, options)
5367/// * `item` - The full `impl` block token stream
5368///
5369/// # Returns
5370///
5371/// A token stream containing the original impl block (with miniextendr attrs stripped),
5372/// C wrapper functions, an R wrapper string constant, a `R_CallMethodDef` array constant,
5373/// and any forwarding trait impls for `as.<class>()` coercion.
5374pub fn expand_impl(
5375    attr: proc_macro::TokenStream,
5376    item: proc_macro::TokenStream,
5377) -> proc_macro::TokenStream {
5378    let item_impl = match syn::parse::<syn::ItemImpl>(item.clone()) {
5379        Ok(i) => i,
5380        Err(e) => return e.into_compile_error().into(),
5381    };
5382
5383    // Check if this is a trait impl (impl Trait for Type)
5384    if item_impl.trait_.is_some() {
5385        // Delegate to trait ABI vtable generator
5386        return crate::miniextendr_impl_trait::expand_miniextendr_impl_trait(attr, item);
5387    }
5388
5389    // Otherwise, this is an inherent impl - parse class system attrs
5390    let attrs = match syn::parse::<ImplAttrs>(attr) {
5391        Ok(a) => a,
5392        Err(e) => return e.into_compile_error().into(),
5393    };
5394
5395    let mut parsed = match ParsedImpl::parse(attrs, item_impl) {
5396        Ok(p) => p,
5397        Err(e) => return e.into_compile_error().into(),
5398    };
5399
5400    if let Err(e) = check_class_members(&parsed) {
5401        return e.into_compile_error().into();
5402    }
5403    // A block that infers its constructor is written in one of two
5404    // renderings, decided per type by the wrapper writer: with that
5405    // constructor, or (when another block declares one) with it as a static
5406    // method. Both must hold.
5407    let infers_constructor = parsed.inferred_constructor().is_some();
5408    let demoted_class = if infers_constructor {
5409        parsed.constructor_demoted = true;
5410        let demoted = parsed
5411            .reject_unsupported_describe_in()
5412            .and_then(|()| check_class_members(&parsed))
5413            .map(|()| (generate_class_wrapper(&parsed), parsed.class_definitions()));
5414        parsed.constructor_demoted = false;
5415        match demoted {
5416            Ok(class) => Some(class),
5417            Err(e) => return e.into_compile_error().into(),
5418        }
5419    } else {
5420        None
5421    };
5422
5423    let type_ident = &parsed.type_ident;
5424    let cfg_attrs = &parsed.cfg_attrs;
5425
5426    // Generate C wrappers for all included methods
5427    let c_wrappers: Vec<TokenStream> = parsed
5428        .included_methods()
5429        .map(|m| generate_method_c_wrapper(&parsed, m))
5430        .collect();
5431    // The `.Call` entries of the field fallback (#2150), which this block's
5432    // field methods call; only the block that defines the class has one.
5433    let fallback_entries = field_syntax::fallback_c_entries(&parsed);
5434
5435    // The R wrapper, in the parts the wrapper writer merges per type (#1776).
5436    let class = generate_class_wrapper(&parsed);
5437
5438    let original_impl = &parsed.original_impl;
5439
5440    // Generate forwarding trait impls for as.<class>() coercion methods
5441    let trait_impls = generate_as_coercion_trait_impls(&parsed);
5442
5443    // Every part is qualified against the formals pooled over the whole block
5444    // (the constructor included).
5445    let formals = crate::r_shadowing::formal_names(
5446        parsed
5447            .included_methods()
5448            .map(|m| (&m.sig.inputs, m.formals())),
5449    );
5450    let r_part = |lines: &[String]| crate::r_wrapper_raw_literal(&lines.join("\n"), &formals);
5451    let class_parts = |class: &ClassWrapper, defines: &[String]| {
5452        let head = r_part(&class.head);
5453        let class_docs = r_part(&class.class_docs);
5454        let class_doc_lines = r_part(&class.class_doc_lines);
5455        let members = r_part(&class.members);
5456        let tail = r_part(&class.tail);
5457        let member_names = &class.member_names;
5458        {
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::__private::push_ident(&mut _s, "defines");
    ::quote::__private::push_colon(&mut _s);
    ::quote::__private::push_and(&mut _s);
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Bracket,
        {
            let mut _s = ::quote::__private::TokenStream::new();
            {
                use ::quote::__private::ext::*;
                let mut _first = true;
                let has_iter = ::quote::__private::HasIterator::<false>;
                #[allow(unused_mut)]
                let (mut defines, i) = defines.quote_into_iter();
                let has_iter = has_iter | i;
                <_ as
                        ::quote::__private::CheckHasIterator<true>>::check(has_iter);
                while true {
                    let defines =
                        match defines.next() {
                            Some(_x) => ::quote::__private::RepInterp(_x),
                            None => break,
                        };
                    if !_first { ::quote::__private::push_comma(&mut _s); }
                    _first = false;
                    ::quote::ToTokens::to_tokens(&defines, &mut _s);
                }
            }
            _s
        });
    ::quote::__private::push_comma(&mut _s);
    ::quote::__private::push_ident(&mut _s, "head");
    ::quote::__private::push_colon(&mut _s);
    ::quote::ToTokens::to_tokens(&head, &mut _s);
    ::quote::__private::push_comma(&mut _s);
    ::quote::__private::push_ident(&mut _s, "class_docs");
    ::quote::__private::push_colon(&mut _s);
    ::quote::ToTokens::to_tokens(&class_docs, &mut _s);
    ::quote::__private::push_comma(&mut _s);
    ::quote::__private::push_ident(&mut _s, "class_doc_lines");
    ::quote::__private::push_colon(&mut _s);
    ::quote::ToTokens::to_tokens(&class_doc_lines, &mut _s);
    ::quote::__private::push_comma(&mut _s);
    ::quote::__private::push_ident(&mut _s, "members");
    ::quote::__private::push_colon(&mut _s);
    ::quote::ToTokens::to_tokens(&members, &mut _s);
    ::quote::__private::push_comma(&mut _s);
    ::quote::__private::push_ident(&mut _s, "member_names");
    ::quote::__private::push_colon(&mut _s);
    ::quote::__private::push_and(&mut _s);
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Bracket,
        {
            let mut _s = ::quote::__private::TokenStream::new();
            {
                use ::quote::__private::ext::*;
                let mut _first = true;
                let has_iter = ::quote::__private::HasIterator::<false>;
                #[allow(unused_mut)]
                let (mut member_names, i) = member_names.quote_into_iter();
                let has_iter = has_iter | i;
                <_ as
                        ::quote::__private::CheckHasIterator<true>>::check(has_iter);
                while true {
                    let member_names =
                        match member_names.next() {
                            Some(_x) => ::quote::__private::RepInterp(_x),
                            None => break,
                        };
                    if !_first { ::quote::__private::push_comma(&mut _s); }
                    _first = false;
                    ::quote::ToTokens::to_tokens(&member_names, &mut _s);
                }
            }
            _s
        });
    ::quote::__private::push_comma(&mut _s);
    ::quote::__private::push_ident(&mut _s, "tail");
    ::quote::__private::push_colon(&mut _s);
    ::quote::ToTokens::to_tokens(&tail, &mut _s);
    ::quote::__private::push_comma(&mut _s);
    _s
}quote! {
5459            defines: &[#(#defines),*],
5460            head: #head,
5461            class_docs: #class_docs,
5462            class_doc_lines: #class_doc_lines,
5463            members: #members,
5464            member_names: &[#(#member_names),*],
5465            tail: #tail,
5466        }
5467    };
5468
5469    let source_loc_doc = crate::source_location_doc(type_ident.span());
5470    let source_start = type_ident.span().start();
5471    let source_line_lit = syn::LitInt::new(&source_start.line.to_string(), type_ident.span());
5472    let source_col_lit =
5473        syn::LitInt::new(&(source_start.column + 1).to_string(), type_ident.span());
5474
5475    let param_warnings = &parsed.param_warnings;
5476
5477    // r_class_name is the R-visible name (may differ from type_ident when
5478    // `class = "Override"` was set on the impl block).
5479    let r_class_name_str = parsed.class_name();
5480    let class_system_str = parsed.class_system.to_ident().to_string();
5481    // `$` / `[[` field syntax (`s3/s4/env(r_data_accessors)`): the wrapper
5482    // writer then keeps the type's standalone sidecar accessors internal
5483    // (#1900). R6 bindings and S7 properties leave them exported.
5484    let field_syntax = parsed.r_data_accessors.enabled()
5485        && #[allow(non_exhaustive_omitted_patterns)] match parsed.class_system {
    ClassSystem::S3 | ClassSystem::S4 | ClassSystem::Env => true,
    _ => false,
}matches!(
5486            parsed.class_system,
5487            ClassSystem::S3 | ClassSystem::S4 | ClassSystem::Env
5488        );
5489    let settings = parsed.class_settings();
5490    let parts = class_parts(&class, &parsed.class_definitions());
5491    let constructor = parsed
5492        .constructor()
5493        .map(|m| crate::naming::ident_name(&m.ident))
5494        .unwrap_or_default();
5495    let candidates: Vec<String> = parsed
5496        .constructor_candidates()
5497        .map(|m| crate::naming::ident_name(&m.ident))
5498        .collect();
5499    let block_entry = |constructor: &str, inferred: bool, demoted, parts| {
5500        {
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "miniextendr_api");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "registry");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "ClassBlockEntry");
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Brace,
        {
            let mut _s = ::quote::__private::TokenStream::new();
            ::quote::__private::push_ident(&mut _s, "rust_type");
            ::quote::__private::push_colon(&mut _s);
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "core");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "stringify");
            ::quote::__private::push_bang(&mut _s);
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::ToTokens::to_tokens(&type_ident, &mut _s);
                    _s
                });
            ::quote::__private::push_comma(&mut _s);
            ::quote::__private::push_ident(&mut _s, "type_path");
            ::quote::__private::push_colon(&mut _s);
            ::quote::__private::push_or_or(&mut _s);
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "core");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "any");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "type_name");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_lt(&mut _s);
            ::quote::ToTokens::to_tokens(&type_ident, &mut _s);
            ::quote::__private::push_gt(&mut _s);
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                ::quote::__private::TokenStream::new());
            ::quote::__private::push_comma(&mut _s);
            ::quote::__private::push_ident(&mut _s, "r_class_name");
            ::quote::__private::push_colon(&mut _s);
            ::quote::ToTokens::to_tokens(&r_class_name_str, &mut _s);
            ::quote::__private::push_comma(&mut _s);
            ::quote::__private::push_ident(&mut _s, "source_file");
            ::quote::__private::push_colon(&mut _s);
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "core");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "file");
            ::quote::__private::push_bang(&mut _s);
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                ::quote::__private::TokenStream::new());
            ::quote::__private::push_comma(&mut _s);
            ::quote::__private::push_ident(&mut _s, "source_line");
            ::quote::__private::push_colon(&mut _s);
            ::quote::ToTokens::to_tokens(&source_line_lit, &mut _s);
            ::quote::__private::push_comma(&mut _s);
            ::quote::__private::push_ident(&mut _s, "source_column");
            ::quote::__private::push_colon(&mut _s);
            ::quote::ToTokens::to_tokens(&source_col_lit, &mut _s);
            ::quote::__private::push_comma(&mut _s);
            ::quote::__private::push_ident(&mut _s, "settings");
            ::quote::__private::push_colon(&mut _s);
            ::quote::ToTokens::to_tokens(&settings, &mut _s);
            ::quote::__private::push_comma(&mut _s);
            ::quote::__private::push_ident(&mut _s, "constructor");
            ::quote::__private::push_colon(&mut _s);
            ::quote::ToTokens::to_tokens(&constructor, &mut _s);
            ::quote::__private::push_comma(&mut _s);
            ::quote::__private::push_ident(&mut _s, "constructor_inferred");
            ::quote::__private::push_colon(&mut _s);
            ::quote::ToTokens::to_tokens(&inferred, &mut _s);
            ::quote::__private::push_comma(&mut _s);
            ::quote::__private::push_ident(&mut _s, "constructor_candidates");
            ::quote::__private::push_colon(&mut _s);
            ::quote::__private::push_and(&mut _s);
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Bracket,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    {
                        use ::quote::__private::ext::*;
                        let mut _first = true;
                        let has_iter = ::quote::__private::HasIterator::<false>;
                        #[allow(unused_mut)]
                        let (mut candidates, i) = candidates.quote_into_iter();
                        let has_iter = has_iter | i;
                        <_ as
                                ::quote::__private::CheckHasIterator<true>>::check(has_iter);
                        while true {
                            let candidates =
                                match candidates.next() {
                                    Some(_x) => ::quote::__private::RepInterp(_x),
                                    None => break,
                                };
                            if !_first { ::quote::__private::push_comma(&mut _s); }
                            _first = false;
                            ::quote::ToTokens::to_tokens(&candidates, &mut _s);
                        }
                    }
                    _s
                });
            ::quote::__private::push_comma(&mut _s);
            ::quote::__private::push_ident(&mut _s, "demoted");
            ::quote::__private::push_colon(&mut _s);
            ::quote::ToTokens::to_tokens(&demoted, &mut _s);
            ::quote::__private::push_comma(&mut _s);
            ::quote::ToTokens::to_tokens(&parts, &mut _s);
            _s
        });
    _s
}quote! {
5501            ::miniextendr_api::registry::ClassBlockEntry {
5502                rust_type: ::core::stringify!(#type_ident),
5503                type_path: || ::core::any::type_name::<#type_ident>(),
5504                r_class_name: #r_class_name_str,
5505                source_file: ::core::file!(),
5506                source_line: #source_line_lit,
5507                source_column: #source_col_lit,
5508                settings: #settings,
5509                constructor: #constructor,
5510                constructor_inferred: #inferred,
5511                constructor_candidates: &[#(#candidates),*],
5512                demoted: #demoted,
5513                #parts
5514            }
5515        }
5516    };
5517    // A block that infers its constructor also registers the rendering in
5518    // which that method is a static method (`demoted`).
5519    let (demoted_static, demoted_ref) = match &demoted_class {
5520        Some((demoted, definitions)) => {
5521            let entry = block_entry(
5522                "",
5523                false,
5524                {
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "core");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "option");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "Option");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "None");
    _s
}quote!(::core::option::Option::None),
5525                class_parts(demoted, definitions),
5526            );
5527            (
5528                {
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::__private::push_ident(&mut _s, "static");
    ::quote::__private::push_ident(&mut _s, "DEMOTED");
    ::quote::__private::push_colon(&mut _s);
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "miniextendr_api");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "registry");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "ClassBlockEntry");
    ::quote::__private::push_eq(&mut _s);
    ::quote::ToTokens::to_tokens(&entry, &mut _s);
    ::quote::__private::push_semi(&mut _s);
    _s
}quote! {
5529                    static DEMOTED: ::miniextendr_api::registry::ClassBlockEntry = #entry;
5530                },
5531                {
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "core");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "option");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "Option");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "Some");
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Parenthesis,
        {
            let mut _s = ::quote::__private::TokenStream::new();
            ::quote::__private::push_and(&mut _s);
            ::quote::__private::push_ident(&mut _s, "DEMOTED");
            _s
        });
    _s
}quote!(::core::option::Option::Some(&DEMOTED)),
5532            )
5533        }
5534        None => (::quote::__private::TokenStream::new()quote!(), {
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "core");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "option");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "Option");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "None");
    _s
}quote!(::core::option::Option::None)),
5535    };
5536    let class_block = block_entry(&constructor, infers_constructor, demoted_ref, parts);
5537    let constructor_check = constructor_check(&parsed);
5538
5539    // The registrations sit in an anonymous `const _` so that any number of
5540    // impl blocks of one type can register theirs; the wrapper writer merges
5541    // them per type (#1776).
5542    let expanded = {
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::__private::push_pound(&mut _s);
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Bracket,
        {
            let mut _s = ::quote::__private::TokenStream::new();
            ::quote::__private::push_ident(&mut _s, "doc");
            ::quote::__private::push_eq(&mut _s);
            ::quote::ToTokens::to_tokens(&source_loc_doc, &mut _s);
            _s
        });
    ::quote::__private::push_pound(&mut _s);
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Bracket,
        {
            let mut _s = ::quote::__private::TokenStream::new();
            ::quote::__private::push_ident(&mut _s, "doc");
            ::quote::__private::push_eq(&mut _s);
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "core");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "concat");
            ::quote::__private::push_bang(&mut _s);
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::__private::parse(&mut _s,
                        "\"Generated from source file `\"");
                    ::quote::__private::push_comma(&mut _s);
                    ::quote::__private::push_colon2(&mut _s);
                    ::quote::__private::push_ident(&mut _s, "core");
                    ::quote::__private::push_colon2(&mut _s);
                    ::quote::__private::push_ident(&mut _s, "file");
                    ::quote::__private::push_bang(&mut _s);
                    ::quote::__private::push_group(&mut _s,
                        ::quote::__private::Delimiter::Parenthesis,
                        ::quote::__private::TokenStream::new());
                    ::quote::__private::push_comma(&mut _s);
                    ::quote::__private::parse(&mut _s, "\"`.\"");
                    _s
                });
            _s
        });
    ::quote::ToTokens::to_tokens(&original_impl, &mut _s);
    ::quote::ToTokens::to_tokens(&param_warnings, &mut _s);
    {
        use ::quote::__private::ext::*;
        let has_iter = ::quote::__private::HasIterator::<false>;
        #[allow(unused_mut)]
        let (mut c_wrappers, i) = c_wrappers.quote_into_iter();
        let has_iter = has_iter | i;
        <_ as ::quote::__private::CheckHasIterator<true>>::check(has_iter);
        while true {
            let c_wrappers =
                match c_wrappers.next() {
                    Some(_x) => ::quote::__private::RepInterp(_x),
                    None => break,
                };
            ::quote::ToTokens::to_tokens(&c_wrappers, &mut _s);
        }
    }
    ::quote::ToTokens::to_tokens(&fallback_entries, &mut _s);
    ::quote::ToTokens::to_tokens(&trait_impls, &mut _s);
    ::quote::ToTokens::to_tokens(&constructor_check, &mut _s);
    {
        use ::quote::__private::ext::*;
        let has_iter = ::quote::__private::HasIterator::<false>;
        #[allow(unused_mut)]
        let (mut cfg_attrs, i) = cfg_attrs.quote_into_iter();
        let has_iter = has_iter | i;
        <_ as ::quote::__private::CheckHasIterator<true>>::check(has_iter);
        while true {
            let cfg_attrs =
                match cfg_attrs.next() {
                    Some(_x) => ::quote::__private::RepInterp(_x),
                    None => break,
                };
            ::quote::ToTokens::to_tokens(&cfg_attrs, &mut _s);
        }
    }
    ::quote::__private::push_ident(&mut _s, "const");
    ::quote::__private::push_underscore(&mut _s);
    ::quote::__private::push_colon(&mut _s);
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Parenthesis,
        ::quote::__private::TokenStream::new());
    ::quote::__private::push_eq(&mut _s);
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Brace,
        {
            let mut _s = ::quote::__private::TokenStream::new();
            ::quote::ToTokens::to_tokens(&demoted_static, &mut _s);
            ::quote::__private::push_pound(&mut _s);
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Bracket,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::__private::push_ident(&mut _s, "cfg_attr");
                    ::quote::__private::push_group(&mut _s,
                        ::quote::__private::Delimiter::Parenthesis,
                        {
                            let mut _s = ::quote::__private::TokenStream::new();
                            ::quote::__private::push_ident(&mut _s, "not");
                            ::quote::__private::push_group(&mut _s,
                                ::quote::__private::Delimiter::Parenthesis,
                                {
                                    let mut _s = ::quote::__private::TokenStream::new();
                                    ::quote::__private::push_ident(&mut _s, "target_arch");
                                    ::quote::__private::push_eq(&mut _s);
                                    ::quote::__private::parse(&mut _s, "\"wasm32\"");
                                    _s
                                });
                            ::quote::__private::push_comma(&mut _s);
                            ::quote::__private::push_colon2(&mut _s);
                            ::quote::__private::push_ident(&mut _s, "miniextendr_api");
                            ::quote::__private::push_colon2(&mut _s);
                            ::quote::__private::push_ident(&mut _s, "linkme");
                            ::quote::__private::push_colon2(&mut _s);
                            ::quote::__private::push_ident(&mut _s,
                                "distributed_slice");
                            ::quote::__private::push_group(&mut _s,
                                ::quote::__private::Delimiter::Parenthesis,
                                {
                                    let mut _s = ::quote::__private::TokenStream::new();
                                    ::quote::__private::push_colon2(&mut _s);
                                    ::quote::__private::push_ident(&mut _s, "miniextendr_api");
                                    ::quote::__private::push_colon2(&mut _s);
                                    ::quote::__private::push_ident(&mut _s, "registry");
                                    ::quote::__private::push_colon2(&mut _s);
                                    ::quote::__private::push_ident(&mut _s, "MX_CLASS_BLOCKS");
                                    _s
                                });
                            ::quote::__private::push_comma(&mut _s);
                            ::quote::__private::push_ident(&mut _s, "linkme");
                            ::quote::__private::push_group(&mut _s,
                                ::quote::__private::Delimiter::Parenthesis,
                                {
                                    let mut _s = ::quote::__private::TokenStream::new();
                                    ::quote::__private::push_ident(&mut _s, "crate");
                                    ::quote::__private::push_eq(&mut _s);
                                    ::quote::__private::push_colon2(&mut _s);
                                    ::quote::__private::push_ident(&mut _s, "miniextendr_api");
                                    ::quote::__private::push_colon2(&mut _s);
                                    ::quote::__private::push_ident(&mut _s, "linkme");
                                    _s
                                });
                            _s
                        });
                    _s
                });
            ::quote::__private::push_ident(&mut _s, "static");
            ::quote::__private::push_ident(&mut _s, "CLASS_BLOCK");
            ::quote::__private::push_colon(&mut _s);
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "miniextendr_api");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "registry");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "ClassBlockEntry");
            ::quote::__private::push_eq(&mut _s);
            ::quote::ToTokens::to_tokens(&class_block, &mut _s);
            ::quote::__private::push_semi(&mut _s);
            ::quote::__private::push_pound(&mut _s);
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Bracket,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::__private::push_ident(&mut _s, "cfg_attr");
                    ::quote::__private::push_group(&mut _s,
                        ::quote::__private::Delimiter::Parenthesis,
                        {
                            let mut _s = ::quote::__private::TokenStream::new();
                            ::quote::__private::push_ident(&mut _s, "not");
                            ::quote::__private::push_group(&mut _s,
                                ::quote::__private::Delimiter::Parenthesis,
                                {
                                    let mut _s = ::quote::__private::TokenStream::new();
                                    ::quote::__private::push_ident(&mut _s, "target_arch");
                                    ::quote::__private::push_eq(&mut _s);
                                    ::quote::__private::parse(&mut _s, "\"wasm32\"");
                                    _s
                                });
                            ::quote::__private::push_comma(&mut _s);
                            ::quote::__private::push_colon2(&mut _s);
                            ::quote::__private::push_ident(&mut _s, "miniextendr_api");
                            ::quote::__private::push_colon2(&mut _s);
                            ::quote::__private::push_ident(&mut _s, "linkme");
                            ::quote::__private::push_colon2(&mut _s);
                            ::quote::__private::push_ident(&mut _s,
                                "distributed_slice");
                            ::quote::__private::push_group(&mut _s,
                                ::quote::__private::Delimiter::Parenthesis,
                                {
                                    let mut _s = ::quote::__private::TokenStream::new();
                                    ::quote::__private::push_colon2(&mut _s);
                                    ::quote::__private::push_ident(&mut _s, "miniextendr_api");
                                    ::quote::__private::push_colon2(&mut _s);
                                    ::quote::__private::push_ident(&mut _s, "registry");
                                    ::quote::__private::push_colon2(&mut _s);
                                    ::quote::__private::push_ident(&mut _s, "MX_CLASS_NAMES");
                                    _s
                                });
                            ::quote::__private::push_comma(&mut _s);
                            ::quote::__private::push_ident(&mut _s, "linkme");
                            ::quote::__private::push_group(&mut _s,
                                ::quote::__private::Delimiter::Parenthesis,
                                {
                                    let mut _s = ::quote::__private::TokenStream::new();
                                    ::quote::__private::push_ident(&mut _s, "crate");
                                    ::quote::__private::push_eq(&mut _s);
                                    ::quote::__private::push_colon2(&mut _s);
                                    ::quote::__private::push_ident(&mut _s, "miniextendr_api");
                                    ::quote::__private::push_colon2(&mut _s);
                                    ::quote::__private::push_ident(&mut _s, "linkme");
                                    _s
                                });
                            _s
                        });
                    _s
                });
            ::quote::__private::push_ident(&mut _s, "static");
            ::quote::__private::push_ident(&mut _s, "CLASS_NAME");
            ::quote::__private::push_colon(&mut _s);
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "miniextendr_api");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "registry");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "ClassNameEntry");
            ::quote::__private::push_eq(&mut _s);
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "miniextendr_api");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "registry");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "ClassNameEntry");
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Brace,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::__private::push_ident(&mut _s, "rust_type");
                    ::quote::__private::push_colon(&mut _s);
                    ::quote::__private::push_colon2(&mut _s);
                    ::quote::__private::push_ident(&mut _s, "core");
                    ::quote::__private::push_colon2(&mut _s);
                    ::quote::__private::push_ident(&mut _s, "stringify");
                    ::quote::__private::push_bang(&mut _s);
                    ::quote::__private::push_group(&mut _s,
                        ::quote::__private::Delimiter::Parenthesis,
                        {
                            let mut _s = ::quote::__private::TokenStream::new();
                            ::quote::ToTokens::to_tokens(&type_ident, &mut _s);
                            _s
                        });
                    ::quote::__private::push_comma(&mut _s);
                    ::quote::__private::push_ident(&mut _s, "r_class_name");
                    ::quote::__private::push_colon(&mut _s);
                    ::quote::ToTokens::to_tokens(&r_class_name_str, &mut _s);
                    ::quote::__private::push_comma(&mut _s);
                    ::quote::__private::push_ident(&mut _s, "class_system");
                    ::quote::__private::push_colon(&mut _s);
                    ::quote::ToTokens::to_tokens(&class_system_str, &mut _s);
                    ::quote::__private::push_comma(&mut _s);
                    ::quote::__private::push_ident(&mut _s, "field_syntax");
                    ::quote::__private::push_colon(&mut _s);
                    ::quote::ToTokens::to_tokens(&field_syntax, &mut _s);
                    ::quote::__private::push_comma(&mut _s);
                    _s
                });
            ::quote::__private::push_semi(&mut _s);
            _s
        });
    ::quote::__private::push_semi(&mut _s);
    _s
}quote! {
5543        #[doc = #source_loc_doc]
5544        #[doc = ::core::concat!("Generated from source file `", ::core::file!(), "`.")]
5545        #original_impl
5546
5547        // Warnings for @param tags on impl blocks
5548        #param_warnings
5549
5550        // C wrappers and call method defs
5551        #(#c_wrappers)*
5552        #fallback_entries
5553
5554        // Forwarding trait impls for as.<class>() coercion methods
5555        #trait_impls
5556
5557        #constructor_check
5558
5559        #(#cfg_attrs)*
5560        const _: () = {
5561            #demoted_static
5562
5563            // The impl block's share of the R class, merged with the type's
5564            // other impl blocks by the wrapper writer.
5565            #[cfg_attr(not(target_arch = "wasm32"), ::miniextendr_api::linkme::distributed_slice(::miniextendr_api::registry::MX_CLASS_BLOCKS), linkme(crate = ::miniextendr_api::linkme))]
5566            static CLASS_BLOCK: ::miniextendr_api::registry::ClassBlockEntry = #class_block;
5567
5568            // Class name registration for cross-reference placeholder resolution.
5569            // Maps the Rust type name to the R-visible class name at link time.
5570            #[cfg_attr(not(target_arch = "wasm32"), ::miniextendr_api::linkme::distributed_slice(::miniextendr_api::registry::MX_CLASS_NAMES), linkme(crate = ::miniextendr_api::linkme))]
5571            static CLASS_NAME: ::miniextendr_api::registry::ClassNameEntry =
5572                ::miniextendr_api::registry::ClassNameEntry {
5573                    rust_type: ::core::stringify!(#type_ident),
5574                    r_class_name: #r_class_name_str,
5575                    class_system: #class_system_str,
5576                    field_syntax: #field_syntax,
5577                };
5578        };
5579    };
5580
5581    expanded.into()
5582}
5583
5584/// The checks of the members a block generates, which `expand_impl` runs on
5585/// each rendering of the block.
5586fn check_class_members(parsed: &ParsedImpl) -> syn::Result<()> {
5587    // Every R formal must parse, be distinct, and leave the names the class
5588    // system's wrapper binds itself (receiver, R6 `self` / `private`) alone.
5589    check_method_formals(parsed)?;
5590    check_env_member_collisions(parsed)?;
5591    check_r6_initialize_collision(parsed)?;
5592    check_r6_static_collisions(parsed)?;
5593    check_s4_generic_collisions(parsed)?;
5594    // A replacement method (`s3(generic = "[[<-")`) takes `value` last.
5595    check_s3_replacement_methods(parsed)?;
5596    // `dots = empty | forward(..) | data` needs a `...` to describe (#2157).
5597    check_dots_policies(parsed)?;
5598    // Detect S7 fast-path shortcut name collisions (#986). The shortcut
5599    // `<ClassName>_<method>` shares a namespace with the standalone functions
5600    // emitted for static methods — an `r_name` override (or a static method
5601    // literally named like an instance method) can make two definitions clash,
5602    // with the last one silently winning. Fail loudly at compile time instead.
5603    check_s7_shortcut_collisions(parsed)?;
5604    // S7 only registers a method whose formals start with the generic's
5605    // dispatch arguments (`s7(dispatch)`, the `e1, e2` of an Ops operator);
5606    // anything else would stop the package from loading.
5607    check_s7_dispatch(parsed)
5608}
5609
5610/// The block's R wrapper, in the parts the wrapper writer merges per type
5611/// (#1776), with the `as.<class>()` coercion methods (any class system).
5612fn generate_class_wrapper(parsed: &ParsedImpl) -> ClassWrapper {
5613    let mut class = match parsed.class_system {
5614        ClassSystem::Env => generate_env_class(parsed),
5615        ClassSystem::R6 => generate_r6_class(parsed),
5616        ClassSystem::S3 => generate_s3_class(parsed),
5617        ClassSystem::S7 => generate_s7_class(parsed),
5618        ClassSystem::S4 => generate_s4_class(parsed),
5619        ClassSystem::Vctrs => generate_vctrs_class(parsed),
5620    };
5621    let as_coercion_wrappers = generate_as_coercion_methods(parsed);
5622    if !as_coercion_wrappers.is_empty() {
5623        class.members.push(String::new());
5624        class.members.push(as_coercion_wrappers);
5625    }
5626    class
5627}
5628
5629/// The compile-time half of the constructor decision. A block that declares
5630/// the constructor implements the hidden
5631/// `miniextendr_api::registry::ClassConstructor` for its type, under a
5632/// type private to the block. A block with several constructor candidates
5633/// and none declared requires that impl, once per candidate and spanned at
5634/// it, so the type compiles only when another of its impl blocks declares
5635/// the constructor; the error names each candidate. Two candidates in two
5636/// blocks are the wrapper writer's to refuse.
5637fn constructor_check(parsed: &ParsedImpl) -> TokenStream {
5638    let cfg_attrs = &parsed.cfg_attrs;
5639    let (impl_generics, _, where_clause) = parsed.original_impl.generics.split_for_impl();
5640    let self_ty = &parsed.original_impl.self_ty;
5641    if parsed.declared_constructor().is_some() {
5642        return {
    let mut _s = ::quote::__private::TokenStream::new();
    {
        use ::quote::__private::ext::*;
        let has_iter = ::quote::__private::HasIterator::<false>;
        #[allow(unused_mut)]
        let (mut cfg_attrs, i) = cfg_attrs.quote_into_iter();
        let has_iter = has_iter | i;
        <_ as ::quote::__private::CheckHasIterator<true>>::check(has_iter);
        while true {
            let cfg_attrs =
                match cfg_attrs.next() {
                    Some(_x) => ::quote::__private::RepInterp(_x),
                    None => break,
                };
            ::quote::ToTokens::to_tokens(&cfg_attrs, &mut _s);
        }
    }
    ::quote::__private::push_ident(&mut _s, "const");
    ::quote::__private::push_underscore(&mut _s);
    ::quote::__private::push_colon(&mut _s);
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Parenthesis,
        ::quote::__private::TokenStream::new());
    ::quote::__private::push_eq(&mut _s);
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Brace,
        {
            let mut _s = ::quote::__private::TokenStream::new();
            ::quote::__private::push_ident(&mut _s, "struct");
            ::quote::__private::push_ident(&mut _s, "__MxConstructorBlock");
            ::quote::__private::push_semi(&mut _s);
            ::quote::__private::push_ident(&mut _s, "impl");
            ::quote::ToTokens::to_tokens(&impl_generics, &mut _s);
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "miniextendr_api");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "registry");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "ClassConstructor");
            ::quote::__private::push_lt(&mut _s);
            ::quote::__private::push_ident(&mut _s, "__MxConstructorBlock");
            ::quote::__private::push_gt(&mut _s);
            ::quote::__private::push_ident(&mut _s, "for");
            ::quote::ToTokens::to_tokens(&self_ty, &mut _s);
            ::quote::ToTokens::to_tokens(&where_clause, &mut _s);
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Brace,
                ::quote::__private::TokenStream::new());
            _s
        });
    ::quote::__private::push_semi(&mut _s);
    _s
}quote! {
5643            #(#cfg_attrs)*
5644            const _: () = {
5645                // Named so that no user type spelled in `#self_ty` is shadowed.
5646                struct __MxConstructorBlock;
5647                impl #impl_generics
5648                    ::miniextendr_api::registry::ClassConstructor<__MxConstructorBlock>
5649                    for #self_ty #where_clause {}
5650            };
5651        };
5652    }
5653    if parsed.constructor_candidates().nth(1).is_none() {
5654        return TokenStream::new();
5655    }
5656    // The type spanned at each candidate, so the error points there.
5657    let requirements = parsed.constructor_candidates().map(|m| {
5658        let mut ty = parsed.type_ident.clone();
5659        ty.set_span(m.ident.span());
5660        {
    let mut _s = ::quote::__private::TokenStream::new();
    let _span: ::quote::__private::Span =
        ::quote::__private::get_span(m.ident.span()).__into_span();
    ::quote::__private::push_colon2_spanned(&mut _s, _span);
    ::quote::__private::push_ident_spanned(&mut _s, _span, "miniextendr_api");
    ::quote::__private::push_colon2_spanned(&mut _s, _span);
    ::quote::__private::push_ident_spanned(&mut _s, _span, "registry");
    ::quote::__private::push_colon2_spanned(&mut _s, _span);
    ::quote::__private::push_ident_spanned(&mut _s, _span,
        "require_class_constructor");
    ::quote::__private::push_colon2_spanned(&mut _s, _span);
    ::quote::__private::push_lt_spanned(&mut _s, _span);
    ::quote::ToTokens::to_tokens(&ty, &mut _s);
    ::quote::__private::push_comma_spanned(&mut _s, _span);
    ::quote::__private::push_underscore_spanned(&mut _s, _span);
    ::quote::__private::push_gt_spanned(&mut _s, _span);
    ::quote::__private::push_group_spanned(&mut _s, _span,
        ::quote::__private::Delimiter::Parenthesis,
        {
            let _: ::quote::__private::Span =
                ::quote::__private::get_span(_span).__into_span();
            ::quote::__private::TokenStream::new()
        });
    ::quote::__private::push_semi_spanned(&mut _s, _span);
    _s
}quote::quote_spanned! {m.ident.span()=>
5661            ::miniextendr_api::registry::require_class_constructor::<#ty, _>();
5662        }
5663    });
5664    {
    let mut _s = ::quote::__private::TokenStream::new();
    {
        use ::quote::__private::ext::*;
        let has_iter = ::quote::__private::HasIterator::<false>;
        #[allow(unused_mut)]
        let (mut cfg_attrs, i) = cfg_attrs.quote_into_iter();
        let has_iter = has_iter | i;
        <_ as ::quote::__private::CheckHasIterator<true>>::check(has_iter);
        while true {
            let cfg_attrs =
                match cfg_attrs.next() {
                    Some(_x) => ::quote::__private::RepInterp(_x),
                    None => break,
                };
            ::quote::ToTokens::to_tokens(&cfg_attrs, &mut _s);
        }
    }
    ::quote::__private::push_ident(&mut _s, "const");
    ::quote::__private::push_underscore(&mut _s);
    ::quote::__private::push_colon(&mut _s);
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Parenthesis,
        ::quote::__private::TokenStream::new());
    ::quote::__private::push_eq(&mut _s);
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Brace,
        {
            let mut _s = ::quote::__private::TokenStream::new();
            ::quote::__private::push_pound(&mut _s);
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Bracket,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::__private::push_ident(&mut _s, "allow");
                    ::quote::__private::push_group(&mut _s,
                        ::quote::__private::Delimiter::Parenthesis,
                        {
                            let mut _s = ::quote::__private::TokenStream::new();
                            ::quote::__private::push_ident(&mut _s, "dead_code");
                            _s
                        });
                    _s
                });
            ::quote::__private::push_ident(&mut _s, "fn");
            ::quote::__private::push_ident(&mut _s, "constructor_check");
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                ::quote::__private::TokenStream::new());
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Brace,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    {
                        use ::quote::__private::ext::*;
                        let has_iter = ::quote::__private::HasIterator::<false>;
                        #[allow(unused_mut)]
                        let (mut requirements, i) = requirements.quote_into_iter();
                        let has_iter = has_iter | i;
                        <_ as
                                ::quote::__private::CheckHasIterator<true>>::check(has_iter);
                        while true {
                            let requirements =
                                match requirements.next() {
                                    Some(_x) => ::quote::__private::RepInterp(_x),
                                    None => break,
                                };
                            ::quote::ToTokens::to_tokens(&requirements, &mut _s);
                        }
                    }
                    _s
                });
            _s
        });
    ::quote::__private::push_semi(&mut _s);
    _s
}quote! {
5665        #(#cfg_attrs)*
5666        const _: () = {
5667            #[allow(dead_code)]
5668            fn constructor_check() {
5669                #(#requirements)*
5670            }
5671        };
5672    }
5673}
5674
5675#[cfg(test)]
5676mod tests;
5677// endregion