Skip to content

Commit 942624e

Browse files
committed
Cast before recycling in list_unchop()
Carefully retaining names without sacrificing performance to do so
1 parent 50f6a77 commit 942624e

File tree

3 files changed

+48
-9
lines changed

3 files changed

+48
-9
lines changed

NEWS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# vctrs (development version)
22

3+
* `list_unchop()` now works in an edge case with a single `NA` recycled to size 0 (#1989).
4+
35
* `list_unchop()` has gained new `size`, `default`, and `unmatched` arguments (#1982).
46

57
* Functions backed by a dictionary based implementation are often significantly faster, depending on the exact inputs used. This includes: `vec_match()`, `vec_in()`, `vec_group_loc()`, `vec_count()`, `vec_unique()`, and more (#1976).

src/c-unchop.c

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,9 @@ r_obj* list_unchop(r_obj* xs,
219219
r_keep_loc out_names_pi;
220220
KEEP_HERE(out_names, &out_names_pi);
221221

222+
r_keep_loc x_pi;
223+
KEEP_HERE(r_null, &x_pi);
224+
222225
r_ssize i = 0;
223226

224227
struct vctrs_arg* p_x_arg = new_subscript_arg(
@@ -243,17 +246,36 @@ r_obj* list_unchop(r_obj* xs,
243246
};
244247

245248
for (; i < xs_size; ++i) {
246-
r_obj* x = r_list_get(xs, i);
249+
r_obj* elt = r_list_get(xs, i);
247250

248-
if (x == r_null) {
251+
if (elt == r_null) {
249252
continue;
250253
}
251254

252255
r_obj* index = r_list_get(indices, i);
253256
const r_ssize index_size = r_length(index);
254257

258+
// Cast before recycling for maximal efficiency
259+
// (i.e. cast `1L` to `1` before recycling to size `N`)
260+
unchop_cast_opts.x = elt;
261+
r_obj* x = vec_cast_opts(&unchop_cast_opts);
262+
KEEP_AT(x, x_pi);
263+
264+
// FIXME: `vec_cast()` can currently drop names, so we carefully add them back
265+
// if it looks like casting dropped them. This should be removed eventually.
266+
// https://github.com/r-lib/vctrs/issues/623
267+
if (x != elt) {
268+
r_obj* names = KEEP(vec_names(elt));
269+
if (names != r_null && vec_names(x) == r_null) {
270+
x = vec_set_names(x, names);
271+
KEEP_AT(x, x_pi);
272+
}
273+
FREE(1);
274+
}
275+
255276
// Each element of `xs` is recycled to its corresponding index's size
256-
x = KEEP(vec_check_recycle(x, index_size, p_x_arg, error_call));
277+
x = vec_check_recycle(x, index_size, p_x_arg, error_call);
278+
KEEP_AT(x, x_pi);
257279

258280
if (assign_names) {
259281
r_obj* outer = xs_is_named ? r_chr_get(xs_names, i) : r_null;
@@ -274,14 +296,9 @@ r_obj* list_unchop(r_obj* xs,
274296
FREE(2);
275297
}
276298

277-
unchop_cast_opts.x = x;
278-
x = KEEP(vec_cast_opts(&unchop_cast_opts));
279-
280299
// Total ownership of `proxy` because it was freshly created with `vec_init()`
281300
proxy = vec_proxy_assign_opts(proxy, index, x, VCTRS_OWNED_true, &unchop_assign_opts);
282301
KEEP_AT(proxy, proxy_pi);
283-
284-
FREE(2);
285302
}
286303

287304
if (is_data_frame(proxy)) {
@@ -300,7 +317,7 @@ r_obj* list_unchop(r_obj* xs,
300317
out = vec_set_names(out, r_null);
301318
}
302319

303-
FREE(10);
320+
FREE(11);
304321
return out;
305322
}
306323

tests/testthat/test-slice-chop.R

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1345,6 +1345,26 @@ test_that("list_unchop() fails if foreign classes are not homogeneous and there
13451345
})
13461346
})
13471347

1348+
test_that("Size 1 unspecified `NA` that isn't used doesn't error", {
1349+
# Requires that casting happen before recycling, because the `NA` recycles
1350+
# to size zero since it isn't used, resulting in a logical rather than an
1351+
# unspecified (#1989).
1352+
expect_identical(
1353+
list_unchop(
1354+
list("x", NA),
1355+
indices = list(1L, integer())
1356+
),
1357+
"x"
1358+
)
1359+
expect_identical(
1360+
list_unchop(
1361+
list("x", NA),
1362+
indices = list(integer(), 1L)
1363+
),
1364+
NA_character_
1365+
)
1366+
})
1367+
13481368
test_that("list_unchop() `default` is inserted correctly", {
13491369
xs <- list("a", "b")
13501370
indices <- list(1, 3)

0 commit comments

Comments
 (0)