Skip to main content

miniextendr_macros/
externalptr_derive.rs

1//! # `#[derive(ExternalPtr)]` - ExternalPtr Support
2//!
3//! This module implements the `#[derive(ExternalPtr)]` macro which generates
4//! a `TypedExternal` impl for use with `ExternalPtr<T>`.
5//!
6//! Trait ABI wrapper infrastructure is automatically generated when you use
7//! `#[miniextendr]` on `impl Trait for Type` blocks.
8//!
9//! ## Sidecar codegen vs `#[miniextendr]` fns
10//!
11//! `#[derive(ExternalPtr)]` with `#[r_data]` fields emits a pair of C/R
12//! wrappers per sidecar field (`<Type>_get_<field>` /
13//! `<Type>_set_<field>`). **These wrappers do NOT go through
14//! `c_wrapper_builder::CWrapperContext`** — they are hand-rolled in this
15//! module so they can present the simplest signature R expects from a slot
16//! accessor:
17//!
18//! | Aspect | `#[miniextendr]` fn / method (`c_wrapper_builder`) | Sidecar accessor (here) |
19//! |---|---|---|
20//! | First C param | `__miniextendr_call: SEXP` | none |
21//! | Last C param | — | `require_live: SEXP`, the restored-pointer flag (#1891) |
22//! | C `numArgs` | 1 + user args | `2` (getter) / `3` (setter) |
23//! | R `.Call` | `.Call(C_…, .call = sys.call(), …)` | `.Call(C_…, x, FALSE)` / `.Call(C_…, x, value, FALSE)`; the field methods pass `TRUE` |
24//! | Error transport | tagged-condition SEXP via `with_r_unwind_protect(…, Some(call))` | tagged-condition SEXP via `with_r_unwind_protect(…, None)`; the R guard's `sys.call()` supplies attribution |
25//!
26//! **A sidecar R wrapper passes no `.call` argument.** The C function doesn't
27//! have a slot for it: R would throw "Incorrect number of arguments" at
28//! runtime (#344, #348).
29//!
30//! **The `require_live` flag** is how the generated `$` / `[[` / `$<-` /
31//! `[[<-` field methods refuse a pointer restored from a saved session while
32//! the standalone accessors, the R6 active bindings and the S7 properties
33//! keep reading its `Sidecar` values (#1891): one accessor per field, no
34//! extra `.Call()`. Only a `Sidecar` slot's accessors read the flag
35//! (`externalptr::sidecar_require_live`); the other kinds need the live Rust
36//! value whatever it says.
37//!
38//! ## Usage
39//!
40//! ### Basic (no traits)
41//!
42//! ```ignore
43//! #[derive(ExternalPtr)]
44//! struct MyData {
45//!     value: i32,
46//! }
47//! // Generates: impl TypedExternal for MyData { ... }
48//! ```
49//!
50//! ### With R Sidecar Slots and Class System
51//!
52//! The `#[r_data]` attribute marks fields that get R accessors. Every public
53//! slot gets the standalone `Type_get_field(x)` / `Type_set_field(x, value)`,
54//! which take the pointer or any object carrying it in `.ptr` (a list, an
55//! environment, an R6 / S4 / S7 handle). `#[externalptr(...)]` names the class
56//! system; the impl's `r_data_accessors` option also wires the slots into the
57//! class. Under the S3, S4 and env field syntax, `$` / `[[` are the class's R
58//! API and the standalone accessors are internal: no `@export`, no "Type
59//! Sidecar Accessors" page (#1900). The derive emits both renderings in a
60//! `SidecarAccessorsEntry`; the wrapper writer picks one by the class's
61//! `ClassNameEntry::field_syntax`, since the impl block is a separate
62//! expansion. Otherwise they are exported on the type's page.
63//!
64//! | Class System | Attribute | R Accessors |
65//! |--------------|-----------|-------------|
66//! | Environment | `#[externalptr(env)]` (default) + `#[miniextendr(env(r_data_accessors))]` | `obj$field` / `obj[["field"]]` next to the methods, `obj$field <- value` |
67//! | R6 | `#[externalptr(r6)]` + `#[miniextendr(r6(r_data_accessors))]` | Active bindings, `obj$field` / `obj$field <- value` |
68//! | S3 | `#[externalptr(s3)]` + `#[miniextendr(s3(r_data_accessors))]` | `$` / `[[` / `$<-` / `[[<-` methods |
69//! | S4 | `#[externalptr(s4)]` + `#[miniextendr(s4(r_data_accessors))]` | `$` / `$<-` methods |
70//! | S7 | `#[externalptr(s7)]` + `#[miniextendr(s7(r_data_accessors))]` | Properties, `obj@field` / `obj@field <- value` |
71//!
72//! For S3, S4 and env, `r_data_accessors = "get"` generates the getters only.
73//! The `$` / `[[` methods share the helpers [`generate_field_syntax_helpers`]
74//! prepares for every type with R accessors (#1848). The wrapper writer includes
75//! them only when an impl enables S3, S4 or env field syntax.
76//!
77//! Standalone setters and R6 active-binding setters return the receiver invisibly
78//! by default. `#[r_data(setter = "visible")]` exposes that return value;
79//! `setter = "invisible"` explicitly requests the default. These options also
80//! control S7 property setters, whose unmarked return remains visible.
81//! R assignment itself stays invisible regardless of the setter's choice.
82//!
83//! Four field tiers are supported:
84//!
85//! 1. **Rooted values** (`Sidecar<T>`) - kept in the external pointer's
86//!    protection list, which roots them and serializes them with the
87//!    pointer. The derive generates typed accessors as associated functions
88//!    of the struct, with the field's visibility, taking a view or the
89//!    `ExternalPtr` handle (the struct holds nothing that points back at its
90//!    pointer): `#[r_data(ref)]` a getter
91//!    `fn f(ptr: &impl SidecarOwner<Self>) -> T`, `#[r_data(mut)]` a setter
92//!    `fn set_f(ptr: &mut impl SidecarOwnerMut<Self>, value: T)`,
93//!    `#[r_data(ref, mut)]` or a bare `#[r_data]` both. A method that needs
94//!    the values takes a view as its receiver (`self: ExternalPtrRef<'_, Self>`
95//!    / `mut self: ExternalPtrMut<'_, Self>`) and calls `Self::f(&self)`; see
96//!    `miniextendr_api::externalptr::Sidecar`. A bare `SEXP` field is
97//!    refused: the struct can't root it.
98//! 2. **Scalars** (`i32`, `f64`, `bool`, `u8`) - struct fields, returned as a
99//!    length-1 vector and written with `Rf_as*`
100//! 3. **Conversion types** (anything else) - struct fields, converted with the
101//!    `IntoR`/`TryFromSexp` traits on every read and write
102//! 4. **Computed fields** (`#[r_data(get = "Self::f")] pub f: Computed`,
103//!    #1883) - no value in the struct; the getter calls the named
104//!    `fn(&Self) -> impl IntoR` on the live Rust value. There is no setter:
105//!    no `Type_set_f()`, and under the get/set field syntax an assignment
106//!    raises `miniextendr_read_only_field`. Only `pub`, and not under
107//!    `#[externalptr(r6)]` / `#[externalptr(s7)]`, whose bindings and
108//!    properties need a setter (#1888).
109//!
110//! `#[r_data(name = "keys")]` gives a field its R name (#1891): the field
111//! list, the `$` / `[[` labels, the R6 binding / S7 property and the
112//! standalone `Type_get_keys()` / `Type_set_keys()` use it; the C symbols and
113//! the Rust accessors keep the Rust identifier. The name must be a syntactic
114//! R name, unique in the type, and not `.ptr`. A `Sidecar` field's R name is
115//! also the save format (#2215): a save names the field's slot after it
116//! (`SIDECAR_NAMES`), and a reload or a rebuild hook finds the slot by it. So
117//! renaming the Rust field keeps earlier saves readable (give it the old
118//! ident as its `name` if it had none), while renaming the R name orphans
119//! the slot in every earlier save.
120//!
121//! Scalar, conversion and computed values come from the Rust struct, behind
122//! the pointer's address, which `saveRDS` does not write. The R accessors of
123//! a `Sidecar` field read and write the protection list by slot index (a
124//! restored pointer's by the field's R name, #2187): the
125//! setter validates the value with `TryFromSexp::<T>` and stores the value R
126//! gave, and the standalone accessors keep working on a pointer `readRDS()`
127//! brought back without an address (checked by the type ID in `prot[0]`).
128//!
129//! ### A saved and restored object
130//!
131//! `saveRDS()` / `serialize()` keep the pointer's `prot` list (the `Sidecar`
132//! values and the user slot) but not the Rust value. On the restored pointer
133//! the standalone `Sidecar` accessors, the R6 active bindings and the S7
134//! properties work; the generated `$` / `[[` / `$<-` / `[[<-` field methods
135//! refuse it whatever the field (they pass the accessors' `require_live`
136//! flag, #1891), and the accessor of a struct or computed field, like every
137//! method, raises the classed error `miniextendr_restored_no_value` ("restored
138//! from a saved session and has no Rust value; re-create it"). A pointer
139//! another version of the crate saved is refused by every accessor with
140//! `miniextendr_restored_other_version`, which names both versions.
141//! `#[externalptr(restored(class = ["pkg_saved", "pkg_error"], message =
142//! "..."))]` puts the package's classes in front of those (which stay) and
143//! replaces the message, on every restored refusal of the type. Its
144//! `refuse = "every_read"` (#1901) extends the field methods' refusal to the
145//! reads that never call an accessor: `names()` and an unknown `$` / `[[`
146//! name check the pointer first through one more C function
147//! (`.rdata_live_<Type>`, `externalptr::sidecar_r_live`), and `.DollarNames()`
148//! returns `character(0)` for a restored pointer. R's `y <- x`
149//! shares the one Rust value and the one `prot` list: a write through `y` is
150//! visible through `x`.
151//!
152//! `#[externalptr(field_error(class = ["pkg_error_field", "pkg_error"]))]`
153//! (#1899) puts the package's classes in front of the field readers' own
154//! errors: a position that picks no field (`subscriptOutOfBoundsError`
155//! stays behind them, and `0` / a negative position raise it too), a pointer
156//! of another type, and a receiver of the class that carries no pointer
157//! (`miniextendr_no_handle`, which also gets the `restored(class)` classes).
158//! They reach R as `.rdata_error_class_<Type>` and Rust as
159//! `TypedExternal::FIELD_ERROR_CLASS`. `field_error(no_field_hint = "...")`
160//! (#2112) adds the package's sentence after the field list of the
161//! `miniextendr_no_field` error, each `%s` in it the name asked for.
162//!
163//! ```ignore
164//! #[derive(ExternalPtr)]
165//! #[externalptr(r6)]
166//! pub struct MyType {
167//!     pub x: i32,
168//!
169//!     #[r_data]
170//!     r: RSidecar,  // Selector - enables R accessors for this type
171//!
172//!     #[r_data(ref, mut)]
173//!     pub keys: Sidecar<Vec<i32>>,  // In the protection list; `MyType::keys(&ptr)` / `MyType::set_keys(&mut ptr, v)`
174//!
175//!     #[r_data]
176//!     pub count: i32,  // Scalar struct field
177//!
178//!     #[r_data]
179//!     pub name: String,  // Conversion: uses IntoR/TryFromSexp
180//! }
181//!
182//! #[miniextendr(r6(r_data_accessors))]
183//! impl MyType {
184//!     pub fn new(n: i32) -> Self {
185//!         MyType { x: 0, r: RSidecar, keys: Sidecar::new((1..=n).collect()), count: n, name: String::new() }
186//!     }
187//!     pub fn key_count(self: ExternalPtrRef<'_, Self>) -> usize {
188//!         Self::keys(&self).len()
189//!     }
190//! }
191//! // Generates: active bindings `keys`, `count`, `name` in the R6Class
192//! ```
193//!
194//! ### Trait ABI wiring
195//!
196//! ```ignore
197//! #[derive(ExternalPtr)]
198//! struct MyCounter {
199//!     value: i32,
200//! }
201//!
202//! #[miniextendr]
203//! impl Counter for MyCounter { /* ... */ }
204//! ```
205//!
206//! ## Generated Types
207//!
208//! The runtime's generic `ErasedObject<MyCounter>` owns the value and its
209//! `mx_erased` header; no per-type wrapper struct or drop function is emitted.
210//!
211//! ### Base Vtable
212//!
213//! ```ignore
214//! static __MX_BASE_VTABLE_MyCounter: mx_base_vtable = mx_base_vtable {
215//!     drop: ErasedObject::<MyCounter>::drop_raw,
216//!     concrete_tag: __MX_TAG_MyCounter,
217//!     query: ErasedObject::<MyCounter>::query,
218//!     data_offset: ErasedObject::<MyCounter>::DATA_OFFSET,
219//! };
220//! ```
221//!
222//! Trait implementations register their vtables with the dispatch registry.
223//! The shared `registry::universal_query` looks them up by concrete type tag
224//! and trait tag; the derive emits no per-type query function.
225
226use proc_macro2::{Span, TokenStream};
227use syn::{DeriveInput, Field, Ident, Visibility};
228
229use crate::miniextendr_impl::ClassSystem;
230
231/// The package's condition classes and message for a pointer of the type
232/// restored from a saved session, `#[externalptr(restored(class = "..." |
233/// ["...", "..."], message = "...", refuse = "every_read"))]` (#1891,
234/// #1901). Each is optional; the derive emits
235/// `TypedExternal::RESTORED_ERROR_CLASS` / `RESTORED_ERROR_MESSAGE` for the
236/// ones given.
237#[derive(#[automatically_derived]
impl ::core::fmt::Debug for RestoredOptions {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field3_finish(f,
            "RestoredOptions", "class", &self.class, "message", &self.message,
            "every_read", &&self.every_read)
    }
}Debug, #[automatically_derived]
impl ::core::default::Default for RestoredOptions {
    #[inline]
    fn default() -> Self {
        RestoredOptions {
            class: ::core::default::Default::default(),
            message: ::core::default::Default::default(),
            every_read: ::core::default::Default::default(),
        }
    }
}Default, #[automatically_derived]
impl ::core::clone::Clone for RestoredOptions {
    #[inline]
    fn clone(&self) -> Self {
        RestoredOptions {
            class: ::core::clone::Clone::clone(&self.class),
            message: ::core::clone::Clone::clone(&self.message),
            every_read: ::core::clone::Clone::clone(&self.every_read),
        }
    }
}Clone, #[automatically_derived]
impl ::core::marker::StructuralPartialEq for RestoredOptions { }
#[automatically_derived]
impl ::core::cmp::PartialEq for RestoredOptions {
    #[inline]
    fn eq(&self, other: &Self) -> bool {
        self.every_read == other.every_read && self.class == other.class &&
            self.message == other.message
    }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for RestoredOptions {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_fields_are_eq(&self) {
        let _: ::core::cmp::AssertParamIsEq<Vec<String>>;
        let _: ::core::cmp::AssertParamIsEq<Option<String>>;
        let _: ::core::cmp::AssertParamIsEq<bool>;
    }
}Eq)]
238struct RestoredOptions {
239    /// The classes, in order, put in front of the `miniextendr_restored*`
240    /// classes.
241    class: Vec<String>,
242    /// The message, replacing miniextendr's in both restored cases.
243    message: Option<String>,
244    /// `refuse = "every_read"`: the field methods refuse a restored pointer
245    /// on the reads that never reach a field accessor too (`names()`, a name
246    /// that is not a field), and `.DollarNames()` offers nothing for it
247    /// (#1901). The derive emits the liveness check they call.
248    every_read: bool,
249}
250
251/// What `#[externalptr(...)]` says about the type.
252struct ExternalPtrAttrs {
253    class_system: ClassSystem,
254    restored: RestoredOptions,
255    /// `field_error(class = ..., message = ...)`: the field readers' own
256    /// errors (#1899, #1920).
257    field_error: FieldErrorOptions,
258    /// `rebuild = path`: the hook that rebuilds a pointer restored from a
259    /// saved session (#2146).
260    rebuild: Option<syn::Path>,
261    /// `any_version`, next to `rebuild`: the hook also rebuilds a save by
262    /// another version of the crate (#2187).
263    any_version: bool,
264}
265
266/// `#[externalptr(field_error(class = ..., message = ..., no_field_hint = ...))]`.
267#[derive(#[automatically_derived]
impl ::core::default::Default for FieldErrorOptions {
    #[inline]
    fn default() -> Self {
        FieldErrorOptions {
            class: ::core::default::Default::default(),
            message: ::core::default::Default::default(),
            no_field_hint: ::core::default::Default::default(),
        }
    }
}Default)]
268struct FieldErrorOptions {
269    /// The classes put in front of the errors the R field readers raise
270    /// themselves (#1899), `TypedExternal::FIELD_ERROR_CLASS`.
271    class: Vec<String>,
272    /// The package's message for a receiver of the class whose pointer is no
273    /// `T` or that carries none (#1920), `TypedExternal::FIELD_ERROR_MESSAGE`.
274    message: Option<String>,
275    /// A sentence the `miniextendr_no_field` error adds after its field list,
276    /// each `%s` replaced by the name asked for (#2112).
277    no_field_hint: Option<String>,
278}
279
280/// Parse `#[externalptr(...)]` attributes: the class system, the restored
281/// refusal's classes, message and scope, and the field readers' classes.
282///
283/// Supported forms:
284/// - `#[externalptr(env)]` - Environment style (default)
285/// - `#[externalptr(r6)]` - R6 class
286/// - `#[externalptr(s3)]` - S3 class
287/// - `#[externalptr(s4)]` - S4 class
288/// - `#[externalptr(s7)]` - S7 class
289/// - `#[externalptr(vctrs)]` - vctrs class
290/// - `#[externalptr(restored(class = "pkg_saved", message = "...", refuse = "every_read"))]`,
291///   with `class = ["pkg_saved", "pkg_error"]` for several classes; next to
292///   the class system (`#[externalptr(s3, restored(...))]`) or on its own.
293/// - `#[externalptr(field_error(class = ["pkg_error_config", "pkg_error"], message = "...", no_field_hint = "..."))]`
294/// - `#[externalptr(rebuild = Self::from_saved)]`, the rebuild hook of a
295///   pointer restored from a saved session (#2146), with `any_version` for
296///   saves by another version of the crate too (#2187).
297fn parse_externalptr_attrs(input: &DeriveInput) -> syn::Result<ExternalPtrAttrs> {
298    let mut class_system = ClassSystem::Env; // Default
299    let mut restored: Option<RestoredOptions> = None;
300    let mut field_error: Option<FieldErrorOptions> = None;
301    let mut rebuild: Option<syn::Path> = None;
302    let mut any_version: Option<proc_macro2::Span> = None;
303
304    for attr in &input.attrs {
305        if attr.path().is_ident("externalptr") {
306            attr.parse_nested_meta(|meta| {
307                let ident_str = meta
308                    .path
309                    .get_ident()
310                    .map(|i| i.to_string())
311                    .unwrap_or_default();
312
313                match ident_str.as_str() {
314                    "env" => class_system = ClassSystem::Env,
315                    "r6" => class_system = ClassSystem::R6,
316                    "s3" => class_system = ClassSystem::S3,
317                    "s4" => class_system = ClassSystem::S4,
318                    "s7" => class_system = ClassSystem::S7,
319                    "vctrs" => class_system = ClassSystem::Vctrs,
320                    "restored" => {
321                        if restored.is_some() {
322                            return Err(meta.error("`restored(...)` is given twice"));
323                        }
324                        restored = Some(parse_restored_options(&meta)?);
325                    }
326                    "field_error" => {
327                        if field_error.is_some() {
328                            return Err(meta.error("`field_error(...)` is given twice"));
329                        }
330                        field_error = Some(parse_field_error_options(&meta)?);
331                    }
332                    "rebuild" => {
333                        if rebuild.is_some() {
334                            return Err(meta.error("`rebuild = ...` is given twice"));
335                        }
336                        rebuild = Some(meta.value()?.parse::<syn::Path>().map_err(|e| {
337                            syn::Error::new(
338                                e.span(),
339                                "`rebuild` takes the path of a function `fn(&Saved<'_, Self>) -> Result<Self, E>`, e.g. `rebuild = Self::from_saved`",
340                            )
341                        })?);
342                    }
343                    "any_version" => {
344                        if any_version.is_some() {
345                            return Err(meta.error("`any_version` is given twice"));
346                        }
347                        any_version = Some(syn::spanned::Spanned::span(&meta.path));
348                    }
349                    _ => {
350                        return Err(syn::Error::new_spanned(
351                            &meta.path,
352                            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("unknown `externalptr` option \'{0}\'; expected a class system (env, r6, s3, s4, s7, vctrs), `restored(class = ..., message = ..., refuse = ...)`, `field_error(class = ..., message = ...)`, `rebuild = path` or `any_version`",
                ident_str))
    })format!(
353                                "unknown `externalptr` option '{}'; expected a class system (env, r6, s3, s4, s7, vctrs), `restored(class = ..., message = ..., refuse = ...)`, `field_error(class = ..., message = ...)`, `rebuild = path` or `any_version`",
354                                ident_str
355                            ),
356                        ));
357                    }
358                }
359                Ok(())
360            })?;
361        }
362    }
363
364    if let (Some(span), None) = (any_version, &rebuild) {
365        return Err(syn::Error::new(
366            span,
367            "`any_version` lets the rebuild hook read a save by another version of the crate; \
368             it needs `rebuild = path` next to it",
369        ));
370    }
371
372    Ok(ExternalPtrAttrs {
373        class_system,
374        restored: restored.unwrap_or_default(),
375        field_error: field_error.unwrap_or_default(),
376        rebuild,
377        any_version: any_version.is_some(),
378    })
379}
380
381/// The value of a `class = "..."` or `class = ["...", ...]` argument: the
382/// condition classes in order, non-empty, none of them empty.
383fn parse_class_list(inner: &syn::meta::ParseNestedMeta) -> syn::Result<Vec<String>> {
384    let value = inner.value()?;
385    let classes: Vec<syn::LitStr> = if value.peek(syn::token::Bracket) {
386        let content;
387        match ::syn::__private::parse_brackets(&value) {
    ::syn::__private::Ok(brackets) => {
        content = brackets.content;
        _ = content;
        brackets.token
    }
    ::syn::__private::Err(error) => { return ::syn::__private::Err(error); }
};syn::bracketed!(content in value);
388        content
389            .parse_terminated(<syn::LitStr as syn::parse::Parse>::parse, ::syn::token::Commasyn::Token![,])?
390            .into_iter()
391            .collect()
392    } else {
393        ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
        [value.parse()?]))vec![value.parse()?]
