Skip to content

Commit 1246ad4

Browse files
committed
Require compile-time conformability for matrix ops
- Error on ambiguous cbind/rbind dimensions - Error on ambiguous crossprod/tcrossprod shapes - Add public API tests for matrix parse/constraints
1 parent 6cfcda6 commit 1246ad4

4 files changed

Lines changed: 118 additions & 21 deletions

File tree

R/r2f-matrix.R

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -181,13 +181,6 @@ bind_output_mode <- function(values, context) {
181181
return("logical")
182182
}
183183
if ("raw" %in% modes) {
184-
if (length(modes) > 1L) {
185-
stop(
186-
context,
187-
" does not support mixing raw with other types",
188-
call. = FALSE
189-
)
190-
}
191184
return("raw")
192185
}
193186
stop(context, " does not support input mode(s): ", str_flatten_commas(modes))
@@ -263,7 +256,13 @@ bind_common_dim <- function(dim_list, scalar_flags, context, label) {
263256
)
264257
}
265258
if (conform$unknown) {
266-
warn_conformability_unknown(target, dim_list[[idx]], context)
259+
stop(
260+
context,
261+
" requires inputs with a common ",
262+
label,
263+
" count",
264+
call. = FALSE
265+
)
267266
}
268267
}
269268
}
@@ -866,7 +865,12 @@ crossprod_like <- function(
866865
stop("non-conformable arguments in ", context, call. = FALSE)
867866
}
868867
if (conform$unknown) {
869-
warn_conformability_unknown(x_eff$cols, y_eff$rows, context)
868+
stop(
869+
"cannot verify conformability in ",
870+
context,
871+
" at compile time",
872+
call. = FALSE
873+
)
870874
}
871875

872876
m <- x_eff$rows

tests/testthat/test-bind.R

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ test_that("cbind rejects mixing complex with non-complex", {
246246
expect_error(quick(bad), "mixing complex", fixed = TRUE)
247247
})
248248

249-
test_that("cbind warns when conformability cannot be verified", {
249+
test_that("cbind requires dimensions to be equal at compile time", {
250250
fn <- function(x, y, n, m) {
251251
declare(
252252
type(n = integer(1)),
@@ -257,18 +257,44 @@ test_that("cbind warns when conformability cannot be verified", {
257257
cbind(x, y)
258258
}
259259

260-
qfn <- NULL
261-
expect_warning(
262-
qfn <- quick(fn),
263-
"cannot verify conformability in cbind()",
260+
expect_error(
261+
quick(fn),
262+
"common row count",
263+
fixed = TRUE
264+
)
265+
})
266+
267+
test_that("cbind supports symbolic dimension expressions in output shape", {
268+
fn <- function(x, A, n, m) {
269+
declare(
270+
type(n = integer(1)),
271+
type(m = integer(1)),
272+
type(x = double(n)),
273+
type(A = double(n, m))
274+
)
275+
cbind(x, A)
276+
}
277+
278+
set.seed(99)
279+
n <- 3L
280+
m <- 2L
281+
x <- runif(n)
282+
A <- matrix(runif(n * m), nrow = n)
283+
expect_bind_equal(fn, list(x, A, n, m))
284+
})
285+
286+
test_that("cbind requires inferred dimensions to match", {
287+
unknown_rows <- function(A, B) {
288+
declare(type(A = double(NA, 2)), type(B = double(NA, 1)))
289+
cbind(A, B)
290+
}
291+
292+
q_unknown_rows <- NULL
293+
expect_error(
294+
q_unknown_rows <- quick(unknown_rows),
295+
"common row count",
264296
fixed = TRUE
265297
)
266298

267-
x <- c(1.0, 2.0, 3.0)
268-
y <- c(-1.0, 0.0, 1.0)
269-
qres <- qfn(x, y, 3L, 3L)
270-
rres <- fn(x, y, 3L, 3L)
271-
expect_identical(dim(qres), dim(rres))
272-
expect_identical(typeof(qres), typeof(rres))
273-
expect_equal(unname(qres), unname(rres))
299+
expect_null(q_unknown_rows)
274300
})

tests/testthat/test-matrix-inference.R

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,26 @@ test_that("matrix ops infer destination sizes for assignments", {
135135
expect_quick_equal(chol2inv_infer, list(A = A_pd))
136136
})
137137

138+
test_that("crossprod requires conformability at compile time", {
139+
fn <- function(x, y, n, p, m, k) {
140+
declare(
141+
type(n = integer(1)),
142+
type(p = integer(1)),
143+
type(m = integer(1)),
144+
type(k = integer(1)),
145+
type(x = double(n, m)),
146+
type(y = double(p, k))
147+
)
148+
crossprod(x, y)
149+
}
150+
151+
expect_error(
152+
quick(fn),
153+
"cannot verify conformability in crossprod",
154+
fixed = TRUE
155+
)
156+
})
157+
138158
test_that("matrix helpers report unsupported inputs", {
139159
matmul_bad_rank <- function(a, b) {
140160
declare(type(a = double(2, 2, 2)), type(b = double(2, 2)))

tests/testthat/test-matrix-parse.R

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Coverage-focused tests for matrix parsing helpers.
2+
3+
test_that("transpose parsing handles scalars and rejects rank > 2", {
4+
scalar_left <- function(B) {
5+
declare(type(B = double(2, 2)))
6+
t(1.25) %*% B
7+
}
8+
9+
expect_error(
10+
quick(scalar_left),
11+
"non-conformable arguments in %*%",
12+
fixed = TRUE
13+
)
14+
15+
rank3_left <- function(x, B) {
16+
declare(type(x = double(2, 2, 2)), type(B = double(2, 2)))
17+
t(x) %*% B
18+
}
19+
20+
expect_error(
21+
quick(rank3_left),
22+
"t() only supports rank 0-2 inputs",
23+
fixed = TRUE
24+
)
25+
})
26+
27+
test_that("matrix helpers validate logical args", {
28+
bad_chol <- function(A) {
29+
declare(type(A = double(2, 2)))
30+
chol(A, pivot = 1)
31+
}
32+
33+
expect_error(
34+
quick(bad_chol),
35+
"chol\\(\\) only supports literal pivot = TRUE/FALSE"
36+
)
37+
38+
bad_diag <- function(x) {
39+
declare(type(x = double(3)))
40+
diag(x, names = 1)
41+
}
42+
43+
expect_error(
44+
quick(bad_diag),
45+
"diag\\(\\) only supports literal names = TRUE/FALSE"
46+
)
47+
})

0 commit comments

Comments
 (0)