Skip to content

Commit b9f6cb8

Browse files
committed
feat: perrmit (most) reserved attributes names as property names
1 parent bd70ec3 commit b9f6cb8

File tree

6 files changed

+225
-99
lines changed

6 files changed

+225
-99
lines changed

R/class.R

Lines changed: 74 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -95,14 +95,14 @@
9595
#' r <- Range(start = 10, end = 20)
9696
#' try(r@start <- 25)
9797
new_class <- function(
98-
name,
99-
parent = S7_object,
100-
package = topNamespaceName(parent.frame()),
101-
properties = list(),
102-
abstract = FALSE,
103-
constructor = NULL,
104-
validator = NULL) {
105-
98+
name,
99+
parent = S7_object,
100+
package = topNamespaceName(parent.frame()),
101+
properties = list(),
102+
abstract = FALSE,
103+
constructor = NULL,
104+
validator = NULL
105+
) {
106106
check_name(name)
107107

108108
parent <- as_class(parent)
@@ -119,7 +119,10 @@ new_class <- function(
119119
if (!is.null(validator)) {
120120
check_function(validator, alist(self = ))
121121
}
122-
if (abstract && (!is_class(parent) || !(parent@abstract || parent@name == "S7_object"))) {
122+
if (
123+
abstract &&
124+
(!is_class(parent) || !(parent@abstract || parent@name == "S7_object"))
125+
) {
123126
stop("Abstract classes must have abstract parents")
124127
}
125128
}
@@ -128,12 +131,16 @@ new_class <- function(
128131
all_props <- attr(parent, "properties", exact = TRUE) %||% list()
129132
new_props <- as_properties(properties)
130133
check_prop_names(new_props)
134+
131135
all_props[names(new_props)] <- new_props
132136

133137
if (is.null(constructor)) {
134-
constructor <- new_constructor(parent, all_props,
135-
envir = parent.frame(),
136-
package = package)
138+
constructor <- new_constructor(
139+
parent,
140+
all_props,
141+
envir = parent.frame(),
142+
package = package
143+
)
137144
}
138145

139146
object <- constructor
@@ -150,7 +157,15 @@ new_class <- function(
150157
global_variables(names(all_props))
151158
object
152159
}
153-
globalVariables(c("name", "parent", "package", "properties", "abstract", "constructor", "validator"))
160+
globalVariables(c(
161+
"name",
162+
"parent",
163+
"package",
164+
"properties",
165+
"abstract",
166+
"constructor",
167+
"validator"
168+
))
154169

155170
#' @rawNamespace if (getRversion() >= "4.3.0") S3method(nameOfClass, S7_class, S7_class_name)
156171
S7_class_name <- function(x) {
@@ -203,7 +218,12 @@ print.S7_class <- function(x, ...) {
203218
#' @export
204219
str.S7_class <- function(object, ..., nest.lev = 0) {
205220
cat(if (nest.lev > 0) " ")
206-
cat("<", paste0(class_dispatch(object), collapse = "/"), "> constructor", sep = "")
221+
cat(
222+
"<",
223+
paste0(class_dispatch(object), collapse = "/"),
224+
"> constructor",
225+
sep = ""
226+
)
207227
cat("\n")
208228

209229
if (nest.lev == 0) {
@@ -248,7 +268,10 @@ new_object <- function(.parent, ...) {
248268
stop("`new_object()` must be called from within a constructor")
249269
}
250270
if (class@abstract) {
251-
msg <- sprintf("Can't construct an object from abstract class <%s>", class@name)
271+
msg <- sprintf(
272+
"Can't construct an object from abstract class <%s>",
273+
class@name
274+
)
252275
stop(msg)
253276
}
254277

@@ -265,20 +288,22 @@ new_object <- function(.parent, ...) {
265288

266289
attrs <- c(
267290
list(class = class_dispatch(class), S7_class = class),
268-
args[!has_setter],
291+
with_remap_reserved_names(args[!has_setter]),
269292
attributes(object)
270293
)
271294
attrs <- attrs[!duplicated(names(attrs))]
272295
attributes(object) <- attrs
273296

274297
# invoke custom property setters
275298
prop_setter_vals <- args[has_setter]
276-
for (name in names(prop_setter_vals))
299+
for (name in names(prop_setter_vals)) {
277300
prop(object, name, check = FALSE) <- prop_setter_vals[[name]]
301+
}
278302

279303
# Don't need to validate if parent class already validated,
280304
# i.e. it's a non-abstract S7 class
281-
parent_validated <- inherits(class@parent, "S7_object") && !class@parent@abstract
305+
parent_validated <- inherits(class@parent, "S7_object") &&
306+
!class@parent@abstract
282307
validate(object, recursive = !parent_validated)
283308

284309
object
@@ -295,8 +320,9 @@ str.S7_object <- function(object, ..., nest.lev = 0) {
295320
cat(obj_desc(object))
296321

297322
if (!is_S7_type(object)) {
298-
if (!typeof(object) %in% c("numeric", "integer", "character", "double"))
323+
if (!typeof(object) %in% c("numeric", "integer", "character", "double")) {
299324
cat(" ")
325+
}
300326

301327
attrs <- attributes(object)
302328
if (is.environment(object)) {
@@ -328,15 +354,38 @@ S7_class <- function(object) {
328354
attr(object, "S7_class", exact = TRUE)
329355
}
330356

331-
332357
check_prop_names <- function(properties, error_call = sys.call(-1L)) {
333-
# these attributes have special C handlers in base R
334-
forbidden <- c("names", "dim", "dimnames", "class",
335-
"tsp", "comment", "row.names", "...")
358+
forbidden <- c("...")
336359
forbidden <- intersect(forbidden, names(properties))
337360
if (length(forbidden)) {
338-
msg <- paste0("property can't be named: ",
339-
paste0(forbidden, collapse = ", "))
361+
msg <- paste0(
362+
"property can't be named: ",
363+
paste0("'", forbidden, "'", collapse = ", ")
364+
)
340365
stop(simpleError(msg, error_call))
341366
}
342367
}
368+
369+
remap_reserved_names <- function(names) {
370+
# these attributes have special C handlers in base R
371+
forbidden <- c(
372+
"names",
373+
"dim",
374+
"dimnames",
375+
"class",
376+
"tsp",
377+
"comment",
378+
"row.names",
379+
"..."
380+
)
381+
382+
is_forbidden <- names %in% forbidden
383+
names[is_forbidden] <- paste0(".__S7_prop__", names[is_forbidden], "__")
384+
385+
names
386+
}
387+
388+
with_remap_reserved_names <- function(x) {
389+
names(x) <- remap_reserved_names(names(x))
390+
x
391+
}

R/property.R

Lines changed: 40 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
#' function promise in the default constructor, evaluated at the time the
4141
#' object is constructed.
4242
#' @param name Property name, primarily used for error messages. Generally
43-
#' don't need to set this here, as it's more convenient to supply as
43+
#' don't need to set this here, as it's more convenient to supply as
4444
#' the element name when defining a list of properties. If both `name`
4545
#' and a list-name are supplied, the list-name will be used.
4646
#' @returns An S7 property, i.e. a list with class `S7_property`.
@@ -72,12 +72,14 @@
7272
#' # argument to the default constructor
7373
#' try(Clock(now = 10))
7474
#' args(Clock)
75-
new_property <- function(class = class_any,
76-
getter = NULL,
77-
setter = NULL,
78-
validator = NULL,
79-
default = NULL,
80-
name = NULL) {
75+
new_property <- function(
76+
class = class_any,
77+
getter = NULL,
78+
setter = NULL,
79+
validator = NULL,
80+
default = NULL,
81+
name = NULL
82+
) {
8183
class <- as_class(class)
8284
check_prop_default(default, class)
8385

@@ -119,7 +121,7 @@ check_prop_default <- function(default, class, error_call = sys.call(-1)) {
119121
# The meaning of a `...` prop default needs discussion
120122
stop(simpleError("`default` cannot be `...`", error_call))
121123
}
122-
if (identical(default, quote(expr =))) {
124+
if (identical(default, quote(expr = ))) {
123125
# The meaning of a missing prop default needs discussion
124126
stop(simpleError("`default` cannot be missing", error_call))
125127
}
@@ -128,11 +130,15 @@ check_prop_default <- function(default, class, error_call = sys.call(-1)) {
128130
return()
129131
}
130132

131-
if (class_inherits(default, class))
133+
if (class_inherits(default, class)) {
132134
return()
135+
}
133136

134-
msg <- sprintf("`default` must be an instance of %s, not a %s",
135-
class_desc(class), obj_desc(default))
137+
msg <- sprintf(
138+
"`default` must be an instance of %s, not a %s",
139+
class_desc(class),
140+
obj_desc(default)
141+
)
136142

137143
stop(simpleError(msg, error_call))
138144
}
@@ -188,7 +194,8 @@ prop_default <- function(prop, envir, package) {
188194
#' lexington@height <- 14
189195
#' prop(lexington, "height") <- 15
190196
prop <- function(object, name) {
191-
.Call(prop_, object, name)
197+
attr_name <- remap_reserved_names(name)
198+
.Call(prop_, object, name, attr_name)
192199
}
193200

194201
propr <- function(object, name) {
@@ -229,11 +236,12 @@ prop_obj <- function(object, name) {
229236
#' [validate()] on the object before returning.
230237
#' @export
231238
`prop<-` <- function(object, name, check = TRUE, value) {
232-
.Call(prop_set_, object, name, check, value)
239+
attr_name <- remap_reserved_names(name)
240+
.Call(prop_set_, object, name, attr_name, check, value)
233241
}
234242

235243
`propr<-` <- local({
236-
# reference implementation of `prop<-()` implemented in R
244+
# reference implementation of `prop<-()` implemented in R
237245
# This flag is used to avoid infinite loops if you are assigning a property from a setter function
238246
setter_property <- NULL
239247

@@ -246,7 +254,11 @@ prop_obj <- function(object, name) {
246254
}
247255

248256
if (!is.null(prop$getter) && is.null(prop$setter)) {
249-
msg <- sprintf("Can't set read-only property %s@%s", obj_desc(object), name)
257+
msg <- sprintf(
258+
"Can't set read-only property %s@%s",
259+
obj_desc(object),
260+
name
261+
)
250262
stop(msg, call. = FALSE)
251263
}
252264

@@ -293,7 +305,8 @@ prop_error_unknown <- function(object, prop_name) {
293305
# called from src/prop.c
294306
prop_validate <- function(prop, value, object = NULL) {
295307
if (!class_inherits(value, prop$class)) {
296-
return(sprintf("%s must be %s, not %s",
308+
return(sprintf(
309+
"%s must be %s, not %s",
297310
prop_label(object, prop$name),
298311
class_desc(prop$class),
299312
obj_desc(value)
@@ -319,7 +332,8 @@ prop_validate <- function(prop, value, object = NULL) {
319332

320333
stop(sprintf(
321334
"%s validator must return NULL or a character, not <%s>.",
322-
prop_label(object, prop$name), typeof(val)
335+
prop_label(object, prop$name),
336+
typeof(val)
323337
))
324338
}
325339

@@ -363,7 +377,15 @@ prop_names <- function(object) {
363377

364378
if (inherits(object, "S7_class")) {
365379
# S7_class isn't a S7_class (somewhat obviously) so we fake the property names
366-
c("name", "parent", "package", "properties", "abstract", "constructor", "validator")
380+
c(
381+
"name",
382+
"parent",
383+
"package",
384+
"properties",
385+
"abstract",
386+
"constructor",
387+
"validator"
388+
)
367389
} else {
368390
class <- S7_class(object)
369391
props <- attr(class, "properties", exact = TRUE)
@@ -473,7 +495,6 @@ as_properties <- function(x) {
473495
}
474496

475497
as_property <- function(x, name, i) {
476-
477498
if (is_property(x)) {
478499
if (name == "") {
479500
if (is.null(x$name)) {
@@ -502,4 +523,3 @@ prop_is_read_only <- function(prop) {
502523
prop_has_setter <- function(prop) is.function(prop$setter)
503524

504525
prop_is_dynamic <- function(prop) is.function(prop$getter)
505-

src/init.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,16 @@ extern SEXP method_call_(SEXP, SEXP, SEXP, SEXP);
99
extern SEXP test_call_(SEXP, SEXP, SEXP, SEXP);
1010
extern SEXP S7_class_(SEXP, SEXP);
1111
extern SEXP S7_object_(void);
12-
extern SEXP prop_(SEXP, SEXP);
13-
extern SEXP prop_set_(SEXP, SEXP, SEXP, SEXP);
12+
extern SEXP prop_(SEXP, SEXP, SEXP);
13+
extern SEXP prop_set_(SEXP, SEXP, SEXP, SEXP, SEXP);
1414

1515
#define CALLDEF(name, n) {#name, (DL_FUNC) &name, n}
1616

1717
static const R_CallMethodDef CallEntries[] = {
1818
CALLDEF(method_, 4),
1919
CALLDEF(S7_object_, 0),
20-
CALLDEF(prop_, 2),
21-
CALLDEF(prop_set_, 4),
20+
CALLDEF(prop_, 3),
21+
CALLDEF(prop_set_, 5),
2222
{NULL, NULL, 0}
2323
};
2424

0 commit comments

Comments
 (0)