394    };
395    if classes.is_empty() {
396        return Err(inner.error("`class` needs at least one condition class"));
397    }
398    for class in &classes {
399        if class.value().is_empty() {
400            return Err(syn::Error::new_spanned(
401                class,
402                "a condition class can't be empty",
403            ));
404        }
405    }
406    Ok(classes.iter().map(syn::LitStr::value).collect())
407}
408
409/// The arguments of `restored(...)`: `class = "..."` or `class = ["...", ...]`
410/// (non-empty, in order), `message = "..."` (non-empty) and `refuse =
411/// "every_read"`, each at most once. syn refuses empty parentheses, so at
412/// least one is given.
413fn parse_restored_options(meta: &syn::meta::ParseNestedMeta) -> syn::Result<RestoredOptions> {
414    let mut options = RestoredOptions::default();
415    let mut class_given = false;
416    let mut refuse_given = false;
417    meta.parse_nested_meta(|inner| {
418        if inner.path.is_ident("class") {
419            if std::mem::replace(&mut class_given, true) {
420                return Err(inner.error("`class` is given twice"));
421            }
422            options.class = parse_class_list(&inner)?;
423            Ok(())
424        } else if inner.path.is_ident("message") {
425            if options.message.is_some() {
426                return Err(inner.error("`message` is given twice"));
427            }
428            let message: syn::LitStr = inner.value()?.parse()?;
429            if message.value().is_empty() {
430                return Err(syn::Error::new_spanned(
431                    message,
432                    "`message` can't be empty",
433                ));
434            }
435            options.message = Some(message.value());
436            Ok(())
437        } else if inner.path.is_ident("refuse") {
438            if std::mem::replace(&mut refuse_given, true) {
439                return Err(inner.error("`refuse` is given twice"));
440            }
441            let refuse: syn::LitStr = inner.value()?.parse()?;
442            if refuse.value() != "every_read" {
443                return Err(syn::Error::new_spanned(
444                    refuse,
445                    "`refuse` takes \"every_read\": the field methods then refuse a restored pointer \
446                     on `names()` and on a name that is not a field too",
447                ));
448            }
449            options.every_read = true;
450            Ok(())
451        } else {
452            Err(inner.error(
453                "unknown `restored` option; expected `class = \"...\"` (or `class = [\"...\", ...]`), `message = \"...\"` and `refuse = \"every_read\"`",
454            ))
455        }
456    })?;
457    Ok(options)
458}
459
460/// The arguments of `field_error(...)`: `class = "..."` or `class = ["...",
461/// ...]`, `message = "..."` and `no_field_hint = "..."`, each once.
462fn parse_field_error_options(meta: &syn::meta::ParseNestedMeta) -> syn::Result<FieldErrorOptions> {
463    let mut class: Option<Vec<String>> = None;
464    let mut message: Option<String> = None;
465    let mut no_field_hint: Option<String> = None;
466    meta.parse_nested_meta(|inner| {
467        if inner.path.is_ident("class") {
468            if class.is_some() {
469                return Err(inner.error("`class` is given twice"));
470            }
471            class = Some(parse_class_list(&inner)?);
472            Ok(())
473        } else if inner.path.is_ident("message") {
474            if message.is_some() {
475                return Err(inner.error("`message` is given twice"));
476            }
477            let value: syn::LitStr = inner.value()?.parse()?;
478            if value.value().is_empty() {
479                return Err(syn::Error::new_spanned(value, "`message` is empty"));
480            }
481            message = Some(value.value());
482            Ok(())
483        } else if inner.path.is_ident("no_field_hint") {
484            if no_field_hint.is_some() {
485                return Err(inner.error("`no_field_hint` is given twice"));
486            }
487            let value: syn::LitStr = inner.value()?.parse()?;
488            if value.value().is_empty() {
489                return Err(syn::Error::new_spanned(value, "`no_field_hint` is empty"));
490            }
491            no_field_hint = Some(value.value());
492            Ok(())
493        } else {
494            Err(inner.error(
495                "unknown `field_error` option; expected `class = \"...\"` (or `class = [\"...\", ...]`), `message = \"...\"` and `no_field_hint = \"...\"`",
496            ))
497        }
498    })?;
499    Ok(FieldErrorOptions {
500        class: class.unwrap_or_default(),
501        message,
502        no_field_hint,
503    })
504}
505
506/// Check if a field has the `#[r_data]` attribute.
507fn has_r_data_attr(field: &Field) -> bool {
508    field.attrs.iter().any(|a| a.path().is_ident("r_data"))
509}
510
511/// The last path segment that names the rooted sidecar field type,
512/// `miniextendr_api::externalptr::Sidecar<T>`. Its final name is #1857; the
513/// derive spells it here only.
514const SIDECAR_TYPE_NAME: &str = "Sidecar";
515
516/// The `T` of a `Sidecar<T>` field type, `None` for any other type.
517fn sidecar_inner_type(ty: &syn::Type) -> Option<&syn::Type> {
518    let syn::Type::Path(type_path) = ty else {
519        return None;
520    };
521    let seg = type_path.path.segments.last()?;
522    if seg.ident != SIDECAR_TYPE_NAME {
523        return None;
524    }
525    let syn::PathArguments::AngleBracketed(args) = &seg.arguments else {
526        return None;
527    };
528    let mut types = args.args.iter().filter_map(|arg| match arg {
529        syn::GenericArgument::Type(ty) => Some(ty),
530        _ => None,
531    });
532    let inner = types.next()?;
533    types.next().is_none().then_some(inner)
534}
535
536/// The last path segment that names the marker type of a computed field,
537/// `miniextendr_api::externalptr::Computed` (#1883).
538const COMPUTED_TYPE_NAME: &str = "Computed";
539
540/// Whether a field type is `Computed`, the marker of a read-only field
541/// computed from the Rust value by the function `#[r_data(get = "...")]`
542/// names.
543fn is_computed_type(ty: &syn::Type) -> bool {
544    #[allow(non_exhaustive_omitted_patterns)] match ty {
    syn::Type::Path(type_path) if
        type_path.path.segments.last().is_some_and(|seg|
                seg.ident == COMPUTED_TYPE_NAME) => true,
    _ => false,
}matches!(ty, syn::Type::Path(type_path)
545        if type_path.path.segments.last().is_some_and(|seg| seg.ident == COMPUTED_TYPE_NAME))
546}
547
548/// Options for a sidecar field's generated documentation, setters and Rust
549/// accessors.
550#[derive(#[automatically_derived]
impl ::core::default::Default for RDataOptions {
    #[inline]
    fn default() -> Self {
        RDataOptions {
            prop_doc: ::core::default::Default::default(),
            setter_invisible: ::core::default::Default::default(),
            access_ref: ::core::default::Default::default(),
            access_mut: ::core::default::Default::default(),
            name: ::core::default::Default::default(),
            get: ::core::default::Default::default(),
        }
    }
}Default)]
551struct RDataOptions {
552    prop_doc: Option<String>,
553    setter_invisible: Option<bool>,
554    /// `ref`: generate the Rust getter of a `Sidecar<T>` field.
555    access_ref: Option<Span>,
556    /// `mut`: generate the Rust setter of a `Sidecar<T>` field.
557    access_mut: Option<Span>,
558    /// `name = "..."`: the field's R name, in place of the Rust identifier
559    /// (#1891).
560    name: Option<syn::LitStr>,
561    /// `get = "path"`: the getter of a `Computed` field, a
562    /// `fn(&Self) -> impl IntoR` (#1883).
563    get: Option<syn::LitStr>,
564}
565
566/// Which Rust accessors a `Sidecar<T>` field gets: `#[r_data(ref)]` the
567/// getter, `#[r_data(mut)]` the setter, `#[r_data(ref, mut)]` or a bare
568/// `#[r_data]` both.
569#[derive(#[automatically_derived]
impl ::core::fmt::Debug for SidecarAccess {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        ::core::fmt::Formatter::debug_struct_field2_finish(f, "SidecarAccess",
            "get", &self.get, "set", &&self.set)
    }
}Debug, #[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for SidecarAccess { }
#[automatically_derived]
impl ::core::clone::Clone for SidecarAccess {
    #[inline]
    fn clone(&self) -> Self {
        let _: ::core::clone::AssertParamIsClone<bool>;
        *self
    }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for SidecarAccess { }Copy, #[automatically_derived]
impl ::core::marker::StructuralPartialEq for SidecarAccess { }
#[automatically_derived]
impl ::core::cmp::PartialEq for SidecarAccess {
    #[inline]
    fn eq(&self, other: &Self) -> bool {
        self.get == other.get && self.set == other.set
    }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for SidecarAccess {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_fields_are_eq(&self) {
        let _: ::core::cmp::AssertParamIsEq<bool>;
    }
}Eq, #[automatically_derived]
impl ::core::default::Default for SidecarAccess {
    #[inline]
    fn default() -> Self {
        SidecarAccess {
            get: ::core::default::Default::default(),
            set: ::core::default::Default::default(),
        }
    }
}Default)]
570struct SidecarAccess {
571    get: bool,
572    set: bool,
573}
574
575/// One item of `#[r_data(...)]`: `ref`, `mut` (both keywords, so
576/// `parse_nested_meta` cannot read them) or `key = "value"`.
577enum RDataArg {
578    Ref(Span),
579    Mut(Span),
580    KeyValue { key: Ident, value: syn::LitStr },
581}
582
583impl syn::parse::Parse for RDataArg {
584    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
585        let lookahead = input.lookahead1();
586        if lookahead.peek(::syn::token::Refsyn::Token![ref]) {
587            let token: ::syn::token::Refsyn::Token![ref] = input.parse()?;
588            Ok(Self::Ref(token.span))
589        } else if lookahead.peek(::syn::token::Mutsyn::Token![mut]) {
590            let token: ::syn::token::Mutsyn::Token![mut] = input.parse()?;
591            Ok(Self::Mut(token.span))
592        } else if lookahead.peek(Ident) {
593            let key: Ident = input.parse()?;
594            let _eq: ::syn::token::Eqsyn::Token![=] = input.parse()?;
595            let value: syn::LitStr = input.parse()?;
596            Ok(Self::KeyValue { key, value })
597        } else {
598            Err(lookahead.error())
599        }
600    }
601}
602
603/// Parse field-level `#[r_data(ref, mut, prop_doc = "...", setter = "visible")]`
604/// options.
605fn parse_r_data_options(field: &Field) -> syn::Result<RDataOptions> {
606    let mut options = RDataOptions::default();
607    for attr in &field.attrs {
608        if !attr.path().is_ident("r_data") || #[allow(non_exhaustive_omitted_patterns)] match attr.meta {
    syn::Meta::Path(_) => true,
    _ => false,
}matches!(attr.meta, syn::Meta::Path(_)) {
609            continue;
610        }
611        let args = attr.parse_args_with(
612            syn::punctuated::Punctuated::<RDataArg, ::syn::token::Commasyn::Token![,]>::parse_terminated,
613        )?;
614        for arg in args {
615            match arg {
616                RDataArg::Ref(span) => {
617                    if options.access_ref.replace(span).is_some() {
618                        return Err(syn::Error::new(span, "duplicate `ref` option"));
619                    }
620                }
621                RDataArg::Mut(span) => {
622                    if options.access_mut.replace(span).is_some() {
623                        return Err(syn::Error::new(span, "duplicate `mut` option"));
624                    }
625                }
626                RDataArg::KeyValue { key, value } if key == "prop_doc" => {
627                    options.prop_doc = Some(value.value());
628                }
629                RDataArg::KeyValue { key, value } if key == "setter" => {
630                    if options.setter_invisible.is_some() {
631                        return Err(syn::Error::new_spanned(key, "duplicate `setter` option"));
632                    }
633                    options.setter_invisible = Some(match value.value().as_str() {
634                        "visible" => false,
635                        "invisible" => true,
636                        _ => {
637                            return Err(syn::Error::new_spanned(
638                                value,
639                                "setter must be \"visible\" or \"invisible\"",
640                            ));
641                        }
642                    });
643                }
644                RDataArg::KeyValue { key, value } if key == "name" => {
645                    if options.name.is_some() {
646                        return Err(syn::Error::new_spanned(key, "duplicate `name` option"));
647                    }
648                    options.name = Some(value);
649                }
650                RDataArg::KeyValue { key, value } if key == "get" => {
651                    if options.get.is_some() {
652                        return Err(syn::Error::new_spanned(key, "duplicate `get` option"));
653                    }
654                    options.get = Some(value);
655                }
656                RDataArg::KeyValue { key, .. } => {
657                    return Err(syn::Error::new_spanned(
658                        &key,
659                        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("unknown key `{0}`; supported: `ref`, `mut`, `name`, `get`, `prop_doc`, `setter`",
                key))
    })format!(
660                            "unknown key `{key}`; supported: `ref`, `mut`, `name`, `get`, `prop_doc`, `setter`"
661                        ),
662                    ));
663                }
664            }
665        }
666    }
667    if options.setter_invisible.is_some()
668        && (is_rsidecar_type(field)
669            || is_computed_type(&field.ty)
670            || !is_pub(field)
671            || field.ident.is_none())
672    {
673        return Err(syn::Error::new_spanned(
674            field,
675            "`setter` requires a public named sidecar slot with a setter, not the RSidecar \
676             selector or a `Computed` field",
677        ));
678    }
679    if let Some(span) = options.access_ref.or(options.access_mut)
680        && sidecar_inner_type(&field.ty).is_none()
681    {
682        return Err(syn::Error::new(
683            span,
684            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("`ref` and `mut` in `#[r_data(...)]` apply to `{1}<T>` fields only; a plain `#[r_data]` field stays a struct field with its R accessors{0}",
                if is_computed_type(&field.ty) {
                    ::alloc::__export::must_use({
                            ::alloc::fmt::format(format_args!(", and a `{0}` field is read through its `get` function",
                                    COMPUTED_TYPE_NAME))
                        })
                } else { String::new() }, SIDECAR_TYPE_NAME))
    })format!(
685                "`ref` and `mut` in `#[r_data(...)]` apply to `{SIDECAR_TYPE_NAME}<T>` fields only; \
686                 a plain `#[r_data]` field stays a struct field with its R accessors{}",
687                if is_computed_type(&field.ty) {
688                    format!(
689                        ", and a `{COMPUTED_TYPE_NAME}` field is read through its `get` function"
690                    )
691                } else {
692                    String::new()
693                }
694            ),
695        ));
696    }
697    if let Some(name) = &options.name {
698        if is_rsidecar_type(field) {
699            return Err(syn::Error::new_spanned(
700                name,
701                "`name` applies to a sidecar slot, not the RSidecar selector, which has no R name",
702            ));
703        }
704        let value = name.value();
705        if value.is_empty() {
706            return Err(syn::Error::new_spanned(
707                name,
708                "an R field name can't be empty",
709            ));
710        }
711        if value == ".ptr" {
712            return Err(syn::Error::new_spanned(
713                name,
714                "`.ptr` is the element that carries the pointer in a list or environment receiver, \
715                 so it can't be an R field name",
716            ));
717        }
718        if !crate::naming::is_syntactic_r_name(&value) {
719            return Err(syn::Error::new_spanned(
720                name,
721                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("`{0}` is not a syntactic R name; an R field name has letters, digits, `.` and `_` only, starts with a letter or a `.` not followed by a digit, and is not a reserved word",
                value))
    })format!(
722                    "`{value}` is not a syntactic R name; an R field name has letters, digits, `.` and `_` \
723                     only, starts with a letter or a `.` not followed by a digit, and is not a reserved word"
724                ),
725            ));
726        }
727    }
728    match (&options.get, is_computed_type(&field.ty)) {
729        (Some(get), false) => {
730            return Err(syn::Error::new_spanned(
731                get,
732                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("`get` applies to a `{0}` field only: declare the field as `pub <name>: {0}`, and `get` names the function that computes its value",
                COMPUTED_TYPE_NAME))
    })format!(
733                    "`get` applies to a `{COMPUTED_TYPE_NAME}` field only: declare the field as \
734                     `pub <name>: {COMPUTED_TYPE_NAME}`, and `get` names the function that computes its value"
735                ),
736            ));
737        }
738        (None, true) => {
739            return Err(syn::Error::new_spanned(
740                &field.ty,
741                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("a `{0}` field needs `#[r_data(get = \"Self::f\")]`, the function `fn(&Self) -> impl IntoR` that computes its value",
                COMPUTED_TYPE_NAME))
    })format!(
742                    "a `{COMPUTED_TYPE_NAME}` field needs `#[r_data(get = \"Self::f\")]`, the function \
743                     `fn(&Self) -> impl IntoR` that computes its value"
744                ),
745            ));
746        }
747        _ => {}
748    }
749    Ok(options)
750}
751
752/// Check if a field type is `RSidecar`.
753///
754/// Returns `true` if the last path segment of the field's type is `RSidecar`,
755/// which acts as the selector marker enabling R sidecar accessor generation.
756pub(crate) fn is_rsidecar_type(field: &Field) -> bool {
757    if let syn::Type::Path(type_path) = &field.ty {
758        type_path
759            .path
760            .segments
761            .last()
762            .map(|seg| seg.ident == "RSidecar")
763            .unwrap_or(false)
764    } else {
765        false
766    }
767}
768
769/// Check if a field is public.
770fn is_pub(field: &Field) -> bool {
771    #[allow(non_exhaustive_omitted_patterns)] match field.vis {
    Visibility::Public(_) => true,
    _ => false,
}matches!(field.vis, Visibility::Public(_))
772}
773
774/// The kind of sidecar slot, determining how getter/setter FFI functions are generated.
775///
776/// Each kind maps to a different codegen strategy for reading and writing the
777/// slot through R's `.Call` interface:
778/// - `Sidecar`: a value in the external pointer's protection list, validated
779///   with `TryFromSexp::<T>` on write and stored as R gave it.
780/// - Scalars: a struct field, read into R with `Rf_Scalar*`, written with `Rf_as*`.
781/// - Conversion: a struct field, converted with the `IntoR`/`TryFromSexp` traits.
782/// - Computed: no field; a getter function of the Rust value, no setter.
783#[derive(#[automatically_derived]
impl ::core::fmt::Debug for SlotKind {
    #[inline]
    fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
        match self {
            Self::Sidecar(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f,
                    "Sidecar", &__self_0),
            Self::Computed(__self_0) =>
                ::core::fmt::Formatter::debug_tuple_field1_finish(f,
                    "Computed", &__self_0),
            Self::ScalarInt =>
                ::core::fmt::Formatter::write_str(f, "ScalarInt"),
            Self::ScalarReal =>
                ::core::fmt::Formatter::write_str(f, "ScalarReal"),
            Self::ScalarLogical =>
                ::core::fmt::Formatter::write_str(f, "ScalarLogical"),
            Self::ScalarRaw =>
                ::core::fmt::Formatter::write_str(f, "ScalarRaw"),
            Self::Conversion =>
                ::core::fmt::Formatter::write_str(f, "Conversion"),
        }
    }
}Debug, #[automatically_derived]
impl ::core::clone::Clone for SlotKind {
    #[inline]
    fn clone(&self) -> Self {
        match self {
            Self::Sidecar(__self_0) =>
                SlotKind::Sidecar(::core::clone::Clone::clone(__self_0)),
            Self::Computed(__self_0) =>
                SlotKind::Computed(::core::clone::Clone::clone(__self_0)),
            Self::ScalarInt => SlotKind::ScalarInt,
            Self::ScalarReal => SlotKind::ScalarReal,
            Self::ScalarLogical => SlotKind::ScalarLogical,
            Self::ScalarRaw => SlotKind::ScalarRaw,
            Self::Conversion => SlotKind::Conversion,
        }
    }
}Clone, #[automatically_derived]
impl ::core::marker::StructuralPartialEq for SlotKind { }
#[automatically_derived]
impl ::core::cmp::PartialEq for SlotKind {
    #[inline]
    fn eq(&self, other: &Self) -> bool {
        ::core::intrinsics::discriminant_value(self) ==
                ::core::intrinsics::discriminant_value(other) &&
            match (self, other) {
                (Self::Sidecar(__self_0), Self::Sidecar(__arg1_0)) =>
                    __self_0 == __arg1_0,
                (Self::Computed(__self_0), Self::Computed(__arg1_0)) =>
                    __self_0 == __arg1_0,
                _ => true,
            }
    }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for SlotKind {
    #[inline]
    #[doc(hidden)]
    #[coverage(off)]
    fn assert_fields_are_eq(&self) {
        let _: ::core::cmp::AssertParamIsEq<usize>;
        let _: ::core::cmp::AssertParamIsEq<syn::Path>;
    }
}Eq)]
784pub(crate) enum SlotKind {
785    /// `Sidecar<T>` field: the value at this position among the type's
786    /// `Sidecar` fields, kept in the external pointer's protection list
787    /// (getter returns it, setter validates `value` as a `T` and stores it
788    /// there).
789    Sidecar(usize),
790    /// `Computed` marker field: the getter calls this function, a
791    /// `fn(&Self) -> impl IntoR` (a leading `Self` already replaced by the
792    /// struct's name), on the live Rust value; there is no setter (#1883).
793    Computed(syn::Path),
794    /// Scalar struct field `i32` (or `i16`/`i8`), returned as a length-1 integer.
795    ScalarInt,
796    /// Scalar struct field `f64` (or `f32`), returned as a length-1 double.
797    ScalarReal,
798    /// Scalar struct field `bool` (or `Rbool`), returned as a length-1 logical.
799    ScalarLogical,
800    /// Scalar struct field `u8`, returned as a length-1 raw.
801    ScalarRaw,
802    /// Conversion type -- uses `IntoR`/`TryFromSexp` traits for arbitrary Rust types.
803    Conversion,
804}
805
806/// Information about a single `#[r_data]`-annotated sidecar slot field.
807///
808/// Collected during struct field parsing and used to generate FFI getter/setter
809/// functions and R wrapper code for each public slot.
810pub(crate) struct SidecarSlot {
811    /// Rust identifier of the field (e.g., `count`, `name`). The C symbols,
812    /// the `R_CallMethodDef` statics and the Rust accessors are named by it.
813    pub(crate) name: Ident,
814    /// The field's R name: `#[r_data(name = "...")]`, else the Rust
815    /// identifier (#1891). Every R-facing string uses it: `.rdata_fields_T`,
816    /// the `$` switch labels, the R6 binding / S7 property, the standalone
817    /// `T_get_<name>()` / `T_set_<name>()` and the setter's message. A
818    /// `Sidecar` field's save slot is named by it too (`SIDECAR_NAMES`, the
819    /// name the accessors and `__mx_visit_sidecars` pass, #2215).
820    pub(crate) r_name: String,
821    /// Rust type of the field, used in conversion-based getter/setter codegen.
822    ty: syn::Type,
823    /// The field's visibility, which its generated Rust accessors take.
824    vis: Visibility,
825    /// Whether the field is `pub`. Only public fields get R accessor functions.
826    is_public: bool,
827    /// Determines the codegen strategy for reading/writing this slot.
828    pub(crate) kind: SlotKind,
829    /// The Rust accessors of a `Sidecar<T>` field (`#[r_data(ref | mut)]`);
830    /// unused by the other kinds.
831    access: SidecarAccess,
832    /// Optional documentation string for the S7 `@prop` tag.
833    /// Sourced from `#[r_data(prop_doc = "...")]`. `None` means no doc was supplied;
834    /// a default fallback string is used at emit time.
835    prop_doc: Option<String>,
836    /// Explicit setter visibility; each generated setter keeps its default when absent.
837    setter_invisible: Option<bool>,
838}
839
840/// Aggregated sidecar information extracted from struct field analysis.
841///
842/// Contains everything needed to generate sidecar accessor code: the
843/// selector presence, the list of typed slots, and the target class system.
844pub(crate) struct SidecarInfo {
845    /// Whether the struct contains an `RSidecar`-typed field marked with `#[r_data]`.
846    /// At most one selector is allowed per struct.
847    has_selector: bool,
848    /// The `#[r_data]` slot fields (excluding the RSidecar selector itself),
849    /// each carrying its index, kind, and visibility.
850    pub(crate) slots: Vec<SidecarSlot>,
851    /// The R class system chosen via `#[externalptr(...)]`, controlling the
852    /// style of generated R wrapper code.
853    class_system: ClassSystem,
854}
855
856/// The getter path of a `Computed` field, `#[r_data(get = "Self::f")]`,
857/// parsed from the literal; a leading `Self` becomes the struct's name, since
858/// the getter is called from a free function the derive emits outside any
859/// impl.
860fn computed_getter_path(get: &syn::LitStr, struct_name: &Ident) -> syn::Result<syn::Path> {
861    let mut path: syn::Path = get.parse().map_err(|err| {
862        syn::Error::new_spanned(
863            get,
864            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("`get` must be a path to the getter function, e.g. `Self::f`: {0}",
                err))
    })format!("`get` must be a path to the getter function, e.g. `Self::f`: {err}"),
865        )
866    })?;
867    crate::util::resolve_self(&mut path, struct_name);
868    Ok(path)
869}
870
871/// R views stored in ordinary Rust fields have no GC owner. Inspect only
872/// known views and containers: aliases and user-defined wrappers are opaque to
873/// a derive, and a generic argument alone need not be stored (PhantomData).
874fn contains_unrooted_r_view(ty: &syn::Type) -> bool {
875    if crate::type_inspect::is_rooted_reference(ty) {
876        return true;
877    }
878    match ty {
879        syn::Type::Array(t) => contains_unrooted_r_view(&t.elem),
880        syn::Type::Slice(t) => contains_unrooted_r_view(&t.elem),
881        syn::Type::Tuple(t) => t.elems.iter().any(contains_unrooted_r_view),
882        syn::Type::Reference(t) => contains_unrooted_r_view(&t.elem),
883        syn::Type::Paren(t) => contains_unrooted_r_view(&t.elem),
884        syn::Type::Group(t) => contains_unrooted_r_view(&t.elem),
885        syn::Type::Path(t) if t.qself.is_none() => {
886            let Some(segment) = t.path.segments.last() else {
887                return false;
888            };
889            match segment.ident.to_string().as_str() {
890                "SEXP" | "List" | "ListMut" | "NamedList" | "DataFrame" | "StrVec" | "RCow"
891                | "RArray" | "RVector" | "RMatrix" | "RArray3D" | "Factor" | "FactorMut" => true,
892                "Option" | "Result" | "Vec" | "VecDeque" | "LinkedList" | "BinaryHeap"
893                | "HashSet" | "BTreeSet" | "HashMap" | "BTreeMap" | "Box" | "Rc" | "Arc"
894                | "Cow" | "Cell" | "RefCell" | "Mutex" | "RwLock" | "OnceCell" | "OnceLock" => {
895                    let syn::PathArguments::AngleBracketed(args) = &segment.arguments else {
896                        return false;
897                    };
898                    args.args.iter().any(|arg| {
899                        #[allow(non_exhaustive_omitted_patterns)] match arg {
    syn::GenericArgument::Type(ty) if contains_unrooted_r_view(ty) => true,
    _ => false,
}matches!(arg, syn::GenericArgument::Type(ty) if contains_unrooted_r_view(ty))
900                    })
901                }
902                _ => false,
903            }
904        }
905        _ => false,
906    }
907}
908
909/// Determine the [`SlotKind`] for a field type by inspecting its last path segment.
910///
911/// Recognizes `Sidecar<T>`, which takes position `sidecars` (the number of
912/// `Sidecar` fields before it), `Computed` with its `get` function, scalar
913/// numerics (`i32`, `i16`, `i8`, `f64`, `f32`), booleans (`bool`, `Rbool`),
914/// and raw bytes (`u8`). An R view, including one in a standard container,
915/// is an error: the struct can't root it.
916/// Everything else falls through to [`SlotKind::Conversion`].
917fn slot_kind_for_type(
918    ty: &syn::Type,
919    sidecars: usize,
920    get: Option<&syn::LitStr>,
921    struct_name: &Ident,
922) -> syn::Result<SlotKind> {
923    if sidecar_inner_type(ty).is_some() {
924        return Ok(SlotKind::Sidecar(sidecars));
925    }
926    if is_computed_type(ty) {
927        // `parse_r_data_options` refused a `Computed` field without `get`.
928        let get = get.expect("a Computed field has a `get` option");
929        return Ok(SlotKind::Computed(computed_getter_path(get, struct_name)?));
930    }
931    if contains_unrooted_r_view(ty) {
932        let field_type = crate::type_inspect::type_display(ty);
933        return Err(syn::Error::new_spanned(
934            ty,
935            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("a `{0}` sidecar field is not rooted: the GC frees its value once R drops its own references. Declare the field as `{1}<{0}>`, which keeps the value in the external pointer\'s protection list",
                field_type, SIDECAR_TYPE_NAME))
    })format!(
936                "a `{field_type}` sidecar field is not rooted: the GC frees its value once R drops \
937                 its own references. Declare the field as `{SIDECAR_TYPE_NAME}<{field_type}>`, which \
938                 keeps the value in the external pointer's protection list"
939            ),
940        ));
941    }
942    if let syn::Type::Path(type_path) = ty
943        && let Some(seg) = type_path.path.segments.last()
944    {
945        let ident = &seg.ident;
946        if ident == SIDECAR_TYPE_NAME {
947            return Err(syn::Error::new_spanned(
948                ty,
949                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("`{0}` takes one type argument: `{0}<T>`",
                SIDECAR_TYPE_NAME))
    })format!("`{SIDECAR_TYPE_NAME}` takes one type argument: `{SIDECAR_TYPE_NAME}<T>`"),
950            ));
951        }
952        if ident == "i32" || ident == "i16" || ident == "i8" {
953            return Ok(SlotKind::ScalarInt);
954        }
955        if ident == "f64" || ident == "f32" {
956            return Ok(SlotKind::ScalarReal);
957        }
958        if ident == "bool" || ident == "Rbool" {
959            return Ok(SlotKind::ScalarLogical);
960        }
961        if ident == "u8" {
962            return Ok(SlotKind::ScalarRaw);
963        }
964    }
965    // Everything else uses conversion
966    Ok(SlotKind::Conversion)
967}
968
969/// Parse struct fields for sidecar information.
970///
971/// Iterates over all fields, identifying `#[r_data]` markers. Fields with
972/// `RSidecar` type are tracked as selector markers (at most one allowed);
973/// all other `#[r_data]` fields become [`SidecarSlot`] entries with their
974/// slot kind inferred from the field type.
975///
976/// Returns `Err` if more than one `RSidecar` field is found.
977pub(crate) fn parse_sidecar_info(
978    input: &DeriveInput,
979    class_system: ClassSystem,
980) -> syn::Result<SidecarInfo> {
981    let fields = match &input.data {
982        syn::Data::Struct(data) => &data.fields,
983        _ => {
984            return Ok(SidecarInfo {
985                has_selector: false,
986                slots: ::alloc::vec::Vec::new()vec![],
987                class_system,
988            });
989        }
990    };
991
992    let mut selector_fields: Vec<&Field> = ::alloc::vec::Vec::new()vec![];
993    let mut slots = ::alloc::vec::Vec::new()vec![];
994    let mut sidecars = 0usize;
995
996    for field in fields.iter() {
997        if !has_r_data_attr(field) {
998            if sidecar_inner_type(&field.ty).is_some() {
999                return Err(syn::Error::new_spanned(
1000                    &field.ty,
1001                    ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("a `{0}<T>` field needs `#[r_data]`, `#[r_data(ref)]`, `#[r_data(mut)]` or `#[r_data(ref, mut)]`: the derive attaches only the fields it knows to the external pointer",
                SIDECAR_TYPE_NAME))
    })format!(
1002                        "a `{SIDECAR_TYPE_NAME}<T>` field needs `#[r_data]`, `#[r_data(ref)]`, \
1003                         `#[r_data(mut)]` or `#[r_data(ref, mut)]`: the derive attaches only the \
1004                         fields it knows to the external pointer"
1005                    ),
1006                ));
1007            }
1008            continue;
1009        }
1010
1011        let options = parse_r_data_options(field)?;
1012        if is_rsidecar_type(field) {
1013            // RSidecar is the selector marker, not a slot
1014            selector_fields.push(field);
1015        } else if let Some(ref ident) = field.ident {
1016            // Any other type with #[r_data] becomes a slot
1017            let kind = slot_kind_for_type(&field.ty, sidecars, options.get.as_ref(), &input.ident)?;
1018            if #[allow(non_exhaustive_omitted_patterns)] match kind {
    SlotKind::Computed(_) => true,
    _ => false,
}matches!(kind, SlotKind::Computed(_)) {
1019                // Only a `pub` `#[r_data]` field reaches R, and a computed
1020                // field exists for R only.
1021                if !is_pub(field) {
1022                    return Err(syn::Error::new_spanned(
1023                        field,
1024                        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("a `{0}` field must be `pub`: it exists only as an R field, and only `pub` `#[r_data]` fields reach R",
                COMPUTED_TYPE_NAME))
    })format!(
1025                            "a `{COMPUTED_TYPE_NAME}` field must be `pub`: it exists only as an R field, \
1026                             and only `pub` `#[r_data]` fields reach R"
1027                        ),
1028                    ));
1029                }
1030                // The R6 active bindings and the S7 properties need a setter;
1031                // a getters-only form for them is #1888.
1032                if #[allow(non_exhaustive_omitted_patterns)] match class_system {
    ClassSystem::R6 | ClassSystem::S7 => true,
    _ => false,
}matches!(class_system, ClassSystem::R6 | ClassSystem::S7) {
1033                    return Err(syn::Error::new_spanned(
1034                        field,
1035                        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("a `{1}` field is not supported under `#[externalptr({0})]`: its R6 active bindings / S7 properties need a setter, and a computed field has none (#1888). Use an active method / a computed property, or an S3, S4 or env class",
                if class_system == ClassSystem::R6 { "r6" } else { "s7" },
                COMPUTED_TYPE_NAME))
    })format!(
1036                            "a `{COMPUTED_TYPE_NAME}` field is not supported under `#[externalptr({})]`: \
1037                             its R6 active bindings / S7 properties need a setter, and a computed field \
1038                             has none (#1888). Use an active method / a computed property, or an S3, S4 \
1039                             or env class",
1040                            if class_system == ClassSystem::R6 {
1041                                "r6"
1042                            } else {
1043                                "s7"
1044                            }
1045                        ),
1046                    ));
1047                }
1048            }
1049            let access = if #[allow(non_exhaustive_omitted_patterns)] match kind {
    SlotKind::Sidecar(_) => true,
    _ => false,
}matches!(kind, SlotKind::Sidecar(_)) {
1050                sidecars += 1;
1051                let (get, set) = (options.access_ref.is_some(), options.access_mut.is_some());
1052                // A bare `#[r_data]` on a `Sidecar` field means `ref, mut`.
1053                SidecarAccess {
1054                    get: get || !set,
1055                    set: set || !get,
1056                }
1057            } else {
1058                SidecarAccess::default()
1059            };
1060            let r_name = options
1061                .name
1062                .as_ref()
1063                .map_or_else(|| crate::naming::ident_name(ident), syn::LitStr::value);
1064            slots.push(SidecarSlot {
1065                name: ident.clone(),
1066                r_name,
1067                ty: field.ty.clone(),
1068                vis: field.vis.clone(),
1069                is_public: is_pub(field),
1070                kind,
1071                access,
1072                prop_doc: options.prop_doc,
1073                setter_invisible: options.setter_invisible,
1074            });
1075        }
1076    }
1077
1078    // Check for multiple selectors
1079    if selector_fields.len() > 1 {
1080        return Err(syn::Error::new_spanned(
1081            selector_fields[1],
1082            "only one RSidecar field is allowed per struct",
1083        ));
1084    }
1085
1086    // Two slots can't share an R name: a renamed field can't take another
1087    // field's name (the `$` switch and the standalone accessors would clash).
1088    for (i, slot) in slots.iter().enumerate() {
1089        if let Some(earlier) = slots[..i].iter().find(|other| other.r_name == slot.r_name) {
1090            return Err(syn::Error::new(
1091                slot.name.span(),
1092                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("the R field name `{0}` is already taken by the field `{1}`; every `#[r_data]` field of a type needs its own R name (`#[r_data(name = \"...\")]` renames one)",
                slot.r_name, earlier.name))
    })format!(
1093                    "the R field name `{}` is already taken by the field `{}`; every `#[r_data]` field \
1094                     of a type needs its own R name (`#[r_data(name = \"...\")]` renames one)",
1095                    slot.r_name, earlier.name
1096                ),
1097            ));
1098        }
1099    }
1100
1101    Ok(SidecarInfo {
1102        has_selector: !selector_fields.is_empty(),
1103        slots,
1104        class_system,
1105    })
1106}
1107
1108/// Whether a slot's C accessors read the `require_live` flag: only a
1109/// `Sidecar` slot can serve a restored pointer, so only its accessors decide
1110/// by the flag; the other kinds need the live Rust value whatever it says.
1111fn uses_require_live(slot: &SidecarSlot) -> bool {
1112    #[allow(non_exhaustive_omitted_patterns)] match slot.kind {
    SlotKind::Sidecar(_) => true,
    _ => false,
}matches!(slot.kind, SlotKind::Sidecar(_))
1113}
1114
1115/// The `require_live` parameter of a slot's C accessors: named for the body
1116/// to read when the slot uses it, underscored otherwise.
1117fn require_live_param(slot: &SidecarSlot) -> Ident {
1118    if uses_require_live(slot) {
1119        ::quote::__private::mk_ident(&::alloc::__export::must_use({
                ::alloc::fmt::format(format_args!("require_live"))
            }), ::quote::__private::Option::None)quote::format_ident!("require_live")
1120    } else {
1121        ::quote::__private::mk_ident(&::alloc::__export::must_use({
                ::alloc::fmt::format(format_args!("_require_live"))
            }), ::quote::__private::Option::None)quote::format_ident!("_require_live")
1122    }
1123}
1124
1125/// Generate the token stream for a sidecar getter function body.
1126///
1127/// Reads the slot and returns it as an R SEXP. The strategy depends on the
1128/// slot kind:
1129/// - `Sidecar`: returns the value from the external pointer's protection list,
1130///   after flushing a live struct's pending value for the field. It needs no
1131///   live address, so without `require_live` it also reads a pointer that
1132///   `readRDS` brought back; with it (the field methods of
1133///   `r_data_accessors`, #1891) a restored pointer is refused.
1134/// - Scalar kinds: reads the struct field (accessed via the external pointer
1135///   address) and wraps it with `Rf_Scalar*`.
1136/// - `Conversion`: clones the struct field and calls `IntoR::into_sexp`.
1137/// - `Computed`: calls the `get` function on the live struct and converts
1138///   its value with `IntoR::into_sexp`.
1139///
1140/// The body runs inside `with_r_unwind_protect` (see the emission site in
1141/// [`generate_sidecar_accessors`]). The receiver is the pointer or an object
1142/// carrying it in `.ptr`, as for an instance method; an argument carrying no
1143/// pointer, a null pointer address, or a wrong stored type panics with the
1144/// same `expected ExternalPtr<T>` message the main class-method path uses.
1145/// The panic is transported as a tagged condition and re-raised by the R
1146/// wrapper.
1147fn generate_getter_body(struct_name: &syn::Ident, slot: &SidecarSlot) -> TokenStream {
1148    let field_name = &slot.name;
1149
1150    // Helper: the live struct behind the pointer, borrowed shared for the
1151    // getter. `sidecar_r_struct` panics (caught by the surrounding
1152    // with_r_unwind_protect and raised as an R error) on a non-pointer, a
1153    // wrong type, or a pointer without an address (a restored object: the
1154    // classed errors) instead of silently returning R_NilValue, and while a
1155    // running method holds the struct mutably (`miniextendr_borrow_conflict`).
1156    let extract_ref = {
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::__private::push_ident(&mut _s, "let");
    ::quote::__private::push_ident(&mut _s, "__mx_data");
    ::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, "sidecar_r_struct");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_lt(&mut _s);
    ::quote::ToTokens::to_tokens(&struct_name, &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, "x");
            _s
        });
    ::quote::__private::push_semi(&mut _s);
    ::quote::__private::push_ident(&mut _s, "let");
    ::quote::__private::push_ident(&mut _s, "data");
    ::quote::__private::push_colon(&mut _s);
    ::quote::__private::push_and(&mut _s);
    ::quote::ToTokens::to_tokens(&struct_name, &mut _s);
    ::quote::__private::push_eq(&mut _s);
    ::quote::__private::push_and(&mut _s);
    ::quote::__private::push_ident(&mut _s, "__mx_data");
    ::quote::__private::push_semi(&mut _s);
    _s
}quote::quote! {
1157        let __mx_data = ::miniextendr_api::externalptr::sidecar_r_struct::<#struct_name>(x);
1158        let data: &#struct_name = &__mx_data;
1159    };
1160
1161    match &slot.kind {
1162        SlotKind::Sidecar(index) => {
1163            {
    let mut _s = ::quote::__private::TokenStream::new();
    ::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, "sidecar_r_get");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_lt(&mut _s);
            ::quote::ToTokens::to_tokens(&struct_name, &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, "x");
                    ::quote::__private::push_comma(&mut _s);
                    ::quote::ToTokens::to_tokens(&index, &mut _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, "externalptr");
                    ::quote::__private::push_colon2(&mut _s);
                    ::quote::__private::push_ident(&mut _s,
                        "sidecar_require_live");
                    ::quote::__private::push_group(&mut _s,
                        ::quote::__private::Delimiter::Parenthesis,
                        {
                            let mut _s = ::quote::__private::TokenStream::new();
                            ::quote::__private::push_ident(&mut _s, "require_live");
                            _s
                        });
                    ::quote::__private::push_comma(&mut _s);
                    _s
                });
            _s
        });
    _s
}quote::quote! {
1164                unsafe {
1165                    ::miniextendr_api::externalptr::sidecar_r_get::<#struct_name>(
1166                        x,
1167                        #index,
1168                        ::miniextendr_api::externalptr::sidecar_require_live(require_live),
1169                    )
1170                }
1171            }
1172        }
1173        SlotKind::Computed(getter) => {
1174            // The getter's own conditions (`rust_error!(class = ...)`) are
1175            // panics the surrounding guard catches, so they reach R with
1176            // their classes. Naming the marker field is its only Rust use:
1177            // it keeps `dead_code` quiet and checks the field's type.
1178            {
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::__private::push_ident(&mut _s, "use");
    ::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, "into_r");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "IntoR");
    ::quote::__private::push_semi(&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::ToTokens::to_tokens(&extract_ref, &mut _s);
            ::quote::__private::push_ident(&mut _s, "let");
            ::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, "Computed");
            ::quote::__private::push_eq(&mut _s);
            ::quote::__private::push_ident(&mut _s, "data");
            ::quote::__private::push_dot(&mut _s);
            ::quote::ToTokens::to_tokens(&field_name, &mut _s);
            ::quote::__private::push_semi(&mut _s);
            ::quote::__private::push_ident(&mut _s, "IntoR");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "into_sexp");
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::ToTokens::to_tokens(&getter, &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, "data");
                            _s
                        });
                    _s
                });
            _s
        });
    _s
}quote::quote! {
1179                use ::miniextendr_api::into_r::IntoR;
1180                unsafe {
1181                    #extract_ref
1182                    let ::miniextendr_api::externalptr::Computed = data.#field_name;
1183                    IntoR::into_sexp(#getter(data))
1184                }
1185            }
1186        }
1187        SlotKind::ScalarInt => {
1188            {
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::__private::push_ident(&mut _s, "use");
    ::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_semi(&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::ToTokens::to_tokens(&extract_ref, &mut _s);
            ::quote::__private::push_ident(&mut _s, "SEXP");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "scalar_integer");
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::__private::push_ident(&mut _s, "data");
                    ::quote::__private::push_dot(&mut _s);
                    ::quote::ToTokens::to_tokens(&field_name, &mut _s);
                    _s
                });
            _s
        });
    _s
}quote::quote! {
1189                use ::miniextendr_api::SEXP;
1190                unsafe {
1191                    #extract_ref
1192                    SEXP::scalar_integer(data.#field_name)
1193                }
1194            }
1195        }
1196        SlotKind::ScalarReal => {
1197            {
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::__private::push_ident(&mut _s, "use");
    ::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_semi(&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::ToTokens::to_tokens(&extract_ref, &mut _s);
            ::quote::__private::push_ident(&mut _s, "SEXP");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "scalar_real");
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::__private::push_ident(&mut _s, "data");
                    ::quote::__private::push_dot(&mut _s);
                    ::quote::ToTokens::to_tokens(&field_name, &mut _s);
                    _s
                });
            _s
        });
    _s
}quote::quote! {
1198                use ::miniextendr_api::SEXP;
1199                unsafe {
1200                    #extract_ref
1201                    SEXP::scalar_real(data.#field_name)
1202                }
1203            }
1204        }
1205        SlotKind::ScalarLogical => {
1206            {
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::__private::push_ident(&mut _s, "use");
    ::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_semi(&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::ToTokens::to_tokens(&extract_ref, &mut _s);
            ::quote::__private::push_ident(&mut _s, "SEXP");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "scalar_logical");
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::__private::push_ident(&mut _s, "data");
                    ::quote::__private::push_dot(&mut _s);
                    ::quote::ToTokens::to_tokens(&field_name, &mut _s);
                    _s
                });
            _s
        });
    _s
}quote::quote! {
1207                use ::miniextendr_api::SEXP;
1208                unsafe {
1209                    #extract_ref
1210                    SEXP::scalar_logical(data.#field_name)
1211                }
1212            }
1213        }
1214        SlotKind::ScalarRaw => {
1215            {
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::__private::push_ident(&mut _s, "use");
    ::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_semi(&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::ToTokens::to_tokens(&extract_ref, &mut _s);
            ::quote::__private::push_ident(&mut _s, "SEXP");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "scalar_raw");
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::__private::push_ident(&mut _s, "data");
                    ::quote::__private::push_dot(&mut _s);
                    ::quote::ToTokens::to_tokens(&field_name, &mut _s);
                    _s
                });
            _s
        });
    _s
}quote::quote! {
1216                use ::miniextendr_api::SEXP;
1217                unsafe {
1218                    #extract_ref
1219                    SEXP::scalar_raw(data.#field_name)
1220                }
1221            }
1222        }
1223        SlotKind::Conversion => {
1224            // Use IntoR trait for conversion (e.g., String -> character). The
1225            // getter reads the field through a shared borrow, so converting it
1226            // needs a clone; spanned at the field type, so a type without
1227            // `Clone` is reported at the field.
1228            let ty = &slot.ty;
1229            let span = syn::spanned::Spanned::span(ty);
1230            let clone = {
    let mut _s = ::quote::__private::TokenStream::new();
    let _span: ::quote::__private::Span =
        ::quote::__private::get_span(span).__into_span();
    ::quote::__private::push_lt_spanned(&mut _s, _span);
    ::quote::ToTokens::to_tokens(&ty, &mut _s);
    ::quote::__private::push_ident_spanned(&mut _s, _span, "as");
    ::quote::__private::push_colon2_spanned(&mut _s, _span);
    ::quote::__private::push_ident_spanned(&mut _s, _span, "core");
    ::quote::__private::push_colon2_spanned(&mut _s, _span);
    ::quote::__private::push_ident_spanned(&mut _s, _span, "clone");
    ::quote::__private::push_colon2_spanned(&mut _s, _span);
    ::quote::__private::push_ident_spanned(&mut _s, _span, "Clone");
    ::quote::__private::push_gt_spanned(&mut _s, _span);
    ::quote::__private::push_colon2_spanned(&mut _s, _span);
    ::quote::__private::push_ident_spanned(&mut _s, _span, "clone");
    _s
}quote::quote_spanned! {span=> <#ty as ::core::clone::Clone>::clone };
1231            {
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::__private::push_ident(&mut _s, "use");
    ::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, "into_r");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "IntoR");
    ::quote::__private::push_semi(&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::ToTokens::to_tokens(&extract_ref, &mut _s);
            ::quote::__private::push_ident(&mut _s, "let");
            ::quote::__private::push_ident(&mut _s, "val");
            ::quote::__private::push_colon(&mut _s);
            ::quote::ToTokens::to_tokens(&ty, &mut _s);
            ::quote::__private::push_eq(&mut _s);
            ::quote::ToTokens::to_tokens(&clone, &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, "data");
                    ::quote::__private::push_dot(&mut _s);
                    ::quote::ToTokens::to_tokens(&field_name, &mut _s);
                    _s
                });
            ::quote::__private::push_semi(&mut _s);
            ::quote::__private::push_lt(&mut _s);
            ::quote::ToTokens::to_tokens(&ty, &mut _s);
            ::quote::__private::push_ident(&mut _s, "as");
            ::quote::__private::push_ident(&mut _s, "IntoR");
            ::quote::__private::push_gt(&mut _s);
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "into_sexp");
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::__private::push_ident(&mut _s, "val");
                    _s
                });
            _s
        });
    _s
}quote::quote! {
1232                use ::miniextendr_api::into_r::IntoR;
1233                unsafe {
1234                    #extract_ref
1235                    let val: #ty = #clone(&data.#field_name);
1236                    <#ty as IntoR>::into_sexp(val)
1237                }
1238            }
1239        }
1240    }
1241}
1242
1243/// Generate the token stream for a sidecar setter function body.
1244///
1245/// Stores the incoming R SEXP `value` in the slot. The strategy depends on the
1246/// slot kind:
1247/// - `Sidecar`: validates `value` with `TryFromSexp::<T>` (a failure raises
1248///   the conversion error) and stores it in the external pointer's protection
1249///   list, which roots it; a live struct's pending value for the field is
1250///   dropped.
1251/// - Scalar kinds: writes the struct field, using `Rf_as*` or coercion for
1252///   single-element extraction;
1253///   an input that doesn't reduce to a single non-NA scalar raises a
1254///   conversion error instead of silently storing an NA/`false` sentinel.
1255/// - `Conversion`: uses `TryFromSexp::try_from_sexp`; a failed conversion
1256///   raises a conversion error instead of silently dropping the write.
1257///
1258/// Returns the external pointer `x` (for R's invisible return convention).
1259/// The body runs inside `with_r_unwind_protect` (see the emission site in
1260/// [`generate_sidecar_accessors`]); a bad receiver panics with the main-path
1261/// `expected ExternalPtr<T>` message and conversion failures return a tagged
1262/// condition SEXP with kind `conversion` — both are re-raised by the R
1263/// wrapper. Sidecar accessors have no `__miniextendr_call` slot (#344/#348),
1264/// so the tagged conditions carry null call attribution.
1265///
1266/// A `Computed` slot has no setter: the result is empty, and the caller
1267/// emits none.
1268fn generate_setter_body(struct_name: &syn::Ident, slot: &SidecarSlot) -> TokenStream {
1269    let field_name = &slot.name;
1270
1271    // Helper: the live struct behind the pointer, borrowed exclusively for
1272    // the setter. Failure modes panic like the getter's
1273    // (`sidecar_r_struct_mut`) instead of silently no-op'ing, and a running
1274    // method holding the struct makes it raise `miniextendr_borrow_conflict`.
1275    let extract_mut = {
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::__private::push_ident(&mut _s, "let");
    ::quote::__private::push_ident(&mut _s, "mut");
    ::quote::__private::push_ident(&mut _s, "__mx_data");
    ::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, "sidecar_r_struct_mut");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_lt(&mut _s);
    ::quote::ToTokens::to_tokens(&struct_name, &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, "x");
            _s
        });
    ::quote::__private::push_semi(&mut _s);
    ::quote::__private::push_ident(&mut _s, "let");
    ::quote::__private::push_ident(&mut _s, "data");
    ::quote::__private::push_colon(&mut _s);
    ::quote::__private::push_and(&mut _s);
    ::quote::__private::push_ident(&mut _s, "mut");
    ::quote::ToTokens::to_tokens(&struct_name, &mut _s);
    ::quote::__private::push_eq(&mut _s);
    ::quote::__private::push_and(&mut _s);
    ::quote::__private::push_ident(&mut _s, "mut");
    ::quote::__private::push_ident(&mut _s, "__mx_data");
    ::quote::__private::push_semi(&mut _s);
    _s
}quote::quote! {
1276        let mut __mx_data =
1277            ::miniextendr_api::externalptr::sidecar_r_struct_mut::<#struct_name>(x);
1278        let data: &mut #struct_name = &mut __mx_data;
1279    };
1280
1281    // Conversion-failure condition, the argument error of #1594 on the
1282    // setter's `value` formal: `'<field>' must be <expected>: <reason>` with
1283    // the field's R-facing expectation. The message names the field, which is
1284    // what an R6 active binding (`obj$count <- x`) or an S7 property
1285    // (`obj@count <- x`) user assigned to: their condition call is the
1286    // binding's anonymous function. `e$param == "value"` (the formal), the
1287    // field's Rust type is `e$rust_type`, and the crate's
1288    // `conversion_error_class` applies. A `Conversion` slot's expectation comes
1289    // from the same type table as an argument's, else from the error at run
1290    // time, else `invalid '<field>' argument`
1291    // (`rust_conversion_builder::conversion_value_tokens`); a scalar
1292    // slot reads its value with `Rf_as*`, which has no error value, so the
1293    // reason is worded from the rejected value (`from_r::scalar_rejection_reason`).
1294    let crate_class = crate::crate_config::conversion_error_class();
1295    // A `Sidecar<T>` slot converts and reports its `T`.
1296    let value_ty = sidecar_inner_type(&slot.ty).unwrap_or(&slot.ty);
1297    let rust_type = crate::type_inspect::type_display(value_ty);
1298    // The message names the field as R knows it.
1299    let field_r_name = slot.r_name.clone();
1300    let scalar_err = |expected: &str| -> proc_macro2::TokenStream {
1301        let prefix = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("\'{0}\' must be {1}", field_r_name,
                expected))
    })format!("'{field_r_name}' must be {expected}");
