Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -50,25 +50,41 @@ export(class_POSIXct)
export(class_POSIXlt)
export(class_POSIXt)
export(class_any)
export(class_array)
export(class_atomic)
export(class_call)
export(class_character)
export(class_character_array)
export(class_character_matrix)
export(class_complex)
export(class_complex_array)
export(class_complex_matrix)
export(class_data.frame)
export(class_double)
export(class_double_array)
export(class_double_matrix)
export(class_environment)
export(class_expression)
export(class_factor)
export(class_formula)
export(class_function)
export(class_integer)
export(class_integer_array)
export(class_integer_matrix)
export(class_language)
export(class_list)
export(class_list_array)
export(class_list_matrix)
export(class_logical)
export(class_logical_array)
export(class_logical_matrix)
export(class_matrix)
export(class_missing)
export(class_name)
export(class_numeric)
export(class_raw)
export(class_raw_array)
export(class_raw_matrix)
export(class_vector)
export(convert)
export(method)
Expand Down
2 changes: 2 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# S7 (development version)

* Errors thrown by S7 now report the function where they occurred, making it easier to track down the source of a problem (#646).
* `class_array` and `class_matrix` are new unions that, together with the element-typed `class_double_matrix`, `class_integer_array`, etc. (one per base vector type), let you subclass matrices and arrays, e.g. `new_class("my_matrix", class_double_matrix)`. Like `class_numeric`, `class_array` and `class_matrix` can be used in `method()` and `new_property()` but not as a `parent`, so you must pick an element type when subclassing (#401).
* `method<-` now accepts `NULL` to unregister an existing method, e.g. `method(foo, class_character) <- NULL` (#613).
* `new_object()` no longer materialises ALTREP parent values (e.g. `seq_len()`), so constructing an S7 object that wraps a large compact integer sequence is now O(1) in memory instead of O(n) (@kschaubroeck, #607).
* Method dispatch on `class_missing` now correctly handles missing arguments forwarded through a wrapper functions (#595).
Expand All @@ -17,6 +18,7 @@
* `super()` now works with S3 and S4 objects, not just S7 objects (#500).
* `new_object()` no longer materialises ALTREP parent values (e.g. `seq_len()`), so constructing an S7 object that wraps a large compact integer sequence is now O(1) in memory instead of O(n) (@kschaubroeck, #607).
* `new_object()` no longer re-runs property validators for properties inherited unchanged from an already-validated parent class, so constructing an instance of a deeply nested class hierarchy validates each property exactly once (#539).
* `new_property()` now runs the property class's own validator when checking a value, not just the structural class check, so a property typed with a class like `class_double_matrix` enforces constraints (such as element type) that aren't visible in `class()` (#401).
* `new_property()` now accepts a `setter` that takes `self`, `name`, and `value` making it easy to reuse the same definition for multiple properties (#552).
* `new_S3_class()` objects now work with `inherits()` (and other functions that use `nameOfClass()`) in R 4.3 and later (@lawremi, #521).
* New `prop_info()` returns a data frame summarising the properties of an S7 object or class, with one row per property and columns for name, default, class, getter, setter, and validator (#551).
Expand Down
102 changes: 5 additions & 97 deletions R/S3.R
Original file line number Diff line number Diff line change
Expand Up @@ -184,9 +184,9 @@ validate_POSIXct <- function(self) {
return("Underlying data must be numeric")
}

tz <- attr(self, "tz")
if (!is.character(tz) || length(tz) != 1) {
return("attr(, 'tz') must be a single string")
tz <- attr(self, "tzone", exact = TRUE)
if (!is.null(tz) && (!is.character(tz) || length(tz) != 1)) {
return("attr(, 'tzone') must be NULL or a single string")
}
}

Expand Down Expand Up @@ -218,56 +218,6 @@ validate_data.frame <- function(self) {
}
}

valid_dimnames <- function(self) {
dn <- dimnames(self)
if (is.null(dn)) {
TRUE
} else if (!is.list(dn) || length(dn) != length(dim(self))) {
FALSE
} else {
for (i in seq_along(dimnames(self))) {
if (is.null(dn[[i]])) {
next
}
if (!is.character(dn[[i]]) || length(dn[[i]]) != dim(self)[[i]]) {
return(FALSE)
}
}
}
TRUE
}

validate_matrix <- function(self) {
if (!is.matrix(self)) {
# is.matrix() methods should only return TRUE if valid
"is.matrix(self) is FALSE"
} else if (
!is.integer(dim(self)) || length(dim(self)) != 2L || !all(dim(self) >= 0L)
) {
"dim(self) must be a non-negative integer vector of length 2"
} else if (!valid_dimnames(self)) {
"dimnames(self) must be NULL or a length 2 list of either NULL or a character vector of length equal to its corresponding dimension"
}
}

validate_array <- function(self) {
if (is.array(self)) {
# is.array() methods should only return TRUE if valid
return(invisible(NULL))
}
if (
!is.integer(dim(self)) || length(dim(self)) == 0L || !all(dim(self) >= 0L)
) {
return("dim(self) must be a non-empty non-negative integer vector")
}
if (!valid_dimnames(self)) {
return(
"dimnames(self) must be NULL or a list of either NULL or a character vector of length equal to its corresponding dimension"
)
}
"is.array(self) is FALSE"
}

validate_formula <- function(self) {
if (is.null(environment(self))) {
return("environment(self) must be non-NULL")
Expand Down Expand Up @@ -295,10 +245,9 @@ validate_formula <- function(self) {
#' * `class_Date` for dates.
#' * `class_factor` for factors.
#' * `class_POSIXct`, `class_POSIXlt` and `class_POSIXt` for date-times.
# * `class_matrix` for matrices.
# * `class_array` for arrays.
#' * `class_formula` for formulas.

#'
#' Matrices and arrays are documented separately in [base_arrays].
#'
#' @export
#' @name base_s3_classes
Expand Down Expand Up @@ -373,47 +322,6 @@ class_data.frame <- new_S3_class(
validator = validate_data.frame
)

# @export
# @rdname base_s3_classes
# @format NULL
# @order 3
class_matrix <- new_S3_class(
"matrix",
constructor = function(
.data = logical(),
nrow = NULL,
ncol = NULL,
byrow = FALSE,
dimnames = NULL
) {
nrow <- nrow %||% NROW(.data)
if (is.null(ncol)) {
ncol <- NCOL(.data)
if (length(.data) != (nrow * ncol)) {
ncol <- length(.data) %/% nrow
}
}
matrix(.data, nrow, ncol, byrow, dimnames)
},
validator = validate_matrix
)

# @export
# @rdname base_s3_classes
# @format NULL
# @order 3
class_array <- new_S3_class(
"array",
constructor = function(
.data = logical(),
dim = base::dim(.data) %||% length(.data),
dimnames = base::dimnames(.data)
) {
array(.data, dim, dimnames)
},
validator = validate_array
)

#' @export
#' @rdname base_s3_classes
#' @format NULL
Expand Down
Loading
Loading