Skip to content

Commit 37e0094

Browse files
authored
Support splicing in named arguments of aes() (#4802)
Closes #2675
1 parent 6c0f14a commit 37e0094

File tree

5 files changed

+87
-2
lines changed

5 files changed

+87
-2
lines changed

NEWS.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# ggplot2 (development version)
22

3+
* `aes()` now supports the `!!!` operator in its first two arguments
4+
(#2675). Thanks to @yutannihilation and @teunbrand for draft
5+
implementations.
6+
37
* Require rlang >= 1.0.0 (@billybarc, #4797)
48

59
* `geom_violin()` no longer issues "collapsing to unique 'x' values" warning

R/aes.r

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,21 @@ NULL
7979
#' cut3 <- function(x) cut_number(x, 3)
8080
#' scatter_by(mtcars, cut3(disp), drat)
8181
aes <- function(x, y, ...) {
82-
exprs <- enquos(x = x, y = y, ..., .ignore_empty = "all")
83-
aes <- new_aes(exprs, env = parent.frame())
82+
xs <- arg_enquos("x")
83+
ys <- arg_enquos("y")
84+
dots <- enquos(...)
85+
86+
args <- c(xs, ys, dots)
87+
args <- Filter(Negate(quo_is_missing), args)
88+
89+
# Pass arguments to helper dummy to throw an error when duplicate
90+
# `x` and `y` arguments are passed through dots
91+
local({
92+
aes <- function(x, y, ...) NULL
93+
inject(aes(!!!args))
94+
})
95+
96+
aes <- new_aes(args, env = parent.frame())
8497
rename_aes(aes)
8598
}
8699

@@ -426,3 +439,26 @@ extract_target_is_likely_data <- function(x, data, env) {
426439
identical(data_eval, data)
427440
}, error = function(err) FALSE)
428441
}
442+
443+
# Takes a quosure and returns a named list of quosures, expanding
444+
# `!!!` expressions as needed
445+
arg_enquos <- function(name, frame = caller_env()) {
446+
# First start with `enquo0()` which does not process injection
447+
# operators
448+
quo <- inject(enquo0(!!sym(name)), frame)
449+
expr <- quo_get_expr(quo)
450+
451+
if (!is_missing(expr) && is_triple_bang(expr)) {
452+
# Evaluate `!!!` operand and create a list of quosures
453+
env <- quo_get_env(quo)
454+
xs <- eval_bare(expr[[2]][[2]][[2]], env)
455+
xs <- lapply(xs, as_quosure, env = env)
456+
} else {
457+
# Redefuse `x` to process injection operators, then store in a
458+
# length-1 list of quosures
459+
quo <- inject(enquo(!!sym(name)), frame)
460+
xs <- set_names(list(quo), name)
461+
}
462+
463+
new_quosures(xs)
464+
}

R/utilities.r

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -604,3 +604,25 @@ split_with_index <- function(x, f, n = max(f)) {
604604
attributes(f) <- list(levels = as.character(seq_len(n)), class = "factor")
605605
unname(split(x, f))
606606
}
607+
608+
is_bang <- function(x) {
609+
is_call(x, "!", n = 1)
610+
}
611+
612+
is_triple_bang <- function(x) {
613+
if (!is_bang(x)) {
614+
return(FALSE)
615+
}
616+
617+
x <- x[[2]]
618+
if (!is_bang(x)) {
619+
return(FALSE)
620+
}
621+
622+
x <- x[[2]]
623+
if (!is_bang(x)) {
624+
return(FALSE)
625+
}
626+
627+
TRUE
628+
}

tests/testthat/_snaps/aes.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# aes() supports `!!!` in named arguments (#2675)
2+
3+
Code
4+
(expect_error(aes(y = 1, !!!list(y = 2))))
5+
Output
6+
<simpleError in aes(y = 1, y = 2): formal argument "y" matched by multiple actual arguments>
7+

tests/testthat/test-aes.r

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,22 @@ test_that("Warnings are issued when plots use discouraged extract usage within a
152152
expect_warning(ggplot_build(p), "Use of `df\\$x` is discouraged")
153153
})
154154

155+
test_that("aes() supports `!!!` in named arguments (#2675)", {
156+
expect_equal(
157+
aes(!!!list(y = 1)),
158+
aes(y = 1)
159+
)
160+
expect_equal(
161+
aes(!!!list(x = 1), !!!list(y = 2)),
162+
aes(x = 1, y = 2)
163+
)
164+
expect_equal(
165+
aes(, , !!!list(y = 1)),
166+
aes(y = 1)
167+
)
168+
expect_snapshot((expect_error(aes(y = 1, !!!list(y = 2)))))
169+
})
170+
155171
# Visual tests ------------------------------------------------------------
156172

157173
test_that("aesthetics are drawn correctly", {

0 commit comments

Comments
 (0)