diff --git a/NEWS.md b/NEWS.md index e10b9348f..e5c3adbf2 100644 --- a/NEWS.md +++ b/NEWS.md @@ -28,7 +28,9 @@ 4. `sum()` by group is correct with missing entries and GForce activated ([#7571](https://github.com/Rdatatable/data.table/issues/7571)). Thanks to @rweberc for the report and @manmita for the fix. The issue was caused by a faulty early `break` that spilled between groups, and resulted in silently incorrect results! -5. `fread(text=)` could segfault when reading text input ending with a `\x1a` (ASCII SUB) character after a long line, [#7407](https://github.com/Rdatatable/data.table/issues/7407) which is solved by adding check for eof. Thanks @aitap for the report and @manmita for the fix. +5. `fread(text=)` could segfault when reading text input ending with a `\x1a` (ASCII SUB) character after a long line, [#7407](https://github.com/Rdatatable/data.table/issues/7407) which is solved by adding check for eof. Thanks @aitap for the report and @manmita for the fix. + +6. `rowwiseDT()` now provides a helpful error message when a complex object that is not a list (e.g., a function) is provided as a cell value, instructing the user to wrap it in `list()`, [#7219](https://github.com/Rdatatable/data.table/issues/7219). Thanks @kylebutts for the report and @venom1204 for the fix. ### Notes diff --git a/R/rowwiseDT.R b/R/rowwiseDT.R index 81114ead6..1451e5849 100644 --- a/R/rowwiseDT.R +++ b/R/rowwiseDT.R @@ -13,6 +13,14 @@ rowwiseDT = function(...) { nrows = length(body) %/% ncols if (length(body) != nrows * ncols) stopf("There are %d columns but the number of cells is %d, which is not an integer multiple of the columns", ncols, length(body)) + is_problematic = vapply_1b(body, function(v) !(is.atomic(v) || is.null(v) || typeof(v) == "list")) + if (any(is_problematic)) { + idx = which(is_problematic)[1L] + col_idx = (idx - 1L) %% ncols + 1L + col_name = header[col_idx] + obj_type = class1(body[[idx]]) + stopf("Column '%s' is type '%s'. Non-atomic, non-list objects must be wrapped in list(), e.g., list(f) instead of f", col_name, obj_type) + } # make all the non-scalar elements to a list needs_list = lengths(body) != 1L body[needs_list] = lapply(body[needs_list], list) diff --git a/inst/tests/tests.Rraw b/inst/tests/tests.Rraw index 1e2b985a4..46ff6063b 100644 --- a/inst/tests/tests.Rraw +++ b/inst/tests/tests.Rraw @@ -22035,3 +22035,16 @@ if (test_bit64) local({ # 7407 Test for fread() handling \x1A (ASCII SUB) at end of input txt = paste0("foo\n", strrep("a", 4096 * 100), "\x1A") test(2359.1, nchar(fread(txt)$foo), 409600L) + +# rowwiseDT() valid and invalid handling of complex objects #7219 +test(2360.1, rowwiseDT(x =, y =, 1, 2, 3, 4), data.table(x = c(1, 3), y = c(2, 4))) +test(2360.2, rowwiseDT(x =, func =, + 1, list(\(x) x + 1), + 2, list(function(z) z * 2)), + data.table(x = c(1, 2), func = list(\(x) x + 1, function(z) z * 2))) +test(2360.3, rowwiseDT(x =, func =, 1, \(x) x + 1), + error = "Column 'func' is type 'function'. Non-atomic, non-list objects must be wrapped in list\\(\\)") +test(2360.4, rowwiseDT(x =, expr =, 1, quote(a + b)), + error = "Column 'expr' is type 'call'. Non-atomic, non-list objects must be wrapped in list\\(\\)") +test(2360.5, rowwiseDT(x =, plist =, 1, as.pairlist(list(123))), + error = "Column 'plist' is type 'pairlist'. Non-atomic, non-list objects must be wrapped in list\\(\\)")