1302        {
    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, "error_value");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "conversion_condition_value");
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Parenthesis,
        {
            let mut _s = ::quote::__private::TokenStream::new();
            ::quote::ToTokens::to_tokens(&prefix, &mut _s);
            ::quote::__private::push_comma(&mut _s);
            ::quote::__private::parse(&mut _s, "\"value\"");
            ::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, "Some");
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::ToTokens::to_tokens(&rust_type, &mut _s);
                    _s
                });
            ::quote::__private::push_comma(&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 crate_class, i) = crate_class.quote_into_iter();
                        let has_iter = has_iter | i;
                        <_ as
                                ::quote::__private::CheckHasIterator<true>>::check(has_iter);
                        while true {
                            let crate_class =
                                match crate_class.next() {
                                    Some(_x) => ::quote::__private::RepInterp(_x),
                                    None => break,
                                };
                            if !_first { ::quote::__private::push_comma(&mut _s); }
                            _first = false;
                            ::quote::ToTokens::to_tokens(&crate_class, &mut _s);
                        }
                    }
                    _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,
                "__mx_conversion_err_parts");
            ::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::push_colon2(&mut _s);
                    ::quote::__private::push_ident(&mut _s, "miniextendr_api");
                    ::quote::__private::push_colon2(&mut _s);
                    ::quote::__private::push_ident(&mut _s, "from_r");
                    ::quote::__private::push_colon2(&mut _s);
                    ::quote::__private::push_ident(&mut _s,
                        "scalar_rejection_reason");
                    ::quote::__private::push_group(&mut _s,
                        ::quote::__private::Delimiter::Parenthesis,
                        {
                            let mut _s = ::quote::__private::TokenStream::new();
                            ::quote::__private::push_ident(&mut _s, "value");
                            _s
                        });
                    ::quote::__private::push_comma(&mut _s);
                    ::quote::__private::push_ident(&mut _s, "true");
                    _s
                });
            ::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_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_comma(&mut _s);
            _s
        });
    _s
}quote::quote! {
1303            ::miniextendr_api::error_value::conversion_condition_value(
1304                #prefix,
1305                "value",
1306                ::core::option::Option::Some(#rust_type),
1307                &[#(#crate_class),*],
1308                ::miniextendr_api::__mx_conversion_err_parts!(
1309                    ::miniextendr_api::from_r::scalar_rejection_reason(value),
1310                    true
1311                ),
1312                ::core::option::Option::None,
1313                ::core::option::Option::None,
1314            )
1315        }
1316    };
1317
1318    // Conversion-failure value of a `Conversion` or `Sidecar` slot: the field
1319    // type's expectation, else the one the type declares or the error knows at
1320    // run time (a newtype, a `match_arg` enum field), as for an argument.
1321    let conversion_err = |ty: &syn::Type| -> proc_macro2::TokenStream {
1322        let prefix = crate::r_preconditions::conversion_expectation(ty, false)
1323            .map(|expected| ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("\'{0}\' must be {1}", field_r_name,
                expected))
    })format!("'{field_r_name}' must be {expected}"));
1324        let declared = crate::rust_conversion_builder::declared_expectation(ty);
1325        crate::rust_conversion_builder::conversion_value_tokens(
1326            &crate::rust_conversion_builder::ConversionSubject {
1327                expected: prefix.as_deref().map_or(
1328                    crate::rust_conversion_builder::Expected::FromError(Some(&declared)),
1329                    crate::rust_conversion_builder::Expected::Literal,
1330                ),
1331                quoted: &field_r_name,
1332                param: "value",
1333                nullable: crate::type_inspect::is_option_type(ty),
1334                rust_type: &rust_type,
1335            },
1336            &crate_class,
1337            &{
    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::quote!(::core::option::Option::None),
1338            syn::spanned::Spanned::span(ty),
1339        )
1340    };
1341
1342    match &slot.kind {
1343        SlotKind::Computed(_) => TokenStream::new(),
1344        SlotKind::Sidecar(index) => {
1345            let err_value = conversion_err(value_ty);
1346            // The receiver is checked before the value, so a wrong pointer is
1347            // reported as such whatever the value.
1348            {
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::__private::push_ident(&mut _s, "use");
    ::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, "TryFromSexp");
    ::quote::__private::push_semi(&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_ident(&mut _s, "let");
            ::quote::__private::push_ident(&mut _s, "require_live");
            ::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, "sidecar_require_live");
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::__private::push_ident(&mut _s, "require_live");
                    _s
                });
            ::quote::__private::push_semi(&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, "sidecar_r_check");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_lt(&mut _s);
            ::quote::ToTokens::to_tokens(&struct_name, &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, "x");
                    ::quote::__private::push_comma(&mut _s);
                    ::quote::__private::push_ident(&mut _s, "require_live");
                    _s
                });
            ::quote::__private::push_semi(&mut _s);
            ::quote::__private::push_ident(&mut _s, "if");
            ::quote::__private::push_ident(&mut _s, "let");
            ::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_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "Err");
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::__private::push_ident(&mut _s, "e");
                    _s
                });
            ::quote::__private::push_eq(&mut _s);
            ::quote::__private::push_lt(&mut _s);
            ::quote::ToTokens::to_tokens(&value_ty, &mut _s);
            ::quote::__private::push_ident(&mut _s, "as");
            ::quote::__private::push_ident(&mut _s, "TryFromSexp");
            ::quote::__private::push_gt(&mut _s);
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "try_from_sexp");
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::__private::push_ident(&mut _s, "value");
                    _s
                });
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Brace,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::__private::push_ident(&mut _s, "return");
                    ::quote::ToTokens::to_tokens(&err_value, &mut _s);
                    ::quote::__private::push_semi(&mut _s);
                    _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, "sidecar_r_set");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_lt(&mut _s);
            ::quote::ToTokens::to_tokens(&struct_name, &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, "x");
                    ::quote::__private::push_comma(&mut _s);
                    ::quote::ToTokens::to_tokens(&index, &mut _s);
                    ::quote::__private::push_comma(&mut _s);
                    ::quote::__private::push_ident(&mut _s, "value");
                    ::quote::__private::push_comma(&mut _s);
                    ::quote::__private::push_ident(&mut _s, "require_live");
                    ::quote::__private::push_comma(&mut _s);
                    _s
                });
            ::quote::__private::push_semi(&mut _s);
            ::quote::__private::push_ident(&mut _s, "x");
            _s
        });
    _s
}quote::quote! {
1349                use ::miniextendr_api::TryFromSexp;
1350                unsafe {
1351                    let require_live =
1352                        ::miniextendr_api::externalptr::sidecar_require_live(require_live);
1353                    ::miniextendr_api::externalptr::sidecar_r_check::<#struct_name>(x, require_live);
1354                    if let ::core::result::Result::Err(e) = <#value_ty as TryFromSexp>::try_from_sexp(value) {
1355                        return #err_value;
1356                    }
1357                    ::miniextendr_api::externalptr::sidecar_r_set::<#struct_name>(
1358                        x, #index, value, require_live,
1359                    );
1360                    x
1361                }
1362            }
1363        }
1364        SlotKind::ScalarInt => {
1365            let err_value = scalar_err("a number");
1366            {
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::__private::push_ident(&mut _s, "use");
    ::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, "SexpExt");
    ::quote::__private::push_semi(&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::ToTokens::to_tokens(&extract_mut, &mut _s);
            ::quote::__private::push_ident(&mut _s, "data");
            ::quote::__private::push_dot(&mut _s);
            ::quote::ToTokens::to_tokens(&field_name, &mut _s);
            ::quote::__private::push_eq(&mut _s);
            ::quote::__private::push_ident(&mut _s, "match");
            ::quote::__private::push_ident(&mut _s, "value");
            ::quote::__private::push_dot(&mut _s);
            ::quote::__private::push_ident(&mut _s, "as_integer");
            ::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_ident(&mut _s, "return");
                    ::quote::ToTokens::to_tokens(&err_value, &mut _s);
                    ::quote::__private::push_comma(&mut _s);
                    _s
                });
            ::quote::__private::push_semi(&mut _s);
            ::quote::__private::push_ident(&mut _s, "x");
            _s
        });
    _s
}quote::quote! {
1367                use ::miniextendr_api::SexpExt;
1368                unsafe {
1369                    #extract_mut
1370                    data.#field_name = match value.as_integer() {
1371                        ::core::option::Option::Some(v) => v,
1372                        ::core::option::Option::None => return #err_value,
1373                    };
1374                    x
1375                }
1376            }
1377        }
1378        SlotKind::ScalarReal => {
1379            let err_value = scalar_err("a number");
1380            {
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::__private::push_ident(&mut _s, "use");
    ::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, "SexpExt");
    ::quote::__private::push_semi(&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::ToTokens::to_tokens(&extract_mut, &mut _s);
            ::quote::__private::push_ident(&mut _s, "data");
            ::quote::__private::push_dot(&mut _s);
            ::quote::ToTokens::to_tokens(&field_name, &mut _s);
            ::quote::__private::push_eq(&mut _s);
            ::quote::__private::push_ident(&mut _s, "match");
            ::quote::__private::push_ident(&mut _s, "value");
            ::quote::__private::push_dot(&mut _s);
            ::quote::__private::push_ident(&mut _s, "as_real");
            ::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_ident(&mut _s, "return");
                    ::quote::ToTokens::to_tokens(&err_value, &mut _s);
                    ::quote::__private::push_comma(&mut _s);
                    _s
                });
            ::quote::__private::push_semi(&mut _s);
            ::quote::__private::push_ident(&mut _s, "x");
            _s
        });
    _s
}quote::quote! {
1381                use ::miniextendr_api::SexpExt;
1382                unsafe {
1383                    #extract_mut
1384                    data.#field_name = match value.as_real() {
1385                        ::core::option::Option::Some(v) => v,
1386                        ::core::option::Option::None => return #err_value,
1387                    };
1388                    x
1389                }
1390            }
1391        }
1392        SlotKind::ScalarLogical => {
1393            let err_value = scalar_err("TRUE or FALSE");
1394            {
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::__private::push_ident(&mut _s, "use");
    ::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, "SexpExt");
    ::quote::__private::push_semi(&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::ToTokens::to_tokens(&extract_mut, &mut _s);
            ::quote::__private::push_ident(&mut _s, "data");
            ::quote::__private::push_dot(&mut _s);
            ::quote::ToTokens::to_tokens(&field_name, &mut _s);
            ::quote::__private::push_eq(&mut _s);
            ::quote::__private::push_ident(&mut _s, "match");
            ::quote::__private::push_ident(&mut _s, "value");
            ::quote::__private::push_dot(&mut _s);
            ::quote::__private::push_ident(&mut _s, "as_logical");
            ::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_ident(&mut _s, "return");
                    ::quote::ToTokens::to_tokens(&err_value, &mut _s);
                    ::quote::__private::push_comma(&mut _s);
                    _s
                });
            ::quote::__private::push_semi(&mut _s);
            ::quote::__private::push_ident(&mut _s, "x");
            _s
        });
    _s
}quote::quote! {
1395                use ::miniextendr_api::SexpExt;
1396                unsafe {
1397                    #extract_mut
1398                    data.#field_name = match value.as_logical() {
1399                        ::core::option::Option::Some(v) => v,
1400                        ::core::option::Option::None => return #err_value,
1401                    };
1402                    x
1403                }
1404            }
1405        }
1406        SlotKind::ScalarRaw => {
1407            let err_value = scalar_err("a raw value");
1408            {
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::__private::push_ident(&mut _s, "use");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "miniextendr_api");
    ::quote::__private::push_colon2(&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, "SexpExt");
            ::quote::__private::push_comma(&mut _s);
            ::quote::__private::push_ident(&mut _s, "SEXPTYPE");
            _s
        });
    ::quote::__private::push_semi(&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::ToTokens::to_tokens(&extract_mut, &mut _s);
            ::quote::__private::push_ident(&mut _s, "let");
            ::quote::__private::push_ident(&mut _s, "raw_vec");
            ::quote::__private::push_eq(&mut _s);
            ::quote::__private::push_ident(&mut _s, "value");
            ::quote::__private::push_dot(&mut _s);
            ::quote::__private::push_ident(&mut _s, "coerce");
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::__private::push_ident(&mut _s, "SEXPTYPE");
                    ::quote::__private::push_colon2(&mut _s);
                    ::quote::__private::push_ident(&mut _s, "RAWSXP");
                    _s
                });
            ::quote::__private::push_semi(&mut _s);
            ::quote::__private::push_ident(&mut _s, "if");
            ::quote::__private::push_ident(&mut _s, "raw_vec");
            ::quote::__private::push_dot(&mut _s);
            ::quote::__private::push_ident(&mut _s, "len");
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                ::quote::__private::TokenStream::new());
            ::quote::__private::push_eq_eq(&mut _s);
            ::quote::__private::parse(&mut _s, "0");
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Brace,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::__private::push_ident(&mut _s, "return");
                    ::quote::ToTokens::to_tokens(&err_value, &mut _s);
                    ::quote::__private::push_semi(&mut _s);
                    _s
                });
            ::quote::__private::push_ident(&mut _s, "data");
            ::quote::__private::push_dot(&mut _s);
            ::quote::ToTokens::to_tokens(&field_name, &mut _s);
            ::quote::__private::push_eq(&mut _s);
            ::quote::__private::push_ident(&mut _s, "raw_vec");
            ::quote::__private::push_dot(&mut _s);
            ::quote::__private::push_ident(&mut _s, "raw_elt");
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::__private::parse(&mut _s, "0");
                    _s
                });
            ::quote::__private::push_semi(&mut _s);
            ::quote::__private::push_ident(&mut _s, "x");
            _s
        });
    _s
}quote::quote! {
1409                use ::miniextendr_api::{SexpExt, SEXPTYPE};
1410                unsafe {
1411                    #extract_mut
1412                    let raw_vec = value.coerce(SEXPTYPE::RAWSXP);
1413                    if raw_vec.len() == 0 {
1414                        return #err_value;
1415                    }
1416                    data.#field_name = raw_vec.raw_elt(0);
1417                    x
1418                }
1419            }
1420        }
1421        SlotKind::Conversion => {
1422            let ty = &slot.ty;
1423            let err_value = conversion_err(ty);
1424            {
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::__private::push_ident(&mut _s, "use");
    ::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, "TryFromSexp");
    ::quote::__private::push_semi(&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::ToTokens::to_tokens(&extract_mut, &mut _s);
            ::quote::__private::push_ident(&mut _s, "data");
            ::quote::__private::push_dot(&mut _s);
            ::quote::ToTokens::to_tokens(&field_name, &mut _s);
            ::quote::__private::push_eq(&mut _s);
            ::quote::__private::push_ident(&mut _s, "match");
            ::quote::__private::push_lt(&mut _s);
            ::quote::ToTokens::to_tokens(&ty, &mut _s);
            ::quote::__private::push_ident(&mut _s, "as");
            ::quote::__private::push_ident(&mut _s, "TryFromSexp");
            ::quote::__private::push_gt(&mut _s);
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "try_from_sexp");
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::__private::push_ident(&mut _s, "value");
                    _s
                });
            ::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, "result");
                    ::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, "Ok");
                    ::quote::__private::push_group(&mut _s,
                        ::quote::__private::Delimiter::Parenthesis,
                        {
                            let mut _s = ::quote::__private::TokenStream::new();
                            ::quote::__private::push_ident(&mut _s, "val");
                            _s
                        });
                    ::quote::__private::push_fat_arrow(&mut _s);
                    ::quote::__private::push_ident(&mut _s, "val");
                    ::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, "result");
                    ::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, "Err");
                    ::quote::__private::push_group(&mut _s,
                        ::quote::__private::Delimiter::Parenthesis,
                        {
                            let mut _s = ::quote::__private::TokenStream::new();
                            ::quote::__private::push_ident(&mut _s, "e");
                            _s
                        });
                    ::quote::__private::push_fat_arrow(&mut _s);
                    ::quote::__private::push_ident(&mut _s, "return");
                    ::quote::ToTokens::to_tokens(&err_value, &mut _s);
                    ::quote::__private::push_comma(&mut _s);
                    _s
                });
            ::quote::__private::push_semi(&mut _s);
            ::quote::__private::push_ident(&mut _s, "x");
            _s
        });
    _s
}quote::quote! {
1425                use ::miniextendr_api::TryFromSexp;
1426                unsafe {
1427                    #extract_mut
1428                    data.#field_name = match <#ty as TryFromSexp>::try_from_sexp(value) {
1429                        ::core::result::Result::Ok(val) => val,
1430                        ::core::result::Result::Err(e) => return #err_value,
1431                    };
1432                    x
1433                }
1434            }
1435        }
1436    }
1437}
1438
1439/// Generate R wrapper code (roxygen-annotated R functions) for a single sidecar slot.
1440///
1441/// Produces getter and setter R functions that call the corresponding C entry
1442/// points via `.Call`. The generated R code includes roxygen tags (`@rdname`,
1443/// `@param`, `@return`, `@export`) for documentation.
1444///
1445/// All class systems generate the same standalone function pattern
1446/// (`Type_get_field` / `Type_set_field`); only the roxygen title suffix
1447/// differs. Class-specific integration (e.g., R6 active bindings, S7
1448/// properties) is handled separately by [`generate_class_integration_r_code`].
1449///
1450/// Each wrapper body carries the shared tagged-condition guard
1451/// ([`crate::method_return_builder::standalone_body`]) so Rust-origin
1452/// panics/conversion failures are re-raised as structured R conditions.
1453/// Note the sidecar C functions have **no** `.call` slot — the `.Call()`
1454/// passes no `.call` argument (#344/#348); `sys.call()` inside the guard
1455/// supplies fallback attribution instead. The last `.Call()` argument is the
1456/// `require_live` flag: `FALSE` here, so the standalone accessors keep
1457/// reading a save's `Sidecar` values (#1891).
1458///
1459/// `field_name` is the field's R name (`Type_get_<name>()`). A `Computed`
1460/// field (`setter_c_name` is `None`) gets the getter only.
1461///
1462/// `exported` writes them on the type's page with `@export`; otherwise they
1463/// are internal (`@noRd`, no export), the rendering a type whose class has
1464/// `$` / `[[` field syntax gets (#1900).
1465fn generate_r_wrapper_for_slot(
1466    class_system: ClassSystem,
1467    type_name: &str,
1468    field_name: &str,
1469    getter_c_name: &str,
1470    setter_c_name: Option<&str>,
1471    setter_invisible: bool,
1472    exported: bool,
1473) -> String {
1474    // Only the roxygen title suffix differs between class systems.
1475    let suffix = match class_system {
1476        ClassSystem::Env => "",
1477        ClassSystem::R6 => " (for R6)",
1478        ClassSystem::S3 => " (for S3)",
1479        ClassSystem::S4 => " (for S4)",
1480        ClassSystem::S7 => " (for S7)",
1481        ClassSystem::Vctrs => " (for vctrs)",
1482    };
1483    let r_getter_name = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0}_get_{1}", type_name,
                field_name))
    })format!("{}_get_{}", type_name, field_name);
1484    let getter_body = crate::method_return_builder::standalone_body(
1485        &::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!(".Call({0}, x, FALSE)",
                getter_c_name))
    })format!(".Call({getter_c_name}, x, FALSE)"),
1486        ".val",
1487        "  ",
1488    );
1489    let getter_tags = if exported {
1490        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("#\' @rdname {0}\n#\' @param x The {0} external pointer\n#\' @return The value of the `{1}` field\n#\' @export",
                type_name, field_name))
    })format!(
1491            "#' @rdname {type_name}\n#' @param x The {type_name} external pointer\n\
1492             #' @return The value of the `{field_name}` field\n#' @export"
1493        )
1494    } else {
1495        "#' @noRd".to_string()
1496    };
1497    let mut out = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("\n#\' Get `{1}` field from {0}{2}\n{5}\n{3} <- function(x) {{\n  {4}\n}}\n",
                type_name, field_name, suffix, r_getter_name, getter_body,
                getter_tags))
    })format!(
1498        r#"
1499#' Get `{field}` field from {type}{suffix}
1500{getter_tags}
1501{r_getter} <- function(x) {{
1502  {getter_body}
1503}}
1504"#,
1505        type = type_name,
1506        field = field_name,
1507        suffix = suffix,
1508        r_getter = r_getter_name,
1509        getter_body = getter_body,
1510    );
1511    let Some(setter_c_name) = setter_c_name else {
1512        return out;
1513    };
1514    let r_setter_name = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0}_set_{1}", type_name,
                field_name))
    })format!("{}_set_{}", type_name, field_name);
1515    let setter_body = crate::method_return_builder::standalone_body(
1516        &::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!(".Call({0}, x, value, FALSE)",
                setter_c_name))
    })format!(".Call({setter_c_name}, x, value, FALSE)"),
