local_error_call: Set local error call in an execution environment

View source: R/cnd-abort.R

local_error_callR Documentation

Set local error call in an execution environment

Description

local_error_call() is an alternative to explicitly passing a call argument to abort(). It sets the call (or a value that indicates where to find the call, see below) in a local binding that is automatically picked up by abort().

Usage

local_error_call(call, frame = caller_env())

Arguments

call

This can be:

  • A call to be used as context for an error thrown in that execution environment.

  • The NULL value to show no context.

  • An execution environment, e.g. as returned by caller_env(). The sys.call() for that environment is taken as context.

frame

The execution environment in which to set the local error call.

Motivation for setting local error calls

By default abort() uses the function call of its caller as context in error messages:

foo <- function() abort("Uh oh.")
foo()
#> Error in `foo()`: Uh oh.

This is not always appropriate. For example a function that checks an input on the behalf of another function should reference the latter, not the former:

arg_check <- function(arg,
                      error_arg = as_string(substitute(arg))) {
  abort(cli::format_error("{.arg {error_arg}} is failing."))
}

foo <- function(x) arg_check(x)
foo()
#> Error in `arg_check()`: `x` is failing.

The mismatch is clear in the example above. arg_check() does not have any x argument and so it is confusing to present arg_check() as being the relevant context for the failure of the x argument.

One way around this is to take a call or error_call argument and pass it to abort(). Here we name this argument error_call for consistency with error_arg which is prefixed because there is an existing arg argument. In other situations, taking arg and call arguments might be appropriate.

arg_check <- function(arg,
                      error_arg = as_string(substitute(arg)),
                      error_call = caller_env()) {
  abort(
    cli::format_error("{.arg {error_arg}} is failing."),
    call = error_call
  )
}

foo <- function(x) arg_check(x)
foo()
#> Error in `foo()`: `x` is failing.

This is the generally recommended pattern for argument checking functions. If you mention an argument in an error message, provide your callers a way to supply a different argument name and a different error call. abort() stores the error call in the call condition field which is then used to generate the "in" part of error messages.

In more complex cases it's often burdensome to pass the relevant call around, for instance if your checking and throwing code is structured into many different functions. In this case, use local_error_call() to set the call locally or instruct abort() to climb the call stack one level to find the relevant call. In the following example, the complexity is not so important that sparing the argument passing makes a big difference. However this illustrates the pattern:

arg_check <- function(arg,
                      error_arg = caller_arg(arg),
                      error_call = caller_env()) {
  # Set the local error call
  local_error_call(error_call)

  my_classed_stop(
    cli::format_error("{.arg {error_arg}} is failing.")
  )
}

my_classed_stop <- function(message) {
  # Forward the local error call to the caller's
  local_error_call(caller_env())

  abort(message, class = "my_class")
}

foo <- function(x) arg_check(x)
foo()
#> Error in `foo()`: `x` is failing.

Error call flags in performance-critical functions

The call argument can also be the string "caller". This is equivalent to caller_env() or parent.frame() but has a lower overhead because call stack introspection is only performed when an error is triggered. Note that eagerly calling caller_env() is fast enough in almost all cases.

If your function needs to be really fast, assign the error call flag directly instead of calling local_error_call():

.__error_call__. <- "caller"

Examples

# Set a context for error messages
function() {
  local_error_call(quote(foo()))
  local_error_call(sys.call())
}

# Disable the context
function() {
  local_error_call(NULL)
}

# Use the caller's context
function() {
  local_error_call(caller_env())
}

hadley/rlang documentation built on Dec. 17, 2024, 3:26 a.m.