Skip to content

Commit 9682f05

Browse files
committed
enable the sugar length checks in development versions only
Twelve CRAN packages rely on a longer sugar operand being cut short, so an unconditional error would block the release until all of them update. Derive RCPP_SUGAR_LENGTH_CHECKS from the version in config.h: on when the fourth component is non-zero (development snapshots, which is what reverse-dependency checks run against) and off in releases. Defining the macro before including Rcpp.h overrides the default. The tests check for the errors only when enabled, and for the truncation otherwise.
1 parent 2e6ab0b commit 9682f05

7 files changed

Lines changed: 94 additions & 26 deletions

File tree

‎ChangeLog‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,16 @@
1+
2026-10-09 Kevin Ushey <kevinushey@gmail.com>
2+
3+
* inst/include/Rcpp/config.h: Define RCPP_SUGAR_LENGTH_CHECKS, on for
4+
development versions and off for releases unless overridden
5+
* inst/include/Rcpp/sugar/tools/check_sizes.h: Compile the length checks
6+
out when RCPP_SUGAR_LENGTH_CHECKS is 0
7+
* inst/tinytest/cpp/sugar_expressions.cpp: Report whether the checks are
8+
enabled
9+
* inst/tinytest/test_sugar.R: Test the length errors only when enabled,
10+
and the truncation otherwise
11+
* vignettes/rmd/Rcpp-sugar.Rmd: Document when the lengths are checked
12+
* inst/NEWS.Rd: Idem
13+
114
2026-10-09 Kevin Ushey <kevinushey@gmail.com>
215

316
* inst/include/Rcpp/sugar/functions/lapply.h: Index the operand through

‎inst/NEWS.Rd‎

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,15 @@
2828
\item The \code{col} constructor now uses \code{nrow} in an
2929
initialization (Iñaki in \ghpr{1509})
3030
\item Sugar expressions hold nested expressions by value so they can be
31-
stored, evaluate expressions that may read the target before assigning
32-
them in place, and signal an error for operands of different lengths
33-
(Kevin in \ghpr{1513} closing \ghit{1512}). Same-length assignment
34-
of an expression that is not elementwise, such as \code{x = rev(x)},
35-
now goes through a temporary vector.
31+
stored, and evaluate expressions that may read the target before
32+
assigning them in place (Kevin in \ghpr{1513} closing \ghit{1512}).
33+
Same-length assignment of an expression that is not elementwise, such
34+
as \code{x = rev(x)}, now goes through a temporary vector.
35+
\item Development versions check that sugar operands, and vectors
36+
assigned into a range, row or column, have matching lengths, and signal
37+
an error otherwise; releases do not check, and the define
38+
\code{RCPP_SUGAR_LENGTH_CHECKS} overrides the default (Kevin in
39+
\ghpr{1513})
3640
\item Assignment between overlapping subsets of the same vector, as in
3741
\code{x[i] = x[j]}, reads the source elements before writing (Kevin in
3842
\ghpr{1513})

‎inst/include/Rcpp/config.h‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,18 @@
3333
#define RCPP_DEV_VERSION RcppDevVersion(1,1,2,4)
3434
#define RCPP_DEV_VERSION_STRING "1.1.2.4"
3535

36+
// Whether sugar checks that the operands of an expression, and a vector
37+
// assigned into a range, row or column, have matching lengths. On by default
38+
// in development versions (a non-zero fourth version component), so that
39+
// reverse-dependency checks catch mismatches; off in releases, so that
40+
// packages on CRAN are not affected. Define RCPP_SUGAR_LENGTH_CHECKS as 1 or
41+
// 0 before including Rcpp.h to override.
42+
#ifndef RCPP_SUGAR_LENGTH_CHECKS
43+
# if (RCPP_DEV_VERSION % 100) != 0
44+
# define RCPP_SUGAR_LENGTH_CHECKS 1
45+
# else
46+
# define RCPP_SUGAR_LENGTH_CHECKS 0
47+
# endif
48+
#endif
49+
3650
#endif

‎inst/include/Rcpp/sugar/tools/check_sizes.h‎

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,13 @@
2222
#ifndef Rcpp__sugar__tools__check_sizes_h
2323
#define Rcpp__sugar__tools__check_sizes_h
2424