1517        if setter_invisible {
1518            "invisible(x)"
1519        } else {
1520            "x"
1521        },
1522        "  ",
1523    );
1524    let setter_tags = if exported {
1525        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("#\' @rdname {1}\n#\' @param x The {1} external pointer\n#\' @param value The new value to set\n#\' @return The {1} pointer ({0})\n#\' @export",
                if setter_invisible { "invisibly" } else { "visibly" },
                type_name))
    })format!(
1526            "#' @rdname {type_name}\n#' @param x The {type_name} external pointer\n\
1527             #' @param value The new value to set\n#' @return The {type_name} pointer ({visibility})\n\
1528             #' @export",
1529            visibility = if setter_invisible {
1530                "invisibly"
1531            } else {
1532                "visibly"
1533            },
1534        )
1535    } else {
1536        "#' @noRd".to_string()
1537    };
1538    out.push_str(&::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("\n#\' Set `{1}` field on {0}{2}\n{5}\n{3} <- function(x, value) {{\n  {4}\n}}\n",
                type_name, field_name, suffix, r_setter_name, setter_body,
                setter_tags))
    })format!(
1539        r#"
1540#' Set `{field}` field on {type}{suffix}
1541{setter_tags}
1542{r_setter} <- function(x, value) {{
1543  {setter_body}
1544}}
1545"#,
1546        type = type_name,
1547        field = field_name,
1548        suffix = suffix,
1549        r_setter = r_setter_name,
1550        setter_body = setter_body,
1551    ));
1552    out
1553}
1554
1555/// Generate class-integrated R code for sidecar fields.
1556///
1557/// For R6: generates `Type$set("active", "field", ...)` calls that add active bindings
1558/// referencing the sidecar getter/setter .Call entrypoints.
1559///
1560/// For S7: generates `.rdata_properties_Type <- list(...)` with S7::new_property()
1561/// definitions that can be spliced into the S7 class's properties list.
1562///
1563/// Other class systems return empty strings (their standalone accessors suffice).
1564fn generate_class_integration_r_code(
1565    class_system: ClassSystem,
1566    type_name: &str,
1567    pub_slots: &[&SidecarSlot],
1568) -> String {
1569    if pub_slots.is_empty() {
1570        return String::new();
1571    }
1572
1573    // Shared tagged-condition guard (one line); `sys.call()` supplies fallback
1574    // attribution because the sidecar C functions carry no `.call` slot
1575    // (#344/#348 — a sidecar `.Call()` passes no `.call` argument).
1576    let guard =
1577        |indent: &str| crate::method_return_builder::condition_check_lines(indent).join("\n");
1578
1579    match class_system {
1580        ClassSystem::R6 => {
1581            // Generate $set("active", ...) calls for each sidecar field.
1582            // These are appended after the R6Class definition and add active bindings
1583            // that delegate to the sidecar .Call accessors.
1584            let mut code = String::new();
1585            code.push_str(&::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("\n# Auto-generated active bindings for {0} sidecar fields.\n",
                type_name))
    })format!(
1586                "\n# Auto-generated active bindings for {type} sidecar fields.\n",
1587                type = type_name,
1588            ));
1589            code.push_str(
1590                "# These are applied when `r_data_accessors` is set on the impl block.\n",
1591            );
1592            code.push_str(&::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!(".rdata_active_bindings_{0} <- function(cls) {{\n  # R CMD check: self/private are R6 runtime bindings (set by cls$set)\n  self <- private <- NULL\n",
                type_name))
    })format!(
1593                ".rdata_active_bindings_{type} <- function(cls) {{\n\
1594                 \x20 # R CMD check: self/private are R6 runtime bindings (set by cls$set)\n\
1595                 \x20 self <- private <- NULL\n",
1596                type = type_name,
1597            ));
1598            for slot in pub_slots {
1599                // The binding takes the R name; the C symbols keep the Rust
1600                // identifier (#1891).
1601                let field = &slot.r_name;
1602                let rust_field = crate::naming::ident_name(&slot.name);
1603                // Must match the sidecar accessor's actual C symbol exactly (#1273
1604                // crate-prefixing) — routed through the shared naming.rs helpers.
1605                let getter_c = crate::naming::sidecar_getter_c_name(type_name, &rust_field);
1606                let setter_c = crate::naming::sidecar_setter_c_name(type_name, &rust_field);
1607                // `FALSE`: the bindings keep reading a save's `Sidecar` values.
1608                code.push_str(&::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("  cls$set(\"active\", \"{0}\", function(value) {{\n    if (missing(value)) {{\n      .val <- .Call({1}, private$.ptr, FALSE)\n{3}\n      .val\n    }} else {{\n      .val <- .Call({2}, private$.ptr, value, FALSE)\n{4}\n      {5}\n    }}\n  }}, overwrite = TRUE)\n",
                field, getter_c, setter_c, guard("      "), guard("      "),
                if slot.setter_invisible.unwrap_or(true) {
                    "invisible(self)"
                } else { "self" }))
    })format!(
1609                    "  cls$set(\"active\", \"{field}\", function(value) {{\n\
1610                     \x20   if (missing(value)) {{\n\
1611                     \x20     .val <- .Call({getter_c}, private$.ptr, FALSE)\n\
1612                     {getter_guard}\n\
1613                     \x20     .val\n\
1614                     \x20   }} else {{\n\
1615                     \x20     .val <- .Call({setter_c}, private$.ptr, value, FALSE)\n\
1616                     {setter_guard}\n\
1617                     \x20     {setter_return}\n\
1618                     \x20   }}\n\
1619                     \x20 }}, overwrite = TRUE)\n",
1620                    field = field,
1621                    getter_c = getter_c,
1622                    setter_c = setter_c,
1623                    getter_guard = guard("      "),
1624                    setter_guard = guard("      "),
1625                    setter_return = if slot.setter_invisible.unwrap_or(true) {
1626                        "invisible(self)"
1627                    } else {
1628                        "self"
1629                    },
1630                ));
1631            }
1632            code.push_str("}\n");
1633            code
1634        }
1635        ClassSystem::S7 => {
1636            // Generate a helper list of S7::new_property() definitions.
1637            // The S7 wrapper generator references this when `r_data_accessors` is set.
1638            let mut code = String::new();
1639            code.push_str(&::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("\n# Auto-generated S7 property definitions for {0} sidecar fields.\n",
                type_name))
    })format!(
1640                "\n# Auto-generated S7 property definitions for {type} sidecar fields.\n",
1641                type = type_name,
1642            ));
1643            code.push_str(&::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!(".rdata_properties_{0} <- list(\n",
                type_name))
    })format!(
1644                ".rdata_properties_{type} <- list(\n",
1645                type = type_name,
1646            ));
1647            for (i, slot) in pub_slots.iter().enumerate() {
1648                // The property takes the R name; the C symbols keep the Rust
1649                // identifier (#1891).
1650                let field = &slot.r_name;
1651                let rust_field = crate::naming::ident_name(&slot.name);
1652                // Must match the sidecar accessor's actual C symbol exactly (#1273
1653                // crate-prefixing) — routed through the shared naming.rs helpers.
1654                let getter_c = crate::naming::sidecar_getter_c_name(type_name, &rust_field);
1655                let setter_c = crate::naming::sidecar_setter_c_name(type_name, &rust_field);
1656                let comma = if i < pub_slots.len() - 1 { "," } else { "" };
1657                // `FALSE`: the properties keep reading a save's `Sidecar` values.
1658                code.push_str(&::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("    {0} = S7::new_property(\n        getter = function(self) {{\n          .val <- .Call({1}, self@.ptr, FALSE)\n{3}\n          .val\n        }},\n        setter = function(self, value) {{\n          .val <- .Call({2}, self@.ptr, value, FALSE)\n{4}\n          {5}\n        }}\n    ){6}\n",
                field, getter_c, setter_c, guard("          "),
                guard("          "),
                if slot.setter_invisible.unwrap_or(false) {
                    "invisible(self)"
                } else { "self" }, comma))
    })format!(
1659                    "    {field} = S7::new_property(\n\
1660                     \x20       getter = function(self) {{\n\
1661                     \x20         .val <- .Call({getter_c}, self@.ptr, FALSE)\n\
1662                     {getter_guard}\n\
1663                     \x20         .val\n\
1664                     \x20       }},\n\
1665                     \x20       setter = function(self, value) {{\n\
1666                     \x20         .val <- .Call({setter_c}, self@.ptr, value, FALSE)\n\
1667                     {setter_guard}\n\
1668                     \x20         {setter_return}\n\
1669                     \x20       }}\n\
1670                     \x20   ){comma}\n",
1671                    field = field,
1672                    getter_c = getter_c,
1673                    setter_c = setter_c,
1674                    getter_guard = guard("          "),
1675                    setter_guard = guard("          "),
1676                    setter_return = if slot.setter_invisible.unwrap_or(false) {
1677                        "invisible(self)"
1678                    } else {
1679                        "self"
1680                    },
1681                    comma = comma,
1682                ));
1683            }
1684            code.push_str(")\n");
1685            code
1686        }
1687        // Other class systems don't need class-integrated code
1688        _ => String::new(),
1689    }
1690}
1691
1692/// Generate the R helpers the `$` / `[[` field methods of `r_data_accessors`
1693/// share on S3, S4 and env classes (#1848): the field names, a getter and a
1694/// setter `switch()` over the fields' `.Call()`s, the classed
1695/// `miniextendr_no_field` error naming the fields, the classes the methods
1696/// put in front of their own errors (`field_error`, `NULL` without them,
1697/// #1899), and the liveness check of `restored(refuse = "every_read")`
1698/// with the flag that gates it (`FALSE` and a stub without it, #1901).
1699///
1700/// Prepared for every type with R accessors, whatever `#[externalptr(...)]`
1701/// names, and bundled with its internal accessor rendering. The writer selects
1702/// that rendering only when an impl enables field syntax, so the impl's option
1703/// never depends on the derive attribute. The
1704/// helpers are named by the Rust type ([`crate::naming::rdata_helper_name`]);
1705/// the field names and the `switch()` labels are the fields' R names, in
1706/// declared order, computed fields included (#1891, #1883).
1707///
1708/// Every arm passes `TRUE` as the accessor's `require_live` flag, so the
1709/// field methods refuse a pointer restored from a saved session (the
1710/// standalone accessors pass `FALSE` and keep reading its `Sidecar` values).
1711/// The accessors return a failed conversion or a panic as a tagged condition
1712/// value with no call; the calling method raises it with its own frame as
1713/// the fallback, so the condition reports `x$keys <- value`. A computed
1714/// field's setter arm raises `miniextendr_read_only_field` itself, with the
1715/// calling method's frame (`parent.frame()`) resolved the same way. The
1716/// liveness check returns the same kind of value, which the method raises.
1717///
1718/// `field_error(no_field_hint = "...")` adds a sentence after the field list
1719/// of the `miniextendr_no_field` message, each `%s` in it replaced by the
1720/// name asked for (#2112). The hint is split on `%s` here and pasted around
1721/// the name, so any other `%` or `\` in it, or in the name, stays as written.
1722fn generate_field_syntax_helpers(
1723    type_name: &str,
1724    pub_slots: &[&SidecarSlot],
1725    field_error: &[String],
1726    no_field_hint: Option<&str>,
1727    every_read: bool,
1728) -> String {
1729    use crate::naming::{RDataHelper, rdata_helper_name};
1730
1731    let error_class_var = rdata_helper_name(RDataHelper::ErrorClass, type_name);
1732    let names = pub_slots
1733        .iter()
1734        .map(|slot| ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("\"{0}\"", slot.r_name))
    })format!("\"{}\"", slot.r_name))
1735        .collect::<Vec<_>>()
1736        .join(", ");
1737    let listed = pub_slots
1738        .iter()
1739        .map(|slot| ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("`{0}`", slot.r_name))
    })format!("`{}`", slot.r_name))
1740        .collect::<Vec<_>>()
1741        .join(", ");
1742    // `switch()` arm labels are R symbols: a raw identifier such as `r#for`
1743    // becomes the reserved word `for`, which needs backticks. The C symbols
1744    // are named by the Rust identifier.
1745    let arms = |setter: bool| {
1746        pub_slots
1747            .iter()
1748            .map(|slot| {
1749                let rust_field = crate::naming::ident_name(&slot.name);
1750                let call = match (&slot.kind, setter) {
1751                    // The message names the receiver's R class (#1920), else
1752                    // the Rust type.
1753                    (SlotKind::Computed(_), true) => ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("stop(errorCondition(sprintf(\"`{0}` of `%s` is computed from the Rust value and can\'t be assigned\", if (is.null(oldClass(x))) \"{1}\" else oldClass(x)[[1L]]), class = c({2}, \"miniextendr_read_only_field\"), call = .miniextendr_frame_call(parent.frame())))",
                slot.r_name, type_name, error_class_var))
    })format!(
1754                        "stop(errorCondition(sprintf(\"`{field}` of `%s` is computed from the Rust \
1755                         value and can't be assigned\", if (is.null(oldClass(x))) \"{type_name}\" else oldClass(x)[[1L]]), \
1756                         class = c({error_class_var}, \"miniextendr_read_only_field\"), \
1757                         call = .miniextendr_frame_call(parent.frame())))",
1758                        field = slot.r_name,
1759                    ),
1760                    (_, true) => ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!(".Call({0}, x, value, TRUE)",
                crate::naming::sidecar_setter_c_name(type_name, &rust_field)))
    })format!(
1761                        ".Call({}, x, value, TRUE)",
1762                        crate::naming::sidecar_setter_c_name(type_name, &rust_field)
1763                    ),
1764                    (_, false) => ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!(".Call({0}, x, TRUE)",
                crate::naming::sidecar_getter_c_name(type_name, &rust_field)))
    })format!(
1765                        ".Call({}, x, TRUE)",
1766                        crate::naming::sidecar_getter_c_name(type_name, &rust_field)
1767                    ),
1768                };
1769                ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("  {0} = {1}",
                crate::naming::r_def_name(&slot.r_name), call))
    })format!("  {} = {call}", crate::naming::r_def_name(&slot.r_name))
1770            })
1771            .collect::<Vec<_>>()
1772            .join(",\n")
1773    };
1774    let error_class = if field_error.is_empty() {
1775        "NULL".to_string()
1776    } else {
1777        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("c({0})",
                field_error.iter().map(|class|
                                ::alloc::__export::must_use({
                                        ::alloc::fmt::format(format_args!("\"{0}\"",
                                                crate::naming::r_string_escape(class)))
                                    })).collect::<Vec<_>>().join(", ")))
    })format!(
1778            "c({})",
1779            field_error
1780                .iter()
1781                .map(|class| format!("\"{}\"", crate::naming::r_string_escape(class)))
1782                .collect::<Vec<_>>()
1783                .join(", ")
1784        )
1785    };
1786    // Without the option the methods never call the check (the flag is
1787    // `FALSE`), but they name it, and `R CMD check` notes a call to a binding
1788    // that is no function.
1789    let live = if every_read {
1790        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("function(x, raise) .Call({0}, x, raise)",
                crate::naming::sidecar_live_c_name(type_name)))
    })format!(
1791            "function(x, raise) .Call({}, x, raise)",
1792            crate::naming::sidecar_live_c_name(type_name)
1793        )
1794    } else {
1795        "function(x, raise) TRUE".to_string()
1796    };
1797    let every_read = if every_read { "TRUE" } else { "FALSE" };
1798    let no_field_message =
1799        ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("sprintf(\"`%s` is not a field of `%s`; its fields are {0}\", name, cls)",
                listed))
    })format!("sprintf(\"`%s` is not a field of `%s`; its fields are {listed}\", name, cls)");
1800    let no_field_message = match no_field_hint {
1801        None => no_field_message,
1802        Some(hint) => {
1803            let hint = hint
1804                .split("%s")
1805                .map(|part| ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("\"{0}\"",
                crate::naming::r_string_escape(part)))
    })format!("\"{}\"", crate::naming::r_string_escape(part)))
1806                .collect::<Vec<_>>()
1807                .join(", name, ");
1808            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("paste0({0}, \". \", {1})",
                no_field_message, hint))
    })format!("paste0({no_field_message}, \". \", {hint})")
1809        }
1810    };
1811    ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("\n# Field syntax helpers for {8} sidecar fields, shared by the `$` / `[[`\n# methods `r_data_accessors` generates on S3, S4 and env classes. The `TRUE`\n# is the accessors\' `require_live` flag: the methods refuse a restored pointer.\n{0} <- c({9})\n{1} <- function(x, name) switch(name,\n{6})\n{2} <- function(x, name, value) switch(name,\n{7})\n{10} <- {11}\n{3} <- function(name, env, cls) stop(errorCondition(\n  {12},\n  class = c({10}, \"miniextendr_no_field\"), call = .miniextendr_frame_call(env)))\n{4} <- {13}\n{5} <- {14}\n",
                rdata_helper_name(RDataHelper::Fields, type_name),
                rdata_helper_name(RDataHelper::Get, type_name),
                rdata_helper_name(RDataHelper::Set, type_name),
                rdata_helper_name(RDataHelper::NoField, type_name),
                rdata_helper_name(RDataHelper::EveryRead, type_name),
                rdata_helper_name(RDataHelper::Live, type_name), arms(false),
                arms(true), type_name, names, error_class_var, error_class,
                no_field_message, every_read, live))
    })format!(
1812        "\n# Field syntax helpers for {type_name} sidecar fields, shared by the `$` / `[[`\n\
1813         # methods `r_data_accessors` generates on S3, S4 and env classes. The `TRUE`\n\
1814         # is the accessors' `require_live` flag: the methods refuse a restored pointer.\n\
1815         {fields_var} <- c({names})\n\
1816         {get_fn} <- function(x, name) switch(name,\n{get_arms})\n\
1817         {set_fn} <- function(x, name, value) switch(name,\n{set_arms})\n\
1818         {error_class_var} <- {error_class}\n\
1819         {no_field_fn} <- function(name, env, cls) stop(errorCondition(\n\
1820         \x20 {no_field_message},\n\
1821         \x20 class = c({error_class_var}, \"miniextendr_no_field\"), call = .miniextendr_frame_call(env)))\n\
1822         {every_read_var} <- {every_read}\n\
1823         {live_fn} <- {live}\n",
1824        fields_var = rdata_helper_name(RDataHelper::Fields, type_name),
1825        get_fn = rdata_helper_name(RDataHelper::Get, type_name),
1826        set_fn = rdata_helper_name(RDataHelper::Set, type_name),
1827        no_field_fn = rdata_helper_name(RDataHelper::NoField, type_name),
1828        every_read_var = rdata_helper_name(RDataHelper::EveryRead, type_name),
1829        live_fn = rdata_helper_name(RDataHelper::Live, type_name),
1830        get_arms = arms(false),
1831        set_arms = arms(true),
1832    )
1833}
1834
1835/// Generate sidecar accessor constants and `extern "C-unwind"` functions.
1836///
1837/// For each public `#[r_data]` field, generates:
1838/// - A getter FFI function (`C_<crate>__mx_rdata_get_Type_field`)
1839/// - A setter FFI function (`C_<crate>__mx_rdata_set_Type_field`)
1840/// - `R_CallMethodDef` entries for routine registration
1841/// - R wrapper function code (roxygen-documented)
1842/// - Class-integration code for R6 / S7 (active bindings / properties)
1843/// - The helpers of the S3 / S4 / env field methods
1844///   ([`generate_field_syntax_helpers`])
1845///
1846/// - With `restored(refuse = "every_read")`, the liveness check
1847///   (`C_<crate>__mx_rdata_live_Type`, #1901) the field methods call
1848///
1849/// The generated statics are:
1850/// - `__MX_CALL_DEF_RDATA_*`: the `R_CallMethodDef` entries
1851/// - `R_WRAPPERS_RDATA_{Type}`: the R6 / S7 integration, when present
1852/// - `R_SIDECAR_ACCESSORS_{Type}`: the standalone accessors, exported and
1853///   internal, which the wrapper writer picks between by the class's field
1854///   syntax (`registry::SidecarAccessorsEntry`, #1900). Only the internal
1855///   rendering includes the field-syntax helpers.
1856///
1857/// Returns `Err` if the struct has generic type parameters (`.Call` entrypoints
1858/// cannot be generic).
1859fn generate_sidecar_accessors(
1860    input: &DeriveInput,
1861    info: &SidecarInfo,
1862    restored: &RestoredOptions,
1863    field_error: &FieldErrorOptions,
1864) -> syn::Result<TokenStream> {
1865    // Reject generic structs — .Call entrypoints cannot be generic
1866    if !input.generics.params.is_empty() {
1867        return Err(syn::Error::new_spanned(
1868            &input.generics,
1869            "ExternalPtr does not support generic structs; \
1870             .Call entrypoints cannot be generic",
1871        ));
1872    }
1873
1874    // If no selector or no public slots, nothing to register
1875    let pub_slots: Vec<_> = info.slots.iter().filter(|s| s.is_public).collect();
1876    if !info.has_selector || pub_slots.is_empty() {
1877        return Ok(::quote::__private::TokenStream::new()quote::quote! {});
1878    }
1879
1880    let name = &input.ident;
1881    let name_str = name.to_string();
1882    let name_symbol = crate::naming::ident_name(name);
1883
1884    // Generate getter/setter functions and R wrappers for each pub slot
1885    let mut c_functions = ::alloc::vec::Vec::new()vec![];
1886    // The standalone accessors, in the two renderings the wrapper writer
1887    // picks between (#1900).
1888    let mut exported = String::new();
1889    let mut internal = String::new();
1890
1891    // The exported rendering's page header establishes the @name topic for
1892    // the accessors' `@rdname`, even if the main class definition uses @noRd.
1893    let source_line = crate::roxygen::source_tag(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("Generated by miniextendr from `#[derive(ExternalPtr)]` on `{0}`",
                name_str))
    })format!(
1894        "Generated by miniextendr from `#[derive(ExternalPtr)]` on `{name_str}`"
1895    ))
1896    .map(|line| ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0}\n", line))
    })format!("{line}\n"))
1897    .unwrap_or_default();
1898    exported.push_str(&::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("\n#\' @title {0} Sidecar Accessors\n#\' @name {0}\n#\' @description Getter and setter functions for `#[r_data]` fields on `{0}`.\n{1}NULL\n\n",
                name_str, source_line))
    })format!(
1899        r#"
1900#' @title {type} Sidecar Accessors
1901#' @name {type}
1902#' @description Getter and setter functions for `#[r_data]` fields on `{type}`.
1903{source_line}NULL
1904
1905"#,
1906        type = name_str,
1907    ));
1908
1909    for slot in &pub_slots {
1910        let field_name = &slot.name;
1911        // The C symbols and the `R_CallMethodDef` statics are named by the
1912        // Rust identifier (`Ident::new` would refuse an R name such as
1913        // `n.rows`); every R-facing string takes the R name (#1891).
1914        let field_name_str = crate::naming::ident_name(field_name);
1915        let r_name = &slot.r_name;
1916        let computed = #[allow(non_exhaustive_omitted_patterns)] match slot.kind {
    SlotKind::Computed(_) => true,
    _ => false,
}matches!(slot.kind, SlotKind::Computed(_));
1917
1918        // C function names (crate-prefixed for webR cross-package symbol
1919        // uniqueness — #1273, routed through the shared naming.rs helpers)
1920        let getter_c_name = crate::naming::sidecar_getter_c_name(&name_str, &field_name_str);
1921        let setter_c_name = crate::naming::sidecar_setter_c_name(&name_str, &field_name_str);
1922        let getter_fn_name = Ident::new(&getter_c_name, Span::call_site());
1923        let setter_fn_name = Ident::new(&setter_c_name, Span::call_site());
1924        let source_location_doc = crate::source_location_doc(field_name.span());
1925        let getter_doc = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("Generated sidecar getter for `{0}` field on Rust type `{1}`.",
                field_name_str, name_str))
    })format!(
1926            "Generated sidecar getter for `{}` field on Rust type `{}`.",
1927            field_name_str, name_str
1928        );
1929        let setter_doc = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("Generated sidecar setter for `{0}` field on Rust type `{1}`.",
                field_name_str, name_str))
    })format!(
1930            "Generated sidecar setter for `{}` field on Rust type `{}`.",
1931            field_name_str, name_str
1932        );
1933        let getter_doc_lit = syn::LitStr::new(&getter_doc, field_name.span());
1934        let setter_doc_lit = syn::LitStr::new(&setter_doc, field_name.span());
1935        // The `require_live` flag the R side passes last: `TRUE` from the
1936        // field methods, `FALSE` from the standalone accessors, the R6
1937        // bindings and the S7 properties. Only a `Sidecar` slot reads it.
1938        let require_live = require_live_param(slot);
1939
1940        // Generate getter/setter bodies based on slot kind
1941        let getter_body = generate_getter_body(name, slot);
1942        let setter_body = generate_setter_body(name, slot);
1943
1944        // Generate C getter function.
1945        //
1946        // The body runs under `with_r_unwind_protect` so Rust panics (and R
1947        // longjmps during allocation) become a tagged condition SEXP that the
1948        // R wrapper re-raises, matching the main call-slot path. Sidecar
1949        // accessors have no `__miniextendr_call` slot (#344/#348), so the
1950        // transport uses null call attribution; the R wrapper's guard passes
1951        // `sys.call()` to `.miniextendr_raise_condition` as the fallback.
1952        // Conditions queued inside the body (a `defer_warning!` in a field's
1953        // `IntoR` / `TryFromSexp`) are signalled at this call, through the
1954        // same `mark()` / `finish()` pair the generated wrappers have.
1955        c_functions.push({
    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(&getter_doc_lit, &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::ToTokens::to_tokens(&source_location_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::__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_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::__private::push_ident(&mut _s, "hidden");
                    _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, "unsafe");
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::__private::push_ident(&mut _s, "no_mangle");
                    _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, "allow");
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::__private::push_ident(&mut _s, "non_snake_case");
                    _s
                });
            _s
        });
    ::quote::__private::push_ident(&mut _s, "pub");
    ::quote::__private::push_ident(&mut _s, "unsafe");
    ::quote::__private::push_ident(&mut _s, "extern");
    ::quote::__private::parse(&mut _s, "\"C-unwind\"");
    ::quote::__private::push_ident(&mut _s, "fn");
    ::quote::ToTokens::to_tokens(&getter_fn_name, &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, "x");
            ::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, "SEXP");
            ::quote::__private::push_comma(&mut _s);
            ::quote::ToTokens::to_tokens(&require_live, &mut _s);
            ::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, "SEXP");
            ::quote::__private::push_comma(&mut _s);
            _s
        });
    ::quote::__private::push_rarrow(&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_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,
                "__miniextendr_deferred_mark");
            ::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, "deferred_condition");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "mark");
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                ::quote::__private::TokenStream::new());
            ::quote::__private::push_semi(&mut _s);
            ::quote::__private::push_ident(&mut _s, "let");
            ::quote::__private::push_ident(&mut _s, "__miniextendr_value");
            ::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, "unwind_protect");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "with_r_unwind_protect");
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::__private::push_or_or(&mut _s);
                    ::quote::__private::push_group(&mut _s,
                        ::quote::__private::Delimiter::Brace,
                        {
                            let mut _s = ::quote::__private::TokenStream::new();
                            ::quote::ToTokens::to_tokens(&getter_body, &mut _s);
                            _s
                        });
                    ::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_comma(&mut _s);
                    _s
                });
            ::quote::__private::push_semi(&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,
                        "deferred_condition");
                    ::quote::__private::push_colon2(&mut _s);
                    ::quote::__private::push_ident(&mut _s, "finish");
                    ::quote::__private::push_group(&mut _s,
                        ::quote::__private::Delimiter::Parenthesis,
                        {
                            let mut _s = ::quote::__private::TokenStream::new();
                            ::quote::__private::push_ident(&mut _s,
                                "__miniextendr_deferred_mark");
                            ::quote::__private::push_comma(&mut _s);
                            ::quote::__private::push_ident(&mut _s,
                                "__miniextendr_value");
                            ::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_comma(&mut _s);
                            _s
                        });
                    _s
                });
            _s
        });
    _s
}quote::quote! {
1956            #[doc = #getter_doc_lit]
1957            #[doc = #source_location_doc]
1958            #[doc = ::core::concat!("Generated from source file `", ::core::file!(), "`.")]
1959            #[doc(hidden)]
1960            #[unsafe(no_mangle)]
1961            #[allow(non_snake_case)]
1962            pub unsafe extern "C-unwind" fn #getter_fn_name(
1963                x: ::miniextendr_api::SEXP,
1964                #require_live: ::miniextendr_api::SEXP,
1965            ) -> ::miniextendr_api::SEXP {
1966                let __miniextendr_deferred_mark = ::miniextendr_api::deferred_condition::mark();
1967                let __miniextendr_value = ::miniextendr_api::unwind_protect::with_r_unwind_protect(
1968                    || { #getter_body },
1969                    ::core::option::Option::None,
1970                );
1971                unsafe {
1972                    ::miniextendr_api::deferred_condition::finish(
1973                        __miniextendr_deferred_mark,
1974                        __miniextendr_value,
1975                        ::core::option::Option::None,
1976                    )
1977                }
1978            }
1979        });
1980
1981        // Generate R_CallMethodDef entries via distributed slice
1982        let getter_def_ident = Ident::new(
1983            &::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("__MX_CALL_DEF_RDATA_GET_{0}_{1}",
                name_symbol, field_name_str))
    })format!("__MX_CALL_DEF_RDATA_GET_{}_{}", name_symbol, field_name_str),
1984            Span::call_site(),
1985        );
1986        let getter_registration =
1987            crate::call_method_def::call_method_def(&getter_def_ident, &getter_fn_name, 2);
1988        c_functions.push({
    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_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::__private::push_ident(&mut _s, "hidden");
                    _s
                });
            _s
        });
    ::quote::ToTokens::to_tokens(&getter_registration, &mut _s);
    _s
}quote::quote! {
1989            #[doc(hidden)]
1990            #getter_registration
1991        });
1992
1993        // A computed field has no setter: no C function, no `CallDef`, no
1994        // `Type_set_<name>()` (#1883).
1995        if !computed {
1996            // Generate C setter function (same unwind/condition transport as
1997            // the getter above).
1998            c_functions.push({
    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(&setter_doc_lit, &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::ToTokens::to_tokens(&source_location_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::__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_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::__private::push_ident(&mut _s, "hidden");
                    _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, "unsafe");
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::__private::push_ident(&mut _s, "no_mangle");
                    _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, "allow");
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::__private::push_ident(&mut _s, "non_snake_case");
                    _s
                });
            _s
        });
    ::quote::__private::push_ident(&mut _s, "pub");
    ::quote::__private::push_ident(&mut _s, "unsafe");
    ::quote::__private::push_ident(&mut _s, "extern");
    ::quote::__private::parse(&mut _s, "\"C-unwind\"");
    ::quote::__private::push_ident(&mut _s, "fn");
    ::quote::ToTokens::to_tokens(&setter_fn_name, &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, "x");
            ::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, "SEXP");
            ::quote::__private::push_comma(&mut _s);
            ::quote::__private::push_ident(&mut _s, "value");
            ::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, "SEXP");
            ::quote::__private::push_comma(&mut _s);
            ::quote::ToTokens::to_tokens(&require_live, &mut _s);
            ::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, "SEXP");
            ::quote::__private::push_comma(&mut _s);
            _s
        });
    ::quote::__private::push_rarrow(&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_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,
                "__miniextendr_deferred_mark");
            ::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, "deferred_condition");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "mark");
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                ::quote::__private::TokenStream::new());
            ::quote::__private::push_semi(&mut _s);
            ::quote::__private::push_ident(&mut _s, "let");
            ::quote::__private::push_ident(&mut _s, "__miniextendr_value");
            ::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, "unwind_protect");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "with_r_unwind_protect");
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::__private::push_or_or(&mut _s);
                    ::quote::__private::push_group(&mut _s,
                        ::quote::__private::Delimiter::Brace,
                        {
                            let mut _s = ::quote::__private::TokenStream::new();
                            ::quote::ToTokens::to_tokens(&setter_body, &mut _s);
                            _s
                        });
                    ::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_comma(&mut _s);
                    _s
                });
            ::quote::__private::push_semi(&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,
                        "deferred_condition");
                    ::quote::__private::push_colon2(&mut _s);
                    ::quote::__private::push_ident(&mut _s, "finish");
                    ::quote::__private::push_group(&mut _s,
                        ::quote::__private::Delimiter::Parenthesis,
                        {
                            let mut _s = ::quote::__private::TokenStream::new();
                            ::quote::__private::push_ident(&mut _s,
                                "__miniextendr_deferred_mark");
                            ::quote::__private::push_comma(&mut _s);
                            ::quote::__private::push_ident(&mut _s,
                                "__miniextendr_value");
                            ::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_comma(&mut _s);
                            _s
                        });
                    _s
                });
            _s
        });
    _s
}quote::quote! {
1999                #[doc = #setter_doc_lit]
2000                #[doc = #source_location_doc]
2001                #[doc = ::core::concat!("Generated from source file `", ::core::file!(), "`.")]
2002                #[doc(hidden)]
2003                #[unsafe(no_mangle)]
2004                #[allow(non_snake_case)]
2005                pub unsafe extern "C-unwind" fn #setter_fn_name(
2006                    x: ::miniextendr_api::SEXP,
2007                    value: ::miniextendr_api::SEXP,
2008                    #require_live: ::miniextendr_api::SEXP,
2009                ) -> ::miniextendr_api::SEXP {
2010                    let __miniextendr_deferred_mark = ::miniextendr_api::deferred_condition::mark();
2011                    let __miniextendr_value = ::miniextendr_api::unwind_protect::with_r_unwind_protect(
2012                        || { #setter_body },
2013                        ::core::option::Option::None,
2014                    );
2015                    unsafe {
2016                        ::miniextendr_api::deferred_condition::finish(
2017                            __miniextendr_deferred_mark,
2018                            __miniextendr_value,
2019                            ::core::option::Option::None,
2020                        )
2021                    }
2022                }
2023            });
2024
2025            let setter_def_ident = Ident::new(
2026                &::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("__MX_CALL_DEF_RDATA_SET_{0}_{1}",
                name_symbol, field_name_str))
    })format!("__MX_CALL_DEF_RDATA_SET_{}_{}", name_symbol, field_name_str),
