1use crate::indexing::selectors::{
2 index_scalar_from_value, logical_indices_linear, materialize_index_value,
3 numeric_tensor_indices, SliceSelector,
4};
5use crate::indexing::EndExpr;
6use crate::runtime_error::semantic_error as mex;
7use crate::{builtins::common::shape::is_scalar_shape, RuntimeError};
8use runmat_value::Value;
9use std::future::Future;
10
11pub type VmResult<T> = Result<T, RuntimeError>;
12
13#[derive(Debug, Clone, Default)]
14pub struct IndexPlanProperties {
15 pub full_row: Option<usize>,
16 pub full_column: Option<usize>,
17}
18
19#[derive(Debug, Clone)]
20pub struct IndexPlan {
21 pub indices: Vec<u32>,
22 pub output_shape: Vec<usize>,
23 pub selection_lengths: Vec<usize>,
24 pub dims: usize,
25 pub base_shape: Vec<usize>,
26 pub properties: IndexPlanProperties,
27}
28
29impl IndexPlan {
30 pub fn new(
31 indices: Vec<u32>,
32 output_shape: Vec<usize>,
33 selection_lengths: Vec<usize>,
34 dims: usize,
35 base_shape: Vec<usize>,
36 ) -> Self {
37 let properties = derive_plan_properties(&indices, dims, &base_shape);
38 Self {
39 indices,
40 output_shape,
41 selection_lengths,
42 dims,
43 base_shape,
44 properties,
45 }
46 }
47}
48
49fn derive_plan_properties(
50 indices: &[u32],
51 dims: usize,
52 base_shape: &[usize],
53) -> IndexPlanProperties {
54 let mut properties = IndexPlanProperties {
55 full_row: None,
56 full_column: None,
57 };
58 if dims != 2 || indices.is_empty() {
59 return properties;
60 }
61 let rows = base_shape.first().copied().unwrap_or(1);
62 let cols = base_shape.get(1).copied().unwrap_or(1);
63 if indices.len() == rows {
64 let first = indices[0] as usize;
65 if first.is_multiple_of(rows) {
66 let col = first / rows;
67 if col < cols
68 && indices
69 .iter()
70 .enumerate()
71 .all(|(r, &idx)| idx as usize == col * rows + r)
72 {
73 properties.full_column = Some(col);
74 }
75 }
76 }
77 if indices.len() == cols {
78 let first = indices[0] as usize;
79 let row = first % rows;
80 if row < rows
81 && indices
82 .iter()
83 .enumerate()
84 .all(|(c, &idx)| idx as usize == row + c * rows)
85 {
86 properties.full_row = Some(row);
87 }
88 }
89 properties
90}
91
92fn cartesian_product<F: FnMut(&[usize])>(lists: &[Vec<usize>], mut f: F) {
93 let dims = lists.len();
94 if dims == 0 {
95 return;
96 }
97 let mut idx = vec![0usize; dims];
98 loop {
99 let current: Vec<usize> = (0..dims).map(|d| lists[d][idx[d]]).collect();
100 f(¤t);
101 let mut d = 0usize;
102 while d < dims {
103 idx[d] += 1;
104 if idx[d] < lists[d].len() {
105 break;
106 }
107 idx[d] = 0;
108 d += 1;
109 }
110 if d == dims {
111 break;
112 }
113 }
114}
115
116pub fn total_len_from_shape(shape: &[usize]) -> usize {
117 if is_scalar_shape(shape) {
118 1
119 } else {
120 shape.iter().copied().product()
121 }
122}
123
124fn checked_total_len_from_shape(shape: &[usize]) -> VmResult<usize> {
125 if is_scalar_shape(shape) {
126 return Ok(1);
127 }
128 shape.iter().try_fold(1usize, |acc, dim| {
129 acc.checked_mul(*dim)
130 .ok_or_else(|| mex("IndexOutOfBounds", "Index dimensions overflow"))
131 })
132}
133
134fn checked_u32_index(index: usize) -> VmResult<u32> {
135 u32::try_from(index).map_err(|_| mex("IndexOutOfBounds", "Index exceeds supported range"))
136}
137
138fn matlab_squeezed_shape(selection_lengths: &[usize], scalar_mask: &[bool]) -> Vec<usize> {
139 let mut dims: Vec<(usize, usize, bool)> = selection_lengths
140 .iter()
141 .enumerate()
142 .map(|(d, &len)| (d, len, scalar_mask.get(d).copied().unwrap_or(false)))
143 .collect();
144 while dims.len() > 2
145 && dims
146 .last()
147 .map(|&(_, len, is_scalar)| len == 1 && is_scalar)
148 .unwrap_or(false)
149 {
150 dims.pop();
151 }
152 let out: Vec<usize> = dims.into_iter().map(|(_, len, _)| len).collect();
153 if out.is_empty() {
154 vec![1, 1]
155 } else {
156 out
157 }
158}
159
160fn exact_index_from_f64(value: f64) -> Option<i64> {
161 if !value.is_finite() {
162 return None;
163 }
164 let rounded = value.round();
165 if (rounded - value).abs() > f64::EPSILON {
166 return None;
167 }
168 if rounded < i64::MIN as f64 || rounded > i64::MAX as f64 {
169 return None;
170 }
171 Some(rounded as i64)
172}
173
174pub fn build_index_plan(
175 selectors: &[SliceSelector],
176 dims: usize,
177 base_shape: &[usize],
178) -> VmResult<IndexPlan> {
179 let total_len = checked_total_len_from_shape(base_shape)?;
180 if dims == 1 {
181 let list = selectors
182 .first()
183 .cloned()
184 .unwrap_or(SliceSelector::Indices(Vec::new()));
185 let indices = match &list {
186 SliceSelector::Colon => (1..=total_len).collect::<Vec<usize>>(),
187 SliceSelector::Scalar(i) => vec![*i],
188 SliceSelector::Indices(v) => v.clone(),
189 SliceSelector::LinearIndices { values, .. } => values.clone(),
190 };
191 if indices.iter().any(|&i| i == 0 || i > total_len) {
192 return Err(mex("IndexOutOfBounds", "Index out of bounds"));
193 }
194 let zero_based: Vec<u32> = indices
195 .iter()
196 .map(|&i| checked_u32_index(i - 1))
197 .collect::<Result<_, _>>()?;
198 let count = zero_based.len();
199 let base_is_row_vector = base_shape.first().copied().unwrap_or(1) == 1
200 && base_shape.get(1).copied().unwrap_or(1) > 1;
201 let shape = match list {
202 SliceSelector::Colon => vec![count, 1],
203 SliceSelector::LinearIndices { output_shape, .. } => output_shape,
204 _ if count == 0 => vec![0, 1],
205 _ if count <= 1 => vec![1, 1],
206 _ if base_is_row_vector => vec![1, count],
207 _ => vec![count, 1],
208 };
209 return Ok(IndexPlan::new(
210 zero_based,
211 shape,
212 vec![count],
213 dims,
214 base_shape.to_vec(),
215 ));
216 }
217
218 let mut selection_lengths = Vec::with_capacity(dims);
219 let mut per_dim_lists: Vec<Vec<usize>> = Vec::with_capacity(dims);
220 let mut scalar_mask: Vec<bool> = Vec::with_capacity(dims);
221 for (d, sel) in selectors.iter().enumerate().take(dims) {
222 let dim_len = base_shape.get(d).copied().unwrap_or(1);
223 let idxs = match sel {
224 SliceSelector::Colon => (1..=dim_len).collect::<Vec<usize>>(),
225 SliceSelector::Scalar(i) => vec![*i],
226 SliceSelector::Indices(v) => v.clone(),
227 SliceSelector::LinearIndices { values: v, .. } => v.clone(),
228 };
229 if idxs.iter().any(|&i| i == 0 || i > dim_len) {
230 return Err(mex("IndexOutOfBounds", "Index out of bounds"));
231 }
232 selection_lengths.push(idxs.len());
233 per_dim_lists.push(idxs);
234 scalar_mask.push(matches!(sel, SliceSelector::Scalar(_)));
235 }
236
237 let mut out_shape = matlab_squeezed_shape(&selection_lengths, &scalar_mask);
238 if selection_lengths.contains(&0) {
239 let selection_lengths = out_shape.clone();
240 return Ok(IndexPlan::new(
241 Vec::new(),
242 out_shape,
243 selection_lengths,
244 dims,
245 base_shape.to_vec(),
246 ));
247 }
248
249 let mut base_norm = base_shape.to_vec();
250 if base_norm.len() < dims {
251 base_norm.resize(dims, 1);
252 }
253 let mut strides = vec![1usize; dims];
254 for d in 1..dims {
255 strides[d] = strides[d - 1]
256 .checked_mul(base_norm[d - 1].max(1))
257 .ok_or_else(|| mex("IndexOutOfBounds", "Index dimensions overflow"))?;
258 }
259
260 let mut indices = Vec::new();
261 let mut index_error: Option<RuntimeError> = None;
262 cartesian_product(&per_dim_lists, |multi| {
263 if index_error.is_some() {
264 return;
265 }
266 let mut lin = 0usize;
267 for d in 0..dims {
268 let idx = multi[d] - 1;
269 let offset = match idx.checked_mul(strides[d]) {
270 Some(offset) => offset,
271 None => {
272 index_error = Some(mex("IndexOutOfBounds", "Index dimensions overflow"));
273 return;
274 }
275 };
276 lin = match lin.checked_add(offset) {
277 Some(sum) => sum,
278 None => {
279 index_error = Some(mex("IndexOutOfBounds", "Index dimensions overflow"));
280 return;
281 }
282 };
283 }
284 match checked_u32_index(lin) {
285 Ok(index) => indices.push(index),
286 Err(err) => index_error = Some(err),
287 }
288 });
289 if let Some(err) = index_error {
290 return Err(err);
291 }
292
293 let total_out: usize = selection_lengths.iter().product();
294 if total_out == 1 {
295 out_shape = vec![1, 1];
296 }
297 let selection_lengths = out_shape.clone();
298 Ok(IndexPlan::new(
299 indices,
300 out_shape,
301 selection_lengths,
302 dims,
303 base_shape.to_vec(),
304 ))
305}
306
307pub fn build_sparse_assignment_plan(
311 selectors: &[SliceSelector],
312 dims: usize,
313 base_shape: &[usize],
314) -> VmResult<IndexPlan> {
315 if dims != 2 {
316 return build_index_plan(selectors, dims, base_shape);
317 }
318
319 let mut target_shape = base_shape.to_vec();
320 target_shape.resize(dims, 1);
321 let mut planned_selectors = Vec::with_capacity(dims);
322 for (d, target_len) in target_shape.iter_mut().enumerate().take(dims) {
323 let original_len = base_shape.get(d).copied().unwrap_or(1);
324 let selector = selectors
325 .get(d)
326 .cloned()
327 .unwrap_or(SliceSelector::Indices(Vec::new()));
328 let values = match &selector {
329 SliceSelector::Colon => (1..=original_len).collect::<Vec<_>>(),
330 SliceSelector::Scalar(value) => vec![*value],
331 SliceSelector::Indices(values) => values.clone(),
332 SliceSelector::LinearIndices { values, .. } => values.clone(),
333 };
334 if values.contains(&0) {
335 return Err(mex("IndexOutOfBounds", "Index out of bounds"));
336 }
337 if let Some(&max_value) = values.iter().max() {
338 *target_len = (*target_len).max(max_value);
339 }
340 planned_selectors.push(match selector {
341 SliceSelector::Colon => SliceSelector::Indices(values),
342 other => other,
343 });
344 }
345 build_index_plan(&planned_selectors, dims, &target_shape)
346}
347
348#[derive(Clone)]
349enum ExprSel {
350 Colon,
351 Scalar(usize),
352 Indices(Vec<usize>),
353 Range {
354 start: i64,
355 step: i64,
356 end_off: EndExpr,
357 },
358}
359
360pub struct ExprPlanSpec<'a> {
361 pub dims: usize,
362 pub colon_mask: u32,
363 pub end_mask: u32,
364 pub range_dims: &'a [usize],
365 pub range_params: &'a [(f64, f64)],
366 pub range_start_exprs: &'a [Option<EndExpr>],
367 pub range_step_exprs: &'a [Option<EndExpr>],
368 pub range_end_exprs: &'a [EndExpr],
369 pub numeric: &'a [Value],
370 pub shape: &'a [usize],
371}
372
373fn selector_mask_has_dim(mask: u32, dim: usize) -> bool {
374 dim < u32::BITS as usize && (mask & (1u32 << dim)) != 0
375}
376
377fn validate_expr_range_selector_plan(
378 spec: &ExprPlanSpec<'_>,
379) -> Result<Vec<Option<usize>>, RuntimeError> {
380 let range_len = spec.range_dims.len();
381 if spec.range_params.len() != range_len
382 || spec.range_start_exprs.len() != range_len
383 || spec.range_step_exprs.len() != range_len
384 || spec.range_end_exprs.len() != range_len
385 {
386 return Err(mex(
387 "InvalidRangeSelectorPlan",
388 "inconsistent range selector metadata",
389 ));
390 }
391
392 let mut by_dim = vec![None; spec.dims];
393 for (pos, &dim) in spec.range_dims.iter().enumerate() {
394 if dim >= spec.dims {
395 return Err(mex(
396 "InvalidRangeSelectorDim",
397 "range selector dimension is out of bounds",
398 ));
399 }
400 let conflicts_with_colon = selector_mask_has_dim(spec.colon_mask, dim);
401 let conflicts_with_end = selector_mask_has_dim(spec.end_mask, dim);
402 if conflicts_with_colon || conflicts_with_end {
403 return Err(mex(
404 "InvalidRangeSelectorPlan",
405 "range selector conflicts with colon/end selector masks",
406 ));
407 }
408 if by_dim[dim].replace(pos).is_some() {
409 return Err(mex(
410 "InvalidRangeSelectorPlan",
411 "range selector dimension appears more than once",
412 ));
413 }
414 }
415 Ok(by_dim)
416}
417
418pub async fn build_expr_index_plan<ResolveEnd, Fut>(
419 spec: ExprPlanSpec<'_>,
420 resolve_end: ResolveEnd,
421) -> Result<IndexPlan, RuntimeError>
422where
423 ResolveEnd: FnMut(usize, &EndExpr) -> Fut,
424 Fut: Future<Output = Result<f64, RuntimeError>>,
425{
426 build_expr_index_plan_with_growth(spec, resolve_end, false).await
427}
428
429pub async fn build_expr_sparse_assignment_plan<ResolveEnd, Fut>(
430 spec: ExprPlanSpec<'_>,
431 resolve_end: ResolveEnd,
432) -> Result<IndexPlan, RuntimeError>
433where
434 ResolveEnd: FnMut(usize, &EndExpr) -> Fut,
435 Fut: Future<Output = Result<f64, RuntimeError>>,
436{
437 build_expr_index_plan_with_growth(spec, resolve_end, true).await
438}
439
440async fn build_expr_index_plan_with_growth<ResolveEnd, Fut>(
441 spec: ExprPlanSpec<'_>,
442 mut resolve_end: ResolveEnd,
443 allow_sparse_growth: bool,
444) -> Result<IndexPlan, RuntimeError>
445where
446 ResolveEnd: FnMut(usize, &EndExpr) -> Fut,
447 Fut: Future<Output = Result<f64, RuntimeError>>,
448{
449 let allow_sparse_growth = allow_sparse_growth && spec.dims == 2;
450 let rank = spec.shape.len();
451 let full_shape: Vec<usize> = if spec.dims == 1 {
452 vec![checked_total_len_from_shape(spec.shape)?]
453 } else if rank < spec.dims {
454 let mut s = spec.shape.to_vec();
455 s.resize(spec.dims, 1);
456 s
457 } else {
458 spec.shape.to_vec()
459 };
460
461 let range_pos_by_dim = validate_expr_range_selector_plan(&spec)?;
462 let mut selectors: Vec<ExprSel> = Vec::with_capacity(spec.dims);
463 let mut linear_output_shape: Option<Vec<usize>> = None;
464 let mut num_iter = 0usize;
465 for (d, range_pos) in range_pos_by_dim.iter().enumerate().take(spec.dims) {
466 let is_colon = selector_mask_has_dim(spec.colon_mask, d);
467 let is_end = selector_mask_has_dim(spec.end_mask, d);
468 if is_colon {
469 selectors.push(ExprSel::Colon);
470 } else if is_end {
471 selectors.push(ExprSel::Scalar(*full_shape.get(d).unwrap_or(&1)));
472 } else if let Some(pos) = *range_pos {
473 let (raw_st, raw_sp) = spec.range_params[pos];
474 let dim_len = *full_shape.get(d).unwrap_or(&1);
475 let st = if let Some(expr) = &spec.range_start_exprs[pos] {
476 resolve_end(dim_len, expr).await? as f64
477 } else {
478 raw_st
479 };
480 let sp = if let Some(expr) = &spec.range_step_exprs[pos] {
481 resolve_end(dim_len, expr).await? as f64
482 } else {
483 raw_sp
484 };
485 let start = exact_index_from_f64(st).ok_or_else(|| {
486 mex(
487 "UnsupportedIndexType",
488 "Index values must be positive integers or logical values",
489 )
490 })?;
491 let step = exact_index_from_f64(sp).ok_or_else(|| {
492 mex(
493 "UnsupportedIndexType",
494 "Index values must be positive integers or logical values",
495 )
496 })?;
497 let off = spec.range_end_exprs[pos].clone();
498 selectors.push(ExprSel::Range {
499 start,
500 step,
501 end_off: off,
502 });
503 } else {
504 let v = spec
505 .numeric
506 .get(num_iter)
507 .ok_or_else(|| mex("MissingNumericIndex", "missing numeric index"))?;
508 num_iter += 1;
509 if let Some(idx) = index_scalar_from_value(v).await? {
510 if idx.is_below_one() {
511 return Err(mex("IndexOutOfBounds", "Index out of bounds"));
512 }
513 let index = idx
514 .positive_usize()
515 .ok_or_else(|| mex("IndexOutOfBounds", "Index out of bounds"))?;
516 selectors.push(ExprSel::Scalar(index));
517 } else {
518 let materialized = materialize_index_value(v).await?;
522 let v = &materialized;
523 match v {
524 Value::Bool(b) => {
525 selectors.push(if *b {
526 ExprSel::Indices(vec![1])
527 } else {
528 ExprSel::Indices(Vec::new())
529 });
530 }
531 Value::LogicalArray(la) => {
532 if la.data.len() == 1 && is_scalar_shape(&la.shape) {
533 selectors.push(if la.data[0] != 0 {
534 ExprSel::Indices(vec![1])
535 } else {
536 ExprSel::Indices(Vec::new())
537 });
538 } else {
539 let dim_len = *full_shape.get(d).unwrap_or(&1);
540 if spec.dims == 1 {
541 let vv = logical_indices_linear(la, dim_len)?;
542 linear_output_shape = Some(vec![vv.len(), 1]);
544 selectors.push(ExprSel::Indices(vv));
545 } else {
546 if la.data.len() != dim_len {
547 return Err(mex(
548 "IndexShape",
549 "Logical mask length mismatch for dimension",
550 ));
551 }
552 let vv = la
553 .data
554 .iter()
555 .enumerate()
556 .filter_map(|(index, &selected)| {
557 (selected != 0).then_some(index + 1)
558 })
559 .collect();
560 selectors.push(ExprSel::Indices(vv));
561 }
562 }
563 }
564 Value::Tensor(idx_t) => {
565 if spec.dims == 1 {
566 linear_output_shape = Some(idx_t.shape.clone());
567 }
568 let vv = numeric_tensor_indices(idx_t, None)?;
569 selectors.push(ExprSel::Indices(vv));
570 }
571 _ => return Err(mex("UnsupportedIndexType", "Unsupported index type")),
572 }
573 }
574 }
575 }
576
577 let mut per_dim_indices: Vec<Vec<usize>> = Vec::with_capacity(spec.dims);
578 let mut selection_lengths: Vec<usize> = Vec::with_capacity(spec.dims);
579 let mut scalar_mask: Vec<bool> = Vec::with_capacity(spec.dims);
580 let base_is_row_vector = spec.dims == 1
581 && spec.shape.first().copied().unwrap_or(1) == 1
582 && spec.shape.get(1).copied().unwrap_or(1) > 1;
583 let linear_selector_is_colon = matches!(selectors.first(), Some(ExprSel::Colon));
584 let linear_selector_is_range = matches!(selectors.first(), Some(ExprSel::Range { .. }));
585 for (d, sel) in selectors.iter().enumerate().take(spec.dims) {
586 let dim_len = full_shape[d] as i64;
587 let idxs: Vec<usize> = match sel {
588 ExprSel::Colon => (1..=full_shape[d]).collect(),
589 ExprSel::Scalar(i) => vec![*i],
590 ExprSel::Indices(v) => v.clone(),
591 ExprSel::Range {
592 start,
593 step,
594 end_off,
595 } => {
596 let mut v = Vec::new();
597 let mut cur = *start;
598 let stp = *step;
599 let end_bound = resolve_end(dim_len as usize, end_off).await?;
600 if stp == 0 {
601 return Err(mex("IndexStepZero", "Index step cannot be zero"));
602 }
603 if !end_bound.is_finite() {
604 return Err(mex(
605 "UnsupportedIndexType",
606 "Index values must be positive integers or logical values",
607 ));
608 }
609 let end_i = if stp > 0 {
610 end_bound.floor()
611 } else {
612 end_bound.ceil()
613 };
614 if end_i < i64::MIN as f64 || end_i > i64::MAX as f64 {
615 return Err(mex(
616 "UnsupportedIndexType",
617 "Index values must be positive integers or logical values",
618 ));
619 }
620 let end_i = end_i as i64;
621 if stp > 0 {
622 while cur <= end_i {
623 if cur < 1 || (!allow_sparse_growth && cur > dim_len) {
624 return Err(mex("IndexOutOfBounds", "Index out of bounds"));
625 }
626 v.push(cur as usize);
627 cur += stp;
628 }
629 } else {
630 while cur >= end_i {
631 if cur < 1 || (!allow_sparse_growth && cur > dim_len) {
632 return Err(mex("IndexOutOfBounds", "Index out of bounds"));
633 }
634 v.push(cur as usize);
635 cur += stp;
636 }
637 }
638 v
639 }
640 };
641 if idxs
642 .iter()
643 .any(|&i| i == 0 || (!allow_sparse_growth && i > full_shape[d]))
644 {
645 return Err(mex("IndexOutOfBounds", "Index out of bounds"));
646 }
647 selection_lengths.push(idxs.len());
648 per_dim_indices.push(idxs);
649 scalar_mask.push(matches!(sel, ExprSel::Scalar(_)));
650 }
651
652 let mut planned_shape = full_shape.clone();
653 if allow_sparse_growth && spec.dims > 1 {
654 for (d, indices) in per_dim_indices.iter().enumerate().take(spec.dims) {
655 if let Some(&max_index) = indices.iter().max() {
656 planned_shape[d] = planned_shape[d].max(max_index);
657 }
658 }
659 }
660 let mut strides: Vec<usize> = vec![0; spec.dims];
661 let mut acc = 1usize;
662 for (d, stride) in strides.iter_mut().enumerate().take(spec.dims) {
663 *stride = acc;
664 acc = acc
665 .checked_mul(planned_shape[d])
666 .ok_or_else(|| mex("IndexOutOfBounds", "Index dimensions overflow"))?;
667 }
668 let total_out: usize = per_dim_indices.iter().try_fold(1usize, |acc, values| {
669 acc.checked_mul(values.len())
670 .ok_or_else(|| mex("IndexOutOfBounds", "Index result dimensions overflow"))
671 })?;
672 if total_out == 0 {
673 let output_shape = if spec.dims == 1 {
674 if let Some(shape) = linear_output_shape.clone() {
675 shape
676 } else if linear_selector_is_colon {
677 vec![0, 1]
678 } else if linear_selector_is_range || base_is_row_vector {
679 vec![1, 0]
680 } else {
681 vec![0, 1]
682 }
683 } else {
684 let mut dims_out: Vec<(usize, usize, bool)> = selection_lengths
685 .iter()
686 .enumerate()
687 .map(|(d, &len)| (d, len, scalar_mask.get(d).copied().unwrap_or(false)))
688 .collect();
689 while dims_out.len() > 2
690 && dims_out
691 .last()
692 .map(|&(_, len, is_scalar)| len == 1 && is_scalar)
693 .unwrap_or(false)
694 {
695 dims_out.pop();
696 }
697 if dims_out.is_empty() {
698 vec![1, 1]
699 } else if dims_out.len() == 1 {
700 let (dim, len, _) = dims_out[0];
701 if dim == 1 {
702 vec![1, len]
703 } else {
704 vec![len, 1]
705 }
706 } else {
707 dims_out.into_iter().map(|(_, len, _)| len).collect()
708 }
709 };
710 return Ok(IndexPlan::new(
711 Vec::new(),
712 output_shape,
713 selection_lengths,
714 spec.dims,
715 planned_shape,
716 ));
717 }
718
719 let mut indices: Vec<u32> = Vec::with_capacity(total_out);
720 let mut idx = vec![0usize; spec.dims];
721 loop {
722 let mut lin = 0usize;
723 for d in 0..spec.dims {
724 let i0 = per_dim_indices[d][idx[d]] - 1;
725 let offset = i0
726 .checked_mul(strides[d])
727 .ok_or_else(|| mex("IndexOutOfBounds", "Index dimensions overflow"))?;
728 lin = lin
729 .checked_add(offset)
730 .ok_or_else(|| mex("IndexOutOfBounds", "Index dimensions overflow"))?;
731 }
732 indices.push(checked_u32_index(lin)?);
733 let mut d = 0usize;
734 while d < spec.dims {
735 idx[d] += 1;
736 if idx[d] < per_dim_indices[d].len() {
737 break;
738 }
739 idx[d] = 0;
740 d += 1;
741 }
742 if d == spec.dims {
743 break;
744 }
745 }
746
747 let output_shape = if spec.dims == 1 {
748 if let Some(shape) = linear_output_shape {
749 shape
750 } else if total_out <= 1 {
751 vec![1, 1]
752 } else if linear_selector_is_colon {
753 vec![total_out, 1]
754 } else if linear_selector_is_range || base_is_row_vector {
755 vec![1, total_out]
756 } else {
757 vec![total_out, 1]
758 }
759 } else {
760 let mut dims_out: Vec<(usize, usize, bool)> = selection_lengths
761 .iter()
762 .enumerate()
763 .map(|(d, &len)| (d, len, scalar_mask.get(d).copied().unwrap_or(false)))
764 .collect();
765 while dims_out.len() > 2
766 && dims_out
767 .last()
768 .map(|&(_, len, is_scalar)| len == 1 && is_scalar)
769 .unwrap_or(false)
770 {
771 dims_out.pop();
772 }
773 if dims_out.is_empty() {
774 vec![1, 1]
775 } else if dims_out.len() == 1 {
776 let (dim, len, _) = dims_out[0];
777 if dim == 1 {
778 vec![1, len]
779 } else {
780 vec![len, 1]
781 }
782 } else {
783 dims_out.into_iter().map(|(_, len, _)| len).collect()
784 }
785 };
786 Ok(IndexPlan::new(
787 indices,
788 output_shape,
789 selection_lengths,
790 spec.dims,
791 planned_shape,
792 ))
793}
794
795#[cfg(test)]
796mod tests {
797 use super::{
798 build_expr_index_plan, build_index_plan, build_sparse_assignment_plan, ExprPlanSpec,
799 };
800 use crate::indexing::selectors::{build_slice_selectors, SliceSelector};
801 use crate::indexing::EndExpr;
802 use runmat_value::{IntegerStorage, LogicalArray, Tensor, Value};
803
804 #[test]
805 fn sparse_assignment_plan_expands_numeric_dimensions_but_keeps_colon_at_old_extent() {
806 let selectors = vec![SliceSelector::Indices(vec![3, 4]), SliceSelector::Colon];
807 let plan = build_sparse_assignment_plan(&selectors, 2, &[2, 2])
808 .expect("sparse assignment plan should grow rows");
809 assert_eq!(plan.base_shape, vec![4, 2]);
810 assert_eq!(plan.selection_lengths, vec![2, 2]);
811 assert_eq!(plan.indices, vec![2, 3, 6, 7]);
812 }
813
814 #[test]
815 fn plain_and_expr_linear_range_plans_match() {
816 futures::executor::block_on(async {
817 let shape = vec![1, 10];
818 let numeric = vec![Value::Tensor(
819 Tensor::new(vec![2.0, 4.0, 6.0, 8.0], vec![1, 4]).unwrap(),
820 )];
821 let plain_selectors = build_slice_selectors(1, 0, 0, &numeric, &shape)
822 .await
823 .unwrap();
824 let plain = build_index_plan(&plain_selectors, 1, &shape).unwrap();
825 let expr = build_expr_index_plan(
826 ExprPlanSpec {
827 dims: 1,
828 colon_mask: 0,
829 end_mask: 0,
830 range_dims: &[0],
831 range_params: &[(2.0, 2.0)],
832 range_start_exprs: &[None],
833 range_step_exprs: &[None],
834 range_end_exprs: &[EndExpr::Sub(
835 Box::new(EndExpr::End),
836 Box::new(EndExpr::Const(1.0)),
837 )],
838 numeric: &[],
839 shape: &shape,
840 },
841 |dim_len, expr| {
842 let expr = expr.clone();
843 async move {
844 Ok(match &expr {
845 EndExpr::End => dim_len as f64,
846 EndExpr::Const(value) => *value,
847 EndExpr::Sub(lhs, rhs) => {
848 let lhs_val = match lhs.as_ref() {
849 EndExpr::End => dim_len as f64,
850 EndExpr::Const(value) => *value,
851 other => panic!("unsupported lhs expr: {other:?}"),
852 };
853 let rhs_val = match rhs.as_ref() {
854 EndExpr::Const(value) => *value,
855 other => panic!("unsupported rhs expr: {other:?}"),
856 };
857 lhs_val - rhs_val
858 }
859 other => panic!("unsupported expr: {other:?}"),
860 })
861 }
862 },
863 )
864 .await
865 .unwrap();
866 assert_eq!(plain.indices, expr.indices);
867 assert_eq!(plain.output_shape, expr.output_shape);
868 assert_eq!(plain.selection_lengths, expr.selection_lengths);
869 assert_eq!(plain.properties.full_row, expr.properties.full_row);
870 assert_eq!(plain.properties.full_column, expr.properties.full_column);
871 })
872 }
873
874 #[test]
875 fn expr_integer_index_vectors_use_exact_storage_for_all_classes() {
876 macro_rules! assert_indices {
877 ($storage:expr) => {{
878 let indices =
879 Tensor::new_integer($storage, vec![1, 2]).expect("typed integer index tensor");
880 let numeric = vec![Value::Tensor(indices)];
881 let plan = futures::executor::block_on(build_expr_index_plan(
882 ExprPlanSpec {
883 dims: 1,
884 colon_mask: 0,
885 end_mask: 0,
886 range_dims: &[],
887 range_params: &[],
888 range_start_exprs: &[],
889 range_step_exprs: &[],
890 range_end_exprs: &[],
891 numeric: &numeric,
892 shape: &[1, 2],
893 },
894 |_, _| async { Ok(0.0) },
895 ))
896 .expect("exact integer vector index plan");
897 assert_eq!(plan.indices, vec![0, 1]);
898 }};
899 }
900
901 assert_indices!(IntegerStorage::I8(vec![1, 2]));
902 assert_indices!(IntegerStorage::I16(vec![1, 2]));
903 assert_indices!(IntegerStorage::I32(vec![1, 2]));
904 assert_indices!(IntegerStorage::I64(vec![1, 2]));
905 assert_indices!(IntegerStorage::U8(vec![1, 2]));
906 assert_indices!(IntegerStorage::U16(vec![1, 2]));
907 assert_indices!(IntegerStorage::U32(vec![1, 2]));
908 assert_indices!(IntegerStorage::U64(vec![1, 2]));
909 }
910
911 #[test]
912 fn expr_integer_index_vectors_reject_wide_values() {
913 let indices = Tensor::new_integer(IntegerStorage::U64(vec![1, u64::MAX]), vec![1, 2])
914 .expect("typed integer index tensor");
915 let numeric = vec![Value::Tensor(indices)];
916
917 let err = futures::executor::block_on(build_expr_index_plan(
918 ExprPlanSpec {
919 dims: 1,
920 colon_mask: 0,
921 end_mask: 0,
922 range_dims: &[],
923 range_params: &[],
924 range_start_exprs: &[],
925 range_step_exprs: &[],
926 range_end_exprs: &[],
927 numeric: &numeric,
928 shape: &[1, 2],
929 },
930 |_, _| async { Ok(0.0) },
931 ))
932 .expect_err("wide exact integer index must not use its float mirror");
933 assert_eq!(err.identifier(), Some("RunMat:IndexOutOfBounds"));
934 }
935
936 #[test]
937 fn plain_and_expr_column_plans_match_properties() {
938 futures::executor::block_on(async {
939 let shape = vec![3, 4];
940 let numeric = vec![Value::Num(3.0)];
941 let plain_selectors = build_slice_selectors(2, 1, 0, &numeric, &shape)
942 .await
943 .unwrap();
944 let plain = build_index_plan(&plain_selectors, 2, &shape).unwrap();
945 let expr = build_expr_index_plan(
946 ExprPlanSpec {
947 dims: 2,
948 colon_mask: 1,
949 end_mask: 0,
950 range_dims: &[],
951 range_params: &[],
952 range_start_exprs: &[],
953 range_step_exprs: &[],
954 range_end_exprs: &[],
955 numeric: &numeric,
956 shape: &shape,
957 },
958 |_dim_len, _expr| async move { unreachable!() },
959 )
960 .await
961 .unwrap();
962 assert_eq!(plain.indices, expr.indices);
963 assert_eq!(plain.properties.full_column, Some(2));
964 assert_eq!(plain.properties.full_column, expr.properties.full_column);
965 assert_eq!(plain.properties.full_row, expr.properties.full_row);
966 })
967 }
968
969 #[test]
970 fn expr_linear_range_on_column_vector_uses_range_shape() {
971 futures::executor::block_on(async {
972 let plan = build_expr_index_plan(
973 ExprPlanSpec {
974 dims: 1,
975 colon_mask: 0,
976 end_mask: 0,
977 range_dims: &[0],
978 range_params: &[(1.0, 1.0)],
979 range_start_exprs: &[None],
980 range_step_exprs: &[None],
981 range_end_exprs: &[EndExpr::Var(0)],
982 numeric: &[],
983 shape: &[10, 1],
984 },
985 |_dim_len, expr| {
986 let expr = expr.clone();
987 async move {
988 match expr {
989 EndExpr::Var(_) => Ok(6.0),
990 other => panic!("unsupported expr: {other:?}"),
991 }
992 }
993 },
994 )
995 .await
996 .unwrap();
997 assert_eq!(plan.indices, vec![0, 1, 2, 3, 4, 5]);
998 assert_eq!(plan.output_shape, vec![1, 6]);
999 assert_eq!(plan.selection_lengths, vec![6]);
1000 })
1001 }
1002
1003 #[test]
1004 fn expr_empty_linear_range_uses_range_shape() {
1005 futures::executor::block_on(async {
1006 let plan = build_expr_index_plan(
1007 ExprPlanSpec {
1008 dims: 1,
1009 colon_mask: 0,
1010 end_mask: 0,
1011 range_dims: &[0],
1012 range_params: &[(1.0, 1.0)],
1013 range_start_exprs: &[None],
1014 range_step_exprs: &[None],
1015 range_end_exprs: &[EndExpr::Var(0)],
1016 numeric: &[],
1017 shape: &[10, 1],
1018 },
1019 |_dim_len, expr| {
1020 let expr = expr.clone();
1021 async move {
1022 match expr {
1023 EndExpr::Var(_) => Ok(0.0),
1024 other => panic!("unsupported expr: {other:?}"),
1025 }
1026 }
1027 },
1028 )
1029 .await
1030 .unwrap();
1031 assert!(plan.indices.is_empty());
1032 assert_eq!(plan.output_shape, vec![1, 0]);
1033 assert_eq!(plan.selection_lengths, vec![0]);
1034 })
1035 }
1036
1037 #[test]
1038 fn expr_plan_rejects_range_dim_conflicting_with_colon_mask() {
1039 futures::executor::block_on(async {
1040 let err = build_expr_index_plan(
1041 ExprPlanSpec {
1042 dims: 2,
1043 colon_mask: 0b01,
1044 end_mask: 0,
1045 range_dims: &[0],
1046 range_params: &[(1.0, 1.0)],
1047 range_start_exprs: &[None],
1048 range_step_exprs: &[None],
1049 range_end_exprs: &[EndExpr::End],
1050 numeric: &[Value::Num(1.0)],
1051 shape: &[3, 3],
1052 },
1053 |_dim_len, _expr| async move { unreachable!() },
1054 )
1055 .await
1056 .expect_err("range/colon conflict should fail");
1057 assert_eq!(err.identifier(), Some("RunMat:InvalidRangeSelectorPlan"));
1058 })
1059 }
1060
1061 #[test]
1062 fn expr_plan_rejects_range_dim_conflicting_with_end_mask() {
1063 futures::executor::block_on(async {
1064 let err = build_expr_index_plan(
1065 ExprPlanSpec {
1066 dims: 2,
1067 colon_mask: 0,
1068 end_mask: 0b10,
1069 range_dims: &[1],
1070 range_params: &[(1.0, 1.0)],
1071 range_start_exprs: &[None],
1072 range_step_exprs: &[None],
1073 range_end_exprs: &[EndExpr::End],
1074 numeric: &[Value::Num(1.0)],
1075 shape: &[3, 3],
1076 },
1077 |_dim_len, _expr| async move { unreachable!() },
1078 )
1079 .await
1080 .expect_err("range/end conflict should fail");
1081 assert_eq!(err.identifier(), Some("RunMat:InvalidRangeSelectorPlan"));
1082 })
1083 }
1084
1085 #[test]
1086 fn expr_plan_rejects_duplicate_range_dims() {
1087 futures::executor::block_on(async {
1088 let err = build_expr_index_plan(
1089 ExprPlanSpec {
1090 dims: 2,
1091 colon_mask: 0,
1092 end_mask: 0,
1093 range_dims: &[1, 1],
1094 range_params: &[(1.0, 1.0), (1.0, 1.0)],
1095 range_start_exprs: &[None, None],
1096 range_step_exprs: &[None, None],
1097 range_end_exprs: &[EndExpr::End, EndExpr::End],
1098 numeric: &[Value::Num(1.0)],
1099 shape: &[3, 3],
1100 },
1101 |_dim_len, _expr| async move { unreachable!() },
1102 )
1103 .await
1104 .expect_err("duplicate range dims should fail");
1105 assert_eq!(err.identifier(), Some("RunMat:InvalidRangeSelectorPlan"));
1106 })
1107 }
1108
1109 #[test]
1110 fn expr_plan_rejects_out_of_bounds_range_dim() {
1111 futures::executor::block_on(async {
1112 let err = build_expr_index_plan(
1113 ExprPlanSpec {
1114 dims: 2,
1115 colon_mask: 0,
1116 end_mask: 0,
1117 range_dims: &[2],
1118 range_params: &[(1.0, 1.0)],
1119 range_start_exprs: &[None],
1120 range_step_exprs: &[None],
1121 range_end_exprs: &[EndExpr::End],
1122 numeric: &[Value::Num(1.0), Value::Num(1.0)],
1123 shape: &[3, 3],
1124 },
1125 |_dim_len, _expr| async move { unreachable!() },
1126 )
1127 .await
1128 .expect_err("out-of-bounds range dim should fail");
1129 assert_eq!(err.identifier(), Some("RunMat:InvalidRangeSelectorDim"));
1130 })
1131 }
1132
1133 #[test]
1134 fn expr_plan_rejects_inconsistent_range_metadata_lengths() {
1135 futures::executor::block_on(async {
1136 let err = build_expr_index_plan(
1137 ExprPlanSpec {
1138 dims: 2,
1139 colon_mask: 0,
1140 end_mask: 0,
1141 range_dims: &[1],
1142 range_params: &[],
1143 range_start_exprs: &[None],
1144 range_step_exprs: &[None],
1145 range_end_exprs: &[EndExpr::End],
1146 numeric: &[Value::Num(1.0)],
1147 shape: &[3, 3],
1148 },
1149 |_dim_len, _expr| async move { unreachable!() },
1150 )
1151 .await
1152 .expect_err("inconsistent range metadata should fail");
1153 assert_eq!(err.identifier(), Some("RunMat:InvalidRangeSelectorPlan"));
1154 })
1155 }
1156
1157 #[test]
1158 #[cfg(target_pointer_width = "64")]
1159 fn index_plan_rejects_sparse_sized_indices_beyond_u32_storage() {
1160 let selectors = vec![SliceSelector::Scalar((u32::MAX as usize) + 2)];
1161 let err = build_index_plan(&selectors, 1, &[u32::MAX as usize + 2, 1])
1162 .expect_err("linear index should exceed u32 plan storage");
1163 assert_eq!(err.identifier(), Some("RunMat:IndexOutOfBounds"));
1164 }
1165
1166 #[test]
1167 fn index_plan_rejects_dimension_product_overflow() {
1168 let selectors = vec![SliceSelector::Colon];
1169 let err = build_index_plan(&selectors, 1, &[usize::MAX, 2])
1170 .expect_err("linearized sparse shape should overflow");
1171 assert_eq!(err.identifier(), Some("RunMat:IndexOutOfBounds"));
1172 }
1173
1174 #[test]
1175 fn expr_plan_supports_dims_beyond_mask_width() {
1176 futures::executor::block_on(async {
1177 let numeric = vec![Value::Num(1.0); 31];
1178 let shape = vec![1usize; 33];
1179 let plan = build_expr_index_plan(
1180 ExprPlanSpec {
1181 dims: 33,
1182 colon_mask: 0b1,
1183 end_mask: 0b10,
1184 range_dims: &[],
1185 range_params: &[],
1186 range_start_exprs: &[],
1187 range_step_exprs: &[],
1188 range_end_exprs: &[],
1189 numeric: &numeric,
1190 shape: &shape,
1191 },
1192 |_dim_len, _expr| async move { unreachable!() },
1193 )
1194 .await
1195 .expect("expr plan for dims beyond mask width");
1196 assert_eq!(plan.dims, 33);
1197 assert!(!plan.indices.is_empty());
1198 })
1199 }
1200
1201 #[test]
1202 fn expr_plan_tensor_selector_length_match_uses_numeric_indices() {
1203 futures::executor::block_on(async {
1204 let shape = vec![3, 2];
1205 let numeric = vec![Value::Tensor(
1206 Tensor::new(vec![2.0, 1.0, 3.0], vec![1, 3]).expect("selector tensor"),
1207 )];
1208 let plain_selectors = build_slice_selectors(2, 0b10, 0, &numeric, &shape)
1209 .await
1210 .unwrap();
1211 let plain = build_index_plan(&plain_selectors, 2, &shape).unwrap();
1212 let expr = build_expr_index_plan(
1213 ExprPlanSpec {
1214 dims: 2,
1215 colon_mask: 0b10,
1216 end_mask: 0,
1217 range_dims: &[],
1218 range_params: &[],
1219 range_start_exprs: &[],
1220 range_step_exprs: &[],
1221 range_end_exprs: &[],
1222 numeric: &numeric,
1223 shape: &shape,
1224 },
1225 |_dim_len, _expr| async move { unreachable!() },
1226 )
1227 .await
1228 .unwrap();
1229 assert_eq!(plain.indices, expr.indices);
1230 assert_eq!(plain.output_shape, expr.output_shape);
1231 assert_eq!(plain.selection_lengths, expr.selection_lengths);
1232 })
1233 }
1234
1235 #[test]
1236 fn expr_plan_logical_selector_remains_logical_mask() {
1237 futures::executor::block_on(async {
1238 let shape = vec![3, 2];
1239 let numeric = vec![Value::LogicalArray(
1240 LogicalArray::new(vec![0, 1, 1], vec![1, 3]).expect("logical selector"),
1241 )];
1242 let plain_selectors = build_slice_selectors(2, 0b10, 0, &numeric, &shape)
1243 .await
1244 .unwrap();
1245 let plain = build_index_plan(&plain_selectors, 2, &shape).unwrap();
1246 let expr = build_expr_index_plan(
1247 ExprPlanSpec {
1248 dims: 2,
1249 colon_mask: 0b10,
1250 end_mask: 0,
1251 range_dims: &[],
1252 range_params: &[],
1253 range_start_exprs: &[],
1254 range_step_exprs: &[],
1255 range_end_exprs: &[],
1256 numeric: &numeric,
1257 shape: &shape,
1258 },
1259 |_dim_len, _expr| async move { unreachable!() },
1260 )
1261 .await
1262 .unwrap();
1263 assert_eq!(plain.indices, expr.indices);
1264 assert_eq!(plain.output_shape, expr.output_shape);
1265 assert_eq!(plain.selection_lengths, expr.selection_lengths);
1266 })
1267 }
1268
1269 #[test]
1270 fn expr_plan_linear_tensor_selector_preserves_tensor_shape() {
1271 futures::executor::block_on(async {
1272 let shape = vec![1, 10];
1273 let numeric = vec![Value::Tensor(
1274 Tensor::new(vec![2.0, 4.0], vec![2, 1]).expect("selector tensor"),
1275 )];
1276 let plain_selectors = build_slice_selectors(1, 0, 0, &numeric, &shape)
1277 .await
1278 .unwrap();
1279 let plain = build_index_plan(&plain_selectors, 1, &shape).unwrap();
1280 let expr = build_expr_index_plan(
1281 ExprPlanSpec {
1282 dims: 1,
1283 colon_mask: 0,
1284 end_mask: 0,
1285 range_dims: &[],
1286 range_params: &[],
1287 range_start_exprs: &[],
1288 range_step_exprs: &[],
1289 range_end_exprs: &[],
1290 numeric: &numeric,
1291 shape: &shape,
1292 },
1293 |_dim_len, _expr| async move { unreachable!() },
1294 )
1295 .await
1296 .unwrap();
1297 assert_eq!(plain.indices, expr.indices);
1298 assert_eq!(plain.output_shape, expr.output_shape);
1299 assert_eq!(plain.selection_lengths, expr.selection_lengths);
1300 })
1301 }
1302
1303 #[test]
1304 fn expr_plan_linear_colon_selector_matches_plain_shape() {
1305 futures::executor::block_on(async {
1306 let shape = vec![1, 5];
1307 let plain = build_index_plan(&[SliceSelector::Colon], 1, &shape).unwrap();
1308 let expr = build_expr_index_plan(
1309 ExprPlanSpec {
1310 dims: 1,
1311 colon_mask: 0b1,
1312 end_mask: 0,
1313 range_dims: &[],
1314 range_params: &[],
1315 range_start_exprs: &[],
1316 range_step_exprs: &[],
1317 range_end_exprs: &[],
1318 numeric: &[],
1319 shape: &shape,
1320 },
1321 |_dim_len, _expr| async move { unreachable!() },
1322 )
1323 .await
1324 .unwrap();
1325 assert_eq!(plain.indices, expr.indices);
1326 assert_eq!(plain.output_shape, expr.output_shape);
1327 assert_eq!(plain.selection_lengths, expr.selection_lengths);
1328 })
1329 }
1330
1331 #[test]
1332 fn expr_plan_linear_logical_mask_matches_plain_shape() {
1333 futures::executor::block_on(async {
1334 let shape = vec![1, 5];
1335 let numeric = vec![Value::LogicalArray(
1336 LogicalArray::new(vec![1, 0, 1, 0, 1], vec![1, 5]).expect("logical selector"),
1337 )];
1338 let plain_selectors = build_slice_selectors(1, 0, 0, &numeric, &shape)
1339 .await
1340 .unwrap();
1341 let plain = build_index_plan(&plain_selectors, 1, &shape).unwrap();
1342 let expr = build_expr_index_plan(
1343 ExprPlanSpec {
1344 dims: 1,
1345 colon_mask: 0,
1346 end_mask: 0,
1347 range_dims: &[],
1348 range_params: &[],
1349 range_start_exprs: &[],
1350 range_step_exprs: &[],
1351 range_end_exprs: &[],
1352 numeric: &numeric,
1353 shape: &shape,
1354 },
1355 |_dim_len, _expr| async move { unreachable!() },
1356 )
1357 .await
1358 .unwrap();
1359 assert_eq!(plain.indices, expr.indices);
1360 assert_eq!(plain.output_shape, expr.output_shape);
1361 assert_eq!(plain.selection_lengths, expr.selection_lengths);
1362 })
1363 }
1364
1365 #[test]
1366 fn expr_plan_short_linear_logical_mask_matches_plain_column_shape() {
1367 futures::executor::block_on(async {
1368 let shape = vec![2, 3];
1369 let numeric = vec![Value::LogicalArray(
1370 LogicalArray::new(vec![0, 1, 1], vec![1, 3]).expect("logical selector"),
1371 )];
1372 let plain_selectors = build_slice_selectors(1, 0, 0, &numeric, &shape)
1373 .await
1374 .unwrap();
1375 let plain = build_index_plan(&plain_selectors, 1, &shape).unwrap();
1376 let expr = build_expr_index_plan(
1377 ExprPlanSpec {
1378 dims: 1,
1379 colon_mask: 0,
1380 end_mask: 0,
1381 range_dims: &[],
1382 range_params: &[],
1383 range_start_exprs: &[],
1384 range_step_exprs: &[],
1385 range_end_exprs: &[],
1386 numeric: &numeric,
1387 shape: &shape,
1388 },
1389 |_dim_len, _expr| async move { unreachable!() },
1390 )
1391 .await
1392 .unwrap();
1393 assert_eq!(plain.indices, vec![1, 2]);
1394 assert_eq!(plain.indices, expr.indices);
1395 assert_eq!(plain.output_shape, vec![2, 1]);
1396 assert_eq!(plain.output_shape, expr.output_shape);
1397 assert_eq!(plain.selection_lengths, expr.selection_lengths);
1398 })
1399 }
1400
1401 #[test]
1402 fn linear_empty_selector_uses_empty_column_shape() {
1403 let plan = build_index_plan(&[SliceSelector::Indices(Vec::new())], 1, &[1, 5])
1404 .expect("empty linear selector should build");
1405 assert!(plan.indices.is_empty());
1406 assert_eq!(plan.output_shape, vec![0, 1]);
1407 }
1408
1409 #[test]
1410 fn expr_plan_linear_empty_logical_mask_matches_plain_shape() {
1411 futures::executor::block_on(async {
1412 let shape = vec![1, 5];
1413 let numeric = vec![Value::LogicalArray(
1414 LogicalArray::new(vec![0, 0, 0, 0, 0], vec![1, 5]).expect("logical selector"),
1415 )];
1416 let plain_selectors = build_slice_selectors(1, 0, 0, &numeric, &shape)
1417 .await
1418 .unwrap();
1419 let plain = build_index_plan(&plain_selectors, 1, &shape).unwrap();
1420 let expr = build_expr_index_plan(
1421 ExprPlanSpec {
1422 dims: 1,
1423 colon_mask: 0,
1424 end_mask: 0,
1425 range_dims: &[],
1426 range_params: &[],
1427 range_start_exprs: &[],
1428 range_step_exprs: &[],
1429 range_end_exprs: &[],
1430 numeric: &numeric,
1431 shape: &shape,
1432 },
1433 |_dim_len, _expr| async move { unreachable!() },
1434 )
1435 .await
1436 .unwrap();
1437 assert!(plain.indices.is_empty());
1438 assert!(expr.indices.is_empty());
1439 assert_eq!(plain.output_shape, vec![0, 1]);
1440 assert_eq!(plain.output_shape, expr.output_shape);
1441 assert_eq!(plain.selection_lengths, expr.selection_lengths);
1442 })
1443 }
1444}