25+
#include <Rcpp/config.h>
26+
2527
namespace Rcpp{
2628
namespace sugar{
2729

30+
#if RCPP_SUGAR_LENGTH_CHECKS
31+
2832
// Kept out of line: inlining the error path keeps the compiler from
2933
// optimizing the loops that follow these checks.
3034
#if defined(__GNUC__)
@@ -41,18 +45,28 @@ namespace sugar{
4145
stop_sizes("sugar operands have different lengths (%d and %d)", n1, n2);
4246
}
4347

44-
inline void check_sizes(R_xlen_t n1, R_xlen_t n2, R_xlen_t n3) {
45-
check_sizes(n1, n2);
46-
check_sizes(n1, n3);
47-
}
48-
4948
// Likewise, a vector assigned into a range, row or column must have the
5049
// same length as that target.
5150
inline void check_assign_size(R_xlen_t target, R_xlen_t value) {
5251
if (target != value)
5352
stop_sizes("cannot assign a vector of length %d to a target of length %d", value, target);
5453
}
5554

55+
#else
56+
57+
// Checks disabled (the default in released versions, see config.h): the
58+
// result is sized by the first operand, or by the target, as it always
59+
// was, and a longer operand is cut short.
60+
inline void check_sizes(R_xlen_t, R_xlen_t) {}
61+
inline void check_assign_size(R_xlen_t, R_xlen_t) {}
62+
63+
#endif
64+
65+
inline void check_sizes(R_xlen_t n1, R_xlen_t n2, R_xlen_t n3) {
66+
check_sizes(n1, n2);
67+
check_sizes(n1, n3);
68+
}
69+
5670
} // sugar
5771
} // Rcpp
5872

‎inst/tinytest/cpp/sugar_expressions.cpp‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,11 @@ LogicalVector elementwise_flags(NumericVector x, NumericMatrix m) {
257257
);
258258
}
259259

