R: Set local error call in an execution environment

local_error_call {rlang} | R 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())
    }

[Package rlang version 1.0.2 Index]