Treat singletons and arrays as subtypes rather than coercible (#7181)

Signed-off-by: Nick Cameron <nrc@ncameron.org>
This commit is contained in:
Nick Cameron
2025-05-28 16:29:23 +12:00
committed by GitHub
parent 9dfb67cf61
commit 783b6ed76c
71 changed files with 5119 additions and 6067 deletions

View File

@ -214,9 +214,9 @@ impl Args {
/// Get a labelled keyword arg, check it's an array, and return all items in the array
/// plus their source range.
pub(crate) fn kw_arg_array_and_source<'a, T>(&'a self, label: &str) -> Result<Vec<(T, SourceRange)>, KclError>
pub(crate) fn kw_arg_array_and_source<T>(&self, label: &str) -> Result<Vec<(T, SourceRange)>, KclError>
where
T: FromKclValue<'a>,
T: for<'a> FromKclValue<'a>,
{
let Some(arg) = self.kw_args.labeled.get(label) else {
let err = KclError::Semantic(KclErrorDetails::new(
@ -225,18 +225,9 @@ impl Args {
));
return Err(err);
};
let Some(array) = arg.value.as_array() else {
let err = KclError::Semantic(KclErrorDetails::new(
format!(
"Expected an array of {} but found {}",
tynm::type_name::<T>(),
arg.value.human_friendly_type()
),
vec![arg.source_range],
));
return Err(err);
};
array
arg.value
.clone()
.into_array()
.iter()
.map(|item| {
let source = SourceRange::from(item);
@ -255,6 +246,19 @@ impl Args {
.collect::<Result<Vec<_>, _>>()
}
pub(crate) fn get_unlabeled_kw_arg_array_and_type(
&self,
label: &str,
exec_state: &mut ExecState,
) -> Result<(Vec<KclValue>, RuntimeType), KclError> {
let value = self.get_unlabeled_kw_arg_typed(label, &RuntimeType::any_array(), exec_state)?;
Ok(match value {
KclValue::HomArray { value, ty } => (value, ty),
KclValue::Tuple { value, .. } => (value, RuntimeType::any()),
val => (vec![val], RuntimeType::any()),
})
}
/// Get the unlabeled keyword argument. If not set, returns Err. If it
/// can't be converted to the given type, returns Err.
pub(crate) fn get_unlabeled_kw_arg<'a, T>(&'a self, label: &str) -> Result<T, KclError>
@ -331,7 +335,7 @@ impl Args {
T::from_kcl_val(&arg).ok_or_else(|| {
KclError::Internal(KclErrorDetails::new(
"Mismatch between type coercion and value extraction (this isn't your fault).\nTo assist in bug-reporting, expected type: {ty:?}; actual value: {arg:?}".to_owned(),
format!("Mismatch between type coercion and value extraction (this isn't your fault).\nTo assist in bug-reporting, expected type: {ty:?}; actual value: {arg:?}"),
vec![self.source_range],
))
})
@ -728,7 +732,7 @@ impl<'a> FromKclValue<'a> for Vec<TagIdentifier> {
impl<'a> FromKclValue<'a> for Vec<KclValue> {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
arg.as_array().map(|v| v.to_vec())
Some(arg.clone().into_array())
}
}
@ -1039,7 +1043,8 @@ macro_rules! impl_from_kcl_for_vec {
($typ:path) => {
impl<'a> FromKclValue<'a> for Vec<$typ> {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
arg.as_array()?
arg.clone()
.into_array()
.iter()
.map(|value| FromKclValue::from_kcl_val(value))
.collect::<Option<_>>()
@ -1054,6 +1059,8 @@ impl_from_kcl_for_vec!(crate::execution::Metadata);
impl_from_kcl_for_vec!(super::fillet::EdgeReference);
impl_from_kcl_for_vec!(ExtrudeSurface);
impl_from_kcl_for_vec!(TyF64);
impl_from_kcl_for_vec!(Solid);
impl_from_kcl_for_vec!(Sketch);
impl<'a> FromKclValue<'a> for SourceRange {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
@ -1379,27 +1386,9 @@ impl<'a> FromKclValue<'a> for Box<Solid> {
}
}
impl<'a> FromKclValue<'a> for Vec<Solid> {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let KclValue::HomArray { value, .. } = arg else {
return None;
};
value.iter().map(Solid::from_kcl_val).collect()
}
}
impl<'a> FromKclValue<'a> for Vec<Sketch> {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
let KclValue::HomArray { value, .. } = arg else {
return None;
};
value.iter().map(Sketch::from_kcl_val).collect()
}
}
impl<'a> FromKclValue<'a> for &'a FunctionSource {
fn from_kcl_val(arg: &'a KclValue) -> Option<Self> {
arg.get_function()
arg.as_function()
}
}

View File

@ -14,7 +14,7 @@ use crate::{
/// Apply a function to each element of an array.
pub async fn map(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let array: Vec<KclValue> = args.get_unlabeled_kw_arg("array")?;
let array: Vec<KclValue> = args.get_unlabeled_kw_arg_typed("array", &RuntimeType::any_array(), exec_state)?;
let f: &FunctionSource = args.get_kw_arg("f")?;
let new_array = inner_map(array, f, exec_state, &args).await?;
Ok(KclValue::HomArray {
@ -68,7 +68,7 @@ async fn call_map_closure(
/// For each item in an array, update a value.
pub async fn reduce(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let array: Vec<KclValue> = args.get_unlabeled_kw_arg("array")?;
let array: Vec<KclValue> = args.get_unlabeled_kw_arg_typed("array", &RuntimeType::any_array(), exec_state)?;
let f: &FunctionSource = args.get_kw_arg("f")?;
let initial: KclValue = args.get_kw_arg("initial")?;
inner_reduce(array, initial, f, exec_state, &args).await
@ -126,61 +126,23 @@ 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")?;
pub async fn push(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (mut array, ty) = args.get_unlabeled_kw_arg_array_and_type("array", exec_state)?;
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::new(
format!("You can't push to a value of type {actual_type}, only an array"),
meta,
)));
};
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 })
}
fn inner_push(mut array: Vec<KclValue>, item: KclValue) -> Vec<KclValue> {
array.push(item);
array
Ok(KclValue::HomArray { value: array, ty })
}
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::new(
format!("You can't pop from a value of type {actual_type}, only an array"),
meta,
)));
};
let new_array = inner_pop(values, &args)?;
Ok(KclValue::HomArray { value: new_array, ty })
}
fn inner_pop(array: Vec<KclValue>, args: &Args) -> Result<Vec<KclValue>, KclError> {
pub async fn pop(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let (mut array, ty) = args.get_unlabeled_kw_arg_array_and_type("array", exec_state)?;
if array.is_empty() {
return Err(KclError::Semantic(KclErrorDetails::new(
"Cannot pop from an empty array".to_string(),
vec![args.source_range],
)));
}
// Create a new array with all elements except the last one
let new_array = array[..array.len() - 1].to_vec();
Ok(new_array)
array.pop();
Ok(KclValue::HomArray { value: array, ty })
}