1struct StructEnumAttrs {
28 class: Option<String>,
30 base: Option<String>,
32 list: bool,
34 dataframe: bool,
36 externalptr: bool,
38 match_arg: bool,
40 factor: bool,
42 prefer: Option<String>,
45}
46
47fn parse_attrs(attr: proc_macro::TokenStream) -> syn::Result<StructEnumAttrs> {
54 use syn::parse::Parser;
55
56 let mut attrs = StructEnumAttrs {
57 class: None,
58 base: None,
59 list: false,
60 dataframe: false,
61 externalptr: false,
62 match_arg: false,
63 factor: false,
64 prefer: None,
65 };
66
67 let parser = syn::punctuated::Punctuated::<syn::Meta, ::syn::token::Commasyn::Token![,]>::parse_terminated;
69 let metas = parser.parse(attr)?;
70
71 for meta in &metas {
72 match meta {
73 syn::Meta::Path(path) => {
74 let ident = path
75 .get_ident()
76 .ok_or_else(|| syn::Error::new_spanned(path, "expected identifier"))?;
77 match ident.to_string().as_str() {
78 "list" => attrs.list = true,
79 "dataframe" => attrs.dataframe = true,
80 "externalptr" => attrs.externalptr = true,
81 "match_arg" => attrs.match_arg = true,
82 "factor" => attrs.factor = true,
83 _ => {
84 return Err(syn::Error::new_spanned(
85 ident,
86 ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("unknown #[miniextendr] attribute `{0}`; expected one of: list, dataframe, externalptr, match_arg, factor, prefer, class, base",
ident))
})format!(
87 "unknown #[miniextendr] attribute `{}`; expected one of: \
88 list, dataframe, externalptr, match_arg, factor, prefer, class, base",
89 ident
90 ),
91 ));
92 }
93 }
94 }
95 syn::Meta::NameValue(nv) => {
96 let key = nv
97 .path
98 .get_ident()
99 .map(|i| i.to_string())
100 .unwrap_or_default();
101 if let syn::Expr::Lit(syn::ExprLit {
102 lit: syn::Lit::Str(s),
103 ..
104 }) = &nv.value
105 {
106 match key.as_str() {
107 "class" => attrs.class = Some(s.value()),
108 "base" => attrs.base = Some(s.value()),
109 "prefer" => attrs.prefer = Some(s.value()),
110 _ => {
111 return Err(syn::Error::new_spanned(
112 &nv.path,
113 ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("unknown #[miniextendr] attribute `{0}`; expected one of: class, base, prefer",
key))
})format!(
114 "unknown #[miniextendr] attribute `{}`; expected one of: \
115 class, base, prefer",
116 key
117 ),
118 ));
119 }
120 }
121 }
122 }
123 syn::Meta::List(list) => {
124 return Err(syn::Error::new_spanned(
125 list,
126 "unexpected list-style attribute; use path (`list`) or key-value (`class = \"...\"`) syntax",
127 ));
128 }
129 }
130 }
131
132 Ok(attrs)
133}
134
135fn is_fieldless_enum(item: &syn::ItemEnum) -> bool {
137 item.variants
138 .iter()
139 .all(|v| #[allow(non_exhaustive_omitted_patterns)] match v.fields {
syn::Fields::Unit => true,
_ => false,
}matches!(v.fields, syn::Fields::Unit))
140}
141
142pub fn expand_struct_or_enum(
150 attr: proc_macro::TokenStream,
151 item: proc_macro::TokenStream,
152) -> proc_macro::TokenStream {
153 if let Ok(item_struct) = syn::parse::<syn::ItemStruct>(item.clone()) {
155 return expand_struct(attr, item, &item_struct);
156 }
157
158 if let Ok(item_enum) = syn::parse::<syn::ItemEnum>(item.clone()) {
159 return expand_enum(attr, item, &item_enum);
160 }
161
162 if let Some(found) = crate::misplaced_dots::find(item.into()) {
165 return syn::Error::new_spanned(
166 found.dots,
167 crate::misplaced_dots::message(found.name.as_deref()),
168 )
169 .into_compile_error()
170 .into();
171 }
172
173 syn::Error::new(
175 proc_macro2::Span::call_site(),
176 "#[miniextendr] on non-function items requires a struct or enum",
177 )
178 .into_compile_error()
179 .into()
180}
181
182fn expand_struct(
194 attr: proc_macro::TokenStream,
195 item: proc_macro::TokenStream,
196 item_struct: &syn::ItemStruct,
197) -> proc_macro::TokenStream {
198 let attrs = match parse_attrs(attr.clone()) {
199 Ok(a) => a,
200 Err(e) => return e.into_compile_error().into(),
201 };
202
203 let has_altrep_attrs = attrs.class.is_some() || attrs.base.is_some();
204 let has_mode_attr = attrs.list || attrs.dataframe || attrs.externalptr;
205
206 let effective_list = attrs.list || (!has_mode_attr && attrs.prefer.as_deref() == Some("list"));
208 let effective_dataframe =
209 attrs.dataframe || (!has_mode_attr && attrs.prefer.as_deref() == Some("dataframe"));
210 let effective_externalptr =
211 attrs.externalptr || (!has_mode_attr && attrs.prefer.as_deref() == Some("externalptr"));
212 let effective_mode = effective_list || effective_dataframe || effective_externalptr;
213
214 if has_altrep_attrs && !effective_mode {
217 return syn::Error::new(
218 item_struct.ident.span(),
219 "#[miniextendr] no longer supports ALTREP (class/base attributes). \
220 Use #[derive(miniextendr_api::Altrep)] with #[altrep(class = \"...\")] instead.",
221 )
222 .into_compile_error()
223 .into();
224 }
225
226 if has_altrep_attrs && effective_mode {
228 return syn::Error::new(
229 item_struct.ident.span(),
230 "cannot combine ALTREP attributes (class, base) with mode attributes (list, dataframe, externalptr)",
231 )
232 .into_compile_error()
233 .into();
234 }
235
236 let mode_count = [effective_list, effective_dataframe, effective_externalptr]
238 .iter()
239 .filter(|&&b| b)
240 .count();
241 if mode_count > 1 {
242 return syn::Error::new(
243 item_struct.ident.span(),
244 "only one of `list`, `dataframe`, `externalptr` can be specified",
245 )
246 .into_compile_error()
247 .into();
248 }
249
250 if let Some(ref prefer) = attrs.prefer
252 && !#[allow(non_exhaustive_omitted_patterns)] match prefer.as_str() {
"externalptr" | "list" | "dataframe" | "native" => true,
_ => false,
}matches!(
253 prefer.as_str(),
254 "externalptr" | "list" | "dataframe" | "native"
255 )
256 {
257 return syn::Error::new(
258 item_struct.ident.span(),
259 ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("unknown prefer value `{0}`; expected one of: externalptr, list, dataframe, native",
prefer))
})format!(
260 "unknown prefer value `{}`; expected one of: externalptr, list, dataframe, native",
261 prefer
262 ),
263 )
264 .into_compile_error()
265 .into();
266 }
267
268 if effective_list {
272 return {
let mut _s = ::quote::__private::TokenStream::new();
::quote::__private::push_pound(&mut _s);
::quote::__private::push_group(&mut _s,
::quote::__private::Delimiter::Bracket,
{
let mut _s = ::quote::__private::TokenStream::new();
::quote::__private::push_ident(&mut _s, "derive");
::quote::__private::push_group(&mut _s,
::quote::__private::Delimiter::Parenthesis,
{
let mut _s = ::quote::__private::TokenStream::new();
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "miniextendr_api");
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "IntoList");
::quote::__private::push_comma(&mut _s);
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "miniextendr_api");
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "TryFromList");
::quote::__private::push_comma(&mut _s);
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "miniextendr_api");
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "PreferList");
::quote::__private::push_comma(&mut _s);
_s
});
_s
});
::quote::ToTokens::to_tokens(&item_struct, &mut _s);
_s
}quote::quote! {
273 #[derive(
274 ::miniextendr_api::IntoList,
275 ::miniextendr_api::TryFromList,
276 ::miniextendr_api::PreferList,
277 )]
278 #item_struct
279 }
280 .into();
281 }
282
283 if effective_dataframe {
284 return {
let mut _s = ::quote::__private::TokenStream::new();
::quote::__private::push_pound(&mut _s);
::quote::__private::push_group(&mut _s,
::quote::__private::Delimiter::Bracket,
{
let mut _s = ::quote::__private::TokenStream::new();
::quote::__private::push_ident(&mut _s, "derive");
::quote::__private::push_group(&mut _s,
::quote::__private::Delimiter::Parenthesis,
{
let mut _s = ::quote::__private::TokenStream::new();
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "miniextendr_api");
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "DataFrameRow");
_s
});
_s
});
::quote::ToTokens::to_tokens(&item_struct, &mut _s);
_s
}quote::quote! {
286 #[derive(::miniextendr_api::DataFrameRow)]
287 #item_struct
288 }
289 .into();
290 }
291
292 let derive_input: syn::DeriveInput = match syn::parse(item.clone()) {
294 Ok(d) => d,
295 Err(e) => return e.into_compile_error().into(),
296 };
297 let derive_input = strip_miniextendr_attrs(derive_input);
298 let item_ts: proc_macro2::TokenStream = item.into();
299
300 let result = (|| -> syn::Result<proc_macro2::TokenStream> {
303 let external_ptr = crate::externalptr_derive::derive_external_ptr(
307 derive_input.clone(),
308 attrs.prefer.as_deref() != Some("native"),
309 )?;
310
311 let prefer = if attrs.prefer.as_deref() == Some("native") {
313 crate::list_derive::derive_prefer_rnative(derive_input)?
314 } else {
315 proc_macro2::TokenStream::new()
316 };
317
318 Ok({
let mut _s = ::quote::__private::TokenStream::new();
::quote::ToTokens::to_tokens(&item_ts, &mut _s);
::quote::ToTokens::to_tokens(&external_ptr, &mut _s);
::quote::ToTokens::to_tokens(&prefer, &mut _s);
_s
}quote::quote! {
319 #item_ts
320 #external_ptr
321 #prefer
322 })
323 })();
324 result.unwrap_or_else(|e| e.into_compile_error()).into()
325}
326
327fn expand_enum(
333 attr: proc_macro::TokenStream,
334 item: proc_macro::TokenStream,
335 item_enum: &syn::ItemEnum,
336) -> proc_macro::TokenStream {
337 let attrs = match parse_attrs(attr) {
338 Ok(a) => a,
339 Err(e) => return e.into_compile_error().into(),
340 };
341
342 if !is_fieldless_enum(item_enum) {
343 return syn::Error::new(
344 item_enum.ident.span(),
345 "#[miniextendr] on enums requires all variants to be fieldless (C-style)",
346 )
347 .into_compile_error()
348 .into();
349 }
350
351 let derive_input: syn::DeriveInput = match syn::parse(item.clone()) {
352 Ok(d) => d,
353 Err(e) => return e.into_compile_error().into(),
354 };
355 let derive_input = strip_miniextendr_attrs(derive_input);
356
357 let item_ts: proc_macro2::TokenStream = item.into();
358
359 if attrs.match_arg {
360 let result = crate::match_arg_derive::derive_match_arg(derive_input);
362 return match result {
363 Ok(ts) => {
let mut _s = ::quote::__private::TokenStream::new();
::quote::ToTokens::to_tokens(&item_ts, &mut _s);
::quote::ToTokens::to_tokens(&ts, &mut _s);
_s
}quote::quote! { #item_ts #ts }.into(),
364 Err(e) => e.into_compile_error().into(),
365 };
366 }
367
368 if attrs.factor {
369 let result = crate::factor_derive::derive_r_factor(derive_input);
371 return match result {
372 Ok(ts) => {
let mut _s = ::quote::__private::TokenStream::new();
::quote::ToTokens::to_tokens(&item_ts, &mut _s);
::quote::ToTokens::to_tokens(&ts, &mut _s);
_s
}quote::quote! { #item_ts #ts }.into(),
373 Err(e) => e.into_compile_error().into(),
374 };
375 }
376
377 let result = crate::factor_derive::derive_r_factor(derive_input);
379 match result {
380 Ok(ts) => {
let mut _s = ::quote::__private::TokenStream::new();
::quote::ToTokens::to_tokens(&item_ts, &mut _s);
::quote::ToTokens::to_tokens(&ts, &mut _s);
_s
}quote::quote! { #item_ts #ts }.into(),
381 Err(e) => e.into_compile_error().into(),
382 }
383}
384
385fn strip_miniextendr_attrs(mut input: syn::DeriveInput) -> syn::DeriveInput {
390 input
391 .attrs
392 .retain(|attr| !attr.path().is_ident("miniextendr"));
393 input
394}