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::sys::altrep::R_altrep_class_t {
124 use ::std::sync::OnceLock;
125 static CLASS: OnceLock<::miniextendr_api::sys::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::SEXP, Self::Error> {
147 Ok(self.into_sexp())
148 }
149
150 unsafe fn try_into_sexp_unchecked(self) -> ::core::result::Result<::miniextendr_api::SEXP, Self::Error> {
151 Ok(unsafe { self.into_sexp_unchecked() })
152 }
153
154 fn into_sexp(self) -> ::miniextendr_api::SEXP {
155 use ::miniextendr_api::altrep_registration::RegisterAltrep;
156 use ::miniextendr_api::externalptr::ExternalPtr;
157 use ::miniextendr_api::SEXP;
158 use ::miniextendr_api::sys::{Rf_protect, Rf_unprotect};
159
160 let ext_ptr = ExternalPtr::new(self);
161 let cls = Self::get_or_init_class();
162 let data1 = ext_ptr.as_sexp();
163 unsafe {
164 Rf_protect(data1);
165 let altrep = cls.new_altrep(data1, SEXP::nil());
166 Rf_unprotect(1);
167 altrep
168 }
169 }
170
171 unsafe fn into_sexp_unchecked(self) -> ::miniextendr_api::SEXP {
172 use ::miniextendr_api::altrep_registration::RegisterAltrep;
173 use ::miniextendr_api::externalptr::ExternalPtr;
174 use ::miniextendr_api::sys::{Rf_protect_unchecked, Rf_unprotect_unchecked};
175
176 let ext_ptr = ExternalPtr::new_unchecked(self);
177 let cls = Self::get_or_init_class();
178 let data1 = ext_ptr.as_sexp();
179 unsafe {
180 Rf_protect_unchecked(data1);
181 let altrep = cls.new_altrep_unchecked(
182 data1,
183 ::miniextendr_api::SEXP::nil(),
184 );
185 Rf_unprotect_unchecked(1);
186 altrep
187 }
188 }
189 }
190
191 #[doc = #ref_doc]
193 pub struct #ref_ident(::miniextendr_api::externalptr::ExternalPtr<#ident #ty_generics>);
194
195 impl ::miniextendr_api::TryFromSexp for #ref_ident {
196 type Error = ::miniextendr_api::SexpTypeError;
197
198 fn try_from_sexp(sexp: ::miniextendr_api::SEXP) -> ::core::result::Result<Self, Self::Error> {
199 use ::miniextendr_api::SEXPTYPE;
200
201 if !::miniextendr_api::SexpExt::is_altrep(&sexp) {
202 return Err(::miniextendr_api::SexpTypeError {
203 expected: <#ident #ty_generics as ::miniextendr_api::altrep::AltrepClass>::BASE.sexptype(),
204 actual: ::miniextendr_api::SexpExt::type_of(&sexp),
205 });
206 }
207
208 match unsafe { ::miniextendr_api::altrep_data1_as::<#ident #ty_generics>(sexp) } {
209 Some(ptr) => Ok(#ref_ident(ptr)),
210 None => Err(::miniextendr_api::SexpTypeError {
211 expected: SEXPTYPE::EXTPTRSXP,
212 actual: ::miniextendr_api::SexpExt::type_of(&sexp),
213 }),
214 }
215 }
216 }
217
218 impl ::core::ops::Deref for #ref_ident {
219 type Target = #ident #ty_generics;
220
221 fn deref(&self) -> &Self::Target {
222 &*self.0
223 }
224 }
225
226 #[doc = #mut_doc]
227 pub struct #mut_ident(::miniextendr_api::externalptr::ExternalPtr<#ident #ty_generics>);
228
229 impl ::miniextendr_api::TryFromSexp for #mut_ident {
230 type Error = ::miniextendr_api::SexpTypeError;
231
232 fn try_from_sexp(sexp: ::miniextendr_api::SEXP) -> ::core::result::Result<Self, Self::Error> {
233 use ::miniextendr_api::SEXPTYPE;
234
235 if !::miniextendr_api::SexpExt::is_altrep(&sexp) {
236 return Err(::miniextendr_api::SexpTypeError {
237 expected: <#ident #ty_generics as ::miniextendr_api::altrep::AltrepClass>::BASE.sexptype(),
238 actual: ::miniextendr_api::SexpExt::type_of(&sexp),
239 });
240 }
241
242 match unsafe { ::miniextendr_api::altrep_data1_as::<#ident #ty_generics>(sexp) } {
243 Some(ptr) => Ok(#mut_ident(ptr)),
244 None => Err(::miniextendr_api::SexpTypeError {
245 expected: SEXPTYPE::EXTPTRSXP,
246 actual: ::miniextendr_api::SexpExt::type_of(&sexp),
247 }),
248 }
249 }
250 }
251
252 impl ::core::ops::Deref for #mut_ident {
253 type Target = #ident #ty_generics;
254
255 fn deref(&self) -> &Self::Target {
256 &*self.0
257 }
258 }
259
260 impl ::core::ops::DerefMut for #mut_ident {
261 fn deref_mut(&mut self) -> &mut Self::Target {
262 &mut *self.0
263 }
264 }
265
266 #altrep_reg_entry
267 })
268}
269
270pub fn derive_altrep(input: syn::DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
288 use syn::spanned::Spanned;
289
290 let ident = &input.ident;
291
292 if !matches!(input.data, syn::Data::Struct(_)) {
293 return Err(syn::Error::new(
294 input.span(),
295 "#[derive(Altrep)] can only be applied to structs",
296 ));
297 }
298
299 let mut class_name = None::<String>;
301
302 for attr in &input.attrs {
303 if !attr.path().is_ident("altrep") {
304 continue;
305 }
306 attr.parse_nested_meta(|meta| {
307 if meta.path.is_ident("class") {
308 let value: syn::LitStr = meta.value()?.parse()?;
309 class_name = Some(value.value());
310 } else {
311 return Err(meta.error("unknown #[altrep(...)] attribute; expected `class`"));
312 }
313 Ok(())
314 })?;
315 }
316
317 let class_name = class_name.unwrap_or_else(|| ident.to_string());
318
319 generate_direct_altrep_registration(ident, &input.generics, &class_name)
320}