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