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