miniextendr_macros/
altrep.rs1pub(crate) fn generate_direct_altrep_registration(
33 ident: &syn::Ident,
34 generics: &syn::Generics,
35 class_name: &str,
36) -> syn::Result<proc_macro2::TokenStream> {
37 let (_impl_generics, ty_generics, where_clause) = generics.split_for_impl();
38
39 let class_cstr = syn::LitCStr::new(&std::ffi::CString::new(class_name).unwrap(), ident.span());
41
42 let type_name_str = class_name;
44 let type_name_bytes = format!("{}\0", type_name_str);
45 let type_name_byte_lit = syn::LitByteStr::new(type_name_bytes.as_bytes(), ident.span());
46
47 let ref_ident = quote::format_ident!("{}Ref", ident);
48 let mut_ident = quote::format_ident!("{}Mut", ident);
49
50 let into_r_doc = format!(
51 "Convert [`{}`] to an R ALTREP SEXP.\n\nIn debug builds, asserts that we're on R's main thread.",
52 ident
53 );
54 let ref_doc = format!(
55 "Immutable reference wrapper for [`{}`] ALTREP data. Implements `TryFromSexp` and `Deref<Target = {}>`.",
56 ident, ident
57 );
58 let mut_doc = format!(
59 "Mutable reference wrapper for [`{}`] ALTREP data. Implements `TryFromSexp`, `Deref`, and `DerefMut`.",
60 ident
61 );
62
63 let altrep_reg_entry = if generics.params.is_empty() {
76 let reg_fn_name = crate::naming::altrep_reg_fn_ident(ident);
77 let entry_ident = quote::format_ident!("__MX_ALTREP_REG_ENTRY_{}", ident);
78 quote::quote! {
79 #[doc(hidden)]
80 #[unsafe(no_mangle)]
81 pub extern "C" fn #reg_fn_name() {
82 <#ident as ::miniextendr_api::altrep_registration::RegisterAltrep>::get_or_init_class();
83 }
84
85 #[cfg_attr(not(target_arch = "wasm32"), ::miniextendr_api::linkme::distributed_slice(::miniextendr_api::registry::MX_ALTREP_REGISTRATIONS), linkme(crate = ::miniextendr_api::linkme))]
86 #[doc(hidden)]
87 #[allow(non_upper_case_globals)]
88 static #entry_ident: ::miniextendr_api::registry::AltrepRegistration =
89 ::miniextendr_api::registry::AltrepRegistration {
90 register: #reg_fn_name,
91 symbol: stringify!(#reg_fn_name),
92 };
93 }
94 } else {
95 quote::quote! {}
96 };
97
98 let source_loc_doc = crate::source_location_doc(ident.span());
99
100 Ok(quote::quote! {
101 impl ::miniextendr_api::externalptr::TypedExternal for #ident #ty_generics #where_clause {
105 const TYPE_NAME: &'static str = #type_name_str;
106 const TYPE_NAME_CSTR: &'static [u8] = #type_name_byte_lit;
107 const TYPE_ID_CSTR: &'static [u8] =
108 concat!(module_path!(), "::", stringify!(#ident), "\0").as_bytes();
109 }
110
111 #[doc = concat!("ALTREP class descriptor for [`", stringify!(#ident), "`].")]
113 #[doc = #source_loc_doc]
114 impl ::miniextendr_api::altrep::AltrepClass for #ident #ty_generics #where_clause {
115 const CLASS_NAME: &'static ::core::ffi::CStr = #class_cstr;
116 const BASE: ::miniextendr_api::altrep::RBase =
117 <#ident #ty_generics as ::miniextendr_api::altrep_data::InferBase>::BASE;
118 }
119
120 #[doc = concat!("Registration entry point for [`", stringify!(#ident), "`] ALTREP class.")]
122 #[doc = #source_loc_doc]
123 impl ::miniextendr_api::altrep_registration::RegisterAltrep for #ident #ty_generics #where_clause {
124 fn get_or_init_class() -> ::miniextendr_api::sys::altrep::R_altrep_class_t {
125 use ::std::sync::OnceLock;
126 static CLASS: OnceLock<::miniextendr_api::sys::altrep::R_altrep_class_t> = OnceLock::new();
127 *CLASS.get_or_init(move || {
128 let cls = unsafe {
129 <#ident as ::miniextendr_api::altrep_data::InferBase>::make_class(
130 <#ident as ::miniextendr_api::altrep::AltrepClass>::CLASS_NAME.as_ptr(),
131 ::miniextendr_api::AltrepPkgName::as_ptr(),
132 )
133 };
134 unsafe {
135 <#ident as ::miniextendr_api::altrep_data::InferBase>::install_methods(cls);
136 }
137 cls
138 })
139 }
140 }
141
142 #[doc = #into_r_doc]
144 impl ::miniextendr_api::IntoR for #ident #ty_generics #where_clause {
145 type Error = ::core::convert::Infallible;
146
147 fn try_into_sexp(self) -> ::core::result::Result<::miniextendr_api::SEXP, Self::Error> {
148 Ok(self.into_sexp())
149 }
150
151 unsafe fn try_into_sexp_unchecked(self) -> ::core::result::Result<::miniextendr_api::SEXP, Self::Error> {
152 Ok(unsafe { self.into_sexp_unchecked() })
153 }
154
155 fn into_sexp(self) -> ::miniextendr_api::SEXP {
156 use ::miniextendr_api::altrep_registration::RegisterAltrep;
157 use ::miniextendr_api::externalptr::ExternalPtr;
158 use ::miniextendr_api::SEXP;
159 use ::miniextendr_api::sys::{Rf_protect, Rf_unprotect};
160
161 let ext_ptr = ExternalPtr::new(self);
162 let cls = Self::get_or_init_class();
163 let data1 = ext_ptr.as_sexp();
164 unsafe {
165 Rf_protect(data1);
166 let altrep = cls.new_altrep(data1, SEXP::nil());
167 Rf_unprotect(1);
168 altrep
169 }
170 }
171
172 unsafe fn into_sexp_unchecked(self) -> ::miniextendr_api::SEXP {
173 use ::miniextendr_api::altrep_registration::RegisterAltrep;
174 use ::miniextendr_api::externalptr::ExternalPtr;
175 use ::miniextendr_api::sys::{Rf_protect_unchecked, Rf_unprotect_unchecked};
176
177 let ext_ptr = ExternalPtr::new_unchecked(self);
178 let cls = Self::get_or_init_class();
179 let data1 = ext_ptr.as_sexp();
180 unsafe {
181 Rf_protect_unchecked(data1);
182 let altrep = cls.new_altrep_unchecked(
183 data1,
184 ::miniextendr_api::SEXP::nil(),
185 );
186 Rf_unprotect_unchecked(1);
187 altrep
188 }
189 }
190 }
191
192 #[doc = #ref_doc]
194 pub struct #ref_ident(::miniextendr_api::externalptr::ExternalPtr<#ident #ty_generics>);
195
196 impl ::miniextendr_api::TryFromSexp for #ref_ident {
197 type Error = ::miniextendr_api::SexpTypeError;
198
199 fn try_from_sexp(sexp: ::miniextendr_api::SEXP) -> ::core::result::Result<Self, Self::Error> {
200 use ::miniextendr_api::SEXPTYPE;
201
202 if !::miniextendr_api::SexpExt::is_altrep(&sexp) {
203 return Err(::miniextendr_api::SexpTypeError {
204 expected: <#ident #ty_generics as ::miniextendr_api::altrep::AltrepClass>::BASE.sexptype(),
205 actual: ::miniextendr_api::SexpExt::type_of(&sexp),
206 });
207 }
208
209 match unsafe { ::miniextendr_api::altrep_data1_as::<#ident #ty_generics>(sexp) } {
210 Some(ptr) => Ok(#ref_ident(ptr)),
211 None => Err(::miniextendr_api::SexpTypeError {
212 expected: SEXPTYPE::EXTPTRSXP,
213 actual: ::miniextendr_api::SexpExt::type_of(&sexp),
214 }),
215 }
216 }
217 }
218
219 impl ::core::ops::Deref for #ref_ident {
220 type Target = #ident #ty_generics;
221
222 fn deref(&self) -> &Self::Target {
223 &*self.0
224 }
225 }
226
227 #[doc = #mut_doc]
228 pub struct #mut_ident(::miniextendr_api::externalptr::ExternalPtr<#ident #ty_generics>);
229
230 impl ::miniextendr_api::TryFromSexp for #mut_ident {
231 type Error = ::miniextendr_api::SexpTypeError;
232
233 fn try_from_sexp(sexp: ::miniextendr_api::SEXP) -> ::core::result::Result<Self, Self::Error> {
234 use ::miniextendr_api::SEXPTYPE;
235
236 if !::miniextendr_api::SexpExt::is_altrep(&sexp) {
237 return Err(::miniextendr_api::SexpTypeError {
238 expected: <#ident #ty_generics as ::miniextendr_api::altrep::AltrepClass>::BASE.sexptype(),
239 actual: ::miniextendr_api::SexpExt::type_of(&sexp),
240 });
241 }
242
243 match unsafe { ::miniextendr_api::altrep_data1_as::<#ident #ty_generics>(sexp) } {
244 Some(ptr) => Ok(#mut_ident(ptr)),
245 None => Err(::miniextendr_api::SexpTypeError {
246 expected: SEXPTYPE::EXTPTRSXP,
247 actual: ::miniextendr_api::SexpExt::type_of(&sexp),
248 }),
249 }
250 }
251 }
252
253 impl ::core::ops::Deref for #mut_ident {
254 type Target = #ident #ty_generics;
255
256 fn deref(&self) -> &Self::Target {
257 &*self.0
258 }
259 }
260
261 impl ::core::ops::DerefMut for #mut_ident {
262 fn deref_mut(&mut self) -> &mut Self::Target {
263 &mut *self.0
264 }
265 }
266
267 #altrep_reg_entry
268 })
269}
270
271pub fn derive_altrep(input: syn::DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
289 use syn::spanned::Spanned;
290
291 let ident = &input.ident;
292
293 if !matches!(input.data, syn::Data::Struct(_)) {
294 return Err(syn::Error::new(
295 input.span(),
296 "#[derive(Altrep)] can only be applied to structs",
297 ));
298 }
299
300 let mut class_name = None::<String>;
302
303 for attr in &input.attrs {
304 if !attr.path().is_ident("altrep") {
305 continue;
306 }
307 attr.parse_nested_meta(|meta| {
308 if meta.path.is_ident("class") {
309 let value: syn::LitStr = meta.value()?.parse()?;
310 class_name = Some(value.value());
311 } else {
312 return Err(meta.error("unknown #[altrep(...)] attribute; expected `class`"));
313 }
314 Ok(())
315 })?;
316 }
317
318 let class_name = class_name.unwrap_or_else(|| ident.to_string());
319
320 generate_direct_altrep_registration(ident, &input.generics, &class_name)
321}