Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

setDT() works on S4 slots (again), and := works in under-allocated S4 slots #6703

Merged
merged 4 commits into from
Jan 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ rowwiseDT(

16. Joins of `integer64` and `double` columns succeed when the `double` column has lossless `integer64` representation, [#4167](https://github.com/Rdatatable/data.table/issues/4167) and [#6625](https://github.com/Rdatatable/data.table/issues/6625). Previously, this only worked when the double column had lossless _32-bit_ integer representation. Thanks @MichaelChirico for the reports and fix.

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.

## NOTES

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).
Expand Down
13 changes: 10 additions & 3 deletions R/data.table.R
Original file line number Diff line number Diff line change
Expand Up @@ -1221,7 +1221,7 @@ replace_dot_alias = function(e) {
setalloccol(x, n, verbose=verbose) # always assigns to calling scope; i.e. this scope
if (is.name(name)) {
assign(as.character(name),x,parent.frame(),inherits=TRUE)
} else if (name %iscall% c('$', '[[') && is.name(name[[2L]])) {
} else if (.is_simple_extraction(name)) { # TODO(#6702): use a helper here as the code is very similar to setDT().
k = eval(name[[2L]], parent.frame(), parent.frame())
if (is.list(k)) {
origj = j = if (name[[1L]] == "$") as.character(name[[3L]]) else eval(name[[3L]], parent.frame(), parent.frame())
Expand All @@ -1233,6 +1233,8 @@ replace_dot_alias = function(e) {
.Call(Csetlistelt,k,as.integer(j), x)
} else if (is.environment(k) && exists(as.character(name[[3L]]), k)) {
assign(as.character(name[[3L]]), x, k, inherits=FALSE)
} else if (isS4(k)) {
.Call(CsetS4elt, k, as.character(name[[3L]]), x)
}
} # TO DO: else if env$<- or list$<-
}
Expand Down Expand Up @@ -2967,7 +2969,7 @@ setDT = function(x, keep.rownames=FALSE, key=NULL, check.names=FALSE) {
if (is.name(name)) {
name = as.character(name)
assign(name, x, parent.frame(), inherits=TRUE)
} else if (name %iscall% c('$', '[[') && is.name(name[[2L]])) {
} else if (.is_simple_extraction(name)) {
# common case is call from 'lapply()'
k = eval(name[[2L]], parent.frame(), parent.frame())
if (is.list(k)) {
Expand All @@ -2979,9 +2981,11 @@ setDT = function(x, keep.rownames=FALSE, key=NULL, check.names=FALSE) {
stopf("Item '%s' not found in names of input list", origj)
}
}
.Call(Csetlistelt,k,as.integer(j), x)
.Call(Csetlistelt, k, as.integer(j), x)
} else if (is.environment(k) && exists(as.character(name[[3L]]), k)) {
assign(as.character(name[[3L]]), x, k, inherits=FALSE)
} else if (isS4(k)) {
.Call(CsetS4elt, k, as.character(name[[3L]]), x)
}
}
.Call(CexpandAltRep, x) # issue#2866 and PR#2882
Expand Down Expand Up @@ -3048,6 +3052,9 @@ rleidv = function(x, cols=seq_along(x), prefix=NULL) {
is.name(e[[1L]]) && is.name(e[[2L]]) # e.g. V1:V2, but not min(V1):max(V2) or 1:max(V2)
}

# for assignments like x[[1]][, a := 2] or setDT(x@DT)
.is_simple_extraction = function(e) e %iscall% c('$', '@', '[[') && is.name(e[[2L]])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

great thanks for adding this helper fun


# GForce functions
# to add a new function to GForce (from the R side -- the easy part!):
# (1) add it to gfuns
Expand Down
9 changes: 9 additions & 0 deletions inst/tests/S4.Rraw
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,12 @@ DT = data.table(a = rep(1:2, c(1, 100)))
# Set the S4 bit on a simple object
DT[, b := asS4(seq_len(.N))]
test(6, DT[, b, by=a, verbose=TRUE][, isS4(b)], output="dogroups: growing")

# setDT() works for a data.frame slot, #6701
setClass("DataFrame", slots=c(x="data.frame"))
DF = new("DataFrame", x=data.frame(a=1))
setDT(DF@x)
test(7.1, is.data.table(DF@x))
# Similar code for under-allocated data.tables in S4 slots, #6704
setClass("DataTable", slots=c(x="data.table"))
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
1 change: 1 addition & 0 deletions src/data.table.h
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,7 @@ SEXP freadR(SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SE
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);
SEXP rbindlist(SEXP, SEXP, SEXP, SEXP, SEXP);
SEXP setlistelt(SEXP, SEXP, SEXP);
SEXP setS4elt(SEXP, SEXP, SEXP);
SEXP address(SEXP);
SEXP expandAltRep(SEXP);
SEXP fmelt(SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP, SEXP);
Expand Down
1 change: 1 addition & 0 deletions src/init.c
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ R_CallMethodDef callMethods[] = {
{"Crbindlist", (DL_FUNC) &rbindlist, -1},
{"Cvecseq", (DL_FUNC) &vecseq, -1},
{"Csetlistelt", (DL_FUNC) &setlistelt, -1},
{"CsetS4elt", (DL_FUNC) &setS4elt, -1},
{"Caddress", (DL_FUNC) &address, -1},
{"CexpandAltRep", (DL_FUNC) &expandAltRep, -1},
{"Cfmelt", (DL_FUNC) &fmelt, -1},
Expand Down
18 changes: 13 additions & 5 deletions src/wrappers.c
Original file line number Diff line number Diff line change
Expand Up @@ -59,18 +59,26 @@ SEXP copy(SEXP x)
return(duplicate(x));
}

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

// Internal use only. So that := can update elements of a slot of data.table, #6701.
SEXP setS4elt(SEXP obj, SEXP name, SEXP value)
{
if (!isS4(obj)) internal_error(__func__, "First argument to setS4elt must be an S4 object");
if (!isString(name) || LENGTH(name)!=1) internal_error(__func__, "Second argument to setS4elt must be a character string");
R_do_slot_assign(obj, name, value);
return(R_NilValue);
}

SEXP address(SEXP x)
{
// A better way than : http://stackoverflow.com/a/10913296/403310
Expand Down
Loading