Skip to content

Commit 2cec150

Browse files
authored
Ensure Ops falls back to base behaviour (RConsortium#382)
Fixes RConsortium#320
1 parent 42536b6 commit 2cec150

File tree

5 files changed

+45
-2
lines changed

5 files changed

+45
-2
lines changed

NEWS.md

+7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# S7 (development version)
22

3+
* When a method is not found, the error now has class `S7_error_method_not_found`.
4+
5+
* The `Ops` generic now falls back to base Ops behaviour when one of the
6+
arguments is not an S7 object (#320). This means that you get the somewhat
7+
inconsistent base behaviour, but means that S7 doesn't introduce a new axis
8+
of inconsistency.
9+
310
* In `new_class()`, properties can either be named by naming the element
411
of the list or by supplying the `name` argument to `new_property()` (#371).
512

R/method-dispatch.R

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
method_lookup_error <- function(name, args) {
33
types <- vcapply(args, obj_desc)
44
msg <- method_lookup_error_message(name, types)
5-
stop(msg, call. = FALSE)
5+
cnd <- errorCondition(msg, class = c("S7_error_method_not_found", "error"))
6+
stop(cnd)
67
}
78

89
method_lookup_error_message <- function(name, types) {

R/method-ops.R

+12-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,18 @@ on_load_define_ops <- function() {
1616

1717
#' @export
1818
Ops.S7_object <- function(e1, e2) {
19-
base_ops[[.Generic]](e1, e2)
19+
cnd <- tryCatch(
20+
return(base_ops[[.Generic]](e1, e2)),
21+
S7_error_method_not_found = function(cnd) cnd
22+
)
23+
24+
if (S7_inherits(e1) && S7_inherits(e2)) {
25+
stop(cnd)
26+
} else {
27+
# Must call NextMethod() directly in the method, not wrapped in an
28+
# anonymous function.
29+
NextMethod()
30+
}
2031
}
2132

2233
#' @rawNamespace if (getRversion() >= "4.3.0") S3method(chooseOpsMethod, S7_object)

tests/testthat/test-method-dispatch.R

+4
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,8 @@ test_that("single dispatch fails with informative messages", {
143143
fail(foo())
144144
fail(Foo(x = 1))
145145
})
146+
147+
expect_error(fail(TRUE), class = "S7_error_method_not_found")
146148
})
147149

148150
test_that("multiple dispatch fails with informative messages", {
@@ -157,6 +159,8 @@ test_that("multiple dispatch fails with informative messages", {
157159
fail(, TRUE)
158160
fail(TRUE, TRUE)
159161
})
162+
163+
expect_error(fail(TRUE, TRUE), class = "S7_error_method_not_found")
160164
})
161165

162166

tests/testthat/test-method-ops.R

+20
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ test_that("Ops generics dispatch to S7 methods for S7 classes", {
1212
expect_equal(foo1() + foo2(), "foo1-foo2")
1313
expect_equal(foo2() + foo1(), "foo2-foo1")
1414
expect_equal(foo2() + foo2(), "foo2-foo2")
15+
16+
expect_error(foo1() + new_class("foo3")(), class = "S7_error_method_not_found")
1517
})
1618

1719
test_that("Ops generics dispatch to S3 methods", {
@@ -76,6 +78,24 @@ test_that("Ops generics dispatch to S7 methods for NULL", {
7678
expect_equal(NULL + foo(), "NULL-foo")
7779
})
7880

81+
test_that("Ops generics falls back to base behaviour", {
82+
local_methods(base_ops[["+"]])
83+
84+
foo <- new_class("foo", parent = class_double)
85+
expect_equal(foo(1) + 1, foo(2))
86+
expect_equal(foo(1) + 1:2, 2:3)
87+
expect_equal(1 + foo(1), foo(2))
88+
expect_equal(1:2 + foo(1), 2:3)
89+
90+
# but can be overridden
91+
method(`+`, list(foo, class_numeric)) <- function(e1, e2) "foo-numeric"
92+
method(`+`, list(class_numeric, foo)) <- function(e1, e2) "numeric-foo"
93+
expect_equal(foo(1) + 1, "foo-numeric")
94+
expect_equal(foo(1) + 1:2, "foo-numeric")
95+
expect_equal(1 + foo(1), "numeric-foo")
96+
expect_equal(1:2 + foo(1), "numeric-foo")
97+
})
98+
7999
test_that("`%*%` dispatches to S7 methods", {
80100
skip_if(getRversion() < "4.3")
81101
local_methods(base_ops[["+"]])

0 commit comments

Comments
 (0)