@@ -73,6 +73,188 @@ test_that("reduction intrinsics cover scalar, multi-arg, and mask cases", {
7373 expect_quick_equal(prod_two , list (c(1 , 2 , 3 ), c(4 , 5 , 6 )))
7474})
7575
76+ test_that(" any/all reduction intrinsics cover scalar, multi-arg, and mask cases" , {
77+ any_basic <- function (x ) {
78+ declare(type(x = logical (NA )))
79+ any(x )
80+ }
81+ fsub <- r2f(any_basic )
82+ expect_match(as.character(fsub ), " any\\ (" )
83+ expect_quick_identical(any_basic , list (c(FALSE , FALSE )), list (c(FALSE , TRUE )))
84+
85+ all_basic <- function (x ) {
86+ declare(type(x = logical (NA )))
87+ all(x )
88+ }
89+ fsub <- r2f(all_basic )
90+ expect_match(as.character(fsub ), " all\\ (" )
91+ expect_quick_identical(all_basic , list (c(TRUE , TRUE )), list (c(TRUE , FALSE )))
92+
93+ any_two <- function (a , b ) {
94+ declare(type(a = logical (NA )), type(b = logical (NA )))
95+ any(a , b )
96+ }
97+ fsub <- r2f(any_two )
98+ expect_match(as.character(fsub ), " \\ .or\\ ." )
99+ expect_quick_identical(
100+ any_two ,
101+ list (a = c(FALSE , FALSE ), b = c(FALSE , FALSE )),
102+ list (a = c(FALSE , FALSE ), b = c(TRUE , FALSE ))
103+ )
104+
105+ all_two <- function (a , b ) {
106+ declare(type(a = logical (NA )), type(b = logical (NA )))
107+ all(a , b )
108+ }
109+ fsub <- r2f(all_two )
110+ expect_match(as.character(fsub ), " \\ .and\\ ." )
111+ expect_quick_identical(
112+ all_two ,
113+ list (a = c(TRUE , TRUE ), b = c(TRUE , TRUE )),
114+ list (a = c(TRUE , TRUE ), b = c(FALSE , TRUE ))
115+ )
116+
117+ # Scalar masked subset: preserve empty-selection semantics.
118+ any_scalar_masked_empty <- function (x ) {
119+ declare(type(x = logical (1 )))
120+ any(x [c(FALSE )])
121+ }
122+ expect_quick_identical(any_scalar_masked_empty , list (TRUE ), list (FALSE ))
123+
124+ all_scalar_masked_empty <- function (x ) {
125+ declare(type(x = logical (1 )))
126+ all(x [c(FALSE )])
127+ }
128+ expect_quick_identical(all_scalar_masked_empty , list (TRUE ), list (FALSE ))
129+
130+ # Scalar masked subset with a scalar mask variable: should compile and
131+ # preserve empty-selection semantics.
132+ any_scalar_masked_var <- function (x , m ) {
133+ declare(type(x = logical (1 )), type(m = logical (1 )))
134+ any(x [m ])
135+ }
136+ expect_quick_identical(
137+ any_scalar_masked_var ,
138+ list (x = TRUE , m = FALSE ),
139+ list (x = FALSE , m = TRUE )
140+ )
141+
142+ all_scalar_masked_var <- function (x , m ) {
143+ declare(type(x = logical (1 )), type(m = logical (1 )))
144+ all(x [m ])
145+ }
146+ expect_quick_identical(
147+ all_scalar_masked_var ,
148+ list (x = FALSE , m = FALSE ),
149+ list (x = FALSE , m = TRUE )
150+ )
151+
152+ # Scalar `x` with a longer logical mask diverges from simple empty/non-empty
153+ # semantics in R because selecting out-of-range positions yields NAs. We don't
154+ # implement that behavior here, so fail fast instead of silently emitting
155+ # wrong code.
156+ any_scalar_masked_long_mask <- function (x ) {
157+ declare(type(x = logical (1 )))
158+ any(x [c(FALSE , TRUE )])
159+ }
160+ expect_error(r2f(any_scalar_masked_long_mask ), " scalar masked subsets" )
161+
162+ all_scalar_masked_long_mask <- function (x ) {
163+ declare(type(x = logical (1 )))
164+ all(x [c(FALSE , TRUE )])
165+ }
166+ expect_error(r2f(all_scalar_masked_long_mask ), " scalar masked subsets" )
167+
168+ # 1-element vector expressions like c(FALSE) compile to Fortran array
169+ # constructors (`[.false.]`) but any()/all() must still return scalars.
170+ any_array_ctor_len1 <- function () {
171+ any(c(FALSE ))
172+ }
173+ expect_quick_identical(any_array_ctor_len1 , list ())
174+
175+ all_array_ctor_len1 <- function () {
176+ all(c(TRUE ))
177+ }
178+ expect_quick_identical(all_array_ctor_len1 , list ())
179+
180+ # Masked reduction over a 1-element array constructor: `[` can hoist the mask
181+ # while leaving `x` as a rank-1 array constructor (`[.true.]`), but the
182+ # reduction result must still be scalar.
183+ any_array_ctor_len1_masked <- function () {
184+ any(c(TRUE )[c(TRUE )])
185+ }
186+ expect_quick_identical(any_array_ctor_len1_masked , list ())
187+
188+ all_array_ctor_len1_masked_empty <- function () {
189+ all(c(TRUE )[c(FALSE )])
190+ }
191+ expect_quick_identical(all_array_ctor_len1_masked_empty , list ())
192+
193+ # Length-1 mask constructors (c(TRUE)/c(FALSE)) are hoisted as rank-1 array
194+ # constructors but should behave like scalars via recycling in R. The lowering
195+ # must scalarize them so elementwise `.and.`/`.or.` remain conformable.
196+ any_mask_ctor_len1 <- function (x ) {
197+ declare(type(x = double(NA )))
198+ pred <- x > 1
199+ any(pred [c(TRUE )])
200+ }
201+ fsub <- r2f(any_mask_ctor_len1 )
202+ expect_false(grepl(" \\ .and\\ . \\ (\\ [" , as.character(fsub )))
203+ expect_quick_identical(
204+ any_mask_ctor_len1 ,
205+ list (c(- 2 , - 1 )),
206+ list (c(0.2 , 2 ))
207+ )
208+
209+ all_mask_ctor_len1_empty <- function (x ) {
210+ declare(type(x = double(NA )))
211+ pred <- x > 1
212+ all(pred [c(FALSE )])
213+ }
214+ fsub <- r2f(all_mask_ctor_len1_empty )
215+ expect_false(grepl(" \\ .not\\ . \\ (\\ [" , as.character(fsub )))
216+ expect_false(grepl(" \\ .or\\ . \\ (\\ [" , as.character(fsub )))
217+ expect_quick_identical(
218+ all_mask_ctor_len1_empty ,
219+ list (c(- 2 , - 1 )),
220+ list (c(0.2 , 2 ))
221+ )
222+
223+ # Masked subset: preserve empty-selection semantics without pack() temporaries.
224+ any_masked <- function (x ) {
225+ declare(type(x = double(NA )))
226+ pred <- x > 1
227+ any(pred [x > 0 ])
228+ }
229+ fsub <- r2f(any_masked )
230+ expect_false(grepl(" pack\\ (" , as.character(fsub )))
231+ expect_match(as.character(fsub ), " \\ .and\\ ." )
232+ expect_match(as.character(fsub ), " any\\ (" )
233+ expect_quick_identical(
234+ any_masked ,
235+ list (c(- 2 , - 1 )), # empty selection -> any(logical(0)) == FALSE
236+ list (c(0.2 , 0.3 )), # selection all FALSE
237+ list (c(0.5 , 2 )) # selection contains TRUE
238+ )
239+
240+ all_masked <- function (x ) {
241+ declare(type(x = double(NA )))
242+ pred <- x > 1
243+ all(pred [x > 0 ])
244+ }
245+ fsub <- r2f(all_masked )
246+ expect_false(grepl(" pack\\ (" , as.character(fsub )))
247+ expect_match(as.character(fsub ), " \\ .not\\ ." )
248+ expect_match(as.character(fsub ), " \\ .or\\ ." )
249+ expect_match(as.character(fsub ), " all\\ (" )
250+ expect_quick_identical(
251+ all_masked ,
252+ list (c(- 2 , - 1 )), # empty selection -> all(logical(0)) == TRUE
253+ list (c(0.2 , 0.3 )), # selection all FALSE -> FALSE
254+ list (c(0.5 , 2 )) # selection has a FALSE -> FALSE
255+ )
256+ })
257+
76258
77259test_that(" 1x1 subsetting keeps dims and C bridge builds" , {
78260 fn <- function (m ) {
0 commit comments