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

Automatically detect package name in new_class(package=) #459

Merged
merged 11 commits into from
Oct 16, 2024
7 changes: 5 additions & 2 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
# S7 (development version)

* `new_class()` now automatically infers the package name when called from
within an R package (#459).

* Improved error message when custom validators return invalid values (#454, #457).

* New `nameOfClass()` method exported for S7 base classes, to enable usage like
`inherits("foo", S7::class_character)` (#432, #458)

* Added support for more base/S3 classes (#434):
`class_POSIXlt`, `class_POSIXt`, `class_matrix`, `class_array`,
`class_formula`, `class_call`, `class_language`, `class_name`
`class_POSIXlt`, `class_POSIXt`, `class_formula`,
`class_call`, `class_language`, `class_name`

* Fixed S3 methods registration across packages (#422).

Expand Down
7 changes: 7 additions & 0 deletions R/aaa.R
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ new_function <- function(args = NULL,
x
}


topNamespaceName <- function(env = parent.frame()) {
env <- topenv(env)
if (isNamespace(env))
getNamespaceName(env)
}

is_string <- function(x) {
identical(class(x), "character") && length(x) == 1L && !is.na(x) && x != ""
}
2 changes: 1 addition & 1 deletion R/class.R
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@
new_class <- function(
name,
parent = S7_object,
package = NULL,
package = topNamespaceName(parent.frame()),
properties = list(),
abstract = FALSE,
constructor = NULL,
Expand Down
1 change: 0 additions & 1 deletion R/method-register.R
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,6 @@ register_S3_method <- function(generic, signature, method, envir = parent.frame(
}

class <- S7_class_name(signature[[1]])
# dbg(generic$name, class, method, envir)
registerS3method(generic$name, class, method, envir)
}

Expand Down
2 changes: 1 addition & 1 deletion R/super.R
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
#' For example, imagine that you have made a subclass of "integer":
#'
#' ```{r}
#' myint <- new_class("myint", parent = class_integer)
#' myint <- new_class("myint", parent = class_integer, package = NULL)
#' ```
#'
#' Now you go to write a custom print method:
Expand Down
3 changes: 3 additions & 0 deletions R/zzz.R
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#' S7_object
S7_object <- new_class(
name = "S7_object",
package = NULL,
parent = NULL,
constructor = function() {
.Call(S7_object_)
Expand Down Expand Up @@ -96,6 +97,7 @@ on_load_define_S7_generic <- function() {
# errors if `@` is not usable.
S7_generic <<- new_class(
name = "S7_generic",
package = NULL,
properties = list(
name = class_character,
methods = class_environment,
Expand All @@ -114,6 +116,7 @@ S7_method <- NULL
on_load_define_S7_method <- function() {
S7_method <<- new_class(
"S7_method",
package = NULL,
parent = class_function,
properties = list(generic = S7_generic, signature = class_list)
)
Expand Down
2 changes: 1 addition & 1 deletion man/new_class.Rd

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

2 changes: 1 addition & 1 deletion man/super.Rd

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

5 changes: 3 additions & 2 deletions tests/testthat/_snaps/class.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,8 @@
# S7 object: displays nicely

Code
foo <- new_class("foo", properties = list(x = class_double, y = class_double))
foo <- new_class("foo", properties = list(x = class_double, y = class_double),
package = NULL)
foo()
Output
<foo>
Expand All @@ -191,7 +192,7 @@
# S7 object: displays objects with data nicely

Code
text <- new_class("text", class_character)
text <- new_class("text", class_character, package = NULL)
text("x")
Output
<text> chr "x"
Expand Down
4 changes: 2 additions & 2 deletions tests/testthat/_snaps/inherits.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
# throws informative error

Code
foo1 <- new_class("foo1")
foo2 <- new_class("foo2")
foo1 <- new_class("foo1", package = NULL)
foo2 <- new_class("foo2", package = NULL)
check_is_S7(foo1(), foo2)
Condition
Error:
Expand Down
2 changes: 1 addition & 1 deletion tests/testthat/_snaps/super.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# super(): checks to

Code
foo <- new_class("foo")
foo <- new_class("foo", package = NULL)
super(foo(), class_character)
Condition
Error in `super()`:
Expand Down
4 changes: 2 additions & 2 deletions tests/testthat/_snaps/union.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# has useful print method

Code
foo1 <- new_class("foo1")
foo2 <- new_class("foo2")
foo1 <- new_class("foo1", package = NULL)
foo2 <- new_class("foo2", package = NULL)
new_union(foo1, foo2)
Output
<S7_union>: <foo1> or <foo2>
Expand Down
13 changes: 10 additions & 3 deletions tests/testthat/helper.R
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,15 @@ named_list <- function(...) {

`add<-` <- `+`

dbg <- function(..., .display = utils::str) {
dbg <- function(..., .display = utils::str, .file = NULL) {
out <- NULL
exprs <- as.list(substitute(list(...)))[-1L]

if (!is.null(.file)) {
sink(.file, append = TRUE)
on.exit(sink())
}

for (i in seq_len(...length())) {
..i <- as.symbol(sprintf("..%i", i))
if (eval(substitute(missing(..i)))) {
Expand All @@ -98,7 +103,7 @@ dbg <- function(..., .display = utils::str) {
} else {
sprintf("(%s) `%s`", name, expr)
}
cat(label, ": ", sep = "")
cat(label, if (identical(.display, utils::str)) ": " else "\n", sep = "")
.display(out <- eval(..i))
}

Expand Down Expand Up @@ -127,9 +132,11 @@ dbg <- function(..., .display = utils::str) {
cat(loc, "\n")
} else {
cat(sprintf("(from call: %s (srcfile missing))\n", trimws(
deparse1(sys.call(-2), width.cutoff = 60)
deparse1(sys.call(-2) %error% sys.call(-1), width.cutoff = 60)
)))
}

invisible(out)
}

`%error%` <- function(x, y) tryCatch(x, error = function(e) y)
4 changes: 2 additions & 2 deletions tests/testthat/test-S3.R
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ test_that("new_S3_class has a print method", {
})

test_that("can construct objects that extend S3 classes", {
ordered2 <- new_class("ordered2", parent = class_factor)
ordered2 <- new_class("ordered2", parent = class_factor, package = NULL)
x <- ordered2(c(1L, 2L, 1L), letters[1:3])
expect_equal(class(x), c("ordered2", "factor", "S7_object"))
expect_equal(prop_names(x), character())
Expand All @@ -15,7 +15,7 @@ test_that("subclasses inherit validator", {
function(.data) structure(.data, class = "foo"),
function(x) if (!is.double(x)) "Underlying data must be a double"
)
foo2 <- new_class("foo2", foo)
foo2 <- new_class("foo2", foo, package = NULL)

expect_snapshot(error = TRUE, foo2("a"))
})
Expand Down
9 changes: 5 additions & 4 deletions tests/testthat/test-class-spec.R
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
test_that("can work with S7 classes", {
klass <- new_class("klass")
klass <- new_class("klass", package = NULL)
expect_equal(as_class(klass), klass)

expect_equal(class_type(klass), "S7")
Expand Down Expand Up @@ -35,8 +35,8 @@ test_that("can work with S7 classes in packages", {
})

test_that("can work with unions", {
text <- new_class("text", class_character)
number <- new_class("number", class_double)
text <- new_class("text", class_character, package = NULL)
number <- new_class("number", class_double, package = NULL)
klass <- new_union(text, number)
expect_equal(as_class(klass), klass)

Expand Down Expand Up @@ -144,7 +144,8 @@ test_that("can work with S3 classes", {

test_that("can work with S7 classes that extend S3 classes", {
Date <- new_S3_class("Date", constructor = function(.data = numeric()) .Date(.data))
Date2 <- new_class("Date2", parent = Date, properties = list(x = class_numeric))
Date2 <- new_class("Date2", parent = Date, properties = list(x = class_numeric),
package = NULL)

expect_equal(class_type(Date2), "S7")
expect_equal(class_dispatch(Date2), c("Date2", "Date", "S7_object"))
Expand Down
30 changes: 17 additions & 13 deletions tests/testthat/test-class.R
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ describe("S7 classes", {
})

it("print nicely", {
foo1 <- new_class("foo1", properties = list(x = class_integer, y = class_integer))
foo2 <- new_class("foo2", foo1)
foo1 <- new_class("foo1", properties = list(x = class_integer, y = class_integer), package = NULL)
foo2 <- new_class("foo2", foo1, package = NULL)

expect_snapshot({
foo2
Expand Down Expand Up @@ -85,8 +85,8 @@ describe("abstract classes", {
})
})
it("can construct concrete subclasses", {
foo1 <- new_class("foo1", abstract = TRUE)
foo2 <- new_class("foo2", parent = foo1)
foo1 <- new_class("foo1", abstract = TRUE, package = NULL)
foo2 <- new_class("foo2", parent = foo1, package = NULL)
expect_s3_class(foo2(), "foo2")
})
it("can use inherited validator from abstract class", {
Expand All @@ -96,9 +96,10 @@ describe("abstract classes", {
abstract = TRUE,
validator = function(self) {
if (self@x == 2) "@x has bad value"
}
},
package = NULL
)
foo2 <- new_class("foo2", parent = foo1)
foo2 <- new_class("foo2", parent = foo1, package = NULL)
expect_no_error(foo2(x = 1))
expect_snapshot(foo2(x = 2), error = TRUE)
})
Expand All @@ -112,7 +113,8 @@ describe("new_object()", {
it("validates object", {
foo <- new_class("foo",
properties = list(x = new_property(class_double)),
validator = function(self) if (self@x < 0) "x must be positive"
validator = function(self) if (self@x < 0) "x must be positive",
package = NULL
)

expect_snapshot(error = TRUE, {
Expand All @@ -136,23 +138,24 @@ describe("new_object()", {

describe("S7 object", {
it("has an S7 and S3 class", {
foo <- new_class("foo")
foo <- new_class("foo", package = NULL)
x <- foo()
expect_equal(S7_class(x), foo)
expect_equal(class(x), c("foo", "S7_object"))
})

it("displays nicely", {
expect_snapshot({
foo <- new_class("foo", properties = list(x = class_double, y = class_double))
foo <- new_class("foo", properties = list(x = class_double, y = class_double),
package = NULL)
foo()
str(list(foo()))
})
})

it("displays objects with data nicely", {
expect_snapshot({
text <- new_class("text", class_character)
text <- new_class("text", class_character, package = NULL)
text("x")
str(list(text("x")))
})
Expand All @@ -162,7 +165,8 @@ describe("S7 object", {
foo1 <- new_class(
"foo1",
parent = class_list,
properties = list(x = class_double, y = class_list)
properties = list(x = class_double, y = class_list),
package = NULL
)
expect_snapshot(
foo1(
Expand Down Expand Up @@ -213,8 +217,8 @@ describe("default constructor", {
})

it("initializes property with S7 object", {
foo1 <- new_class("foo1")
foo2 <- new_class("foo2", properties = list(x = foo1))
foo1 <- new_class("foo1", package = NULL)
foo2 <- new_class("foo2", properties = list(x = foo1), package = NULL)
x <- foo2()
expect_s3_class(x@x, "foo1")
})
Expand Down
2 changes: 1 addition & 1 deletion tests/testthat/test-constructor.R
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ test_that("can create constructors with missing or lazy defaults", {

test_that("Dynamic settable properties are included in constructor", {
Foo <- new_class(
name = "Foo",
name = "Foo", package = NULL,
properties = list(
dynamic_settable = new_property(
class_numeric,
Expand Down
10 changes: 5 additions & 5 deletions tests/testthat/test-convert.R
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
test_that("can register convert methods", {
local_methods(convert)
converttest <- new_class("converttest")
converttest <- new_class("converttest", package = NULL)
method(convert, list(converttest, class_character)) <- function(from, to, ...) "c"
method(convert, list(converttest, class_integer)) <- function(from, to, ...) "i"

Expand All @@ -25,17 +25,17 @@ describe("fallback convert", {
local_methods(convert)

it("can convert to own class", {
foo1 <- new_class("foo1")
foo2 <- new_class("foo2", foo1)
foo1 <- new_class("foo1", package = NULL)
foo2 <- new_class("foo2", foo1, package = NULL)

obj <- convert(foo2(), to = foo2)
expect_equal(class(obj), c("foo2", "foo1", "S7_object"))
expect_equal(S7_class(obj), foo2)
})

it("can convert to super class", {
foo1 <- new_class("foo1", properties = list(x = class_double))
foo2 <- new_class("foo2", foo1, properties = list(y = class_double))
foo1 <- new_class("foo1", properties = list(x = class_double), package = NULL)
foo2 <- new_class("foo2", foo1, properties = list(y = class_double), package = NULL)

obj <- convert(foo2(1, 2), to = foo1)
expect_equal(class(obj), c("foo1", "S7_object"))
Expand Down
2 changes: 1 addition & 1 deletion tests/testthat/test-generic.R
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ test_that("check_dispatch_args() produces informative errors", {

test_that("S7_generic printing", {
foo1 <- new_generic("foo1", "x")
text <- new_class("text")
text <- new_class("text", package = NULL)

method(foo1, class_character) <- function(x) 1
method(foo1, text) <- function(x) 2
Expand Down
4 changes: 2 additions & 2 deletions tests/testthat/test-inherits.R
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ test_that("checks that input is a class", {

test_that("throws informative error", {
expect_snapshot(error = TRUE, {
foo1 <- new_class("foo1")
foo2 <- new_class("foo2")
foo1 <- new_class("foo1", package = NULL)
foo2 <- new_class("foo2", package = NULL)
check_is_S7(foo1(), foo2)
})
expect_snapshot(check_is_S7("a"), error = TRUE)
Expand Down
2 changes: 1 addition & 1 deletion tests/testthat/test-method-dispatch.R
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ test_that("can dispatch on base 'union' types", {
test_that("single dispatch fails with informative messages", {
fail <- new_generic("fail", "x")

foo <- new_class("foo")
foo <- new_class("foo", package = NULL)
Foo <- setClass("Foo", slots = list("x" = "numeric"))
on.exit(S4_remove_classes("Foo"))

Expand Down
Loading
Loading