From b9f6cb8df06b6fa13f8d7543c77418a178dd1ac1 Mon Sep 17 00:00:00 2001 From: dgkf <18220321+dgkf@users.noreply.github.com> Date: Wed, 26 Nov 2025 13:33:14 -0500 Subject: [PATCH 1/2] feat: perrmit (most) reserved attributes names as property names --- R/class.R | 99 +++++++++++++++++++++++++--------- R/property.R | 60 ++++++++++++++------- src/init.c | 8 +-- src/prop.c | 14 +++-- tests/testthat/_snaps/class.md | 44 ++++++--------- tests/testthat/test-class.R | 99 +++++++++++++++++++++++++++------- 6 files changed, 225 insertions(+), 99 deletions(-) diff --git a/R/class.R b/R/class.R index 29887e47..358ca24a 100644 --- a/R/class.R +++ b/R/class.R @@ -95,14 +95,14 @@ #' r <- Range(start = 10, end = 20) #' try(r@start <- 25) new_class <- function( - name, - parent = S7_object, - package = topNamespaceName(parent.frame()), - properties = list(), - abstract = FALSE, - constructor = NULL, - validator = NULL) { - + name, + parent = S7_object, + package = topNamespaceName(parent.frame()), + properties = list(), + abstract = FALSE, + constructor = NULL, + validator = NULL +) { check_name(name) parent <- as_class(parent) @@ -119,7 +119,10 @@ new_class <- function( if (!is.null(validator)) { check_function(validator, alist(self = )) } - if (abstract && (!is_class(parent) || !(parent@abstract || parent@name == "S7_object"))) { + if ( + abstract && + (!is_class(parent) || !(parent@abstract || parent@name == "S7_object")) + ) { stop("Abstract classes must have abstract parents") } } @@ -128,12 +131,16 @@ new_class <- function( all_props <- attr(parent, "properties", exact = TRUE) %||% list() new_props <- as_properties(properties) check_prop_names(new_props) + all_props[names(new_props)] <- new_props if (is.null(constructor)) { - constructor <- new_constructor(parent, all_props, - envir = parent.frame(), - package = package) + constructor <- new_constructor( + parent, + all_props, + envir = parent.frame(), + package = package + ) } object <- constructor @@ -150,7 +157,15 @@ new_class <- function( global_variables(names(all_props)) object } -globalVariables(c("name", "parent", "package", "properties", "abstract", "constructor", "validator")) +globalVariables(c( + "name", + "parent", + "package", + "properties", + "abstract", + "constructor", + "validator" +)) #' @rawNamespace if (getRversion() >= "4.3.0") S3method(nameOfClass, S7_class, S7_class_name) S7_class_name <- function(x) { @@ -203,7 +218,12 @@ print.S7_class <- function(x, ...) { #' @export str.S7_class <- function(object, ..., nest.lev = 0) { cat(if (nest.lev > 0) " ") - cat("<", paste0(class_dispatch(object), collapse = "/"), "> constructor", sep = "") + cat( + "<", + paste0(class_dispatch(object), collapse = "/"), + "> constructor", + sep = "" + ) cat("\n") if (nest.lev == 0) { @@ -248,7 +268,10 @@ new_object <- function(.parent, ...) { stop("`new_object()` must be called from within a constructor") } if (class@abstract) { - msg <- sprintf("Can't construct an object from abstract class <%s>", class@name) + msg <- sprintf( + "Can't construct an object from abstract class <%s>", + class@name + ) stop(msg) } @@ -265,7 +288,7 @@ new_object <- function(.parent, ...) { attrs <- c( list(class = class_dispatch(class), S7_class = class), - args[!has_setter], + with_remap_reserved_names(args[!has_setter]), attributes(object) ) attrs <- attrs[!duplicated(names(attrs))] @@ -273,12 +296,14 @@ new_object <- function(.parent, ...) { # invoke custom property setters prop_setter_vals <- args[has_setter] - for (name in names(prop_setter_vals)) + for (name in names(prop_setter_vals)) { prop(object, name, check = FALSE) <- prop_setter_vals[[name]] + } # Don't need to validate if parent class already validated, # i.e. it's a non-abstract S7 class - parent_validated <- inherits(class@parent, "S7_object") && !class@parent@abstract + parent_validated <- inherits(class@parent, "S7_object") && + !class@parent@abstract validate(object, recursive = !parent_validated) object @@ -295,8 +320,9 @@ str.S7_object <- function(object, ..., nest.lev = 0) { cat(obj_desc(object)) if (!is_S7_type(object)) { - if (!typeof(object) %in% c("numeric", "integer", "character", "double")) + if (!typeof(object) %in% c("numeric", "integer", "character", "double")) { cat(" ") + } attrs <- attributes(object) if (is.environment(object)) { @@ -328,15 +354,38 @@ S7_class <- function(object) { attr(object, "S7_class", exact = TRUE) } - check_prop_names <- function(properties, error_call = sys.call(-1L)) { - # these attributes have special C handlers in base R - forbidden <- c("names", "dim", "dimnames", "class", - "tsp", "comment", "row.names", "...") + forbidden <- c("...") forbidden <- intersect(forbidden, names(properties)) if (length(forbidden)) { - msg <- paste0("property can't be named: ", - paste0(forbidden, collapse = ", ")) + msg <- paste0( + "property can't be named: ", + paste0("'", forbidden, "'", collapse = ", ") + ) stop(simpleError(msg, error_call)) } } + +remap_reserved_names <- function(names) { + # these attributes have special C handlers in base R + forbidden <- c( + "names", + "dim", + "dimnames", + "class", + "tsp", + "comment", + "row.names", + "..." + ) + + is_forbidden <- names %in% forbidden + names[is_forbidden] <- paste0(".__S7_prop__", names[is_forbidden], "__") + + names +} + +with_remap_reserved_names <- function(x) { + names(x) <- remap_reserved_names(names(x)) + x +} diff --git a/R/property.R b/R/property.R index 801be6b9..03f2c948 100644 --- a/R/property.R +++ b/R/property.R @@ -40,7 +40,7 @@ #' function promise in the default constructor, evaluated at the time the #' object is constructed. #' @param name Property name, primarily used for error messages. Generally -#' don't need to set this here, as it's more convenient to supply as +#' don't need to set this here, as it's more convenient to supply as #' the element name when defining a list of properties. If both `name` #' and a list-name are supplied, the list-name will be used. #' @returns An S7 property, i.e. a list with class `S7_property`. @@ -72,12 +72,14 @@ #' # argument to the default constructor #' try(Clock(now = 10)) #' args(Clock) -new_property <- function(class = class_any, - getter = NULL, - setter = NULL, - validator = NULL, - default = NULL, - name = NULL) { +new_property <- function( + class = class_any, + getter = NULL, + setter = NULL, + validator = NULL, + default = NULL, + name = NULL +) { class <- as_class(class) check_prop_default(default, class) @@ -119,7 +121,7 @@ check_prop_default <- function(default, class, error_call = sys.call(-1)) { # The meaning of a `...` prop default needs discussion stop(simpleError("`default` cannot be `...`", error_call)) } - if (identical(default, quote(expr =))) { + if (identical(default, quote(expr = ))) { # The meaning of a missing prop default needs discussion stop(simpleError("`default` cannot be missing", error_call)) } @@ -128,11 +130,15 @@ check_prop_default <- function(default, class, error_call = sys.call(-1)) { return() } - if (class_inherits(default, class)) + if (class_inherits(default, class)) { return() + } - msg <- sprintf("`default` must be an instance of %s, not a %s", - class_desc(class), obj_desc(default)) + msg <- sprintf( + "`default` must be an instance of %s, not a %s", + class_desc(class), + obj_desc(default) + ) stop(simpleError(msg, error_call)) } @@ -188,7 +194,8 @@ prop_default <- function(prop, envir, package) { #' lexington@height <- 14 #' prop(lexington, "height") <- 15 prop <- function(object, name) { - .Call(prop_, object, name) + attr_name <- remap_reserved_names(name) + .Call(prop_, object, name, attr_name) } propr <- function(object, name) { @@ -229,11 +236,12 @@ prop_obj <- function(object, name) { #' [validate()] on the object before returning. #' @export `prop<-` <- function(object, name, check = TRUE, value) { - .Call(prop_set_, object, name, check, value) + attr_name <- remap_reserved_names(name) + .Call(prop_set_, object, name, attr_name, check, value) } `propr<-` <- local({ - # reference implementation of `prop<-()` implemented in R + # reference implementation of `prop<-()` implemented in R # This flag is used to avoid infinite loops if you are assigning a property from a setter function setter_property <- NULL @@ -246,7 +254,11 @@ prop_obj <- function(object, name) { } if (!is.null(prop$getter) && is.null(prop$setter)) { - msg <- sprintf("Can't set read-only property %s@%s", obj_desc(object), name) + msg <- sprintf( + "Can't set read-only property %s@%s", + obj_desc(object), + name + ) stop(msg, call. = FALSE) } @@ -293,7 +305,8 @@ prop_error_unknown <- function(object, prop_name) { # called from src/prop.c prop_validate <- function(prop, value, object = NULL) { if (!class_inherits(value, prop$class)) { - return(sprintf("%s must be %s, not %s", + return(sprintf( + "%s must be %s, not %s", prop_label(object, prop$name), class_desc(prop$class), obj_desc(value) @@ -319,7 +332,8 @@ prop_validate <- function(prop, value, object = NULL) { stop(sprintf( "%s validator must return NULL or a character, not <%s>.", - prop_label(object, prop$name), typeof(val) + prop_label(object, prop$name), + typeof(val) )) } @@ -363,7 +377,15 @@ prop_names <- function(object) { if (inherits(object, "S7_class")) { # S7_class isn't a S7_class (somewhat obviously) so we fake the property names - c("name", "parent", "package", "properties", "abstract", "constructor", "validator") + c( + "name", + "parent", + "package", + "properties", + "abstract", + "constructor", + "validator" + ) } else { class <- S7_class(object) props <- attr(class, "properties", exact = TRUE) @@ -473,7 +495,6 @@ as_properties <- function(x) { } as_property <- function(x, name, i) { - if (is_property(x)) { if (name == "") { if (is.null(x$name)) { @@ -502,4 +523,3 @@ prop_is_read_only <- function(prop) { prop_has_setter <- function(prop) is.function(prop$setter) prop_is_dynamic <- function(prop) is.function(prop$getter) - diff --git a/src/init.c b/src/init.c index add8c765..f571ecb9 100644 --- a/src/init.c +++ b/src/init.c @@ -9,16 +9,16 @@ extern SEXP method_call_(SEXP, SEXP, SEXP, SEXP); extern SEXP test_call_(SEXP, SEXP, SEXP, SEXP); extern SEXP S7_class_(SEXP, SEXP); extern SEXP S7_object_(void); -extern SEXP prop_(SEXP, SEXP); -extern SEXP prop_set_(SEXP, SEXP, SEXP, SEXP); +extern SEXP prop_(SEXP, SEXP, SEXP); +extern SEXP prop_set_(SEXP, SEXP, SEXP, SEXP, SEXP); #define CALLDEF(name, n) {#name, (DL_FUNC) &name, n} static const R_CallMethodDef CallEntries[] = { CALLDEF(method_, 4), CALLDEF(S7_object_, 0), - CALLDEF(prop_, 2), - CALLDEF(prop_set_, 4), + CALLDEF(prop_, 3), + CALLDEF(prop_set_, 5), {NULL, NULL, 0} }; diff --git a/src/prop.c b/src/prop.c index 1086fa0d..55a549ef 100644 --- a/src/prop.c +++ b/src/prop.c @@ -292,13 +292,16 @@ Rboolean getter_callable_no_recurse(SEXP getter, SEXP object, SEXP name_sym) { } -SEXP prop_(SEXP object, SEXP name) { +SEXP prop_(SEXP object, SEXP name, SEXP attr_name) { check_is_S7(object); SEXP name_rchar = STRING_ELT(name, 0); const char* name_char = CHAR(name_rchar); SEXP name_sym = Rf_installTrChar(name_rchar); + SEXP attr_name_rchar = STRING_ELT(attr_name, 0); + SEXP attr_name_sym = Rf_installTrChar(attr_name_rchar); + SEXP S7_class = Rf_getAttrib(object, sym_S7_class); SEXP properties = Rf_getAttrib(S7_class, sym_properties); @@ -315,7 +318,7 @@ SEXP prop_(SEXP object, SEXP name) { } // try to resolve property from the object attributes - SEXP value = Rf_getAttrib(object, name_sym); + SEXP value = Rf_getAttrib(object, attr_name_sym); // This is commented out because we currently have no way to distinguish between // a prop with a value of NULL, and a prop value that is unset/missing. @@ -343,7 +346,7 @@ SEXP prop_(SEXP object, SEXP name) { } -SEXP prop_set_(SEXP object, SEXP name, SEXP check_sexp, SEXP value) { +SEXP prop_set_(SEXP object, SEXP name, SEXP attr_name, SEXP check_sexp, SEXP value) { check_is_S7(object); @@ -351,6 +354,9 @@ SEXP prop_set_(SEXP object, SEXP name, SEXP check_sexp, SEXP value) { const char *name_char = CHAR(name_rchar); SEXP name_sym = Rf_installTrChar(name_rchar); + SEXP attr_name_rchar = STRING_ELT(attr_name, 0); + SEXP attr_name_sym = Rf_installTrChar(attr_name_rchar); + Rboolean check = Rf_asLogical(check_sexp); Rboolean should_validate_obj = check; Rboolean should_validate_prop = check; @@ -382,7 +388,7 @@ SEXP prop_set_(SEXP object, SEXP name, SEXP check_sexp, SEXP value) { // don't use setter() if (should_validate_prop) prop_validate(property, value, object); - Rf_setAttrib(object, name_sym, value); + Rf_setAttrib(object, attr_name_sym, value); } if (should_validate_obj) diff --git a/tests/testthat/_snaps/class.md b/tests/testthat/_snaps/class.md index 709708a4..9c2ea2d9 100644 --- a/tests/testthat/_snaps/class.md +++ b/tests/testthat/_snaps/class.md @@ -1,4 +1,4 @@ -# S7 classes / print nicely +# S7 classes: print nicely Code foo2 @@ -41,7 +41,7 @@ List of 1 $ : constructor -# S7 classes / prints @package and @abstract details +# S7 classes: prints @package and @abstract details Code foo @@ -52,7 +52,7 @@ @ validator : @ properties : -# S7 classes / checks inputs +# S7 classes: checks inputs Code new_class(1) @@ -85,7 +85,7 @@ Error: ! `validator` must be function(self), not function() -# S7 classes / can't inherit from S4 or class unions +# S7 classes: can't inherit from S4 or class unions Code new_class("test", parent = parentS4) @@ -98,7 +98,7 @@ Error: ! Can't convert `X[[i]]` to a valid class. Class specification must be an S7 class object, the result of `new_S3_class()`, an S4 class object, or a base class, not a . -# S7 classes / can't inherit from an environment +# S7 classes: can't inherit from an environment Code new_class("test", parent = class_environment) @@ -106,7 +106,7 @@ Error: ! Can't inherit from an environment. -# abstract classes / can't be instantiated +# abstract classes: can't be instantiated Code foo <- new_class("foo", abstract = TRUE) @@ -115,7 +115,7 @@ Error in `S7::new_object()`: ! Can't construct an object from abstract class -# abstract classes / can't inherit from concrete class +# abstract classes: can't inherit from concrete class Code foo1 <- new_class("foo1") @@ -124,7 +124,7 @@ Error in `new_class()`: ! Abstract classes must have abstract parents -# abstract classes / can use inherited validator from abstract class +# abstract classes: can use inherited validator from abstract class Code foo2(x = 2) @@ -133,7 +133,7 @@ ! object is invalid: - @x has bad value -# new_object() / gives useful error if called directly +# new_object(): gives useful error if called directly Code new_object() @@ -141,7 +141,7 @@ Error in `new_object()`: ! `new_object()` must be called from within a constructor -# new_object() / validates object +# new_object(): validates object Code foo("x") @@ -156,7 +156,7 @@ ! object is invalid: - x must be positive -# new_object() / runs each parent validator exactly once +# new_object(): runs each parent validator exactly once Code . <- A() @@ -171,7 +171,7 @@ Output A B C -# S7 object / displays nicely +# S7 object: displays nicely Code foo <- new_class("foo", properties = list(x = class_double, y = class_double), @@ -189,7 +189,7 @@ ..@ x: num(0) ..@ y: num(0) -# S7 object / displays objects with data nicely +# S7 object: displays objects with data nicely Code text <- new_class("text", class_character, package = NULL) @@ -202,7 +202,7 @@ List of 1 $ : chr "x" -# S7 object / displays list objects nicely +# S7 object: displays list objects nicely Code foo1(list(x = 1, y = list(a = 21, b = 22)), x = 3, y = list(a = 41, b = 42)) @@ -225,21 +225,11 @@ Error: ! Can not combine S7 class objects -# can't create class with reserved property names +# can't create class with forbidden property names Code - new_class("foo", properties = list(names = class_character)) + new_class("foo", properties = list(... = class_character)) Condition Error in `new_class()`: - ! property can't be named: names - Code - new_class("foo", properties = list(dim = NULL | class_integer)) - Condition - Error in `new_class()`: - ! property can't be named: dim - Code - new_class("foo", properties = list(dim = NULL | class_integer, dimnames = class_list)) - Condition - Error in `new_class()`: - ! property can't be named: dim, dimnames + ! property can't be named: '...' diff --git a/tests/testthat/test-class.R b/tests/testthat/test-class.R index 684826c1..55a873c5 100644 --- a/tests/testthat/test-class.R +++ b/tests/testthat/test-class.R @@ -11,7 +11,11 @@ describe("S7 classes", { }) it("print nicely", { - foo1 <- new_class("foo1", properties = list(x = class_integer, y = class_integer), package = NULL) + foo1 <- new_class( + "foo1", + properties = list(x = class_integer, y = class_integer), + package = NULL + ) foo2 <- new_class("foo2", foo1, package = NULL) expect_snapshot({ @@ -106,12 +110,13 @@ describe("abstract classes", { }) describe("new_object()", { - it("gives useful error if called directly",{ + it("gives useful error if called directly", { expect_snapshot(new_object(), error = TRUE) }) it("validates object", { - foo <- new_class("foo", + foo <- new_class( + "foo", properties = list(x = new_property(class_double)), validator = function(self) if (self@x < 0) "x must be positive", package = NULL @@ -146,8 +151,11 @@ describe("S7 object", { it("displays nicely", { expect_snapshot({ - foo <- new_class("foo", properties = list(x = class_double, y = class_double), - package = NULL) + foo <- new_class( + "foo", + properties = list(x = class_double, y = class_double), + package = NULL + ) foo() str(list(foo())) }) @@ -198,9 +206,12 @@ describe("default constructor", { }) it("can initialise a property to NULL", { - foo <- new_class("foo", properties = list( - x = new_property(default = 10) - )) + foo <- new_class( + "foo", + properties = list( + x = new_property(default = 10) + ) + ) x <- foo(x = NULL) expect_equal(x@x, NULL) }) @@ -232,11 +243,14 @@ test_that("c(, ...) gives error", { }) test_that("can round trip to disk and back", { - eval(quote({ - foo1 <- new_class("foo1", properties = list(y = class_integer)) - foo2 <- new_class("foo2", properties = list(x = foo1)) - f <- foo2(x = foo1(y = 1L)) - }), globalenv()) + eval( + quote({ + foo1 <- new_class("foo1", properties = list(y = class_integer)) + foo2 <- new_class("foo2", properties = list(x = foo1)) + f <- foo2(x = foo1(y = 1L)) + }), + globalenv() + ) f <- globalenv()[["f"]] path <- tempfile() @@ -247,12 +261,59 @@ test_that("can round trip to disk and back", { rm(foo1, foo2, f, envir = globalenv()) }) - -test_that("can't create class with reserved property names", { +test_that("can't create class with forbidden property names", { expect_snapshot(error = TRUE, { - new_class("foo", properties = list(names = class_character)) - new_class("foo", properties = list(dim = NULL | class_integer)) - new_class("foo", properties = list(dim = NULL | class_integer, - dimnames = class_list)) + new_class("foo", properties = list("..." = class_character)) + }) +}) + +test_that("can create class with reserved property names", { + expect_no_error({ + cls <- new_class("foo", properties = list(names = class_character)) + cls() + cls(names = "test") + + cls <- new_class("foo", properties = list(dim = NULL | class_integer)) + cls() + cls(dim = NULL) + cls(dim = 1L) + + cls <- new_class( + "foo", + properties = list(dim = NULL | class_integer, dimnames = class_list) + ) + + cls() + cls(dimnames = list(1, 2, 3)) + }) +}) + +test_that("get and set properties with resrved attribute names", { + obj <- expect_no_error({ + cls <- new_class( + "foo", + properties = list( + names = class_character, + dim = NULL | class_integer, + class = class_logical + ) + ) + + cls() }) + + expect_no_error(obj@names <- "test") + expect_equal(obj@names, "test") + expect_no_error(prop(obj, "names") <- "test") + expect_equal(prop(obj, "names"), "test") + + expect_no_error(obj@dim <- 2L) + expect_equal(obj@dim, 2L) + expect_no_error(prop(obj, "dim") <- 2L) + expect_equal(prop(obj, "dim"), 2L) + + expect_no_error(obj@class <- FALSE) + expect_equal(obj@class, FALSE) + expect_no_error(prop(obj, "class") <- FALSE) + expect_equal(prop(obj, "class"), FALSE) }) From a156fc61038d6ac50a3ce8fe74b9002835689b6f Mon Sep 17 00:00:00 2001 From: dgkf <18220321+dgkf@users.noreply.github.com> Date: Wed, 26 Nov 2025 14:18:41 -0500 Subject: [PATCH 2/2] fix: rebuilding tests-class snapshots --- tests/testthat/_snaps/class.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/tests/testthat/_snaps/class.md b/tests/testthat/_snaps/class.md index 9c2ea2d9..19d43239 100644 --- a/tests/testthat/_snaps/class.md +++ b/tests/testthat/_snaps/class.md @@ -1,4 +1,4 @@ -# S7 classes: print nicely +# S7 classes / print nicely Code foo2 @@ -41,7 +41,7 @@ List of 1 $ : constructor -# S7 classes: prints @package and @abstract details +# S7 classes / prints @package and @abstract details Code foo @@ -52,7 +52,7 @@ @ validator : @ properties : -# S7 classes: checks inputs +# S7 classes / checks inputs Code new_class(1) @@ -85,7 +85,7 @@ Error: ! `validator` must be function(self), not function() -# S7 classes: can't inherit from S4 or class unions +# S7 classes / can't inherit from S4 or class unions Code new_class("test", parent = parentS4) @@ -98,7 +98,7 @@ Error: ! Can't convert `X[[i]]` to a valid class. Class specification must be an S7 class object, the result of `new_S3_class()`, an S4 class object, or a base class, not a . -# S7 classes: can't inherit from an environment +# S7 classes / can't inherit from an environment Code new_class("test", parent = class_environment) @@ -106,7 +106,7 @@ Error: ! Can't inherit from an environment. -# abstract classes: can't be instantiated +# abstract classes / can't be instantiated Code foo <- new_class("foo", abstract = TRUE) @@ -115,7 +115,7 @@ Error in `S7::new_object()`: ! Can't construct an object from abstract class -# abstract classes: can't inherit from concrete class +# abstract classes / can't inherit from concrete class Code foo1 <- new_class("foo1") @@ -124,7 +124,7 @@ Error in `new_class()`: ! Abstract classes must have abstract parents -# abstract classes: can use inherited validator from abstract class +# abstract classes / can use inherited validator from abstract class Code foo2(x = 2) @@ -133,7 +133,7 @@ ! object is invalid: - @x has bad value -# new_object(): gives useful error if called directly +# new_object() / gives useful error if called directly Code new_object() @@ -141,7 +141,7 @@ Error in `new_object()`: ! `new_object()` must be called from within a constructor -# new_object(): validates object +# new_object() / validates object Code foo("x") @@ -156,7 +156,7 @@ ! object is invalid: - x must be positive -# new_object(): runs each parent validator exactly once +# new_object() / runs each parent validator exactly once Code . <- A() @@ -171,7 +171,7 @@ Output A B C -# S7 object: displays nicely +# S7 object / displays nicely Code foo <- new_class("foo", properties = list(x = class_double, y = class_double), @@ -189,7 +189,7 @@ ..@ x: num(0) ..@ y: num(0) -# S7 object: displays objects with data nicely +# S7 object / displays objects with data nicely Code text <- new_class("text", class_character, package = NULL) @@ -202,7 +202,7 @@ List of 1 $ : chr "x" -# S7 object: displays list objects nicely +# S7 object / displays list objects nicely Code foo1(list(x = 1, y = list(a = 21, b = 22)), x = 3, y = list(a = 41, b = 42))