1use std::cmp::Ordering;
2use std::fmt::{Debug, Formatter};
3use std::num::{NonZeroI64, NonZeroUsize};
4use std::ops::{Add, AddAssign};
5
6use comemo::Tracked;
7use ecow::{eco_format, EcoString, EcoVec};
8use serde::{Deserialize, Serialize};
9use smallvec::SmallVec;
10use typst_syntax::{Span, Spanned};
11
12use crate::diag::{
13 bail, At, DeprecationSink, HintedStrResult, SourceDiagnostic, SourceResult, StrResult,
14};
15use crate::engine::Engine;
16use crate::foundations::{
17 cast, func, ops, repr, scope, ty, Args, Bytes, CastInfo, Context, Dict, FromValue,
18 Func, IntoValue, Reflect, Repr, Str, Value, Version,
19};
20
21#[macro_export]
23#[doc(hidden)]
24macro_rules! __array {
25 ($value:expr; $count:expr) => {
26 $crate::foundations::Array::from($crate::foundations::eco_vec![
27 $crate::foundations::IntoValue::into_value($value);
28 $count
29 ])
30 };
31
32 ($($value:expr),* $(,)?) => {
33 $crate::foundations::Array::from($crate::foundations::eco_vec![$(
34 $crate::foundations::IntoValue::into_value($value)
35 ),*])
36 };
37}
38
39#[doc(inline)]
40pub use crate::__array as array;
41
42#[ty(scope, cast)]
73#[derive(Default, Clone, PartialEq, Hash, Serialize, Deserialize)]
74#[serde(transparent)]
75pub struct Array(EcoVec<Value>);
76
77impl Array {
78 pub fn new() -> Self {
80 Self::default()
81 }
82
83 pub fn with_capacity(capacity: usize) -> Self {
85 Self(EcoVec::with_capacity(capacity))
86 }
87
88 pub fn is_empty(&self) -> bool {
90 self.0.is_empty()
91 }
92
93 pub fn as_slice(&self) -> &[Value] {
95 self.0.as_slice()
96 }
97
98 pub fn iter(&self) -> std::slice::Iter<Value> {
100 self.0.iter()
101 }
102
103 pub fn first_mut(&mut self) -> StrResult<&mut Value> {
105 self.0.make_mut().first_mut().ok_or_else(array_is_empty)
106 }
107
108 pub fn last_mut(&mut self) -> StrResult<&mut Value> {
110 self.0.make_mut().last_mut().ok_or_else(array_is_empty)
111 }
112
113 pub fn at_mut(&mut self, index: i64) -> StrResult<&mut Value> {
115 let len = self.len();
116 self.locate_opt(index, false)
117 .and_then(move |i| self.0.make_mut().get_mut(i))
118 .ok_or_else(|| out_of_bounds(index, len))
119 }
120
121 fn locate(&self, index: i64, end_ok: bool) -> StrResult<usize> {
123 self.locate_opt(index, end_ok)
124 .ok_or_else(|| out_of_bounds(index, self.len()))
125 }
126
127 fn locate_opt(&self, index: i64, end_ok: bool) -> Option<usize> {
131 let wrapped =
132 if index >= 0 { Some(index) } else { (self.len() as i64).checked_add(index) };
133
134 wrapped
135 .and_then(|v| usize::try_from(v).ok())
136 .filter(|&v| v < self.0.len() + end_ok as usize)
137 }
138
139 pub fn repeat(&self, n: usize) -> StrResult<Self> {
141 let count = self
142 .len()
143 .checked_mul(n)
144 .ok_or_else(|| format!("cannot repeat this array {n} times"))?;
145
146 Ok(self.iter().cloned().cycle().take(count).collect())
147 }
148
149 pub fn contains_impl(&self, value: &Value, sink: &mut dyn DeprecationSink) -> bool {
151 self.0.iter().any(|v| ops::equal(v, value, sink))
152 }
153}
154
155#[scope]
156impl Array {
157 #[func(constructor)]
168 pub fn construct(
169 value: ToArray,
171 ) -> Array {
172 value.0
173 }
174
175 #[func(title = "Length")]
177 pub fn len(&self) -> usize {
178 self.0.len()
179 }
180
181 #[func]
184 pub fn first(&self) -> StrResult<Value> {
185 self.0.first().cloned().ok_or_else(array_is_empty)
186 }
187
188 #[func]
191 pub fn last(&self) -> StrResult<Value> {
192 self.0.last().cloned().ok_or_else(array_is_empty)
193 }
194
195 #[func]
200 pub fn at(
201 &self,
202 index: i64,
205 #[named]
207 default: Option<Value>,
208 ) -> StrResult<Value> {
209 self.locate_opt(index, false)
210 .and_then(|i| self.0.get(i).cloned())
211 .or(default)
212 .ok_or_else(|| out_of_bounds_no_default(index, self.len()))
213 }
214
215 #[func]
217 pub fn push(
218 &mut self,
219 value: Value,
221 ) {
222 self.0.push(value);
223 }
224
225 #[func]
228 pub fn pop(&mut self) -> StrResult<Value> {
229 self.0.pop().ok_or_else(array_is_empty)
230 }
231
232 #[func]
238 pub fn insert(
239 &mut self,
240 index: i64,
243 value: Value,
245 ) -> StrResult<()> {
246 let i = self.locate(index, true)?;
247 self.0.insert(i, value);
248 Ok(())
249 }
250
251 #[func]
253 pub fn remove(
254 &mut self,
255 index: i64,
258 #[named]
260 default: Option<Value>,
261 ) -> StrResult<Value> {
262 self.locate_opt(index, false)
263 .map(|i| self.0.remove(i))
264 .or(default)
265 .ok_or_else(|| out_of_bounds_no_default(index, self.len()))
266 }
267
268 #[func]
271 pub fn slice(
272 &self,
273 start: i64,
275 #[default]
278 end: Option<i64>,
279 #[named]
282 count: Option<i64>,
283 ) -> StrResult<Array> {
284 let mut end = end;
285 if end.is_none() {
286 end = count.map(|c: i64| start + c);
287 }
288 let start = self.locate(start, true)?;
289 let end = self.locate(end.unwrap_or(self.len() as i64), true)?.max(start);
290 Ok(self.0[start..end].into())
291 }
292
293 #[func]
298 pub fn contains(
299 &self,
300 engine: &mut Engine,
301 span: Span,
302 value: Value,
304 ) -> bool {
305 self.contains_impl(&value, &mut (engine, span))
306 }
307
308 #[func]
311 pub fn find(
312 &self,
313 engine: &mut Engine,
314 context: Tracked<Context>,
315 searcher: Func,
317 ) -> SourceResult<Option<Value>> {
318 for item in self.iter() {
319 if searcher
320 .call(engine, context, [item.clone()])?
321 .cast::<bool>()
322 .at(searcher.span())?
323 {
324 return Ok(Some(item.clone()));
325 }
326 }
327 Ok(None)
328 }
329
330 #[func]
333 pub fn position(
334 &self,
335 engine: &mut Engine,
336 context: Tracked<Context>,
337 searcher: Func,
339 ) -> SourceResult<Option<i64>> {
340 for (i, item) in self.iter().enumerate() {
341 if searcher
342 .call(engine, context, [item.clone()])?
343 .cast::<bool>()
344 .at(searcher.span())?
345 {
346 return Ok(Some(i as i64));
347 }
348 }
349
350 Ok(None)
351 }
352
353 #[func]
370 pub fn range(
371 args: &mut Args,
372 #[external]
374 #[default]
375 start: i64,
376 #[external]
378 end: i64,
379 #[named]
381 #[default(NonZeroI64::new(1).unwrap())]
382 step: NonZeroI64,
383 ) -> SourceResult<Array> {
384 let first = args.expect::<i64>("end")?;
385 let (start, end) = match args.eat::<i64>()? {
386 Some(second) => (first, second),
387 None => (0, first),
388 };
389
390 let step = step.get();
391
392 let mut x = start;
393 let mut array = Self::new();
394
395 while x.cmp(&end) == 0.cmp(&step) {
396 array.push(x.into_value());
397 x += step;
398 }
399
400 Ok(array)
401 }
402
403 #[func]
406 pub fn filter(
407 &self,
408 engine: &mut Engine,
409 context: Tracked<Context>,
410 test: Func,
412 ) -> SourceResult<Array> {
413 let mut kept = EcoVec::new();
414 for item in self.iter() {
415 if test
416 .call(engine, context, [item.clone()])?
417 .cast::<bool>()
418 .at(test.span())?
419 {
420 kept.push(item.clone())
421 }
422 }
423 Ok(kept.into())
424 }
425
426 #[func]
429 pub fn map(
430 self,
431 engine: &mut Engine,
432 context: Tracked<Context>,
433 mapper: Func,
435 ) -> SourceResult<Array> {
436 self.into_iter()
437 .map(|item| mapper.call(engine, context, [item]))
438 .collect()
439 }
440
441 #[func]
447 pub fn enumerate(
448 self,
449 #[named]
451 #[default(0)]
452 start: i64,
453 ) -> StrResult<Array> {
454 self.into_iter()
455 .enumerate()
456 .map(|(i, value)| {
457 Ok(array![
458 start
459 .checked_add_unsigned(i as u64)
460 .ok_or("array index is too large")?,
461 value
462 ]
463 .into_value())
464 })
465 .collect()
466 }
467
468 #[func]
481 pub fn zip(
482 self,
483 args: &mut Args,
484 #[named]
488 #[default(false)]
489 exact: bool,
490 #[external]
492 #[variadic]
493 others: Vec<Array>,
494 ) -> SourceResult<Array> {
495 let remaining = args.remaining();
496
497 if remaining == 0 {
499 return Ok(self.into_iter().map(|item| array![item].into_value()).collect());
500 }
501
502 if remaining == 1 {
504 let Spanned { v: other, span: other_span } =
505 args.expect::<Spanned<Array>>("others")?;
506 if exact && self.len() != other.len() {
507 bail!(
508 other_span,
509 "second array has different length ({}) from first array ({})",
510 other.len(),
511 self.len()
512 );
513 }
514 return Ok(self
515 .into_iter()
516 .zip(other)
517 .map(|(first, second)| array![first, second].into_value())
518 .collect());
519 }
520
521 let mut out = Self::with_capacity(self.len());
523 let arrays = args.all::<Spanned<Array>>()?;
524 if exact {
525 let errs = arrays
526 .iter()
527 .filter(|sp| sp.v.len() != self.len())
528 .map(|Spanned { v, span }| {
529 SourceDiagnostic::error(
530 *span,
531 eco_format!(
532 "array has different length ({}) from first array ({})",
533 v.len(),
534 self.len()
535 ),
536 )
537 })
538 .collect::<EcoVec<_>>();
539 if !errs.is_empty() {
540 return Err(errs);
541 }
542 }
543
544 let mut iterators =
545 arrays.into_iter().map(|i| i.v.into_iter()).collect::<Vec<_>>();
546
547 for this in self {
548 let mut row = Self::with_capacity(1 + iterators.len());
549 row.push(this.clone());
550
551 for iterator in &mut iterators {
552 let Some(item) = iterator.next() else {
553 return Ok(out);
554 };
555
556 row.push(item);
557 }
558
559 out.push(row.into_value());
560 }
561
562 Ok(out)
563 }
564
565 #[func]
567 pub fn fold(
568 self,
569 engine: &mut Engine,
570 context: Tracked<Context>,
571 init: Value,
573 folder: Func,
576 ) -> SourceResult<Value> {
577 let mut acc = init;
578 for item in self {
579 acc = folder.call(engine, context, [acc, item])?;
580 }
581 Ok(acc)
582 }
583
584 #[func]
586 pub fn sum(
587 self,
588 engine: &mut Engine,
589 span: Span,
590 #[named]
593 default: Option<Value>,
594 ) -> HintedStrResult<Value> {
595 let mut iter = self.into_iter();
596 let mut acc = iter
597 .next()
598 .or(default)
599 .ok_or("cannot calculate sum of empty array with no default")?;
600 for item in iter {
601 acc = ops::add(acc, item, &mut (&mut *engine, span))?;
602 }
603 Ok(acc)
604 }
605
606 #[func]
609 pub fn product(
610 self,
611 #[named]
614 default: Option<Value>,
615 ) -> HintedStrResult<Value> {
616 let mut iter = self.into_iter();
617 let mut acc = iter
618 .next()
619 .or(default)
620 .ok_or("cannot calculate product of empty array with no default")?;
621 for item in iter {
622 acc = ops::mul(acc, item)?;
623 }
624 Ok(acc)
625 }
626
627 #[func]
629 pub fn any(
630 self,
631 engine: &mut Engine,
632 context: Tracked<Context>,
633 test: Func,
635 ) -> SourceResult<bool> {
636 for item in self {
637 if test.call(engine, context, [item])?.cast::<bool>().at(test.span())? {
638 return Ok(true);
639 }
640 }
641
642 Ok(false)
643 }
644
645 #[func]
647 pub fn all(
648 self,
649 engine: &mut Engine,
650 context: Tracked<Context>,
651 test: Func,
653 ) -> SourceResult<bool> {
654 for item in self {
655 if !test.call(engine, context, [item])?.cast::<bool>().at(test.span())? {
656 return Ok(false);
657 }
658 }
659
660 Ok(true)
661 }
662
663 #[func]
665 pub fn flatten(self) -> Array {
666 let mut flat = EcoVec::with_capacity(self.0.len());
667 for item in self {
668 if let Value::Array(nested) = item {
669 flat.extend(nested.flatten());
670 } else {
671 flat.push(item);
672 }
673 }
674 flat.into()
675 }
676
677 #[func(title = "Reverse")]
679 pub fn rev(self) -> Array {
680 self.into_iter().rev().collect()
681 }
682
683 #[func]
685 pub fn split(
686 &self,
687 at: Value,
689 ) -> Array {
690 self.as_slice()
691 .split(|value| *value == at)
692 .map(|subslice| Value::Array(subslice.iter().cloned().collect()))
693 .collect()
694 }
695
696 #[func]
698 pub fn join(
699 self,
700 engine: &mut Engine,
701 span: Span,
702 #[default]
704 separator: Option<Value>,
705 #[named]
707 last: Option<Value>,
708 ) -> StrResult<Value> {
709 let len = self.0.len();
710 let separator = separator.unwrap_or(Value::None);
711
712 let mut last = last;
713 let mut result = Value::None;
714 for (i, value) in self.into_iter().enumerate() {
715 if i > 0 {
716 if i + 1 == len && last.is_some() {
717 result = ops::join(
718 result,
719 last.take().unwrap(),
720 &mut (&mut *engine, span),
721 )?;
722 } else {
723 result =
724 ops::join(result, separator.clone(), &mut (&mut *engine, span))?;
725 }
726 }
727
728 result = ops::join(result, value, &mut (&mut *engine, span))?;
729 }
730
731 Ok(result)
732 }
733
734 #[func]
737 pub fn intersperse(
738 self,
739 separator: Value,
741 ) -> Array {
742 let size = match self.len() {
745 0 => return Array::new(),
746 n => (2 * n) - 1,
747 };
748 let mut vec = EcoVec::with_capacity(size);
749 let mut iter = self.into_iter();
750
751 if let Some(first) = iter.next() {
752 vec.push(first);
753 }
754
755 for value in iter {
756 vec.push(separator.clone());
757 vec.push(value);
758 }
759
760 Array(vec)
761 }
762
763 #[func]
776 pub fn chunks(
777 self,
778 chunk_size: NonZeroUsize,
780 #[named]
782 #[default(false)]
783 exact: bool,
784 ) -> Array {
785 let to_array = |chunk| Array::from(chunk).into_value();
786 if exact {
787 self.0.chunks_exact(chunk_size.get()).map(to_array).collect()
788 } else {
789 self.0.chunks(chunk_size.get()).map(to_array).collect()
790 }
791 }
792
793 #[func]
802 pub fn windows(
803 self,
804 window_size: NonZeroUsize,
806 ) -> Array {
807 self.0
808 .windows(window_size.get())
809 .map(|window| Array::from(window).into_value())
810 .collect()
811 }
812
813 #[func]
832 pub fn sorted(
833 self,
834 engine: &mut Engine,
835 context: Tracked<Context>,
836 span: Span,
837 #[named]
840 key: Option<Func>,
841 ) -> SourceResult<Array> {
842 let mut result = Ok(());
843 let mut vec = self.0;
844 let mut key_of = |x: Value| match &key {
845 Some(f) => f.call(engine, context, [x]),
848 None => Ok(x),
849 };
850 vec.make_mut().sort_by(|a, b| {
851 match (key_of(a.clone()), key_of(b.clone())) {
853 (Ok(a), Ok(b)) => ops::compare(&a, &b).unwrap_or_else(|err| {
854 if result.is_ok() {
855 result = Err(err).at(span);
856 }
857 Ordering::Equal
858 }),
859 (Err(e), _) | (_, Err(e)) => {
860 if result.is_ok() {
861 result = Err(e);
862 }
863 Ordering::Equal
864 }
865 }
866 });
867 result.map(|_| vec.into())
868 }
869
870 #[func(title = "Deduplicate")]
879 pub fn dedup(
880 self,
881 engine: &mut Engine,
882 context: Tracked<Context>,
883 span: Span,
884 #[named]
887 key: Option<Func>,
888 ) -> SourceResult<Array> {
889 let mut out = EcoVec::with_capacity(self.0.len());
890 let key_of = |engine: &mut Engine, x: Value| match &key {
891 Some(f) => f.call(engine, context, [x]),
894 None => Ok(x),
895 };
896
897 'outer: for value in self {
901 let key = key_of(&mut *engine, value.clone())?;
902 if out.is_empty() {
903 out.push(value);
904 continue;
905 }
906
907 for second in out.iter() {
908 if ops::equal(
909 &key,
910 &key_of(&mut *engine, second.clone())?,
911 &mut (&mut *engine, span),
912 ) {
913 continue 'outer;
914 }
915 }
916
917 out.push(value);
918 }
919
920 Ok(Self(out))
921 }
922
923 #[func]
936 pub fn to_dict(self) -> StrResult<Dict> {
937 self.into_iter()
938 .map(|value| {
939 let value_ty = value.ty();
940 let pair = value.cast::<Array>().map_err(|_| {
941 eco_format!("expected (str, any) pairs, found {}", value_ty)
942 })?;
943 if let [key, value] = pair.as_slice() {
944 let key = key.clone().cast::<Str>().map_err(|_| {
945 eco_format!("expected key of type str, found {}", value.ty())
946 })?;
947 Ok((key, value.clone()))
948 } else {
949 bail!("expected pairs of length 2, found length {}", pair.len());
950 }
951 })
952 .collect()
953 }
954
955 #[func]
968 pub fn reduce(
969 self,
970 engine: &mut Engine,
971 context: Tracked<Context>,
972 reducer: Func,
975 ) -> SourceResult<Value> {
976 let mut iter = self.into_iter();
977 let mut acc = iter.next().unwrap_or_default();
978 for item in iter {
979 acc = reducer.call(engine, context, [acc, item])?;
980 }
981 Ok(acc)
982 }
983}
984
985pub struct ToArray(Array);
987
988cast! {
989 ToArray,
990 v: Array => Self(v),
991 v: Bytes => Self(v.iter().map(|&b| Value::Int(b.into())).collect()),
992 v: Version => Self(v.values().iter().map(|&v| Value::Int(v as i64)).collect())
993}
994
995impl Debug for Array {
996 fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
997 f.debug_list().entries(&self.0).finish()
998 }
999}
1000
1001impl Repr for Array {
1002 fn repr(&self) -> EcoString {
1003 let max = 40;
1004 let mut pieces: Vec<_> = self
1005 .iter()
1006 .take(max)
1007 .map(|value| eco_format!("{}", value.repr()))
1008 .collect();
1009 if self.len() > max {
1010 pieces.push(eco_format!(".. ({} items omitted)", self.len() - max));
1011 }
1012 repr::pretty_array_like(&pieces, self.len() == 1).into()
1013 }
1014}
1015
1016impl Add for Array {
1017 type Output = Self;
1018
1019 fn add(mut self, rhs: Array) -> Self::Output {
1020 self += rhs;
1021 self
1022 }
1023}
1024
1025impl AddAssign for Array {
1026 fn add_assign(&mut self, rhs: Self) {
1027 self.0.extend(rhs.0);
1028 }
1029}
1030
1031impl Extend<Value> for Array {
1032 fn extend<T: IntoIterator<Item = Value>>(&mut self, iter: T) {
1033 self.0.extend(iter);
1034 }
1035}
1036
1037impl FromIterator<Value> for Array {
1038 fn from_iter<T: IntoIterator<Item = Value>>(iter: T) -> Self {
1039 Self(iter.into_iter().collect())
1040 }
1041}
1042
1043impl IntoIterator for Array {
1044 type Item = Value;
1045 type IntoIter = ecow::vec::IntoIter<Value>;
1046
1047 fn into_iter(self) -> Self::IntoIter {
1048 self.0.into_iter()
1049 }
1050}
1051
1052impl<'a> IntoIterator for &'a Array {
1053 type Item = &'a Value;
1054 type IntoIter = std::slice::Iter<'a, Value>;
1055
1056 fn into_iter(self) -> Self::IntoIter {
1057 self.iter()
1058 }
1059}
1060
1061impl From<EcoVec<Value>> for Array {
1062 fn from(v: EcoVec<Value>) -> Self {
1063 Array(v)
1064 }
1065}
1066
1067impl From<&[Value]> for Array {
1068 fn from(v: &[Value]) -> Self {
1069 Array(v.into())
1070 }
1071}
1072
1073impl<T> Reflect for Vec<T> {
1074 fn input() -> CastInfo {
1075 Array::input()
1076 }
1077
1078 fn output() -> CastInfo {
1079 Array::output()
1080 }
1081
1082 fn castable(value: &Value) -> bool {
1083 Array::castable(value)
1084 }
1085}
1086
1087impl<T: Reflect, const N: usize> Reflect for SmallVec<[T; N]> {
1088 fn input() -> CastInfo {
1089 Array::input()
1090 }
1091
1092 fn output() -> CastInfo {
1093 Array::output()
1094 }
1095
1096 fn castable(value: &Value) -> bool {
1097 Array::castable(value)
1098 }
1099}
1100
1101impl<T: IntoValue> IntoValue for Vec<T> {
1102 fn into_value(self) -> Value {
1103 Value::Array(self.into_iter().map(IntoValue::into_value).collect())
1104 }
1105}
1106
1107impl<T: IntoValue, const N: usize> IntoValue for SmallVec<[T; N]> {
1108 fn into_value(self) -> Value {
1109 Value::Array(self.into_iter().map(IntoValue::into_value).collect())
1110 }
1111}
1112
1113impl<T: FromValue> FromValue for Vec<T> {
1114 fn from_value(value: Value) -> HintedStrResult<Self> {
1115 value.cast::<Array>()?.into_iter().map(Value::cast).collect()
1116 }
1117}
1118
1119impl<T: FromValue, const N: usize> FromValue for SmallVec<[T; N]> {
1120 fn from_value(value: Value) -> HintedStrResult<Self> {
1121 value.cast::<Array>()?.into_iter().map(Value::cast).collect()
1122 }
1123}
1124
1125#[derive(Debug, Clone, PartialEq, Hash)]
1127pub struct OneOrMultiple<T>(pub Vec<T>);
1128
1129impl<T: Reflect> Reflect for OneOrMultiple<T> {
1130 fn input() -> CastInfo {
1131 T::input() + Array::input()
1132 }
1133
1134 fn output() -> CastInfo {
1135 T::output() + Array::output()
1136 }
1137
1138 fn castable(value: &Value) -> bool {
1139 Array::castable(value) || T::castable(value)
1140 }
1141}
1142
1143impl<T: IntoValue + Clone> IntoValue for OneOrMultiple<T> {
1144 fn into_value(self) -> Value {
1145 self.0.into_value()
1146 }
1147}
1148
1149impl<T: FromValue> FromValue for OneOrMultiple<T> {
1150 fn from_value(value: Value) -> HintedStrResult<Self> {
1151 if T::castable(&value) {
1152 return Ok(Self(vec![T::from_value(value)?]));
1153 }
1154 if Array::castable(&value) {
1155 return Ok(Self(
1156 Array::from_value(value)?
1157 .into_iter()
1158 .map(|value| T::from_value(value))
1159 .collect::<HintedStrResult<_>>()?,
1160 ));
1161 }
1162 Err(Self::error(&value))
1163 }
1164}
1165
1166impl<T> Default for OneOrMultiple<T> {
1167 fn default() -> Self {
1168 Self(vec![])
1169 }
1170}
1171
1172#[cold]
1174fn array_is_empty() -> EcoString {
1175 "array is empty".into()
1176}
1177
1178#[cold]
1180fn out_of_bounds(index: i64, len: usize) -> EcoString {
1181 eco_format!("array index out of bounds (index: {index}, len: {len})")
1182}
1183
1184#[cold]
1186fn out_of_bounds_no_default(index: i64, len: usize) -> EcoString {
1187 eco_format!(
1188 "array index out of bounds (index: {index}, len: {len}) \
1189 and no default value was specified",
1190 )
1191}