miniextendr_macros/
list_derive.rs1use proc_macro2::TokenStream;
33use quote::quote;
34use syn::{DeriveInput, Fields, parse_quote, spanned::Spanned};
35
36fn field_is_ignored(field: &syn::Field) -> syn::Result<bool> {
41 let mut ignored = false;
42
43 for attr in &field.attrs {
44 if !attr.path().is_ident("into_list") {
45 continue;
46 }
47
48 attr.parse_nested_meta(|meta| {
49 if meta.path.is_ident("ignore") {
50 ignored = true;
51 return Ok(());
52 }
53
54 Err(meta.error("unknown #[into_list(...)] option; supported: ignore"))
55 })?;
56 }
57
58 Ok(ignored)
59}
60
61pub fn derive_into_list(input: DeriveInput) -> syn::Result<TokenStream> {
73 let struct_data = match input.data {
74 syn::Data::Struct(data) => data,
75 _ => {
76 return Err(syn::Error::new(
77 input.ident.span(),
78 "IntoList can only be derived for structs",
79 ));
80 }
81 };
82
83 let name = &input.ident;
84 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
85
86 let mut bounds: Vec<syn::WherePredicate> = Vec::new();
87
88 let (destructure_pat, list_construction) = match &struct_data.fields {
89 Fields::Named(fields) => {
91 let mut names: Vec<String> = Vec::new();
92 let mut idents: Vec<syn::Ident> = Vec::new();
93
94 for f in fields.named.iter() {
95 let ident = f.ident.as_ref().unwrap().clone();
96 if field_is_ignored(f)? {
97 continue;
98 }
99 let ty = &f.ty;
100 bounds.push(parse_quote!(#ty: ::miniextendr_api::into_r::IntoR));
101 names.push(ident.to_string());
102 idents.push(ident);
103 }
104
105 let pat = if idents.is_empty() {
106 quote! { { .. } }
107 } else {
108 quote! { { #(#idents),*, .. } }
109 };
110 let construction = quote! {
115 unsafe {
117 let __scope = ::miniextendr_api::gc_protect::ProtectScope::new();
118 ::miniextendr_api::list::List::from_raw_pairs(vec![ #( (#names, __scope.protect_raw(#idents.into_sexp())) ),* ])
119 }
120 };
121 (pat, construction)
122 }
123
124 Fields::Unnamed(fields) => {
126 let mut pat_elems: Vec<proc_macro2::TokenStream> = Vec::new();
127 let mut value_idents: Vec<syn::Ident> = Vec::new();
128
129 for (idx, f) in fields.unnamed.iter().enumerate() {
130 if field_is_ignored(f)? {
131 pat_elems.push(quote! { _ });
132 continue;
133 }
134 let ident = syn::Ident::new(&format!("_field{idx}"), f.span());
135 let ty = &f.ty;
136 bounds.push(parse_quote!(#ty: ::miniextendr_api::into_r::IntoR));
137 pat_elems.push(quote! { #ident });
138 value_idents.push(ident);
139 }
140
141 let pat = quote! { ( #(#pat_elems),* ) };
142 let construction = quote! {
143 unsafe {
145 let __scope = ::miniextendr_api::gc_protect::ProtectScope::new();
146 ::miniextendr_api::list::List::from_raw_values(vec![ #( __scope.protect_raw(#value_idents.into_sexp()) ),* ])
147 }
148 };
149 (pat, construction)
150 }
151
152 Fields::Unit => {
154 let pat = quote! {};
155 let construction = quote! {
156 ::miniextendr_api::list::List::from_raw_values(vec![])
157 };
158 (pat, construction)
159 }
160 };
161
162 let mut where_clause = where_clause.cloned().unwrap_or_else(|| syn::WhereClause {
164 where_token: <syn::Token![where]>::default(),
165 predicates: syn::punctuated::Punctuated::new(),
166 });
167 for b in bounds {
168 where_clause.predicates.push(b);
169 }
170
171 let expand = quote! {
172 impl #impl_generics ::miniextendr_api::list::IntoList for #name #ty_generics #where_clause {
173 fn into_list(self) -> ::miniextendr_api::list::List {
174 use ::miniextendr_api::into_r::IntoR;
175 let Self #destructure_pat = self;
176 #list_construction
177 }
178 }
179 };
180
181 Ok(expand)
182}
183
184pub fn derive_try_from_list(input: DeriveInput) -> syn::Result<TokenStream> {
196 let struct_data = match input.data {
197 syn::Data::Struct(data) => data,
198 _ => {
199 return Err(syn::Error::new(
200 input.ident.span(),
201 "TryFromList can only be derived for structs",
202 ));
203 }
204 };
205
206 let name = &input.ident;
207 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
208
209 let mut bounds: Vec<syn::WherePredicate> = Vec::new();
210
211 let from_list_body = match &struct_data.fields {
212 Fields::Named(fields) => {
214 let mut field_extractions: Vec<proc_macro2::TokenStream> = Vec::new();
215 let mut field_inits: Vec<proc_macro2::TokenStream> = Vec::new();
216
217 for f in fields.named.iter() {
218 let ident = f.ident.as_ref().unwrap().clone();
219 let ty = &f.ty;
220
221 if field_is_ignored(f)? {
222 bounds.push(parse_quote!(#ty: ::core::default::Default));
223 field_inits.push(quote! { #ident: ::core::default::Default::default() });
224 continue;
225 }
226
227 bounds.push(parse_quote!(#ty: ::miniextendr_api::from_r::TryFromSexp));
233 bounds.push(parse_quote!(::miniextendr_api::from_r::SexpError: ::core::convert::From<<#ty as ::miniextendr_api::from_r::TryFromSexp>::Error>));
234
235 let name_str = ident.to_string();
236 field_extractions.push(quote! {
240 let #ident: #ty = {
241 let __elem = list.get_named_sexp(#name_str)
242 .ok_or_else(|| ::miniextendr_api::from_r::SexpError::MissingField(#name_str.into()))?;
243 <#ty as ::miniextendr_api::from_r::TryFromSexp>::try_from_sexp(__elem)
244 .map_err(::miniextendr_api::from_r::SexpError::from)?
245 };
246 });
247 field_inits.push(quote! { #ident });
248 }
249
250 quote! {
251 #(#field_extractions)*
252 Ok(Self { #(#field_inits),* })
253 }
254 }
255
256 Fields::Unnamed(fields) => {
258 let mut field_extractions: Vec<proc_macro2::TokenStream> = Vec::new();
259 let mut ctor_args: Vec<proc_macro2::TokenStream> = Vec::new();
260 let mut ignored_fields: Vec<bool> = Vec::with_capacity(fields.unnamed.len());
261 for f in fields.unnamed.iter() {
262 ignored_fields.push(field_is_ignored(f)?);
263 }
264 let input_fields: usize = ignored_fields.iter().filter(|&&b| !b).count();
265 let mut input_idx: usize = 0;
266
267 for (idx, f) in fields.unnamed.iter().enumerate() {
268 let ty = &f.ty;
269
270 if ignored_fields[idx] {
271 bounds.push(parse_quote!(#ty: ::core::default::Default));
272 ctor_args.push(quote! { ::core::default::Default::default() });
273 continue;
274 }
275
276 let ident = syn::Ident::new(&format!("_field{idx}"), f.span());
277 bounds.push(parse_quote!(#ty: ::miniextendr_api::from_r::TryFromSexp));
281 bounds.push(parse_quote!(::miniextendr_api::from_r::SexpError: ::core::convert::From<<#ty as ::miniextendr_api::from_r::TryFromSexp>::Error>));
282
283 let idx_isize = input_idx as isize;
284 field_extractions.push(quote! {
285 let #ident: #ty = {
286 let __elem = list.get(#idx_isize)
287 .ok_or_else(|| ::miniextendr_api::from_r::SexpError::Length(
288 ::miniextendr_api::from_r::SexpLengthError {
289 expected: #input_fields,
290 actual: list.len() as usize,
291 }
292 ))?;
293 <#ty as ::miniextendr_api::from_r::TryFromSexp>::try_from_sexp(__elem)
294 .map_err(::miniextendr_api::from_r::SexpError::from)?
295 };
296 });
297 ctor_args.push(quote! { #ident });
298 input_idx += 1;
299 }
300
301 quote! {
302 #(#field_extractions)*
303 Ok(Self( #(#ctor_args),* ))
304 }
305 }
306
307 Fields::Unit => {
309 quote! { Ok(Self) }
310 }
311 };
312
313 let mut where_clause = where_clause.cloned().unwrap_or_else(|| syn::WhereClause {
315 where_token: <syn::Token![where]>::default(),
316 predicates: syn::punctuated::Punctuated::new(),
317 });
318 for b in bounds {
319 where_clause.predicates.push(b);
320 }
321
322 let expand = quote! {
323 impl #impl_generics ::miniextendr_api::list::TryFromList for #name #ty_generics #where_clause {
324 type Error = ::miniextendr_api::from_r::SexpError;
325
326 fn try_from_list(list: ::miniextendr_api::list::List) -> Result<Self, Self::Error> {
327 #from_list_body
328 }
329 }
330 };
331
332 Ok(expand)
333}
334
335fn prefer_conflict_marker(input: &DeriveInput) -> TokenStream {
352 let name = &input.ident;
353 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
354
355 quote! {
356 #[allow(non_upper_case_globals, dead_code)]
357 impl #impl_generics #name #ty_generics #where_clause {
358 const __miniextendr_conflicting_Prefer_derives__keep_ONE_or_use_call_site_As_wrappers: () = ();
363 }
364 }
365}
366
367pub fn derive_prefer_list(input: DeriveInput) -> syn::Result<TokenStream> {
373 let name = &input.ident;
374 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
375 let conflict_marker = prefer_conflict_marker(&input);
376
377 let expand = quote! {
378 impl #impl_generics ::miniextendr_api::into_r::IntoR for #name #ty_generics #where_clause {
379 type Error = std::convert::Infallible;
380
381 #[inline]
382 fn try_into_sexp(self) -> Result<::miniextendr_api::SEXP, Self::Error> {
383 Ok(self.into_sexp())
384 }
385
386 #[inline]
387 unsafe fn try_into_sexp_unchecked(self) -> Result<::miniextendr_api::SEXP, Self::Error> {
388 self.try_into_sexp()
389 }
390
391 #[inline]
392 fn into_sexp(self) -> ::miniextendr_api::SEXP {
393 ::miniextendr_api::list::IntoList::into_list(self).into_sexp()
394 }
395
396 #[inline]
397 unsafe fn into_sexp_unchecked(self) -> ::miniextendr_api::SEXP {
398 ::miniextendr_api::list::IntoList::into_list(self).into_sexp()
399 }
400 }
401
402 #conflict_marker
403 };
404
405 Ok(expand)
406}
407
408pub fn derive_prefer_externalptr(input: DeriveInput) -> syn::Result<TokenStream> {
414 let name = &input.ident;
415 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
416 let conflict_marker = prefer_conflict_marker(&input);
417
418 let expand = quote! {
419 impl #impl_generics ::miniextendr_api::into_r::IntoR for #name #ty_generics #where_clause {
420 type Error = std::convert::Infallible;
421
422 #[inline]
423 fn try_into_sexp(self) -> Result<::miniextendr_api::SEXP, Self::Error> {
424 Ok(self.into_sexp())
425 }
426
427 #[inline]
428 unsafe fn try_into_sexp_unchecked(self) -> Result<::miniextendr_api::SEXP, Self::Error> {
429 self.try_into_sexp()
430 }
431
432 #[inline]
433 fn into_sexp(self) -> ::miniextendr_api::SEXP {
434 ::miniextendr_api::externalptr::ExternalPtr::new(self).into_sexp()
435 }
436
437 #[inline]
438 unsafe fn into_sexp_unchecked(self) -> ::miniextendr_api::SEXP {
439 ::miniextendr_api::externalptr::ExternalPtr::new(self).into_sexp()
440 }
441 }
442
443 #conflict_marker
444 };
445
446 Ok(expand)
447}
448
449pub fn derive_prefer_data_frame(input: DeriveInput) -> syn::Result<TokenStream> {
455 let name = &input.ident;
456 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
457 let conflict_marker = prefer_conflict_marker(&input);
458
459 let expand = quote! {
460 impl #impl_generics ::miniextendr_api::into_r::IntoR for #name #ty_generics #where_clause {
461 type Error = std::convert::Infallible;
462
463 #[inline]
464 fn try_into_sexp(self) -> Result<::miniextendr_api::SEXP, Self::Error> {
465 Ok(self.into_sexp())
466 }
467
468 #[inline]
469 unsafe fn try_into_sexp_unchecked(self) -> Result<::miniextendr_api::SEXP, Self::Error> {
470 self.try_into_sexp()
471 }
472
473 #[inline]
474 fn into_sexp(self) -> ::miniextendr_api::SEXP {
475 ::miniextendr_api::convert::ColumnSource::into_column_list(self).into_sexp()
476 }
477
478 #[inline]
479 unsafe fn into_sexp_unchecked(self) -> ::miniextendr_api::SEXP {
480 ::miniextendr_api::convert::ColumnSource::into_column_list(self).into_sexp()
481 }
482 }
483
484 #conflict_marker
485 };
486
487 Ok(expand)
488}
489
490pub fn derive_prefer_rnative(input: DeriveInput) -> syn::Result<TokenStream> {
497 let name = &input.ident;
498 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
499 let conflict_marker = prefer_conflict_marker(&input);
500
501 let expand = quote! {
502 impl #impl_generics ::miniextendr_api::into_r::IntoR for #name #ty_generics #where_clause {
503 type Error = std::convert::Infallible;
504
505 #[inline]
506 fn try_into_sexp(self) -> Result<::miniextendr_api::SEXP, Self::Error> {
507 Ok(self.into_sexp())
508 }
509
510 #[inline]
511 unsafe fn try_into_sexp_unchecked(self) -> Result<::miniextendr_api::SEXP, Self::Error> {
512 self.try_into_sexp()
513 }
514
515 #[inline]
516 fn into_sexp(self) -> ::miniextendr_api::SEXP {
517 ::miniextendr_api::into_r::IntoR::into_sexp(
518 ::miniextendr_api::convert::AsRNative(self)
519 )
520 }
521
522 #[inline]
523 unsafe fn into_sexp_unchecked(self) -> ::miniextendr_api::SEXP {
524 ::miniextendr_api::into_r::IntoR::into_sexp_unchecked(
525 ::miniextendr_api::convert::AsRNative(self)
526 )
527 }
528 }
529
530 #conflict_marker
531 };
532
533 Ok(expand)
534}
535
536#[cfg(feature = "vctrs")]
544pub fn derive_prefer_vctrs(input: DeriveInput) -> syn::Result<TokenStream> {
545 let name = &input.ident;
546 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
547 let conflict_marker = prefer_conflict_marker(&input);
548
549 let expand = quote! {
550 impl #impl_generics ::miniextendr_api::into_r::IntoR for #name #ty_generics #where_clause {
551 type Error = ::miniextendr_api::vctrs::VctrsBuildError;
552
553 #[inline]
554 fn try_into_sexp(self) -> Result<::miniextendr_api::SEXP, Self::Error> {
555 ::miniextendr_api::vctrs::IntoVctrs::into_vctrs(self)
556 }
557
558 #[inline]
559 unsafe fn try_into_sexp_unchecked(self) -> Result<::miniextendr_api::SEXP, Self::Error> {
560 self.try_into_sexp()
561 }
562 }
563
564 #conflict_marker
565 };
566
567 Ok(expand)
568}