Skip to content

Commit 68874ec

Browse files
committed
Accept valid singleton and double-negated subscripts
1 parent 9b50725 commit 68874ec

3 files changed

Lines changed: 76 additions & 13 deletions

File tree

R/r2f-iterables-helpers.R

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -381,6 +381,10 @@ check_subscript_range_bounds <- function(info, from, to, by_f, hoist, scope) {
381381
from_lit <- lit(info$from)
382382
to_lit <- lit(info$to)
383383
by_lit <- if (is.null(info$by)) 1L else lit(info$by)
384+
same_endpoint <- identical(
385+
unwrap_parens(info$from),
386+
unwrap_parens(info$to)
387+
)
384388

385389
bounds_msg <- "index ranges in x[a:b] must have bounds >= 1"
386390
if (isTRUE(from_lit < 1L) || isTRUE(to_lit < 1L)) {
@@ -416,15 +420,15 @@ check_subscript_range_bounds <- function(info, from, to, by_f, hoist, scope) {
416420
emit(paste(checks, collapse = " .or. "), bounds_msg)
417421
}
418422

419-
# Explicit seq() step. The result length divides by the step, and that
420-
# length is evaluated in the C bridge *before* any Fortran guard can run
421-
# (a zero step would be a division-by-zero crash there), so a non-literal
422-
# step is a compile error, not a guard. With a literal step and symbolic
423-
# bounds, R errors when the step's sign opposes the direction -- the
424-
# emitted section would be zero-length while the claimed length is not;
425-
# that case is checkable at runtime. All-literal ranges were already
426-
# validated by seq_like_length_expr() at compile time.
427-
if (!is.null(info$by)) {
423+
# Explicit seq() step. When the endpoints differ, the result length divides
424+
# by the step, and that length is evaluated in the C bridge *before* any
425+
# Fortran guard can run (a zero step would be a division-by-zero crash
426+
# there), so a non-literal step is a compile error, not a guard. With a
427+
# literal step and symbolic bounds, R errors when the step's sign opposes
428+
# the direction -- the emitted section would be zero-length while the
429+
# claimed length is not; that case is checkable at runtime. All-literal
430+
# ranges were already validated by seq_like_length_expr() at compile time.
431+
if (!is.null(info$by) && !same_endpoint) {
428432
if (is.na(by_lit)) {
429433
stop(
430434
"seq() in x[...] requires a literal `by` step ",

R/r2f-subscript.R

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -232,9 +232,10 @@ r2f_handlers[["["]] <- function(
232232
# Reject non-positive subscripts at compile time. R's negative subscript
233233
# means exclusion, so the result's shape depends on the subscript's value --
234234
# not representable in quickr's static-shape model -- while the generated
235-
# Fortran would silently read out of bounds. Unary minus on a subscript is
236-
# unambiguously exclusion syntax in R, so the form is rejected, not just
237-
# statically-known values. Binary minus (x[n - 1]) is untouched.
235+
# Fortran would silently read out of bounds. After cancelling paired unary
236+
# minuses, unary minus on a subscript is exclusion syntax in R, so the form is
237+
# rejected, not just statically-known values. Binary minus (x[n - 1]) is
238+
# untouched.
238239
#
239240
# When the base's extent along the subscript's axis is statically known,
240241
# literal values beyond it are also compile errors: R pads out-of-range
@@ -246,6 +247,23 @@ r2f_handlers[["["]] <- function(
246247
# check_subscript_range_bounds().
247248
check_subscript_expr <- function(e, extent = NULL) {
248249
e <- unwrap_parens(e)
250+
while (is_call(e, quote(`-`)) && length(e) == 2L) {
251+
inner <- unwrap_parens(e[[2L]])
252+
if (
253+
is.numeric(inner) &&
254+
length(inner) == 1L &&
255+
!is.na(inner) &&
256+
is.finite(inner) &&
257+
inner < 0
258+
) {
259+
e <- -inner
260+
next
261+
}
262+
if (!is_call(inner, quote(`-`)) || length(inner) != 2L) {
263+
break
264+
}
265+
e <- unwrap_parens(inner[[2L]])
266+
}
249267
if (!is_wholenumber(extent)) {
250268
extent <- NULL
251269
}

tests/testthat/test-subscript-validation.R

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,15 @@ test_that("negative and zero subscripts are rejected at compile time", {
4141
expect_error(quick(fnm), "negative subscripts|subscripts must be positive")
4242
})
4343

44+
test_that("double negation remains a positive subscript", {
45+
fn <- function(x) {
46+
declare(type(x = double(4)))
47+
x[-(-1L)]
48+
}
49+
50+
expect_quick_identical(fn, list(as.double(1:4)))
51+
})
52+
4453
test_that("literal subscripts beyond a known extent are compile errors", {
4554
# R pads out-of-range reads with NA; quickr refuses at compile time
4655
fn <- function(x) {
@@ -195,7 +204,7 @@ test_that("literal in-range bounds emit no guard; bad literals error at compile
195204
expect_error(quick(fbad), "bounds >= 1")
196205
})
197206

198-
test_that("x[seq(a, b, by)] requires a literal step and guards wrong signs", {
207+
test_that("non-singleton x[seq(a, b, by)] validates the step", {
199208
fdesc <- function(x) {
200209
declare(type(x = double(5)))
201210
x[seq(5L, 1L, by = -1L)]
@@ -220,6 +229,38 @@ test_that("x[seq(a, b, by)] requires a literal step and guards wrong signs", {
220229
expect_error(qfs(as.double(1:9), 2L), "wrong sign")
221230
})
222231

232+
test_that("a singleton seq() subscript accepts a symbolic step", {
233+
fn <- function(x, n, k) {
234+
declare(
235+
type(x = double(5)),
236+
type(n = integer(1)),
237+
type(k = integer(1))
238+
)
239+
x[seq(n, n, by = k)]
240+
}
241+
242+
expect_quick_identical(
243+
fn,
244+
list(as.double(1:5), 3L, 0L),
245+
list(as.double(5:1), 3L, -2L)
246+
)
247+
248+
fparen <- function(x, n, k) {
249+
declare(
250+
type(x = double(5)),
251+
type(n = integer(1)),
252+
type(k = integer(1))
253+
)
254+
x[seq(n, (n), by = k)]
255+
}
256+
257+
expect_quick_identical(
258+
fparen,
259+
list(as.double(1:5), 3L, 0L),
260+
list(as.double(5:1), 3L, -2L)
261+
)
262+
})
263+
223264
test_that("seq() value with a symbolic by is sized by the step", {
224265
fn <- function(k) {
225266
declare(type(k = integer(1)))

0 commit comments

Comments
 (0)