Fix the KCL any type and array coercion incorrectly nesting (#6816)

* Add sim test for any type

* Fix doc comments to match code

* Add array ascription tests

* Commit new test output

* Fix to not panic when type is undefined

* Fix to not panic on use of the any type

* Update test and generated output

* Fix error message after rebase

* Fix subtype of any

* Fix KCL to use new keyword args

* Fix to not nest MixedArray in HomArray

* Update output

* Remove all creation of MixedArray and use HomArray instead

* Rename MixedArray to Tuple

* Fix to coerce arrays the way tuples are done

* Restructure to appease the type signature extraction

* Fix TS unit test

* Update output after switch to HomArray

* Update docs

* Fix to remove edge case when creating points

* Update docs with broken point signature

* Fix display of tuples to not collide with arrays

* Change push to an array with type mismatch to be an error

* Add sim test for push type error

* Fix acription to more general array element type

* Fix to coerce point types

* Change array push to not error when item type differs

* Fix coercion tests

* Change to only flatten as a last resort and remove flattening tuples

* Contort code to appease doc generation

* Update docs

* Fix coerce axes

* Fix flattening test to test arrays instead of tuples

* Remove special subtype case for singleton coercion
This commit is contained in:
Jonathan Tran
2025-05-11 23:57:31 -04:00
committed by GitHub
parent 86e8bcfe0b
commit bbe89f56a7
53 changed files with 2942 additions and 1839 deletions

View File

@ -557,24 +557,23 @@ impl Args {
Ok(())
}
pub(crate) fn make_user_val_from_point(&self, p: [TyF64; 2]) -> Result<KclValue, KclError> {
pub(crate) fn make_kcl_val_from_point(&self, p: [f64; 2], ty: NumericType) -> Result<KclValue, KclError> {
let meta = Metadata {
source_range: self.source_range,
};
let x = KclValue::Number {
value: p[0].n,
value: p[0],
meta: vec![meta],
ty: p[0].ty.clone(),
ty: ty.clone(),
};
let y = KclValue::Number {
value: p[1].n,
value: p[1],
meta: vec![meta],
ty: p[1].ty.clone(),
ty: ty.clone(),
};
Ok(KclValue::MixedArray {
value: vec![x, y],
meta: vec![meta],
})
let ty = RuntimeType::Primitive(PrimitiveType::Number(ty));
Ok(KclValue::HomArray { value: vec![x, y], ty })
}
pub(super) fn make_user_val_from_f64_with_type(&self, f: TyF64) -> KclValue {
@ -796,7 +795,7 @@ impl<'a> FromKclValue<'a> for Vec<TagIdentifier> {
let tags = value.iter().map(|v| v.get_tag_identifier().unwrap()).collect();
Some(tags)
}
KclValue::MixedArray { value, .. } => {
KclValue::Tuple { value, .. } => {
let tags = value.iter().map(|v| v.get_tag_identifier().unwrap()).collect();
Some(tags)
}
@ -1136,8 +1135,11 @@ impl_from_kcl_for_vec!(TyF64);
impl<'a> FromKclValue<'a> for SourceRange {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let KclValue::MixedArray { value, meta: _ } = arg else {
return None;
let value = match arg {
KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => value,
_ => {
return None;
}
};
if value.len() != 3 {
return None;
@ -1334,7 +1336,7 @@ impl<'a> FromKclValue<'a> for TyF64 {
impl<'a> FromKclValue<'a> for [TyF64; 2] {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
match arg {
KclValue::MixedArray { value, meta: _ } | KclValue::HomArray { value, .. } => {
KclValue::Tuple { value, meta: _ } | KclValue::HomArray { value, .. } => {
if value.len() != 2 {
return None;
}
@ -1351,7 +1353,7 @@ impl<'a> FromKclValue<'a> for [TyF64; 2] {
impl<'a> FromKclValue<'a> for [TyF64; 3] {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
match arg {
KclValue::MixedArray { value, meta: _ } | KclValue::HomArray { value, .. } => {
KclValue::Tuple { value, meta: _ } | KclValue::HomArray { value, .. } => {
if value.len() != 3 {
return None;
}

View File

@ -9,6 +9,7 @@ use crate::{
errors::{KclError, KclErrorDetails},
execution::{
kcl_value::{FunctionSource, KclValue},
types::RuntimeType,
ExecState,
},
source_range::SourceRange,
@ -19,9 +20,11 @@ use crate::{
pub async fn map(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let array: Vec<KclValue> = args.get_unlabeled_kw_arg("array")?;
let f: &FunctionSource = args.get_kw_arg("f")?;
let meta = vec![args.source_range.into()];
let new_array = inner_map(array, f, exec_state, &args).await?;
Ok(KclValue::MixedArray { value: new_array, meta })
Ok(KclValue::HomArray {
value: new_array,
ty: RuntimeType::any(),
})
}
/// Apply a function to every element of a list.
@ -257,6 +260,31 @@ async fn call_reduce_closure(
Ok(out)
}
pub async fn push(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let array = args.get_unlabeled_kw_arg("array")?;
let item: KclValue = args.get_kw_arg("item")?;
let KclValue::HomArray { value: values, ty } = array else {
let meta = vec![args.source_range];
let actual_type = array.human_friendly_type();
return Err(KclError::Semantic(KclErrorDetails {
source_ranges: meta,
message: format!("You can't push to a value of type {actual_type}, only an array"),
}));
};
let ty = if item.has_type(&ty) {
ty
} else {
// The user pushed an item with a type that differs from the array's
// element type.
RuntimeType::any()
};
let new_array = inner_push(values, item);
Ok(KclValue::HomArray { value: new_array, ty })
}
/// Append an element to the end of an array.
///
/// Returns a new array with the element appended.
@ -276,28 +304,26 @@ async fn call_reduce_closure(
},
tags = ["array"]
}]
async fn inner_push(mut array: Vec<KclValue>, item: KclValue, args: &Args) -> Result<KclValue, KclError> {
fn inner_push(mut array: Vec<KclValue>, item: KclValue) -> Vec<KclValue> {
array.push(item);
Ok(KclValue::MixedArray {
value: array,
meta: vec![args.source_range.into()],
})
array
}
pub async fn push(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
// Extract the array and the element from the arguments
let val: KclValue = args.get_unlabeled_kw_arg("array")?;
let item = args.get_kw_arg("item")?;
let meta = vec![args.source_range];
let KclValue::MixedArray { value: array, meta: _ } = val else {
let actual_type = val.human_friendly_type();
pub async fn pop(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let array = args.get_unlabeled_kw_arg("array")?;
let KclValue::HomArray { value: values, ty } = array else {
let meta = vec![args.source_range];
let actual_type = array.human_friendly_type();
return Err(KclError::Semantic(KclErrorDetails {
source_ranges: meta,
message: format!("You can't push to a value of type {actual_type}, only an array"),
message: format!("You can't pop from a value of type {actual_type}, only an array"),
}));
};
inner_push(array, item, &args).await
let new_array = inner_pop(values, &args)?;
Ok(KclValue::HomArray { value: new_array, ty })
}
/// Remove the last element from an array.
@ -320,7 +346,7 @@ pub async fn push(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, K
},
tags = ["array"]
}]
async fn inner_pop(array: Vec<KclValue>, args: &Args) -> Result<KclValue, KclError> {
fn inner_pop(array: Vec<KclValue>, args: &Args) -> Result<Vec<KclValue>, KclError> {
if array.is_empty() {
return Err(KclError::Semantic(KclErrorDetails {
message: "Cannot pop from an empty array".to_string(),
@ -331,24 +357,5 @@ async fn inner_pop(array: Vec<KclValue>, args: &Args) -> Result<KclValue, KclErr
// Create a new array with all elements except the last one
let new_array = array[..array.len() - 1].to_vec();
Ok(KclValue::MixedArray {
value: new_array,
meta: vec![args.source_range.into()],
})
}
pub async fn pop(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
// Extract the array from the arguments
let val = args.get_unlabeled_kw_arg("array")?;
let meta = vec![args.source_range];
let KclValue::MixedArray { value: array, meta: _ } = val else {
let actual_type = val.human_friendly_type();
return Err(KclError::Semantic(KclErrorDetails {
source_ranges: meta,
message: format!("You can't pop from a value of type {actual_type}, only an array"),
}));
};
inner_pop(array, &args).await
Ok(new_array)
}

View File

@ -452,7 +452,7 @@ async fn make_transform<T: GeometryTrait>(
})?;
let transforms = match transform_fn_return {
KclValue::Object { value, meta: _ } => vec![value],
KclValue::MixedArray { value, meta: _ } => {
KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => {
let transforms: Vec<_> = value
.into_iter()
.map(|val| {
@ -671,12 +671,44 @@ impl GeometryTrait for Solid {
#[cfg(test)]
mod tests {
use super::*;
use crate::execution::types::NumericType;
use crate::execution::types::{NumericType, PrimitiveType};
#[tokio::test(flavor = "multi_thread")]
async fn test_array_to_point3d() {
let mut exec_state = ExecState::new(&ExecutorContext::new_mock().await);
let input = KclValue::MixedArray {
let input = KclValue::HomArray {
value: vec![
KclValue::Number {
value: 1.1,
meta: Default::default(),
ty: NumericType::mm(),
},
KclValue::Number {
value: 2.2,
meta: Default::default(),
ty: NumericType::mm(),
},
KclValue::Number {
value: 3.3,
meta: Default::default(),
ty: NumericType::mm(),
},
],
ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
};
let expected = [
TyF64::new(1.1, NumericType::mm()),
TyF64::new(2.2, NumericType::mm()),
TyF64::new(3.3, NumericType::mm()),
];
let actual = array_to_point3d(&input, Vec::new(), &mut exec_state);
assert_eq!(actual.unwrap(), expected);
}
#[tokio::test(flavor = "multi_thread")]
async fn test_tuple_to_point3d() {
let mut exec_state = ExecState::new(&ExecutorContext::new_mock().await);
let input = KclValue::Tuple {
value: vec![
KclValue::Number {
value: 1.1,

View File

@ -17,9 +17,9 @@ use crate::{
/// Returns the point at the end of the given segment.
pub async fn segment_end(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let tag: TagIdentifier = args.get_unlabeled_kw_arg("tag")?;
let result = inner_segment_end(&tag, exec_state, args.clone())?;
let pt = inner_segment_end(&tag, exec_state, args.clone())?;
args.make_user_val_from_point(result)
args.make_kcl_val_from_point([pt[0].n, pt[1].n], pt[0].ty.clone())
}
/// Compute the ending point of the provided line segment.
@ -64,8 +64,11 @@ fn inner_segment_end(tag: &TagIdentifier, exec_state: &mut ExecState, args: Args
source_ranges: vec![args.source_range],
})
})?;
let (p, ty) = path.end_point_components();
// Docs generation isn't smart enough to handle ([f64; 2], NumericType).
let point = [TyF64::new(p[0], ty.clone()), TyF64::new(p[1], ty)];
Ok(path.get_to().clone())
Ok(point)
}
/// Returns the segment end of x.
@ -156,9 +159,9 @@ fn inner_segment_end_y(tag: &TagIdentifier, exec_state: &mut ExecState, args: Ar
/// Returns the point at the start of the given segment.
pub async fn segment_start(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let tag: TagIdentifier = args.get_unlabeled_kw_arg("tag")?;
let result = inner_segment_start(&tag, exec_state, args.clone())?;
let pt = inner_segment_start(&tag, exec_state, args.clone())?;
args.make_user_val_from_point(result)
args.make_kcl_val_from_point([pt[0].n, pt[1].n], pt[0].ty.clone())
}
/// Compute the starting point of the provided line segment.
@ -203,8 +206,11 @@ fn inner_segment_start(tag: &TagIdentifier, exec_state: &mut ExecState, args: Ar
source_ranges: vec![args.source_range],
})
})?;
let (p, ty) = path.start_point_components();
// Docs generation isn't smart enough to handle ([f64; 2], NumericType).
let point = [TyF64::new(p[0], ty.clone()), TyF64::new(p[1], ty)];
Ok(path.get_from().to_owned())
Ok(point)
}
/// Returns the segment start of x.