1#' Dynamic dots
2#'
3#' @description
4#'
5#' The `...` syntax of base R allows you to:
6#'
7#' - __Forward__ arguments from function to function, matching them
8#'   along the way to function parameters.
9#'
10#' - __Collect__ arguments inside data structures, e.g. with [c()] or
11#'   [list()].
12#'
13#' Dynamic dots offer a few additional features:
14#'
15#' 1. You can __splice__ arguments saved in a list with the [big
16#'    bang][quasiquotation] operator `!!!`.
17#'
18#' 2. You can __unquote__ names by using the [glue][glue::glue] syntax
19#'    or the [bang bang][quasiquotation] operator `!!` on the
20#'    left-hand side of `:=`.
21#'
22#' 3. Trailing commas are ignored, making it easier to copy and paste
23#'    lines of arguments.
24#'
25#'
26#' @section Add dynamic dots support in your functions:
27#'
28#' If your function takes dots, adding support for dynamic features is
29#' as easy as collecting the dots with [list2()] instead of [list()].
30#'
31#' Other dynamic dots collectors are [dots_list()], which is more
32#' configurable than [list2()], `vars()` which doesn't force its
33#' arguments, and [call2()] for creating calls.
34#'
35#' Document dynamic docs using this standard tag:
36#'
37#' ```
38#'  @@param ... <[`dynamic-dots`][rlang::dyn-dots]> What these dots do.
39#' ```
40#'
41#' @name dyn-dots
42#' @aliases tidy-dots
43#'
44#' @examples
45#' f <- function(...) {
46#'   out <- list2(...)
47#'   rev(out)
48#' }
49#'
50#' # Splice
51#' x <- list(alpha = "first", omega = "last")
52#' f(!!!x)
53#'
54#' # Unquote a name, showing both the `!!` bang bang and `{}` glue style
55#' nm <- "key"
56#' f(!!nm := "value")
57#' f("{nm}" := "value")
58#' f("prefix_{nm}" := "value")
59#'
60#' # Tolerate a trailing comma
61#' f(this = "that", )
62NULL
63
64#' Collect dots in a list
65#'
66#' `list2(...)` is equivalent to `list(...)` with a few additional
67#' features, collectively called [dynamic dots][dyn-dots]. While
68#' `list2()` hard-code these features, `dots_list()` is a lower-level
69#' version that offers more control.
70#'
71#' @param ... Arguments to collect in a list. These dots are
72#'   [dynamic][dyn-dots].
73#' @return A list containing the `...` inputs.
74#'
75#' @export
76list2 <- function(...) {
77  .Call(rlang_dots_list,
78    frame_env = environment(),
79    named = FALSE,
80    ignore_empty = "trailing",
81    preserve_empty = FALSE,
82    unquote_names = TRUE,
83    homonyms = "keep",
84    check_assign = FALSE
85  )
86}
87#' @rdname list2
88#' @usage NULL
89#' @export
90ll <- list2
91
92# Preserves empty arguments
93list3 <- function(...) {
94  .Call(rlang_dots_list,
95    frame_env = environment(),
96    named = FALSE,
97    ignore_empty = "trailing",
98    preserve_empty = TRUE,
99    unquote_names = TRUE,
100    homonyms = "keep",
101    check_assign = FALSE
102  )
103}
104
105
106#' @rdname list2
107#' @param .named Whether to ensure all dots are named. Unnamed
108#'   elements are processed with [as_label()] to build a default
109#'   name.
110#' @param .ignore_empty Whether to ignore empty arguments. Can be one
111#'   of `"trailing"`, `"none"`, `"all"`. If `"trailing"`, only the
112#'   last argument is ignored if it is empty.
113#' @param .preserve_empty Whether to preserve the empty arguments that
114#'   were not ignored. If `TRUE`, empty arguments are stored with
115#'   [missing_arg()] values. If `FALSE` (the default) an error is
116#'   thrown when an empty argument is detected.
117#' @param .homonyms How to treat arguments with the same name. The
118#'   default, `"keep"`, preserves these arguments. Set `.homonyms` to
119#'   `"first"` to only keep the first occurrences, to `"last"` to keep
120#'   the last occurrences, and to `"error"` to raise an informative
121#'   error and indicate what arguments have duplicated names.
122#' @param .check_assign Whether to check for `<-` calls passed in
123#'   dots. When `TRUE` and a `<-` call is detected, a warning is
124#'   issued to advise users to use `=` if they meant to match a
125#'   function parameter, or wrap the `<-` call in braces otherwise.
126#'   This ensures assignments are explicit.
127#' @export
128#' @examples
129#' # Let's create a function that takes a variable number of arguments:
130#' numeric <- function(...) {
131#'   dots <- list2(...)
132#'   num <- as.numeric(dots)
133#'   set_names(num, names(dots))
134#' }
135#' numeric(1, 2, 3)
136#'
137#' # The main difference with list(...) is that list2(...) enables
138#' # the `!!!` syntax to splice lists:
139#' x <- list(2, 3)
140#' numeric(1, !!! x, 4)
141#'
142#' # As well as unquoting of names:
143#' nm <- "yup!"
144#' numeric(!!nm := 1)
145#'
146#'
147#' # One useful application of splicing is to work around exact and
148#' # partial matching of arguments. Let's create a function taking
149#' # named arguments and dots:
150#' fn <- function(data, ...) {
151#'   list2(...)
152#' }
153#'
154#' # You normally cannot pass an argument named `data` through the dots
155#' # as it will match `fn`'s `data` argument. The splicing syntax
156#' # provides a workaround:
157#' fn("wrong!", data = letters)  # exact matching of `data`
158#' fn("wrong!", dat = letters)   # partial matching of `data`
159#' fn(some_data, !!!list(data = letters))  # no matching
160#'
161#'
162#' # Empty arguments trigger an error by default:
163#' try(fn(, ))
164#'
165#' # You can choose to preserve empty arguments instead:
166#' list3 <- function(...) dots_list(..., .preserve_empty = TRUE)
167#'
168#' # Note how the last empty argument is still ignored because
169#' # `.ignore_empty` defaults to "trailing":
170#' list3(, )
171#'
172#' # The list with preserved empty arguments is equivalent to:
173#' list(missing_arg())
174#'
175#'
176#' # Arguments with duplicated names are kept by default:
177#' list2(a = 1, a = 2, b = 3, b = 4, 5, 6)
178#'
179#' # Use the `.homonyms` argument to keep only the first of these:
180#' dots_list(a = 1, a = 2, b = 3, b = 4, 5, 6, .homonyms = "first")
181#'
182#' # Or the last:
183#' dots_list(a = 1, a = 2, b = 3, b = 4, 5, 6, .homonyms = "last")
184#'
185#' # Or raise an informative error:
186#' try(dots_list(a = 1, a = 2, b = 3, b = 4, 5, 6, .homonyms = "error"))
187#'
188#'
189#' # dots_list() can be configured to warn when a `<-` call is
190#' # detected:
191#' my_list <- function(...) dots_list(..., .check_assign = TRUE)
192#' my_list(a <- 1)
193#'
194#' # There is no warning if the assignment is wrapped in braces.
195#' # This requires users to be explicit about their intent:
196#' my_list({ a <- 1 })
197dots_list <- function(...,
198                      .named = FALSE,
199                      .ignore_empty = c("trailing", "none", "all"),
200                      .preserve_empty = FALSE,
201                      .homonyms = c("keep", "first", "last", "error"),
202                      .check_assign = FALSE) {
203  dots <- .Call(rlang_dots_list,
204    frame_env = environment(),
205    named = .named,
206    ignore_empty = .ignore_empty,
207    preserve_empty = .preserve_empty,
208    unquote_names = TRUE,
209    homonyms = .homonyms,
210    check_assign = .check_assign
211  )
212  names(dots) <- names2(dots)
213  dots
214}
215
216dots_split <- function(...,
217                       .n_unnamed = NULL,
218                       .ignore_empty = c("trailing", "none", "all"),
219                       .preserve_empty = FALSE,
220                       .homonyms = c("keep", "first", "last", "error"),
221                       .check_assign = FALSE) {
222  dots <- .Call(rlang_dots_list,
223    frame_env = environment(),
224    named = FALSE,
225    ignore_empty = .ignore_empty,
226    preserve_empty = .preserve_empty,
227    unquote_names = TRUE,
228    homonyms = .homonyms,
229    check_assign = .check_assign
230  )
231
232  if (is_null(names(dots))) {
233    if (length(dots)) {
234      unnamed_idx <- TRUE
235    } else {
236      unnamed_idx <- lgl()
237    }
238    n <- length(dots)
239  } else {
240    unnamed_idx <- names(dots) == ""
241    n <- sum(unnamed_idx)
242  }
243
244  if (!is_null(.n_unnamed) && all(n != .n_unnamed)) {
245    ns <- chr_enumerate(.n_unnamed)
246    abort(sprintf("Expected %s unnamed arguments in `...`", ns))
247  }
248
249  unnamed <- dots[unnamed_idx]
250  named <- dots[!unnamed_idx]
251
252  # Remove empty names vector
253  names(unnamed) <- NULL
254
255  list(named = named, unnamed = unnamed)
256}
257
258#' Splice lists
259#'
260#' @description
261#'
262#' \Sexpr[results=rd, stage=render]{rlang:::lifecycle("questioning")}
263#'
264#' - `splice` marks an object to be spliced. It is equivalent to using
265#'   `!!!` in a function taking [dynamic dots][dyn-dots].
266#'
267#' - `dots_splice()` is like [dots_list()] but automatically splices
268#'   list inputs.
269#'
270#'
271#' @section Standard splicing versus quoting splicing:
272#'
273#' The `!!!` operator works differently in _standard_ functions taking
274#' dots with `dots_list()` than in _quoting_ functions taking dots
275#' with [enexprs()] or [enquos()].
276#'
277#' * In quoting functions `!!!` disaggregates its argument (let's call
278#'   it `x`) into as many objects as there are elements in
279#'   `x`. E.g. `quo(foo(!!! c(1, 2)))` is completely equivalent to
280#'   `quo(foo(1, 2))`. The creation of those separate objects has an
281#'   overhead but is typically not important when manipulating calls
282#'   because function calls typically take a small number of
283#'   arguments.
284#'
285#' * In standard functions, disaggregating the spliced collection
286#'   would have a negative performance impact in cases where
287#'   `dots_list()` is used to build up data structures from user
288#'   inputs. To avoid this spliced inputs are marked with [splice()]
289#'   and the final list is built with (the equivalent of)
290#'   `flatten_if(dots, is_spliced)`.
291#'
292#' Most of the time you should not care about the difference. However
293#' if you use a standard function taking tidy dots within a quoting
294#' function, the `!!!` operator will disaggregate its argument because
295#' the behaviour of the quasiquoting function has priority. You might
296#' then observe some performance cost in edge cases. Here is one
297#' example where this would happen:
298#'
299#' ```
300#' purrr::rerun(10, dplyr::bind_rows(!!! x))
301#' ```
302#'
303#' `purrr::rerun()` is a quoting function and `dplyr::bind_rows()` is
304#' a standard function. Because `bind_rows()` is called _inside_
305#' `rerun()`, the list `x` will be disaggregated into a pairlist of
306#' arguments. To avoid this you can use `splice()` instead:
307#'
308#' ```
309#' purrr::rerun(10, dplyr::bind_rows(splice(x)))
310#' ```
311#'
312#'
313#' @section Life cycle:
314#'
315#' * `dots_splice()` is in the questioning stage. It is part of our
316#'   experiments with dots semantics. Compared to `dots_list()`,
317#'   `dots_splice()` automatically splices lists. We now lean towards
318#'   adopting a single type of dots semantics (those of `dots_list()`)
319#'   where splicing is explicit.
320#'
321#' * `splice()` is in the questioning stage. It is not clear whether it is
322#'   really needed as there are other ways to avoid the performance
323#'   issue discussed above.
324#'
325#'
326#' @param x A list to splice.
327#'
328#' @keywords internal
329#' @export
330splice <- function(x) {
331  .Call(rlang_new_splice_box, x)
332}
333#' @rdname splice
334#' @export
335is_spliced <- function(x) {
336  .Call(rlang_is_splice_box, x)
337}
338#' @rdname splice
339#' @export
340is_spliced_bare <- function(x) {
341  is_bare_list(x) || is_spliced(x)
342}
343#' @export
344print.rlang_box_splice <- function(x, ...) {
345  cat_line("<spliced>")
346  print(unbox(x))
347}
348
349#' @rdname splice
350#' @inheritParams dots_list
351#' @export
352dots_splice <- function(...,
353                        .ignore_empty = c("trailing", "none", "all"),
354                        .preserve_empty = FALSE,
355                        .homonyms = c("keep", "first", "last", "error"),
356                        .check_assign = FALSE) {
357  dots <- .Call(rlang_dots_flat_list,
358    frame_env = environment(),
359    named = FALSE,
360    ignore_empty = .ignore_empty,
361    preserve_empty = .preserve_empty,
362    unquote_names = TRUE,
363    homonyms = .homonyms,
364    check_assign = .check_assign
365  )
366  names(dots) <- names2(dots)
367  dots
368}
369
370
371#' Evaluate dots with preliminary splicing
372#'
373#' This is a tool for advanced users. It captures dots, processes
374#' unquoting and splicing operators, and evaluates them. Unlike
375#' [dots_list()], it does not flatten spliced objects, instead they
376#' are attributed a `spliced` class (see [splice()]). You can process
377#' spliced objects manually, perhaps with a custom predicate (see
378#' [flatten_if()]).
379#'
380#' @inheritParams dots_list
381#' @param ... Arguments to evaluate and process splicing operators.
382#'
383#' @keywords internal
384#' @export
385#' @examples
386#' dots <- dots_values(!!! list(1, 2), 3)
387#' dots
388#'
389#' # Flatten the objects marked as spliced:
390#' flatten_if(dots, is_spliced)
391dots_values <- function(...,
392                        .ignore_empty = c("trailing", "none", "all"),
393                        .preserve_empty = FALSE,
394                        .homonyms = c("keep", "first", "last", "error"),
395                        .check_assign = FALSE) {
396  .External(rlang_ext_dots_values,
397    env = environment(),
398    named = FALSE,
399    ignore_empty = .ignore_empty,
400    preserve_empty = .preserve_empty,
401    unquote_names = TRUE,
402    homonyms = .homonyms,
403    check_assign = .check_assign
404  )
405}
406
407# Micro optimisation: Inline character vectors in formals list
408formals(dots_values) <- pairlist(
409  ... = quote(expr = ),
410  .ignore_empty = c("trailing", "none", "all"),
411  .preserve_empty = FALSE,
412  .homonyms = c("keep", "first", "last", "error"),
413  .check_assign = FALSE
414)
415
416#' Capture definition objects
417#'
418#' @section Life cycle:
419#'
420#' `dots_definitions()` is experimental. Expect API changes.
421#'
422#' @inheritParams nse-defuse
423#'
424#' @keywords internal
425#' @export
426dots_definitions <- function(...,
427                             .named = FALSE,
428                             .ignore_empty = c("trailing", "none", "all")) {
429  dots <- .Call(rlang_quos_interp,
430    frame_env = environment(),
431    named = .named,
432    ignore_empty = .ignore_empty,
433    unquote_names = FALSE,
434    homonyms = "keep",
435    check_assign = FALSE
436  )
437
438  is_def <- map_lgl(dots, function(dot) is_definition(quo_get_expr(dot)))
439  defs <- map(dots[is_def], as_definition)
440
441  list(dots = dots[!is_def], defs = defs)
442}
443as_definition <- function(def) {
444  # The definition comes wrapped in a quosure
445  env <- quo_get_env(def)
446  def <- quo_get_expr(def)
447
448  list(
449    lhs = new_quosure(f_lhs(def), env),
450    rhs = new_quosure(f_rhs(def), env)
451  )
452}
453
454dots_node <- function(...) {
455  node_cdr(sys.call())
456}
457
458#' How many arguments are currently forwarded in dots?
459#'
460#' This returns the number of arguments currently forwarded in `...`
461#' as an integer.
462#'
463#' @param ... Forwarded arguments.
464#' @keywords internal
465#' @export
466#' @examples
467#' fn <- function(...) dots_n(..., baz)
468#' fn(foo, bar)
469dots_n <- function(...) {
470  nargs()
471}
472
473abort_dots_homonyms <- function(dots, dups) {
474  nms <- names(dots)
475
476  # This includes the first occurrence as well
477  dups_all <- nms %in% nms[dups]
478
479  dups_nms <- unique(nms[dups_all])
480  dups_n <- length(dups_nms)
481
482  if (!dups_n) {
483    abort("Internal error: Expected dots duplicates")
484  }
485
486  if (dups_n == 1L) {
487    nm <- dups_nms
488    enum <- homonym_enum(nm, dups_all, nms)
489    pos_msg <- sprintf(
490      "We found multiple arguments named `%s` at positions %s",
491      nm,
492      enum
493    )
494    abort(paste_line(
495      "Arguments can't have the same name.",
496      pos_msg
497    ))
498  }
499
500  enums <- map(dups_nms, homonym_enum, dups_all, nms)
501  line <- "* Multiple arguments named `%s` at positions %s"
502  enums_lines <- map2(dups_nms, enums, sprintf, fmt = line)
503
504  abort(paste_line(
505    "Arguments can't have the same name. We found these problems:",
506    !!!enums_lines
507  ))
508}
509
510homonym_enum <- function(nm, dups, nms) {
511  dups[nms != nm] <- FALSE
512  chr_enumerate(as.character(which(dups)), final = "and")
513}
514
515check_dots_empty <- function(...) {
516  if (nargs()) {
517    abort("These `...` must be empty")
518  }
519}
520
521
522# This helper is used when splicing S3 or S4 objects found
523# in `!!!`. It is similar to `as.list()`, but the names of
524# `x` always end up on the names of the output list,
525# unlike `as.list.factor()`.
526rlang_as_list <- function(x) {
527  if (is.list(x)) {
528    out <- rlang_as_list_from_list_impl(x)
529  } else {
530    out <- rlang_as_list_impl(x)
531  }
532
533  names(out) <- names(x)
534
535  out
536}
537
538rlang_as_list_impl <- function(x) {
539  n <- length(x)
540  out <- vector("list", n)
541
542  for (i in seq_len(n)) {
543    out[[i]] <- x[[i]]
544  }
545
546  out
547}
548
549# Special handling if `x` is already a list.
550# This avoids the potential for `out[[i]] <- NULL`,
551# which shortens the list.
552rlang_as_list_from_list_impl <- function(x) {
553  n <- length(x)
554  out <- vector("list", n)
555
556  for (i in seq_len(n)) {
557    elt <- x[[i]]
558
559    if (is.null(elt)) {
560      next
561    }
562
563    out[[i]] <- elt
564  }
565
566  out
567}
568