1pub(crate) fn nth_type_argument(seg: &syn::PathSegment, n: usize) -> Option<&syn::Type> {
5 if let syn::PathArguments::AngleBracketed(ab) = &seg.arguments {
6 let mut count = 0;
7 for arg in ab.args.iter() {
8 if let syn::GenericArgument::Type(ty) = arg {
9 if count == n {
10 return Some(ty);
11 }
12 count += 1;
13 }
14 }
15 }
16 None
17}
18
19pub(crate) fn first_type_argument(seg: &syn::PathSegment) -> Option<&syn::Type> {
21 nth_type_argument(seg, 0)
22}
23
24pub(crate) fn second_type_argument(seg: &syn::PathSegment) -> Option<&syn::Type> {
26 nth_type_argument(seg, 1)
27}
28
29pub(crate) fn peel_visibility_marker(ty: &syn::Type) -> (Option<bool>, &syn::Type) {
39 let syn::Type::Path(p) = ty else {
40 return (None, ty);
41 };
42 let Some(seg) = p.path.segments.last() else {
43 return (None, ty);
44 };
45 let invisible = match seg.ident.to_string().as_str() {
46 "Invisible" => true,
47 "Visible" => false,
48 _ => return (None, ty),
49 };
50 match first_type_argument(seg) {
51 Some(inner) => (Some(invisible), inner),
52 None => (None, ty),
53 }
54}
55
56pub(crate) fn peel_return_visibility(output: &syn::ReturnType) -> (Option<bool>, syn::ReturnType) {
61 match output {
62 syn::ReturnType::Type(arrow, ty) => {
63 let (marker, inner) = peel_visibility_marker(ty);
64 match marker {
65 Some(_) => (
66 marker,
67 syn::ReturnType::Type(*arrow, Box::new(inner.clone())),
68 ),
69 None => (None, output.clone()),
70 }
71 }
72 syn::ReturnType::Default => (None, output.clone()),
73 }
74}
75
76pub(crate) fn serialize_return_type(output: &syn::ReturnType, serialize: bool) -> syn::ReturnType {
80 if !serialize {
81 return output.clone();
82 }
83 let ty: syn::Type = match output {
84 syn::ReturnType::Default => ::syn::__private::parse_quote({
let mut _s = ::quote::__private::TokenStream::new();
::quote::__private::push_group(&mut _s,
::quote::__private::Delimiter::Parenthesis,
::quote::__private::TokenStream::new());
_s
})syn::parse_quote!(()),
85 syn::ReturnType::Type(_, ty) => (**ty).clone(),
86 };
87 ::syn::__private::parse_quote({
let mut _s = ::quote::__private::TokenStream::new();
::quote::__private::push_rarrow(&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, "serde");
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "AsSerialize");
::quote::__private::push_lt(&mut _s);
::quote::ToTokens::to_tokens(&ty, &mut _s);
::quote::__private::push_gt(&mut _s);
_s
})syn::parse_quote!(-> ::miniextendr_api::serde::AsSerialize<#ty>)
88}
89
90pub(crate) fn prepare_return_value(
93 call: proc_macro2::TokenStream,
94 visibility_marker: Option<bool>,
95 serialize: bool,
96) -> proc_macro2::TokenStream {
97 let value = if visibility_marker.is_some() {
98 {
let mut _s = ::quote::__private::TokenStream::new();
::quote::__private::push_group(&mut _s,
::quote::__private::Delimiter::Parenthesis,
{
let mut _s = ::quote::__private::TokenStream::new();
::quote::ToTokens::to_tokens(&call, &mut _s);
_s
});
::quote::__private::push_dot(&mut _s);
::quote::__private::parse(&mut _s, "0");
_s
}quote::quote! { (#call).0 }
99 } else {
100 call
101 };
102 if serialize {
103 {
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, "serde");
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "AsSerialize");
::quote::__private::push_group(&mut _s,
::quote::__private::Delimiter::Parenthesis,
{
let mut _s = ::quote::__private::TokenStream::new();
::quote::ToTokens::to_tokens(&value, &mut _s);
_s
});
_s
}quote::quote! { ::miniextendr_api::serde::AsSerialize(#value) }
104 } else {
105 value
106 }
107}
108
109pub(crate) fn resolve_visibility(
114 marker: Option<bool>,
115 attr: Option<bool>,
116 shape_default: bool,
117 span: proc_macro2::Span,
118) -> syn::Result<bool> {
119 match (marker, attr) {
120 (Some(m), Some(a)) if m != a => Err(syn::Error::new(
121 span,
122 "the return type's visibility marker and the `invisible` / `visible` attribute disagree; keep one of them (or make them agree)",
123 )),
124 (Some(m), _) => Ok(m),
125 (None, Some(a)) => Ok(a),
126 (None, None) => Ok(shape_default),
127 }
128}
129
130pub(crate) fn visibility_marker_error(ty: &syn::Type, what: &str) -> Option<syn::Error> {
135 let (marker, inner) = peel_visibility_marker(ty);
136 if what == "return"
137 && let Some((param_marker, _)) = outer_param_marker(inner)
138 {
139 return Some(syn::Error::new_spanned(
140 ty,
141 ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("`{0}<T>` marks a parameter only; it cannot be used as a return type (return the inner `T`)",
param_marker.name()))
})format!(
142 "`{}<T>` marks a parameter only; it cannot be used as a return type (return the \
143 inner `T`)",
144 param_marker.name()
145 ),
146 ));
147 }
148 marker?;
149 if what == "argument" {
150 return Some(syn::Error::new_spanned(
151 ty,
152 "`Invisible<T>` / `Visible<T>` mark the return type only; they cannot be used as a parameter type",
153 ));
154 }
155 if peel_visibility_marker(inner).0.is_some() {
156 return Some(syn::Error::new_spanned(
157 ty,
158 "visibility markers cannot be nested; use a single `Invisible<T>` or `Visible<T>` around the return type",
159 ));
160 }
161 None
162}
163
164#[inline]
166pub(crate) fn is_sexp_type(ty: &syn::Type) -> bool {
167 #[allow(non_exhaustive_omitted_patterns)] match ty {
syn::Type::Path(p) if
p.path.segments.last().map(|s| s.ident == "SEXP").unwrap_or(false) =>
true,
_ => false,
}matches!(ty, syn::Type::Path(p) if p
168 .path
169 .segments
170 .last()
171 .map(|s| s.ident == "SEXP")
172 .unwrap_or(false))
173}
174
175const MAIN_THREAD_BOUND: &[&str] = &[
187 "SEXP",
188 "Call",
190 "CallerCall",
191 "Quoted",
194 "Quosure",
195 "LazyDots",
197 "AltrepSexp",
198 "RDVector",
199 "RDMatrix",
200 "RndVec",
201 "RndMat",
202 "ProtectedStrVec",
203 "StrVec",
204 "RCow",
205 "BuiltDataFrame",
206 "NamedListBuilder",
207 "BuiltList",
208 "DataFrameShape",
209 "Preserved",
213 "REvalError",
214 "RTxtProgressBar",
215];
216
217#[inline]
224pub(crate) fn is_main_thread_bound_input(ty: &syn::Type) -> bool {
225 is_rooted_view(ty)
226 || crate::externalptr_args::external_arg(ty).is_some()
229 || #[allow(non_exhaustive_omitted_patterns)] match ty {
syn::Type::Path(p) if
p.path.segments.last().map(|s|
MAIN_THREAD_BOUND.contains(&s.ident.to_string().as_str())).unwrap_or(false)
=> true,
_ => false,
}matches!(ty, syn::Type::Path(p) if p
230 .path
231 .segments
232 .last()
233 .map(|s| MAIN_THREAD_BOUND.contains(&s.ident.to_string().as_str()))
234 .unwrap_or(false))
235}
236
237pub(crate) fn is_main_thread_bound_return(ty: &syn::Type) -> bool {
247 match ty {
248 syn::Type::Path(p) => p.path.segments.last().is_some_and(|seg| {
249 if MAIN_THREAD_BOUND.contains(&seg.ident.to_string().as_str()) {
250 return true;
251 }
252 if let syn::PathArguments::AngleBracketed(ab) = &seg.arguments {
253 return ab.args.iter().any(|arg| {
254 #[allow(non_exhaustive_omitted_patterns)] match arg {
syn::GenericArgument::Type(t) if is_main_thread_bound_return(t) => true,
_ => false,
}matches!(arg, syn::GenericArgument::Type(t) if is_main_thread_bound_return(t))
255 });
256 }
257 false
258 }),
259 syn::Type::Reference(r) => is_main_thread_bound_return(&r.elem),
260 syn::Type::Paren(p) => is_main_thread_bound_return(&p.elem),
261 syn::Type::Group(g) => is_main_thread_bound_return(&g.elem),
262 syn::Type::Tuple(t) => t.elems.iter().any(is_main_thread_bound_return),
263 syn::Type::Array(a) => is_main_thread_bound_return(&a.elem),
264 syn::Type::Slice(s) => is_main_thread_bound_return(&s.elem),
265 _ => false,
266 }
267}
268
269pub(crate) fn signature_holds_r_values(sig: &syn::Signature) -> bool {
275 sig.inputs.iter().any(|arg| {
276 #[allow(non_exhaustive_omitted_patterns)] match arg {
syn::FnArg::Typed(pt) if
is_main_thread_bound_input(&pt.ty) ||
unevaluated_param(&pt.ty).is_some() => true,
_ => false,
}matches!(arg, syn::FnArg::Typed(pt)
277 if is_main_thread_bound_input(&pt.ty) || unevaluated_param(&pt.ty).is_some())
278 }) || #[allow(non_exhaustive_omitted_patterns)] match &sig.output {
syn::ReturnType::Type(_, ty) if is_main_thread_bound_return(ty) => true,
_ => false,
}matches!(&sig.output, syn::ReturnType::Type(_, ty) if is_main_thread_bound_return(ty))
279}
280
281pub(crate) fn call_marker(ty: &syn::Type) -> Option<crate::r_wrapper_builder::CallAttribution> {
290 let syn::Type::Path(p) = ty else {
291 return None;
292 };
293 let seg = p.path.segments.last()?;
294 if !#[allow(non_exhaustive_omitted_patterns)] match seg.arguments {
syn::PathArguments::None => true,
_ => false,
}matches!(seg.arguments, syn::PathArguments::None) {
295 return None;
296 }
297 match seg.ident.to_string().as_str() {
298 "Call" => Some(crate::r_wrapper_builder::CallAttribution::Wrapper),
299 "CallerCall" => Some(crate::r_wrapper_builder::CallAttribution::Caller),
300 _ => None,
301 }
302}
303
304pub(crate) fn is_nargs_marker(ty: &syn::Type) -> bool {
316 let syn::Type::Path(p) = ty else {
317 return false;
318 };
319 p.path
320 .segments
321 .last()
322 .is_some_and(|seg| seg.ident == "NArgs" && seg.arguments.is_none())
323}
324
325pub(crate) fn is_rng_guard_param(ty: &syn::Type) -> bool {
334 let syn::Type::Reference(reference) = ty else {
335 return false;
336 };
337 reference.mutability.is_none()
338 && #[allow(non_exhaustive_omitted_patterns)] match reference.elem.as_ref() {
syn::Type::Path(p) if
p.qself.is_none() &&
p.path.segments.last().is_some_and(|seg|
seg.ident == "RngGuard" && seg.arguments.is_none()) => true,
_ => false,
}matches!(reference.elem.as_ref(), syn::Type::Path(p) if p.qself.is_none() && p
339 .path
340 .segments
341 .last()
342 .is_some_and(|seg| seg.ident == "RngGuard" && seg.arguments.is_none()))
343}
344
345pub(crate) fn mentions_rng_guard(ty: &syn::Type) -> bool {
349 match ty {
350 syn::Type::Path(p) => p.path.segments.iter().any(|seg| {
351 seg.ident == "RngGuard"
352 || match &seg.arguments {
353 syn::PathArguments::AngleBracketed(ab) => ab.args.iter().any(
354 |arg| #[allow(non_exhaustive_omitted_patterns)] match arg {
syn::GenericArgument::Type(t) if mentions_rng_guard(t) => true,
_ => false,
}matches!(arg, syn::GenericArgument::Type(t) if mentions_rng_guard(t)),
355 ),
356 _ => false,
357 }
358 }),
359 syn::Type::Reference(r) => mentions_rng_guard(&r.elem),
360 syn::Type::Paren(p) => mentions_rng_guard(&p.elem),
361 syn::Type::Group(g) => mentions_rng_guard(&g.elem),
362 syn::Type::Tuple(t) => t.elems.iter().any(mentions_rng_guard),
363 syn::Type::Array(a) => mentions_rng_guard(&a.elem),
364 syn::Type::Slice(s) => mentions_rng_guard(&s.elem),
365 _ => false,
366 }
367}
368
369pub(crate) fn rng_guard_arg() -> proc_macro2::TokenStream {
374 {
let mut _s = ::quote::__private::TokenStream::new();
::quote::__private::push_and(&mut _s);
::quote::__private::push_ident(&mut _s, "unsafe");
::quote::__private::push_group(&mut _s,
::quote::__private::Delimiter::Brace,
{
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, "rng");
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "RngGuard");
::quote::__private::push_colon2(&mut _s);
::quote::__private::push_ident(&mut _s, "__mx_loaded_by_wrapper");
::quote::__private::push_group(&mut _s,
::quote::__private::Delimiter::Parenthesis,
::quote::__private::TokenStream::new());
_s
});
_s
}quote::quote! {
375 &unsafe { ::miniextendr_api::rng::RngGuard::__mx_loaded_by_wrapper() }
376 }
377}
378
379pub(crate) fn is_rooted_view(ty: &syn::Type) -> bool {
386 let syn::Type::Path(path) = ty else {
387 return false;
388 };
389 if path.qself.is_some() {
390 return false;
391 }
392 let Some(seg) = path.path.segments.last() else {
393 return false;
394 };
395 if seg.ident == "StrVec" || seg.ident == "RCow" || seg.ident == "Cow" {
396 return true;
397 }
398 seg.ident == "Vec"
399 && first_type_argument(seg).is_some_and(|elem| {
400 let elem = option_inner_type(elem).unwrap_or(elem);
401 #[allow(non_exhaustive_omitted_patterns)] match elem {
syn::Type::Path(p) if
p.qself.is_none() &&
p.path.segments.last().is_some_and(|s| s.ident == "Cow") => true,
_ => false,
}matches!(elem, syn::Type::Path(p) if p.qself.is_none() && p.path.segments.last()
402 .is_some_and(|s| s.ident == "Cow"))
403 })
404}
405
406pub(crate) fn is_rooted_reference(ty: &syn::Type) -> bool {
411 let ty = option_inner_type(ty).unwrap_or(ty);
412 if let syn::Type::Path(path) = ty
413 && path.qself.is_none()
414 && let Some(segment) = path.path.segments.last()
415 && segment.ident == "Vec"
416 {
417 return first_type_argument(segment).is_some_and(is_rooted_reference);
418 }
419 let syn::Type::Reference(reference) = ty else {
420 return false;
421 };
422 if reference.mutability.is_some() {
423 return false;
424 }
425 match reference.elem.as_ref() {
426 syn::Type::Slice(_) => true,
427 syn::Type::Path(path) if path.qself.is_none() => {
428 path.path.segments.last().is_some_and(|seg| {
429 seg.arguments.is_none()
430 && #[allow(non_exhaustive_omitted_patterns)] match seg.ident.to_string().as_str()
{
"i32" | "f64" | "u8" | "RLogical" | "Rcomplex" | "str" => true,
_ => false,
}matches!(
431 seg.ident.to_string().as_str(),
432 "i32" | "f64" | "u8" | "RLogical" | "Rcomplex" | "str"
433 )
434 })
435 }
436 _ => false,
437 }
438}
439
440pub(crate) fn static_rooted_view_binding(
448 ident: &syn::Ident,
449 ty: &syn::Type,
450) -> Option<proc_macro2::TokenStream> {
451 let lifetime = static_lifetime(ty)?;
452 Some({
let mut _s = ::quote::__private::TokenStream::new();
let _span: ::quote::__private::Span =
::quote::__private::get_span(lifetime.span()).__into_span();
::quote::__private::push_colon2_spanned(&mut _s, _span);
::quote::__private::push_ident_spanned(&mut _s, _span, "core");
::quote::__private::push_colon2_spanned(&mut _s, _span);
::quote::__private::push_ident_spanned(&mut _s, _span, "compile_error");
::quote::__private::push_bang_spanned(&mut _s, _span);
::quote::__private::push_group_spanned(&mut _s, _span,
::quote::__private::Delimiter::Parenthesis,
{
let mut _s = ::quote::__private::TokenStream::new();
let _span: ::quote::__private::Span =
::quote::__private::get_span(_span).__into_span();
::quote::__private::parse_spanned(&mut _s, _span,
"\"this parameter borrows the call\'s argument and cannot be `\'static`; write `\'_` or \\\n elide the lifetime, or take an owned type (`ProtectedStrVec`, `Vec`, `String`) to \\\n keep the data after the call\"");
_s
});
::quote::__private::push_semi_spanned(&mut _s, _span);
::quote::__private::push_ident_spanned(&mut _s, _span, "let");
::quote::ToTokens::to_tokens(&ident, &mut _s);
::quote::__private::push_colon_spanned(&mut _s, _span);
::quote::ToTokens::to_tokens(&ty, &mut _s);
::quote::__private::push_eq_spanned(&mut _s, _span);
::quote::__private::push_colon2_spanned(&mut _s, _span);
::quote::__private::push_ident_spanned(&mut _s, _span, "core");
::quote::__private::push_colon2_spanned(&mut _s, _span);
::quote::__private::push_ident_spanned(&mut _s, _span, "option");
::quote::__private::push_colon2_spanned(&mut _s, _span);
::quote::__private::push_ident_spanned(&mut _s, _span, "Option");
::quote::__private::push_colon2_spanned(&mut _s, _span);
::quote::__private::push_lt_spanned(&mut _s, _span);
::quote::ToTokens::to_tokens(&ty, &mut _s);
::quote::__private::push_gt_spanned(&mut _s, _span);
::quote::__private::push_colon2_spanned(&mut _s, _span);
::quote::__private::push_ident_spanned(&mut _s, _span, "None");
::quote::__private::push_dot_spanned(&mut _s, _span);
::quote::__private::push_ident_spanned(&mut _s, _span, "unwrap");
::quote::__private::push_group_spanned(&mut _s, _span,
::quote::__private::Delimiter::Parenthesis,
{
let _: ::quote::__private::Span =
::quote::__private::get_span(_span).__into_span();
::quote::__private::TokenStream::new()
});
::quote::__private::push_semi_spanned(&mut _s, _span);
_s
}quote::quote_spanned! {lifetime.span()=>
453 ::core::compile_error!(
454 "this parameter borrows the call's argument and cannot be `'static`; write `'_` or \
455 elide the lifetime, or take an owned type (`ProtectedStrVec`, `Vec`, `String`) to \
456 keep the data after the call"
457 );
458 let #ident: #ty = ::core::option::Option::<#ty>::None.unwrap();
459 })
460}
461
462pub(crate) fn static_lifetime(ty: &syn::Type) -> Option<&syn::Lifetime> {
464 let is_static = |lt: &&syn::Lifetime| lt.ident == "static";
465 match ty {
466 syn::Type::Reference(r) => r
467 .lifetime
468 .as_ref()
469 .filter(is_static)
470 .or_else(|| static_lifetime(&r.elem)),
471 syn::Type::Path(p) => p.path.segments.iter().find_map(|seg| {
472 let syn::PathArguments::AngleBracketed(args) = &seg.arguments else {
473 return None;
474 };
475 args.args.iter().find_map(|arg| match arg {
476 syn::GenericArgument::Lifetime(lt) => Some(lt).filter(is_static),
477 syn::GenericArgument::Type(ty) => static_lifetime(ty),
478 _ => None,
479 })
480 }),
481 syn::Type::Slice(s) => static_lifetime(&s.elem),
482 syn::Type::Array(a) => static_lifetime(&a.elem),
483 syn::Type::Paren(p) => static_lifetime(&p.elem),
484 syn::Type::Group(g) => static_lifetime(&g.elem),
485 syn::Type::Tuple(t) => t.elems.iter().find_map(static_lifetime),
486 _ => None,
487 }
488}
489
490pub(crate) fn is_lazy_dots_marker(ty: &syn::Type) -> bool {
504 let syn::Type::Path(p) = ty else {
505 return false;
506 };
507 p.path.segments.last().is_some_and(|seg| {
508 seg.ident == "LazyDots"
509 && match &seg.arguments {
510 syn::PathArguments::None => true,
511 syn::PathArguments::AngleBracketed(ab) => ab
512 .args
513 .iter()
514 .all(|arg| #[allow(non_exhaustive_omitted_patterns)] match arg {
syn::GenericArgument::Lifetime(_) => true,
_ => false,
}matches!(arg, syn::GenericArgument::Lifetime(_))),
515 syn::PathArguments::Parenthesized(_) => false,
516 }
517 })
518}
519
520pub(crate) fn mentions_lazy_dots(ty: &syn::Type) -> bool {
522 match ty {
523 syn::Type::Path(p) => p.path.segments.iter().any(|seg| {
524 seg.ident == "LazyDots"
525 || match &seg.arguments {
526 syn::PathArguments::AngleBracketed(ab) => ab.args.iter().any(
527 |arg| #[allow(non_exhaustive_omitted_patterns)] match arg {
syn::GenericArgument::Type(t) if mentions_lazy_dots(t) => true,
_ => false,
}matches!(arg, syn::GenericArgument::Type(t) if mentions_lazy_dots(t)),
528 ),
529 _ => false,
530 }
531 }),
532 syn::Type::Reference(r) => mentions_lazy_dots(&r.elem),
533 syn::Type::Paren(p) => mentions_lazy_dots(&p.elem),
534 syn::Type::Group(g) => mentions_lazy_dots(&g.elem),
535 syn::Type::Tuple(t) => t.elems.iter().any(mentions_lazy_dots),
536 syn::Type::Array(a) => mentions_lazy_dots(&a.elem),
537 syn::Type::Slice(s) => mentions_lazy_dots(&s.elem),
538 _ => false,
539 }
540}
541
542#[derive(#[automatically_derived]
impl ::core::fmt::Debug for UnevaluatedKind {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::write_str(f,
match self {
UnevaluatedKind::Quoted => "Quoted",
UnevaluatedKind::Quosure => "Quosure",
})
}
}Debug, #[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for UnevaluatedKind { }
#[automatically_derived]
impl ::core::clone::Clone for UnevaluatedKind {
#[inline]
fn clone(&self) -> Self { *self }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for UnevaluatedKind { }Copy, #[automatically_derived]
impl ::core::marker::StructuralPartialEq for UnevaluatedKind { }
#[automatically_derived]
impl ::core::cmp::PartialEq for UnevaluatedKind {
#[inline]
fn eq(&self, other: &Self) -> bool {
::core::intrinsics::discriminant_value(self) ==
::core::intrinsics::discriminant_value(other)
}
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for UnevaluatedKind { }Eq)]
548pub(crate) enum UnevaluatedKind {
549 Quoted,
551 Quosure,
553}
554
555impl UnevaluatedKind {
556 pub(crate) fn name(self) -> &'static str {
558 match self {
559 Self::Quoted => "Quoted",
560 Self::Quosure => "Quosure",
561 }
562 }
563
564 pub(crate) fn r_call_arg(self, p: &str) -> String {
568 let capture = match self {
569 Self::Quoted => ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("list(substitute({0}), parent.frame())",
p))
})format!("list(substitute({p}), parent.frame())"),
570 Self::Quosure => ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("rlang::enquo({0})", p))
})format!("rlang::enquo({p})"),
571 };
572 ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("if (missing({0})) quote(expr=) else {1}",
p, capture))
})format!("if (missing({p})) quote(expr=) else {capture}")
573 }
574
575 pub(crate) fn api_path(self) -> proc_macro2::TokenStream {
577 match self {
578 Self::Quoted => {
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, "Quoted");
_s
}quote::quote!(::miniextendr_api::Quoted),
579 Self::Quosure => {
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, "Quosure");
_s
}quote::quote!(::miniextendr_api::Quosure),
580 }
581 }
582
583 fn of_segment(seg: &syn::PathSegment) -> Option<Self> {
586 let lifetimes_only = match &seg.arguments {
587 syn::PathArguments::None => true,
588 syn::PathArguments::AngleBracketed(ab) => ab
589 .args
590 .iter()
591 .all(|arg| #[allow(non_exhaustive_omitted_patterns)] match arg {
syn::GenericArgument::Lifetime(_) => true,
_ => false,
}matches!(arg, syn::GenericArgument::Lifetime(_))),
592 syn::PathArguments::Parenthesized(_) => false,
593 };
594 if !lifetimes_only {
595 return None;
596 }
597 match seg.ident.to_string().as_str() {
598 "Quoted" => Some(Self::Quoted),
599 "Quosure" => Some(Self::Quosure),
600 _ => None,
601 }
602 }
603}
604
605#[derive(#[automatically_derived]
impl ::core::fmt::Debug for UnevaluatedParam {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_struct_field2_finish(f,
"UnevaluatedParam", "kind", &self.kind, "optional",
&&self.optional)
}
}Debug, #[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for UnevaluatedParam { }
#[automatically_derived]
impl ::core::clone::Clone for UnevaluatedParam {
#[inline]
fn clone(&self) -> Self {
let _: ::core::clone::AssertParamIsClone<UnevaluatedKind>;
let _: ::core::clone::AssertParamIsClone<bool>;
*self
}
}Clone, #[automatically_derived]
impl ::core::marker::Copy for UnevaluatedParam { }Copy, #[automatically_derived]
impl ::core::marker::StructuralPartialEq for UnevaluatedParam { }
#[automatically_derived]
impl ::core::cmp::PartialEq for UnevaluatedParam {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.optional == other.optional && self.kind == other.kind
}
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for UnevaluatedParam {
#[inline]
#[doc(hidden)]
#[coverage(off)]
fn assert_fields_are_eq(&self) {
let _: ::core::cmp::AssertParamIsEq<UnevaluatedKind>;
let _: ::core::cmp::AssertParamIsEq<bool>;
}
}Eq)]
608pub(crate) struct UnevaluatedParam {
609 pub(crate) kind: UnevaluatedKind,
610 pub(crate) optional: bool,
613}
614
615pub(crate) fn unevaluated_param(ty: &syn::Type) -> Option<UnevaluatedParam> {
621 let marker = |ty: &syn::Type| match ty {
622 syn::Type::Path(p) => p.path.segments.last().and_then(UnevaluatedKind::of_segment),
623 _ => None,
624 };
625 if let Some(kind) = marker(ty) {
626 return Some(UnevaluatedParam {
627 kind,
628 optional: false,
629 });
630 }
631 let inner = crate::miniextendr_fn::get_missing_inner_type(ty)?;
632 marker(inner).map(|kind| UnevaluatedParam {
633 kind,
634 optional: true,
635 })
636}
637
638pub(crate) fn mentions_unevaluated_marker(ty: &syn::Type) -> bool {
640 match ty {
641 syn::Type::Path(p) => p.path.segments.iter().any(|seg| {
642 #[allow(non_exhaustive_omitted_patterns)] match seg.ident.to_string().as_str()
{
"Quoted" | "Quosure" => true,
_ => false,
}matches!(seg.ident.to_string().as_str(), "Quoted" | "Quosure")
643 || match &seg.arguments {
644 syn::PathArguments::AngleBracketed(ab) => ab.args.iter().any(|arg| {
645 #[allow(non_exhaustive_omitted_patterns)] match arg {
syn::GenericArgument::Type(t) if mentions_unevaluated_marker(t) => true,
_ => false,
}matches!(arg, syn::GenericArgument::Type(t) if mentions_unevaluated_marker(t))
646 }),
647 _ => false,
648 }
649 }),
650 syn::Type::Reference(r) => mentions_unevaluated_marker(&r.elem),
651 syn::Type::Paren(p) => mentions_unevaluated_marker(&p.elem),
652 syn::Type::Group(g) => mentions_unevaluated_marker(&g.elem),
653 syn::Type::Tuple(t) => t.elems.iter().any(mentions_unevaluated_marker),
654 syn::Type::Array(a) => mentions_unevaluated_marker(&a.elem),
655 syn::Type::Slice(s) => mentions_unevaluated_marker(&s.elem),
656 _ => false,
657 }
658}
659
660#[derive(#[automatically_derived]
impl ::core::fmt::Debug for ParamMarker {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::write_str(f,
match self {
ParamMarker::Checked => "Checked",
ParamMarker::Unchecked => "Unchecked",
})
}
}Debug, #[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for ParamMarker { }
#[automatically_derived]
impl ::core::clone::Clone for ParamMarker {
#[inline]
fn clone(&self) -> Self { *self }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for ParamMarker { }Copy, #[automatically_derived]
impl ::core::marker::StructuralPartialEq for ParamMarker { }
#[automatically_derived]
impl ::core::cmp::PartialEq for ParamMarker {
#[inline]
fn eq(&self, other: &Self) -> bool {
::core::intrinsics::discriminant_value(self) ==
::core::intrinsics::discriminant_value(other)
}
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for ParamMarker { }Eq)]
668pub(crate) enum ParamMarker {
669 Checked,
671 Unchecked,
673}
674
675#[derive(#[automatically_derived]
impl ::core::fmt::Debug for ParamMarkerFamily {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::write_str(f, "Preconditions")
}
}Debug, #[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for ParamMarkerFamily { }
#[automatically_derived]
impl ::core::clone::Clone for ParamMarkerFamily {
#[inline]
fn clone(&self) -> Self { *self }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for ParamMarkerFamily { }Copy, #[automatically_derived]
impl ::core::marker::StructuralPartialEq for ParamMarkerFamily { }
#[automatically_derived]
impl ::core::cmp::PartialEq for ParamMarkerFamily {
#[inline]
fn eq(&self, other: &Self) -> bool { true }
}PartialEq, #[automatically_derived]
impl ::core::cmp::Eq for ParamMarkerFamily { }Eq)]
678pub(crate) enum ParamMarkerFamily {
679 Preconditions,
681}
682
683impl ParamMarker {
684 fn from_ident(ident: &syn::Ident) -> Option<Self> {
686 match ident.to_string().as_str() {
687 "Checked" => Some(Self::Checked),
688 "Unchecked" => Some(Self::Unchecked),
689 _ => None,
690 }
691 }
692
693 pub(crate) fn name(self) -> &'static str {
695 match self {
696 Self::Checked => "Checked",
697 Self::Unchecked => "Unchecked",
698 }
699 }
700
701 pub(crate) fn family(self) -> ParamMarkerFamily {
703 match self {
704 Self::Checked | Self::Unchecked => ParamMarkerFamily::Preconditions,
705 }
706 }
707
708 pub(crate) fn ctor_path(self) -> proc_macro2::TokenStream {
710 match self {
711 Self::Checked => {
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, "Checked");
_s
}quote::quote!(::miniextendr_api::Checked),
712 Self::Unchecked => {
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, "Unchecked");
_s
}quote::quote!(::miniextendr_api::Unchecked),
713 }
714 }
715
716 pub(crate) fn preconditions(self) -> Option<bool> {
719 match self {
720 Self::Checked => Some(true),
721 Self::Unchecked => Some(false),
722 }
723 }
724}
725
726fn outer_param_marker(ty: &syn::Type) -> Option<(ParamMarker, &syn::Type)> {
731 let syn::Type::Path(p) = ty else {
732 return None;
733 };
734 let seg = p.path.segments.last()?;
735 let marker = ParamMarker::from_ident(&seg.ident)?;
736 let syn::PathArguments::AngleBracketed(ab) = &seg.arguments else {
737 return None;
738 };
739 let mut types = ab.args.iter().filter_map(|arg| match arg {
740 syn::GenericArgument::Type(t) => Some(t),
741 _ => None,
742 });
743 let inner = types.next()?;
744 if types.next().is_some() {
745 return None;
746 }
747 Some((marker, inner))
748}
749
750pub(crate) fn peel_param_markers(ty: &syn::Type) -> syn::Result<(Vec<ParamMarker>, &syn::Type)> {
757 let mut markers: Vec<ParamMarker> = Vec::new();
758 let mut inner = ty;
759 while let Some((marker, next)) = outer_param_marker(inner) {
760 if let Some(first) = markers.iter().find(|m| m.family() == marker.family()) {
761 return Err(syn::Error::new_spanned(
762 ty,
763 ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("at most one precondition marker per parameter: `{0}` already decides this parameter\'s R-side type checks, so drop `{1}`",
first.name(), marker.name()))
})format!(
764 "at most one precondition marker per parameter: `{}` already decides this \
765 parameter's R-side type checks, so drop `{}`",
766 first.name(),
767 marker.name()
768 ),
769 ));
770 }
771 markers.push(marker);
772 inner = next;
773 }
774 if let Some(nested) = nested_param_marker(inner) {
775 return Err(syn::Error::new_spanned(
776 ty,
777 ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("put `{0}` outermost: `{0}<Option<T>>`, not `Option<{0}<T>>`; the marker applies to the whole parameter",
nested.name()))
})format!(
778 "put `{0}` outermost: `{0}<Option<T>>`, not `Option<{0}<T>>`; the marker \
779 applies to the whole parameter",
780 nested.name()
781 ),
782 ));
783 }
784 Ok((markers, inner))
785}
786
787fn nested_param_marker(ty: &syn::Type) -> Option<ParamMarker> {
790 let arg = match ty {
791 syn::Type::Reference(r) => r.elem.as_ref(),
792 syn::Type::Paren(p) => p.elem.as_ref(),
793 syn::Type::Path(p) => {
794 let seg = p.path.segments.last()?;
795 if !#[allow(non_exhaustive_omitted_patterns)] match seg.ident.to_string().as_str()
{
"Option" | "Missing" | "Vec" => true,
_ => false,
}matches!(seg.ident.to_string().as_str(), "Option" | "Missing" | "Vec") {
796 return None;
797 }
798 first_type_argument(seg)?
799 }
800 _ => return None,
801 };
802 match outer_param_marker(arg) {
803 Some((marker, _)) => Some(marker),
804 None => nested_param_marker(arg),
805 }
806}
807
808pub(crate) fn marker_in_family(
810 markers: &[ParamMarker],
811 family: ParamMarkerFamily,
812) -> Option<ParamMarker> {
813 markers.iter().copied().find(|m| m.family() == family)
814}
815
816pub(crate) fn type_display(ty: &syn::Type) -> String {
831 let mut out = String::new();
832 write_type_tokens({
let mut _s = ::quote::__private::TokenStream::new();
::quote::ToTokens::to_tokens(&ty, &mut _s);
_s
}quote::quote!(#ty), &mut out);
833 out
834}
835
836fn write_type_tokens(tokens: proc_macro2::TokenStream, out: &mut String) {
837 use proc_macro2::{Delimiter, Spacing, TokenTree};
838
839 #[derive(#[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for Prev { }
#[automatically_derived]
impl ::core::clone::Clone for Prev {
#[inline]
fn clone(&self) -> Self { *self }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for Prev { }Copy, #[automatically_derived]
impl ::core::marker::StructuralPartialEq for Prev { }
#[automatically_derived]
impl ::core::cmp::PartialEq for Prev {
#[inline]
fn eq(&self, other: &Self) -> bool {
::core::intrinsics::discriminant_value(self) ==
::core::intrinsics::discriminant_value(other)
}
}PartialEq)]
841 enum Prev {
842 Glue,
845 Tick,
847 Word,
849 Keyword,
852 }
853
854 let mut prev = Prev::Glue;
855 let mut pending_arrow = false;
857 let mut pending_path_sep = false;
858 for tt in tokens {
859 match tt {
860 TokenTree::Ident(ident) => {
861 let name = ident.to_string();
862 if #[allow(non_exhaustive_omitted_patterns)] match prev {
Prev::Word | Prev::Keyword => true,
_ => false,
}matches!(prev, Prev::Word | Prev::Keyword) {
863 out.push(' ');
864 }
865 out.push_str(&name);
866 prev = if prev == Prev::Tick
867 || #[allow(non_exhaustive_omitted_patterns)] match name.as_str() {
"mut" | "const" | "dyn" | "impl" => true,
_ => false,
}matches!(name.as_str(), "mut" | "const" | "dyn" | "impl")
868 {
869 Prev::Keyword
870 } else {
871 Prev::Word
872 };
873 }
874 TokenTree::Literal(lit) => {
875 if #[allow(non_exhaustive_omitted_patterns)] match prev {
Prev::Word | Prev::Keyword => true,
_ => false,
}matches!(prev, Prev::Word | Prev::Keyword) {
876 out.push(' ');
877 }
878 out.push_str(&lit.to_string());
879 prev = Prev::Word;
880 }
881 TokenTree::Group(group) => {
882 if prev == Prev::Keyword
883 || (prev == Prev::Word && group.delimiter() == Delimiter::Bracket)
884 {
885 out.push(' ');
886 }
887 let (open, close) = match group.delimiter() {
888 Delimiter::Parenthesis => ("(", ")"),
889 Delimiter::Bracket => ("[", "]"),
890 Delimiter::Brace => ("{", "}"),
891 Delimiter::None => ("", ""),
892 };
893 out.push_str(open);
894 write_type_tokens(group.stream(), out);
895 out.push_str(close);
896 prev = Prev::Word;
897 }
898 TokenTree::Punct(punct) => {
899 let c = punct.as_char();
900 match c {
901 ',' | ';' => {
902 out.push(c);
903 out.push(' ');
904 prev = Prev::Glue;
905 }
906 '+' | '=' => {
907 out.push(' ');
908 out.push(c);
909 out.push(' ');
910 prev = Prev::Glue;
911 }
912 '-' if punct.spacing() == Spacing::Joint => {
913 out.push_str(" -");
914 pending_arrow = true;
915 }
916 '>' if pending_arrow => {
917 out.push_str("> ");
918 pending_arrow = false;
919 prev = Prev::Glue;
920 }
921 '>' => {
922 out.push('>');
923 prev = Prev::Word;
924 }
925 ':' if punct.spacing() == Spacing::Joint => {
926 out.push(':');
927 pending_path_sep = true;
928 prev = Prev::Glue;
929 }
930 ':' if pending_path_sep => {
931 out.push(':');
932 pending_path_sep = false;
933 prev = Prev::Glue;
934 }
935 ':' => {
936 out.push_str(": ");
938 prev = Prev::Glue;
939 }
940 '\'' => {
941 if #[allow(non_exhaustive_omitted_patterns)] match prev {
Prev::Word | Prev::Keyword => true,
_ => false,
}matches!(prev, Prev::Word | Prev::Keyword) {
942 out.push(' ');
943 }
944 out.push('\'');
945 prev = Prev::Tick;
946 }
947 _ => {
948 out.push(c);
949 prev = Prev::Glue;
950 }
951 }
952 }
953 }
954 }
955}
956
957pub(crate) fn erase_lifetimes(ty: &syn::Type) -> syn::Type {
963 use proc_macro2::{Group, Ident, TokenStream, TokenTree};
964
965 fn erase(tokens: TokenStream) -> TokenStream {
966 let mut out = Vec::new();
967 let mut iter = tokens.into_iter();
968 while let Some(tt) = iter.next() {
969 match tt {
970 TokenTree::Punct(p) if p.as_char() == '\'' => {
971 out.push(TokenTree::Punct(p));
972 if let Some(TokenTree::Ident(lifetime)) = iter.next() {
973 out.push(TokenTree::Ident(Ident::new("_", lifetime.span())));
974 }
975 }
976 TokenTree::Group(g) => {
977 let mut erased = Group::new(g.delimiter(), erase(g.stream()));
978 erased.set_span(g.span());
979 out.push(TokenTree::Group(erased));
980 }
981 other => out.push(other),
982 }
983 }
984 out.into_iter().collect()
985 }
986
987 syn::parse2(erase({
let mut _s = ::quote::__private::TokenStream::new();
::quote::ToTokens::to_tokens(&ty, &mut _s);
_s
}quote::quote!(#ty))).unwrap_or_else(|_| ty.clone())
988}
989
990#[cfg(test)]
993mod tests {
994 use super::{
995 ParamMarker, UnevaluatedKind, UnevaluatedParam, call_marker, choice_layer_name,
996 choice_layers, erase_lifetimes, is_lazy_dots_marker, is_main_thread_bound_input,
997 is_main_thread_bound_return, is_nargs_marker, is_rng_guard_param, match_arg_choices_ty,
998 mentions_lazy_dots, mentions_rng_guard, mentions_unevaluated_marker, peel_param_markers,
999 r_value_noun, type_display, unevaluated_param, visibility_marker_error,
1000 };
1001 use crate::r_wrapper_builder::CallAttribution;
1002
1003 fn ty(s: &str) -> syn::Type {
1004 syn::parse_str(s).unwrap()
1005 }
1006
1007 #[test]
1008 fn main_thread_bound_return_detects_nested_positions() {
1009 assert!(is_main_thread_bound_return(&ty("BuiltDataFrame")));
1011 assert!(is_main_thread_bound_return(&ty(
1012 "miniextendr_api::dataframe::BuiltDataFrame"
1013 )));
1014 assert!(is_main_thread_bound_return(&ty("DataFrameShape")));
1015 assert!(is_main_thread_bound_input(&ty("NamedListBuilder")));
1016 assert!(is_main_thread_bound_return(&ty("NamedListBuilder")));
1017 assert!(is_main_thread_bound_return(&ty(
1018 "Result<miniextendr_api::NamedListBuilder, String>"
1019 )));
1020 assert!(is_main_thread_bound_return(&ty("SEXP")));
1021 assert!(is_main_thread_bound_return(&ty(
1023 "Result<BuiltDataFrame, String>"
1024 )));
1025 assert!(is_main_thread_bound_return(&ty(
1026 "Result<DataFrameShape, std::string::String>"
1027 )));
1028 assert!(is_main_thread_bound_return(&ty("Option<BuiltDataFrame>")));
1029 assert!(is_main_thread_bound_return(&ty("Vec<BuiltDataFrame>")));
1030 assert!(is_main_thread_bound_return(&ty("(i32, BuiltDataFrame)")));
1031 assert!(is_main_thread_bound_input(&ty("Preserved")));
1033 assert!(is_main_thread_bound_return(&ty("Result<bool, REvalError>")));
1034 assert!(is_main_thread_bound_input(&ty(
1035 "miniextendr_api::txt_progress_bar::RTxtProgressBar"
1036 )));
1037 assert!(!is_main_thread_bound_return(&ty("i32")));
1039 assert!(!is_main_thread_bound_return(&ty(
1040 "Result<Vec<f64>, String>"
1041 )));
1042 assert!(!is_main_thread_bound_return(&ty("ExternalPtr<MyType>")));
1043 assert!(!is_main_thread_bound_return(&ty("DataFrame")));
1044 }
1045
1046 #[test]
1047 fn unevaluated_param_matches_bare_and_missing_markers_only() {
1048 let required = |kind| {
1049 Some(UnevaluatedParam {
1050 kind,
1051 optional: false,
1052 })
1053 };
1054 let optional = |kind| {
1055 Some(UnevaluatedParam {
1056 kind,
1057 optional: true,
1058 })
1059 };
1060 assert_eq!(
1061 unevaluated_param(&ty("Quoted")),
1062 required(UnevaluatedKind::Quoted)
1063 );
1064 assert_eq!(
1065 unevaluated_param(&ty("Quoted<'_>")),
1066 required(UnevaluatedKind::Quoted)
1067 );
1068 assert_eq!(
1069 unevaluated_param(&ty("::miniextendr_api::Quosure<'a>")),
1070 required(UnevaluatedKind::Quosure)
1071 );
1072 assert_eq!(
1073 unevaluated_param(&ty("Missing<Quoted>")),
1074 optional(UnevaluatedKind::Quoted)
1075 );
1076 assert_eq!(
1077 unevaluated_param(&ty("miniextendr_api::Missing<Quosure<'_>>")),
1078 optional(UnevaluatedKind::Quosure)
1079 );
1080 for other in [
1081 "Option<Quoted>",
1082 "&Quoted",
1083 "Vec<Quosure>",
1084 "Quoted<i32>",
1085 "Missing<Option<Quoted>>",
1086 "SEXP",
1087 ] {
1088 assert_eq!(unevaluated_param(&ty(other)), None, "{other}");
1089 }
1090 for misplaced in [
1093 "Option<Quoted>",
1094 "&Quoted",
1095 "Vec<Quosure<'_>>",
1096 "(i32, Quoted)",
1097 ] {
1098 assert!(mentions_unevaluated_marker(&ty(misplaced)), "{misplaced}");
1099 }
1100 assert!(!mentions_unevaluated_marker(&ty("Vec<i32>")));
1101 assert!(is_main_thread_bound_input(&ty("Quoted")));
1104 assert!(is_main_thread_bound_input(&ty("Quosure<'_>")));
1105 }
1106
1107 #[test]
1108 fn unevaluated_kind_r_call_args_never_force_the_argument() {
1109 assert_eq!(
1110 UnevaluatedKind::Quoted.r_call_arg("cond"),
1111 "if (missing(cond)) quote(expr=) else list(substitute(cond), parent.frame())"
1112 );
1113 assert_eq!(
1114 UnevaluatedKind::Quosure.r_call_arg("cols"),
1115 "if (missing(cols)) quote(expr=) else rlang::enquo(cols)"
1116 );
1117 }
1118
1119 #[test]
1120 fn call_marker_matches_bare_marker_types_only() {
1121 assert_eq!(call_marker(&ty("Call")), Some(CallAttribution::Wrapper));
1122 assert_eq!(
1123 call_marker(&ty("miniextendr_api::CallerCall")),
1124 Some(CallAttribution::Caller)
1125 );
1126 assert_eq!(
1127 call_marker(&ty("::miniextendr_api::call_marker::Call")),
1128 Some(CallAttribution::Wrapper)
1129 );
1130 assert_eq!(call_marker(&ty("Call<i32>")), None);
1132 assert_eq!(call_marker(&ty("&Call")), None);
1133 assert_eq!(call_marker(&ty("Option<Call>")), None);
1134 assert_eq!(call_marker(&ty("SEXP")), None);
1135 assert_eq!(call_marker(&ty("Caller")), None);
1136 assert!(is_main_thread_bound_input(&ty("Call")));
1138 assert!(is_main_thread_bound_input(&ty(
1139 "miniextendr_api::CallerCall"
1140 )));
1141 }
1142
1143 #[test]
1144 fn nargs_marker_matches_the_bare_type_only() {
1145 assert!(is_nargs_marker(&ty("NArgs")));
1146 assert!(is_nargs_marker(&ty("miniextendr_api::NArgs")));
1147 assert!(is_nargs_marker(&ty("::miniextendr_api::nargs::NArgs")));
1148 assert!(!is_nargs_marker(&ty("NArgs<i32>")));
1150 assert!(!is_nargs_marker(&ty("&NArgs")));
1151 assert!(!is_nargs_marker(&ty("Option<NArgs>")));
1152 assert!(!is_nargs_marker(&ty("Missing<NArgs>")));
1153 assert!(!is_nargs_marker(&ty("Nargs")));
1154 assert!(!is_main_thread_bound_input(&ty("NArgs")));
1156 }
1157
1158 #[test]
1159 fn rng_guard_param_is_a_shared_borrow_of_the_bare_type() {
1160 for guard in [
1161 "&RngGuard",
1162 "&'a RngGuard",
1163 "&miniextendr_api::RngGuard",
1164 "&::miniextendr_api::rng::RngGuard",
1165 ] {
1166 assert!(is_rng_guard_param(&ty(guard)), "{guard}");
1167 assert!(mentions_rng_guard(&ty(guard)), "{guard}");
1168 }
1169 for misplaced in [
1171 "&mut RngGuard",
1172 "RngGuard",
1173 "Option<&RngGuard>",
1174 "(i32, &RngGuard)",
1175 ] {
1176 assert!(!is_rng_guard_param(&ty(misplaced)), "{misplaced}");
1177 assert!(mentions_rng_guard(&ty(misplaced)), "{misplaced}");
1178 }
1179 for other in ["&RngGuard<i32>", "&Guard", "&str", "SEXP"] {
1180 assert!(!is_rng_guard_param(&ty(other)), "{other}");
1181 }
1182 assert!(!mentions_rng_guard(&ty("Vec<f64>")));
1183 }
1184
1185 #[test]
1186 fn lazy_dots_marker_matches_the_whole_type_by_value_only() {
1187 assert!(is_lazy_dots_marker(&ty("LazyDots")));
1188 assert!(is_lazy_dots_marker(&ty("LazyDots<'_>")));
1189 assert!(is_lazy_dots_marker(&ty("miniextendr_api::LazyDots<'a>")));
1190 assert!(is_lazy_dots_marker(&ty(
1191 "::miniextendr_api::lazy_dots::LazyDots"
1192 )));
1193 for other in [
1195 "LazyDots<i32>",
1196 "&LazyDots",
1197 "Option<LazyDots>",
1198 "Missing<LazyDots>",
1199 "Dots",
1200 "&Dots",
1201 ] {
1202 assert!(!is_lazy_dots_marker(&ty(other)), "{other}");
1203 }
1204 for misplaced in [
1206 "&LazyDots",
1207 "Option<LazyDots<'_>>",
1208 "Vec<LazyDots>",
1209 "Missing<LazyDots>",
1210 ] {
1211 assert!(mentions_lazy_dots(&ty(misplaced)), "{misplaced}");
1212 }
1213 assert!(!mentions_lazy_dots(&ty("&Dots")));
1214 assert!(is_main_thread_bound_input(&ty("LazyDots<'_>")));
1216 }
1217
1218 fn peeled(s: &str) -> (Vec<ParamMarker>, String) {
1220 let t = ty(s);
1221 let (markers, inner) = peel_param_markers(&t).expect("peels");
1222 (markers, type_display(inner))
1223 }
1224
1225 fn peel_error(s: &str) -> String {
1226 peel_param_markers(&ty(s))
1227 .expect_err("rejected")
1228 .to_string()
1229 }
1230
1231 #[test]
1232 fn param_markers_peel_the_outermost_marker() {
1233 assert_eq!(
1234 peeled("Checked<i32>"),
1235 (vec![ParamMarker::Checked], "i32".into())
1236 );
1237 assert_eq!(
1238 peeled("miniextendr_api::Unchecked<Vec<f64>>"),
1239 (vec![ParamMarker::Unchecked], "Vec<f64>".into())
1240 );
1241 assert_eq!(
1242 peeled("::miniextendr_api::precondition_marker::Checked<Option<i32>>"),
1243 (vec![ParamMarker::Checked], "Option<i32>".into())
1244 );
1245 assert_eq!(
1246 peeled("Checked<&'a [f64]>"),
1247 (vec![ParamMarker::Checked], "&'a [f64]".into())
1248 );
1249 assert_eq!(peeled("Checked"), (vec![], "Checked".into()));
1251 assert_eq!(
1252 peeled("Checked<i32, f64>"),
1253 (vec![], "Checked<i32, f64>".into())
1254 );
1255 assert_eq!(peeled("Vec<i32>"), (vec![], "Vec<i32>".into()));
1256 assert_eq!(peeled("Missing<i32>"), (vec![], "Missing<i32>".into()));
1257 }
1258
1259 #[test]
1260 fn param_markers_reject_stacks_and_nesting() {
1261 for stacked in ["Checked<Unchecked<i32>>", "Checked<Checked<i32>>"] {
1262 let err = peel_error(stacked);
1263 assert!(
1264 err.contains("at most one precondition marker per parameter"),
1265 "{stacked}: {err}"
1266 );
1267 }
1268 for nested in [
1269 "Option<Checked<i32>>",
1270 "Missing<Unchecked<i32>>",
1271 "Vec<Checked<i32>>",
1272 "&Checked<i32>",
1273 "Option<Vec<Unchecked<i32>>>",
1274 "Checked<Option<Unchecked<i32>>>",
1275 ] {
1276 let err = peel_error(nested);
1277 assert!(err.contains("outermost"), "{nested}: {err}");
1278 }
1279 assert!(peel_error("Option<Checked<i32>>").contains("put `Checked` outermost"));
1280 }
1281
1282 #[test]
1285 fn param_markers_are_rejected_in_return_position() {
1286 for ret in [
1287 "Checked<i32>",
1288 "miniextendr_api::Unchecked<Vec<f64>>",
1289 "Invisible<Checked<i32>>",
1290 ] {
1291 let err = visibility_marker_error(&ty(ret), "return")
1292 .expect("rejected")
1293 .to_string();
1294 assert!(err.contains("marks a parameter only"), "{ret}: {err}");
1295 }
1296 assert!(visibility_marker_error(&ty("Checked<i32>"), "argument").is_none());
1297 }
1298
1299 #[test]
1300 fn type_display_uses_source_spacing() {
1301 let cases = [
1302 ("i32", "i32"),
1303 ("AsFromStrVec<i32>", "AsFromStrVec<i32>"),
1304 ("Either<i32, String>", "Either<i32, String>"),
1305 ("&[f64]", "&[f64]"),
1306 ("&mut [f64]", "&mut [f64]"),
1307 ("&str", "&str"),
1308 ("&'a str", "&'a str"),
1309 ("&'static [u8]", "&'static [u8]"),
1310 ("Option<Vec<String>>", "Option<Vec<String>>"),
1311 (
1312 "HashMap<String, Vec<(i32, f64)>>",
1313 "HashMap<String, Vec<(i32, f64)>>",
1314 ),
1315 ("::std::vec::Vec<i32>", "::std::vec::Vec<i32>"),
1316 ("[u8; 4]", "[u8; 4]"),
1317 ("Box<[Mode]>", "Box<[Mode]>"),
1318 ("()", "()"),
1319 ("*const T", "*const T"),
1320 ("RCow<'a, i32>", "RCow<'a, i32>"),
1321 (
1322 "Box<dyn Fn(i32) -> i32 + Send + 'static>",
1323 "Box<dyn Fn(i32) -> i32 + Send + 'static>",
1324 ),
1325 ("impl Iterator<Item = i32>", "impl Iterator<Item = i32>"),
1326 ("<T as Trait>::Assoc", "<T as Trait>::Assoc"),
1327 ("for<'a> fn(&'a str) -> bool", "for<'a> fn(&'a str) -> bool"),
1328 ];
1329 for (src, want) in cases {
1330 assert_eq!(type_display(&ty(src)), want, "rendering `{src}`");
1331 }
1332 }
1333
1334 #[test]
1335 fn r_value_noun_names_the_r_side() {
1336 let cases = [
1337 ("DataFrame", "a data frame"),
1338 ("miniextendr_api::DataFrame", "a data frame"),
1339 ("List", "a list"),
1340 ("HashMap<String, i32>", "a list"),
1341 ("String", "a string"),
1342 ("&str", "a string"),
1343 ("f64", "a number"),
1344 ("i32", "an integer"),
1345 ("bool", "TRUE or FALSE"),
1346 ("Vec<f64>", "a numeric vector"),
1347 ("&[i32]", "an integer vector"),
1348 ("Vec<Option<String>>", "a character vector"),
1349 ("SEXP", "any other R value"),
1350 ("ExternalPtr<Model>", "an external pointer"),
1351 ("Model", "a `Model`"),
1352 ("Vec<Model>", "a `Vec<Model>`"),
1353 ];
1354 for (src, want) in cases {
1355 assert_eq!(r_value_noun(&ty(src)), want, "noun for `{src}`");
1356 }
1357 }
1358
1359 #[test]
1360 fn choice_layers_peel_missing_option_either() {
1361 let layers = |src: &str| {
1362 let t = ty(src);
1363 let l = choice_layers(&t);
1364 (
1365 l.missing,
1366 l.nullable,
1367 l.either_right.map(type_display),
1368 type_display(l.value),
1369 )
1370 };
1371 assert_eq!(layers("Mode"), (false, false, None, "Mode".to_string()));
1372 assert_eq!(
1373 layers("Missing<Option<Either<Mode, DataFrame>>>"),
1374 (
1375 true,
1376 true,
1377 Some("DataFrame".to_string()),
1378 "Mode".to_string()
1379 )
1380 );
1381 assert_eq!(
1382 layers("Either<Mode, Option<f64>>"),
1383 (
1384 false,
1385 false,
1386 Some("Option<f64>".to_string()),
1387 "Mode".to_string()
1388 )
1389 );
1390 assert_eq!(
1393 layers("Option<Missing<Mode>>"),
1394 (false, true, None, "Missing<Mode>".to_string())
1395 );
1396 assert_eq!(choice_layer_name(&ty("Missing<Mode>")), Some("Missing"));
1397 assert_eq!(choice_layer_name(&ty("Either<Mode, f64>")), Some("Either"));
1398 assert_eq!(choice_layer_name(&ty("Either<Mode>")), None);
1399 assert_eq!(
1400 type_display(match_arg_choices_ty(&ty("Missing<Vec<Mode>>"), true)),
1401 "Mode"
1402 );
1403 assert_eq!(
1404 type_display(match_arg_choices_ty(
1405 &ty("Option<Either<Mode, List>>"),
1406 false
1407 )),
1408 "Mode"
1409 );
1410 assert_eq!(
1412 layers("Either<Vec<Mode>, DataFrame>"),
1413 (
1414 false,
1415 false,
1416 Some("DataFrame".to_string()),
1417 "Vec<Mode>".to_string()
1418 )
1419 );
1420 assert_eq!(
1421 type_display(match_arg_choices_ty(
1422 &ty("Missing<Either<Vec<Mode>, DataFrame>>"),
1423 true
1424 )),
1425 "Mode"
1426 );
1427 }
1428
1429 #[test]
1430 fn erase_lifetimes_replaces_every_lifetime() {
1431 let erased = |s: &str| type_display(&erase_lifetimes(&ty(s)));
1432 assert_eq!(erased("&'a [f64]"), "&'_ [f64]");
1433 assert_eq!(erased("&'static str"), "&'_ str");
1434 assert_eq!(erased("&mut [i32]"), "&mut [i32]");
1435 assert_eq!(erased("RCow<'a, Vec<&'b str>>"), "RCow<'_, Vec<&'_ str>>");
1436 assert_eq!(erased("&str"), "&str");
1437 }
1438}
1439
1440pub(crate) fn is_option_type(ty: &syn::Type) -> bool {
1442 option_inner_type(ty).is_some()
1443}
1444
1445pub(crate) fn option_inner_type(ty: &syn::Type) -> Option<&syn::Type> {
1447 let syn::Type::Path(tp) = ty else {
1448 return None;
1449 };
1450 let seg = tp.path.segments.last()?;
1451 if seg.ident != "Option" {
1452 return None;
1453 }
1454 first_type_argument(seg)
1455}
1456
1457pub(crate) fn peel_external_ptr(ty: &syn::Type) -> &syn::Type {
1463 let syn::Type::Path(tp) = ty else {
1464 return ty;
1465 };
1466 match tp.path.segments.last() {
1467 Some(seg) if seg.ident == "ExternalPtr" => first_type_argument(seg).unwrap_or(ty),
1468 _ => ty,
1469 }
1470}
1471
1472#[derive(#[automatically_derived]
impl<'a> ::core::fmt::Debug for ChoiceLayers<'a> {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_struct_field4_finish(f, "ChoiceLayers",
"missing", &self.missing, "nullable", &self.nullable,
"either_right", &self.either_right, "value", &&self.value)
}
}Debug, #[automatically_derived]
#[doc(hidden)]
unsafe impl<'a> ::core::clone::TrivialClone for ChoiceLayers<'a> { }
#[automatically_derived]
impl<'a> ::core::clone::Clone for ChoiceLayers<'a> {
#[inline]
fn clone(&self) -> Self {
let _: ::core::clone::AssertParamIsClone<bool>;
let _: ::core::clone::AssertParamIsClone<Option<&'a syn::Type>>;
let _: ::core::clone::AssertParamIsClone<&'a syn::Type>;
*self
}
}Clone, #[automatically_derived]
impl<'a> ::core::marker::Copy for ChoiceLayers<'a> { }Copy)]
1486pub(crate) struct ChoiceLayers<'a> {
1487 pub missing: bool,
1489 pub nullable: bool,
1491 pub either_right: Option<&'a syn::Type>,
1493 pub value: &'a syn::Type,
1496}
1497
1498pub(crate) fn choice_layers(ty: &syn::Type) -> ChoiceLayers<'_> {
1500 let (missing, ty) = match crate::miniextendr_fn::get_missing_inner_type(ty) {
1501 Some(inner) => (true, inner),
1502 None => (false, ty),
1503 };
1504 let (nullable, ty) = match option_inner_type(ty) {
1505 Some(inner) => (true, inner),
1506 None => (false, ty),
1507 };
1508 let (either_right, value) = match either_arms(ty) {
1509 Some((left, right)) => (Some(right), left),
1510 None => (None, ty),
1511 };
1512 ChoiceLayers {
1513 missing,
1514 nullable,
1515 either_right,
1516 value,
1517 }
1518}
1519
1520pub(crate) fn choice_layer_name(ty: &syn::Type) -> Option<&'static str> {
1525 if crate::miniextendr_fn::get_missing_inner_type(ty).is_some() {
1526 Some("Missing")
1527 } else if option_inner_type(ty).is_some() {
1528 Some("Option")
1529 } else if either_arms(ty).is_some() {
1530 Some("Either")
1531 } else {
1532 None
1533 }
1534}
1535
1536pub(crate) fn either_arms(ty: &syn::Type) -> Option<(&syn::Type, &syn::Type)> {
1539 let syn::Type::Path(tp) = ty else {
1540 return None;
1541 };
1542 let seg = tp.path.segments.last()?;
1543 if seg.ident != "Either" {
1544 return None;
1545 }
1546 let syn::PathArguments::AngleBracketed(args) = &seg.arguments else {
1547 return None;
1548 };
1549 let mut types = args.args.iter().filter_map(|arg| match arg {
1550 syn::GenericArgument::Type(ty) => Some(ty),
1551 _ => None,
1552 });
1553 match (types.next(), types.next(), types.next()) {
1554 (Some(left), Some(right), None) => Some((left, right)),
1555 _ => None,
1556 }
1557}
1558
1559pub(crate) fn r_value_noun(ty: &syn::Type) -> String {
1565 let named = match ty {
1566 syn::Type::Reference(r) => match r.elem.as_ref() {
1567 syn::Type::Slice(s) => vector_noun(&s.elem),
1568 elem => return r_value_noun(elem),
1569 },
1570 syn::Type::Slice(s) => vector_noun(&s.elem),
1571 syn::Type::Path(tp) => {
1572 tp.path
1573 .segments
1574 .last()
1575 .and_then(|seg| match seg.ident.to_string().as_str() {
1576 "DataFrame" | "BuiltDataFrame" => Some("a data frame"),
1577 "List" | "ListMut" | "NamedList" | "HashMap" | "BTreeMap" => Some("a list"),
1578 "String" | "str" | "char" | "PathBuf" | "Path" => Some("a string"),
1579 "f64" | "f32" => Some("a number"),
1580 "i8" | "i16" | "i32" | "i64" | "isize" | "u16" | "u32" | "u64" | "usize" => {
1581 Some("an integer")
1582 }
1583 "bool" | "Rbool" | "Rboolean" => Some("TRUE or FALSE"),
1584 "Rcomplex" => Some("a complex number"),
1585 "ExternalPtr" => Some("an external pointer"),
1586 "SEXP" => Some("any other R value"),
1587 "Vec" => first_type_argument(seg).and_then(vector_noun),
1588 _ => None,
1589 })
1590 }
1591 _ => None,
1592 };
1593 named.map_or_else(|| ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("a `{0}`", type_display(ty)))
})format!("a `{}`", type_display(ty)), str::to_string)
1594}
1595
1596fn vector_noun(elem: &syn::Type) -> Option<&'static str> {
1598 let syn::Type::Path(tp) = elem else {
1599 return None;
1600 };
1601 let seg = tp.path.segments.last()?;
1602 match seg.ident.to_string().as_str() {
1603 "f64" | "f32" => Some("a numeric vector"),
1604 "i8" | "i16" | "i32" | "i64" | "isize" | "u16" | "u32" | "u64" | "usize" => {
1605 Some("an integer vector")
1606 }
1607 "String" | "str" => Some("a character vector"),
1608 "bool" | "Rbool" | "Rboolean" => Some("a logical vector"),
1609 "u8" => Some("a raw vector"),
1610 "Option" => first_type_argument(seg).and_then(vector_noun),
1611 _ => None,
1612 }
1613}
1614
1615pub(crate) fn match_arg_choices_ty(param_ty: &syn::Type, several_ok: bool) -> &syn::Type {
1626 let value = choice_layers(param_ty).value;
1627 if several_ok {
1628 classify_several_ok_container(value)
1629 .map(|(_, inner)| inner)
1630 .unwrap_or(value)
1631 } else {
1632 value
1633 }
1634}
1635
1636#[derive(#[automatically_derived]
impl ::core::fmt::Debug for SeveralOkContainer {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
match self {
Self::Vec => ::core::fmt::Formatter::write_str(f, "Vec"),
Self::BoxedSlice =>
::core::fmt::Formatter::write_str(f, "BoxedSlice"),
Self::Array(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f, "Array",
&__self_0),
Self::BorrowedSlice =>
::core::fmt::Formatter::write_str(f, "BorrowedSlice"),
}
}
}Debug, #[automatically_derived]
impl ::core::clone::Clone for SeveralOkContainer {
#[inline]
fn clone(&self) -> Self {
match self {
Self::Vec => SeveralOkContainer::Vec,
Self::BoxedSlice => SeveralOkContainer::BoxedSlice,
Self::Array(__self_0) =>
SeveralOkContainer::Array(::core::clone::Clone::clone(__self_0)),
Self::BorrowedSlice => SeveralOkContainer::BorrowedSlice,
}
}
}Clone)]
1641pub(crate) enum SeveralOkContainer {
1642 Vec,
1644 BoxedSlice,
1646 Array(usize),
1648 BorrowedSlice,
1650}
1651
1652pub(crate) fn classify_several_ok_container(
1658 ty: &syn::Type,
1659) -> Option<(SeveralOkContainer, &syn::Type)> {
1660 match ty {
1661 syn::Type::Path(tp) => {
1663 let seg = tp.path.segments.last()?;
1664 if seg.ident == "Vec" {
1665 let inner = first_type_argument(seg)?;
1666 return Some((SeveralOkContainer::Vec, inner));
1667 }
1668 if seg.ident == "Box"
1670 && let syn::PathArguments::AngleBracketed(ab) = &seg.arguments
1671 {
1672 for arg in &ab.args {
1673 if let syn::GenericArgument::Type(syn::Type::Slice(s)) = arg {
1674 return Some((SeveralOkContainer::BoxedSlice, s.elem.as_ref()));
1675 }
1676 }
1677 }
1678 None
1679 }
1680 syn::Type::Array(arr) => {
1682 if let syn::Expr::Lit(syn::ExprLit {
1683 lit: syn::Lit::Int(n),
1684 ..
1685 }) = &arr.len
1686 {
1687 let n = n.base10_parse::<usize>().ok()?;
1688 return Some((SeveralOkContainer::Array(n), arr.elem.as_ref()));
1689 }
1690 None
1691 }
1692 syn::Type::Reference(r) => {
1694 if let syn::Type::Slice(s) = r.elem.as_ref() {
1695 return Some((SeveralOkContainer::BorrowedSlice, s.elem.as_ref()));
1696 }
1697 None
1698 }
1699 _ => None,
1700 }
1701}
1702
1703pub(crate) fn self_ref_mutability(ty: &syn::Type) -> Option<bool> {
1706 if let syn::Type::Reference(reference) = ty
1707 && let syn::Type::Path(path) = reference.elem.as_ref()
1708 && path.path.is_ident("Self")
1709 {
1710 Some(reference.mutability.is_some())
1711 } else {
1712 None
1713 }
1714}