Skip to content

Commit

Permalink
Ensure Ops falls back to base behaviour (#382)
Browse files Browse the repository at this point in the history
Fixes #320
  • Loading branch information
hadley committed Nov 30, 2023
1 parent 42536b6 commit 2cec150
Show file tree
Hide file tree
Showing 5 changed files with 45 additions and 2 deletions.
7 changes: 7 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# S7 (development version)

* When a method is not found, the error now has class `S7_error_method_not_found`.

* The `Ops` generic now falls back to base Ops behaviour when one of the
arguments is not an S7 object (#320). This means that you get the somewhat
inconsistent base behaviour, but means that S7 doesn't introduce a new axis
of inconsistency.

* In `new_class()`, properties can either be named by naming the element
of the list or by supplying the `name` argument to `new_property()` (#371).

Expand Down
3 changes: 2 additions & 1 deletion R/method-dispatch.R
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
method_lookup_error <- function(name, args) {
types <- vcapply(args, obj_desc)
msg <- method_lookup_error_message(name, types)
stop(msg, call. = FALSE)
cnd <- errorCondition(msg, class = c("S7_error_method_not_found", "error"))
stop(cnd)
}

method_lookup_error_message <- function(name, types) {
Expand Down
13 changes: 12 additions & 1 deletion R/method-ops.R
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,18 @@ on_load_define_ops <- function() {

#' @export
Ops.S7_object <- function(e1, e2) {
base_ops[[.Generic]](e1, e2)
cnd <- tryCatch(
return(base_ops[[.Generic]](e1, e2)),
S7_error_method_not_found = function(cnd) cnd
)

if (S7_inherits(e1) && S7_inherits(e2)) {
stop(cnd)
} else {
# Must call NextMethod() directly in the method, not wrapped in an
# anonymous function.
NextMethod()
}
}

#' @rawNamespace if (getRversion() >= "4.3.0") S3method(chooseOpsMethod, S7_object)
Expand Down
4 changes: 4 additions & 0 deletions tests/testthat/test-method-dispatch.R
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,8 @@ test_that("single dispatch fails with informative messages", {
fail(foo())
fail(Foo(x = 1))
})

expect_error(fail(TRUE), class = "S7_error_method_not_found")
})

test_that("multiple dispatch fails with informative messages", {
Expand All @@ -157,6 +159,8 @@ test_that("multiple dispatch fails with informative messages", {
fail(, TRUE)
fail(TRUE, TRUE)
})

expect_error(fail(TRUE, TRUE), class = "S7_error_method_not_found")
})


Expand Down
20 changes: 20 additions & 0 deletions tests/testthat/test-method-ops.R
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ test_that("Ops generics dispatch to S7 methods for S7 classes", {
expect_equal(foo1() + foo2(), "foo1-foo2")
expect_equal(foo2() + foo1(), "foo2-foo1")
expect_equal(foo2() + foo2(), "foo2-foo2")

expect_error(foo1() + new_class("foo3")(), class = "S7_error_method_not_found")
})

test_that("Ops generics dispatch to S3 methods", {
Expand Down Expand Up @@ -76,6 +78,24 @@ test_that("Ops generics dispatch to S7 methods for NULL", {
expect_equal(NULL + foo(), "NULL-foo")
})

test_that("Ops generics falls back to base behaviour", {
local_methods(base_ops[["+"]])

foo <- new_class("foo", parent = class_double)
expect_equal(foo(1) + 1, foo(2))
expect_equal(foo(1) + 1:2, 2:3)
expect_equal(1 + foo(1), foo(2))
expect_equal(1:2 + foo(1), 2:3)

# but can be overridden
method(`+`, list(foo, class_numeric)) <- function(e1, e2) "foo-numeric"
method(`+`, list(class_numeric, foo)) <- function(e1, e2) "numeric-foo"
expect_equal(foo(1) + 1, "foo-numeric")
expect_equal(foo(1) + 1:2, "foo-numeric")
expect_equal(1 + foo(1), "numeric-foo")
expect_equal(1:2 + foo(1), "numeric-foo")
})

test_that("`%*%` dispatches to S7 methods", {
skip_if(getRversion() < "4.3")
local_methods(base_ops[["+"]])
Expand Down

0 comments on commit 2cec150

Please sign in to comment.