1use crate::error::{Error, ErrorKind, Result, Span};
10
11pub const MAX_ELEMENTS: usize = 1 << 32;
18
19pub fn elements(shape: &[usize], span: Span) -> Result<usize> {
24 if shape.contains(&0) {
25 return Ok(0);
26 }
27 let mut n: u128 = 1;
28 for &d in shape {
29 n *= d as u128;
30 if n > MAX_ELEMENTS as u128 {
31 return Err(too_many(n, Some(shape), span));
32 }
33 }
34 Ok(n as usize)
35}
36
37pub fn count(n: u128, span: Span) -> Result<usize> {
39 if n > MAX_ELEMENTS as u128 {
40 return Err(too_many(n, None, span));
41 }
42 Ok(n as usize)
43}
44
45fn too_many(n: u128, shape: Option<&[usize]>, span: Span) -> Error {
46 let e = Error::new(
47 ErrorKind::Limit,
48 format!("a result of {n} elements is past the {MAX_ELEMENTS}-element ceiling"),
49 Some(span),
50 );
51 match shape {
52 None => e,
53 Some(s) => {
54 let dims: Vec<String> = s.iter().map(usize::to_string).collect();
55 e.note(format!("the shape asked for is {}", dims.join(" ")))
56 }
57 }
58}
59
60#[cfg(test)]
61mod tests {
62 use super::*;
63
64 const SPAN: Span = Span { start: 0, end: 0 };
65
66 #[test]
67 fn an_ordinary_shape_passes() {
68 assert_eq!(elements(&[2, 3, 4], SPAN).unwrap(), 24);
69 assert_eq!(elements(&[], SPAN).unwrap(), 1);
70 }
71
72 #[test]
73 fn an_empty_axis_beats_the_ceiling() {
74 assert_eq!(elements(&[usize::MAX, 0], SPAN).unwrap(), 0);
75 }
76
77 #[test]
78 fn a_product_that_would_wrap_is_refused() {
79 let e = elements(&[1 << 32, 1 << 32], SPAN).unwrap_err();
81 assert_eq!(e.kind, ErrorKind::Limit);
82 assert!(e.msg.contains("18446744073709551616"), "{}", e.msg);
83 }
84
85 #[test]
86 fn the_ceiling_itself_is_allowed() {
87 assert!(elements(&[MAX_ELEMENTS], SPAN).is_ok());
88 assert!(elements(&[MAX_ELEMENTS + 1], SPAN).is_err());
89 }
90}