Skip to content

Commit 02dfd63

Browse files
committed
Fallback for older S3
1 parent 27d964c commit 02dfd63

5 files changed

Lines changed: 31 additions & 3 deletions

File tree

R/S3.R

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,21 @@ is_S3_class <- function(x) {
147147
inherits(x, "S7_S3_class")
148148
}
149149

150+
# Detect the stub constructor that `new_S3_class()` inserts when no constructor
151+
# is supplied. Needed as a fallback for `S7_S3_class` objects created by older
152+
# versions of S7.
153+
is_S3_stub_constructor <- function(constructor) {
154+
if (!is.function(constructor)) {
155+
return(FALSE)
156+
}
157+
call <- find_call(body(constructor), quote(sprintf))
158+
if (is.null(call)) {
159+
return(FALSE)
160+
}
161+
fmt <- call[[2]]
162+
is.character(fmt) && grepl("doesn't have a constructor", fmt, fixed = TRUE)
163+
}
164+
150165
# -------------------------------------------------------------------------
151166
# Pull out validation functions so hit by code coverage
152167

R/class.R

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -270,8 +270,13 @@ is_class <- function(x) inherits(x, "S7_class")
270270
# A class you can't supply an instance of: an abstract S7 class, or an S3 class
271271
# registered without a constructor (e.g. a marker class like "gg" or "POSIXt").
272272
class_is_abstract <- function(class) {
273-
(is_class(class) && class@abstract) ||
274-
(is_S3_class(class) && isTRUE(class$abstract))
273+
if (is_class(class)) {
274+
class@abstract
275+
} else if (is_S3_class(class)) {
276+
class$abstract %||% is_S3_stub_constructor(class$constructor)
277+
} else {
278+
FALSE
279+
}
275280
}
276281

277282
check_parent <- function(parent, class, call = sys.call(-1L)) {

R/convert.R

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ convert_up <- function(from, to, call = sys.call(-1L)) {
169169
if (is_base_class(to)) {
170170
from <- zap_attr(from, c(from_props, "S7_class", "class"))
171171
} else if (is_S3_class(to)) {
172-
if (to$abstract) {
172+
if (class_is_abstract(to)) {
173173
msg <- sprintf("Can't convert to abstract class <%s>.", to$class[[1]])
174174
stop2(msg, call = call)
175175
}

tests/testthat/_snaps/convert.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,4 @@
2121
Condition
2222
Error in `convert()`:
2323
! Can't convert to abstract class <POSIXt>.
24+

tests/testthat/test-class.R

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,13 @@ describe("new_object()", {
167167
expect_no_error(Concrete(list(1, "A")))
168168
})
169169

170+
it("has fallback for S3 classes created by older S7 (#686)", {
171+
old_s3 <- class_POSIXt
172+
old_s3$abstract <- NULL
173+
Foo <- new_class("Foo", parent = old_s3, constructor = \(x) new_object(x))
174+
expect_no_error(Foo(list(1, "A")))
175+
})
176+
170177
it("errors if `.parent` is supplied but class has no parent", {
171178
NoParent <- new_class(
172179
"NoParent",

0 commit comments

Comments
 (0)