r-lib / tidyselect

A backend for functions taking tidyverse selections
https://tidyselect.r-lib.org
Other
124 stars 39 forks source link

Feature request: classed errors for specific tidyselect errors #351

Open yjunechoe opened 5 months ago

yjunechoe commented 5 months ago

A user's attempt at selecting a column can fail for various reasons - currently, there are some (extremely brittle) ways to infer what went wrong.

Briefly, if we wanted to distinguish between these three types of tidyselect errors (mismatch, other evaluation error, empty):

library(rlang)
library(tidyselect)
df <- data.frame(x = 1)

selectors <- exprs(
  mismatch = z,
  error = stop(),
  empty = 
)

We know that a mismatch error is classed with a vctrs subscript oob error (as of the most recent update) and also stores the problematic selection in $i:

err_mismatch <- catch_cnd(
  eval_select(selectors$mismatch, df)
)
class(err_mismatch)
#> [1] "vctrs_error_subscript_oob" "vctrs_error_subscript"    
#> [3] "rlang_error"               "error"                    
#> [5] "condition"
err_mismatch$parent
#> NULL
err_mismatch$i
#> [1] "z"

An error from evaluating the user-supplied expression gets chained, so these errors are distinguished by having a $parent:

err_error <- catch_cnd(
  eval_select(selectors$error, df)
)
class(err_error)
#> [1] "rlang_error" "error"       "condition"
err_error$parent
#> <simpleError in eval_tidy(as_quosure(expr, env), context_mask): >
err_error$i
#> NULL

Lastly, an empty selection that's promoted to an error with allow_empty = FALSE has neither $i nor a $parent:

err_empty <- catch_cnd(
  eval_select(selectors$empty, df, allow_empty = FALSE)
)
class(err_empty)
#> [1] "rlang_error" "error"       "condition"
err_empty$parent
#> NULL
err_empty$i
#> NULL

I fully acknowledge that these are not part of the official API, so I do not mean to build on top of this pattern. Instead, I was hoping that tidyselect could throw more specific, classed errors, where possible. For example, it'd be nice for an empty selection error with allow_empty = FALSE to look something like this:

err_empty2 <- catch_cnd(
  eval_select(selectors$empty, df, allow_empty = FALSE)
)
class(err_empty2)
#> [1] "tidyselect_error_disallow_empty" "rlang_error"                    
#> [3] "error"                           "condition"

... by adding the "tidyselect_error_*" class where the error gets thrown:

https://github.com/r-lib/tidyselect/blob/fd22cc1dc08bd67fd4501bfad9de67628a35e854/R/eval-walk.R#L128-L132

I realize that this is a non-trivial ask that probably requires a lot of deliberation, but I was curious whether this is within scope / planned to be worked on. Thanks in advance for considering!

yjunechoe commented 5 months ago

Geez I'm just seeing that #350 partially addresses this -- sorry for the noise. My issue is not a complete duplicate but pretty close, so please feel free to close this issue if you decide to merge that other PR.