Skip to content

Commit 6dacb9a

Browse files
committed
Respect local closures in fill detection
1 parent 2756b9c commit 6dacb9a

2 files changed

Lines changed: 44 additions & 6 deletions

File tree

R/r2f-constructors.R

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,14 @@
88
# double(k), numeric(k). These lower to a single scalar literal carrying
99
# array dims, so splicing contexts must spread them explicitly.
1010
# Used by: c(), array()
11-
is_fill_constructor_call <- function(e) {
12-
is.call(e) &&
13-
is.symbol(e[[1L]]) &&
14-
as.character(e[[1L]]) %in% c("logical", "integer", "double", "numeric")
11+
is_fill_constructor_call <- function(e, scope) {
12+
if (!is.call(e) || !is.symbol(e[[1L]])) {
13+
return(FALSE)
14+
}
15+
name <- as.character(e[[1L]])
16+
name %in%
17+
c("logical", "integer", "double", "numeric") &&
18+
(is.null(scope) || !inherits(scope[[name]], LocalClosure))
1519
}
1620

1721
# Name of the call one frame above the current handler ("" at top level).
@@ -44,7 +48,7 @@ r2f_handlers[["c"]] <- function(args, scope = NULL, ...) {
4448
mode <- promoted$mode
4549
# Fill constructors are one scalar literal claiming length k; spread them
4650
# as implied-dos so the emitted element count matches the claimed length.
47-
fill_idx <- which(map_lgl(args, is_fill_constructor_call))
51+
fill_idx <- which(map_lgl(args, is_fill_constructor_call, scope = scope))
4852
if (length(fill_idx)) {
4953
spread_var <- NULL
5054
for (j in fill_idx) {
@@ -390,7 +394,7 @@ r2f_handlers[["array"]] <- function(args, scope = NULL, ..., hoist = NULL) {
390394
}
391395
shape <- glue("int([{dims_f}])")
392396

393-
is_fill_constructor <- is_fill_constructor_call(args$data)
397+
is_fill_constructor <- is_fill_constructor_call(args$data, scope)
394398

395399
axis_terms <- vapply(
396400
target_dims,

tests/testthat/test-recycling.R

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,40 @@ test_that("symbolic fill spreading preserves pointer-sized lengths", {
252252
expect_quick_identical(fn, list(c(2, 4, 6)))
253253
})
254254

255+
test_that("local closures can shadow fill constructors in c() and array()", {
256+
numeric_shadow <- function() {
257+
numeric <- function() c(1, 2)
258+
combined <- c(numeric(), 3)
259+
reshaped <- array(numeric(), dim = c(1L, 2L))
260+
list(combined = combined, reshaped = reshaped)
261+
}
262+
expect_quick_identical(numeric_shadow, list())
263+
264+
integer_shadow <- function() {
265+
integer <- function() c(1L, 2L)
266+
combined <- c(integer(), 3L)
267+
reshaped <- array(integer(), dim = c(1L, 2L))
268+
list(combined = combined, reshaped = reshaped)
269+
}
270+
expect_quick_identical(integer_shadow, list())
271+
272+
double_shadow <- function() {
273+
double <- function() c(1, 2)
274+
combined <- c(double(), 3)
275+
reshaped <- array(double(), dim = c(1L, 2L))
276+
list(combined = combined, reshaped = reshaped)
277+
}
278+
expect_quick_identical(double_shadow, list())
279+
280+
logical_shadow <- function() {
281+
logical <- function() c(1L, 2L)
282+
combined <- c(logical(), 3L)
283+
reshaped <- array(logical(), dim = c(1L, 2L))
284+
list(combined = combined, reshaped = reshaped)
285+
}
286+
expect_quick_identical(logical_shadow, list())
287+
})
288+
255289
test_that("fill constructors materialize where an array is required", {
256290
# A fill reaching c() through an expression is a real array, not a
257291
# scalar literal with claimed dims (which emitted one element where the

0 commit comments

Comments
 (0)