Skip to content

Commit b48649a

Browse files
setDT() works on S4 slots (again), and := works in under-allocated S4 slots (#6703)
* setDT() works on S4 slots * Tweak test so that it would fail on master * typo * NEWS for separately-fixed bug
1 parent ba5773d commit b48649a

File tree

6 files changed

+36
-8
lines changed

6 files changed

+36
-8
lines changed

NEWS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,8 @@ rowwiseDT(
123123

124124
17. `DT[order(...)]` better matches `base::order()` behavior by (1) recognizing the `method=` argument (and erroring since this is not supported) and (2) accepting a vector of `TRUE`/`FALSE` in `decreasing=` as an alternative to using `-a` to convey "sort `a` decreasing", [#4456](https://github.com/Rdatatable/data.table/issues/4456). Thanks @jangorecki for the FR and @MichaelChirico for the PR.
125125

126+
17. Assignment with `:=` to an S4 slot of an under-allocated data.table now works, [#6704](https://github.com/Rdatatable/data.table/issues/6704). Thanks @MichaelChirico for the report and fix.
127+
126128
## NOTES
127129

128130
1. There is a new vignette on joins! See `vignette("datatable-joins")`. Thanks to Angel Feliz for authoring it! Feedback welcome. This vignette has been highly requested since 2017: [#2181](https://github.com/Rdatatable/data.table/issues/2181).

R/data.table.R

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1221,7 +1221,7 @@ replace_dot_alias = function(e) {
12211221
setalloccol(x, n, verbose=verbose) # always assigns to calling scope; i.e. this scope
12221222
if (is.name(name)) {
12231223
assign(as.character(name),x,parent.frame(),inherits=TRUE)
1224-
} else if (name %iscall% c('$', '[[') && is.name(name[[2L]])) {
1224+
} else if (.is_simple_extraction(name)) { # TODO(#6702): use a helper here as the code is very similar to setDT().
12251225
k = eval(name[[2L]], parent.frame(), parent.frame())
12261226
if (is.list(k)) {
12271227
origj = j = if (name[[1L]] == "$") as.character(name[[3L]]) else eval(name[[3L]], parent.frame(), parent.frame())
@@ -1233,6 +1233,8 @@ replace_dot_alias = function(e) {
12331233
.Call(Csetlistelt,k,as.integer(j), x)
12341234
} else if (is.environment(k) && exists(as.character(name[[3L]]), k)) {
12351235
assign(as.character(name[[3L]]), x, k, inherits=FALSE)
1236+
} else if (isS4(k)) {
1237+
.Call(CsetS4elt, k, as.character(name[[3L]]), x)
12361238
}
12371239
} # TO DO: else if env$<- or list$<-
12381240
}
@@ -2967,7 +2969,7 @@ setDT = function(x, keep.rownames=FALSE, key=NULL, check.names=FALSE) {
29672969
if (is.name(name)) {
29682970
name = as.character(name)
29692971
assign(name, x, parent.frame(), inherits=TRUE)
2970-
} else if (name %iscall% c('$', '[[') && is.name(name[[2L]])) {
2972+
} else if (.is_simple_extraction(name)) {
29712973
# common case is call from 'lapply()'
29722974
k = eval(name[[2L]], parent.frame(), parent.frame())
29732975
if (is.list(k)) {
@@ -2979,9 +2981,11 @@ setDT = function(x, keep.rownames=FALSE, key=NULL, check.names=FALSE) {
29792981
stopf("Item '%s' not found in names of input list", origj)
29802982
}
29812983
}
2982-
.Call(Csetlistelt,k,as.integer(j), x)
2984+
.Call(Csetlistelt, k, as.integer(j), x)
29832985
} else if (is.environment(k) && exists(as.character(name[[3L]]), k)) {
29842986
assign(as.character(name[[3L]]), x, k, inherits=FALSE)
2987+
} else if (isS4(k)) {
2988+
.Call(CsetS4elt, k, as.character(name[[3L]]), x)
29852989
}
29862990
}
29872991
.Call(CexpandAltRep, x) # issue#2866 and PR#2882
@@ -3048,6 +3052,9 @@ rleidv = function(x, cols=seq_along(x), prefix=NULL) {
30483052
!is.call(e[[2L]]) && !is.call(e[[3L]]) # e.g. V1:V2, but not min(V1):max(V2) or 1:max(V2)
30493053
}
30503054

3055+
# for assignments like x[[1]][, a := 2] or setDT(x@DT)
3056+
.is_simple_extraction = function(e) e %iscall% c('$', '@', '[[') && is.name(e[[2L]])
3057+
30513058
# GForce functions
30523059
# to add a new function to GForce (from the R side -- the easy part!):
30533060
# (1) add it to gfuns

inst/tests/S4.Rraw

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,3 +109,12 @@ DT = data.table(a = rep(1:2, c(1, 100)))
109109
# Set the S4 bit on a simple object
110110
DT[, b := asS4(seq_len(.N))]
111111
test(6, DT[, b, by=a, verbose=TRUE][, isS4(b)], output="dogroups: growing")
112+
113+
# setDT() works for a data.frame slot, #6701
114+
setClass("DataFrame", slots=c(x="data.frame"))
115+
DF = new("DataFrame", x=data.frame(a=1))
116+
setDT(DF@x)
117+
test(7.1, is.data.table(DF@x))
118+
# Similar code for under-allocated data.tables in S4 slots, #6704
119+
setClass("DataTable", slots=c(x="data.table"))
120+
test(7.2, options=c(datatable.alloccol=0L), {DT = new("DataTable", x=data.table(a=1)); DT@x[, b := 2L]; DT@x$b}, 2L) # NB: requires assigning DT to test assignment back to that object

src/data.table.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,7 @@ SEXP freadR(SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SE
299299
SEXP fwriteR(SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP);
300300
SEXP rbindlist(SEXP, SEXP, SEXP, SEXP, SEXP);
301301
SEXP setlistelt(SEXP, SEXP, SEXP);
302+
SEXP setS4elt(SEXP, SEXP, SEXP);
302303
SEXP address(SEXP);
303304
SEXP expandAltRep(SEXP);
304305
SEXP fmelt(SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP);

src/init.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ R_CallMethodDef callMethods[] = {
7272
{"Crbindlist", (DL_FUNC) &rbindlist, -1},
7373
{"Cvecseq", (DL_FUNC) &vecseq, -1},
7474
{"Csetlistelt", (DL_FUNC) &setlistelt, -1},
75+
{"CsetS4elt", (DL_FUNC) &setS4elt, -1},
7576
{"Caddress", (DL_FUNC) &address, -1},
7677
{"CexpandAltRep", (DL_FUNC) &expandAltRep, -1},
7778
{"Cfmelt", (DL_FUNC) &fmelt, -1},

src/wrappers.c

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,18 +59,26 @@ SEXP copy(SEXP x)
5959
return(duplicate(x));
6060
}
6161

62+
// Internal use only. So that := can update elements of a list of data.table, #2204. Just needed to overallocate/grow the VECSXP.
6263
SEXP setlistelt(SEXP l, SEXP i, SEXP value)
6364
{
64-
R_len_t i2;
65-
// Internal use only. So that := can update elements of a list of data.table, #2204. Just needed to overallocate/grow the VECSXP.
66-
if (!isNewList(l)) error(_("First argument to setlistelt must be a list()"));
67-
if (!isInteger(i) || LENGTH(i)!=1) error(_("Second argument to setlistelt must a length 1 integer vector"));
68-
i2 = INTEGER(i)[0];
65+
if (!isNewList(l)) internal_error(__func__, "First argument to setlistelt must be a list()");
66+
if (!isInteger(i) || LENGTH(i)!=1) internal_error(__func__, "Second argument to setlistelt must a length 1 integer vector");
67+
R_len_t i2 = INTEGER(i)[0];
6968
if (LENGTH(l) < i2 || i2<1) error(_("i (%d) is outside the range of items [1,%d]"),i2,LENGTH(l));
7069
SET_VECTOR_ELT(l, i2-1, value);
7170
return(R_NilValue);
7271
}
7372

73+
// Internal use only. So that := can update elements of a slot of data.table, #6701.
74+
SEXP setS4elt(SEXP obj, SEXP name, SEXP value)
75+
{
76+
if (!isS4(obj)) internal_error(__func__, "First argument to setS4elt must be an S4 object");
77+
if (!isString(name) || LENGTH(name)!=1) internal_error(__func__, "Second argument to setS4elt must be a character string");
78+
R_do_slot_assign(obj, name, value);
79+
return(R_NilValue);
80+
}
81+
7482
SEXP address(SEXP x)
7583
{
7684
// A better way than : http://stackoverflow.com/a/10913296/403310

0 commit comments

Comments
 (0)