1use proc_macro2::{Span, TokenStream};
44use syn::spanned::Spanned;
45
46#[derive(#[automatically_derived]
#[doc(hidden)]
unsafe impl ::core::clone::TrivialClone for DotsRole { }
#[automatically_derived]
impl ::core::clone::Clone for DotsRole {
#[inline]
fn clone(&self) -> Self { *self }
}Clone, #[automatically_derived]
impl ::core::marker::Copy for DotsRole { }Copy, #[automatically_derived]
impl ::core::fmt::Debug for DotsRole {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::write_str(f,
match self {
DotsRole::Empty => "Empty",
DotsRole::Forward => "Forward",
DotsRole::Data => "Data",
})
}
}Debug, #[automatically_derived]
impl ::core::marker::StructuralPartialEq for DotsRole { }
#[automatically_derived]
impl ::core::cmp::PartialEq for DotsRole {
#[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 DotsRole { }Eq)]
48pub(crate) enum DotsRole {
49 Empty,
51 Forward,
53 Data,
55}
56
57impl DotsRole {
58 pub(crate) fn name(self) -> &'static str {
60 match self {
61 Self::Empty => "empty",
62 Self::Forward => "forward",
63 Self::Data => "data",
64 }
65 }
66}
67
68#[derive(#[automatically_derived]
impl ::core::clone::Clone for DotsPolicy {
#[inline]
fn clone(&self) -> Self {
DotsPolicy {
role: ::core::clone::Clone::clone(&self.role),
allow: ::core::clone::Clone::clone(&self.allow),
class: ::core::clone::Clone::clone(&self.class),
class_expr: ::core::clone::Clone::clone(&self.class_expr),
span: ::core::clone::Clone::clone(&self.span),
}
}
}Clone, #[automatically_derived]
impl ::core::fmt::Debug for DotsPolicy {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_struct_field5_finish(f, "DotsPolicy",
"role", &self.role, "allow", &self.allow, "class", &self.class,
"class_expr", &self.class_expr, "span", &&self.span)
}
}Debug)]
70pub(crate) struct DotsPolicy {
71 pub(crate) role: DotsRole,
72 pub(crate) allow: Vec<String>,
74 pub(crate) class: Vec<String>,
76 pub(crate) class_expr: Option<String>,
81 pub(crate) span: Span,
83}
84
85pub(crate) enum DotsAttr {
87 TypedList(TokenStream),
89 Policy(DotsPolicy),
91}
92
93const EXPECTED: &str = "dots expects `typed_list!(...)`, `empty`, `empty(allow = [\"name\", ...])`, \
94 `forward(allow = [\"name\", ...])` or `data`";
95
96pub(crate) fn parse_dots_attr(value: &syn::Expr) -> syn::Result<DotsAttr> {
98 match value {
99 syn::Expr::Macro(expr_macro) if expr_macro.mac.path.is_ident("typed_list") => {
100 Ok(DotsAttr::TypedList({
let mut _s = ::quote::__private::TokenStream::new();
::quote::ToTokens::to_tokens(&expr_macro, &mut _s);
_s
}quote::quote!(#expr_macro)))
101 }
102 syn::Expr::Path(path) => match role_of(&path.path) {
103 Some(DotsRole::Forward) => Err(syn::Error::new_spanned(
104 path,
105 "`forward` passes the names it lists: `forward(allow = [\"name\", ...])`; \
106 `data` passes every argument",
107 )),
108 Some(role) => Ok(DotsAttr::Policy(DotsPolicy {
109 role,
110 allow: Vec::new(),
111 class: Vec::new(),
112 class_expr: None,
113 span: value.span(),
114 })),
115 None => Err(syn::Error::new_spanned(path, EXPECTED)),
116 },
117 syn::Expr::Call(call) => {
118 let syn::Expr::Path(func) = call.func.as_ref() else {
119 return Err(syn::Error::new_spanned(&call.func, EXPECTED));
120 };
121 let Some(role) = role_of(&func.path) else {
122 return Err(syn::Error::new_spanned(func, EXPECTED));
123 };
124 if role == DotsRole::Data {
125 return Err(syn::Error::new_spanned(
126 call,
127 "`data` takes no options: it passes every argument unchecked",
128 ));
129 }
130 let (allow, class) = parse_options(call)?;
131 if role == DotsRole::Forward && allow.is_empty() {
132 return Err(syn::Error::new_spanned(
133 call,
134 "`forward` passes the names it lists: `forward(allow = [\"name\", ...])`; \
135 `data` passes every argument",
136 ));
137 }
138 Ok(DotsAttr::Policy(DotsPolicy {
139 role,
140 allow,
141 class,
142 class_expr: None,
143 span: value.span(),
144 }))
145 }
146 _ => Err(syn::Error::new_spanned(value, EXPECTED)),
147 }
148}
149
150fn role_of(path: &syn::Path) -> Option<DotsRole> {
151 let ident = path.get_ident()?;
152 match ident.to_string().as_str() {
153 "empty" => Some(DotsRole::Empty),
154 "forward" => Some(DotsRole::Forward),
155 "data" => Some(DotsRole::Data),
156 _ => None,
157 }
158}
159
160fn parse_options(call: &syn::ExprCall) -> syn::Result<(Vec<String>, Vec<String>)> {
163 let mut errors: Option<syn::Error> = None;
164 let mut push = |err: syn::Error| match &mut errors {
165 Some(errors) => errors.combine(err),
166 None => errors = Some(err),
167 };
168 let mut allow: Option<Vec<String>> = None;
169 let mut class: Option<Vec<String>> = None;
170 for arg in &call.args {
171 let syn::Expr::Assign(assign) = arg else {
172 push(syn::Error::new_spanned(
173 arg,
174 "expected `allow = [\"name\", ...]` or `class = \"...\"`",
175 ));
176 continue;
177 };
178 let key = match assign.left.as_ref() {
179 syn::Expr::Path(p) => p.path.get_ident().map(ToString::to_string),
180 _ => None,
181 };
182 let slot = match key.as_deref() {
183 Some("allow") => &mut allow,
184 Some("class") => &mut class,
185 _ => {
186 push(syn::Error::new_spanned(
187 &assign.left,
188 "unknown option; expected `allow = [\"name\", ...]` or `class = \"...\"`",
189 ));
190 continue;
191 }
192 };
193 if slot.is_some() {
194 push(syn::Error::new_spanned(
195 &assign.left,
196 ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("`{0}` is given twice",
key.unwrap_or_default()))
})format!("`{}` is given twice", key.unwrap_or_default()),
197 ));
198 continue;
199 }
200 match string_list(&assign.right, key.as_deref() == Some("allow")) {
201 Ok(values) => *slot = Some(values),
202 Err(err) => push(err),
203 }
204 }
205 match errors {
206 Some(errors) => Err(errors),
207 None => Ok((allow.unwrap_or_default(), class.unwrap_or_default())),
208 }
209}
210
211fn string_list(expr: &syn::Expr, names: bool) -> syn::Result<Vec<String>> {
214 let items: Vec<&syn::Expr> = match expr {
215 syn::Expr::Array(array) if !array.elems.is_empty() => array.elems.iter().collect(),
216 syn::Expr::Array(array) => {
217 return Err(syn::Error::new_spanned(
218 array,
219 "the list is empty; give at least one string",
220 ));
221 }
222 other => ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
[other]))vec![other],
223 };
224 let mut errors: Option<syn::Error> = None;
225 let mut push = |err: syn::Error| match &mut errors {
226 Some(errors) => errors.combine(err),
227 None => errors = Some(err),
228 };
229 let mut values: Vec<String> = Vec::with_capacity(items.len());
230 for item in items {
231 let syn::Expr::Lit(syn::ExprLit {
232 lit: syn::Lit::Str(lit),
233 ..
234 }) = item
235 else {
236 push(syn::Error::new_spanned(item, "expected a string literal"));
237 continue;
238 };
239 let value = lit.value();
240 if value.is_empty() {
241 push(syn::Error::new_spanned(lit, "the string is empty"));
242 } else if value.contains('\0') {
243 push(syn::Error::new_spanned(
244 lit,
245 "an R string cannot hold a NUL",
246 ));
247 } else if names && value == "..." {
248 push(syn::Error::new_spanned(
249 lit,
250 "`...` is not an argument name; list the names `...` takes",
251 ));
252 } else if values.contains(&value) {
253 push(syn::Error::new_spanned(
254 lit,
255 ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("\"{0}\" is listed twice", value))
})format!("\"{value}\" is listed twice"),
256 ));
257 } else {
258 values.push(value);
259 }
260 }
261 match errors {
262 Some(errors) => Err(errors),
263 None => Ok(values),
264 }
265}
266
267impl DotsPolicy {
268 pub(crate) fn empty(class_expr: Option<String>) -> Self {
273 DotsPolicy {
274 role: DotsRole::Empty,
275 allow: Vec::new(),
276 class: Vec::new(),
277 class_expr,
278 span: Span::call_site(),
279 }
280 }
281
282 pub(crate) fn check_signature(&self, rust_dots: bool, wrapper_dots: bool) -> syn::Result<()> {
287 match self.role {
288 DotsRole::Forward | DotsRole::Data if !rust_dots => Err(syn::Error::new(
289 self.span,
290 ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("`dots = {0}` passes arguments to the function, which takes none: add a `...` parameter (`_: ...`, `&Dots` or `LazyDots`)",
self.role.name()))
})format!(
291 "`dots = {}` passes arguments to the function, which takes none: add a \
292 `...` parameter (`_: ...`, `&Dots` or `LazyDots`)",
293 self.role.name()
294 ),
295 )),
296 DotsRole::Empty if !wrapper_dots => Err(syn::Error::new(
297 self.span,
298 "`dots = empty` checks the R wrapper's `...`, and this one has none: R already \
299 refuses an argument the function has no formal for",
300 )),
301 _ => Ok(()),
302 }
303 }
304
305 pub(crate) fn r_check(&self, formals: &str, call: Option<&str>) -> Option<String> {
314 if self.role == DotsRole::Data {
315 return None;
316 }
317 let names: Vec<&str> = crate::roxygen::split_r_formals(formals)
318 .into_iter()
319 .map(crate::roxygen::formal_name)
320 .filter(|name| !name.is_empty())
321 .collect();
322 if !names.contains(&"...") {
323 return None;
324 }
325 let formals: Vec<String> = names
326 .into_iter()
327 .filter(|name| !#[allow(non_exhaustive_omitted_patterns)] match *name {
"..." | ".call" | ".mx_self" | ".ptr" => true,
_ => false,
}matches!(*name, "..." | ".call" | ".mx_self" | ".ptr"))
328 .map(str::to_string)
329 .collect();
330 let mut args = ::alloc::boxed::box_assume_init_into_vec_unsafe(::alloc::intrinsics::write_box_via_move(::alloc::boxed::Box::new_uninit(),
["...names()".to_string(), "...length()".to_string(),
r_chr(&formals)]))vec![
331 "...names()".to_string(),
332 "...length()".to_string(),
333 r_chr(&formals),
334 ];
335 if !self.allow.is_empty() {
336 args.push(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("allow = {0}", r_chr(&self.allow)))
})format!("allow = {}", r_chr(&self.allow)));
337 }
338 if self.role == DotsRole::Forward {
339 args.push("forward = TRUE".to_string());
340 }
341 if let Some(class) = &self.class_expr {
342 args.push(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("class = {0}", class))
})format!("class = {class}"));
343 } else if !self.class.is_empty() {
344 args.push(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("class = {0}", r_chr(&self.class)))
})format!("class = {}", r_chr(&self.class)));
345 }
346 if let Some(call) = call {
347 args.push(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("call = {0}", call))
})format!("call = {call}"));
348 }
349 Some(::alloc::__export::must_use({
::alloc::fmt::format(format_args!("if (...length()) .miniextendr_check_dots({0})",
args.join(", ")))
})format!(
350 "if (...length()) .miniextendr_check_dots({})",
351 args.join(", ")
352 ))
353 }
354}
355
356fn r_chr(values: &[String]) -> String {
358 let quoted: Vec<String> = values
359 .iter()
360 .map(|v| ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("\"{0}\"",
crate::naming::r_string_escape(v)))
})format!("\"{}\"", crate::naming::r_string_escape(v)))
361 .collect();
362 match quoted.as_slice() {
363 [] => "character()".to_string(),
364 [single] => single.clone(),
365 _ => ::alloc::__export::must_use({
::alloc::fmt::format(format_args!("c({0})", quoted.join(", ")))
})format!("c({})", quoted.join(", ")),
366 }
367}
368
369#[cfg(test)]
370mod tests {
371 use super::*;
372
373 fn policy(value: syn::Expr) -> DotsPolicy {
374 match parse_dots_attr(&value) {
375 Ok(DotsAttr::Policy(policy)) => policy,
376 Ok(DotsAttr::TypedList(_)) => panic!("parsed as typed_list!"),
377 Err(err) => panic!("{err}"),
378 }
379 }
380
381 fn error(value: syn::Expr) -> String {
382 match parse_dots_attr(&value) {
383 Err(err) => err
384 .into_iter()
385 .map(|e| e.to_string())
386 .collect::<Vec<_>>()
387 .join(" | "),
388 Ok(_) => panic!("parsed"),
389 }
390 }
391
392 #[test]
393 fn spellings() {
394 let p = policy(syn::parse_quote!(empty));
395 assert_eq!(
396 (p.role, p.allow.len(), p.class.len()),
397 (DotsRole::Empty, 0, 0)
398 );
399 let p = policy(syn::parse_quote!(empty()));
400 assert_eq!((p.role, p.allow.len()), (DotsRole::Empty, 0));
401 let p = policy(syn::parse_quote!(empty(allow = ["row.names", "optional"])));
402 assert_eq!(p.allow, ["row.names", "optional"]);
403 let p = policy(syn::parse_quote!(forward(
404 allow = "digits",
405 class = "pkg_unused"
406 )));
407 assert_eq!(
408 (p.role, p.allow, p.class),
409 (
410 DotsRole::Forward,
411 vec!["digits".into()],
412 vec!["pkg_unused".into()]
413 )
414 );
415 let p = policy(syn::parse_quote!(data));
416 assert_eq!(p.role, DotsRole::Data);
417 assert!(matches!(
418 parse_dots_attr(&syn::parse_quote!(typed_list!(x => numeric()))),
419 Ok(DotsAttr::TypedList(_))
420 ));
421 }
422
423 #[test]
424 fn refusals_are_batched() {
425 let msg = error(syn::parse_quote!(empty(
426 allow = ["a", "", "a", 1, "..."],
427 class = []
428 )));
429 assert!(msg.contains("the string is empty"), "{msg}");
430 assert!(msg.contains("\"a\" is listed twice"), "{msg}");
431 assert!(msg.contains("expected a string literal"), "{msg}");
432 assert!(msg.contains("`...` is not an argument name"), "{msg}");
433 assert!(msg.contains("the list is empty"), "{msg}");
434 assert!(error(syn::parse_quote!(forward)).contains("`forward` passes the names it lists"));
435 assert!(
436 error(syn::parse_quote!(forward())).contains("`forward` passes the names it lists")
437 );
438 assert!(error(syn::parse_quote!(data(allow = "x"))).contains("`data` takes no options"));
439 assert!(error(syn::parse_quote!(empty(allow = "a", allow = "b"))).contains("given twice"));
440 assert!(error(syn::parse_quote!(empty(keep = "a"))).contains("unknown option"));
441 assert!(error(syn::parse_quote!(nothing)).contains("dots expects"));
442 assert!(error(syn::parse_quote!(other_list!(x))).contains("dots expects"));
443 }
444
445 #[test]
446 fn r_check_reads_names_only() {
447 let p = policy(syn::parse_quote!(empty));
448 assert_eq!(
449 p.r_check("object, parm, level = 0.95, ...", None).unwrap(),
450 "if (...length()) .miniextendr_check_dots(...names(), ...length(), \
451 c(\"object\", \"parm\", \"level\"))"
452 );
453 let p = policy(syn::parse_quote!(empty(
454 allow = ["optional"],
455 class = ["a\"b"]
456 )));
457 assert_eq!(
458 p.r_check(".mx_self, x, ..., .call = NULL", Some("environment()"))
459 .unwrap(),
460 "if (...length()) .miniextendr_check_dots(...names(), ...length(), \"x\", \
461 allow = \"optional\", class = \"a\\\"b\", call = environment())"
462 );
463 assert!(p.r_check("x, y", None).is_none());
465 assert_eq!(
467 policy(syn::parse_quote!(forward(allow = ["digits", "nsmall"])))
468 .r_check("x, ...", None)
469 .unwrap(),
470 "if (...length()) .miniextendr_check_dots(...names(), ...length(), \"x\", \
471 allow = c(\"digits\", \"nsmall\"), forward = TRUE)"
472 );
473 assert!(
475 policy(syn::parse_quote!(data))
476 .r_check("x, ...", None)
477 .is_none()
478 );
479 assert_eq!(
481 policy(syn::parse_quote!(empty))
482 .r_check("x, mode = c(\"a\", \"b\"), ...", None)
483 .unwrap(),
484 "if (...length()) .miniextendr_check_dots(...names(), ...length(), c(\"x\", \"mode\"))"
485 );
486 }
487
488 #[test]
489 fn r_check_takes_classes_as_an_r_expression() {
490 let p = DotsPolicy::empty(Some(".rdata_error_class_T".to_string()));
493 assert_eq!(
494 p.r_check("x, i, ..., exact = TRUE", Some("environment()"))
495 .unwrap(),
496 "if (...length()) .miniextendr_check_dots(...names(), ...length(), \
497 c(\"x\", \"i\", \"exact\"), class = .rdata_error_class_T, call = environment())"
498 );
499 assert_eq!(
500 DotsPolicy::empty(None).r_check("x, ...", None).unwrap(),
501 "if (...length()) .miniextendr_check_dots(...names(), ...length(), \"x\")"
502 );
503 }
504}