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