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