diff --git a/NAMESPACE b/NAMESPACE index ff9d8476..3371174b 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -84,6 +84,7 @@ S3method(.cstr_construct,S7_S3_class.new_S3_class) S3method(.cstr_construct,S7_any.class_any) S3method(.cstr_construct,S7_base_class.S7) S3method(.cstr_construct,S7_class.new_class) +S3method(.cstr_construct,S7_class_ref.environment) S3method(.cstr_construct,S7_external_generic.new_external_generic) S3method(.cstr_construct,S7_generic.new_generic) S3method(.cstr_construct,S7_object.S7_object) @@ -525,6 +526,7 @@ S3method(constructive::.cstr_construct,S7_S3_class) S3method(constructive::.cstr_construct,S7_any) S3method(constructive::.cstr_construct,S7_base_class) S3method(constructive::.cstr_construct,S7_class) +S3method(constructive::.cstr_construct,S7_class_ref) S3method(constructive::.cstr_construct,S7_external_generic) S3method(constructive::.cstr_construct,S7_generic) S3method(constructive::.cstr_construct,S7_object) @@ -611,6 +613,7 @@ export(opts_S7_S3_class) export(opts_S7_any) export(opts_S7_base_class) export(opts_S7_class) +export(opts_S7_class_ref) export(opts_S7_external_generic) export(opts_S7_generic) export(opts_S7_object) diff --git a/NEWS.md b/NEWS.md index b30aa8dd..9bf6b329 100644 --- a/NEWS.md +++ b/NEWS.md @@ -36,6 +36,7 @@ New supported classes: * `Matrix`: `"sparseMatrix"`, `"denseMatrix"`, `"diagonalMatrix"`, `"indMatrix"` and `"sparseVector"` * `readr`: `"col_spec"`, `"collector"`, `"locale"` and `"date_names"` +* `S7`: `"S7_class_ref"`, used by S7 > 0.2.2 to store the class of an object * `sf`: `"sf"`, `"sfc"`, `"sfg"`, `"crs"` and `"bbox"` * `survival`: `"Surv"` * `tsibble`: `"tbl_ts"`, `"grouped_ts"`, `"yearmonth"`, `"yearquarter"` and @@ -45,6 +46,9 @@ New supported classes: Fixes and improvements: +* We support S7 > 0.2.2, which stores the class of an object differently. + S7 objects built with an S7 version that stores it differently than the + installed one are considered corrupted. * We don't emit the deprecated `.additional_properties` argument of `ellmer::type_object()` anymore. `ellmer` 0.5.0 made it `deprecated()`. * Coercion generics and `NA` predicates (`as.character()`, `is.na()`, `[[` and diff --git a/R/construct-helpers.R b/R/construct-helpers.R index 5966dd9a..4df40138 100644 --- a/R/construct-helpers.R +++ b/R/construct-helpers.R @@ -438,6 +438,7 @@ all_classes <- list( "S7_any", "S7_base_class", "S7_class", + "S7_class_ref", "S7_external_generic", "S7_generic", "S7_object", diff --git a/R/s3-ggplot2-ggplot2-element_blank.R b/R/s3-ggplot2-ggplot2-element_blank.R index 1bf7ef54..66e1f054 100644 --- a/R/s3-ggplot2-ggplot2-element_blank.R +++ b/R/s3-ggplot2-ggplot2-element_blank.R @@ -13,7 +13,7 @@ opts_ggplot2_element_blank <- function(constructor = c("element_blank", "next"), } `is_corrupted_ggplot2::element_blank` <- function(x) { - typeof(x) != "object" + typeof(x) != "object" || has_foreign_s7_attrs(x) } #' @export @@ -24,7 +24,7 @@ opts_ggplot2_element_blank <- function(constructor = c("element_blank", "next"), } `repair_attributes_ggplot2::element_blank` <- function(x, ...) { - property_names <- names(attr(attr(x, "S7_class"),"properties")) + property_names <- names(attr(s7_class(x),"properties")) .cstr_repair_attributes( x, idiomatic_class = c( @@ -34,7 +34,7 @@ opts_ggplot2_element_blank <- function(constructor = c("element_blank", "next"), "S7_object", "element" ), - ignore = "S7_class", + ignore = s7_internal_attrs(), ... ) } diff --git a/R/s3-ggplot2-ggplot2-element_geom.R b/R/s3-ggplot2-ggplot2-element_geom.R index d067e011..a0659d5c 100644 --- a/R/s3-ggplot2-ggplot2-element_geom.R +++ b/R/s3-ggplot2-ggplot2-element_geom.R @@ -13,7 +13,7 @@ opts_ggplot2_element_geom <- function(constructor = c("element_geom", "next"), . } `is_corrupted_ggplot2::element_geom` <- function(x) { - typeof(x) != "object" + typeof(x) != "object" || has_foreign_s7_attrs(x) } #' @export @@ -57,7 +57,7 @@ opts_ggplot2_element_geom <- function(constructor = c("element_geom", "next"), . } `repair_attributes_ggplot2::element_geom` <- function(x, ...) { - property_names <- names(attr(attr(x, "S7_class"),"properties")) + property_names <- names(attr(s7_class(x),"properties")) .cstr_repair_attributes( x, idiomatic_class = c( @@ -65,7 +65,7 @@ opts_ggplot2_element_geom <- function(constructor = c("element_geom", "next"), . "ggplot2::element", "S7_object" ), - ignore = c("S7_class", property_names), + ignore = c(s7_internal_attrs(), property_names), ... ) } diff --git a/R/s3-ggplot2-ggplot2-element_line.R b/R/s3-ggplot2-ggplot2-element_line.R index 6330368b..b1da5e08 100644 --- a/R/s3-ggplot2-ggplot2-element_line.R +++ b/R/s3-ggplot2-ggplot2-element_line.R @@ -13,7 +13,7 @@ opts_ggplot2_element_line <- function(constructor = c("element_line", "next"), . } `is_corrupted_ggplot2::element_line` <- function(x) { - typeof(x) != "object" + typeof(x) != "object" || has_foreign_s7_attrs(x) } #' @export @@ -38,7 +38,7 @@ opts_ggplot2_element_line <- function(constructor = c("element_line", "next"), . } `repair_attributes_ggplot2::element_line` <- function(x, ...) { - property_names <- names(attr(attr(x, "S7_class"),"properties")) + property_names <- names(attr(s7_class(x),"properties")) .cstr_repair_attributes( x, idiomatic_class = c( @@ -48,7 +48,7 @@ opts_ggplot2_element_line <- function(constructor = c("element_line", "next"), . "S7_object", "element" ), - ignore = c("S7_class", property_names), + ignore = c(s7_internal_attrs(), property_names), ... ) } diff --git a/R/s3-ggplot2-ggplot2-element_point.R b/R/s3-ggplot2-ggplot2-element_point.R index ca53bc42..e9c09c89 100644 --- a/R/s3-ggplot2-ggplot2-element_point.R +++ b/R/s3-ggplot2-ggplot2-element_point.R @@ -13,7 +13,7 @@ opts_ggplot2_element_point <- function(constructor = c("element_point", "next"), } `is_corrupted_ggplot2::element_point` <- function(x) { - typeof(x) != "object" + typeof(x) != "object" || has_foreign_s7_attrs(x) } #' @export @@ -33,7 +33,7 @@ opts_ggplot2_element_point <- function(constructor = c("element_point", "next"), } `repair_attributes_ggplot2::element_point` <- function(x, ...) { - property_names <- names(attr(attr(x, "S7_class"),"properties")) + property_names <- names(attr(s7_class(x),"properties")) .cstr_repair_attributes( x, idiomatic_class = c( @@ -41,7 +41,7 @@ opts_ggplot2_element_point <- function(constructor = c("element_point", "next"), "ggplot2::element", "S7_object" ), - ignore = c("S7_class", property_names), + ignore = c(s7_internal_attrs(), property_names), ... ) } diff --git a/R/s3-ggplot2-ggplot2-element_polygon.R b/R/s3-ggplot2-ggplot2-element_polygon.R index d0b8032b..19ec3233 100644 --- a/R/s3-ggplot2-ggplot2-element_polygon.R +++ b/R/s3-ggplot2-ggplot2-element_polygon.R @@ -13,7 +13,7 @@ opts_ggplot2_element_polygon <- function(constructor = c("element_polygon", "nex } `is_corrupted_ggplot2::element_polygon` <- function(x) { - typeof(x) != "object" + typeof(x) != "object" || has_foreign_s7_attrs(x) } #' @export @@ -33,7 +33,7 @@ opts_ggplot2_element_polygon <- function(constructor = c("element_polygon", "nex } `repair_attributes_ggplot2::element_polygon` <- function(x, ...) { - property_names <- names(attr(attr(x, "S7_class"),"properties")) + property_names <- names(attr(s7_class(x),"properties")) .cstr_repair_attributes( x, idiomatic_class = c( @@ -41,7 +41,7 @@ opts_ggplot2_element_polygon <- function(constructor = c("element_polygon", "nex "ggplot2::element", "S7_object" ), - ignore = c("S7_class", property_names), + ignore = c(s7_internal_attrs(), property_names), ... ) } diff --git a/R/s3-ggplot2-ggplot2-element_rect.R b/R/s3-ggplot2-ggplot2-element_rect.R index a16e7d12..4ed530c8 100644 --- a/R/s3-ggplot2-ggplot2-element_rect.R +++ b/R/s3-ggplot2-ggplot2-element_rect.R @@ -13,7 +13,7 @@ opts_ggplot2_element_rect <- function(constructor = c("element_rect", "next"), . } `is_corrupted_ggplot2::element_rect` <- function(x) { - typeof(x) != "object" + typeof(x) != "object" || has_foreign_s7_attrs(x) } #' @export @@ -33,7 +33,7 @@ opts_ggplot2_element_rect <- function(constructor = c("element_rect", "next"), . } `repair_attributes_ggplot2::element_rect` <- function(x, ...) { - property_names <- names(attr(attr(x, "S7_class"),"properties")) + property_names <- names(attr(s7_class(x),"properties")) .cstr_repair_attributes( x, idiomatic_class = c( @@ -43,7 +43,7 @@ opts_ggplot2_element_rect <- function(constructor = c("element_rect", "next"), . "S7_object", "element" ), - ignore = c("S7_class", property_names), + ignore = c(s7_internal_attrs(), property_names), ... ) } diff --git a/R/s3-ggplot2-ggplot2-element_text.R b/R/s3-ggplot2-ggplot2-element_text.R index f5dff7cb..405eabc3 100644 --- a/R/s3-ggplot2-ggplot2-element_text.R +++ b/R/s3-ggplot2-ggplot2-element_text.R @@ -13,7 +13,7 @@ opts_ggplot2_element_text <- function(constructor = c("element_text", "next"), . } `is_corrupted_ggplot2::element_text` <- function(x) { - typeof(x) != "object" + typeof(x) != "object" || has_foreign_s7_attrs(x) } #' @export @@ -38,7 +38,7 @@ opts_ggplot2_element_text <- function(constructor = c("element_text", "next"), . } `repair_attributes_ggplot2::element_text` <- function(x, ...) { - property_names <- names(attr(attr(x, "S7_class"),"properties")) + property_names <- names(attr(s7_class(x),"properties")) .cstr_repair_attributes( x, idiomatic_class = c( @@ -48,7 +48,7 @@ opts_ggplot2_element_text <- function(constructor = c("element_text", "next"), . "S7_object", "element" ), - ignore = c("S7_class", property_names), + ignore = c(s7_internal_attrs(), property_names), ... ) } diff --git a/R/s3-ggplot2-ggplot2-ggplot.R b/R/s3-ggplot2-ggplot2-ggplot.R index f8d50e3b..dd1b29c2 100644 --- a/R/s3-ggplot2-ggplot2-ggplot.R +++ b/R/s3-ggplot2-ggplot2-ggplot.R @@ -25,7 +25,7 @@ opts_ggplot2_ggplot <- function(constructor = c("ggplot", "next", "list"), ...) } `is_corrupted_ggplot2::ggplot` <- function(x) { - typeof(x) != "object" + typeof(x) != "object" || has_foreign_s7_attrs(x) } #' @export @@ -79,7 +79,7 @@ opts_ggplot2_ggplot <- function(constructor = c("ggplot", "next", "list"), ...) x, code_with_parens, pipe = pipe, idiomatic_class = c("ggplot2::ggplot", "ggplot", "ggplot2::gg", "S7_object", "gg"), ignore = c( - "S7_class", "data", "layers", "scales", "guides", "mapping", "theme", + s7_internal_attrs(), "data", "layers", "scales", "guides", "mapping", "theme", "coordinates", "facet", "layout", "labels", "meta", "plot_env" ), one_liner = one_liner, diff --git a/R/s3-ggplot2-ggplot2-labels.R b/R/s3-ggplot2-ggplot2-labels.R index d69f2c6f..cc86dc35 100644 --- a/R/s3-ggplot2-ggplot2-labels.R +++ b/R/s3-ggplot2-ggplot2-labels.R @@ -23,7 +23,7 @@ opts_ggplot2_labels <- function(constructor = c("labs", "next"), ...) { `is_corrupted_ggplot2::labels` <- function(x) { if (!is.list(x)) return(TRUE) - if (!is.function(attr(x, "S7_class"))) return(TRUE) + if (!is.function(s7_class(x))) return(TRUE) FALSE } @@ -34,7 +34,7 @@ opts_ggplot2_labels <- function(constructor = c("labs", "next"), ...) { code <- constructive::.cstr_apply(args, fun = "ggplot2::labs", ...) constructive::.cstr_repair_attributes( x, code, ..., - ignore = "S7_class", + ignore = s7_internal_attrs(), idiomatic_class = c("ggplot2::labels", "gg", "S7_object") ) } diff --git a/R/s3-ggplot2-ggplot2-mapping.R b/R/s3-ggplot2-ggplot2-mapping.R index 5b96df93..e7b0ff54 100644 --- a/R/s3-ggplot2-ggplot2-mapping.R +++ b/R/s3-ggplot2-ggplot2-mapping.R @@ -57,7 +57,7 @@ opts_ggplot2_mapping <- function(constructor = c("aes", "next", "list"), ...) { `is_corrupted_ggplot2::mapping` <- function(x) { if (!is.list(x)) return(TRUE) - if (!is.function(attr(x, "S7_class"))) return(TRUE) + if (!is.function(s7_class(x))) return(TRUE) FALSE } @@ -65,7 +65,7 @@ opts_ggplot2_mapping <- function(constructor = c("aes", "next", "list"), ...) { .cstr_repair_attributes( x, code, pipe = pipe, idiomatic_class = c("ggplot2::mapping", "uneval", "gg", "S7_object"), - ignore = "S7_class", + ignore = s7_internal_attrs(), ... ) } diff --git a/R/s3-ggplot2-ggplot2-margin.R b/R/s3-ggplot2-ggplot2-margin.R index 068a2c5e..e75bd4c2 100644 --- a/R/s3-ggplot2-ggplot2-margin.R +++ b/R/s3-ggplot2-ggplot2-margin.R @@ -15,7 +15,7 @@ opts_ggplot2_margin <- function(constructor = c("margin", "next", "double"), ... `is_corrupted_ggplot2::margin` <- function(x) { if (!is.double(x)) return(TRUE) if (!is.integer(attr(x, "unit"))) return(TRUE) - if (!is.function(attr(x, "S7_class"))) return(TRUE) + if (!is.function(s7_class(x))) return(TRUE) FALSE } @@ -45,7 +45,7 @@ opts_ggplot2_margin <- function(constructor = c("margin", "next", "double"), ... x, code, idiomatic_class = c("ggplot2::margin", "simpleUnit", "unit", "unit_v2", "S7_object"), - ignore = c("S7_class", "unit"), + ignore = c(s7_internal_attrs(), "unit"), ... ) } diff --git a/R/s3-ggplot2-ggplot2-theme.R b/R/s3-ggplot2-ggplot2-theme.R index 6a85efb8..253ef3dd 100644 --- a/R/s3-ggplot2-ggplot2-theme.R +++ b/R/s3-ggplot2-ggplot2-theme.R @@ -14,7 +14,7 @@ opts_ggplot2_theme <- function(constructor = c("default", "theme", "next", "list `is_corrupted_ggplot2::theme` <- function(x) { if (!is.list(x)) return(TRUE) - if (!is.function(attr(x, "S7_class"))) return(TRUE) + if (!is.function(s7_class(x))) return(TRUE) if (!is.logical(attr(x, "complete")) || length(attr(x, "complete")) != 1) return(TRUE) if (!is.logical(attr(x, "validate")) || length(attr(x, "validate")) != 1) return(TRUE) FALSE @@ -131,7 +131,7 @@ guess_complete_theme <- function(x, ...) { } `repair_attributes_ggplot2::theme` <- function(x, code, ...) { - ignore <- c("S7_class", "complete", "validate") + ignore <- c(s7_internal_attrs(), "complete", "validate") if (identical(names(x), character())) ignore <- c(ignore, "names") .cstr_repair_attributes(x, code, idiomatic_class = c("theme", "ggplot2::theme", "gg", "S7_object"), ignore = ignore, ...) } diff --git a/R/s3-ggplot2-theme.R b/R/s3-ggplot2-theme.R index 34900faf..6d7cd8e1 100644 --- a/R/s3-ggplot2-theme.R +++ b/R/s3-ggplot2-theme.R @@ -15,7 +15,7 @@ opts_theme <- function(constructor = c("default", "theme", "next", "list"), ...) is_corrupted_theme <- function(x) { # because it's the main class in new ggplot we do this to keep the logic separate # for new and old ggplot - !is.list(x) || !is.null(attr(x, "S7_class")) + !is.list(x) || !is.null(s7_class(x)) } #' @export diff --git a/R/s7-S7-object.R b/R/s7-S7-object.R index 7b119bf9..0feec997 100644 --- a/R/s7-S7-object.R +++ b/R/s7-S7-object.R @@ -38,38 +38,66 @@ is_corrupted_S7_object <- function(x) { # directly anyway if (with_versions(R < "4.4")) { # see ?opts_S7_object - typeof(x) != "S4" + if (typeof(x) != "S4") return(TRUE) } else { - typeof(x) != "object" + if (typeof(x) != "object") return(TRUE) } - + has_foreign_s7_attrs(x) } #' @export .cstr_construct.S7_object.new_class<- function(x, ...) { - constructor <- attr(attr(x, "S7_class"), "constructor") + constructor <- attr(s7_class(x), "constructor") we_can_use_compact_syntax <- !identical(x, S7::S7_object()) && is_default_s7_class_constructor(constructor) if (!we_can_use_compact_syntax) return(.cstr_construct.S7_object.S7_object(x, ...)) - fun_code <- .cstr_construct(attr(x, "S7_class"), ...) + fun_code <- .cstr_construct(s7_class(x), ...) args <- attributes(x) args$class <- NULL - args$S7_class <- NULL + args[s7_internal_attrs()] <- NULL code <- constructive::.cstr_apply(args, fun = fun_code, ...) constructive::.cstr_repair_attributes( x, code, ..., ignore = setdiff(names(attributes(x)), "class"), - idiomatic_class = c(attr(attr(x, "S7_class"), "name"), "S7_object") + idiomatic_class = c(attr(s7_class(x), "name"), "S7_object") ) } #' @export .cstr_construct.S7_object.S7_object <- function(x, ...) { code <- "S7::S7_object()" + # since S7 0.2.2.9000 `S7_object()` sets `_S7_version = 1L` + version_set <- + with_versions(S7 > "0.2.2") && identical(attr(x, "_S7_version"), 1L) constructive::.cstr_repair_attributes( x, code, ..., + ignore = if (version_set) "_S7_version", idiomatic_class = "S7_object" ) } +# Attributes that the installed S7 sets itself. The class of an object is stored +# in the "S7_class" attribute up to S7 0.2.2, and since S7 0.2.2.9000 in +# "_S7_class" along with "_S7_version", generally through a shared reference to +# the class +s7_internal_attrs <- function() { + if (with_versions(S7 > "0.2.2")) c("_S7_class", "_S7_version") else "S7_class" +} + +s7_class <- function(x) { + if (with_versions(S7 <= "0.2.2")) return(attr(x, "S7_class")) + class <- attr(x, "_S7_class") + if (is.environment(class)) class <- get("class", envir = class) + class +} + +# S7 objects built with an S7 version that stores the class differently than the +# installed one can't be built idiomatically, we consider them corrupted +has_foreign_s7_attrs <- function(x) { + if (with_versions(S7 <= "0.2.2")) { + !is.null(attr(x, "_S7_class")) || !is.null(attr(x, "_S7_version")) + } else { + !is.null(attr(x, "S7_class")) || is.null(attr(x, "_S7_version")) + } +} diff --git a/R/s7-S7_class.R b/R/s7-S7_class.R index d89cbb00..cf57a548 100644 --- a/R/s7-S7_class.R +++ b/R/s7-S7_class.R @@ -22,7 +22,15 @@ opts_S7_class <- function(constructor = c("new_class", "next"), ...) { } is_corrupted_S7_class <- function(x) { - FALSE + # Since S7 0.2.2.9000 `new_class()` wraps the constructor's environment into + # a new one holding a reference to the class, and sets "S7_version", classes + # built with another S7 version than the installed one are corrupted + wrapped_env <- exists(".S7_class_ref", environment(x), inherits = FALSE) + if (with_versions(S7 <= "0.2.2")) { + wrapped_env || !is.null(attr(x, "S7_version")) + } else { + !wrapped_env || is.null(attr(x, "S7_version")) + } } #' @export @@ -39,17 +47,30 @@ is_corrupted_S7_class <- function(x) { args$constructor <- NULL } args$properties <- simplify_s7_class_properties(args$properties) + # Since S7 0.2.2.9000 `new_class()` wraps the constructor's environment into + # a new one holding a reference to the class, so we provide the original + # environment and let `new_class()` create the wrapper + wrapped_env <- with_versions(S7 > "0.2.2") + if (wrapped_env && !is.null(args$constructor)) { + environment(args$constructor) <- parent.env(environment(x)) + } code <- constructive::.cstr_apply(args, fun = "S7::new_class", ...) # repair environment - code <- .cstr_wrap(code, fun = "") - envir_code <- .cstr_apply(list(environment(x)), "(`environment<-`)", ...) - code <- .cstr_pipe(code, envir_code, ...) + if (!wrapped_env) { + code <- .cstr_wrap(code, fun = "") + envir_code <- .cstr_apply(list(environment(x)), "(`environment<-`)", ...) + code <- .cstr_pipe(code, envir_code, ...) + } constructive::.cstr_repair_attributes( x, code, ..., - ignore = attr_names, + # `new_class()` also computes these since S7 0.2.2.9000 + ignore = c( + attr_names, + if (with_versions(S7 > "0.2.2")) c("S7_class_name", "S7_dispatch", "S7_version") + ), idiomatic_class = c("S7_class", "S7_object") ) } diff --git a/R/s7-S7_class_ref.R b/R/s7-S7_class_ref.R new file mode 100644 index 00000000..55647821 --- /dev/null +++ b/R/s7-S7_class_ref.R @@ -0,0 +1,47 @@ +#' Constructive options for class 'S7_class_ref' +#' +#' These options will be used on objects of class 'S7_class_ref'. Since S7 +#' 0.2.2.9000 an S7 object stores its class in the "_S7_class" attribute through +#' such a reference, which `S7::new_class()` creates and stores in the +#' environment of the class. +#' +#' Depending on `constructor`, we construct the object as follows: +#' * `"environment"` (default): We build the class and fetch the reference +#' from its environment. +#' * `"next"` : Use the constructor for the next supported class. +#' +#' @param constructor String. Name of the function used to construct the object. +#' @param ... Additional options used by user defined constructors through the `opts` object +#' @return An object of class +#' @export +opts_S7_class_ref <- function(constructor = c("environment", "next"), ...) { + constructive::.cstr_options("S7_class_ref", constructor = constructor[[1]], ...) +} + +#' @exportS3Method constructive::.cstr_construct +.cstr_construct.S7_class_ref <- function(x, ...) { + opts <- list(...)$opts$S7_class_ref %||% opts_S7_class_ref() + if (is_corrupted_S7_class_ref(x) || opts$constructor == "next") return(NextMethod()) + UseMethod(".cstr_construct.S7_class_ref", structure(NA, class = opts$constructor)) +} + +is_corrupted_S7_class_ref <- function(x) { + # S7 <= 0.2.2 doesn't use class references + if (with_versions(S7 <= "0.2.2")) return(TRUE) + if (!is.environment(x)) return(TRUE) + class <- get0("class", envir = x, inherits = FALSE) + if (!inherits(class, "S7_class")) return(TRUE) + ref <- get0(".S7_class_ref", envir = environment(class), inherits = FALSE) + !identical(ref, x) +} + +#' @export +.cstr_construct.S7_class_ref.environment <- function(x, ...) { + # opts <- list(...)$opts$S7_class_ref %||% opts_S7_class_ref() + code <- .cstr_apply(list(get("class", envir = x)), "environment", ...) + code[[length(code)]] <- paste0(code[[length(code)]], "$.S7_class_ref") + constructive::.cstr_repair_attributes( + x, code, ..., + idiomatic_class = "S7_class_ref" + ) +} diff --git a/R/s7-S7_generic.R b/R/s7-S7_generic.R index 3cc9aa00..83ad102d 100644 --- a/R/s7-S7_generic.R +++ b/R/s7-S7_generic.R @@ -22,7 +22,7 @@ opts_S7_generic <- function(constructor = c("new_generic", "next"), ...) { } is_corrupted_S7_generic <- function(x) { - FALSE + has_foreign_s7_attrs(x) } #' @export @@ -54,7 +54,7 @@ is_corrupted_S7_generic <- function(x) { constructive::.cstr_repair_attributes( x, code, ..., ignore = c( - "name", "dispatch_args", "S7_class", "parent", "properties", + s7_internal_attrs(), "name", "dispatch_args", "parent", "properties", "constructor", "methods", "srcref" ), idiomatic_class = c("S7_generic", "function", "S7_object") diff --git a/R/s7-ellmer_TypeArray.R b/R/s7-ellmer_TypeArray.R index cb8a2d59..80667f1b 100644 --- a/R/s7-ellmer_TypeArray.R +++ b/R/s7-ellmer_TypeArray.R @@ -22,7 +22,7 @@ opts_ellmer_TypeArray <- function(constructor = c("type_array", "TypeArray", "ne } `is_corrupted_ellmer::TypeArray` <- function(x) { - FALSE + has_foreign_s7_attrs(x) } #' @export @@ -37,7 +37,7 @@ opts_ellmer_TypeArray <- function(constructor = c("type_array", "TypeArray", "ne constructive::.cstr_repair_attributes( x, code, ..., idiomatic_class = c("ellmer::TypeArray", "ellmer::Type", "S7_object"), - ignore = c("S7_class", "description", "required", "items") + ignore = c(s7_internal_attrs(), "description", "required", "items") ) } @@ -54,6 +54,6 @@ opts_ellmer_TypeArray <- function(constructor = c("type_array", "TypeArray", "ne constructive::.cstr_repair_attributes( x, code, ..., idiomatic_class = c("ellmer::TypeArray", "ellmer::Type", "S7_object"), - ignore = c("S7_class", "description", "required", "items") + ignore = c(s7_internal_attrs(), "description", "required", "items") ) } diff --git a/R/s7-elmer_TypeBasic.R b/R/s7-elmer_TypeBasic.R index dadc4737..3351780e 100644 --- a/R/s7-elmer_TypeBasic.R +++ b/R/s7-elmer_TypeBasic.R @@ -22,7 +22,7 @@ opts_ellmer_TypeBasic <- function(constructor = c("default", "TypeBasic", "next" } `is_corrupted_ellmer::TypeBasic` <- function(x) { - FALSE + has_foreign_s7_attrs(x) } #' @export @@ -39,7 +39,7 @@ opts_ellmer_TypeBasic <- function(constructor = c("default", "TypeBasic", "next" constructive::.cstr_repair_attributes( x, code, ..., idiomatic_class = c("ellmer::TypeBasic", "ellmer::Type", "S7_object"), - ignore = c("S7_class", "description", "required", "type") + ignore = c(s7_internal_attrs(), "description", "required", "type") ) } @@ -56,6 +56,6 @@ opts_ellmer_TypeBasic <- function(constructor = c("default", "TypeBasic", "next" constructive::.cstr_repair_attributes( x, code, ..., idiomatic_class = c("ellmer::TypeBasic", "ellmer::Type", "S7_object"), - ignore = c("S7_class", "description", "required", "type") + ignore = c(s7_internal_attrs(), "description", "required", "type") ) } diff --git a/R/s7-elmer_TypeEnum.R b/R/s7-elmer_TypeEnum.R index b8dae2dc..175ac29d 100644 --- a/R/s7-elmer_TypeEnum.R +++ b/R/s7-elmer_TypeEnum.R @@ -22,7 +22,7 @@ opts_ellmer_TypeEnum <- function(constructor = c("type_enum", "TypeEnum", "next" } `is_corrupted_ellmer::TypeEnum` <- function(x) { - FALSE + has_foreign_s7_attrs(x) } #' @export @@ -38,7 +38,7 @@ opts_ellmer_TypeEnum <- function(constructor = c("type_enum", "TypeEnum", "next" constructive::.cstr_repair_attributes( x, code, ..., idiomatic_class = c("ellmer::TypeEnum", "ellmer::Type", "S7_object"), - ignore = c("S7_class", "description", "required", "values") + ignore = c(s7_internal_attrs(), "description", "required", "values") ) } @@ -55,6 +55,6 @@ opts_ellmer_TypeEnum <- function(constructor = c("type_enum", "TypeEnum", "next" constructive::.cstr_repair_attributes( x, code, ..., idiomatic_class = c("ellmer::TypeEnum", "ellmer::Type", "S7_object"), - ignore = c("S7_class", "description", "required", "values") + ignore = c(s7_internal_attrs(), "description", "required", "values") ) } diff --git a/R/s7-elmer_TypeJsonSchema.R b/R/s7-elmer_TypeJsonSchema.R index 67733f0e..25d7220c 100644 --- a/R/s7-elmer_TypeJsonSchema.R +++ b/R/s7-elmer_TypeJsonSchema.R @@ -22,7 +22,7 @@ opts_ellmer_TypeJsonSchema <- function(constructor = c("type_from_schema", "Type } `is_corrupted_ellmer::TypeJsonSchema` <- function(x) { - FALSE + has_foreign_s7_attrs(x) } #' @export @@ -33,7 +33,7 @@ opts_ellmer_TypeJsonSchema <- function(constructor = c("type_from_schema", "Type constructive::.cstr_repair_attributes( x, code, ..., idiomatic_class = c("ellmer::TypeJsonSchema", "ellmer::Type", "S7_object"), - ignore = c("S7_class", "json", "required") + ignore = c(s7_internal_attrs(), "json", "required") ) } @@ -50,6 +50,6 @@ opts_ellmer_TypeJsonSchema <- function(constructor = c("type_from_schema", "Type constructive::.cstr_repair_attributes( x, code, ..., idiomatic_class = c("ellmer::TypeJsonSchema", "ellmer::Type", "S7_object"), - ignore = c("S7_class", "description", "required", "json") + ignore = c(s7_internal_attrs(), "description", "required", "json") ) } diff --git a/R/s7-elmer_TypeObject.R b/R/s7-elmer_TypeObject.R index 4a933051..5da90792 100644 --- a/R/s7-elmer_TypeObject.R +++ b/R/s7-elmer_TypeObject.R @@ -22,7 +22,7 @@ opts_ellmer_TypeObject <- function(constructor = c("type_object", "TypeObject", } `is_corrupted_ellmer::TypeObject` <- function(x) { - FALSE + has_foreign_s7_attrs(x) } #' @export @@ -46,7 +46,7 @@ opts_ellmer_TypeObject <- function(constructor = c("type_object", "TypeObject", constructive::.cstr_repair_attributes( x, code, ..., idiomatic_class = c("ellmer::TypeObject", "ellmer::Type", "S7_object"), - ignore = c("S7_class", "description", "required", "additional_properties", "properties") + ignore = c(s7_internal_attrs(), "description", "required", "additional_properties", "properties") ) } @@ -64,6 +64,6 @@ opts_ellmer_TypeObject <- function(constructor = c("type_object", "TypeObject", constructive::.cstr_repair_attributes( x, code, ..., idiomatic_class = c("ellmer::TypeObject", "ellmer::Type", "S7_object"), - ignore = c("S7_class", "description", "required", "additional_properties", "properties") + ignore = c(s7_internal_attrs(), "description", "required", "additional_properties", "properties") ) } diff --git a/man/construct.Rd b/man/construct.Rd index 238c9155..23186314 100644 --- a/man/construct.Rd +++ b/man/construct.Rd @@ -282,6 +282,7 @@ We can provide calls to `opts_*()` functions to the `...` argument. Each of thes \item \code{\link[=opts_S7_any]{opts_S7_any}(constructor = c("class_any", "next"), ...)} \item \code{\link[=opts_S7_base_class]{opts_S7_base_class}(constructor = c("S7", "next"), ...)} \item \code{\link[=opts_S7_class]{opts_S7_class}(constructor = c("new_class", "next"), ...)} + \item \code{\link[=opts_S7_class_ref]{opts_S7_class_ref}(constructor = c("environment", "next"), ...)} \item \code{\link[=opts_S7_external_generic]{opts_S7_external_generic}(constructor = c("new_external_generic", "next"), ...)} \item \code{\link[=opts_S7_generic]{opts_S7_generic}(constructor = c("new_generic", "next"), ...)} \item \code{\link[=opts_S7_object]{opts_S7_object}(constructor = c("S7_object", "next"), ...)} diff --git a/man/opts_S7_class_ref.Rd b/man/opts_S7_class_ref.Rd new file mode 100644 index 00000000..9bbf2a64 --- /dev/null +++ b/man/opts_S7_class_ref.Rd @@ -0,0 +1,30 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/s7-S7_class_ref.R +\name{opts_S7_class_ref} +\alias{opts_S7_class_ref} +\title{Constructive options for class 'S7_class_ref'} +\usage{ +opts_S7_class_ref(constructor = c("environment", "next"), ...) +} +\arguments{ +\item{constructor}{String. Name of the function used to construct the object.} + +\item{...}{Additional options used by user defined constructors through the \code{opts} object} +} +\value{ +An object of class +} +\description{ +These options will be used on objects of class 'S7_class_ref'. Since S7 +0.2.2.9000 an S7 object stores its class in the "_S7_class" attribute through +such a reference, which \code{S7::new_class()} creates and stores in the +environment of the class. +} +\details{ +Depending on \code{constructor}, we construct the object as follows: +\itemize{ +\item \code{"environment"} (default): We build the class and fetch the reference +from its environment. +\item \code{"next"} : Use the constructor for the next supported class. +} +} diff --git a/tests/testthat/_snaps/s7-S7-object.md b/tests/testthat/_snaps/s7-S7-object.md index 01af6425..15bf8e78 100644 --- a/tests/testthat/_snaps/s7-S7-object.md +++ b/tests/testthat/_snaps/s7-S7-object.md @@ -4,8 +4,32 @@ construct(S7::S7_object()) Output S7::S7_object() + +# S7_object, S7 <= 0.2.2 + + Code + construct(S7::S7_object(), opts_S7_object("next")) + Output + getClass("S4")@prototype |> + asS3(complete = FALSE) |> + structure(class = "S7_object") + Code + construct(structure(S7::S7_object(), `_S7_version` = 1L)) + Output + getClass("S4")@prototype |> + asS3(complete = FALSE) |> + structure(class = "S7_object", `_S7_version` = 1L) + +# S7_object, S7 > 0.2.2 + Code construct(S7::S7_object(), opts_S7_object("next")) + Output + getClass("S4")@prototype |> + asS3(complete = FALSE) |> + structure(class = "S7_object", `_S7_version` = 1L) + Code + construct(structure(S7::S7_object(), `_S7_version` = NULL)) Output getClass("S4")@prototype |> asS3(complete = FALSE) |> diff --git a/tests/testthat/_snaps/s7-S7_class.md b/tests/testthat/_snaps/s7-S7_class.md index e2998f59..da825b1a 100644 --- a/tests/testthat/_snaps/s7-S7_class.md +++ b/tests/testthat/_snaps/s7-S7_class.md @@ -4,6 +4,9 @@ construct(S7::S7_object) Output S7::S7_object + +# S7_class, next, S7 <= 0.2.2 + Code construct(S7::S7_object, opts_S7_class("next")) Output @@ -28,3 +31,32 @@ class = c("S7_class", "S7_object") ) +# S7_class, next, S7 > 0.2.2 + + Code + construct(S7::S7_object, opts_S7_class("next"), check = FALSE) + Output + (function() { + .Call(S7_object_) + }) |> + (`environment<-`)(constructive::.env("0x123456789", parents = "namespace:S7")) |> + structure( + name = "S7_object", + properties = list(), + abstract = FALSE, + constructor = (function() { + .Call(S7_object_) + }) |> + (`environment<-`)(constructive::.env("0x123456789", parents = "namespace:S7")), + validator = (function(self) { + if (!is_S7_type(self)) { + "Underlying data is corrupt" + } + }) |> + (`environment<-`)(asNamespace("S7")), + S7_class_name = "S7_object", + S7_dispatch = "S7_object", + S7_version = 1L, + class = c("S7_class", "S7_object") + ) + diff --git a/tests/testthat/_snaps/s7-S7_class_ref.md b/tests/testthat/_snaps/s7-S7_class_ref.md new file mode 100644 index 00000000..fa7fef6d --- /dev/null +++ b/tests/testthat/_snaps/s7-S7_class_ref.md @@ -0,0 +1,56 @@ +# S7_class_ref + + Code + Dog <- evalq(S7::new_class("Dog", properties = list(name = S7::class_character)), + .GlobalEnv) + construct(Dog(name = "x"), check = FALSE) + Output + S7::S7_object() |> + structure( + class = c("Dog", "S7_object"), + `_S7_class` = environment( + S7::new_class( + "Dog", + parent = S7::S7_object, + properties = list(name = S7::class_character), + constructor = as.function( + list( + name = character(0), + quote({ + name + S7::new_object(S7::S7_object(), name = name) + }) + ), + envir = .GlobalEnv + ) |> + structure(class = "S7_constructor") + ) + )$.S7_class_ref, + name = "x" + ) + Code + construct(attr(Dog(name = "x"), "_S7_class"), check = FALSE) + Output + environment( + S7::new_class( + "Dog", + parent = S7::S7_object, + properties = list(name = S7::class_character), + constructor = as.function( + list( + name = character(0), + quote({ + name + S7::new_object(S7::S7_object(), name = name) + }) + ), + envir = .GlobalEnv + ) |> + structure(class = "S7_constructor") + ) + )$.S7_class_ref + Code + construct(attr(Dog(name = "x"), "_S7_class"), opts_S7_class_ref("next"), check = FALSE) + Output + constructive::.env("0x123456789", parents = "empty", class = "S7_class_ref") + diff --git a/tests/testthat/_snaps/s7-S7_generic.md b/tests/testthat/_snaps/s7-S7_generic.md index 65047717..73024982 100644 --- a/tests/testthat/_snaps/s7-S7_generic.md +++ b/tests/testthat/_snaps/s7-S7_generic.md @@ -1,4 +1,4 @@ -# S7_generic +# S7_generic, S7 <= 0.2.2 Code type_of <- S7::new_generic("type_of", dispatch_args = "x") @@ -46,3 +46,52 @@ "A data frame") S7::method(type_of, S7::class_function) <- (function(x, ...) "A function") +# S7_generic, S7 > 0.2.2 + + Code + type_of <- S7::new_generic("type_of", dispatch_args = "x") + construct(type_of) + Output + S7::new_generic("type_of", dispatch_args = "x") + Code + construct(type_of, opts_S7_generic("next"), opts_function(srcref = TRUE), + check = FALSE) + Output + (function(x, ...) S7::S7_dispatch()) |> + structure( + class = c("S7_generic", "function", "S7_object"), + `_S7_version` = 1L, + `_S7_class` = environment( + S7::new_class( + "S7_generic", + parent = S7::class_function, + properties = list( + name = S7::class_character, + methods = S7::class_environment, + dispatch_args = S7::class_character + ), + constructor = as.function( + list( + .data = quote(function() NULL), + name = character(0), + methods = quote(new.env(parent = emptyenv())), + dispatch_args = character(0), + quote(new_object(fun(.data = .data), name = name, methods = methods, dispatch_args = dispatch_args)) + ), + envir = constructive::.env("0x123456789", parents = c("0x123456789", "namespace:S7")) + ) |> + structure(class = "S7_constructor") + ) + )$.S7_class_ref, + name = "type_of", + methods = constructive::.env("0x123456789", parents = "empty"), + dispatch_args = "x", + srcref = NULL + ) + Code + S7::method(type_of, S7::class_character) <- (function(x, ...) + "A character vector") + S7::method(type_of, S7::new_S3_class("data.frame")) <- (function(x, ...) + "A data frame") + S7::method(type_of, S7::class_function) <- (function(x, ...) "A function") + diff --git a/tests/testthat/_snaps/s7-S7_union.md b/tests/testthat/_snaps/s7-S7_union.md index 051a1f03..e7d5f3a8 100644 --- a/tests/testthat/_snaps/s7-S7_union.md +++ b/tests/testthat/_snaps/s7-S7_union.md @@ -25,6 +25,9 @@ ) ) |> structure(class = "S7_union") + +# S7_union, S7 <= 0.2.2 + Code construct(S7::class_vector, opts_S7_union("next"), opts_S7_base_class("next"), check = FALSE) @@ -131,3 +134,95 @@ ) |> structure(class = "S7_union") +# S7_union, S7 > 0.2.2 + + Code + construct(S7::class_vector, opts_S7_union("next"), opts_S7_base_class("next"), + check = FALSE) + Output + list( + classes = list( + list( + class = "logical", + constructor_name = "logical", + constructor = as.function(list(.data = logical(0), quote(.data)), envir = asNamespace("S7")), + validator = (function(object) if (base_class(object) != "logical") { + sprintf("Underlying data must be <%s> not <%s>", "logical", base_class(object)) + }) |> + (`environment<-`)(asNamespace("S7")) + ) |> + structure(class = "S7_base_class"), + list( + class = "integer", + constructor_name = "integer", + constructor = as.function(list(.data = integer(0), quote(.data)), envir = asNamespace("S7")), + validator = (function(object) if (base_class(object) != "integer") { + sprintf("Underlying data must be <%s> not <%s>", "integer", base_class(object)) + }) |> + (`environment<-`)(asNamespace("S7")) + ) |> + structure(class = "S7_base_class"), + list( + class = "double", + constructor_name = "double", + constructor = as.function(list(.data = numeric(0), quote(.data)), envir = asNamespace("S7")), + validator = (function(object) if (base_class(object) != "double") { + sprintf("Underlying data must be <%s> not <%s>", "double", base_class(object)) + }) |> + (`environment<-`)(asNamespace("S7")) + ) |> + structure(class = "S7_base_class"), + list( + class = "complex", + constructor_name = "complex", + constructor = as.function(list(.data = complex(0), quote(.data)), envir = asNamespace("S7")), + validator = (function(object) if (base_class(object) != "complex") { + sprintf("Underlying data must be <%s> not <%s>", "complex", base_class(object)) + }) |> + (`environment<-`)(asNamespace("S7")) + ) |> + structure(class = "S7_base_class"), + list( + class = "character", + constructor_name = "character", + constructor = as.function(list(.data = character(0), quote(.data)), envir = asNamespace("S7")), + validator = (function(object) if (base_class(object) != "character") { + sprintf("Underlying data must be <%s> not <%s>", "character", base_class(object)) + }) |> + (`environment<-`)(asNamespace("S7")) + ) |> + structure(class = "S7_base_class"), + list( + class = "raw", + constructor_name = "raw", + constructor = as.function(list(.data = raw(0), quote(.data)), envir = asNamespace("S7")), + validator = (function(object) if (base_class(object) != "raw") { + sprintf("Underlying data must be <%s> not <%s>", "raw", base_class(object)) + }) |> + (`environment<-`)(asNamespace("S7")) + ) |> + structure(class = "S7_base_class"), + list( + class = "expression", + constructor_name = "expression", + constructor = as.function(list(.data = expression(), quote(.data)), envir = asNamespace("S7")), + validator = (function(object) if (base_class(object) != "expression") { + sprintf("Underlying data must be <%s> not <%s>", "expression", base_class(object)) + }) |> + (`environment<-`)(asNamespace("S7")) + ) |> + structure(class = "S7_base_class"), + list( + class = "list", + constructor_name = "list", + constructor = as.function(list(.data = list(), quote(.data)), envir = asNamespace("S7")), + validator = (function(object) if (base_class(object) != "list") { + sprintf("Underlying data must be <%s> not <%s>", "list", base_class(object)) + }) |> + (`environment<-`)(asNamespace("S7")) + ) |> + structure(class = "S7_base_class") + ) + ) |> + structure(class = "S7_union") + diff --git a/tests/testthat/test-s7-S7-object.R b/tests/testthat/test-s7-S7-object.R index c5482e55..29acc651 100644 --- a/tests/testthat/test-s7-S7-object.R +++ b/tests/testthat/test-s7-S7-object.R @@ -3,6 +3,28 @@ test_that("S7_object", { testthat::skip_on_os("linux") expect_snapshot({ construct(S7::S7_object()) - construct(S7::S7_object(), opts_S7_object("next")) }) }) + +test_that("S7_object, S7 <= 0.2.2", { + testthat::skip_on_os("linux") + skip_if_not_installed("S7") + skip_if(with_versions(S7 > "0.2.2")) + expect_snapshot({ + construct(S7::S7_object(), opts_S7_object("next")) + # built by another S7 version + construct(structure(S7::S7_object(), `_S7_version` = 1L)) + }) +}) + +test_that("S7_object, S7 > 0.2.2", { + # objects have a "_S7_version" attribute + testthat::skip_on_os("linux") + skip_if_not_installed("S7") + skip_if(with_versions(S7 <= "0.2.2")) + expect_snapshot({ + construct(S7::S7_object(), opts_S7_object("next")) + # built by another S7 version + construct(structure(S7::S7_object(), `_S7_version` = NULL)) + }) +}) diff --git a/tests/testthat/test-s7-S7_class.R b/tests/testthat/test-s7-S7_class.R index 8cba0923..702a0a0d 100644 --- a/tests/testthat/test-s7-S7_class.R +++ b/tests/testthat/test-s7-S7_class.R @@ -23,6 +23,35 @@ test_that("S7_class", { ) expect_snapshot({ construct(S7::S7_object) + }) +}) + +test_that("S7_class, next, S7 <= 0.2.2", { + # see comments above + testthat::skip_on_os("linux") + skip_if_not_installed("S7") + skip_if(with_versions(S7 > "0.2.2")) + skip_if( + !is.null(attr(S7::S7_object@constructor, "srcref")), + "S7 was installed with source references" + ) + expect_snapshot({ construct(S7::S7_object, opts_S7_class("next")) }) }) + +test_that("S7_class, next, S7 > 0.2.2", { + # see comments above + testthat::skip_on_os("linux") + skip_if_not_installed("S7") + skip_if(with_versions(S7 <= "0.2.2")) + skip_if( + !is.null(attr(S7::S7_object@constructor, "srcref")), + "S7 was installed with source references" + ) + expect_snapshot({ + # the class's environment holds a reference to the class, it is constructed + # from its memory address so we use check = FALSE + construct(S7::S7_object, opts_S7_class("next"), check = FALSE) + }) +}) diff --git a/tests/testthat/test-s7-S7_class_ref.R b/tests/testthat/test-s7-S7_class_ref.R new file mode 100644 index 00000000..8a9d869c --- /dev/null +++ b/tests/testthat/test-s7-S7_class_ref.R @@ -0,0 +1,17 @@ +test_that("S7_class_ref", { + # S7 <= 0.2.2 doesn't use class references + testthat::skip_on_os("linux") + skip_if_not_installed("S7") + skip_if(with_versions(S7 <= "0.2.2")) + expect_snapshot({ + Dog <- evalq( + S7::new_class("Dog", properties = list(name = S7::class_character)), + .GlobalEnv + ) + # check = FALSE because the check evaluates the code in the test environment + # where `new_class()` infers the package "constructive" + construct(Dog(name = "x"), check = FALSE) + construct(attr(Dog(name = "x"), "_S7_class"), check = FALSE) + construct(attr(Dog(name = "x"), "_S7_class"), opts_S7_class_ref("next"), check = FALSE) + }) +}) diff --git a/tests/testthat/test-s7-S7_generic.R b/tests/testthat/test-s7-S7_generic.R index d7306b1c..c6c7a821 100644 --- a/tests/testthat/test-s7-S7_generic.R +++ b/tests/testthat/test-s7-S7_generic.R @@ -1,4 +1,6 @@ -test_that("S7_generic", { +test_that("S7_generic, S7 <= 0.2.2", { + skip_if_not_installed("S7") + skip_if(with_versions(S7 > "0.2.2")) expect_snapshot({ # examples from `?S7::new_property` type_of <- S7::new_generic("type_of", dispatch_args = "x") @@ -12,3 +14,19 @@ test_that("S7_generic", { S7::method(type_of, S7::class_function) <- function(x, ...) "A function" }) }) + +test_that("S7_generic, S7 > 0.2.2", { + # The class is stored in a reference shared by all generics, so we don't + # change its environment, check = FALSE because of environment addresses + skip_if_not_installed("S7") + skip_if(with_versions(S7 <= "0.2.2")) + expect_snapshot({ + # examples from `?S7::new_property` + type_of <- S7::new_generic("type_of", dispatch_args = "x") + construct(type_of) + construct(type_of, opts_S7_generic("next"), opts_function(srcref = TRUE), check = FALSE) + S7::method(type_of, S7::class_character) <- function(x, ...) "A character vector" + S7::method(type_of, S7::new_S3_class("data.frame")) <- function(x, ...) "A data frame" + S7::method(type_of, S7::class_function) <- function(x, ...) "A function" + }) +}) diff --git a/tests/testthat/test-s7-S7_union.R b/tests/testthat/test-s7-S7_union.R index 649006ad..1103dc34 100644 --- a/tests/testthat/test-s7-S7_union.R +++ b/tests/testthat/test-s7-S7_union.R @@ -4,11 +4,27 @@ test_that("S7_union", { construct(S7::class_vector, opts_S7_union("|")) construct(S7::class_vector, opts_S7_union("new_union")) construct(S7::class_vector, opts_S7_union("next")) + }) +}) + +test_that("S7_union, S7 <= 0.2.2", { + skip_if_not_installed("S7") + skip_if(with_versions(S7 > "0.2.2")) + expect_snapshot({ # check = FALSE because of changing environment address issues construct(S7::class_vector, opts_S7_union("next"), opts_S7_base_class("next"), check = FALSE) }) }) +test_that("S7_union, S7 > 0.2.2", { + # the base classes are defined differently + skip_if_not_installed("S7") + skip_if(with_versions(S7 <= "0.2.2")) + expect_snapshot({ + construct(S7::class_vector, opts_S7_union("next"), opts_S7_base_class("next"), check = FALSE) + }) +}) + class_logical <- list( class = "logical",