diff --git a/NEWS.md b/NEWS.md index 398f8ac..36db39d 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,5 +1,10 @@ # 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(allow_empty = FALSE)` now throws a classed error message (@olivroy, #347). + # tidyselect 1.2.1 * Performance improvements (#337, #338, #339, #341) diff --git a/R/eval-relocate.R b/R/eval-relocate.R index 45d46b6..b0b1679 100644 --- a/R/eval-relocate.R +++ b/R/eval-relocate.R @@ -89,6 +89,7 @@ eval_relocate <- function(expr, allow_empty = allow_empty, allow_predicates = allow_predicates, type = "relocate", + error_arg = NULL, # TODO need to know which to use. can `before_arg` or `after_arg` be passed here? error_call = error_call ) @@ -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 @@ -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 diff --git a/R/eval-select.R b/R/eval-select.R index 2b9b424..7ee2cc4 100644 --- a/R/eval-select.R +++ b/R/eval-select.R @@ -43,7 +43,8 @@ #' 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 name to include in error message. +#' @param error_arg Argument name to include in error message if `allow_empty = FALSE`. +#' Will give a better error message if the selection ends up empty. #' @inheritParams rlang::args_dots_empty #' #' @return A named vector of numeric locations, one for each of the @@ -104,6 +105,11 @@ #' # Note that the trick above works because `expr({{ arg }})` is the #' # same as `enquo(arg)`. #' +#' # give a better error message if you don't want 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()` diff --git a/R/eval-walk.R b/R/eval-walk.R index caf5dc3..7d8a3f3 100644 --- a/R/eval-walk.R +++ b/R/eval-walk.R @@ -131,7 +131,19 @@ ensure_named <- function(pos, 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_disallowed_empty" + ) + } else { + cli::cli_abort( + "{.or {.arg {error_arg}}} must select at least one column.", + call = call, + class = "tidyselect:::error_disallowed_empty" + ) + } } } diff --git a/R/utils.R b/R/utils.R index 2a66b61..3663c37 100644 --- a/R/utils.R +++ b/R/utils.R @@ -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() @@ -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 ) } diff --git a/man/eval_select.Rd b/man/eval_select.Rd index 11fc8bc..bced545 100644 --- a/man/eval_select.Rd +++ b/man/eval_select.Rd @@ -76,7 +76,8 @@ is useful to implement purely selective behaviour.} in an empty selection. If \code{FALSE}, will error if \code{expr} yields an empty selection.} -\item{error_arg}{Argument name to include in error message.} +\item{error_arg}{Argument name to include in error message if \code{allow_empty = FALSE}. +Will give a better error message if the selection ends up empty.} } \value{ A named vector of numeric locations, one for each of the @@ -143,6 +144,11 @@ my_function <- function(.x, .expr, ...) { # Note that the trick above works because `expr({{ arg }})` is the # same as `enquo(arg)`. +# give a better error message if you don't want 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()` diff --git a/man/faq-selection-context.Rd b/man/faq-selection-context.Rd index d418979..860fbf8 100644 --- a/man/faq-selection-context.Rd +++ b/man/faq-selection-context.Rd @@ -13,23 +13,20 @@ Using a selection helper anywhere else results in an error: \if{html}{\out{