Skip to content

Commit

Permalink
Error consistency (#553)
Browse files Browse the repository at this point in the history
* Modernise error handling in `str_interp()`

* Make errors more consistent
  • Loading branch information
hadley authored Jul 17, 2024
1 parent 5a96ff0 commit 7ae80ff
Show file tree
Hide file tree
Showing 7 changed files with 67 additions and 27 deletions.
21 changes: 14 additions & 7 deletions R/interp.R
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ str_interp <- function(string, env = parent.frame()) {
#'
#' @noRd
#' @author Stefan Milton Bache
interp_placeholders <- function(string) {
interp_placeholders <- function(string, error_call = caller_env()) {
# Find starting position of ${} or $[]{} placeholders.
starts <- gregexpr("\\$(\\[.*?\\])?\\{", string)[[1]]

Expand All @@ -101,7 +101,7 @@ interp_placeholders <- function(string) {
# If there are nested placeholders, each part will not contain a full
# placeholder in which case we report invalid string interpolation template.
if (any(!grepl("\\$(\\[.*?\\])?\\{.+\\}", parts)))
stop("Invalid template string for interpolation.", call. = FALSE)
cli::cli_abort("Invalid template string for interpolation.", call = error_call)

# For each part, find the opening and closing braces.
opens <- lapply(strsplit(parts, ""), function(v) which(v == "{"))
Expand Down Expand Up @@ -134,9 +134,9 @@ interp_placeholders <- function(string) {
#'
#' @noRd
#' @author Stefan Milton Bache
eval_interp_matches <- function(matches, env) {
eval_interp_matches <- function(matches, env, error_call = caller_env()) {
# Extract expressions from the matches
expressions <- extract_expressions(matches)
expressions <- extract_expressions(matches, error_call = error_call)

# Evaluate them in the given environment
values <- lapply(expressions, eval, envir = env,
Expand All @@ -161,12 +161,19 @@ eval_interp_matches <- function(matches, env) {
#'
#' @noRd
#' @author Stefan Milton Bache
extract_expressions <- function(matches) {
extract_expressions <- function(matches, error_call = caller_env()) {
# Parse function for text argument as first argument.

parse_text <- function(text) {
tryCatch(
withCallingHandlers(
parse(text = text),
error = function(e) stop(conditionMessage(e), call. = FALSE)
error = function(e) {
cli::cli_abort(
"Failed to parse input {.str {text}}",
parent = e,
call = error_call
)
}
)
}

Expand Down
4 changes: 2 additions & 2 deletions R/match.R
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
str_match <- function(string, pattern) {
check_lengths(string, pattern)
if (type(pattern) != "regex") {
cli::cli_abort("`pattern` must be a regular expression.")
cli::cli_abort("{.arg pattern} must be a regular expression.")
}

stri_match_first_regex(string,
Expand All @@ -65,7 +65,7 @@ str_match <- function(string, pattern) {
str_match_all <- function(string, pattern) {
check_lengths(string, pattern)
if (type(pattern) != "regex") {
cli::cli_abort("`pattern` must be a regular expression.")
cli::cli_abort("{.arg pattern} must be a regular expression.")
}

stri_match_all_regex(string,
Expand Down
2 changes: 1 addition & 1 deletion R/modifiers.R
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ type.default <- function(x, error_call = caller_env()) {
}

cli::cli_abort(
"`pattern` must be a string, not {.obj_type_friendly {x}}.",
"{.arg pattern} must be a string, not {.obj_type_friendly {x}}.",
call = error_call
)
}
Expand Down
8 changes: 4 additions & 4 deletions R/replace.R
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,8 @@ str_replace_all <- function(string, pattern, replacement) {


switch(type(pattern),
empty = cli::cli_abort("{.arg pattern} can't be empty."),
bound = cli::cli_abort("{.arg pattern} can't be a boundary."),
empty = no_empty(),
bound = no_boundary(),
fixed = stri_replace_all_fixed(string, pattern, replacement,
vectorize_all = vec, opts_fixed = opts(pattern)),
coll = stri_replace_all_coll(string, pattern, replacement,
Expand Down Expand Up @@ -217,13 +217,13 @@ str_transform_all <- function(string, pattern, replacement, error_call = caller_

if (!is.character(new_flat)) {
cli::cli_abort(
"Function {.arg replacement} must return a character vector, not {.obj_type_friendly {new_flat}}.",
"{.arg replacement} function must return a character vector, not {.obj_type_friendly {new_flat}}.",
call = error_call
)
}
if (length(new_flat) != length(old_flat)) {
cli::cli_abort(
"Function {.arg replacement} must return a vector the same length as the input ({length(old_flat)}), not length {length(new_flat)}.",
"{.arg replacement} function must return a vector the same length as the input ({length(old_flat)}), not length {length(new_flat)}.",
call = error_call
)
}
Expand Down
33 changes: 33 additions & 0 deletions tests/testthat/_snaps/interp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# str_interp fails when encountering nested placeholders

Code
str_interp("${${msg}}")
Condition
Error in `str_interp()`:
! Invalid template string for interpolation.
Code
str_interp("$[.2f]{${msg}}")
Condition
Error in `str_interp()`:
! Invalid template string for interpolation.

# str_interp fails when input is not a character string

Code
str_interp(3L)
Condition
Error in `str_interp()`:
! `string` must be a character vector, not the number 3.

# str_interp wraps parsing errors

Code
str_interp("This is a ${1 +}")
Condition
Error in `str_interp()`:
! Failed to parse input "1 +"
Caused by error in `parse()`:
! <text>:2:0: unexpected end of input
1: 1 +
^

6 changes: 3 additions & 3 deletions tests/testthat/_snaps/replace.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
str_replace_all("x", "", "")
Condition
Error in `str_replace_all()`:
! `pattern` can't be empty.
! `pattern` can't be the empty string (`""`).
Code
str_replace_all("x", boundary("word"), "")
Condition
Expand All @@ -46,12 +46,12 @@
str_replace_all("x", "x", ~1)
Condition
Error in `str_replace_all()`:
! Function `replacement` must return a character vector, not a number.
! `replacement` function must return a character vector, not a number.
Code
str_replace_all("x", "x", ~ c("a", "b"))
Condition
Error in `str_replace_all()`:
! Function `replacement` must return a vector the same length as the input (1), not length 2.
! `replacement` function must return a vector the same length as the input (1), not length 2.

# backrefs are correctly translated

Expand Down
20 changes: 10 additions & 10 deletions tests/testthat/test-interp.R
Original file line number Diff line number Diff line change
Expand Up @@ -52,19 +52,19 @@ test_that("str_interp fails when encountering nested placeholders", {
msg <- "This will never see the light of day"
num <- 1.2345

expect_error(
str_interp("${${msg}}"),
"Invalid template string for interpolation"
)

expect_error(
str_interp("$[.2f]{${msg}}"),
"Invalid template string for interpolation"
)
expect_snapshot(error = TRUE, {
str_interp("${${msg}}")
str_interp("$[.2f]{${msg}}")
})
})

test_that("str_interp fails when input is not a character string", {
expect_error(str_interp(3L))
expect_snapshot(str_interp(3L), error = TRUE)
})

test_that("str_interp wraps parsing errors", {
expect_snapshot(str_interp("This is a ${1 +}"), error = TRUE)

})

test_that("str_interp formats list independetly of other placeholders", {
Expand Down

0 comments on commit 7ae80ff

Please sign in to comment.