-
Notifications
You must be signed in to change notification settings - Fork 49
high priorty S4 compatibility fixes #721
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
Changes from all commits
c86a5d8
a609dda
e4bba92
d9e4f05
d2b754e
bd4e0e1
36d225e
ab070fc
328a84c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -347,7 +347,7 @@ check_parent <- function(parent, class, call = sys.call(-1L)) { | |
| #' @rdname new_class | ||
| #' @export | ||
| new_object <- function(`_parent`, ...) { | ||
| class <- sys.function(-1) | ||
| class <- sys.function(sys.parent()) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you explain why this is needed/what it does?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The case that exposed this problem is To quote the admittedly vague man page: I guess this is partly why rlang was created :)
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah, makes sense! |
||
| if (!inherits(class, "S7_class")) { | ||
| stop2("`new_object()` must be called from within a constructor.") | ||
| } | ||
|
|
@@ -485,6 +485,7 @@ check_prop_overrides <- function( | |
| call = sys.call(-1L) | ||
| ) { | ||
| overridden <- intersect(names(child_props), names(parent_props)) | ||
| check_S4_slot_overrides(child_props, parent, call = call) | ||
|
|
||
| for (prop in overridden) { | ||
| child_prop <- child_props[[prop]] | ||
|
|
@@ -516,3 +517,34 @@ check_prop_overrides <- function( | |
| } | ||
| } | ||
| } | ||
|
|
||
| check_S4_slot_overrides <- function( | ||
| child_props, | ||
| parent, | ||
| call = sys.call(-1L) | ||
| ) { | ||
| parent_S4 <- if (is_S4_class(parent)) parent else S4_ancestor(parent) | ||
| if (is.null(parent_S4)) { | ||
| return(invisible()) | ||
| } | ||
|
|
||
| overridden <- intersect(names(child_props), names(parent_S4@slots)) | ||
|
|
||
| for (prop in overridden) { | ||
| child_prop <- child_props[[prop]] | ||
| if (!prop_is_encapsulated(child_prop)) { | ||
| next | ||
| } | ||
|
|
||
| msg <- sprintf( | ||
| paste0( | ||
| "Can't override inherited S4 slot %s with a property that has a ", | ||
| "custom getter or setter." | ||
| ), | ||
| prop | ||
| ) | ||
| stop2(msg, call = call) | ||
| } | ||
|
|
||
| invisible() | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -608,3 +608,7 @@ prop_is_read_only <- function(prop) { | |
| prop_has_setter <- function(prop) is.function(prop$setter) | ||
|
|
||
| prop_is_dynamic <- function(prop) is.function(prop$getter) | ||
|
|
||
| prop_is_encapsulated <- function(prop) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice |
||
| prop_is_dynamic(prop) || prop_has_setter(prop) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -464,15 +464,15 @@ test_that("S4_contains rejects properties with custom accessors", { | |
| expect_no_error(S4_register(S4regContainsDynamic)) | ||
| expect_error( | ||
| S4_contains(S4regContainsDynamic), | ||
| "custom getter" | ||
| "custom getter or setter" | ||
| ) | ||
| methods::setClass( | ||
| "S4regContainsDynamicChild", | ||
| contains = "S7::S4regContainsDynamic" | ||
| ) | ||
| expect_error( | ||
| methods::new("S4regContainsDynamicChild"), | ||
| "custom getter" | ||
| "custom getter or setter" | ||
| ) | ||
|
|
||
| S4regContainsSetter <- new_class( | ||
|
|
@@ -491,15 +491,15 @@ test_that("S4_contains rejects properties with custom accessors", { | |
| expect_no_error(S4_register(S4regContainsSetter)) | ||
| expect_error( | ||
| S4_contains(S4regContainsSetter), | ||
| "custom setter" | ||
| "custom getter or setter" | ||
| ) | ||
| methods::setClass( | ||
| "S4regContainsSetterChild", | ||
| contains = "S7::S4regContainsSetter" | ||
| ) | ||
| expect_error( | ||
| methods::new("S4regContainsSetterChild"), | ||
| "custom setter" | ||
| "custom getter or setter" | ||
| ) | ||
| }) | ||
|
|
||
|
|
@@ -702,6 +702,104 @@ test_that("S7 classes can extend S4 classes", { | |
| expect_error(Child(x = "x", y = "a")) | ||
| }) | ||
|
|
||
| test_that("S7 constructors dispatch to S4 parent initialize methods", { | ||
| defer(S4_remove_classes(c( | ||
| "S4regParentInitialize", | ||
| "S4regChildInitialize", | ||
| "S4regVirtualInitialize", | ||
| "S4regVirtualChildInitialize" | ||
| ))) | ||
| setClass("S4regParentInitialize", slots = list(x = "ANY", y = "matrix")) | ||
| setMethod( | ||
| "initialize", | ||
| "S4regParentInitialize", | ||
| function(.Object, x = NULL, y, value = x) { | ||
| .Object@x <- list(value) | ||
| .Object@y <- y + 1 | ||
| .Object | ||
| } | ||
| ) | ||
| S4regChildInitialize := new_class( | ||
| parent = getClass("S4regParentInitialize"), | ||
| package = NULL | ||
| ) | ||
|
|
||
| y <- matrix(1, nrow = 1) | ||
| object <- S4regChildInitialize(x = 1, y = y) | ||
|
|
||
| expect_equal(object@x, list(1)) | ||
| expect_equal(object@y, y + 1) | ||
| expect_identical(methods::validObject(object), TRUE) | ||
|
|
||
| object <- methods::initialize(object, value = 2, y = y) | ||
| expect_equal(object@x, list(2)) | ||
| expect_equal(object@y, y + 1) | ||
|
|
||
| setClass( | ||
| "S4regVirtualInitialize", | ||
| contains = "VIRTUAL", | ||
| slots = list(x = "ANY", y = "matrix") | ||
| ) | ||
| setMethod( | ||
| "initialize", | ||
| "S4regVirtualInitialize", | ||
| function(.Object, x = NULL, y, value = x) { | ||
| .Object@x <- list(value) | ||
| .Object@y <- y + 1 | ||
| .Object | ||
| } | ||
| ) | ||
| S4regVirtualChildInitialize := new_class( | ||
| parent = getClass("S4regVirtualInitialize"), | ||
| package = NULL | ||
| ) | ||
|
|
||
| object <- S4regVirtualChildInitialize(x = 1, y = y) | ||
|
|
||
| expect_equal(object@x, list(1)) | ||
| expect_equal(object@y, y + 1) | ||
| expect_identical(methods::validObject(object), TRUE) | ||
| }) | ||
|
|
||
| test_that("S7 classes can not override S4 slots with custom accessors", { | ||
| defer(S4_remove_classes(c( | ||
| "S4regAccessorParent", | ||
| "S4regAccessorMiddle" | ||
| ))) | ||
| setClass("S4regAccessorParent", slots = list(x = "numeric")) | ||
|
|
||
| expect_error( | ||
| new_class( | ||
| "S4regAccessorChild", | ||
| parent = getClass("S4regAccessorParent"), | ||
| properties = list( | ||
| x = new_property(class_numeric, getter = function(self) 1) | ||
| ), | ||
| package = NULL | ||
| ), | ||
| "custom getter or setter" | ||
| ) | ||
|
|
||
| S4regAccessorMiddle := new_class( | ||
| parent = getClass("S4regAccessorParent"), | ||
| package = NULL | ||
| ) | ||
| expect_error( | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Might be useful to have at least one snapshot test of this error so you can see the whole thing in one place? |
||
| new_class( | ||
| "S4regAccessorChild", | ||
| parent = S4regAccessorMiddle, | ||
| properties = list( | ||
| x = new_property( | ||
| class_numeric, | ||
| setter = function(self, value) self | ||
| ) | ||
| ), | ||
| package = NULL | ||
| ), | ||
| "custom getter or setter" | ||
| ) | ||
| }) | ||
|
|
||
| test_that("S4 initialization sets S4 slots on subclasses of S7 classes", { | ||
| on.exit(S4_remove_classes(c( | ||
| "ParentForSlots", | ||
|
|
@@ -805,13 +903,13 @@ test_that("S4 classes can not extend S7-over-S4 classes with property setters", | |
| expect_true(attr(Child2(x = 1, y = "a"), "setter_called", exact = TRUE)) | ||
| expect_error( | ||
| S4_contains(Child2), | ||
| "custom setter" | ||
| "custom getter or setter" | ||
| ) | ||
|
|
||
| methods::setClass("S4Child2", contains = "Child2") | ||
| expect_error( | ||
| methods::new("S4Child2"), | ||
| "custom setter" | ||
| "custom getter or setter" | ||
| ) | ||
| }) | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -116,6 +116,47 @@ test_that("inheritance lets child properties narrow with S4 inheritance", { | |
| expect_s4_class(Child(x = x)@x, "S4PropertyChild") | ||
| }) | ||
|
|
||
| test_that("inheritance lets S7 children narrow S4 parent properties", { | ||
| Animal := local_S4_class() | ||
| Kennel := local_S4_class(slots = list(dog = "Animal")) | ||
| Dog := new_class(parent = Animal, package = NULL) | ||
|
|
||
| DogKennel := new_class( | ||
| parent = Kennel, | ||
| properties = list(dog = Dog), | ||
| package = NULL | ||
| ) | ||
| dog <- Dog() | ||
|
|
||
| expect_equal(prop(DogKennel(dog = dog), "dog"), dog) | ||
| }) | ||
|
|
||
| test_that("inheritance lets S4 children narrow S7 parent properties", { | ||
| defer(S4_remove_classes(c( | ||
| "S4PropertyS7Parent", | ||
| "S4PropertyS7Child" | ||
| ))) | ||
|
|
||
| S4PropertyS7Parent := new_class(package = NULL) | ||
| S4_register(S4PropertyS7Parent) | ||
| setClass( | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll make a separate PR addressing the S4 class clean up in tests. |
||
| "S4PropertyS7Child", | ||
| contains = S4_contains(S4PropertyS7Parent) | ||
| ) | ||
| S4PropertyParent := new_class( | ||
| properties = list(x = S4PropertyS7Parent), | ||
| package = NULL | ||
| ) | ||
| S4PropertyChild := new_class( | ||
| parent = S4PropertyParent, | ||
| properties = list(x = getClass("S4PropertyS7Child")), | ||
| package = NULL | ||
| ) | ||
|
|
||
| x <- methods::new("S4PropertyS7Child") | ||
| expect_s4_class(S4PropertyChild(x = x)@x, "S4PropertyS7Child") | ||
| }) | ||
|
|
||
| test_that("inheritance doesn't let child properties narrow S7_object with base or S3 classes", { | ||
| Parent := new_class( | ||
| properties = list(x = S7_object), | ||
|
|
@@ -299,6 +340,15 @@ test_that("new_object() gives useful error if called directly", { | |
| expect_snapshot(new_object(), error = TRUE) | ||
| }) | ||
|
|
||
| test_that("new_object() can be forced lazily from a constructor", { | ||
| Foo := new_class( | ||
| constructor = function() identity(new_object(S7_object())), | ||
| package = NULL | ||
| ) | ||
|
|
||
| expect_equal(S7_class(Foo()), Foo) | ||
| }) | ||
|
|
||
| test_that("new_object() errors if `_parent` doesn't inherit from the parent class (#409)", { | ||
| Bar := new_class(package = NULL) | ||
| # `_parent` should be `Bar()`, not the class spec `Bar` | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.