diff --git a/docs/kcl-lang/arrays.md b/docs/kcl-lang/arrays.md index 01add0efc..a07129eac 100644 --- a/docs/kcl-lang/arrays.md +++ b/docs/kcl-lang/arrays.md @@ -16,15 +16,16 @@ There are some useful functions for working with arrays in the standard library, Arrays have their own types: `[T]` where `T` is the type of the elements of the array, for example, `[string]` means an array of `string`s and `[any]` means an array of any values. -Array types can also include length information: `[T; n]` denotes an array of length `n` (where `n` is a number literal) and `[T; 1+]` denotes an array whose length is at least one (i.e., a non-empty array). E.g., `[string; 1+]` and `[number(mm); 3]` are valid array types. +Array types can also include length information: `[T; n]` denotes an array of length `n` (where `n` is a number literal) and `[T; n+]` denotes an array whose length is at least `n`. The common case for that is `[T; 1+]`, i.e., a non-empty array. E.g., `[string; 1+]` and `[number(mm); 3]` are valid array types. ## Ranges -Ranges are a succinct way to create an array of sequential numbers. The syntax is `[start .. end]` where `start` and `end` evaluate to whole numbers (integers). Ranges are inclusive of the start and end. The end must be greater than the start. Examples: +Ranges are a succinct way to create an array of sequential numbers. The syntax is `[start .. end]` where `start` and `end` evaluate to whole numbers (integers). Ranges are inclusive of the start and end. The end must be greater than the start. A range which is exclusive of its end is written with ` Self { RuntimeType::Array( Box::new(RuntimeType::Primitive(PrimitiveType::Sketch)), - ArrayLen::NonEmpty, + ArrayLen::Minimum(1), ) } @@ -52,7 +52,7 @@ impl RuntimeType { pub fn solids() -> Self { RuntimeType::Array( Box::new(RuntimeType::Primitive(PrimitiveType::Solid)), - ArrayLen::NonEmpty, + ArrayLen::Minimum(1), ) } @@ -208,8 +208,13 @@ impl RuntimeType { pub fn human_friendly_type(&self) -> String { match self { RuntimeType::Primitive(ty) => ty.to_string(), - RuntimeType::Array(ty, ArrayLen::None) => format!("an array of {}", ty.display_multiple()), - RuntimeType::Array(ty, ArrayLen::NonEmpty) => format!("one or more {}", ty.display_multiple()), + RuntimeType::Array(ty, ArrayLen::None | ArrayLen::Minimum(0)) => { + format!("an array of {}", ty.display_multiple()) + } + RuntimeType::Array(ty, ArrayLen::Minimum(1)) => format!("one or more {}", ty.display_multiple()), + RuntimeType::Array(ty, ArrayLen::Minimum(n)) => { + format!("an array of {n} or more {}", ty.display_multiple()) + } RuntimeType::Array(ty, ArrayLen::Known(n)) => format!("an array of {n} {}", ty.display_multiple()), RuntimeType::Union(tys) => tys .iter() @@ -292,7 +297,7 @@ impl fmt::Display for RuntimeType { RuntimeType::Primitive(t) => t.fmt(f), RuntimeType::Array(t, l) => match l { ArrayLen::None => write!(f, "[{t}]"), - ArrayLen::NonEmpty => write!(f, "[{t}; 1+]"), + ArrayLen::Minimum(n) => write!(f, "[{t}; {n}+]"), ArrayLen::Known(n) => write!(f, "[{t}; {n}]"), }, RuntimeType::Tuple(ts) => write!( @@ -321,7 +326,7 @@ impl fmt::Display for RuntimeType { #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, ts_rs::TS, JsonSchema)] pub enum ArrayLen { None, - NonEmpty, + Minimum(usize), Known(usize), } @@ -329,8 +334,9 @@ impl ArrayLen { pub fn subtype(self, other: ArrayLen) -> bool { match (self, other) { (_, ArrayLen::None) => true, - (ArrayLen::NonEmpty, ArrayLen::NonEmpty) => true, - (ArrayLen::Known(size), ArrayLen::NonEmpty) if size > 0 => true, + (ArrayLen::Minimum(s1), ArrayLen::Minimum(s2)) if s1 >= s2 => true, + (ArrayLen::Known(s1), ArrayLen::Minimum(s2)) if s1 >= s2 => true, + (ArrayLen::None, ArrayLen::Minimum(0)) => true, (ArrayLen::Known(s1), ArrayLen::Known(s2)) if s1 == s2 => true, _ => false, } @@ -340,7 +346,7 @@ impl ArrayLen { fn satisfied(self, len: usize, allow_shrink: bool) -> Option { match self { ArrayLen::None => Some(len), - ArrayLen::NonEmpty => (len > 0).then_some(len), + ArrayLen::Minimum(s) => (len >= s).then_some(len), ArrayLen::Known(s) => (if allow_shrink { len >= s } else { len == s }).then_some(s), } } @@ -1557,7 +1563,7 @@ mod test { // Array subtypes let aty = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::None); let aty1 = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Known(1)); - let aty0 = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::NonEmpty); + let aty0 = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Minimum(1)); match v { KclValue::Tuple { .. } | KclValue::HomArray { .. } => { @@ -1640,7 +1646,7 @@ mod test { let aty = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::None); let aty0 = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::Known(0)); let aty1 = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::Known(1)); - let aty1p = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::NonEmpty); + let aty1p = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::Minimum(1)); assert_coerce_results( &none, &aty, @@ -1854,15 +1860,25 @@ mod test { ); let tyh1 = RuntimeType::Array( Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))), - ArrayLen::NonEmpty, + ArrayLen::Minimum(1), ); let tyh3 = RuntimeType::Array( Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))), ArrayLen::Known(3), ); + let tyhm3 = RuntimeType::Array( + Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))), + ArrayLen::Minimum(3), + ); + let tyhm5 = RuntimeType::Array( + Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))), + ArrayLen::Minimum(5), + ); assert_coerce_results(&hom_arr, &tyhn, &hom_arr, &mut exec_state); assert_coerce_results(&hom_arr, &tyh1, &hom_arr, &mut exec_state); hom_arr.coerce(&tyh3, true, &mut exec_state).unwrap_err(); + assert_coerce_results(&hom_arr, &tyhm3, &hom_arr, &mut exec_state); + hom_arr.coerce(&tyhm5, true, &mut exec_state).unwrap_err(); let hom_arr0 = KclValue::HomArray { value: vec![], @@ -2364,7 +2380,7 @@ d = cos(30) // Principal types let tym1 = RuntimeType::Array( Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))), - ArrayLen::NonEmpty, + ArrayLen::Minimum(1), ); let result = KclValue::HomArray { diff --git a/rust/kcl-lib/src/parsing/ast/digest.rs b/rust/kcl-lib/src/parsing/ast/digest.rs index 786693dbc..42a413c2f 100644 --- a/rust/kcl-lib/src/parsing/ast/digest.rs +++ b/rust/kcl-lib/src/parsing/ast/digest.rs @@ -198,7 +198,7 @@ impl Type { hasher.update(ty.compute_digest()); match len { crate::execution::types::ArrayLen::None => {} - crate::execution::types::ArrayLen::NonEmpty => hasher.update(usize::MAX.to_ne_bytes()), + crate::execution::types::ArrayLen::Minimum(n) => hasher.update((-(*n as isize)).to_ne_bytes()), crate::execution::types::ArrayLen::Known(n) => hasher.update(n.to_ne_bytes()), } } diff --git a/rust/kcl-lib/src/parsing/ast/types/mod.rs b/rust/kcl-lib/src/parsing/ast/types/mod.rs index c8e357bda..769ad1dd6 100644 --- a/rust/kcl-lib/src/parsing/ast/types/mod.rs +++ b/rust/kcl-lib/src/parsing/ast/types/mod.rs @@ -3336,7 +3336,7 @@ impl fmt::Display for Type { write!(f, "[{ty}")?; match len { ArrayLen::None => {} - ArrayLen::NonEmpty => write!(f, "; 1+")?, + ArrayLen::Minimum(n) => write!(f, "; {n}+")?, ArrayLen::Known(n) => write!(f, "; {n}")?, } write!(f, "]") diff --git a/rust/kcl-lib/src/parsing/parser.rs b/rust/kcl-lib/src/parsing/parser.rs index 692c567ea..526643027 100644 --- a/rust/kcl-lib/src/parsing/parser.rs +++ b/rust/kcl-lib/src/parsing/parser.rs @@ -2968,16 +2968,9 @@ fn array_type(i: &mut TokenSlice) -> ModalResult> { .parse_next(i)?; close_bracket(i)?; - let len = if let Some((tok, _, n, plus)) = len { + let len = if let Some((_, _, n, plus)) = len { if plus.is_some() { - if n != 1 { - return Err(ErrMode::Cut(ContextError::from(CompilationError::fatal( - tok.as_source_range(), - "Non-empty arrays are specified using `1+`, for a fixed-size array use just an integer", - )))); - } else { - ArrayLen::NonEmpty - } + ArrayLen::Minimum(n) } else { ArrayLen::Known(n) } diff --git a/rust/kcl-lib/src/std/math.rs b/rust/kcl-lib/src/std/math.rs index a7b18c128..4bc22a9e6 100644 --- a/rust/kcl-lib/src/std/math.rs +++ b/rust/kcl-lib/src/std/math.rs @@ -106,7 +106,7 @@ pub async fn ceil(exec_state: &mut ExecState, args: Args) -> Result Result { let nums: Vec = args.get_unlabeled_kw_arg_typed( "input", - &RuntimeType::Array(Box::new(RuntimeType::num_any()), ArrayLen::NonEmpty), + &RuntimeType::Array(Box::new(RuntimeType::num_any()), ArrayLen::Minimum(1)), exec_state, )?; let (nums, ty) = NumericType::combine_eq_array(&nums); @@ -131,7 +131,7 @@ pub async fn min(exec_state: &mut ExecState, args: Args) -> Result Result { let nums: Vec = args.get_unlabeled_kw_arg_typed( "input", - &RuntimeType::Array(Box::new(RuntimeType::num_any()), ArrayLen::NonEmpty), + &RuntimeType::Array(Box::new(RuntimeType::num_any()), ArrayLen::Minimum(1)), exec_state, )?; let (nums, ty) = NumericType::combine_eq_array(&nums); diff --git a/rust/kcl-lib/src/std/shell.rs b/rust/kcl-lib/src/std/shell.rs index dfc95aec5..79ee8b820 100644 --- a/rust/kcl-lib/src/std/shell.rs +++ b/rust/kcl-lib/src/std/shell.rs @@ -20,7 +20,7 @@ pub async fn shell(exec_state: &mut ExecState, args: Args) -> Result Result