Skip to main content

miniextendr_macros/
altrep_derive.rs

1//! Derive macros for ALTREP data traits.
2//!
3//! These macros auto-implement `AltrepLen` and `Alt*Data` traits for simple field-based
4//! ALTREP types, reducing boilerplate for users.
5//!
6//! # Two paths: field-based vs manual
7//!
8//! `#[derive(AltrepInteger)]` (and the `Real` / `Logical` / `Raw` / `String`
9//! / `Complex` / `List` variants) drive one of two code paths:
10//!
11//! 1. **Field-based** *(default)* — point the derive at struct fields that
12//!    hold the vector length and a constant element value:
13//!
14//!    ```ignore
15//!    #[derive(AltrepInteger)]
16//!    #[altrep(len = "n", elt = "value", class = "Repeat")]
17//!    struct Repeat { n: usize, value: i32 }
18//!    ```
19//!
20//!    The derive emits `AltrepLen`, `AltIntegerData`, `Altrep`, `AltVec`,
21//!    `RegisterAltrep`, and the `R_make_altinteger_class` registration. No
22//!    extra trait impls required.
23//!
24//! 2. **Manual** — `#[altrep(manual)]` suppresses `AltrepLen` /
25//!    `AltIntegerData` generation so you can write the element-access logic
26//!    yourself. The low-level trait impls (`Altrep`, `AltVec`, family
27//!    methods, `InferBase`) are still emitted — `#[altrep(no_lowlevel)]` is
28//!    the additional escape hatch when you also want to control `Altrep` /
29//!    `AltVec` directly.
30//!
31//! Pick by what your data looks like: a finite buffer that fits in a struct
32//! field → field-based; a computed element (`fibonacci(i)`) → manual.
33//!
34//! # Guard mode attribute
35//!
36//! The derive recognises `#[altrep(unsafe)]` / `#[altrep(rust_unwind)]` /
37//! `#[altrep(r_unwind)]` on the struct, which sets the runtime
38//! `Altrep::GUARD` const that the trampolines in `altrep_bridge` dispatch
39//! on. Default is `r_unwind` (safe for callbacks that call R APIs).
40//!
41//! # Derive validation rules
42//!
43//! Several `#[altrep(...)]` options conflict; the derive rejects illegal
44//! combinations at compile time:
45//!
46//! | Option | Allowed on families | Conflicts with |
47//! |---|---|---|
48//! | `subset` | all atomic (integer, real, logical, raw, string, complex) | `dataptr`, `list` |
49//! | `dataptr` | integer, real, logical, raw, string, complex | `subset`, `list` |
50//! | `serialize` | all | (none) |
51//! | `manual` | all | (none — turns off `AltrepLen`/`Alt*Data` generation) |
52//! | `no_lowlevel` | all | (none — also suppresses the low-level trait impls) |
53//!
54//! The list family rejects `dataptr` and `subset` — list elements are
55//! arbitrary SEXPs, so there is no contiguous buffer to expose.
56
57use proc_macro2::TokenStream;
58use quote::quote;
59use syn::spanned::Spanned;
60
61/// Per-family configuration controlling low-level code generation for an ALTREP type family.
62///
63/// Each ALTREP family (integer, real, logical, raw, string, complex, list) has a distinct
64/// set of runtime macros for trait implementations and different capabilities (e.g., only
65/// some families support `dataptr` or `subset`). This struct captures those differences
66/// so [`AltrepAttrs::generate_lowlevel`] can emit the correct code.
67struct AltrepFamilyConfig<'a> {
68    /// Human-readable family name used in option-validation error messages
69    /// (e.g., `"AltrepInteger"`).
70    family_name: &'a str,
71    /// If this family supports a typed `dataptr` materialization macro, the tuple contains:
72    /// - The macro name (e.g., `"__impl_altvec_dataptr"`)
73    /// - An optional element type token stream (e.g., `i32` for integer, `f64` for real)
74    ///
75    /// `None` means this family does not have a typed dataptr macro (e.g., list).
76    dataptr_macro: Option<(&'a str, Option<TokenStream>)>,
77    /// Whether this family supports the string-specific dataptr materialization macro
78    /// (`__impl_altvec_string_dataptr`). Only `true` for the String family.
79    string_dataptr: bool,
80    /// Whether this family supports the `subset` option for `Extract_subset` method
81    /// registration. `false` for List (which rejects `subset` and `dataptr`).
82    subset: bool,
83    /// Name of the internal macro for type-specific method implementations
84    /// (e.g., `"__impl_altinteger_methods"` for integer).
85    methods_macro: &'a str,
86    /// Name of the `impl_inferbase_*!` macro that provides `InferBase` for this family
87    /// (e.g., `"impl_inferbase_integer"`).
88    inferbase_macro: &'a str,
89    /// Default guard mode for this family when no explicit guard is specified.
90    /// String family uses `RUnwind` because elt/dataptr call R APIs (Rf_mkCharLenCE).
91    /// All families now default to `RUnwind`.
92    default_guard: &'a str,
93    /// Family-specific materializing dataptr macro (e.g.
94    /// `"__impl_altvec_integer_dataptr"`). The default/serialize arms use it
95    /// to install the trivial `AltrepDataptr` + materializing `AltVec`.
96    materializing_dataptr_macro: &'a str,
97}
98
99/// Parsed `#[altrep(...)]` attributes controlling ALTREP derive code generation.
100///
101/// These attributes are placed on the struct and parsed by all ALTREP derive macros
102/// (`AltrepInteger`, `AltrepReal`, etc.) to customize the generated trait implementations.
103///
104/// # Supported `#[altrep(...)]` keys
105///
106/// | Key | Type | Description |
107/// |-----|------|-------------|
108/// | `len = "field"` | `String` | Name of the struct field that holds the vector length. Auto-detected if a field is named `len` or `length`. |
109/// | `elt = "field"` | `String` | Name of the struct field to return as the element value (produces a constant-value vector). If omitted, the default `elt()` returns `NA` / `NaN` / `0` / `None` depending on the family. |
110/// | `manual` | Flag | Skip automatic `AltrepLen` and `Alt*Data` trait generation. Use when you want to write those trait impls by hand (e.g., for custom `elt` logic, `no_na`, `sum`, etc.). The low-level trait impls (`Altrep`, `AltVec`, family methods, `InferBase`) are still emitted automatically — you do **not** need to write them yourself. Use `no_lowlevel` as an additional escape hatch if you also want to suppress those. |
111/// | `no_lowlevel` | Flag | Suppress the automatic low-level trait impls. Use this when you want to provide your own `Altrep`, `AltVec`, and family-specific trait implementations. |
112/// | `dataptr` | Flag | Enable `Dataptr` method registration, allowing R to get a direct pointer to the underlying data. Mutually exclusive with `subset`. Not supported for List. |
113/// | `serialize` | Flag | Enable `Serialized_state` and `Unserialize` method registration for ALTREP serialization support. |
114/// | `subset` | Flag | Enable `Extract_subset` method registration. Mutually exclusive with `dataptr`. Supported on all atomic families. Not supported for List. |
115/// | `unsafe` | Flag | Set guard mode to `Unsafe` -- no panic protection on ALTREP callbacks. |
116/// | `rust_unwind` | Flag | Set guard mode to `RustUnwind` -- uses `catch_unwind` only (unsafe if callbacks call R APIs). |
117/// | `r_unwind` | Flag | Set guard mode to `RUnwind` (default) -- uses `with_r_unwind_protect` for safe R API calls. |
118/// | `class = "name"` | `String` | Override the ALTREP class name (default: struct name). |
119struct AltrepAttrs {
120    /// Field name containing the vector length, set via `#[altrep(len = "field")]`.
121    /// If `None`, auto-detection looks for fields named `len` or `length`.
122    len_field: Option<syn::Ident>,
123    /// Field name for constant-value element access, set via `#[altrep(elt = "field")]`.
124    /// When set, `elt()` returns `self.{field}` for every index.
125    elt_field: Option<syn::Ident>,
126    /// Field name for delegated element access, set via `#[altrep(elt_delegate = "field")]`.
127    /// When set, `elt()` calls `self.{field}.elt(i)`, delegating to the inner type's
128    /// `AltIntegerData`/`AltRealData`/etc. implementation. Useful for wrapper types
129    /// around `StreamingIntData`, `StreamingRealData`, etc.
130    elt_delegate: Option<syn::Ident>,
131    /// Whether to generate the low-level trait impls (`Altrep`, `AltVec`,
132    /// family methods, `InferBase`). Defaults to `true`. Set to `false` by
133    /// `#[altrep(no_lowlevel)]`.
134    generate_lowlevel: bool,
135    /// Collected option flags (`dataptr`, `serialize`, `subset`) passed to the runtime macro.
136    lowlevel_options: Vec<syn::Ident>,
137    /// Guard mode override for ALTREP trampoline callbacks. Maps to `AltrepGuard` variants:
138    /// - `Unsafe` -- no protection
139    /// - `RustUnwind` -- `catch_unwind` only
140    /// - `RUnwind` -- `with_r_unwind_protect` (default)
141    guard: Option<syn::Ident>,
142    /// Override the ALTREP class name. Default: struct name.
143    class_name: Option<String>,
144    /// Manual mode: skip `AltrepLen` and `Alt*Data` generation. User provides their own.
145    /// Set by `#[altrep(manual)]`. Lowlevel traits + registration still generated.
146    manual: bool,
147}
148
149impl AltrepAttrs {
150    /// Parses all `#[altrep(...)]` attributes from a derive input struct.
151    ///
152    /// Multiple `#[altrep(...)]` attributes are supported and their contents are merged.
153    /// Unknown keys produce a compile error.
154    ///
155    /// # Errors
156    ///
157    /// Returns `Err` if an `#[altrep(...)]` attribute has malformed syntax or contains
158    /// an unknown key.
159    fn parse(input: &syn::DeriveInput) -> syn::Result<Self> {
160        let mut len_field = None;
161        let mut elt_field = None;
162        let mut elt_delegate = None;
163        let mut generate_lowlevel = true; // Default: generate
164        let mut lowlevel_options = Vec::new();
165        let mut guard = None;
166        let mut class_name = None;
167        let mut manual = false;
168
169        for attr in &input.attrs {
170            if !attr.path().is_ident("altrep") {
171                continue;
172            }
173
174            attr.parse_nested_meta(|meta| {
175                if meta.path.is_ident("len") {
176                    let _: syn::Token![=] = meta.input.parse()?;
177                    let field: syn::LitStr = meta.input.parse()?;
178                    len_field = Some(syn::Ident::new(&field.value(), field.span()));
179                } else if meta.path.is_ident("elt") {
180                    let _: syn::Token![=] = meta.input.parse()?;
181                    let field: syn::LitStr = meta.input.parse()?;
182                    elt_field = Some(syn::Ident::new(&field.value(), field.span()));
183                } else if meta.path.is_ident("elt_delegate") {
184                    let _: syn::Token![=] = meta.input.parse()?;
185                    let field: syn::LitStr = meta.input.parse()?;
186                    elt_delegate = Some(syn::Ident::new(&field.value(), field.span()));
187                } else if meta.path.is_ident("no_lowlevel") {
188                    generate_lowlevel = false;
189                } else if meta.path.is_ident("manual") {
190                    manual = true;
191                } else if meta.path.is_ident("class") {
192                    let _: syn::Token![=] = meta.input.parse()?;
193                    let name: syn::LitStr = meta.input.parse()?;
194                    class_name = Some(name.value());
195                } else if meta.path.is_ident("dataptr") {
196                    lowlevel_options.push(syn::Ident::new("dataptr", meta.path.span()));
197                } else if meta.path.is_ident("serialize") {
198                    lowlevel_options.push(syn::Ident::new("serialize", meta.path.span()));
199                } else if meta.path.is_ident("subset") {
200                    lowlevel_options.push(syn::Ident::new("subset", meta.path.span()));
201                } else if meta.path.is_ident("r#unsafe") || meta.path.is_ident("unsafe") {
202                    guard = Some(syn::Ident::new("Unsafe", meta.path.span()));
203                } else if meta.path.is_ident("rust_unwind") {
204                    guard = Some(syn::Ident::new("RustUnwind", meta.path.span()));
205                } else if meta.path.is_ident("r_unwind") {
206                    guard = Some(syn::Ident::new("RUnwind", meta.path.span()));
207                } else {
208                    return Err(meta.error(
209                        "unknown #[altrep(...)] attribute; expected one of: \
210                         `len`, `elt`, `elt_delegate`, `manual`, `no_lowlevel`, `class`, \
211                         `dataptr`, `serialize`, `subset`, `unsafe`, \
212                         `rust_unwind`, `r_unwind`",
213                    ));
214                }
215                Ok(())
216            })?;
217        }
218
219        Ok(Self {
220            len_field,
221            elt_field,
222            elt_delegate,
223            generate_lowlevel,
224            lowlevel_options,
225            guard,
226            class_name,
227            manual,
228        })
229    }
230
231    /// Returns the length field identifier, either from the explicit `len = "..."` attribute
232    /// or by auto-detecting a field named `len` or `length` on the struct.
233    ///
234    /// # Errors
235    ///
236    /// Returns `Err` if the input is not a struct, or if no length field was specified
237    /// and auto-detection fails.
238    fn get_len_field(&self, input: &syn::DeriveInput) -> syn::Result<syn::Ident> {
239        if let Some(ref field) = self.len_field {
240            return Ok(field.clone());
241        }
242
243        // Try to auto-detect: look for field named "len" or "length"
244        let fields = match &input.data {
245            syn::Data::Struct(data_struct) => &data_struct.fields,
246            _ => {
247                return Err(syn::Error::new(
248                    input.span(),
249                    "Altrep derive only supports structs",
250                ));
251            }
252        };
253
254        for field in fields {
255            if let Some(ident) = &field.ident
256                && (ident == "len" || ident == "length")
257            {
258                return Ok(ident.clone());
259            }
260        }
261
262        Err(syn::Error::new(
263            input.span(),
264            "no length field found; specify with #[altrep(len = \"field_name\")]",
265        ))
266    }
267
268    /// Returns `true` if a non-default guard mode is set (i.e., `Unsafe` or `RustUnwind`).
269    ///
270    /// The default guard is `RUnwind`. Non-default guards (e.g., `RustUnwind`,
271    /// `Unsafe`) suppress the materializing-dataptr `AltVec` impl on the
272    /// no-option arm (behaviour preserved from the pre-#711 expanded path).
273    fn has_non_default_guard(&self) -> bool {
274        match &self.guard {
275            Some(g) => g != "RUnwind",
276            None => false,
277        }
278    }
279
280    /// Validates that the requested `#[altrep(...)]` option flags are compatible with
281    /// the given ALTREP type family.
282    ///
283    /// Enforces two rules:
284    /// 1. `subset` is only valid for families where `supports_subset` is `true`.
285    /// 2. `dataptr` and `subset` are mutually exclusive.
286    ///
287    /// # Arguments
288    ///
289    /// * `family` -- A human-readable family name used in error messages (e.g., `"AltrepList"`).
290    /// * `supports_subset` -- Whether this family supports the `Extract_subset` method.
291    ///
292    /// # Errors
293    ///
294    /// Returns `Err` with a span pointing to the offending option identifier.
295    fn validate_options(&self, family: &str, supports_subset: bool) -> syn::Result<()> {
296        let has_dataptr = self.lowlevel_options.iter().any(|o| o == "dataptr");
297        let has_subset = self.lowlevel_options.iter().any(|o| o == "subset");
298
299        if has_subset && !supports_subset {
300            return Err(syn::Error::new(
301                self.lowlevel_options
302                    .iter()
303                    .find(|o| *o == "subset")
304                    .unwrap()
305                    .span(),
306                format!("`subset` is not supported for {family}"),
307            ));
308        }
309
310        if has_dataptr && has_subset {
311            return Err(syn::Error::new(
312                self.lowlevel_options
313                    .iter()
314                    .find(|o| *o == "subset")
315                    .unwrap()
316                    .span(),
317                "`dataptr` and `subset` are mutually exclusive",
318            ));
319        }
320
321        Ok(())
322    }
323
324    /// Generates low-level ALTREP trait implementation code for a given type family.
325    ///
326    /// Emits the underlying trait-impl macros directly (Path (a) from #682,
327    /// landed via #711/#933), reproducing the `impl_alt<family>_from_data!`
328    /// arm expansions from the proc-macro with no declarative-macro hop. The
329    /// four items every arm produces are:
330    ///
331    /// 1. `__impl_altrep_base!(Ty, <guard>[, with_serialize])` — `impl Altrep`.
332    /// 2. an `AltVec` impl, one of:
333    ///    - materializing (`materializing_dataptr_macro`) — default/serialize arms,
334    ///    - direct dataptr (`__impl_altvec_dataptr!(Ty, <elem>)`; string routes
335    ///      through `__impl_altvec_string_dataptr!`) — `dataptr` arm,
336    ///    - subset (`__impl_altvec_extract_subset!`) — `subset` arm,
337    /// 3. `methods_macro!(Ty)` — the family `Alt<Family>` impl,
338    /// 4. `inferbase_macro!(Ty)` — `impl InferBase`.
339    ///
340    /// The guard is honoured uniformly (the retired declarative-macro
341    /// delegation only handled the default `RUnwind` guard; non-default guards
342    /// used a separate expanded path) and option combinations are validated
343    /// here at the derive call site rather than 7 hops deep in macro
344    /// expansion.
345    ///
346    /// # Arguments
347    ///
348    /// * `name` -- The struct identifier.
349    /// * `family` -- The family-specific configuration controlling which macros to emit.
350    ///
351    /// # Returns
352    ///
353    /// A token stream containing the macro invocations, or an empty stream if
354    /// `no_lowlevel` was specified.
355    ///
356    /// # Errors
357    ///
358    /// Returns `Err` if option validation fails (e.g., `subset` on an
359    /// unsupported family, or `dataptr` combined with `subset`).
360    fn generate_lowlevel(
361        &self,
362        name: &syn::Ident,
363        generics: &syn::Generics,
364        family: &AltrepFamilyConfig,
365    ) -> syn::Result<TokenStream> {
366        let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
367        let AltrepFamilyConfig {
368            family_name,
369            ref dataptr_macro,
370            string_dataptr,
371            subset,
372            methods_macro,
373            inferbase_macro,
374            default_guard,
375            materializing_dataptr_macro,
376        } = *family;
377
378        if !self.generate_lowlevel {
379            return Ok(quote! {});
380        }
381
382        self.validate_options(family_name, subset)?;
383
384        let has_serialize = self.lowlevel_options.iter().any(|o| o == "serialize");
385        let has_dataptr = self.lowlevel_options.iter().any(|o| o == "dataptr");
386        let has_subset = self.lowlevel_options.iter().any(|o| o == "subset");
387
388        let guard = self
389            .guard
390            .as_ref()
391            .cloned()
392            .unwrap_or_else(|| syn::Ident::new(default_guard, proc_macro2::Span::call_site()));
393
394        // 1. Altrep base. The declarative macro's default/dataptr/subset arms use
395        //    `__impl_altrep_base!($ty)` (default RUnwind guard); the `*serialize`
396        //    arms use `__impl_altrep_base!($ty, with_serialize)`. We additionally
397        //    thread an explicit guard so non-default guards no longer need a
398        //    separate code path.
399        let base_impl = if has_serialize {
400            quote! { ::miniextendr_api::__impl_altrep_base!(#name #ty_generics, #guard, with_serialize); }
401        } else {
402            quote! { ::miniextendr_api::__impl_altrep_base!(#name #ty_generics, #guard); }
403        };
404
405        // 2. AltVec impl. Mirrors the per-arm choice in `impl_alt<family>_from_data!`:
406        //    - `dataptr` arm -> direct `__impl_altvec_dataptr!($ty, $elem)`; the
407        //      string family has no typed contiguous pointer, so its `dataptr`
408        //      arm routes through `__impl_altvec_string_dataptr!` (whole-vector
409        //      STRSXP materialization — same emission as its default arm),
410        //    - `subset` arm  -> `__impl_altvec_extract_subset!`,
411        //    - default / serialize arms -> the family materializing dataptr macro
412        //      (e.g. `__impl_altvec_integer_dataptr!`), which installs a trivial
413        //      `AltrepDataptr` returning `None` then delegates to
414        //      `__impl_altvec_dataptr!`.
415        let vec_impl = if has_dataptr {
416            if string_dataptr {
417                quote! { ::miniextendr_api::__impl_altvec_string_dataptr!(#name #ty_generics); }
418            } else {
419                let (macro_name, elem_ty) = dataptr_macro
420                    .as_ref()
421                    .expect("emit_direct family must define a typed dataptr macro");
422                let dp_macro = syn::Ident::new(macro_name, proc_macro2::Span::call_site());
423                let elem = elem_ty
424                    .as_ref()
425                    .expect("emit_direct dataptr macro must carry an element type");
426                quote! { ::miniextendr_api::#dp_macro!(#name #ty_generics, #elem); }
427            }
428        } else if has_subset {
429            quote! { ::miniextendr_api::__impl_altvec_extract_subset!(#name #ty_generics); }
430        } else if self.has_non_default_guard() {
431            // Behaviour-preservation: the pre-#711 expanded path (the only path
432            // that handled non-default guards) emitted a *bare* `impl AltVec`
433            // for the no-dataptr/no-subset case — NOT the materializing macro.
434            // Match that exactly so this migration is a pure de-indirection
435            // with no semantic drift. (No production fixture combines a
436            // non-default guard with an ALTREP derive, so this branch is
437            // exercised only by the proc-macro unit tests.)
438            quote! { impl #impl_generics ::miniextendr_api::altrep_traits::AltVec for #name #ty_generics #where_clause {} }
439        } else {
440            let mat_macro =
441                syn::Ident::new(materializing_dataptr_macro, proc_macro2::Span::call_site());
442            quote! { ::miniextendr_api::#mat_macro!(#name #ty_generics); }
443        };
444
445        // 3. Family method impl (`impl Alt<Family>`).
446        let methods_ident = syn::Ident::new(methods_macro, proc_macro2::Span::call_site());
447        let methods_impl = quote! { ::miniextendr_api::#methods_ident!(#name #ty_generics); };
448
449        // 4. InferBase.
450        let inferbase_ident = syn::Ident::new(inferbase_macro, proc_macro2::Span::call_site());
451        let inferbase_impl = quote! { ::miniextendr_api::#inferbase_ident!(#name #ty_generics); };
452
453        Ok(quote! {
454            #base_impl
455            #vec_impl
456            #methods_impl
457            #inferbase_impl
458        })
459    }
460}
461
462/// Generates an `impl AltrepLen for T` block that delegates to a named struct field.
463///
464/// The generated implementation returns `self.{len_field}` cast to `usize` as the
465/// ALTREP vector length.
466///
467/// # Arguments
468///
469/// * `name` -- The struct identifier.
470/// * `generics` -- Generic parameters for the struct.
471/// * `len_field` -- The identifier of the field that holds the length value.
472fn generate_altrep_len(
473    name: &syn::Ident,
474    generics: &syn::Generics,
475    len_field: &syn::Ident,
476) -> TokenStream {
477    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
478
479    quote! {
480        impl #impl_generics ::miniextendr_api::altrep_data::AltrepLen for #name #ty_generics #where_clause {
481            fn len(&self) -> usize {
482                self.#len_field
483            }
484        }
485    }
486}
487
488/// Shared implementation for all non-list ALTREP derive macros.
489///
490/// Generates three items:
491/// 1. `impl AltrepLen` -- delegates to the detected/specified length field
492/// 2. `impl Alt*Data` -- the family-specific data trait with an `elt()` method
493/// 3. Low-level trait impls via [`AltrepAttrs::generate_lowlevel`]
494///
495/// # Arguments
496///
497/// * `input` -- The `DeriveInput` from the proc-macro.
498/// * `data_trait_path` -- The fully qualified path to the data trait
499///   (e.g., `::miniextendr_api::altrep_data::AltIntegerData`).
500/// * `gen_elt_impl` -- A closure that receives the optional `elt_field` and returns
501///   a token stream for the `fn elt(...)` method body. If `elt_field` is `Some`, the
502///   closure typically returns `self.{field}`; if `None`, it returns a family-appropriate
503///   default (`NA_INTEGER`, `f64::NAN`, `0u8`, `Logical::Na`, `None`, or `Rcomplex { NAN, NAN }`).
504/// * `family` -- The [`AltrepFamilyConfig`] for this type family.
505///
506/// # Errors
507///
508/// Returns `Err` if attribute parsing fails, no length field can be found, or
509/// option validation fails.
510fn derive_altrep_generic(
511    input: syn::DeriveInput,
512    data_trait_path: TokenStream,
513    gen_elt_impl: impl FnOnce(Option<&syn::Ident>, Option<&syn::Ident>) -> TokenStream,
514    family: &AltrepFamilyConfig,
515) -> syn::Result<TokenStream> {
516    let name = &input.ident;
517    let generics = &input.generics;
518    let attrs = AltrepAttrs::parse(&input)?;
519
520    // In manual mode, skip AltrepLen + data trait generation — user provides their own.
521    let data_traits = if attrs.manual {
522        quote! {}
523    } else {
524        let len_field = attrs.get_len_field(&input)?;
525        let altrep_len_impl = generate_altrep_len(name, generics, &len_field);
526        let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
527        let elt_impl = gen_elt_impl(attrs.elt_field.as_ref(), attrs.elt_delegate.as_ref());
528        quote! {
529            #altrep_len_impl
530            impl #impl_generics #data_trait_path for #name #ty_generics #where_clause {
531                #elt_impl
532            }
533        }
534    };
535
536    let lowlevel_impl = attrs.generate_lowlevel(name, generics, family)?;
537
538    // Generate full registration (TypedExternal, AltrepClass, RegisterAltrep, IntoR,
539    // linkme entry, Ref/Mut).
540    let class_name = attrs
541        .class_name
542        .as_deref()
543        .unwrap_or(&name.to_string())
544        .to_string();
545    let registration_impl =
546        crate::altrep::generate_direct_altrep_registration(name, generics, &class_name)?;
547
548    Ok(quote! {
549        #data_traits
550        #lowlevel_impl
551        #registration_impl
552    })
553}
554
555/// Derive macro entry point for `AltrepInteger`.
556///
557/// Auto-implements `AltrepLen` and `AltIntegerData` for a struct with a length field.
558/// The `elt()` method returns `self.{elt_field}` as `i32` if `#[altrep(elt = "...")]`
559/// is specified, or `NA_INTEGER` by default.
560///
561/// Supports `#[altrep(dataptr)]` for direct `i32` data pointer access and
562/// `#[altrep(subset)]` for `Extract_subset`.
563pub fn derive_altrep_integer(input: syn::DeriveInput) -> syn::Result<TokenStream> {
564    derive_altrep_generic(
565        input,
566        quote! { ::miniextendr_api::altrep_data::AltIntegerData },
567        |elt_field, elt_delegate| {
568            if let Some(d) = elt_delegate {
569                quote! { fn elt(&self, i: usize) -> i32 { self.#d.elt(i) } }
570            } else if let Some(f) = elt_field {
571                quote! { fn elt(&self, _i: usize) -> i32 { self.#f } }
572            } else {
573                quote! { fn elt(&self, _i: usize) -> i32 { ::miniextendr_api::altrep_traits::NA_INTEGER } }
574            }
575        },
576        &AltrepFamilyConfig {
577            family_name: "AltrepInteger",
578            dataptr_macro: Some(("__impl_altvec_dataptr", Some(quote! { i32 }))),
579            string_dataptr: false,
580            subset: true,
581            methods_macro: "__impl_altinteger_methods",
582            inferbase_macro: "impl_inferbase_integer",
583            default_guard: "RUnwind",
584            materializing_dataptr_macro: "__impl_altvec_integer_dataptr",
585        },
586    )
587}
588
589/// Derive macro entry point for `AltrepReal`.
590///
591/// Auto-implements `AltrepLen` and `AltRealData` for a struct with a length field.
592/// The `elt()` method returns `self.{elt_field}` as `f64` if `#[altrep(elt = "...")]`
593/// is specified, or `f64::NAN` (R's `NA_real_`) by default.
594///
595/// Supports `#[altrep(dataptr)]` for direct `f64` data pointer access and
596/// `#[altrep(subset)]` for `Extract_subset`.
597pub fn derive_altrep_real(input: syn::DeriveInput) -> syn::Result<TokenStream> {
598    derive_altrep_generic(
599        input,
600        quote! { ::miniextendr_api::altrep_data::AltRealData },
601        |elt_field, elt_delegate| {
602            if let Some(d) = elt_delegate {
603                quote! { fn elt(&self, i: usize) -> f64 { self.#d.elt(i) } }
604            } else if let Some(f) = elt_field {
605                quote! { fn elt(&self, _i: usize) -> f64 { self.#f } }
606            } else {
607                quote! { fn elt(&self, _i: usize) -> f64 { f64::NAN } }
608            }
609        },
610        &AltrepFamilyConfig {
611            family_name: "AltrepReal",
612            dataptr_macro: Some(("__impl_altvec_dataptr", Some(quote! { f64 }))),
613            string_dataptr: false,
614            subset: true,
615            methods_macro: "__impl_altreal_methods",
616            inferbase_macro: "impl_inferbase_real",
617            default_guard: "RUnwind",
618            materializing_dataptr_macro: "__impl_altvec_real_dataptr",
619        },
620    )
621}
622
623/// Derive macro entry point for `AltrepLogical`.
624///
625/// Auto-implements `AltrepLen` and `AltLogicalData` for a struct with a length field.
626/// The `elt()` method returns `self.{elt_field}.into()` as `Logical` if
627/// `#[altrep(elt = "...")]` is specified, or `Logical::Na` by default.
628///
629/// Supports `#[altrep(dataptr)]` for direct `i32` data pointer access (logicals are
630/// stored as `i32` in R) and `#[altrep(subset)]` for `Extract_subset`.
631pub fn derive_altrep_logical(input: syn::DeriveInput) -> syn::Result<TokenStream> {
632    derive_altrep_generic(
633        input,
634        quote! { ::miniextendr_api::altrep_data::AltLogicalData },
635        |elt_field, elt_delegate| {
636            if let Some(d) = elt_delegate {
637                quote! { fn elt(&self, i: usize) -> ::miniextendr_api::altrep_data::Logical { self.#d.elt(i) } }
638            } else if let Some(f) = elt_field {
639                quote! { fn elt(&self, _i: usize) -> ::miniextendr_api::altrep_data::Logical { self.#f.into() } }
640            } else {
641                quote! { fn elt(&self, _i: usize) -> ::miniextendr_api::altrep_data::Logical { ::miniextendr_api::altrep_data::Logical::Na } }
642            }
643        },
644        &AltrepFamilyConfig {
645            family_name: "AltrepLogical",
646            dataptr_macro: Some(("__impl_altvec_dataptr", Some(quote! { i32 }))),
647            string_dataptr: false,
648            subset: true,
649            methods_macro: "__impl_altlogical_methods",
650            inferbase_macro: "impl_inferbase_logical",
651            default_guard: "RUnwind",
652            materializing_dataptr_macro: "__impl_altvec_logical_dataptr",
653        },
654    )
655}
656
657/// Derive macro entry point for `AltrepRaw`.
658///
659/// Auto-implements `AltrepLen` and `AltRawData` for a struct with a length field.
660/// The `elt()` method returns `self.{elt_field}` as `u8` if `#[altrep(elt = "...")]`
661/// is specified, or `0u8` by default.
662///
663/// Supports `#[altrep(dataptr)]` for direct `u8` data pointer access and
664/// `#[altrep(subset)]` for `Extract_subset`.
665pub fn derive_altrep_raw(input: syn::DeriveInput) -> syn::Result<TokenStream> {
666    derive_altrep_generic(
667        input,
668        quote! { ::miniextendr_api::altrep_data::AltRawData },
669        |elt_field, elt_delegate| {
670            if let Some(d) = elt_delegate {
671                quote! { fn elt(&self, i: usize) -> u8 { self.#d.elt(i) } }
672            } else if let Some(f) = elt_field {
673                quote! { fn elt(&self, _i: usize) -> u8 { self.#f } }
674            } else {
675                quote! { fn elt(&self, _i: usize) -> u8 { 0 } }
676            }
677        },
678        &AltrepFamilyConfig {
679            family_name: "AltrepRaw",
680            dataptr_macro: Some(("__impl_altvec_dataptr", Some(quote! { u8 }))),
681            string_dataptr: false,
682            subset: true,
683            methods_macro: "__impl_altraw_methods",
684            inferbase_macro: "impl_inferbase_raw",
685            default_guard: "RUnwind",
686            materializing_dataptr_macro: "__impl_altvec_raw_dataptr",
687        },
688    )
689}
690
691/// Derive macro entry point for `AltrepString`.
692///
693/// Auto-implements `AltrepLen` and `AltStringData` for a struct with a length field.
694/// The `elt()` method returns `Some(self.{elt_field}.as_ref())` as `Option<&str>` if
695/// `#[altrep(elt = "...")]` is specified, or `None` (R's `NA_character_`) by default.
696///
697/// String ALTREP supports `#[altrep(dataptr)]` for materialized `STRSXP` dataptr
698/// (via `__impl_altvec_string_dataptr`) and `#[altrep(subset)]` for `Extract_subset`.
699/// Note: String dataptr materializes the entire vector into a cached `STRSXP` in the
700/// data2 slot.
701pub fn derive_altrep_string(input: syn::DeriveInput) -> syn::Result<TokenStream> {
702    derive_altrep_generic(
703        input,
704        quote! { ::miniextendr_api::altrep_data::AltStringData },
705        |elt_field, elt_delegate| {
706            if let Some(d) = elt_delegate {
707                quote! { fn elt(&self, i: usize) -> Option<&str> { self.#d.elt(i) } }
708            } else if let Some(f) = elt_field {
709                quote! { fn elt(&self, _i: usize) -> Option<&str> { Some(self.#f.as_ref()) } }
710            } else {
711                quote! { fn elt(&self, _i: usize) -> Option<&str> { None } }
712            }
713        },
714        &AltrepFamilyConfig {
715            family_name: "AltrepString",
716            dataptr_macro: None,
717            string_dataptr: true,
718            subset: true,
719            methods_macro: "__impl_altstring_methods",
720            inferbase_macro: "impl_inferbase_string",
721            // String elt calls Rf_mkCharLenCE; dataptr calls Rf_allocVector + SET_STRING_ELT.
722            // These R API calls can longjmp — must use RUnwind.
723            default_guard: "RUnwind",
724            // String has no typed contiguous dataptr; its default/dataptr arms
725            // both route through `__impl_altvec_string_dataptr!`.
726            materializing_dataptr_macro: "__impl_altvec_string_dataptr",
727        },
728    )
729}
730
731/// Derive macro entry point for `AltrepComplex`.
732///
733/// Auto-implements `AltrepLen` and `AltComplexData` for a struct with a length field.
734/// The `elt()` method returns `self.{elt_field}` as `Rcomplex` if
735/// `#[altrep(elt = "...")]` is specified, or `Rcomplex { r: NAN, i: NAN }` by default.
736///
737/// Supports `#[altrep(dataptr)]` for direct `Rcomplex` data pointer access and
738/// `#[altrep(subset)]` for `Extract_subset`.
739pub fn derive_altrep_complex(input: syn::DeriveInput) -> syn::Result<TokenStream> {
740    derive_altrep_generic(
741        input,
742        quote! { ::miniextendr_api::altrep_data::AltComplexData },
743        |elt_field, elt_delegate| {
744            if let Some(d) = elt_delegate {
745                quote! { fn elt(&self, i: usize) -> ::miniextendr_api::Rcomplex { self.#d.elt(i) } }
746            } else if let Some(f) = elt_field {
747                quote! { fn elt(&self, _i: usize) -> ::miniextendr_api::Rcomplex { self.#f } }
748            } else {
749                quote! {
750                    fn elt(&self, _i: usize) -> ::miniextendr_api::Rcomplex {
751                        ::miniextendr_api::Rcomplex { r: f64::NAN, i: f64::NAN }
752                    }
753                }
754            }
755        },
756        &AltrepFamilyConfig {
757            family_name: "AltrepComplex",
758            dataptr_macro: Some((
759                "__impl_altvec_dataptr",
760                Some(quote! { ::miniextendr_api::Rcomplex }),
761            )),
762            string_dataptr: false,
763            subset: true,
764            methods_macro: "__impl_altcomplex_methods",
765            inferbase_macro: "impl_inferbase_complex",
766            default_guard: "RUnwind",
767            materializing_dataptr_macro: "__impl_altvec_complex_dataptr",
768        },
769    )
770}
771
772/// Derive macro entry point for `AltrepList`.
773///
774/// Auto-implements `AltrepLen` and `AltListData` for a struct with a length field.
775/// The `elt()` method returns `self.{elt_field}[i]` as `SEXP` if
776/// `#[altrep(elt = "...")]` is specified (the field should be indexable and return `SEXP`),
777/// or `R_NilValue` by default.
778///
779/// List ALTREP does **not** support `#[altrep(dataptr)]` or `#[altrep(subset)]` -- both
780/// are rejected at compile time. `#[altrep(serialize)]` is supported.
781pub fn derive_altrep_list(input: syn::DeriveInput) -> syn::Result<TokenStream> {
782    let name = &input.ident;
783    let generics = &input.generics;
784    let attrs = AltrepAttrs::parse(&input)?;
785    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
786
787    // In manual mode, skip AltrepLen + AltListData generation.
788    let data_traits = if attrs.manual {
789        quote! {}
790    } else {
791        let len_field = attrs.get_len_field(&input)?;
792        let altrep_len_impl = generate_altrep_len(name, generics, &len_field);
793        let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
794
795        let elt_impl = if let Some(ref elt_field) = attrs.elt_field {
796            quote! {
797                fn elt(&self, i: usize) -> ::miniextendr_api::SEXP {
798                    self.#elt_field[i]
799                }
800            }
801        } else {
802            quote! {
803                fn elt(&self, _i: usize) -> ::miniextendr_api::SEXP {
804                    ::miniextendr_api::SEXP::nil()
805                }
806            }
807        };
808
809        quote! {
810            #altrep_len_impl
811            impl #impl_generics ::miniextendr_api::altrep_data::AltListData for #name #ty_generics #where_clause {
812                #elt_impl
813            }
814        }
815    };
816
817    // List does not support dataptr or subset
818    for opt in &attrs.lowlevel_options {
819        if opt == "dataptr" || opt == "subset" {
820            return Err(syn::Error::new(
821                opt.span(),
822                format!("`{opt}` is not supported for AltrepList"),
823            ));
824        }
825    }
826
827    let has_serialize = attrs.lowlevel_options.iter().any(|o| o == "serialize");
828
829    let lowlevel_impl = if !attrs.generate_lowlevel {
830        quote! {}
831    } else {
832        // Direct emit (#933). The non-serialize arm inlines the body of the
833        // retired `impl_altlist_from_data!` delegation: `__impl_altrep_base!`
834        // with an explicit guard is token-identical to that macro's
835        // hand-written `Altrep` impl, and the `elt` body matches its
836        // `altrep_extract_ref` path. The serialize arm keeps its historical
837        // `altrep_data1_as` elt body — switching it to `AltrepExtract` would
838        // change behaviour for types that override extraction.
839        let guard = attrs
840            .guard
841            .as_ref()
842            .cloned()
843            .unwrap_or_else(|| syn::Ident::new("RUnwind", proc_macro2::Span::call_site()));
844        let base_impl = if has_serialize {
845            quote! { ::miniextendr_api::__impl_altrep_base!(#name #ty_generics, #guard, with_serialize); }
846        } else {
847            quote! { ::miniextendr_api::__impl_altrep_base!(#name #ty_generics, #guard); }
848        };
849        let elt_impl = if has_serialize {
850            quote! {
851                fn elt(x: ::miniextendr_api::SEXP, i: ::miniextendr_api::R_xlen_t) -> ::miniextendr_api::SEXP {
852                    unsafe { ::miniextendr_api::altrep_data1_as::<#name #ty_generics>(x) }
853                        .map(|d| <#name #ty_generics as ::miniextendr_api::altrep_data::AltListData>::elt(&*d, i.max(0) as usize))
854                        .unwrap_or(::miniextendr_api::SEXP::nil())
855                }
856            }
857        } else {
858            quote! {
859                fn elt(x: ::miniextendr_api::SEXP, i: ::miniextendr_api::R_xlen_t) -> ::miniextendr_api::SEXP {
860                    let data = unsafe {
861                        <#name #ty_generics as ::miniextendr_api::altrep_data::AltrepExtract>::altrep_extract_ref(x)
862                    };
863                    <#name #ty_generics as ::miniextendr_api::altrep_data::AltListData>::elt(data, i.max(0) as usize)
864                }
865            }
866        };
867        quote! {
868            #base_impl
869            impl #impl_generics ::miniextendr_api::altrep_traits::AltVec for #name #ty_generics #where_clause {}
870            impl #impl_generics ::miniextendr_api::altrep_traits::AltList for #name #ty_generics #where_clause {
871                #elt_impl
872            }
873            ::miniextendr_api::impl_inferbase_list!(#name #ty_generics);
874        }
875    };
876
877    // Generate full registration (TypedExternal, AltrepClass, RegisterAltrep, IntoR,
878    // linkme entry, Ref/Mut) — same as derive_altrep_generic.
879    let class_name = attrs
880        .class_name
881        .as_deref()
882        .unwrap_or(&name.to_string())
883        .to_string();
884    let registration_impl =
885        crate::altrep::generate_direct_altrep_registration(name, generics, &class_name)?;
886
887    Ok(quote! {
888        #data_traits
889        #lowlevel_impl
890        #registration_impl
891    })
892}
893
894// region: Public helper for derive(Altrep) with base parameter