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