1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194
|
#' Does code return an object inheriting from the expected base type, S3 class,
#' or S4 class?
#'
#' @description
#' See <https://adv-r.hadley.nz/oo.html> for an overview of R's OO systems, and
#' the vocabulary used here.
#'
#' * `expect_type(x, type)` checks that `typeof(x)` is `type`.
#' * `expect_s3_class(x, class)` checks that `x` is an S3 object that
#' [inherits()] from `class`
#' * `expect_s3_class(x, NA)` checks that `x` isn't an S3 object.
#' * `expect_s4_class(x, class)` checks that `x` is an S4 object that
#' [is()] `class`.
#' * `expect_s4_class(x, NA)` checks that `x` isn't an S4 object.
#' * `expect_s7_class(x, Class)` checks that `x` is an S7 object that
#' [S7::S7_inherits()] from `Class`
#'
#' See [expect_vector()] for testing properties of objects created by vctrs.
#'
#' @param type String giving base type (as returned by [typeof()]).
#' @param class Either a character vector of class names, or
#' for `expect_s3_class()` and `expect_s4_class()`, an `NA` to assert
#' that `object` isn't an S3 or S4 object.
#' @inheritParams expect_that
#' @family expectations
#' @examples
#' x <- data.frame(x = 1:10, y = "x", stringsAsFactors = TRUE)
#' # A data frame is an S3 object with class data.frame
#' expect_s3_class(x, "data.frame")
#' show_failure(expect_s4_class(x, "data.frame"))
#' # A data frame is built from a list:
#' expect_type(x, "list")
#'
#' # An integer vector is an atomic vector of type "integer"
#' expect_type(x$x, "integer")
#' # It is not an S3 object
#' show_failure(expect_s3_class(x$x, "integer"))
#'
#' # Above, we requested data.frame() converts strings to factors:
#' show_failure(expect_type(x$y, "character"))
#' expect_s3_class(x$y, "factor")
#' expect_type(x$y, "integer")
#' @name inheritance-expectations
NULL
#' @export
#' @rdname inheritance-expectations
expect_type <- function(object, type) {
stopifnot(is.character(type), length(type) == 1)
act <- quasi_label(enquo(object), arg = "object")
act_type <- typeof(act$val)
expect(
identical(act_type, type),
sprintf("%s has type %s, not %s.", act$lab, format_class(act_type), format_class(type))
)
invisible(act$val)
}
#' @export
#' @rdname inheritance-expectations
#' @param exact If `FALSE`, the default, checks that `object` inherits
#' from `class`. If `TRUE`, checks that object has a class that's identical
#' to `class`.
expect_s3_class <- function(object, class, exact = FALSE) {
act <- quasi_label(enquo(object), arg = "object")
act$class <- format_class(class(act$val))
exp_lab <- format_class(class)
if (identical(class, NA)) {
expect(
isS3(object) == !is.na(class),
sprintf("%s is an S3 object", act$lab)
)
} else if (is.character(class)) {
if (!isS3(act$val)) {
fail(sprintf("%s is not an S3 object", act$lab))
} else if (exact) {
expect(
identical(class(act$val), class),
sprintf("%s has class %s, not %s.", act$lab, act$class, exp_lab)
)
} else {
expect(
inherits(act$val, class),
sprintf("%s inherits from %s not %s.", act$lab, act$class, exp_lab)
)
}
} else {
abort("`class` must be a NA or a character vector")
}
invisible(act$val)
}
#' @export
#' @rdname inheritance-expectations
expect_s7_class <- function(object, class) {
check_installed("S7")
if (!inherits(class, "S7_class")) {
stop_input_type(class, "an S7 class object")
}
act <- quasi_label(enquo(object), arg = "object")
if (!S7::S7_inherits(object)) {
fail(sprintf("%s is not an S7 object", act$lab))
} else {
expect(
S7::S7_inherits(object, class),
sprintf(
"%s inherits from %s not <%s>.",
act$lab,
paste0("<", setdiff(base::class(object), "S7_object"), ">", collapse = "/"),
attr(class, "name", TRUE)
)
)
}
invisible(act$val)
}
#' @export
#' @rdname inheritance-expectations
expect_s4_class <- function(object, class) {
act <- quasi_label(enquo(object), arg = "object")
act_val_lab <- format_class(methods::is(object))
exp_lab <- format_class(class)
if (identical(class, NA)) {
expect(
isS4(object) == !is.na(class),
sprintf("%s is an S4 object", act$lab)
)
} else if (is.character(class)) {
if (!isS4(act$val)) {
fail(sprintf("%s is not an S4 object", act$lab))
} else {
expect(
methods::is(act$val, class),
sprintf("%s inherits from %s not %s.", act$lab, act_val_lab, exp_lab)
)
}
} else {
abort("`class` must be a NA or a character vector")
}
invisible(act$val)
}
isS3 <- function(x) is.object(x) && !isS4(x)
#' Does an object inherit from a given class?
#'
#' @description
#' `r lifecycle::badge("superseded")`
#'
#' `expect_is()` is an older form that uses [inherits()] without checking
#' whether `x` is S3, S4, or neither. Instead, I'd recommend using
#' [expect_type()], [expect_s3_class()] or [expect_s4_class()] to more clearly
#' convey your intent.
#'
#' @section 3rd edition:
#' `r lifecycle::badge("deprecated")`
#'
#' `expect_is()` is formally deprecated in the 3rd edition.
#'
#' @keywords internal
#' @inheritParams expect_type
#' @export
expect_is <- function(object, class, info = NULL, label = NULL) {
stopifnot(is.character(class))
edition_deprecate(3, "expect_is()",
"Use `expect_type()`, `expect_s3_class()`, or `expect_s4_class()` instead"
)
act <- quasi_label(enquo(object), label, arg = "object")
act$class <- format_class(class(act$val))
exp_lab <- format_class(class(class))
expect(
inherits(act$val, class),
sprintf("%s inherits from `%s` not `%s`.", act$lab, act$class, exp_lab),
info = info
)
invisible(act$val)
}
format_class <- function(x) {
paste0(encodeString(x, quote = "'"), collapse = "/")
}
|