Skip to content

Commit

Permalink
Throw classed error if allow_empty = FALSE (#350)
Browse files Browse the repository at this point in the history
And add more context to error message with `error_arg` arguments.
  • Loading branch information
olivroy authored Oct 23, 2024
1 parent 3ad00d7 commit e4190a9
Show file tree
Hide file tree
Showing 14 changed files with 137 additions and 30 deletions.
4 changes: 4 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,7 @@ export(where)
export(with_vars)
import(rlang)
importFrom(glue,glue)
importFrom(rlang,enquo)
importFrom(rlang,quo)
importFrom(rlang,quo_name)
importFrom(rlang,quos)
7 changes: 7 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# tidyselect (development version)

* `eval_select(allow_empty = FALSE)` gains a new argument to yield a better error
message in case of empty selection (@olivroy, #327)

* `eval_select()` and `eval_relocate()` gain a new `error_arg` argument that can be specified to throw a better error message when `allow_empty = FALSE`.

* `eval_select()` and `eval_relocate()` throw a classed error message when `allow_empty = FALSE` (@olivroy, #347).

# tidyselect 1.2.1

* Performance improvements (#337, #338, #339, #341)
Expand Down
9 changes: 6 additions & 3 deletions R/eval-relocate.R
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ eval_relocate <- function(expr,
allow_rename = TRUE,
allow_empty = TRUE,
allow_predicates = TRUE,
error_arg = NULL,
before_arg = "before",
after_arg = "after",
env = caller_env(),
Expand All @@ -78,7 +79,6 @@ eval_relocate <- function(expr,
data <- tidyselect_data_proxy(data)

expr <- as_quosure(expr, env = env)

sel <- eval_select_impl(
x = data,
names = names(data),
Expand All @@ -89,6 +89,7 @@ eval_relocate <- function(expr,
allow_empty = allow_empty,
allow_predicates = allow_predicates,
type = "relocate",
error_arg = error_arg,
error_call = error_call
)

Expand Down Expand Up @@ -122,7 +123,8 @@ eval_relocate <- function(expr,
env = env,
error_call = error_call,
allow_predicates = allow_predicates,
allow_rename = FALSE
allow_rename = FALSE,
error_arg = before_arg
),
arg = before_arg,
error_call = error_call
Expand All @@ -143,7 +145,8 @@ eval_relocate <- function(expr,
env = env,
error_call = error_call,
allow_predicates = allow_predicates,
allow_rename = FALSE
allow_rename = FALSE,
error_arg = after_arg
),
arg = after_arg,
error_call = error_call
Expand Down
13 changes: 13 additions & 0 deletions R/eval-select.R
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@
#' use predicates (i.e. in `where()`). If `FALSE`, will error if `expr` uses a
#' predicate. Will automatically be set to `FALSE` if `data` does not
#' support predicates (as determined by [tidyselect_data_has_predicates()]).
#' @param error_arg Argument names for `expr`. These
#' are used in error messages. (You can use `"..."` if `expr = c(...)`).
#' For now, this is used when `allow_empty = FALSE`.
#' @inheritParams rlang::args_dots_empty
#'
#' @return A named vector of numeric locations, one for each of the
Expand Down Expand Up @@ -103,6 +106,12 @@
#' # Note that the trick above works because `expr({{ arg }})` is the
#' # same as `enquo(arg)`.
#'
#' # Supply `error_arg` to improve the error message in case of
#' # unexpected empty selection:
#' select_not_empty <- function(x, cols) {
#' eval_select(expr = enquo(cols), data = x, allow_empty = FALSE, error_arg = "cols")
#' }
#' try(select_not_empty(mtcars, cols = starts_with("vs2")))
#'
#' # The evaluators return a named vector of locations. Here are
#' # examples of using these location vectors to implement `select()`
Expand Down Expand Up @@ -131,6 +140,7 @@ eval_select <- function(expr,
allow_rename = TRUE,
allow_empty = TRUE,
allow_predicates = TRUE,
error_arg = NULL,
error_call = caller_env()) {
check_dots_empty()

Expand All @@ -148,6 +158,7 @@ eval_select <- function(expr,
allow_rename = allow_rename,
allow_empty = allow_empty,
allow_predicates = allow_predicates,
error_arg = error_arg,
error_call = error_call,
)
}
Expand All @@ -163,6 +174,7 @@ eval_select_impl <- function(x,
allow_rename = TRUE,
allow_empty = TRUE,
allow_predicates = TRUE,
error_arg = NULL,
type = "select",
error_call = caller_env()) {
if (!is_null(x)) {
Expand Down Expand Up @@ -190,6 +202,7 @@ eval_select_impl <- function(x,
allow_empty = allow_empty,
allow_predicates = allow_predicates,
type = type,
error_arg = error_arg,
error_call = error_call
),
type = type
Expand Down
24 changes: 20 additions & 4 deletions R/eval-walk.R
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@ vars_select_eval <- function(vars,
allow_empty = TRUE,
allow_predicates = TRUE,
type = "select",
error_arg = NULL,
error_call) {
wrapped <- quo_get_expr2(expr, expr)

if (is_missing(wrapped)) {
pos <- named(int())
check_empty(pos, allow_empty, call = error_call)
check_empty(pos, allow_empty, error_arg, call = error_call)
return(pos)
}

Expand All @@ -35,6 +36,7 @@ vars_select_eval <- function(vars,
uniquely_named = uniquely_named,
allow_rename = allow_rename,
allow_empty = allow_empty,
error_arg = error_arg,
call = error_call
)
return(pos)
Expand Down Expand Up @@ -93,6 +95,7 @@ vars_select_eval <- function(vars,
uniquely_named = uniquely_named,
allow_rename = allow_rename,
allow_empty = allow_empty,
error_arg = error_arg,
call = error_call
)
}
Expand All @@ -102,8 +105,9 @@ ensure_named <- function(pos,
uniquely_named = FALSE,
allow_rename = TRUE,
allow_empty = TRUE,
error_arg = NULL,
call = caller_env()) {
check_empty(pos, allow_empty, call = call)
check_empty(pos, allow_empty, error_arg, call = call)

if (!allow_rename && any(names2(pos) != "")) {
cli::cli_abort(
Expand All @@ -125,9 +129,21 @@ ensure_named <- function(pos,
pos
}

check_empty <- function(x, allow_empty = TRUE, call = caller_env()) {
check_empty <- function(x, allow_empty = TRUE, error_arg = NULL, call = caller_env()) {
if (!allow_empty && length(x) == 0) {
cli::cli_abort("Must select at least one item.", call = call)
if (is.null(error_arg)) {
cli::cli_abort(
"Must select at least one item.",
call = call,
class = "tidyselect_error_empty_selection"
)
} else {
cli::cli_abort(
"{.arg {error_arg}} must select at least one column.",
call = call,
class = "tidyselect_error_empty_selection"
)
}
}
}

Expand Down
4 changes: 4 additions & 0 deletions R/utils.R
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ select_loc <- function(x,
allow_rename = TRUE,
allow_empty = TRUE,
allow_predicates = TRUE,
error_arg = NULL,
error_call = current_env()) {
check_dots_empty()

Expand All @@ -21,6 +22,7 @@ select_loc <- function(x,
allow_rename = allow_rename,
allow_empty = allow_empty,
allow_predicates = allow_predicates,
error_arg = error_arg,
error_call = error_call
)
}
Expand Down Expand Up @@ -51,6 +53,7 @@ relocate_loc <- function(x,
name_spec = NULL,
allow_rename = TRUE,
allow_empty = TRUE,
error_arg = NULL,
before_arg = "before",
after_arg = "after",
error_call = current_env()) {
Expand All @@ -65,6 +68,7 @@ relocate_loc <- function(x,
name_spec = name_spec,
allow_rename = allow_rename,
allow_empty = allow_empty,
error_arg = error_arg,
before_arg = before_arg,
after_arg = after_arg,
error_call = error_call
Expand Down
5 changes: 5 additions & 0 deletions man/eval_relocate.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions man/eval_select.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 4 additions & 7 deletions man/faq-external-vector.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 7 additions & 10 deletions man/faq-selection-context.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 18 additions & 5 deletions tests/testthat/_snaps/eval-relocate.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,21 +82,34 @@
# can forbid empty selections

Code
(expect_error(relocate_loc(x, allow_empty = FALSE)))
(expect_error(relocate_loc(x, allow_empty = FALSE, error_arg = "...")))
Output
<error/rlang_error>
<error/tidyselect_error_empty_selection>
Error in `relocate_loc()`:
! Must select at least one item.
! `...` must select at least one column.
Code
(expect_error(relocate_loc(mtcars, integer(), allow_empty = FALSE)))
Output
<error/rlang_error>
<error/tidyselect_error_empty_selection>
Error in `relocate_loc()`:
! Must select at least one item.
Code
(expect_error(relocate_loc(mtcars, starts_with("z"), allow_empty = FALSE)))
Output
<error/rlang_error>
<error/tidyselect_error_empty_selection>
Error in `relocate_loc()`:
! Must select at least one item.

---

Code
relocate_loc(mtcars, before = integer(), allow_empty = FALSE)
Condition <tidyselect_error_empty_selection>
Error in `relocate_loc()`:
! Must select at least one item.
Code
relocate_loc(mtcars, starts_with("z"), allow_empty = FALSE)
Condition <tidyselect_error_empty_selection>
Error in `relocate_loc()`:
! Must select at least one item.

Expand Down
18 changes: 18 additions & 0 deletions tests/testthat/_snaps/eval-select.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,24 @@
Error in `select_loc()`:
! Must select at least one item.

# can forbid empty selections with informative error

Code
select_loc(mtcars, allow_empty = FALSE, error_arg = "cols")
Condition
Error in `select_loc()`:
! `cols` must select at least one column.
Code
select_loc(mtcars, integer(), allow_empty = FALSE, error_arg = "x")
Condition
Error in `select_loc()`:
! `x` must select at least one column.
Code
select_loc(mtcars, starts_with("z"), allow_empty = FALSE, error_arg = "y")
Condition
Error in `select_loc()`:
! `y` must select at least one column.

# eval_select() errors mention correct calls

Code
Expand Down
Loading

0 comments on commit e4190a9

Please sign in to comment.