260+
// [[Rcpp::export]]
261+
bool sugar_length_checks_enabled() {
262+
return RCPP_SUGAR_LENGTH_CHECKS != 0;
263+
}
264+
260265
// [[Rcpp::export]]
261266
NumericVector length_plus(NumericVector x, NumericVector y) {
262267
return x + y;

‎inst/tinytest/test_sugar.R‎

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1775,19 +1775,29 @@ expect_equal(
17751775
expect_equal(length_plus(c(1, 2), c(10, 20)), c(11, 22), info = "x + y, same length")
17761776
expect_equal(length_plus(numeric(), numeric()), numeric(), info = "x + y, both empty")
17771777
expect_equal(length_plus_scalar(c(1, 2)), c(2, 3), info = "x + scalar")
1778-
expect_error(length_plus(c(1, 2, 3, 4, 5), c(10, 20)), "different lengths", info = "x + <shorter y>")
1779-
expect_error(length_plus(c(1, 2), c(10, 20, 30, 40)), "different lengths", info = "x + <longer y>")
1780-
expect_error(length_compare(c(1, 2, 3), c(1, 2)), "different lengths", info = "x < y")
1781-
expect_error(length_and(c(TRUE, FALSE), TRUE), "different lengths", info = "x & y")
1782-
expect_error(length_pmax(c(1, 2, 3), c(1, 2)), "different lengths", info = "pmax(x, y)")
1783-
expect_error(length_ifelse(c(TRUE, FALSE), c(1, 2), c(1, 2, 3)), "different lengths", info = "ifelse(c, x, y)")
1784-
17851778
expect_equal(length_range(c(1, 2, 3, 4, 5), c(7, 8, 9)), c(7, 8, 9, 4, 5), info = "x[0:2] = y")
1786-
expect_error(length_range(c(1, 2, 3, 4, 5), c(7, 8)), "cannot assign", info = "x[0:2] = <shorter y>")
1787-
expect_error(length_range(c(1, 2, 3, 4, 5), c(6, 7, 8, 9)), "cannot assign", info = "x[0:2] = <longer y>")
1788-
expect_error(length_range_range(c(1, 2, 3, 4, 5)), "cannot assign", info = "x[0:1] = x[0:3]")
1789-
expect_error(length_column(matrix(1, 3, 2), c(7, 8)), "cannot assign", info = "m(_, 0) = <shorter y>")
1790-
expect_error(length_row(matrix(1, 3, 2), c(7, 8, 9)), "cannot assign", info = "m(0, _) = <longer y>")
1779+
1780+
## the length checks are on in development versions and off in releases
1781+
## (RCPP_SUGAR_LENGTH_CHECKS, see config.h)
1782+
if (sugar_length_checks_enabled()) {
1783+
expect_error(length_plus(c(1, 2, 3, 4, 5), c(10, 20)), "different lengths", info = "x + <shorter y>")
1784+
expect_error(length_plus(c(1, 2), c(10, 20, 30, 40)), "different lengths", info = "x + <longer y>")
1785+
expect_error(length_compare(c(1, 2, 3), c(1, 2)), "different lengths", info = "x < y")
1786+
expect_error(length_and(c(TRUE, FALSE), TRUE), "different lengths", info = "x & y")
1787+
expect_error(length_pmax(c(1, 2, 3), c(1, 2)), "different lengths", info = "pmax(x, y)")
1788+
expect_error(length_ifelse(c(TRUE, FALSE), c(1, 2), c(1, 2, 3)), "different lengths", info = "ifelse(c, x, y)")
1789+
1790+
expect_error(length_range(c(1, 2, 3, 4, 5), c(7, 8)), "cannot assign", info = "x[0:2] = <shorter y>")
1791+
expect_error(length_range(c(1, 2, 3, 4, 5), c(6, 7, 8, 9)), "cannot assign", info = "x[0:2] = <longer y>")
1792+
expect_error(length_range_range(c(1, 2, 3, 4, 5)), "cannot assign", info = "x[0:1] = x[0:3]")
1793+
expect_error(length_column(matrix(1, 3, 2), c(7, 8)), "cannot assign", info = "m(_, 0) = <shorter y>")
1794+
expect_error(length_row(matrix(1, 3, 2), c(7, 8, 9)), "cannot assign", info = "m(0, _) = <longer y>")
1795+
} else {
1796+
## with the checks off, a longer operand is cut short, as before
1797+
expect_equal(length_plus(c(1, 2), c(10, 20, 30, 40)), c(11, 22), info = "x + <longer y>, checks off")
1798+
expect_equal(length_range(c(1, 2, 3, 4, 5), c(6, 7, 8, 9)), c(6, 7, 8, 4, 5), info = "x[0:2] = <longer y>, checks off")
1799+
expect_equal(length_row(matrix(1, 3, 2), c(7, 8, 9)), matrix(c(7, 1, 1, 8, 1, 1), 3, 2), info = "m(0, _) = <longer y>, checks off")
1800+
}
17911801

17921802
## &&, || and ! on single logical results (e.g. all(), any()) follow R's
17931803
## three-valued logic

‎vignettes/rmd/Rcpp-sugar.Rmd‎

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -528,10 +528,18 @@ assigned to a vector or otherwise consumed, for example by `sum()`.
528528
## Operand lengths
529529

530530
Unlike \proglang{R}, \sugar does not recycle: the vectors combined by an
531-
expression must all have the same length, and an error is signalled
532-
otherwise. Likewise, a vector assigned into a range, row or column (as in
533-
`m(_, 0) = x`) must have the length of that target. Single values, like the
534-
`2.0` in `x + 2.0`, can be combined with vectors of any length.
531+
expression must all have the same length. Likewise, a vector assigned into a
532+
range, row or column (as in `m(_, 0) = x`) must have the length of that
533+
target. Single values, like the `2.0` in `x + 2.0`, can be combined with
534+
vectors of any length.
535+
536+
Development versions of \pkg{Rcpp} (those with a fourth version component,
537+
such as 1.1.2.5) check these lengths and signal an error on a mismatch, so
538+
that the problem is caught when a package is checked against them. Released
539+
versions do not check: the result takes the length of the first operand, or
540+
of the target, and a longer operand is silently cut short. Defining
541+
`RCPP_SUGAR_LENGTH_CHECKS` as `1` or `0` before including `Rcpp.h` turns the
542+
checks on or off regardless of the version.
535543

536544
## Storing expressions
537545

0 commit comments

Comments
 (0)