Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

59 error handling simpler #65

Merged
merged 22 commits into from
Sep 15, 2022
3 changes: 3 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# Generated by roxygen2: do not edit by hand

S3method("[[",quosure.error)
export(chunk)
export(chunk_call)
export(chunk_comment)
Expand Down Expand Up @@ -42,7 +43,9 @@ exportMethods(get_code)
exportMethods(get_var)
exportMethods(join)
exportMethods(new_quosure)
exportMethods(show)
import(shiny)
importFrom(R6,R6Class)
importFrom(lifecycle,badge)
importFrom(methods,show)
importFrom(styler,style_text)
1 change: 1 addition & 0 deletions R/quosure-class.R
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ setClass(
)

#' It takes a `Quosure` class and returns TRUE if the input is valid
#' @name Quosure-class
#' @keywords internal
setValidity("Quosure", function(object) {
if (length(object@code) != length(object@id)) {
Expand Down
2 changes: 2 additions & 0 deletions R/quosure-errors.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# needed to handle try-error
setOldClass("quosure.error")
81 changes: 45 additions & 36 deletions R/quosure-eval_code.R
Original file line number Diff line number Diff line change
Expand Up @@ -22,47 +22,56 @@ setGeneric("eval_code", function(object, code, name = "code") {

#' @rdname eval_code
#' @export
setMethod(
"eval_code",
signature = c("Quosure", "character"),
function(object, code, name) {
checkmate::assert_string(name)
if (is.null(names(code))) {
code <- paste(code, collapse = "\n")
names(code) <- name
}
id <- sample.int(.Machine$integer.max, size = length(code))
object@id <- c(object@id, id)
object@code <- .keep_code_name_unique(object@code, code)

# need to copy the objects from old env to new env
# to avoid updating environments in the separate objects
object@env <- .copy_env(object@env)
eval(parse(text = code), envir = object@env)
lockEnvironment(object@env)
object
setMethod("eval_code", signature = c("Quosure", "character"), function(object, code, name) {
checkmate::assert_string(name)
if (is.null(names(code))) {
code <- paste(code, collapse = "\n")
names(code) <- name
}
)
id <- sample.int(.Machine$integer.max, size = length(code))

object@id <- c(object@id, id)
object@code <- .keep_code_name_unique(object@code, code)

# need to copy the objects from old env to new env
# to avoid updating environments in the separate objects
object@env <- .copy_env(object@env)
tryCatch(
{
eval(parse(text = code), envir = object@env)
lockEnvironment(object@env)
object
},
error = function(e) {
errorCondition(
message = sprintf(
"%s \n when evaluating Quosure code:\n %s",
conditionMessage(e),
paste(code, collapse = "\n ")
),
class = c("quosure.error", "try-error", "simpleError"),
trace = object@code
)
}
)
})

#' @rdname eval_code
#' @export
setMethod(
"eval_code",
signature = c("Quosure", "expression"),
function(object, code, name) {
code_char <- as.character(code)
eval_code(object, code_char, name = name)
}
)
setMethod("eval_code", signature = c("Quosure", "expression"), function(object, code, name) {
code_char <- as.character(code)
eval_code(object, code_char, name = name)
})

#' @rdname eval_code
#' @export
setMethod(
"eval_code",
signature = c("Quosure", "language"),
function(object, code, name) {
code_char <- as.expression(code)
eval_code(object, code_char, name = name)
}
)
setMethod("eval_code", signature = c("Quosure", "language"), function(object, code, name) {
code_char <- as.expression(code)
eval_code(object, code_char, name = name)
})

#' @rdname eval_code
#' @export
setMethod("eval_code", signature = "quosure.error", function(object, code, name) {
object
})
19 changes: 19 additions & 0 deletions R/quosure-get_code.R
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@
#'
#' @export
setGeneric("get_code", function(object) {
# this line forces evaluation of object before passing to the generic
# needed for error handling to work properly
object
nikolas-burkoff marked this conversation as resolved.
Show resolved Hide resolved

standardGeneric("get_code")
})

Expand All @@ -20,3 +24,18 @@ setGeneric("get_code", function(object) {
setMethod("get_code", signature = "Quosure", function(object) {
object@code
})

#' @rdname get_code
#' @export
setMethod("get_code", signature = "quosure.error", function(object) {
nikolas-burkoff marked this conversation as resolved.
Show resolved Hide resolved
stop(
errorCondition(
sprintf(
"%s\n\ntrace: \n %s\n",
conditionMessage(object),
paste(object$trace, collapse = "\n ")
),
class = c("validation", "try-error", "simpleError")
)
)
})
21 changes: 20 additions & 1 deletion R/quosure-get_var.R
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,31 @@ setMethod("get_var", signature = c("Quosure", "character"), function(object, var
get(var, envir = object@env)
})

#' @rdname get_var
#' @export
setMethod("get_var", signature = "quosure.error", function(object, var) {
stop(errorCondition(
list(message = conditionMessage(object)),
class = c("validation", "try-error", "simpleError")
))
})


#' @param x (`Quosure`)
#' @param i (`character`) name of the binding in environment (name of the variable)
#' @param j not used
#' @param ... not used
#' @rdname get_var
#' @export
setMethod("[[", c("Quosure", "ANY", "missing"), function(x, i, j, ...) {
setMethod("[[", signature = c("Quosure", "ANY", "missing"), function(x, i, j, ...) {
get_var(x, i)
})

#' @rdname get_var
#' @export
`[[.quosure.error` <- function(x, i, j, ...) {
stop(errorCondition(
list(message = conditionMessage(x)),
class = c("validation", "try-error", "simpleError")
))
}
12 changes: 12 additions & 0 deletions R/quosure-join.R
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,18 @@ setMethod("join", signature = c("Quosure", "Quosure"), function(x, y) {
x
})

#' @rdname join
#' @export
setMethod("join", signature = "quosure.error", function(x, y) {
x
})

#' @rdname join
#' @export
setMethod("join", signature = c("Quosure", "quosure.error"), function(x, y) {
y
})

#' If two `Quosure` can be joined
#'
#' Checks if two `Quosure` objects can be combined.
Expand Down
18 changes: 18 additions & 0 deletions R/quosure-show.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#' Show the `Quosure` object
#'
#' Prints the `Quosure` object
#' @param object (`Quosure`)
#' @return nothing
#' @importFrom methods show
#' @examples
#' q1 <- new_quosure(code = "print('a')", env = new.env())
#' q1
#' @export
setMethod("show", "Quosure", function(object) {
obs <- names(as.list(object@env))
if (length(obs) > 0) {
cat(paste("A quosure object containing:", paste(obs, collapse = ", ")))
} else {
cat("A quosure object containing no objects")
}
})
3 changes: 3 additions & 0 deletions man/eval_code.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions man/get_code.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions man/get_var.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions man/join.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 21 additions & 0 deletions man/show-Quosure-method.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

17 changes: 13 additions & 4 deletions tests/testthat/test-eval_code.R
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ testthat::test_that("eval_code doesn't have access to environment where it's cal
a <- 1L
q1 <- new_quosure("a <- 1", env = environment())
b <- 2L
testthat::expect_error(eval_code(q1, "d <- b"), "object 'b' not found")
testthat::expect_s3_class(eval_code(q1, "d <- b"), c("quosure.error", "try-error", "error", "condition"))
})

testthat::test_that("@env in quosure is always a sibling of .GlobalEnv", {
Expand All @@ -30,15 +30,15 @@ testthat::test_that("library have to be called separately before using function
testthat::expect_identical(parent.env(q2@env), parent.env(.GlobalEnv))

detach("package:checkmate", unload = TRUE)
testthat::expect_error(
testthat::expect_s3_class(
eval_code(
new_quosure(),
as.expression(c(
quote(library(checkmate)),
quote(assert_number(1))
))
),
"could not find function \"assert_number\""
"quosure.error"
)
})

Expand Down Expand Up @@ -89,7 +89,16 @@ testthat::test_that("each eval_code adds name to passed code", {
testthat::expect_identical(q3@code, c(test = "a <- 1", test2 = "b <- 2"))
})

testthat::test_that("get_code make name of the code block unique if duplicated", {
testthat::test_that("an error when calling eval_code returns a quosure.error object which has message and trace", {
q <- eval_code(new_quosure(), "x <- 1")
q <- eval_code(q, "y <- 2")
q <- eval_code(q, "z <- w * x")
testthat::expect_s3_class(q, "quosure.error")
testthat::expect_equal(unname(q$trace), c("x <- 1", "y <- 2", "z <- w * x"))
testthat::expect_equal(q$message, "object 'w' not found \n when evaluating Quosure code:\n z <- w * x")
})

testthat::test_that("eval_code make name of the code block unique if duplicated", {
q1 <- new_quosure()
q2 <- eval_code(q1, code = "a <- 1", name = "test")
q3 <- eval_code(q2, code = "b <- 2", name = "test")
Expand Down
21 changes: 21 additions & 0 deletions tests/testthat/test-quosure-get_code.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
testthat::test_that("get_code returns code of Quosure object", {
q <- new_quosure(list2env(list(x = 1)), code = "x <- 1")
q <- eval_code(q, "y <- x", name = "next_code")
testthat::expect_equal(get_code(q), c("initial code" = "x <- 1", "next_code" = "y <- x"))
})

testthat::test_that("get_code called with quosure.error returns error with trace in error message", {
q <- new_quosure(list2env(list(x = 1)), code = "x <- 1")
q <- eval_code(q, "y <- x")
q <- eval_code(q, "w <- v")

code <- tryCatch(
get_code(q),
error = function(e) e
)
testthat::expect_equal(class(code), c("validation", "try-error", "simpleError", "error", "condition"))
testthat::expect_equal(
code$message,
"object 'v' not found \n when evaluating Quosure code:\n w <- v\n\ntrace: \n x <- 1\n y <- x\n w <- v\n"
)
})
Loading