2027                Span::call_site(),
2028            );
2029            let setter_registration =
2030                crate::call_method_def::call_method_def(&setter_def_ident, &setter_fn_name, 3);
2031            c_functions.push({
    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_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::__private::push_ident(&mut _s, "hidden");
                    _s
                });
            _s
        });
    ::quote::ToTokens::to_tokens(&setter_registration, &mut _s);
    _s
}quote::quote! {
2032                #[doc(hidden)]
2033                #setter_registration
2034            });
2035        }
2036
2037        // Generate R wrapper code based on class system
2038        let field_start = field_name.span().start();
2039        let via = if computed {
2040            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("`{0}`", getter_c_name))
    })format!("`{getter_c_name}`")
2041        } else {
2042            ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("`{0}` and `{1}`", getter_c_name,
                setter_c_name))
    })format!("`{getter_c_name}` and `{setter_c_name}`")
2043        };
2044        let comment = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("# Generated from Rust source line {0}:{1}\n# Wraps {2} `{3}` on Rust type `{4}` via {5}.\n",
                field_start.line, field_start.column + 1,
                if computed { "computed field" } else { "sidecar field" },
                r_name, name_str, via))
    })format!(
2045            "# Generated from Rust source line {}:{}\n# Wraps {} `{}` on Rust type `{}` via {}.\n",
2046            field_start.line,
2047            field_start.column + 1,
2048            if computed {
2049                "computed field"
2050            } else {
2051                "sidecar field"
2052            },
2053            r_name,
2054            name_str,
2055            via,
2056        );
2057        for (out, is_exported) in [(&mut exported, true), (&mut internal, false)] {
2058            out.push_str(&comment);
2059            out.push_str(&generate_r_wrapper_for_slot(
2060                info.class_system,
2061                &name_str,
2062                r_name,
2063                &getter_c_name,
2064                (!computed).then_some(setter_c_name.as_str()),
2065                slot.setter_invisible.unwrap_or(true),
2066                is_exported,
2067            ));
2068        }
2069    }
2070
2071    // The liveness check of `restored(refuse = "every_read")` (#1901): the
2072    // field methods ask it before a read that reaches no accessor. `raise`
2073    // refuses a pointer without its Rust value as the accessors do;
2074    // otherwise it answers whether the pointer has one.
2075    if restored.every_read {
2076        let live_c_name = crate::naming::sidecar_live_c_name(&name_str);
2077        let live_fn_name = Ident::new(&live_c_name, Span::call_site());
2078        let live_doc = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("Generated liveness check of the field methods of Rust type `{0}` (`restored(refuse = \"every_read\")`).",
                name_str))
    })format!(
2079            "Generated liveness check of the field methods of Rust type `{name_str}` \
2080             (`restored(refuse = \"every_read\")`)."
2081        );
2082        let live_def_ident = Ident::new(
2083            &::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("__MX_CALL_DEF_RDATA_LIVE_{0}",
                name_symbol))
    })format!("__MX_CALL_DEF_RDATA_LIVE_{name_symbol}"),
2084            Span::call_site(),
2085        );
2086        let live_registration =
2087            crate::call_method_def::call_method_def(&live_def_ident, &live_fn_name, 2);
2088        c_functions.push({
    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(&live_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_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::__private::push_ident(&mut _s, "hidden");
                    _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, "unsafe");
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::__private::push_ident(&mut _s, "no_mangle");
                    _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, "allow");
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::__private::push_ident(&mut _s, "non_snake_case");
                    _s
                });
            _s
        });
    ::quote::__private::push_ident(&mut _s, "pub");
    ::quote::__private::push_ident(&mut _s, "unsafe");
    ::quote::__private::push_ident(&mut _s, "extern");
    ::quote::__private::parse(&mut _s, "\"C-unwind\"");
    ::quote::__private::push_ident(&mut _s, "fn");
    ::quote::ToTokens::to_tokens(&live_fn_name, &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, "x");
            ::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, "SEXP");
            ::quote::__private::push_comma(&mut _s);
            ::quote::__private::push_ident(&mut _s, "raise");
            ::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, "SEXP");
            ::quote::__private::push_comma(&mut _s);
            _s
        });
    ::quote::__private::push_rarrow(&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_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,
                "__miniextendr_deferred_mark");
            ::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, "deferred_condition");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "mark");
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                ::quote::__private::TokenStream::new());
            ::quote::__private::push_semi(&mut _s);
            ::quote::__private::push_ident(&mut _s, "let");
            ::quote::__private::push_ident(&mut _s, "__miniextendr_value");
            ::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, "unwind_protect");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "with_r_unwind_protect");
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::__private::push_or_or(&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, "use");
                            ::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_semi(&mut _s);
                            ::quote::__private::push_ident(&mut _s, "let");
                            ::quote::__private::push_ident(&mut _s, "raise");
                            ::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,
                                "sidecar_require_live");
                            ::quote::__private::push_group(&mut _s,
                                ::quote::__private::Delimiter::Parenthesis,
                                {
                                    let mut _s = ::quote::__private::TokenStream::new();
                                    ::quote::__private::push_ident(&mut _s, "raise");
                                    _s
                                });
                            ::quote::__private::push_semi(&mut _s);
                            ::quote::__private::push_ident(&mut _s, "SEXP");
                            ::quote::__private::push_colon2(&mut _s);
                            ::quote::__private::push_ident(&mut _s, "scalar_logical");
                            ::quote::__private::push_group(&mut _s,
                                ::quote::__private::Delimiter::Parenthesis,
                                {
                                    let mut _s = ::quote::__private::TokenStream::new();
                                    ::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, "sidecar_r_live");
                                            ::quote::__private::push_colon2(&mut _s);
                                            ::quote::__private::push_lt(&mut _s);
                                            ::quote::ToTokens::to_tokens(&name, &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, "x");
                                                    ::quote::__private::push_comma(&mut _s);
                                                    ::quote::__private::push_ident(&mut _s, "raise");
                                                    _s
                                                });
                                            _s
                                        });
                                    _s
                                });
                            _s
                        });
                    ::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_comma(&mut _s);
                    _s
                });
            ::quote::__private::push_semi(&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,
                        "deferred_condition");
                    ::quote::__private::push_colon2(&mut _s);
                    ::quote::__private::push_ident(&mut _s, "finish");
                    ::quote::__private::push_group(&mut _s,
                        ::quote::__private::Delimiter::Parenthesis,
                        {
                            let mut _s = ::quote::__private::TokenStream::new();
                            ::quote::__private::push_ident(&mut _s,
                                "__miniextendr_deferred_mark");
                            ::quote::__private::push_comma(&mut _s);
                            ::quote::__private::push_ident(&mut _s,
                                "__miniextendr_value");
                            ::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_comma(&mut _s);
                            _s
                        });
                    _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_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::__private::push_ident(&mut _s, "hidden");
                    _s
                });
            _s
        });
    ::quote::ToTokens::to_tokens(&live_registration, &mut _s);
    _s
}quote::quote! {
2089            #[doc = #live_doc]
2090            #[doc(hidden)]
2091            #[unsafe(no_mangle)]
2092            #[allow(non_snake_case)]
2093            pub unsafe extern "C-unwind" fn #live_fn_name(
2094                x: ::miniextendr_api::SEXP,
2095                raise: ::miniextendr_api::SEXP,
2096            ) -> ::miniextendr_api::SEXP {
2097                let __miniextendr_deferred_mark = ::miniextendr_api::deferred_condition::mark();
2098                let __miniextendr_value = ::miniextendr_api::unwind_protect::with_r_unwind_protect(
2099                    || {
2100                        use ::miniextendr_api::SEXP;
2101                        let raise = ::miniextendr_api::externalptr::sidecar_require_live(raise);
2102                        SEXP::scalar_logical(unsafe {
2103                            ::miniextendr_api::externalptr::sidecar_r_live::<#name>(x, raise)
2104                        })
2105                    },
2106                    ::core::option::Option::None,
2107                );
2108                unsafe {
2109                    ::miniextendr_api::deferred_condition::finish(
2110                        __miniextendr_deferred_mark,
2111                        __miniextendr_value,
2112                        ::core::option::Option::None,
2113                    )
2114                }
2115            }
2116
2117            #[doc(hidden)]
2118            #live_registration
2119        });
2120    }
2121
2122    // R6 / S7 integration is independent of S3 / S4 / env field syntax.
2123    let r_wrappers = generate_class_integration_r_code(info.class_system, &name_str, &pub_slots);
2124    // The writer selects this rendering only when an impl enables field
2125    // syntax. Other types need their standalone accessors, not these helpers.
2126    internal.push_str(&generate_field_syntax_helpers(
2127        &name_str,
2128        &pub_slots,
2129        &field_error.class,
2130        field_error.no_field_hint.as_deref(),
2131        restored.every_read,
2132    ));
2133
2134    let const_name_wrappers = Ident::new(
2135        &::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("R_WRAPPERS_RDATA_{0}",
                name_symbol))
    })format!("R_WRAPPERS_RDATA_{}", name_symbol),
2136        Span::call_site(),
2137    );
2138    let const_name_accessors = Ident::new(
2139        &::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("R_SIDECAR_ACCESSORS_{0}",
                name_symbol))
    })format!("R_SIDECAR_ACCESSORS_{}", name_symbol),
2140        Span::call_site(),
2141    );
2142    let source_location_doc = crate::source_location_doc(name.span());
2143    let source_line_lit = syn::LitInt::new(&name.span().start().line.to_string(), name.span());
2144
2145    // For S7 class systems, emit MX_S7_SIDECAR_PROPS entries so the S7 codegen
2146    // can substitute @prop lines for sidecar properties at write time.
2147    let sidecar_prop_entries = if info.class_system == ClassSystem::S7 {
2148        let entries: Vec<_> = pub_slots
2149            .iter()
2150            .map(|slot| {
2151                // The property's R name; `registry.rs` derives the
2152                // `<Class>_get_<name>` collision names from it too.
2153                let field_str = slot.r_name.as_str();
2154                let doc_str = slot
2155                    .prop_doc
2156                    .as_deref()
2157                    .unwrap_or("(undocumented sidecar property)");
2158                // The static is named by the Rust identifier: an R name may
2159                // hold a `.`.
2160                let entry_ident = Ident::new(
2161                    &::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("__MX_S7_SIDECAR_PROP_{0}_{1}",
                name_symbol, crate::naming::ident_name(&slot.name)))
    })format!(
2162                        "__MX_S7_SIDECAR_PROP_{}_{}",
2163                        name_symbol,
2164                        crate::naming::ident_name(&slot.name)
2165                    ),
2166                    Span::call_site(),
2167                );
2168                {
    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_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::__private::push_ident(&mut _s, "hidden");
                    _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, "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_S7_SIDECAR_PROPS");
                            _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_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,
                        "non_upper_case_globals");
                    _s
                });
            _s
        });
    ::quote::__private::push_ident(&mut _s, "static");
    ::quote::ToTokens::to_tokens(&entry_ident, &mut _s);
    ::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, "SidecarPropEntry");
    ::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, "SidecarPropEntry");
    ::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::ToTokens::to_tokens(&name_str, &mut _s);
            ::quote::__private::push_comma(&mut _s);
            ::quote::__private::push_ident(&mut _s, "field_name");
            ::quote::__private::push_colon(&mut _s);
            ::quote::ToTokens::to_tokens(&field_str, &mut _s);
            ::quote::__private::push_comma(&mut _s);
            ::quote::__private::push_ident(&mut _s, "prop_doc");
            ::quote::__private::push_colon(&mut _s);
            ::quote::ToTokens::to_tokens(&doc_str, &mut _s);
            ::quote::__private::push_comma(&mut _s);
            _s
        });
    ::quote::__private::push_semi(&mut _s);
    _s
}quote::quote! {
2169                    #[doc(hidden)]
2170                    #[cfg_attr(not(target_arch = "wasm32"), ::miniextendr_api::linkme::distributed_slice(::miniextendr_api::registry::MX_S7_SIDECAR_PROPS), linkme(crate = ::miniextendr_api::linkme))]
2171                    #[allow(non_upper_case_globals)]
2172                    static #entry_ident: ::miniextendr_api::registry::SidecarPropEntry =
2173                        ::miniextendr_api::registry::SidecarPropEntry {
2174                            rust_type: #name_str,
2175                            field_name: #field_str,
2176                            prop_doc: #doc_str,
2177                        };
2178                }
2179            })
2180            .collect();
2181        {
    let mut _s = ::quote::__private::TokenStream::new();
    {
        use ::quote::__private::ext::*;
        let has_iter = ::quote::__private::HasIterator::<false>;
        #[allow(unused_mut)]
        let (mut entries, i) = entries.quote_into_iter();
        let has_iter = has_iter | i;
        <_ as ::quote::__private::CheckHasIterator<true>>::check(has_iter);
        while true {
            let entries =
                match entries.next() {
                    Some(_x) => ::quote::__private::RepInterp(_x),
                    None => break,
                };
            ::quote::ToTokens::to_tokens(&entries, &mut _s);
        }
    }
    _s
}quote::quote! { #(#entries)* }
2182    } else {
2183        ::quote::__private::TokenStream::new();quote::quote! {}
2184    };
2185
2186    let class_integration_wrappers = (!r_wrappers.is_empty()).then(|| {
    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::__private::parse(&mut _s,
                "r\" Sidecar accessor R wrapper code via distributed slice.\"");
            _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::ToTokens::to_tokens(&source_location_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::__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_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::__private::push_ident(&mut _s, "hidden");
                    _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, "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_R_WRAPPERS");
                            _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_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,
                        "non_upper_case_globals");
                    _s
                });
            _s
        });
    ::quote::__private::push_ident(&mut _s, "static");
    ::quote::ToTokens::to_tokens(&const_name_wrappers, &mut _s);
    ::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, "RWrapperEntry");
    ::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, "RWrapperEntry");
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Brace,
        {
            let mut _s = ::quote::__private::TokenStream::new();
            ::quote::__private::push_ident(&mut _s, "priority");
            ::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, "RWrapperPriority");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "Sidecar");
            ::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, "content");
            ::quote::__private::push_colon(&mut _s);
            ::quote::ToTokens::to_tokens(&r_wrappers, &mut _s);
            ::quote::__private::push_comma(&mut _s);
            _s
        });
    ::quote::__private::push_semi(&mut _s);
    _s
}quote::quote! {
2187        /// Sidecar accessor R wrapper code via distributed slice.
2188        #[doc = #source_location_doc]
2189        #[doc = ::core::concat!("Generated from source file `", ::core::file!(), "`.")]
2190        #[doc(hidden)]
2191        #[cfg_attr(not(target_arch = "wasm32"), ::miniextendr_api::linkme::distributed_slice(::miniextendr_api::registry::MX_R_WRAPPERS), linkme(crate = ::miniextendr_api::linkme))]
2192        #[allow(non_upper_case_globals)]
2193        static #const_name_wrappers: ::miniextendr_api::registry::RWrapperEntry =
2194            ::miniextendr_api::registry::RWrapperEntry {
2195                priority: ::miniextendr_api::registry::RWrapperPriority::Sidecar,
2196                source_file: ::core::file!(),
2197                source_line: #source_line_lit,
2198                content: #r_wrappers,
2199            };
2200    });
2201
2202    Ok({
    let mut _s = ::quote::__private::TokenStream::new();
    {
        use ::quote::__private::ext::*;
        let has_iter = ::quote::__private::HasIterator::<false>;
        #[allow(unused_mut)]
        let (mut c_functions, i) = c_functions.quote_into_iter();
        let has_iter = has_iter | i;
        <_ as ::quote::__private::CheckHasIterator<true>>::check(has_iter);
        while true {
            let c_functions =
                match c_functions.next() {
                    Some(_x) => ::quote::__private::RepInterp(_x),
                    None => break,
                };
            ::quote::ToTokens::to_tokens(&c_functions, &mut _s);
        }
    }
    ::quote::ToTokens::to_tokens(&class_integration_wrappers, &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, "doc");
            ::quote::__private::push_eq(&mut _s);
            ::quote::__private::parse(&mut _s,
                "r\" The standalone sidecar accessors, exported and internal, via\"");
            _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::parse(&mut _s, "r\" distributed slice.\"");
            _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::ToTokens::to_tokens(&source_location_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_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::__private::push_ident(&mut _s, "hidden");
                    _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, "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_SIDECAR_ACCESSORS");
                            _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_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,
                        "non_upper_case_globals");
                    _s
                });
            _s
        });
    ::quote::__private::push_ident(&mut _s, "static");
    ::quote::ToTokens::to_tokens(&const_name_accessors, &mut _s);
    ::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, "SidecarAccessorsEntry");
    ::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, "SidecarAccessorsEntry");
    ::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::ToTokens::to_tokens(&name_str, &mut _s);
            ::quote::__private::push_comma(&mut _s);
            ::quote::__private::push_ident(&mut _s, "exported");
            ::quote::__private::push_colon(&mut _s);
            ::quote::ToTokens::to_tokens(&exported, &mut _s);
            ::quote::__private::push_comma(&mut _s);
            ::quote::__private::push_ident(&mut _s, "internal");
            ::quote::__private::push_colon(&mut _s);
            ::quote::ToTokens::to_tokens(&internal, &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);
            _s
        });
    ::quote::__private::push_semi(&mut _s);
    ::quote::ToTokens::to_tokens(&sidecar_prop_entries, &mut _s);
    _s
}quote::quote! {
2203        #(#c_functions)*
2204
2205        #class_integration_wrappers
2206
2207        /// The standalone sidecar accessors, exported and internal, via
2208        /// distributed slice.
2209        #[doc = #source_location_doc]
2210        #[doc(hidden)]
2211        #[cfg_attr(not(target_arch = "wasm32"), ::miniextendr_api::linkme::distributed_slice(::miniextendr_api::registry::MX_SIDECAR_ACCESSORS), linkme(crate = ::miniextendr_api::linkme))]
2212        #[allow(non_upper_case_globals)]
2213        static #const_name_accessors: ::miniextendr_api::registry::SidecarAccessorsEntry =
2214            ::miniextendr_api::registry::SidecarAccessorsEntry {
2215                rust_type: #name_str,
2216                exported: #exported,
2217                internal: #internal,
2218                source_file: ::core::file!(),
2219                source_line: #source_line_lit,
2220            };
2221
2222        #sidecar_prop_entries
2223    })
2224}
2225
2226/// The `Sidecar<T>` slots of a type, in protection-list order.
2227fn sidecar_slots(info: &SidecarInfo) -> impl Iterator<Item = (usize, &SidecarSlot)> {
2228    info.slots.iter().filter_map(|slot| match slot.kind {
2229        SlotKind::Sidecar(index) => Some((index, slot)),
2230        _ => None,
2231    })
2232}
2233
2234/// Generate the `TypedExternal` trait implementation for the derive target.
2235///
2236/// Produces the associated constants:
2237/// - `TYPE_NAME`: the struct name as a `&'static str`
2238/// - `TYPE_NAME_CSTR`: null-terminated byte string of the struct name
2239/// - `TYPE_ID_CSTR`: globally unique ID in the format
2240///   `"<crate_name>@<crate_version>::<module_path>::<type_name>\0"`,
2241///   using `CARGO_PKG_NAME`, `CARGO_PKG_VERSION`, and `module_path!()`.
2242/// - `R_SLOT_COUNT`: the number of `Sidecar<T>` fields, when there are any,
2243///   with the `__mx_visit_sidecars` hook that hands each of them, with its
2244///   slot index and name, to the handle (which flushes their pending values
2245///   into the slots and loads the slots back into them).
2246///
2247/// Supports generic structs (generics are forwarded to the impl).
2248fn generate_typed_external(
2249    input: &DeriveInput,
2250    info: &SidecarInfo,
2251    restored: &RestoredOptions,
2252    field_error: &FieldErrorOptions,
2253    rebuild: Option<&syn::Path>,
2254    any_version: bool,
2255    display_name: Option<&str>,
2256) -> TokenStream {
2257    let name = &input.ident;
2258    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
2259
2260    let name_str = name.to_string();
2261    let name_lit = syn::LitStr::new(&name_str, name.span());
2262    let display_name = display_name.unwrap_or(&name_str);
2263    let display_lit = syn::LitStr::new(display_name, name.span());
2264    let name_cstr = syn::LitByteStr::new(::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("{0}\0", display_name))
    })format!("{display_name}\0").as_bytes(), name.span());
2265    // `#[externalptr(restored(class = ..., message = ...))]` (#1891): the
2266    // package's classes go in front of the `miniextendr_restored*` classes,
2267    // its message replaces miniextendr's, on every restored refusal of the
2268    // type (`refuse_restored`, `TypeMismatchError`).
2269    let restored_class = (!restored.class.is_empty()).then(|| {
2270        let classes = &restored.class;
2271        {
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::__private::push_ident(&mut _s, "const");
    ::quote::__private::push_ident(&mut _s, "RESTORED_ERROR_CLASS");
    ::quote::__private::push_colon(&mut _s);
    ::quote::__private::push_and(&mut _s);
    ::quote::__private::push_lifetime(&mut _s, "\'static");
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Bracket,
        {
            let mut _s = ::quote::__private::TokenStream::new();
            ::quote::__private::push_and(&mut _s);
            ::quote::__private::push_lifetime(&mut _s, "\'static");
            ::quote::__private::push_ident(&mut _s, "str");
            _s
        });
    ::quote::__private::push_eq(&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 classes, i) = classes.quote_into_iter();
                let has_iter = has_iter | i;
                <_ as
                        ::quote::__private::CheckHasIterator<true>>::check(has_iter);
                while true {
                    let classes =
                        match classes.next() {
                            Some(_x) => ::quote::__private::RepInterp(_x),
                            None => break,
                        };
                    if !_first { ::quote::__private::push_comma(&mut _s); }
                    _first = false;
                    ::quote::ToTokens::to_tokens(&classes, &mut _s);
                }
            }
            _s
        });
    ::quote::__private::push_semi(&mut _s);
    _s
}quote::quote! {
2272            const RESTORED_ERROR_CLASS: &'static [&'static str] = &[#(#classes),*];
2273        }
2274    });
2275    let restored_message = restored.message.as_ref().map(|message| {
2276        {
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::__private::push_ident(&mut _s, "const");
    ::quote::__private::push_ident(&mut _s, "RESTORED_ERROR_MESSAGE");
    ::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, "option");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "Option");
    ::quote::__private::push_lt(&mut _s);
    ::quote::__private::push_and(&mut _s);
    ::quote::__private::push_lifetime(&mut _s, "\'static");
    ::quote::__private::push_ident(&mut _s, "str");
    ::quote::__private::push_gt(&mut _s);
    ::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, "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::ToTokens::to_tokens(&message, &mut _s);
            _s
        });
    ::quote::__private::push_semi(&mut _s);
    _s
}quote::quote! {
2277            const RESTORED_ERROR_MESSAGE: ::core::option::Option<&'static str> =
2278                ::core::option::Option::Some(#message);
2279        }
2280    });
2281    // `#[externalptr(field_error(class = ..., message = ...))]` (#1899,
2282    // #1920): the classes in front of the R accessors' type-mismatch and
2283    // no-pointer errors, and the package's message for both.
2284    let field_error_class = (!field_error.class.is_empty()).then(|| {
2285        let classes = &field_error.class;
2286        {
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::__private::push_ident(&mut _s, "const");
    ::quote::__private::push_ident(&mut _s, "FIELD_ERROR_CLASS");
    ::quote::__private::push_colon(&mut _s);
    ::quote::__private::push_and(&mut _s);
    ::quote::__private::push_lifetime(&mut _s, "\'static");
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Bracket,
        {
            let mut _s = ::quote::__private::TokenStream::new();
            ::quote::__private::push_and(&mut _s);
            ::quote::__private::push_lifetime(&mut _s, "\'static");
            ::quote::__private::push_ident(&mut _s, "str");
            _s
        });
    ::quote::__private::push_eq(&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 classes, i) = classes.quote_into_iter();
                let has_iter = has_iter | i;
                <_ as
                        ::quote::__private::CheckHasIterator<true>>::check(has_iter);
                while true {
                    let classes =
                        match classes.next() {
                            Some(_x) => ::quote::__private::RepInterp(_x),
                            None => break,
                        };
                    if !_first { ::quote::__private::push_comma(&mut _s); }
                    _first = false;
                    ::quote::ToTokens::to_tokens(&classes, &mut _s);
                }
            }
            _s
        });
    ::quote::__private::push_semi(&mut _s);
    _s
}quote::quote! {
2287            const FIELD_ERROR_CLASS: &'static [&'static str] = &[#(#classes),*];
2288        }
2289    });
2290    let field_error_message = field_error.message.as_ref().map(|message| {
2291        {
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::__private::push_ident(&mut _s, "const");
    ::quote::__private::push_ident(&mut _s, "FIELD_ERROR_MESSAGE");
    ::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, "option");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "Option");
    ::quote::__private::push_lt(&mut _s);
    ::quote::__private::push_and(&mut _s);
    ::quote::__private::push_lifetime(&mut _s, "\'static");
    ::quote::__private::push_ident(&mut _s, "str");
    ::quote::__private::push_gt(&mut _s);
    ::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, "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::ToTokens::to_tokens(&message, &mut _s);
            _s
        });
    ::quote::__private::push_semi(&mut _s);
    _s
}quote::quote! {
2292            const FIELD_ERROR_MESSAGE: ::core::option::Option<&'static str> =
2293                ::core::option::Option::Some(#message);
2294        }
2295    });
2296    // `#[externalptr(rebuild = path)]` (#2146): a restored pointer is
2297    // rebuilt by `path` on first use. Its `Err` becomes the condition the
2298    // refusal carries as `e$parent`, through the probe a `Result` return
2299    // uses: an `RConditionData` error keeps its classes and fields.
2300    // `any_version` (#2187) lets it rebuild a save by another version too,
2301    // which the saved slot names make safe to read.
2302    let rebuild = rebuild.map(|path| {
2303        let prefix = crate::miniextendr_fn::default_serde_error_prefix();
2304        let marker = crate::miniextendr_fn::init_marker_path();
2305        let call = {
    let mut _s = ::quote::__private::TokenStream::new();
    let _span: ::quote::__private::Span =
        ::quote::__private::get_span(syn::spanned::Spanned::span(path)).__into_span();
    ::quote::ToTokens::to_tokens(&path, &mut _s);
    ::quote::__private::push_group_spanned(&mut _s, _span,
        ::quote::__private::Delimiter::Parenthesis,
        {
            let mut _s = ::quote::__private::TokenStream::new();
            let _span: ::quote::__private::Span =
                ::quote::__private::get_span(_span).__into_span();
            ::quote::__private::push_ident_spanned(&mut _s, _span, "saved");
            _s
        });
    _s
}quote::quote_spanned! {syn::spanned::Spanned::span(path)=> #path(saved) };
2306        let any_version = any_version.then(|| {
2307            {
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::__private::push_ident(&mut _s, "const");
    ::quote::__private::push_ident(&mut _s, "REBUILDS_ANY_VERSION");
    ::quote::__private::push_colon(&mut _s);
    ::quote::__private::push_ident(&mut _s, "bool");
    ::quote::__private::push_eq(&mut _s);
    ::quote::__private::push_ident(&mut _s, "true");
    ::quote::__private::push_semi(&mut _s);
    _s
}quote::quote! { const REBUILDS_ANY_VERSION: bool = true; }
2308        });
2309        {
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::__private::push_ident(&mut _s, "const");
    ::quote::__private::push_ident(&mut _s, "REBUILDS");
    ::quote::__private::push_colon(&mut _s);
    ::quote::__private::push_ident(&mut _s, "bool");
    ::quote::__private::push_eq(&mut _s);
    ::quote::__private::push_ident(&mut _s, "true");
    ::quote::__private::push_semi(&mut _s);
    ::quote::ToTokens::to_tokens(&any_version, &mut _s);
    ::quote::__private::push_ident(&mut _s, "fn");
    ::quote::__private::push_ident(&mut _s, "__mx_rebuild");
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Parenthesis,
        {
            let mut _s = ::quote::__private::TokenStream::new();
            ::quote::__private::push_ident(&mut _s, "saved");
            ::quote::__private::push_colon(&mut _s);
            ::quote::__private::push_and(&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, "Saved");
            ::quote::__private::push_lt(&mut _s);
            ::quote::__private::push_lifetime(&mut _s, "\'_");
            ::quote::__private::push_comma(&mut _s);
            ::quote::__private::push_ident(&mut _s, "Self");
            ::quote::__private::push_gt(&mut _s);
            ::quote::__private::push_comma(&mut _s);
            _s
        });
    ::quote::__private::push_rarrow(&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, "result");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "Result");
    ::quote::__private::push_lt(&mut _s);
    ::quote::__private::push_ident(&mut _s, "Self");
    ::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, "condition");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "RCondition");
    ::quote::__private::push_gt(&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, "match");
            ::quote::ToTokens::to_tokens(&call, &mut _s);
            ::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, "result");
                    ::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, "Ok");
                    ::quote::__private::push_group(&mut _s,
                        ::quote::__private::Delimiter::Parenthesis,
                        {
                            let mut _s = ::quote::__private::TokenStream::new();
                            ::quote::__private::push_ident(&mut _s, "value");
                            _s
                        });
                    ::quote::__private::push_fat_arrow(&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, "result");
                    ::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, "Ok");
                    ::quote::__private::push_group(&mut _s,
                        ::quote::__private::Delimiter::Parenthesis,
                        {
                            let mut _s = ::quote::__private::TokenStream::new();
                            ::quote::__private::push_ident(&mut _s, "value");
                            _s
                        });
                    ::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, "result");
                    ::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, "Err");
                    ::quote::__private::push_group(&mut _s,
                        ::quote::__private::Delimiter::Parenthesis,
                        {
                            let mut _s = ::quote::__private::TokenStream::new();
                            ::quote::__private::push_ident(&mut _s, "e");
                            _s
                        });
                    ::quote::__private::push_fat_arrow(&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, "let");
                            ::quote::__private::push_ident(&mut _s, "parts");
                            ::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,
                                "__mx_result_err_parts");
                            ::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::push_ident(&mut _s, "e");
                                    ::quote::__private::push_comma(&mut _s);
                                    ::quote::ToTokens::to_tokens(&prefix, &mut _s);
                                    ::quote::__private::push_comma(&mut _s);
                                    ::quote::ToTokens::to_tokens(&marker, &mut _s);
                                    _s
                                });
                            ::quote::__private::push_semi(&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, "result");
                            ::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, "Err");
                            ::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, "condition");
                                    ::quote::__private::push_colon2(&mut _s);
                                    ::quote::__private::push_ident(&mut _s, "RCondition");
                                    ::quote::__private::push_colon2(&mut _s);
                                    ::quote::__private::push_ident(&mut _s, "Error");
                                    ::quote::__private::push_group(&mut _s,
                                        ::quote::__private::Delimiter::Brace,
                                        {
                                            let mut _s = ::quote::__private::TokenStream::new();
                                            ::quote::__private::push_ident(&mut _s, "message");
                                            ::quote::__private::push_colon(&mut _s);
                                            ::quote::__private::push_ident(&mut _s, "parts");
                                            ::quote::__private::push_dot(&mut _s);
                                            ::quote::__private::push_ident(&mut _s, "message");
                                            ::quote::__private::push_comma(&mut _s);
                                            ::quote::__private::push_ident(&mut _s, "class");
                                            ::quote::__private::push_colon(&mut _s);
                                            ::quote::__private::push_ident(&mut _s, "parts");
                                            ::quote::__private::push_dot(&mut _s);
                                            ::quote::__private::push_ident(&mut _s, "class");
                                            ::quote::__private::push_comma(&mut _s);
                                            ::quote::__private::push_ident(&mut _s, "data");
                                            ::quote::__private::push_colon(&mut _s);
                                            ::quote::__private::push_ident(&mut _s, "parts");
                                            ::quote::__private::push_dot(&mut _s);
                                            ::quote::__private::push_ident(&mut _s, "data");
                                            ::quote::__private::push_comma(&mut _s);
                                            ::quote::__private::push_ident(&mut _s, "call");
                                            ::quote::__private::push_colon(&mut _s);
                                            ::quote::__private::push_ident(&mut _s, "parts");
                                            ::quote::__private::push_dot(&mut _s);
                                            ::quote::__private::push_ident(&mut _s, "call");
                                            ::quote::__private::push_comma(&mut _s);
                                            _s
                                        });
                                    _s
                                });
                            _s
                        });
                    _s
                });
            _s
        });
    _s
}quote::quote! {
2310            const REBUILDS: bool = true;
2311            #any_version
2312
2313            fn __mx_rebuild(
2314                saved: &::miniextendr_api::externalptr::Saved<'_, Self>,
2315            ) -> ::core::result::Result<Self, ::miniextendr_api::condition::RCondition> {
2316                match #call {
2317                    ::core::result::Result::Ok(value) => ::core::result::Result::Ok(value),
2318                    ::core::result::Result::Err(e) => {
2319                        let parts = ::miniextendr_api::__mx_result_err_parts!(e, #prefix, #marker);
2320                        ::core::result::Result::Err(::miniextendr_api::condition::RCondition::Error {
2321                            message: parts.message,
2322                            class: parts.class,
2323                            data: parts.data,
2324                            call: parts.call,
2325                        })
2326                    }
2327                }
2328            }
2329        }
2330    });
2331    let sidecars: Vec<_> = sidecar_slots(info).collect();
2332    // A save names its slots by the fields' R names (#2215), so renaming a
2333    // Rust field keeps every earlier save readable.
2334    let r_slot_count = (!sidecars.is_empty()).then(|| {
2335        let count = sidecars.len();
2336        let names = sidecars.iter().map(|&(_, slot)| slot.r_name.as_str());
2337        let visits = sidecars.iter().map(|&(index, slot)| {
2338            let field = &slot.name;
2339            let r_name = slot.r_name.as_str();
2340            {
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::__private::push_ident(&mut _s, "visit");
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Parenthesis,
        {
            let mut _s = ::quote::__private::TokenStream::new();
            ::quote::ToTokens::to_tokens(&index, &mut _s);
            ::quote::__private::push_comma(&mut _s);
            ::quote::ToTokens::to_tokens(&r_name, &mut _s);
            ::quote::__private::push_comma(&mut _s);
            ::quote::__private::push_and(&mut _s);
            ::quote::__private::push_ident(&mut _s, "self");
            ::quote::__private::push_dot(&mut _s);
            ::quote::ToTokens::to_tokens(&field, &mut _s);
            _s
        });
    ::quote::__private::push_semi(&mut _s);
    _s
}quote::quote! { visit(#index, #r_name, &self.#field); }
2341        });
2342        {
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::__private::push_ident(&mut _s, "const");
    ::quote::__private::push_ident(&mut _s, "R_SLOT_COUNT");
    ::quote::__private::push_colon(&mut _s);
    ::quote::__private::push_ident(&mut _s, "usize");
    ::quote::__private::push_eq(&mut _s);
    ::quote::ToTokens::to_tokens(&count, &mut _s);
    ::quote::__private::push_semi(&mut _s);
    ::quote::__private::push_ident(&mut _s, "const");
    ::quote::__private::push_ident(&mut _s, "SIDECAR_NAMES");
    ::quote::__private::push_colon(&mut _s);
    ::quote::__private::push_and(&mut _s);
    ::quote::__private::push_lifetime(&mut _s, "\'static");
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Bracket,
        {
            let mut _s = ::quote::__private::TokenStream::new();
            ::quote::__private::push_and(&mut _s);
            ::quote::__private::push_lifetime(&mut _s, "\'static");
            ::quote::__private::push_ident(&mut _s, "str");
            _s
        });
    ::quote::__private::push_eq(&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 names, i) = names.quote_into_iter();
                let has_iter = has_iter | i;
                <_ as
                        ::quote::__private::CheckHasIterator<true>>::check(has_iter);
                while true {
                    let names =
                        match names.next() {
                            Some(_x) => ::quote::__private::RepInterp(_x),
                            None => break,
                        };
                    if !_first { ::quote::__private::push_comma(&mut _s); }
                    _first = false;
                    ::quote::ToTokens::to_tokens(&names, &mut _s);
                }
            }
            _s
        });
    ::quote::__private::push_semi(&mut _s);
    ::quote::__private::push_ident(&mut _s, "fn");
    ::quote::__private::push_ident(&mut _s, "__mx_visit_sidecars");
    ::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");
            ::quote::__private::push_comma(&mut _s);
            ::quote::__private::push_ident(&mut _s, "visit");
            ::quote::__private::push_colon(&mut _s);
            ::quote::__private::push_and(&mut _s);
            ::quote::__private::push_ident(&mut _s, "mut");
            ::quote::__private::push_ident(&mut _s, "dyn");
            ::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, "ops");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "FnMut");
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::__private::push_ident(&mut _s, "usize");
                    ::quote::__private::push_comma(&mut _s);
                    ::quote::__private::push_and(&mut _s);
                    ::quote::__private::push_lifetime(&mut _s, "\'static");
                    ::quote::__private::push_ident(&mut _s, "str");
                    ::quote::__private::push_comma(&mut _s);
                    ::quote::__private::push_and(&mut _s);
                    ::quote::__private::push_ident(&mut _s, "dyn");
                    ::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, "SidecarField");
                    _s
                });
            ::quote::__private::push_comma(&mut _s);
            _s
        });
    ::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 visits, i) = visits.quote_into_iter();
                let has_iter = has_iter | i;
                <_ as
                        ::quote::__private::CheckHasIterator<true>>::check(has_iter);
                while true {
                    let visits =
                        match visits.next() {
                            Some(_x) => ::quote::__private::RepInterp(_x),
                            None => break,
                        };
                    ::quote::ToTokens::to_tokens(&visits, &mut _s);
                }
            }
            _s
        });
    _s
}quote::quote! {
2343            const R_SLOT_COUNT: usize = #count;
2344            const SIDECAR_NAMES: &'static [&'static str] = &[#(#names),*];
2345
2346            fn __mx_visit_sidecars(
2347                &self,
2348                visit: &mut dyn ::core::ops::FnMut(usize, &'static str, &dyn ::miniextendr_api::externalptr::SidecarField),
2349            ) {
2350                #(#visits)*
2351            }
2352        }
2353    });
2354
2355    // TYPE_ID_CSTR format: "<crate_name>@<crate_version>::<module_path>::<type_name>\0"
2356    //
2357    // Uses env!("CARGO_PKG_NAME") and env!("CARGO_PKG_VERSION") for the crate identifier,
2358    // ensuring two packages with the same type name from the same crate+version are compatible,
2359    // while different crate versions are considered distinct types.
2360    //
2361    // The module_path!() may include "crate::" prefix when compiled within the crate,
2362    // but combined with the explicit crate@version prefix, this is unambiguous.
2363    {
    let mut _s = ::quote::__private::TokenStream::new();
    ::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, "externalptr");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "TypedExternal");
    ::quote::__private::push_ident(&mut _s, "for");
    ::quote::ToTokens::to_tokens(&name, &mut _s);
    ::quote::ToTokens::to_tokens(&ty_generics, &mut _s);
    ::quote::ToTokens::to_tokens(&where_clause, &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, "const");
            ::quote::__private::push_ident(&mut _s, "TYPE_NAME");
            ::quote::__private::push_colon(&mut _s);
            ::quote::__private::push_and(&mut _s);
            ::quote::__private::push_lifetime(&mut _s, "\'static");
            ::quote::__private::push_ident(&mut _s, "str");
            ::quote::__private::push_eq(&mut _s);
            ::quote::ToTokens::to_tokens(&display_lit, &mut _s);
            ::quote::__private::push_semi(&mut _s);
            ::quote::__private::push_ident(&mut _s, "const");
            ::quote::__private::push_ident(&mut _s, "TYPE_NAME_CSTR");
            ::quote::__private::push_colon(&mut _s);
            ::quote::__private::push_and(&mut _s);
            ::quote::__private::push_lifetime(&mut _s, "\'static");
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Bracket,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::__private::push_ident(&mut _s, "u8");
                    _s
                });
            ::quote::__private::push_eq(&mut _s);
            ::quote::ToTokens::to_tokens(&name_cstr, &mut _s);
            ::quote::__private::push_semi(&mut _s);
            ::quote::__private::push_ident(&mut _s, "const");
            ::quote::__private::push_ident(&mut _s, "TYPE_ID_CSTR");
            ::quote::__private::push_colon(&mut _s);
            ::quote::__private::push_and(&mut _s);
            ::quote::__private::push_lifetime(&mut _s, "\'static");
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Bracket,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::__private::push_ident(&mut _s, "u8");
                    _s
                });
            ::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::push_colon2(&mut _s);
                    ::quote::__private::push_ident(&mut _s, "core");
                    ::quote::__private::push_colon2(&mut _s);
                    ::quote::__private::push_ident(&mut _s, "env");
                    ::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, "\"CARGO_PKG_NAME\"");
                            _s
                        });
                    ::quote::__private::push_comma(&mut _s);
                    ::quote::__private::parse(&mut _s, "\"@\"");
                    ::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, "env");
                    ::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, "\"CARGO_PKG_VERSION\"");
                            _s
                        });
                    ::quote::__private::push_comma(&mut _s);
                    ::quote::__private::parse(&mut _s, "\"::\"");
                    ::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, "module_path");
                    ::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, "\"::\"");
                    ::quote::__private::push_comma(&mut _s);
                    ::quote::ToTokens::to_tokens(&name_lit, &mut _s);
                    ::quote::__private::push_comma(&mut _s);
                    ::quote::__private::parse(&mut _s, "\"\\0\"");
                    _s
                });
            ::quote::__private::push_dot(&mut _s);
            ::quote::__private::push_ident(&mut _s, "as_bytes");
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                ::quote::__private::TokenStream::new());
            ::quote::__private::push_semi(&mut _s);
            ::quote::ToTokens::to_tokens(&r_slot_count, &mut _s);
            ::quote::ToTokens::to_tokens(&restored_class, &mut _s);
            ::quote::ToTokens::to_tokens(&restored_message, &mut _s);
            ::quote::ToTokens::to_tokens(&field_error_class, &mut _s);
            ::quote::ToTokens::to_tokens(&field_error_message, &mut _s);
            ::quote::ToTokens::to_tokens(&rebuild, &mut _s);
            _s
        });
    _s
}quote::quote! {
2364        impl #impl_generics ::miniextendr_api::externalptr::TypedExternal for #name #ty_generics #where_clause {
2365            const TYPE_NAME: &'static str = #display_lit;
2366            const TYPE_NAME_CSTR: &'static [u8] = #name_cstr;
2367            const TYPE_ID_CSTR: &'static [u8] =
2368                ::core::concat!(
2369                    ::core::env!("CARGO_PKG_NAME"), "@", ::core::env!("CARGO_PKG_VERSION"),
2370                    "::", ::core::module_path!(), "::", #name_lit, "\0"
2371                ).as_bytes();
2372            #r_slot_count
2373            #restored_class
2374            #restored_message
2375            #field_error_class
2376            #field_error_message
2377            #rebuild
2378        }
2379    }
2380}
2381
2382/// ALTREP stores its owner in an ordinary ExternalPtr, so it needs the same
2383/// slot visitor and handle accessors. Only storage is generated here: ALTREP
2384/// has its own IntoR and registration, and isn't an R class with field methods.
2385pub(crate) fn generate_altrep_storage(
2386    input: &DeriveInput,
2387    class_name: &str,
2388) -> syn::Result<TokenStream> {
2389    if let syn::Data::Struct(data) = &input.data {
2390        for field in &data.fields {
2391            for attr in field.attrs.iter().filter(|a| a.path().is_ident("r_data")) {
2392                if sidecar_inner_type(&field.ty).is_none() {
2393                    return Err(syn::Error::new_spanned(
2394                        field,
2395                        "ALTREP #[r_data] fields must be Sidecar<T>; R field accessors belong to ExternalPtr classes",
2396                    ));
2397                }
2398                if #[allow(non_exhaustive_omitted_patterns)] match &attr.meta {
    syn::Meta::List(_) => true,
    _ => false,
}matches!(&attr.meta, syn::Meta::List(_)) {
2399                    attr.parse_nested_meta(|meta| {
2400                        if meta.path.is_ident("ref") || meta.path.is_ident("mut") {
2401                            Ok(())
2402                        } else {
2403                            Err(meta.error("ALTREP sidecars support only `ref` and `mut` access; they have no R field wrappers"))
2404                        }
2405                    })?;
2406                }
2407            }
2408        }
2409    }
2410    let info = parse_sidecar_info(input, ClassSystem::Env)?;
2411    let storage = generate_typed_external(
2412        input,
2413        &info,
2414        &RestoredOptions::default(),
2415        &FieldErrorOptions::default(),
2416        None,
2417        false,
2418        Some(class_name),
2419    );
2420    let accessors = generate_sidecar_rust_accessors(input, &info, false);
2421    Ok({
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::ToTokens::to_tokens(&storage, &mut _s);
    ::quote::ToTokens::to_tokens(&accessors, &mut _s);
    _s
}quote::quote! { #storage #accessors })
2422}
2423
2424/// Generate the Rust accessors of each `Sidecar<T>` field, as associated
2425/// functions of the struct taking an owner handle, with the field's visibility:
2426/// `fn keys(ptr: &impl SidecarOwner<Self>) -> T` for `#[r_data(ref)]`,
2427/// `fn set_keys(ptr: &mut impl SidecarOwnerMut<Self>, value: T)` for `#[r_data(mut)]`,
2428/// both for `#[r_data(ref, mut)]` or a bare `#[r_data]`. The struct holds no
2429/// reference to its pointer, so there is no accessor on `&self` (#1856).
2430///
2431/// A type with a rebuild hook (`rebuilds`) also gets
2432/// `fn try_keys(saved: &Saved<'_, Self>) -> Option<T>` for every `Sidecar`
2433/// field: `None` when the save has no slot for it (#2187).
2434///
2435/// A non-`pub` accessor is `#[allow(dead_code)]`: it is generated whether or
2436/// not the crate calls it. The accessors (and the `TypedExternal` hook) read
2437/// the fields, so rustc sees them used.
2438fn generate_sidecar_rust_accessors(
2439    input: &DeriveInput,
2440    info: &SidecarInfo,
2441    rebuilds: bool,
2442) -> TokenStream {
2443    let methods: Vec<TokenStream> = sidecar_slots(info)
2444        .flat_map(|(index, slot)| {
2445            let inner = sidecar_inner_type(&slot.ty).expect("a Sidecar slot has an inner type");
2446            let field = &slot.name;
2447            let field_name = crate::naming::ident_name(field);
2448            // The accessors are named by the Rust field; the save, and the
2449            // messages R users read, by its R name (#2215).
2450            let r_name = slot.r_name.as_str();
2451            let vis = &slot.vis;
2452            let allow = (!#[allow(non_exhaustive_omitted_patterns)] match vis {
    Visibility::Public(_) => true,
    _ => false,
}matches!(vis, Visibility::Public(_)))
2453                .then(|| {
    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
        });
    _s
}quote::quote! { #[allow(dead_code)] });
2454            let mut methods = Vec::with_capacity(3);
2455            if rebuilds {
2456                let try_getter = match ::quote::__private::IdentFragmentAdapter(&field_name) {
    arg =>
        ::quote::__private::mk_ident(&::alloc::__export::must_use({
                        ::alloc::fmt::format(format_args!("try_{0}", arg))
                    }), ::quote::__private::Option::None.or(arg.span())),
}quote::format_ident!("try_{}", field_name);
2457                let doc = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("The value of the `{0}` sidecar field as the save `saved` kept it, in the slot of its R name `{1}`, for a rebuild hook; `None` when the version that saved the object had no slot of that name. On R\'s main thread.",
                field_name, r_name))
    })format!(
2458                    "The value of the `{field_name}` sidecar field as the save `saved` kept \
2459                     it, in the slot of its R name `{r_name}`, for a rebuild hook; `None` when \
2460                     the version that saved the object had no slot of that name. On R's main \
2461                     thread."
2462                );
2463                methods.push({
    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(&doc, &mut _s);
            _s
        });
    ::quote::ToTokens::to_tokens(&allow, &mut _s);
    ::quote::ToTokens::to_tokens(&vis, &mut _s);
    ::quote::__private::push_ident(&mut _s, "fn");
    ::quote::ToTokens::to_tokens(&try_getter, &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, "saved");
            ::quote::__private::push_colon(&mut _s);
            ::quote::__private::push_and(&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, "Saved");
            ::quote::__private::push_lt(&mut _s);
            ::quote::__private::push_lifetime(&mut _s, "\'_");
            ::quote::__private::push_comma(&mut _s);
            ::quote::__private::push_ident(&mut _s, "Self");
            ::quote::__private::push_gt(&mut _s);
            ::quote::__private::push_comma(&mut _s);
            _s
        });
    ::quote::__private::push_rarrow(&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_lt(&mut _s);
    ::quote::ToTokens::to_tokens(&inner, &mut _s);
    ::quote::__private::push_gt(&mut _s);
    ::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, "sidecar_try_saved");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_lt(&mut _s);
            ::quote::__private::push_ident(&mut _s, "Self");
            ::quote::__private::push_comma(&mut _s);
            ::quote::ToTokens::to_tokens(&inner, &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, "saved");
                    ::quote::__private::push_comma(&mut _s);
                    ::quote::ToTokens::to_tokens(&index, &mut _s);
                    ::quote::__private::push_comma(&mut _s);
                    ::quote::ToTokens::to_tokens(&r_name, &mut _s);
                    ::quote::__private::push_comma(&mut _s);
                    _s
                });
            _s
        });
    _s
}quote::quote! {
2464                    #[doc = #doc]
2465                    #allow
2466                    #vis fn #try_getter(
2467                        saved: &::miniextendr_api::externalptr::Saved<'_, Self>,
2468                    ) -> ::core::option::Option<#inner> {
2469                        ::miniextendr_api::externalptr::sidecar_try_saved::<Self, #inner>(
2470                            saved, #index, #r_name,
2471                        )
2472                    }
2473                });
2474            }
2475            if slot.access.get {
2476                let doc = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("The value of the `{0}` sidecar field of the object `ptr` holds: the R value the external pointer keeps, converted to its Rust type (a copy for a converting type), after moving a pending value into the pointer. On R\'s main thread.",
                field_name))
    })format!(
2477                    "The value of the `{field_name}` sidecar field of the object `ptr` holds: \
2478                     the R value the external pointer keeps, converted to its Rust type (a copy \
2479                     for a converting type), after moving a pending value into the pointer. On \
2480                     R's main thread."
2481                );
2482                methods.push({
    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(&doc, &mut _s);
            _s
        });
    ::quote::ToTokens::to_tokens(&allow, &mut _s);
    ::quote::ToTokens::to_tokens(&vis, &mut _s);
    ::quote::__private::push_ident(&mut _s, "fn");
    ::quote::ToTokens::to_tokens(&field, &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, "ptr");
            ::quote::__private::push_colon(&mut _s);
            ::quote::__private::push_and(&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, "externalptr");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "SidecarOwner");
            ::quote::__private::push_lt(&mut _s);
            ::quote::__private::push_ident(&mut _s, "Self");
            ::quote::__private::push_gt(&mut _s);
            _s
        });
    ::quote::__private::push_rarrow(&mut _s);
    ::quote::ToTokens::to_tokens(&inner, &mut _s);
    ::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, "sidecar_get");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_lt(&mut _s);
            ::quote::__private::push_ident(&mut _s, "Self");
            ::quote::__private::push_comma(&mut _s);
            ::quote::ToTokens::to_tokens(&inner, &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, "ptr");
                    ::quote::__private::push_comma(&mut _s);
                    ::quote::ToTokens::to_tokens(&index, &mut _s);
                    ::quote::__private::push_comma(&mut _s);
                    ::quote::ToTokens::to_tokens(&r_name, &mut _s);
                    ::quote::__private::push_comma(&mut _s);
                    ::quote::__private::push_or(&mut _s);
                    ::quote::__private::push_ident(&mut _s, "this");
                    ::quote::__private::push_colon(&mut _s);
                    ::quote::__private::push_and(&mut _s);
                    ::quote::__private::push_ident(&mut _s, "Self");
                    ::quote::__private::push_or(&mut _s);
                    ::quote::__private::push_and(&mut _s);
                    ::quote::__private::push_ident(&mut _s, "this");
                    ::quote::__private::push_dot(&mut _s);
                    ::quote::ToTokens::to_tokens(&field, &mut _s);
                    ::quote::__private::push_comma(&mut _s);
                    _s
                });
            _s
        });
    _s
}quote::quote! {
2483                    #[doc = #doc]
2484                    #allow
2485                    #vis fn #field(ptr: &impl ::miniextendr_api::externalptr::SidecarOwner<Self>) -> #inner {
2486                        ::miniextendr_api::externalptr::sidecar_get::<Self, #inner>(
2487                            ptr, #index, #r_name, |this: &Self| &this.#field,
2488                        )
2489                    }
2490                });
2491            }
2492            if slot.access.set {
2493                let setter = match ::quote::__private::IdentFragmentAdapter(&field_name) {
    arg =>
        ::quote::__private::mk_ident(&::alloc::__export::must_use({
                        ::alloc::fmt::format(format_args!("set_{0}", arg))
                    }), ::quote::__private::Option::None.or(arg.span())),
}quote::format_ident!("set_{}", field_name);
2494                let doc = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("Stores `value` in the `{0}` sidecar field of the object `ptr` holds: in the external pointer\'s protection list, dropping a pending value. On R\'s main thread.",
                field_name))
    })format!(
2495                    "Stores `value` in the `{field_name}` sidecar field of the object `ptr` \
2496                     holds: in the external pointer's protection list, dropping a pending value. \
2497                     On R's main thread."
2498                );
2499                methods.push({
    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(&doc, &mut _s);
            _s
        });
    ::quote::ToTokens::to_tokens(&allow, &mut _s);
    ::quote::ToTokens::to_tokens(&vis, &mut _s);
    ::quote::__private::push_ident(&mut _s, "fn");
    ::quote::ToTokens::to_tokens(&setter, &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, "ptr");
            ::quote::__private::push_colon(&mut _s);
            ::quote::__private::push_and(&mut _s);
            ::quote::__private::push_ident(&mut _s, "mut");
            ::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, "externalptr");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "SidecarOwnerMut");
            ::quote::__private::push_lt(&mut _s);
            ::quote::__private::push_ident(&mut _s, "Self");
            ::quote::__private::push_gt(&mut _s);
            ::quote::__private::push_comma(&mut _s);
            ::quote::__private::push_ident(&mut _s, "value");
            ::quote::__private::push_colon(&mut _s);
            ::quote::ToTokens::to_tokens(&inner, &mut _s);
            _s
        });
    ::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, "sidecar_set");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_lt(&mut _s);
            ::quote::__private::push_ident(&mut _s, "Self");
            ::quote::__private::push_comma(&mut _s);
            ::quote::ToTokens::to_tokens(&inner, &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, "ptr");
                    ::quote::__private::push_comma(&mut _s);
                    ::quote::ToTokens::to_tokens(&index, &mut _s);
                    ::quote::__private::push_comma(&mut _s);
                    ::quote::ToTokens::to_tokens(&r_name, &mut _s);
                    ::quote::__private::push_comma(&mut _s);
                    ::quote::__private::push_or(&mut _s);
                    ::quote::__private::push_ident(&mut _s, "this");
                    ::quote::__private::push_colon(&mut _s);
                    ::quote::__private::push_and(&mut _s);
                    ::quote::__private::push_ident(&mut _s, "Self");
                    ::quote::__private::push_or(&mut _s);
                    ::quote::__private::push_and(&mut _s);
                    ::quote::__private::push_ident(&mut _s, "this");
                    ::quote::__private::push_dot(&mut _s);
                    ::quote::ToTokens::to_tokens(&field, &mut _s);
                    ::quote::__private::push_comma(&mut _s);
                    ::quote::__private::push_ident(&mut _s, "value");
                    ::quote::__private::push_comma(&mut _s);
                    _s
                });
            _s
        });
    _s
}quote::quote! {
2500                    #[doc = #doc]
2501                    #allow
2502                    #vis fn #setter(ptr: &mut impl ::miniextendr_api::externalptr::SidecarOwnerMut<Self>, value: #inner) {
2503                        ::miniextendr_api::externalptr::sidecar_set::<Self, #inner>(
2504                            ptr, #index, #r_name, |this: &Self| &this.#field, value,
2505                        )
2506                    }
2507                });
2508            }
2509            methods
2510        })
2511        .collect();
2512    if methods.is_empty() {
2513        return TokenStream::new();
2514    }
2515    let name = &input.ident;
2516    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
2517    {
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::__private::push_ident(&mut _s, "impl");
    ::quote::ToTokens::to_tokens(&impl_generics, &mut _s);
    ::quote::ToTokens::to_tokens(&name, &mut _s);
    ::quote::ToTokens::to_tokens(&ty_generics, &mut _s);
    ::quote::ToTokens::to_tokens(&where_clause, &mut _s);
    ::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 methods, i) = methods.quote_into_iter();
                let has_iter = has_iter | i;
                <_ as
                        ::quote::__private::CheckHasIterator<true>>::check(has_iter);
                while true {
                    let methods =
                        match methods.next() {
                            Some(_x) => ::quote::__private::RepInterp(_x),
                            None => break,
                        };
                    ::quote::ToTokens::to_tokens(&methods, &mut _s);
                }
            }
            _s
        });
    _s
}quote::quote! {
2518        impl #impl_generics #name #ty_generics #where_clause {
2519            #(#methods)*
2520        }
2521    }
2522}
2523
2524/// Generate the `IntoExternalPtr` marker trait impl.
2525///
2526/// This marker trait enables the blanket `impl<T: IntoExternalPtr> IntoR for T`
2527/// in miniextendr-api, allowing the type to be returned directly from functions.
2528fn generate_into_external_ptr(input: &DeriveInput) -> TokenStream {
2529    let name = &input.ident;
2530    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
2531
2532    {
    let mut _s = ::quote::__private::TokenStream::new();
    ::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, "externalptr");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "IntoExternalPtr");
    ::quote::__private::push_ident(&mut _s, "for");
    ::quote::ToTokens::to_tokens(&name, &mut _s);
    ::quote::ToTokens::to_tokens(&ty_generics, &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::quote! {
2533        impl #impl_generics ::miniextendr_api::externalptr::IntoExternalPtr for #name #ty_generics #where_clause {}
2534    }
2535}
2536
2537/// Generate the concrete `IntoRVecElement` impl (issue #1284).
2538///
2539/// Lights up the single `impl<T: IntoRVecElement> IntoR for Vec<T>` blanket in
2540/// miniextendr-api, so `-> Vec<MyType>` returns compile and produce a `VECSXP`
2541/// of external pointers. The impl must be concrete (not a blanket over
2542/// `IntoExternalPtr`) because the `MatchArg` bridge already occupies a blanket
2543/// on `IntoRVecElement` and two blankets would collide (E0119) — the same
2544/// funnel design `#[derive(IntoR)]` newtypes use (see
2545/// `miniextendr_api::newtype` module docs).
2546///
2547/// GC discipline: `ExternalPtr::collect_into_r_list` allocates each
2548/// `EXTPTRSXP` directly into the already-protected destination list, so no
2549/// element is ever live-but-unrooted across a later allocation.
2550fn generate_into_r_vec_element(input: &DeriveInput) -> TokenStream {
2551    let name = &input.ident;
2552    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
2553
2554    {
    let mut _s = ::quote::__private::TokenStream::new();
    ::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, "IntoRVecElement");
    ::quote::__private::push_ident(&mut _s, "for");
    ::quote::ToTokens::to_tokens(&name, &mut _s);
    ::quote::ToTokens::to_tokens(&ty_generics, &mut _s);
    ::quote::ToTokens::to_tokens(&where_clause, &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, "inline");
                    _s
                });
            ::quote::__private::push_ident(&mut _s, "fn");
            ::quote::__private::push_ident(&mut _s, "elements_into_sexp");
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::__private::push_ident(&mut _s, "values");
                    ::quote::__private::push_colon(&mut _s);
                    ::quote::__private::push_colon2(&mut _s);
                    ::quote::__private::push_ident(&mut _s, "std");
                    ::quote::__private::push_colon2(&mut _s);
                    ::quote::__private::push_ident(&mut _s, "vec");
                    ::quote::__private::push_colon2(&mut _s);
                    ::quote::__private::push_ident(&mut _s, "Vec");
                    ::quote::__private::push_lt(&mut _s);
                    ::quote::__private::push_ident(&mut _s, "Self");
                    ::quote::__private::push_gt(&mut _s);
                    _s
                });
            ::quote::__private::push_rarrow(&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_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, "ExternalPtr");
                    ::quote::__private::push_colon2(&mut _s);
                    ::quote::__private::push_lt(&mut _s);
                    ::quote::__private::push_ident(&mut _s, "Self");
                    ::quote::__private::push_gt(&mut _s);
                    ::quote::__private::push_colon2(&mut _s);
                    ::quote::__private::push_ident(&mut _s,
                        "collect_into_r_list");
                    ::quote::__private::push_group(&mut _s,
                        ::quote::__private::Delimiter::Parenthesis,
                        {
                            let mut _s = ::quote::__private::TokenStream::new();
                            ::quote::__private::push_ident(&mut _s, "values");
                            _s
                        });
                    _s
                });
            _s
        });
    _s
}quote::quote! {
2555        impl #impl_generics ::miniextendr_api::IntoRVecElement for #name #ty_generics #where_clause {
2556            #[inline]
2557            fn elements_into_sexp(values: ::std::vec::Vec<Self>) -> ::miniextendr_api::SEXP {
2558                ::miniextendr_api::externalptr::ExternalPtr::<Self>::collect_into_r_list(values)
2559            }
2560        }
2561    }
2562}
2563
2564/// Main entry point for `#[derive(ExternalPtr)]`.
2565///
2566/// Orchestrates the full derive expansion:
2567/// 1. Parses `#[externalptr(...)]` attributes for class system selection.
2568/// 2. Analyzes struct fields for `#[r_data]` sidecar slots.
2569/// 3. Generates `TypedExternal` impl (type identity for `ExternalPtr<T>`).
2570/// 4. Generates `IntoExternalPtr` marker impl (enables `IntoR` blanket impl)
2571///    and a concrete `IntoRVecElement` impl (enables the `IntoR for Vec<T>`
2572///    blanket, so `-> Vec<MyType>` returns a `VECSXP` of external pointers —
2573///    #1284). Both are suppressed when `emit_into_r_marker` is `false`, which
2574///    the struct-level `#[miniextendr(prefer = "native")]` path uses so its
2575///    concrete `AsRNative` `IntoR` does not collide with the blanket
2576///    `IntoExternalPtr` `IntoR` (E0119, #1283).
2577/// 5. Generates sidecar accessor FFI functions, registration constants, and R wrappers.
2578///
2579/// Returns the combined token stream of all generated items.
2580pub fn derive_external_ptr(
2581    input: DeriveInput,
2582    emit_into_r_marker: bool,
2583) -> syn::Result<TokenStream> {
2584    // Parse the class system and the restored refusal's classes / message
2585    // from the #[externalptr(...)] attribute
2586    let ExternalPtrAttrs {
2587        class_system,
2588        restored,
2589        field_error,
2590        rebuild,
2591        any_version,
2592    } = parse_externalptr_attrs(&input)?;
2593
2594    // Parse sidecar information from struct fields
2595    let sidecar_info = parse_sidecar_info(&input, class_system)?;
2596
2597    let typed_external = generate_typed_external(
2598        &input,
2599        &sidecar_info,
2600        &restored,
2601        &field_error,
2602        rebuild.as_ref(),
2603        any_version,
2604        None,
2605    );
2606    let sidecar_rust_accessors =
2607        generate_sidecar_rust_accessors(&input, &sidecar_info, rebuild.is_some());
2608    let (into_external_ptr, into_r_vec_element) = if emit_into_r_marker {
2609        (
2610            generate_into_external_ptr(&input),
2611            generate_into_r_vec_element(&input),
2612        )
2613    } else {
2614        (
2615            proc_macro2::TokenStream::new(),
2616            proc_macro2::TokenStream::new(),
2617        )
2618    };
2619    let sidecar_accessors =
2620        generate_sidecar_accessors(&input, &sidecar_info, &restored, &field_error)?;
2621    let erased_wrapper = generate_erased_wrapper(&input);
2622
2623    Ok({
    let mut _s = ::quote::__private::TokenStream::new();
    ::quote::ToTokens::to_tokens(&typed_external, &mut _s);
    ::quote::ToTokens::to_tokens(&sidecar_rust_accessors, &mut _s);
    ::quote::ToTokens::to_tokens(&into_external_ptr, &mut _s);
    ::quote::ToTokens::to_tokens(&into_r_vec_element, &mut _s);
    ::quote::ToTokens::to_tokens(&sidecar_accessors, &mut _s);
    ::quote::ToTokens::to_tokens(&erased_wrapper, &mut _s);
    _s
}quote::quote! {
2624        #typed_external
2625        #sidecar_rust_accessors
2626        #into_external_ptr
2627        #into_r_vec_element
2628        #sidecar_accessors
2629        #erased_wrapper
2630    })
2631}
2632
2633/// Generate the type-erased wrapper infrastructure for trait ABI dispatch.
2634///
2635/// For `#[derive(ExternalPtr)] struct MyCounter { ... }` generates:
2636/// - `__MX_TAG_MyCounter` - concrete type tag (FNV-1a hash of module_path + type)
2637/// - `__MX_BASE_VTABLE_MyCounter` - base vtable with trait/sidecar query
2638/// - `__mx_wrap_MyCounter` - constructor returning `*mut mx_erased`
2639fn generate_erased_wrapper(input: &DeriveInput) -> TokenStream {
2640    let type_ident = &input.ident;
2641
2642    // Only for non-generic types (generics can't participate in trait dispatch)
2643    if !input.generics.params.is_empty() {
2644        return ::quote::__private::TokenStream::new()quote::quote! {};
2645    }
2646
2647    // Preserve case: distinct Rust types need distinct module-local helpers.
2648    let type_name = crate::naming::unraw(type_ident);
2649
2650    let base_vtable_name = match ::quote::__private::IdentFragmentAdapter(&type_name) {
    arg =>
        ::quote::__private::mk_ident(&::alloc::__export::must_use({
                        ::alloc::fmt::format(format_args!("__MX_BASE_VTABLE_{0}",
                                arg))
                    }), ::quote::__private::Option::None.or(arg.span())),
}quote::format_ident!("__MX_BASE_VTABLE_{}", type_name);
2651    let concrete_tag_name = match ::quote::__private::IdentFragmentAdapter(&type_name) {
    arg =>
        ::quote::__private::mk_ident(&::alloc::__export::must_use({
                        ::alloc::fmt::format(format_args!("__MX_TAG_{0}", arg))
                    }), ::quote::__private::Option::None.or(arg.span())),
}quote::format_ident!("__MX_TAG_{}", type_name);
2652    let wrap_fn_name = match ::quote::__private::IdentFragmentAdapter(&type_name) {
    arg =>
        ::quote::__private::mk_ident(&::alloc::__export::must_use({
                        ::alloc::fmt::format(format_args!("__mx_wrap_{0}", arg))
                    }), ::quote::__private::Option::None.or(arg.span())),
}quote::format_ident!("__mx_wrap_{}", type_name);
2653    let source_loc_doc = crate::source_location_doc(type_ident.span());
2654    let tag_path = ::alloc::__export::must_use({
        ::alloc::fmt::format(format_args!("::{0}", type_ident))
    })format!("::{}", type_ident);
2655
2656    {
    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_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::__private::push_ident(&mut _s, "hidden");
                    _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, "allow");
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::__private::push_ident(&mut _s,
                        "non_upper_case_globals");
                    _s
                });
            _s
        });
    ::quote::__private::push_ident(&mut _s, "const");
    ::quote::ToTokens::to_tokens(&concrete_tag_name, &mut _s);
    ::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, "abi");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "mx_tag");
    ::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, "abi");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "mx_tag_from_path");
    ::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, "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::push_colon2(&mut _s);
                    ::quote::__private::push_ident(&mut _s, "core");
                    ::quote::__private::push_colon2(&mut _s);
                    ::quote::__private::push_ident(&mut _s, "module_path");
                    ::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::ToTokens::to_tokens(&tag_path, &mut _s);
                    _s
                });
            _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, "doc");
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::__private::push_ident(&mut _s, "hidden");
                    _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, "allow");
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::__private::push_ident(&mut _s,
                        "non_upper_case_globals");
                    _s
                });
            _s
        });
    ::quote::__private::push_ident(&mut _s, "static");
    ::quote::ToTokens::to_tokens(&base_vtable_name, &mut _s);
    ::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, "abi");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "mx_base_vtable");
    ::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, "abi");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "mx_base_vtable");
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Brace,
        {
            let mut _s = ::quote::__private::TokenStream::new();
            ::quote::__private::push_ident(&mut _s, "drop");
            ::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, "abi");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "ErasedObject");
            ::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, "drop_raw");
            ::quote::__private::push_comma(&mut _s);
            ::quote::__private::push_ident(&mut _s, "concrete_tag");
            ::quote::__private::push_colon(&mut _s);
            ::quote::ToTokens::to_tokens(&concrete_tag_name, &mut _s);
            ::quote::__private::push_comma(&mut _s);
            ::quote::__private::push_ident(&mut _s, "query");
            ::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, "abi");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "ErasedObject");
            ::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, "query");
            ::quote::__private::push_comma(&mut _s);
            ::quote::__private::push_ident(&mut _s, "data_offset");
            ::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, "abi");
            ::quote::__private::push_colon2(&mut _s);
            ::quote::__private::push_ident(&mut _s, "ErasedObject");
            ::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, "DATA_OFFSET");
            ::quote::__private::push_comma(&mut _s);
            _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, "doc");
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::__private::push_ident(&mut _s, "hidden");
                    _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::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, "allow");
            ::quote::__private::push_group(&mut _s,
                ::quote::__private::Delimiter::Parenthesis,
                {
                    let mut _s = ::quote::__private::TokenStream::new();
                    ::quote::__private::push_ident(&mut _s, "non_snake_case");
                    _s
                });
            _s
        });
    ::quote::__private::push_ident(&mut _s, "fn");
    ::quote::ToTokens::to_tokens(&wrap_fn_name, &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, "data");
            ::quote::__private::push_colon(&mut _s);
            ::quote::ToTokens::to_tokens(&type_ident, &mut _s);
            _s
        });
    ::quote::__private::push_rarrow(&mut _s);
    ::quote::__private::push_star(&mut _s);
    ::quote::__private::push_ident(&mut _s, "mut");
    ::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, "abi");
    ::quote::__private::push_colon2(&mut _s);
    ::quote::__private::push_ident(&mut _s, "mx_erased");
    ::quote::__private::push_group(&mut _s,
        ::quote::__private::Delimiter::Brace,
        {
            let mut _s = ::quote::__private::TokenStream::new();
            ::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, "abi");
                    ::quote::__private::push_colon2(&mut _s);
                    ::quote::__private::push_ident(&mut _s, "ErasedObject");
                    ::quote::__private::push_colon2(&mut _s);
                    ::quote::__private::push_ident(&mut _s, "into_raw");
                    ::quote::__private::push_group(&mut _s,
                        ::quote::__private::Delimiter::Parenthesis,
                        {
                            let mut _s = ::quote::__private::TokenStream::new();
                            ::quote::__private::push_ident(&mut _s, "data");
                            ::quote::__private::push_comma(&mut _s);
                            ::quote::__private::push_and(&mut _s);
                            ::quote::ToTokens::to_tokens(&base_vtable_name, &mut _s);
                            _s
                        });
                    _s
                });
            _s
        });
    _s
}quote::quote! {
2657        #[doc(hidden)]
2658        #[allow(non_upper_case_globals)]
2659        const #concrete_tag_name: ::miniextendr_api::abi::mx_tag =
2660            ::miniextendr_api::abi::mx_tag_from_path(::core::concat!(::core::module_path!(), #tag_path));
2661
2662        #[doc(hidden)]
2663        #[allow(non_upper_case_globals)]
2664        static #base_vtable_name: ::miniextendr_api::abi::mx_base_vtable =
2665            ::miniextendr_api::abi::mx_base_vtable {
2666                drop: ::miniextendr_api::abi::ErasedObject::<#type_ident>::drop_raw,
2667                concrete_tag: #concrete_tag_name,
2668                query: ::miniextendr_api::abi::ErasedObject::<#type_ident>::query,
2669                data_offset: ::miniextendr_api::abi::ErasedObject::<#type_ident>::DATA_OFFSET,
2670            };
2671
2672        #[doc(hidden)]
2673        #[doc = #source_loc_doc]
2674        #[allow(non_snake_case)]
2675        fn #wrap_fn_name(data: #type_ident) -> *mut ::miniextendr_api::abi::mx_erased {
2676            // SAFETY: the matching base vtable above uses this storage's
2677            // destructor/offset and this concrete type's registry tag.
2678            unsafe {
2679                ::miniextendr_api::abi::ErasedObject::into_raw(data, &#base_vtable_name)
2680            }
2681        }
2682    }
2683}
2684
2685#[cfg(test)]
2686mod tests {
2687    use super::{ClassSystem, generate_class_integration_r_code, generate_r_wrapper_for_slot};
2688
2689    const ALL_CLASS_SYSTEMS: [ClassSystem; 6] = [
2690        ClassSystem::Env,
2691        ClassSystem::R6,
2692        ClassSystem::S3,
2693        ClassSystem::S4,
2694        ClassSystem::S7,
2695        ClassSystem::Vctrs,
2696    ];
2697
2698    /// Sidecar accessor C functions take only `x` (getter) or `x, value` (setter) —
2699    /// no `__miniextendr_call` parameter. The R wrappers must pass no `.call` argument
2700    /// because it would be counted as an extra positional argument by `.Call()`, causing
2701    /// "Incorrect number of arguments" errors at runtime. Cover every class system variant.
2702    #[test]
2703    fn sidecar_accessors_pass_no_call_slot() {
2704        let getter_c = "C__mx_rdata_get_T_f";
2705        let setter_c = "C__mx_rdata_set_T_f";
2706
2707        for cs in ALL_CLASS_SYSTEMS {
2708            let out =
2709                generate_r_wrapper_for_slot(cs, "T", "f", getter_c, Some(setter_c), true, true);
2710            // Correct form: no .call argument — the C function accepts x (getter)
2711            // or x, value (setter), then the `require_live` flag, `FALSE` for a
2712            // standalone accessor (#1891).
2713            assert!(
2714                out.contains(&format!(".Call({getter_c}, x, FALSE)")),
2715                "{cs:?} getter should call without .call:\n{out}"
2716            );
2717            assert!(
2718                out.contains(&format!(".Call({setter_c}, x, value, FALSE)")),
2719                "{cs:?} setter should call without .call:\n{out}"
2720            );
2721            // No `.call =` in either spelling.
2722            assert!(
2723                !out.contains(".call ="),
2724                "{cs:?} sidecar wrapper must not pass a .call argument:\n{out}"
2725            );
2726        }
2727    }
2728
2729    /// Every sidecar R wrapper (getter and setter, all class systems) must
2730    /// carry the tagged-condition guard so Rust panics / conversion failures
2731    /// transported by `with_r_unwind_protect(…, None)` are re-raised as
2732    /// structured R conditions instead of leaking the tagged SEXP to the user.
2733    #[test]
2734    fn sidecar_accessors_reraise_tagged_conditions() {
2735        let getter_c = "C__mx_rdata_get_T_f";
2736        let setter_c = "C__mx_rdata_set_T_f";
2737
2738        for cs in ALL_CLASS_SYSTEMS {
2739            let out =
2740                generate_r_wrapper_for_slot(cs, "T", "f", getter_c, Some(setter_c), true, true);
2741            assert_eq!(
2742                out.matches(".miniextendr_raise_condition(.val, sys.call())")
2743                    .count(),
2744                2,
2745                "{cs:?} getter+setter should each carry the condition guard:\n{out}"
2746            );
2747            // A computed field gets the getter only (#1883).
2748            let out = generate_r_wrapper_for_slot(cs, "T", "f", getter_c, None, true, true);
2749            assert!(out.contains("T_get_f <- function(x)"), "{out}");
2750            assert!(!out.contains("T_set_f"), "{out}");
2751            assert_eq!(
2752                out.matches(".miniextendr_raise_condition(.val, sys.call())")
2753                    .count(),
2754                1,
2755                "{cs:?} computed getter should carry the condition guard:\n{out}"
2756            );
2757        }
2758    }
2759
2760    /// A sidecar setter's conversion failure is the argument error of #1594
2761    /// on its `value` formal (`e$param`), naming the field: `'f' must be
2762    /// <expected>`, the field's Rust type as `e$rust_type`, and the reason probed from the error (a
2763    /// `Conversion` slot) or worded from the rejected value (a scalar slot).
2764    #[test]
2765    fn sidecar_setter_raises_the_argument_error() {
2766        let body = |ty: syn::Type, kind: super::SlotKind| {
2767            let slot = super::SidecarSlot {
2768                name: syn::Ident::new("f", proc_macro2::Span::call_site()),
2769                r_name: String::from("f"),
2770                ty,
2771                vis: syn::parse_quote!(pub),
2772                is_public: true,
2773                kind,
2774                access: super::SidecarAccess::default(),
2775                prop_doc: None,
2776                setter_invisible: None,
2777            };
2778            super::generate_setter_body(
2779                &syn::Ident::new("T", proc_macro2::Span::call_site()),
2780                &slot,
2781            )
2782            .to_string()
2783        };
2784
2785        let s = body(syn::parse_quote!(i32), super::SlotKind::ScalarInt);
2786        assert!(s.contains("\"'f' must be a number\" , \"value\""), "{s}");
2787        assert!(s.contains("scalar_rejection_reason (value)"), "{s}");
2788        assert!(s.contains("Some (\"i32\")"), "{s}");
2789        assert!(s.contains("__mx_conversion_err_parts ! ("), "{s}");
2790        assert!(!s.contains("failed to convert"), "{s}");
2791
2792        let s = body(syn::parse_quote!(bool), super::SlotKind::ScalarLogical);
2793        assert!(s.contains("\"'f' must be TRUE or FALSE\""), "{s}");
2794
2795        let s = body(syn::parse_quote!(Vec<String>), super::SlotKind::Conversion);
2796        assert!(s.contains("\"'f' must be character\" , \"value\""), "{s}");
2797        assert!(s.contains("Some (\"Vec<String>\")"), "{s}");
2798        assert!(s.contains(", true)"), "{s}");
2799
2800        // No static expectation: the error may supply one at run time (a
2801        // `match_arg` enum field), else `invalid 'f' argument`.
2802        let s = body(syn::parse_quote!(MyType), super::SlotKind::Conversion);
2803        assert!(s.contains("__mx_conversion_expectation ! (e)"), "{s}");
2804        assert!(s.contains("conversion_prefix (\"f\" , false ,"), "{s}");
2805        assert!(
2806            s.contains(") , \"value\" , :: core :: option :: Option :: Some (\"MyType\")"),
2807            "{s}"
2808        );
2809        assert!(s.contains("Some (\"MyType\")"), "{s}");
2810    }
2811
2812    /// The R6 active-binding and S7 property integration code also call the
2813    /// sidecar C entrypoints directly; they need the same guard (with
2814    /// `sys.call()` fallback attribution) and must not pass `.call =`.
2815    #[test]
2816    fn sidecar_class_integration_reraises_tagged_conditions() {
2817        let slot = super::SidecarSlot {
2818            name: syn::Ident::new("f", proc_macro2::Span::call_site()),
2819            r_name: String::from("f"),
2820            ty: syn::parse_quote!(i32),
2821            vis: syn::parse_quote!(pub),
2822            is_public: true,
2823            kind: super::SlotKind::ScalarInt,
2824            access: super::SidecarAccess::default(),
2825            prop_doc: None,
2826            setter_invisible: None,
2827        };
2828        let slots = [&slot];
2829
2830        for cs in [ClassSystem::R6, ClassSystem::S7] {
2831            let out = generate_class_integration_r_code(cs, "T", &slots);
2832            assert_eq!(
2833                out.matches(".miniextendr_raise_condition(.val, sys.call())")
2834                    .count(),
2835                2,
2836                "{cs:?} integration getter+setter should each carry the condition guard:\n{out}"
2837            );
2838            assert!(
2839                !out.contains(".call ="),
2840                "{cs:?} integration code must not pass a .call argument:\n{out}"
2841            );
2842            // The bindings / properties keep reading a save: `FALSE` as the
2843            // accessors' `require_live` flag (#1891).
2844            let getter_c = crate::naming::sidecar_getter_c_name("T", "f");
2845            let setter_c = crate::naming::sidecar_setter_c_name("T", "f");
2846            assert!(
2847                out.contains(&format!(".Call({getter_c}, ")) && out.contains(", FALSE)"),
2848                "{out}"
2849            );
2850            assert!(
2851                out.contains(&format!(".Call({setter_c}, ")) && out.contains(", value, FALSE)"),
2852                "{out}"
2853            );
2854        }
2855    }
2856
2857    /// `#[r_data(name = "...")]` renames the R field: the R6 binding, the S7
2858    /// property and its `@prop` entry, the standalone accessors and the field
2859    /// list use the R name, and so does a save's slot name (#2215); the C
2860    /// symbols, the `CallDef` statics and the Rust accessors keep the Rust
2861    /// identifier (#1891).
2862    #[test]
2863    fn r_data_name_renames_the_r_field_only() {
2864        let input: syn::DeriveInput = syn::parse_str(
2865            "struct Engine { #[r_data] _r: RSidecar, #[r_data(name = \"keys\")] pub r_keys: Sidecar<Vec<i32>>, \
2866             #[r_data(name = \"n.rows\")] pub n_rows: i32 }",
2867        )
2868        .unwrap();
2869        let info = super::parse_sidecar_info(&input, ClassSystem::R6).unwrap();
2870        assert_eq!(info.slots[0].r_name, "keys");
2871        assert_eq!(info.slots[1].r_name, "n.rows");
2872        let pub_slots: Vec<_> = info.slots.iter().collect();
2873
2874        let helpers = super::generate_field_syntax_helpers("Engine", &pub_slots, &[], None, false);
2875        assert!(
2876            helpers.contains(".rdata_fields_Engine <- c(\"keys\", \"n.rows\")\n"),
2877            "{helpers}"
2878        );
2879        let getter = crate::naming::sidecar_getter_c_name("Engine", "r_keys");
2880        assert!(
2881            helpers.contains(&format!("  keys = .Call({getter}, x, TRUE)")),
2882            "{helpers}"
2883        );
2884        assert!(
2885            helpers.contains("its fields are `keys`, `n.rows`"),
2886            "{helpers}"
2887        );
2888
2889        let r6 = super::generate_class_integration_r_code(ClassSystem::R6, "Engine", &pub_slots);
2890        assert!(r6.contains("cls$set(\"active\", \"keys\","), "{r6}");
2891        assert!(
2892            r6.contains(&format!(".Call({getter}, private$.ptr, FALSE)")),
2893            "{r6}"
2894        );
2895        assert!(!r6.contains("\"r_keys\""), "{r6}");
2896        let s7 = super::generate_class_integration_r_code(ClassSystem::S7, "Engine", &pub_slots);
2897        assert!(s7.contains("    keys = S7::new_property("), "{s7}");
2898
2899        let out = super::derive_external_ptr(input, true).unwrap().to_string();
2900        assert!(out.contains("Engine_get_keys <- function(x)"), "{out}");
2901        assert!(
2902            out.contains("Engine_set_keys <- function(x, value)"),
2903            "{out}"
2904        );
2905        assert!(out.contains("Engine_get_n.rows <- function(x)"), "{out}");
2906        assert!(!out.contains("Engine_get_r_keys"), "{out}");
2907        assert!(out.contains(&getter), "{out}");
2908        assert!(
2909            out.contains("__MX_CALL_DEF_RDATA_GET_Engine_r_keys"),
2910            "{out}"
2911        );
2912        assert!(
2913            out.contains("__MX_CALL_DEF_RDATA_GET_Engine_n_rows"),
2914            "{out}"
2915        );
2916        // The Rust accessors keep the Rust name; the visitor hook, which
2917        // names the save's slots, passes the R name (#2215).
2918        assert!(out.contains("pub fn r_keys (ptr :"), "{out}");
2919        assert!(out.contains("pub fn set_r_keys (ptr :"), "{out}");
2920        assert!(
2921            out.contains("visit (0usize , \"keys\" , & self . r_keys)"),
2922            "{out}"
2923        );
2924        // The setter's message names the R field.
2925        assert!(out.contains("'n.rows' must be a number"), "{out}");
2926    }
2927
2928    /// The R name must be non-empty, syntactic, unique, not `.ptr`, and not
2929    /// on the selector.
2930    #[test]
2931    fn r_data_name_is_checked() {
2932        let refused = |source: &str| {
2933            let input: syn::DeriveInput = syn::parse_str(source).unwrap();
2934            super::parse_sidecar_info(&input, ClassSystem::Env)
2935                .err()
2936                .map(|err| err.to_string())
2937                .unwrap_or_else(|| panic!("`{source}` must be refused"))
2938        };
2939        let message = refused("struct E { #[r_data(name = \"\")] pub count: i32 }");
2940        assert!(message.contains("can't be empty"), "{message}");
2941        let message = refused("struct E { #[r_data(name = \".ptr\")] pub count: i32 }");
2942        assert!(message.contains("`.ptr` is the element"), "{message}");
2943        let message = refused("struct E { #[r_data(name = \"n rows\")] pub count: i32 }");
2944        assert!(message.contains("not a syntactic R name"), "{message}");
2945        let message = refused("struct E { #[r_data(name = \"for\")] pub count: i32 }");
2946        assert!(message.contains("not a syntactic R name"), "{message}");
2947        let message = refused(
2948            "struct E { #[r_data] pub keys: i32, #[r_data(name = \"keys\")] pub r_keys: Sidecar<Vec<i32>> }",
2949        );
2950        assert!(
2951            message.contains("`keys` is already taken by the field `keys`"),
2952            "{message}"
2953        );
2954        let message =
2955            refused("struct E { #[r_data(name = \"r\")] _r: RSidecar, #[r_data] pub count: i32 }");
2956        assert!(message.contains("not the RSidecar selector"), "{message}");
2957        let message = refused("struct E { #[r_data(name = \"a\", name = \"b\")] pub count: i32 }");
2958        assert!(message.contains("duplicate `name`"), "{message}");
2959    }
2960
2961    /// A `Computed` field (#1883): its getter arm calls the `get` function on
2962    /// the live struct (a leading `Self` resolved to the type), it takes its
2963    /// declared place among the other fields, it gets no setter C function,
2964    /// `CallDef` or `Type_set_<f>()`, and the setter switch raises
2965    /// `miniextendr_read_only_field` for it.
2966    #[test]
2967    fn computed_fields_get_a_getter_and_a_read_only_setter_arm() {
2968        let input: syn::DeriveInput = syn::parse_str(
2969            "struct Engine { #[r_data] _r: RSidecar, \
2970             #[r_data(get = \"Self::group_by\")] pub group_by: Computed, \
2971             #[r_data(name = \"keys\")] pub r_keys: Sidecar<Vec<i32>>, \
2972             #[r_data(get = \"n_rows_of\")] pub n_rows: Computed, \
2973             #[r_data] pub base: i32 }",
2974        )
2975        .unwrap();
2976        let info = super::parse_sidecar_info(&input, ClassSystem::S3).unwrap();
2977        let kinds: Vec<_> = info.slots.iter().map(|slot| slot.kind.clone()).collect();
2978        assert_eq!(
2979            kinds,
2980            [
2981                super::SlotKind::Computed(syn::parse_quote!(Engine::group_by)),
2982                super::SlotKind::Sidecar(0),
2983                super::SlotKind::Computed(syn::parse_quote!(n_rows_of)),
2984                super::SlotKind::ScalarInt,
2985            ]
2986        );
2987        let name = syn::Ident::new("Engine", proc_macro2::Span::call_site());
2988        let getter = super::generate_getter_body(&name, &info.slots[0]).to_string();
2989        assert!(
2990            getter.contains("sidecar_r_struct :: < Engine > (x)"),
2991            "{getter}"
2992        );
2993        assert!(
2994            getter.contains("IntoR :: into_sexp (Engine :: group_by (data))"),
2995            "{getter}"
2996        );
2997        assert!(
2998            super::generate_setter_body(&name, &info.slots[0]).is_empty(),
2999            "a computed field has no setter body"
3000        );
3001
3002        let pub_slots: Vec<_> = info.slots.iter().collect();
3003        let helpers = super::generate_field_syntax_helpers("Engine", &pub_slots, &[], None, false);
3004        assert!(
3005            helpers.contains(
3006                ".rdata_fields_Engine <- c(\"group_by\", \"keys\", \"n_rows\", \"base\")\n"
3007            ),
3008            "{helpers}"
3009        );
3010        let group_by_getter = crate::naming::sidecar_getter_c_name("Engine", "group_by");
3011        assert!(
3012            helpers.contains(&format!("  group_by = .Call({group_by_getter}, x, TRUE),")),
3013            "{helpers}"
3014        );
3015        assert!(
3016            helpers.contains(
3017                "  group_by = stop(errorCondition(sprintf(\"`group_by` of `%s` is computed from the Rust \
3018                 value and can't be assigned\", if (is.null(oldClass(x))) \"Engine\" else oldClass(x)[[1L]]), \
3019                 class = c(.rdata_error_class_Engine, \"miniextendr_read_only_field\"), \
3020                 call = .miniextendr_frame_call(parent.frame()))),"
3021            ),
3022            "{helpers}"
3023        );
3024        let keys_setter = crate::naming::sidecar_setter_c_name("Engine", "r_keys");
3025        assert!(
3026            helpers.contains(&format!("  keys = .Call({keys_setter}, x, value, TRUE),")),
3027            "{helpers}"
3028        );
3029
3030        let out = super::derive_external_ptr(input, true).unwrap().to_string();
3031        assert!(out.contains("Engine_get_group_by <- function(x)"), "{out}");
3032        assert!(!out.contains("Engine_set_group_by"), "{out}");
3033        assert!(
3034            out.contains("__MX_CALL_DEF_RDATA_GET_Engine_group_by"),
3035            "{out}"
3036        );
3037        assert!(
3038            !out.contains("__MX_CALL_DEF_RDATA_SET_Engine_group_by"),
3039            "{out}"
3040        );
3041        assert!(
3042            !out.contains(&crate::naming::sidecar_setter_c_name("Engine", "group_by")),
3043            "{out}"
3044        );
3045        assert!(
3046            out.contains("Engine_set_keys <- function(x, value)"),
3047            "{out}"
3048        );
3049        // Only the `Sidecar` field's accessors read the flag; the others take
3050        // it unused.
3051        assert!(
3052            out.contains("_require_live : :: miniextendr_api :: SEXP"),
3053            "{out}"
3054        );
3055        assert!(out.contains("sidecar_require_live (require_live)"), "{out}");
3056    }
3057
3058    /// The `Computed` compile errors: `get` on another type, no `get`,
3059    /// `ref` / `mut` / `setter` on it, a non-`pub` one, and one under R6 / S7.
3060    #[test]
3061    fn computed_fields_are_checked() {
3062        let refused = |source: &str, system: ClassSystem| {
3063            let input: syn::DeriveInput = syn::parse_str(source).unwrap();
3064            super::parse_sidecar_info(&input, system)
3065                .err()
3066                .map(|err| err.to_string())
3067                .unwrap_or_else(|| panic!("`{source}` must be refused"))
3068        };
3069        let message = refused(
3070            "struct E { #[r_data(get = \"Self::f\")] pub count: i32 }",
3071            ClassSystem::S3,
3072        );
3073        assert!(
3074            message.contains("`get` applies to a `Computed` field only"),
3075            "{message}"
3076        );
3077        let message = refused(
3078            "struct E { #[r_data] pub count: Computed }",
3079            ClassSystem::S3,
3080        );
3081        assert!(
3082            message.contains("needs `#[r_data(get = \"Self::f\")]`"),
3083            "{message}"
3084        );
3085        let message = refused(
3086            "struct E { #[r_data(ref, get = \"Self::f\")] pub count: Computed }",
3087            ClassSystem::S3,
3088        );
3089        assert!(
3090            message.contains("read through its `get` function"),
3091            "{message}"
3092        );
3093        let message = refused(
3094            "struct E { #[r_data(setter = \"visible\", get = \"Self::f\")] pub count: Computed }",
3095            ClassSystem::S3,
3096        );
3097        assert!(
3098            message.contains("not the RSidecar selector or a `Computed` field"),
3099            "{message}"
3100        );
3101        let message = refused(
3102            "struct E { #[r_data(get = \"Self::f\")] count: Computed }",
3103            ClassSystem::S3,
3104        );
3105        assert!(message.contains("must be `pub`"), "{message}");
3106        for (system, name) in [(ClassSystem::R6, "r6"), (ClassSystem::S7, "s7")] {
3107            let message = refused(
3108                "struct E { #[r_data(get = \"Self::f\")] pub count: Computed }",
3109                system,
3110            );
3111            assert!(
3112                message.contains(&format!("not supported under `#[externalptr({name})]`")),
3113                "{message}"
3114            );
3115        }
3116        let message = refused(
3117            "struct E { #[r_data(get = \"not a path\")] pub count: Computed }",
3118            ClassSystem::S3,
3119        );
3120        assert!(message.contains("`get` must be a path"), "{message}");
3121    }
3122
3123    /// `#[externalptr(restored(class = ..., message = ...))]` (#1891): the
3124    /// classes (one, or several in order) and the message reach the
3125    /// `TypedExternal` impl; each alone works; the arguments are checked.
3126    #[test]
3127    fn restored_options_reach_the_typed_external_impl() {
3128        let attrs = |source: &str| {
3129            let input: syn::DeriveInput = syn::parse_str(source).unwrap();
3130            super::parse_externalptr_attrs(&input).map(|attrs| (attrs.class_system, attrs.restored))
3131        };
3132        let (system, restored) = attrs(
3133            "#[externalptr(s3, restored(class = [\"pkg_saved\", \"pkg_error\"], message = \"saved; rebuild it\"))] struct E;",
3134        )
3135        .unwrap();
3136        assert_eq!(system, ClassSystem::S3);
3137        assert_eq!(restored.class, ["pkg_saved", "pkg_error"]);
3138        assert_eq!(restored.message.as_deref(), Some("saved; rebuild it"));
3139        let (system, restored) =
3140            attrs("#[externalptr(restored(class = \"pkg_saved\"))] #[externalptr(s4)] struct E;")
3141                .unwrap();
3142        assert_eq!(system, ClassSystem::S4);
3143        assert_eq!(restored.class, ["pkg_saved"]);
3144        assert_eq!(restored.message, None);
3145        let (_, restored) = attrs("#[externalptr(restored(message = \"m\"))] struct E;").unwrap();
3146        assert!(restored.class.is_empty());
3147        assert_eq!(restored.message.as_deref(), Some("m"));
3148        let (_, restored) = attrs("#[externalptr(env)] struct E;").unwrap();
3149        assert_eq!(restored, super::RestoredOptions::default());
3150
3151        let refused = |source: &str, expected: &str| {
3152            let message = attrs(source)
3153                .err()
3154                .map(|e| e.to_string())
3155                .unwrap_or_else(|| panic!("`{source}` must be refused"));
3156            assert!(message.contains(expected), "`{source}`: {message}");
3157        };
3158        // syn refuses the empty parentheses itself.
3159        refused(
3160            "#[externalptr(restored())] struct E;",
3161            "expected nested attribute",
3162        );
3163        refused(
3164            "#[externalptr(restored(class = []))] struct E;",
3165            "at least one",
3166        );
3167        refused(
3168            "#[externalptr(restored(class = \"\"))] struct E;",
3169            "can't be empty",
3170        );
3171        refused(
3172            "#[externalptr(restored(message = \"\"))] struct E;",
3173            "can't be empty",
3174        );
3175        refused(
3176            "#[externalptr(restored(class = \"a\", class = \"b\"))] struct E;",
3177            "given twice",
3178        );
3179        refused(
3180            "#[externalptr(restored(class = \"a\"), restored(class = \"b\"))] struct E;",
3181            "given twice",
3182        );
3183        refused(
3184            "#[externalptr(restored(klass = \"a\"))] struct E;",
3185            "unknown `restored` option",
3186        );
3187        refused(
3188            "#[externalptr(saved)] struct E;",
3189            "unknown `externalptr` option",
3190        );
3191
3192        let input: syn::DeriveInput = syn::parse_str(
3193            "#[externalptr(s3, restored(class = [\"pkg_saved\", \"pkg_error\"], message = \"saved; rebuild it\"))] struct E { n: i32 }",
3194        )
3195        .unwrap();
3196        let out = super::derive_external_ptr(input, true).unwrap().to_string();
3197        assert!(
3198            out.contains(
3199                "const RESTORED_ERROR_CLASS : & 'static [& 'static str] = & [\"pkg_saved\" , \"pkg_error\"] ;"
3200            ),
3201            "{out}"
3202        );
3203        assert!(
3204            out.contains(
3205                "const RESTORED_ERROR_MESSAGE : :: core :: option :: Option < & 'static str > = :: core :: option :: Option :: Some (\"saved; rebuild it\") ;"
3206            ),
3207            "{out}"
3208        );
3209        let input: syn::DeriveInput = syn::parse_str("struct E { n: i32 }").unwrap();
3210        let out = super::derive_external_ptr(input, true).unwrap().to_string();
3211        assert!(!out.contains("RESTORED_ERROR_"), "{out}");
3212    }
3213
3214    /// `rebuild = path, any_version` (#2146, #2187): `any_version` needs
3215    /// the hook; with it the impl sets `REBUILDS_ANY_VERSION`, and a type
3216    /// with a hook gets `try_<field>` getters over the save, which take the
3217    /// `Saved` handle.
3218    #[test]
3219    fn rebuild_any_version_option() {
3220        let attrs = |source: &str| {
3221            let input: syn::DeriveInput = syn::parse_str(source).unwrap();
3222            super::parse_externalptr_attrs(&input)
3223        };
3224        let parsed =
3225            attrs("#[externalptr(rebuild = Self::from_saved, any_version)] struct E;").unwrap();
3226        assert!(parsed.rebuild.is_some());
3227        assert!(parsed.any_version);
3228        let parsed = attrs("#[externalptr(rebuild = Self::from_saved)] struct E;").unwrap();
3229        assert!(!parsed.any_version);
3230        for source in [
3231            "#[externalptr(any_version)] struct E;",
3232            "#[externalptr(r6, any_version)] struct E;",
3233        ] {
3234            let message = attrs(source)
3235                .err()
3236                .map(|e| e.to_string())
3237                .unwrap_or_default();
3238            assert!(
3239                message.contains("needs `rebuild = path`"),
3240                "`{source}`: {message}"
3241            );
3242        }
3243        let message = attrs("#[externalptr(rebuild = f, any_version, any_version)] struct E;")
3244            .err()
3245            .map(|e| e.to_string())
3246            .unwrap_or_default();
3247        assert!(
3248            message.contains("`any_version` is given twice"),
3249            "{message}"
3250        );
3251
3252        let input: syn::DeriveInput = syn::parse_str(
3253            "#[externalptr(rebuild = Self::from_saved, any_version)] struct E { #[r_data] keys: Sidecar<Vec<i32>>, #[r_data(name = \"n.rows\")] r_rows: Sidecar<i32> }",
3254        )
3255        .unwrap();
3256        let out = super::derive_external_ptr(input, true).unwrap().to_string();
3257        assert!(
3258            out.contains("const REBUILDS_ANY_VERSION : bool = true ;"),
3259            "{out}"
3260        );
3261        // A save names its slots by the R names (#2215): `name = ..`, else
3262        // the ident. The visitor, which flushes, loads and lays out a
3263        // rebuild's slots, and every accessor pass the same name.
3264        assert!(
3265            out.contains(
3266                "const SIDECAR_NAMES : & 'static [& 'static str] = & [\"keys\" , \"n.rows\"] ;"
3267            ),
3268            "{out}"
3269        );
3270        assert!(
3271            out.contains("visit (1usize , \"n.rows\" , & self . r_rows) ;"),
3272            "{out}"
3273        );
3274        assert!(
3275            out.contains(
3276                "fn try_keys (saved : & :: miniextendr_api :: externalptr :: Saved < '_ , Self > ,)"
3277            ),
3278            "{out}"
3279        );
3280        // The getters keep the Rust names.
3281        assert!(out.contains("fn try_r_rows (saved"), "{out}");
3282        assert!(
3283            out.contains("sidecar_try_saved :: < Self , i32 > (saved , 1usize , \"n.rows\" ,)"),
3284            "{out}"
3285        );
3286        assert!(
3287            out.contains("sidecar_get :: < Self , i32 > (ptr , 1usize , \"n.rows\" ,"),
3288            "{out}"
3289        );
3290        assert!(
3291            out.contains("sidecar_set :: < Self , i32 > (ptr , 1usize , \"n.rows\" ,"),
3292            "{out}"
3293        );
3294        assert!(!out.contains("\"r_rows\""), "{out}");
3295
3296        // Without a hook there is no `Saved` handle to read, so no `try_` getters.
3297        let input: syn::DeriveInput =
3298            syn::parse_str("struct E { #[r_data] keys: Sidecar<Vec<i32>> }").unwrap();
3299        let out = super::derive_external_ptr(input, true).unwrap().to_string();
3300        assert!(!out.contains("try_keys"), "{out}");
3301        assert!(!out.contains("REBUILDS"), "{out}");
3302    }
3303
3304    /// `field_error(class = ...)` (#1899) and `restored(refuse =
3305    /// "every_read")` (#1901): parsed and checked, the classes reach the
3306    /// `TypedExternal` impl and the R helpers, and the opt-in emits the
3307    /// liveness check; without them the helpers are `NULL`.
3308    #[test]
3309    fn field_error_and_every_read_options() {
3310        let attrs = |source: &str| {
3311            let input: syn::DeriveInput = syn::parse_str(source).unwrap();
3312            super::parse_externalptr_attrs(&input)
3313        };
3314        let parsed = attrs(
3315            "#[externalptr(s3, field_error(class = [\"pkg_config\", \"pkg_error\"]), restored(class = \"pkg_saved\", refuse = \"every_read\"))] struct E;",
3316        )
3317        .unwrap();
3318        assert_eq!(parsed.field_error.class, ["pkg_config", "pkg_error"]);
3319        assert_eq!(parsed.field_error.message, None);
3320        assert!(parsed.restored.every_read);
3321        assert_eq!(parsed.restored.class, ["pkg_saved"]);
3322        let parsed = attrs("#[externalptr(field_error(class = \"pkg_error\"))] struct E;").unwrap();
3323        assert_eq!(parsed.field_error.class, ["pkg_error"]);
3324        // A message alone keeps miniextendr's classes (#1920).
3325        let parsed =
3326            attrs("#[externalptr(field_error(message = \"not a rec\"))] struct E;").unwrap();
3327        assert!(parsed.field_error.class.is_empty());
3328        assert_eq!(parsed.field_error.message.as_deref(), Some("not a rec"));
3329        assert!(!parsed.restored.every_read);
3330        // The no-field hint (#2112).
3331        let parsed =
3332            attrs("#[externalptr(field_error(no_field_hint = \"try `y$%s`\"))] struct E;").unwrap();
3333        assert_eq!(
3334            parsed.field_error.no_field_hint.as_deref(),
3335            Some("try `y$%s`")
3336        );
3337        assert_eq!(parsed.field_error.message, None);
3338
3339        let refused = |source: &str, expected: &str| {
3340            let message = attrs(source)
3341                .err()
3342                .map(|e| e.to_string())
3343                .unwrap_or_else(|| panic!("`{source}` must be refused"));
3344            assert!(message.contains(expected), "`{source}`: {message}");
3345        };
3346        refused(
3347            "#[externalptr(restored(refuse = \"names\"))] struct E;",
3348            "`refuse` takes \"every_read\"",
3349        );
3350        refused(
3351            "#[externalptr(restored(refuse = \"every_read\", refuse = \"every_read\"))] struct E;",
3352            "given twice",
3353        );
3354        refused(
3355            "#[externalptr(field_error(class = []))] struct E;",
3356            "at least one",
3357        );
3358        refused(
3359            "#[externalptr(field_error(text = \"m\"))] struct E;",
3360            "unknown `field_error` option",
3361        );
3362        refused(
3363            "#[externalptr(field_error(message = \"\"))] struct E;",
3364            "`message` is empty",
3365        );
3366        refused(
3367            "#[externalptr(field_error(message = \"a\", message = \"b\"))] struct E;",
3368            "given twice",
3369        );
3370        refused(
3371            "#[externalptr(field_error(no_field_hint = \"\"))] struct E;",
3372            "`no_field_hint` is empty",
3373        );
3374        refused(
3375            "#[externalptr(field_error(no_field_hint = \"a\", no_field_hint = \"b\"))] struct E;",
3376            "given twice",
3377        );
3378        refused(
3379            "#[externalptr(field_error(class = \"a\"), field_error(class = \"b\"))] struct E;",
3380            "given twice",
3381        );
3382
3383        let source = "#[externalptr(s3, field_error(class = [\"pkg_config\", \"pkg_error\"]), restored(refuse = \"every_read\"))] \
3384                      struct Engine { #[r_data] r: RSidecar, #[r_data] pub keys: Sidecar<Vec<i32>> }";
3385        let out = super::derive_external_ptr(syn::parse_str(source).unwrap(), true)
3386            .unwrap()
3387            .to_string();
3388        assert!(
3389            out.contains(
3390                "const FIELD_ERROR_CLASS : & 'static [& 'static str] = & [\"pkg_config\" , \"pkg_error\"] ;"
3391            ),
3392            "{out}"
3393        );
3394        let live = crate::naming::sidecar_live_c_name("Engine");
3395        assert!(out.contains(&format!("fn {live}")), "{out}");
3396        assert!(out.contains("sidecar_r_live :: < Engine >"), "{out}");
3397        assert!(
3398            out.contains(".rdata_error_class_Engine <- c(\\\"pkg_config\\\", \\\"pkg_error\\\")"),
3399            "{out}"
3400        );
3401        assert!(
3402            out.contains(&format!(
3403                ".rdata_live_Engine <- function(x, raise) .Call({live}, x, raise)"
3404            )),
3405            "{out}"
3406        );
3407        assert!(out.contains(".rdata_every_read_Engine <- TRUE"), "{out}");
3408
3409        let out = super::derive_external_ptr(
3410            syn::parse_str(
3411                "struct Engine { #[r_data] r: RSidecar, #[r_data] pub keys: Sidecar<Vec<i32>> }",
3412            )
3413            .unwrap(),
3414            true,
3415        )
3416        .unwrap()
3417        .to_string();
3418        assert!(!out.contains("FIELD_ERROR_CLASS"), "{out}");
3419        assert!(!out.contains("mx_rdata_live_"), "{out}");
3420        assert!(out.contains(".rdata_error_class_Engine <- NULL"), "{out}");
3421        assert!(out.contains(".rdata_every_read_Engine <- FALSE"), "{out}");
3422        assert!(
3423            out.contains(".rdata_live_Engine <- function(x, raise) TRUE"),
3424            "{out}"
3425        );
3426    }
3427
3428    /// The standalone accessors go to a `SidecarAccessorsEntry` in two
3429    /// renderings (#1900): exported on the type's page, and internal.
3430    #[test]
3431    fn standalone_accessors_come_exported_and_internal() {
3432        for exported in [true, false] {
3433            let out = generate_r_wrapper_for_slot(
3434                ClassSystem::S3,
3435                "T",
3436                "f",
3437                "get_f",
3438                Some("set_f"),
3439                true,
3440                exported,
3441            );
3442            assert!(out.contains("T_get_f <- function(x)"), "{out}");
3443            assert!(out.contains("T_set_f <- function(x, value)"), "{out}");
3444            assert_eq!(out.contains("@export"), exported, "{out}");
3445            assert_eq!(out.contains("@rdname T"), exported, "{out}");
3446            assert_eq!(
3447                out.matches("#' @noRd").count(),
3448                if exported { 0 } else { 2 },
3449                "{out}"
3450            );
3451        }
3452        let out = super::derive_external_ptr(
3453            syn::parse_str("struct T { #[r_data] r: RSidecar, #[r_data] pub f: i32 }").unwrap(),
3454            true,
3455        )
3456        .unwrap()
3457        .to_string();
3458        assert!(out.contains("MX_SIDECAR_ACCESSORS"), "{out}");
3459        assert!(out.contains("R_SIDECAR_ACCESSORS_T"), "{out}");
3460    }
3461
3462    #[test]
3463    fn field_setter_visibility_reaches_standalone_and_integrated_wrappers() {
3464        for (attribute, explicit) in [
3465            ("", None),
3466            ("setter = \"visible\"", Some(false)),
3467            ("setter = \"invisible\"", Some(true)),
3468        ] {
3469            let input: syn::DeriveInput = syn::parse_str(&format!(
3470                "struct Example {{ #[r_data({attribute})] pub value: i32 }}"
3471            ))
3472            .unwrap();
3473            for system in ALL_CLASS_SYSTEMS {
3474                let info = super::parse_sidecar_info(&input, system).unwrap();
3475                let slot = &info.slots[0];
3476                assert_eq!(slot.setter_invisible, explicit);
3477                let standalone = generate_r_wrapper_for_slot(
3478                    system,
3479                    "Example",
3480                    "value",
3481                    "get_value",
3482                    Some("set_value"),
3483                    slot.setter_invisible.unwrap_or(true),
3484                    true,
3485                );
3486                assert_eq!(
3487                    standalone.contains("invisible(x)"),
3488                    explicit.unwrap_or(true)
3489                );
3490                assert!(standalone.contains(if explicit == Some(false) {
3491                    "pointer (visibly)"
3492                } else {
3493                    "pointer (invisibly)"
3494                }));
3495                let integrated = generate_class_integration_r_code(system, "Example", &[slot]);
3496                match system {
3497                    ClassSystem::R6 => assert_eq!(
3498                        integrated.contains("invisible(self)"),
3499                        explicit.unwrap_or(true)
3500                    ),
3501                    ClassSystem::S7 => assert_eq!(
3502                        integrated.contains("invisible(self)"),
3503                        explicit.unwrap_or(false)
3504                    ),
3505                    _ => assert!(integrated.is_empty()),
3506                }
3507            }
3508        }
3509    }
3510
3511    #[test]
3512    fn setter_visibility_and_property_docs_can_share_or_split_attributes() {
3513        for attrs in [
3514            "#[r_data(prop_doc = \"A counter\", setter = \"visible\")]",
3515            "#[r_data] #[r_data(setter = \"visible\", prop_doc = \"A counter\")]",
3516        ] {
3517            let input: syn::DeriveInput =
3518                syn::parse_str(&format!("struct Example {{ {attrs} pub value: i32 }}")).unwrap();
3519            let info = super::parse_sidecar_info(&input, ClassSystem::S7).unwrap();
3520            assert_eq!(info.slots[0].prop_doc.as_deref(), Some("A counter"));
3521            assert_eq!(info.slots[0].setter_invisible, Some(false));
3522        }
3523    }
3524
3525    /// `Sidecar` fields count among themselves: the scalars and conversion
3526    /// fields between them take no protection-list position. The derive
3527    /// emits the count, the visitor hook, and typed accessors on the handle
3528    /// with each field's visibility; the R accessors read and write the
3529    /// protection list by index.
3530    #[test]
3531    fn sidecar_fields_number_their_own_positions() {
3532        let input: syn::DeriveInput = syn::parse_str(
3533            "struct Engine { #[r_data] _r: RSidecar, #[r_data] pub a: i32, \
3534             #[r_data] pub keys: Sidecar<Vec<i32>>, #[r_data] pub c: String, \
3535             #[r_data] steps: Sidecar<Option<List>> }",
3536        )
3537        .unwrap();
3538        let info = super::parse_sidecar_info(&input, ClassSystem::Env).unwrap();
3539        let kinds: Vec<_> = info.slots.iter().map(|slot| slot.kind.clone()).collect();
3540        assert_eq!(
3541            kinds,
3542            [
3543                super::SlotKind::ScalarInt,
3544                super::SlotKind::Sidecar(0),
3545                super::SlotKind::Conversion,
3546                super::SlotKind::Sidecar(1),
3547            ]
3548        );
3549        // A bare `#[r_data]` means `ref, mut`.
3550        assert_eq!(
3551            info.slots[1].access,
3552            super::SidecarAccess {
3553                get: true,
3554                set: true
3555            }
3556        );
3557
3558        let out = super::derive_external_ptr(input, true).unwrap().to_string();
3559        assert!(
3560            out.contains("const R_SLOT_COUNT : usize = 2usize ;"),
3561            "{out}"
3562        );
3563        assert!(
3564            out.contains("visit (0usize , \"keys\" , & self . keys) ; visit (1usize , \"steps\" , & self . steps) ;"),
3565            "{out}"
3566        );
3567        // The accessors are associated functions taking the handle, not
3568        // methods on the struct (#1856).
3569        assert!(
3570            out.contains(
3571                "pub fn keys (ptr : & impl :: miniextendr_api :: externalptr :: SidecarOwner < Self >) -> Vec < i32 > { :: miniextendr_api :: externalptr :: sidecar_get :: < Self , Vec < i32 > > (ptr , 0usize , \"keys\" , | this : & Self | & this . keys ,) }"
3572            ),
3573            "{out}"
3574        );
3575        assert!(
3576            out.contains(
3577                "pub fn set_keys (ptr : & mut impl :: miniextendr_api :: externalptr :: SidecarOwnerMut < Self > , value : Vec < i32 >) { :: miniextendr_api :: externalptr :: sidecar_set :: < Self , Vec < i32 > > (ptr , 0usize , \"keys\" , | this : & Self | & this . keys , value ,) }"
3578            ),
3579            "{out}"
3580        );
3581        assert!(!out.contains("fn keys (& self)"), "{out}");
3582        // A private field gets private accessors (allowed to be unused) and
3583        // no R accessor.
3584        assert!(
3585            out.contains(
3586                "# [allow (dead_code)] fn steps (ptr : & impl :: miniextendr_api :: externalptr :: SidecarOwner < Self >) -> Option < List >"
3587            ),
3588            "{out}"
3589        );
3590        assert!(
3591            out.contains(
3592                "# [allow (dead_code)] fn set_steps (ptr : & mut impl :: miniextendr_api :: externalptr :: SidecarOwnerMut < Self > , value : Option < List >)"
3593            ),
3594            "{out}"
3595        );
3596        assert!(out.contains("(ptr , 1usize , \"steps\" ,"), "{out}");
3597        assert!(!out.contains("get_steps"), "{out}");
3598
3599        // The R accessors read and write the protection list, not the struct.
3600        let keys = &info.slots[1];
3601        let name = syn::Ident::new("Engine", proc_macro2::Span::call_site());
3602        let getter = super::generate_getter_body(&name, keys).to_string();
3603        // The `require_live` flag of the `.Call()` decides whether a restored
3604        // pointer is read or refused (#1891).
3605        assert!(
3606            getter.contains(
3607                "sidecar_r_get :: < Engine > (x , 0usize , :: miniextendr_api :: externalptr :: sidecar_require_live (require_live) ,)"
3608            ),
3609            "{getter}"
3610        );
3611        assert!(!getter.contains("R_ExternalPtrAddr"), "{getter}");
3612        let setter = super::generate_setter_body(&name, keys).to_string();
3613        assert!(
3614            setter.contains("sidecar_r_check :: < Engine > (x , require_live)"),
3615            "{setter}"
3616        );
3617        // The value is validated as the field's `T` and reported as it.
3618        assert!(
3619            setter.contains("< Vec < i32 > as TryFromSexp > :: try_from_sexp (value)"),
3620            "{setter}"
3621        );
3622        assert!(setter.contains("\"'keys' must be integer\""), "{setter}");
3623        assert!(setter.contains("Some (\"Vec<i32>\")"), "{setter}");
3624        assert!(
3625            setter.contains("sidecar_r_set :: < Engine > (x , 0usize , value , require_live ,)"),
3626            "{setter}"
3627        );
3628    }
3629
3630    /// `ref` generates only the getter, `mut` only the setter.
3631    #[test]
3632    fn sidecar_access_options_select_the_accessors() {
3633        let input: syn::DeriveInput = syn::parse_str(
3634            "struct Engine { #[r_data(ref)] pub keys: Sidecar<Vec<i32>>, \
3635             #[r_data(mut)] pub(crate) note: Sidecar<String> }",
3636        )
3637        .unwrap();
3638        let info = super::parse_sidecar_info(&input, ClassSystem::Env).unwrap();
3639        assert_eq!(
3640            info.slots[0].access,
3641            super::SidecarAccess {
3642                get: true,
3643                set: false
3644            }
3645        );
3646        assert_eq!(
3647            info.slots[1].access,
3648            super::SidecarAccess {
3649                get: false,
3650                set: true
3651            }
3652        );
3653        let out = super::derive_external_ptr(input, true).unwrap().to_string();
3654        assert!(
3655            out.contains(
3656                "pub fn keys (ptr : & impl :: miniextendr_api :: externalptr :: SidecarOwner < Self >) -> Vec < i32 >"
3657            ),
3658            "{out}"
3659        );
3660        assert!(!out.contains("fn set_keys"), "{out}");
3661        assert!(
3662            out.contains(
3663                "# [allow (dead_code)] pub (crate) fn set_note (ptr : & mut impl :: miniextendr_api :: externalptr :: SidecarOwnerMut < Self > , value : String)"
3664            ),
3665            "{out}"
3666        );
3667        assert!(!out.contains("fn note ("), "{out}");
3668        // Both options together, in either order, and the hook still lists
3669        // every field.
3670        let input: syn::DeriveInput =
3671            syn::parse_str("struct Engine { #[r_data(mut, ref)] pub keys: Sidecar<Vec<i32>> }")
3672                .unwrap();
3673        let info = super::parse_sidecar_info(&input, ClassSystem::Env).unwrap();
3674        assert_eq!(
3675            info.slots[0].access,
3676            super::SidecarAccess {
3677                get: true,
3678                set: true
3679            }
3680        );
3681    }
3682
3683    /// `ref` / `mut` are refused on a plain struct field, a `Sidecar` field
3684    /// needs `#[r_data]`, and a duplicate option is an error.
3685    #[test]
3686    fn sidecar_access_options_are_checked() {
3687        let refused = |source: &str| {
3688            let input: syn::DeriveInput = syn::parse_str(source).unwrap();
3689            super::parse_sidecar_info(&input, ClassSystem::Env)
3690                .err()
3691                .map(|err| err.to_string())
3692                .unwrap_or_else(|| panic!("`{source}` must be refused"))
3693        };
3694        let message = refused("struct E { #[r_data(ref)] pub count: i32 }");
3695        assert!(
3696            message.contains("apply to `Sidecar<T>` fields only"),
3697            "{message}"
3698        );
3699        let message = refused("struct E { pub keys: Sidecar<Vec<i32>> }");
3700        assert!(message.contains("needs `#[r_data]`"), "{message}");
3701        let message = refused("struct E { #[r_data(ref, ref)] pub keys: Sidecar<Vec<i32>> }");
3702        assert!(message.contains("duplicate `ref`"), "{message}");
3703        let message = refused("struct E { #[r_data] pub keys: Sidecar }");
3704        assert!(message.contains("takes one type argument"), "{message}");
3705    }
3706
3707    /// A struct without `Sidecar` fields keeps the trait's default count and
3708    /// hook, and gets no accessors.
3709    /// The helpers of the S3 / S4 / env field methods (#1848): the field
3710    /// names, getter and setter `switch()`es over the accessors' C symbols
3711    /// (a raw identifier's R name backtick-quoted), and the classed error.
3712    #[test]
3713    fn field_syntax_helpers_switch_over_the_accessors() {
3714        let slot = |name: &str| super::SidecarSlot {
3715            name: syn::parse_str(name).unwrap(),
3716            r_name: crate::naming::ident_name(&syn::parse_str::<syn::Ident>(name).unwrap()),
3717            ty: syn::parse_quote!(i32),
3718            vis: syn::parse_quote!(pub),
3719            is_public: true,
3720            kind: super::SlotKind::ScalarInt,
3721            access: super::SidecarAccess::default(),
3722            prop_doc: None,
3723            setter_invisible: None,
3724        };
3725        let (keys, reserved) = (slot("keys"), slot("r#for"));
3726        let out =
3727            super::generate_field_syntax_helpers("Engine", &[&keys, &reserved], &[], None, false);
3728        assert!(
3729            out.contains(".rdata_fields_Engine <- c(\"keys\", \"for\")\n"),
3730            "{out}"
3731        );
3732        let getter = crate::naming::sidecar_getter_c_name("Engine", "keys");
3733        let setter = crate::naming::sidecar_setter_c_name("Engine", "for");
3734        assert!(
3735            out.contains(&format!(
3736                ".rdata_get_Engine <- function(x, name) switch(name,\n  keys = .Call({getter}, x, TRUE),"
3737            )),
3738            "{out}"
3739        );
3740        assert!(
3741            out.contains(&format!("  `for` = .Call({setter}, x, value, TRUE))\n")),
3742            "{out}"
3743        );
3744        assert!(
3745            out.contains(
3746                "function(name, env, cls) stop(errorCondition(\n  sprintf(\"`%s` is not a field of `%s`; its fields are `keys`, `for`\", name, cls),"
3747            ),
3748            "{out}"
3749        );
3750        assert!(
3751            out.contains("class = c(.rdata_error_class_Engine, \"miniextendr_no_field\"), call = .miniextendr_frame_call(env)"),
3752            "{out}"
3753        );
3754
3755        // `no_field_hint` (#2112): the hint follows the field list, each `%s`
3756        // the name asked for; any other `%`, a quote or a backslash in it is
3757        // written as given.
3758        let out = super::generate_field_syntax_helpers(
3759            "Engine",
3760            &[&keys],
3761            &[],
3762            Some("See `t$%s` or \"u\\%s\" (50% off)."),
3763            false,
3764        );
3765        assert!(
3766            out.contains(
3767                "  paste0(sprintf(\"`%s` is not a field of `%s`; its fields are `keys`\", name, cls), \". \", \
3768                 \"See `t$\", name, \"` or \\\"u\\\\\", name, \"\\\" (50% off).\"),\n"
3769            ),
3770            "{out}"
3771        );
3772    }
3773
3774    /// The helpers come with the R accessors, whatever the class system; a
3775    /// type without them gets none.
3776    #[test]
3777    fn field_syntax_helpers_follow_the_r_accessors() {
3778        for attr in ["", "#[externalptr(r6)]", "#[externalptr(s3)]"] {
3779            let input: syn::DeriveInput = syn::parse_str(&format!(
3780                "{attr} struct Engine {{ #[r_data] _r: RSidecar, #[r_data] pub count: i32 }}"
3781            ))
3782            .unwrap();
3783            let out = super::derive_external_ptr(input, true).unwrap().to_string();
3784            assert!(out.contains(".rdata_fields_Engine"), "{attr}: {out}");
3785        }
3786        let input: syn::DeriveInput =
3787            syn::parse_str("struct Engine { #[r_data] pub count: i32 }").unwrap();
3788        let out = super::derive_external_ptr(input, true).unwrap().to_string();
3789        assert!(!out.contains(".rdata_"), "{out}");
3790    }
3791
3792    #[test]
3793    fn no_sidecar_fields_emit_no_count_or_hook() {
3794        let input: syn::DeriveInput =
3795            syn::parse_str("struct Plain { #[r_data] _r: RSidecar, #[r_data] pub a: i32 }")
3796                .unwrap();
3797        let out = super::derive_external_ptr(input, true).unwrap().to_string();
3798        assert!(!out.contains("R_SLOT_COUNT"), "{out}");
3799        assert!(!out.contains("__mx_visit_sidecars"), "{out}");
3800        assert!(!out.contains("sidecar_get"), "{out}");
3801    }
3802
3803    #[test]
3804    fn r_views_in_plain_fields_are_refused_recursively() {
3805        for ty in [
3806            "List",
3807            "ListMut",
3808            "NamedList",
3809            "DataFrame",
3810            "StrVec<'static>",
3811            "RCow<'static, i32>",
3812            "RArray<i32, 2>",
3813            "RVector<f64>",
3814            "RMatrix<i32>",
3815            "RArray3D<f64>",
3816            "Factor<'static>",
3817            "FactorMut<'static>",
3818            "::miniextendr_api::list::List",
3819            "Option<List>",
3820            "Vec<Option<DataFrame>>",
3821            "HashMap<String, Vec<List>>",
3822            "Result<Vec<i32>, SEXP>",
3823            "Box<[List]>",
3824            "[Option<List>; 2]",
3825            "(i32, Vec<NamedList>)",
3826            "&'static List",
3827            "&'static str",
3828            "Option<&'static i32>",
3829            "Box<&'static [f64]>",
3830            "Arc<Mutex<Option<List>>>",
3831            "(List)",
3832        ] {
3833            let input =
3834                syn::parse_str(&format!("struct Bad {{ #[r_data] pub value: {ty} }}")).unwrap();
3835            let err = super::derive_external_ptr(input, true).unwrap_err();
3836            assert!(err.to_string().contains("is not rooted"), "{ty}: {err}");
3837            assert!(err.to_string().contains("Sidecar<"), "{ty}: {err}");
3838        }
3839    }
3840
3841    #[test]
3842    fn owned_fields_and_sidecars_keep_their_existing_classification() {
3843        for ty in [
3844            "String",
3845            "Vec<Option<i32>>",
3846            "HashMap<String, Vec<f64>>",
3847            "Preserved",
3848            "BuiltList",
3849            "BuiltDataFrame",
3850            "ProtectedStrVec",
3851            "Option<BuiltList>",
3852            "Cow<'static, str>",
3853            "PhantomData<List>",
3854        ] {
3855            let ty = syn::parse_str(ty).unwrap();
3856            assert!(!super::contains_unrooted_r_view(&ty), "{ty:?}");
3857        }
3858        for ty in [
3859            "Sidecar<List>",
3860            "Sidecar<Option<List>>",
3861            "Sidecar<DataFrame>",
3862        ] {
3863            let ty = syn::parse_str(ty).unwrap();
3864            let kind = super::slot_kind_for_type(&ty, 2, None, &syn::parse_quote!(Host)).unwrap();
3865            assert!(matches!(kind, super::SlotKind::Sidecar(2)));
3866        }
3867    }
3868
3869    /// A bare `SEXP` under `#[r_data]` is refused: nothing would root it.
3870    #[test]
3871    fn bare_sexp_sidecar_field_is_refused() {
3872        let input: syn::DeriveInput =
3873            syn::parse_str("struct Engine { #[r_data] _r: RSidecar, #[r_data] pub keys: SEXP }")
3874                .unwrap();
3875        let Err(err) = super::parse_sidecar_info(&input, ClassSystem::Env) else {
3876            panic!("a SEXP sidecar field must be refused");
3877        };
3878        let message = err.to_string();
3879        assert!(message.contains("is not rooted"), "{message}");
3880        assert!(
3881            message.contains("Declare the field as `Sidecar<SEXP>`"),
3882            "{message}"
3883        );
3884    }
3885}