Treat singletons and arrays as subtypes rather than coercible (#7181)
Signed-off-by: Nick Cameron <nrc@ncameron.org>
This commit is contained in:
@ -944,6 +944,9 @@ impl Node<MemberExpression> {
|
||||
)))
|
||||
}
|
||||
}
|
||||
// Singletons and single-element arrays should be interchangeable, but only indexing by 0 should work.
|
||||
// This is kind of a silly property, but it's possible it occurs in generic code or something.
|
||||
(obj, Property::UInt(0), _) => Ok(obj),
|
||||
(KclValue::HomArray { .. }, p, _) => {
|
||||
let t = p.type_name();
|
||||
let article = article_for(t);
|
||||
@ -1981,6 +1984,38 @@ startSketchOn(XY)
|
||||
assert!(e.message().contains("sqrt"), "Error message: '{}'", e.message());
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn non_array_fns() {
|
||||
let ast = r#"push(1, item = 2)
|
||||
pop(1)
|
||||
map(1, f = fn(@x) { return x + 1 })
|
||||
reduce(1, f = fn(@x, accum) { return accum + x}, initial = 0)"#;
|
||||
|
||||
parse_execute(ast).await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn non_array_indexing() {
|
||||
let good = r#"a = 42
|
||||
good = a[0]
|
||||
"#;
|
||||
let result = parse_execute(good).await.unwrap();
|
||||
let mem = result.exec_state.stack();
|
||||
let num = mem
|
||||
.memory
|
||||
.get_from("good", result.mem_env, SourceRange::default(), 0)
|
||||
.unwrap()
|
||||
.as_ty_f64()
|
||||
.unwrap();
|
||||
assert_eq!(num.n, 42.0);
|
||||
|
||||
let bad = r#"a = 42
|
||||
bad = a[1]
|
||||
"#;
|
||||
|
||||
parse_execute(bad).await.unwrap_err();
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn coerce_unknown_to_length() {
|
||||
let ast = r#"x = 2mm * 2mm
|
||||
|
@ -1,7 +1,6 @@
|
||||
use async_recursion::async_recursion;
|
||||
use indexmap::IndexMap;
|
||||
|
||||
use super::{types::ArrayLen, EnvironmentRef};
|
||||
use crate::{
|
||||
docs::StdLibFn,
|
||||
errors::{KclError, KclErrorDetails},
|
||||
@ -10,7 +9,8 @@ use crate::{
|
||||
kcl_value::FunctionSource,
|
||||
memory,
|
||||
types::RuntimeType,
|
||||
BodyType, ExecState, ExecutorContext, KclValue, Metadata, StatementKind, TagEngineInfo, TagIdentifier,
|
||||
BodyType, EnvironmentRef, ExecState, ExecutorContext, KclValue, Metadata, StatementKind, TagEngineInfo,
|
||||
TagIdentifier,
|
||||
},
|
||||
parsing::ast::types::{CallExpressionKw, DefaultParamVal, FunctionExpression, Node, Program, Type},
|
||||
source_range::SourceRange,
|
||||
@ -294,7 +294,7 @@ impl Node<CallExpressionKw> {
|
||||
// exec_state.
|
||||
let func = fn_name.get_result(exec_state, ctx).await?.clone();
|
||||
|
||||
let Some(fn_src) = func.as_fn() else {
|
||||
let Some(fn_src) = func.as_function() else {
|
||||
return Err(KclError::Semantic(KclErrorDetails::new(
|
||||
"cannot call this because it isn't a function".to_string(),
|
||||
vec![callsite],
|
||||
@ -787,18 +787,8 @@ fn coerce_result_type(
|
||||
) -> Result<Option<KclValue>, KclError> {
|
||||
if let Ok(Some(val)) = result {
|
||||
if let Some(ret_ty) = &fn_def.return_type {
|
||||
let mut ty = RuntimeType::from_parsed(ret_ty.inner.clone(), exec_state, ret_ty.as_source_range())
|
||||
let ty = RuntimeType::from_parsed(ret_ty.inner.clone(), exec_state, ret_ty.as_source_range())
|
||||
.map_err(|e| KclError::Semantic(e.into()))?;
|
||||
// Treat `[T; 1+]` as `T | [T; 1+]` (which can't yet be expressed in our syntax of types).
|
||||
// This is a very specific hack which exists because some std functions can produce arrays
|
||||
// but usually only make a singleton and the frontend expects the singleton.
|
||||
// If we can make the frontend work on arrays (or at least arrays of length 1), then this
|
||||
// can be removed.
|
||||
// I believe this is safe, since anywhere which requires an array should coerce the singleton
|
||||
// to an array and we only do this hack for return values.
|
||||
if let RuntimeType::Array(inner, ArrayLen::Minimum(1)) = &ty {
|
||||
ty = RuntimeType::Union(vec![(**inner).clone(), ty]);
|
||||
}
|
||||
let val = val.coerce(&ty, true, exec_state).map_err(|_| {
|
||||
KclError::Semantic(KclErrorDetails::new(
|
||||
format!(
|
||||
|
@ -290,15 +290,15 @@ impl KclValue {
|
||||
// The principal type of an array uses the array's element type,
|
||||
// which is oftentimes `any`, and that's not a helpful message. So
|
||||
// we show the actual elements.
|
||||
if let Some(elements) = self.as_array() {
|
||||
if let KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } = self {
|
||||
// If it's empty, we want to show the type of the array.
|
||||
if !elements.is_empty() {
|
||||
if !value.is_empty() {
|
||||
// A max of 3 is good because it's common to use 3D points.
|
||||
let max = 3;
|
||||
let len = elements.len();
|
||||
let len = value.len();
|
||||
let ellipsis = if len > max { ", ..." } else { "" };
|
||||
let element_label = if len == 1 { "value" } else { "values" };
|
||||
let element_tys = elements
|
||||
let element_tys = value
|
||||
.iter()
|
||||
.take(max)
|
||||
.map(|elem| elem.inner_human_friendly_type(max_depth - 1))
|
||||
@ -442,144 +442,128 @@ impl KclValue {
|
||||
}
|
||||
|
||||
pub fn as_object(&self) -> Option<&KclObjectFields> {
|
||||
if let KclValue::Object { value, meta: _ } = &self {
|
||||
Some(value)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_object(self) -> Option<KclObjectFields> {
|
||||
if let KclValue::Object { value, meta: _ } = self {
|
||||
Some(value)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> Option<&str> {
|
||||
if let KclValue::String { value, meta: _ } = &self {
|
||||
Some(value)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_array(&self) -> Option<&[KclValue]> {
|
||||
match self {
|
||||
KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => Some(value),
|
||||
KclValue::Object { value, .. } => Some(value),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_object(self) -> Option<KclObjectFields> {
|
||||
match self {
|
||||
KclValue::Object { value, .. } => Some(value),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_str(&self) -> Option<&str> {
|
||||
match self {
|
||||
KclValue::String { value, .. } => Some(value),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_array(self) -> Vec<KclValue> {
|
||||
match self {
|
||||
KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => value,
|
||||
_ => vec![self],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_point2d(&self) -> Option<[TyF64; 2]> {
|
||||
let arr = self.as_array()?;
|
||||
if arr.len() != 2 {
|
||||
let value = match self {
|
||||
KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => value,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
if value.len() != 2 {
|
||||
return None;
|
||||
}
|
||||
let x = arr[0].as_ty_f64()?;
|
||||
let y = arr[1].as_ty_f64()?;
|
||||
let x = value[0].as_ty_f64()?;
|
||||
let y = value[1].as_ty_f64()?;
|
||||
Some([x, y])
|
||||
}
|
||||
|
||||
pub fn as_point3d(&self) -> Option<[TyF64; 3]> {
|
||||
let arr = self.as_array()?;
|
||||
if arr.len() != 3 {
|
||||
let value = match self {
|
||||
KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => value,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
if value.len() != 3 {
|
||||
return None;
|
||||
}
|
||||
let x = arr[0].as_ty_f64()?;
|
||||
let y = arr[1].as_ty_f64()?;
|
||||
let z = arr[2].as_ty_f64()?;
|
||||
let x = value[0].as_ty_f64()?;
|
||||
let y = value[1].as_ty_f64()?;
|
||||
let z = value[2].as_ty_f64()?;
|
||||
Some([x, y, z])
|
||||
}
|
||||
|
||||
pub fn as_uuid(&self) -> Option<uuid::Uuid> {
|
||||
if let KclValue::Uuid { value, meta: _ } = &self {
|
||||
Some(*value)
|
||||
} else {
|
||||
None
|
||||
match self {
|
||||
KclValue::Uuid { value, .. } => Some(*value),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_plane(&self) -> Option<&Plane> {
|
||||
if let KclValue::Plane { value } = &self {
|
||||
Some(value)
|
||||
} else {
|
||||
None
|
||||
match self {
|
||||
KclValue::Plane { value, .. } => Some(value),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_solid(&self) -> Option<&Solid> {
|
||||
if let KclValue::Solid { value } = &self {
|
||||
Some(value)
|
||||
} else {
|
||||
None
|
||||
match self {
|
||||
KclValue::Solid { value, .. } => Some(value),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_sketch(&self) -> Option<&Sketch> {
|
||||
if let KclValue::Sketch { value } = self {
|
||||
Some(value)
|
||||
} else {
|
||||
None
|
||||
match self {
|
||||
KclValue::Sketch { value, .. } => Some(value),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_mut_sketch(&mut self) -> Option<&mut Sketch> {
|
||||
if let KclValue::Sketch { value } = self {
|
||||
Some(value)
|
||||
} else {
|
||||
None
|
||||
match self {
|
||||
KclValue::Sketch { value } => Some(value),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_mut_tag(&mut self) -> Option<&mut TagIdentifier> {
|
||||
if let KclValue::TagIdentifier(value) = self {
|
||||
Some(value)
|
||||
} else {
|
||||
None
|
||||
match self {
|
||||
KclValue::TagIdentifier(value) => Some(value),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn as_f64(&self) -> Option<f64> {
|
||||
if let KclValue::Number { value, .. } = &self {
|
||||
Some(*value)
|
||||
} else {
|
||||
None
|
||||
match self {
|
||||
KclValue::Number { value, .. } => Some(*value),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_ty_f64(&self) -> Option<TyF64> {
|
||||
if let KclValue::Number { value, ty, .. } = &self {
|
||||
Some(TyF64::new(*value, ty.clone()))
|
||||
} else {
|
||||
None
|
||||
match self {
|
||||
KclValue::Number { value, ty, .. } => Some(TyF64::new(*value, ty.clone())),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_bool(&self) -> Option<bool> {
|
||||
if let KclValue::Bool { value, meta: _ } = &self {
|
||||
Some(*value)
|
||||
} else {
|
||||
None
|
||||
match self {
|
||||
KclValue::Bool { value, .. } => Some(*value),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// If this value fits in a u32, return it.
|
||||
pub fn get_u32(&self, source_ranges: Vec<SourceRange>) -> Result<u32, KclError> {
|
||||
let u = self.as_int().and_then(|n| u64::try_from(n).ok()).ok_or_else(|| {
|
||||
KclError::Semantic(KclErrorDetails::new(
|
||||
"Expected an integer >= 0".to_owned(),
|
||||
source_ranges.clone(),
|
||||
))
|
||||
})?;
|
||||
u32::try_from(u)
|
||||
.map_err(|_| KclError::Semantic(KclErrorDetails::new("Number was too big".to_owned(), source_ranges)))
|
||||
}
|
||||
|
||||
/// If this value is of type function, return it.
|
||||
pub fn get_function(&self) -> Option<&FunctionSource> {
|
||||
pub fn as_function(&self) -> Option<&FunctionSource> {
|
||||
match self {
|
||||
KclValue::Function { value, .. } => Some(value),
|
||||
_ => None,
|
||||
@ -610,20 +594,12 @@ impl KclValue {
|
||||
|
||||
/// If this KCL value is a bool, retrieve it.
|
||||
pub fn get_bool(&self) -> Result<bool, KclError> {
|
||||
let Self::Bool { value: b, .. } = self else {
|
||||
return Err(KclError::Type(KclErrorDetails::new(
|
||||
self.as_bool().ok_or_else(|| {
|
||||
KclError::Type(KclErrorDetails::new(
|
||||
format!("Expected bool, found {}", self.human_friendly_type()),
|
||||
self.into(),
|
||||
)));
|
||||
};
|
||||
Ok(*b)
|
||||
}
|
||||
|
||||
pub fn as_fn(&self) -> Option<&FunctionSource> {
|
||||
match self {
|
||||
KclValue::Function { value, .. } => Some(value),
|
||||
_ => None,
|
||||
}
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
pub fn value_str(&self) -> Option<String> {
|
||||
|
@ -32,6 +32,10 @@ impl RuntimeType {
|
||||
RuntimeType::Primitive(PrimitiveType::Any)
|
||||
}
|
||||
|
||||
pub fn any_array() -> Self {
|
||||
RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::None)
|
||||
}
|
||||
|
||||
pub fn edge() -> Self {
|
||||
RuntimeType::Primitive(PrimitiveType::Edge)
|
||||
}
|
||||
@ -238,12 +242,21 @@ impl RuntimeType {
|
||||
(Primitive(t1), Primitive(t2)) => t1.subtype(t2),
|
||||
(Array(t1, l1), Array(t2, l2)) => t1.subtype(t2) && l1.subtype(*l2),
|
||||
(Tuple(t1), Tuple(t2)) => t1.len() == t2.len() && t1.iter().zip(t2).all(|(t1, t2)| t1.subtype(t2)),
|
||||
|
||||
(Union(ts1), Union(ts2)) => ts1.iter().all(|t| ts2.contains(t)),
|
||||
(t1, Union(ts2)) => ts2.iter().any(|t| t1.subtype(t)),
|
||||
|
||||
(Object(t1), Object(t2)) => t2
|
||||
.iter()
|
||||
.all(|(f, t)| t1.iter().any(|(ff, tt)| f == ff && tt.subtype(t))),
|
||||
// Equality between Axis types and their object representation.
|
||||
|
||||
// Equivalence between singleton types and single-item arrays/tuples of the same type (plus transitivity with the array subtyping).
|
||||
(t1, RuntimeType::Array(t2, l)) if t1.subtype(t2) && ArrayLen::Known(1).subtype(*l) => true,
|
||||
(RuntimeType::Array(t1, ArrayLen::Known(1)), t2) if t1.subtype(t2) => true,
|
||||
(t1, RuntimeType::Tuple(t2)) if !t2.is_empty() && t1.subtype(&t2[0]) => true,
|
||||
(RuntimeType::Tuple(t1), t2) if t1.len() == 1 && t1[0].subtype(t2) => true,
|
||||
|
||||
// Equivalence between Axis types and their object representation.
|
||||
(Object(t1), Primitive(PrimitiveType::Axis2d)) => {
|
||||
t1.iter()
|
||||
.any(|(n, t)| n == "origin" && t.subtype(&RuntimeType::point2d()))
|
||||
@ -1051,6 +1064,20 @@ impl KclValue {
|
||||
convert_units: bool,
|
||||
exec_state: &mut ExecState,
|
||||
) -> Result<KclValue, CoercionError> {
|
||||
match self {
|
||||
KclValue::Tuple { value, .. } if value.len() == 1 && !matches!(ty, RuntimeType::Tuple(..)) => {
|
||||
if let Ok(coerced) = value[0].coerce(ty, convert_units, exec_state) {
|
||||
return Ok(coerced);
|
||||
}
|
||||
}
|
||||
KclValue::HomArray { value, .. } if value.len() == 1 && !matches!(ty, RuntimeType::Array(..)) => {
|
||||
if let Ok(coerced) = value[0].coerce(ty, convert_units, exec_state) {
|
||||
return Ok(coerced);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
match ty {
|
||||
RuntimeType::Primitive(ty) => self.coerce_to_primitive_type(ty, convert_units, exec_state),
|
||||
RuntimeType::Array(ty, len) => self.coerce_to_array_type(ty, convert_units, *len, exec_state, false),
|
||||
@ -1066,15 +1093,11 @@ impl KclValue {
|
||||
convert_units: bool,
|
||||
exec_state: &mut ExecState,
|
||||
) -> Result<KclValue, CoercionError> {
|
||||
let value = match self {
|
||||
KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } if value.len() == 1 => &value[0],
|
||||
_ => self,
|
||||
};
|
||||
match ty {
|
||||
PrimitiveType::Any => Ok(value.clone()),
|
||||
PrimitiveType::Any => Ok(self.clone()),
|
||||
PrimitiveType::Number(ty) => {
|
||||
if convert_units {
|
||||
return ty.coerce(value);
|
||||
return ty.coerce(self);
|
||||
}
|
||||
|
||||
// Instead of converting units, reinterpret the number as having
|
||||
@ -1083,7 +1106,7 @@ impl KclValue {
|
||||
// If the user is explicitly specifying units, treat the value
|
||||
// as having had its units erased, rather than forcing the user
|
||||
// to explicitly erase them.
|
||||
if let KclValue::Number { value: n, meta, .. } = &value {
|
||||
if let KclValue::Number { value: n, meta, .. } = &self {
|
||||
if ty.is_fully_specified() {
|
||||
let value = KclValue::Number {
|
||||
ty: NumericType::Any,
|
||||
@ -1093,34 +1116,34 @@ impl KclValue {
|
||||
return ty.coerce(&value);
|
||||
}
|
||||
}
|
||||
ty.coerce(value)
|
||||
ty.coerce(self)
|
||||
}
|
||||
PrimitiveType::String => match value {
|
||||
KclValue::String { .. } => Ok(value.clone()),
|
||||
PrimitiveType::String => match self {
|
||||
KclValue::String { .. } => Ok(self.clone()),
|
||||
_ => Err(self.into()),
|
||||
},
|
||||
PrimitiveType::Boolean => match value {
|
||||
KclValue::Bool { .. } => Ok(value.clone()),
|
||||
PrimitiveType::Boolean => match self {
|
||||
KclValue::Bool { .. } => Ok(self.clone()),
|
||||
_ => Err(self.into()),
|
||||
},
|
||||
PrimitiveType::Sketch => match value {
|
||||
KclValue::Sketch { .. } => Ok(value.clone()),
|
||||
PrimitiveType::Sketch => match self {
|
||||
KclValue::Sketch { .. } => Ok(self.clone()),
|
||||
_ => Err(self.into()),
|
||||
},
|
||||
PrimitiveType::Solid => match value {
|
||||
KclValue::Solid { .. } => Ok(value.clone()),
|
||||
PrimitiveType::Solid => match self {
|
||||
KclValue::Solid { .. } => Ok(self.clone()),
|
||||
_ => Err(self.into()),
|
||||
},
|
||||
PrimitiveType::Plane => match value {
|
||||
PrimitiveType::Plane => match self {
|
||||
KclValue::String { value: s, .. }
|
||||
if [
|
||||
"xy", "xz", "yz", "-xy", "-xz", "-yz", "XY", "XZ", "YZ", "-XY", "-XZ", "-YZ",
|
||||
]
|
||||
.contains(&&**s) =>
|
||||
{
|
||||
Ok(value.clone())
|
||||
Ok(self.clone())
|
||||
}
|
||||
KclValue::Plane { .. } => Ok(value.clone()),
|
||||
KclValue::Plane { .. } => Ok(self.clone()),
|
||||
KclValue::Object { value, meta } => {
|
||||
let origin = value
|
||||
.get("origin")
|
||||
@ -1159,20 +1182,20 @@ impl KclValue {
|
||||
}
|
||||
_ => Err(self.into()),
|
||||
},
|
||||
PrimitiveType::Face => match value {
|
||||
KclValue::Face { .. } => Ok(value.clone()),
|
||||
PrimitiveType::Face => match self {
|
||||
KclValue::Face { .. } => Ok(self.clone()),
|
||||
_ => Err(self.into()),
|
||||
},
|
||||
PrimitiveType::Helix => match value {
|
||||
KclValue::Helix { .. } => Ok(value.clone()),
|
||||
PrimitiveType::Helix => match self {
|
||||
KclValue::Helix { .. } => Ok(self.clone()),
|
||||
_ => Err(self.into()),
|
||||
},
|
||||
PrimitiveType::Edge => match value {
|
||||
KclValue::Uuid { .. } => Ok(value.clone()),
|
||||
KclValue::TagIdentifier { .. } => Ok(value.clone()),
|
||||
PrimitiveType::Edge => match self {
|
||||
KclValue::Uuid { .. } => Ok(self.clone()),
|
||||
KclValue::TagIdentifier { .. } => Ok(self.clone()),
|
||||
_ => Err(self.into()),
|
||||
},
|
||||
PrimitiveType::Axis2d => match value {
|
||||
PrimitiveType::Axis2d => match self {
|
||||
KclValue::Object { value: values, meta } => {
|
||||
if values
|
||||
.get("origin")
|
||||
@ -1183,7 +1206,7 @@ impl KclValue {
|
||||
.ok_or(CoercionError::from(self))?
|
||||
.has_type(&RuntimeType::point2d())
|
||||
{
|
||||
return Ok(value.clone());
|
||||
return Ok(self.clone());
|
||||
}
|
||||
|
||||
let origin = values.get("origin").ok_or(self.into()).and_then(|p| {
|
||||
@ -1212,7 +1235,7 @@ impl KclValue {
|
||||
}
|
||||
_ => Err(self.into()),
|
||||
},
|
||||
PrimitiveType::Axis3d => match value {
|
||||
PrimitiveType::Axis3d => match self {
|
||||
KclValue::Object { value: values, meta } => {
|
||||
if values
|
||||
.get("origin")
|
||||
@ -1223,7 +1246,7 @@ impl KclValue {
|
||||
.ok_or(CoercionError::from(self))?
|
||||
.has_type(&RuntimeType::point3d())
|
||||
{
|
||||
return Ok(value.clone());
|
||||
return Ok(self.clone());
|
||||
}
|
||||
|
||||
let origin = values.get("origin").ok_or(self.into()).and_then(|p| {
|
||||
@ -1252,21 +1275,21 @@ impl KclValue {
|
||||
}
|
||||
_ => Err(self.into()),
|
||||
},
|
||||
PrimitiveType::ImportedGeometry => match value {
|
||||
KclValue::ImportedGeometry { .. } => Ok(value.clone()),
|
||||
PrimitiveType::ImportedGeometry => match self {
|
||||
KclValue::ImportedGeometry { .. } => Ok(self.clone()),
|
||||
_ => Err(self.into()),
|
||||
},
|
||||
PrimitiveType::Function => match value {
|
||||
KclValue::Function { .. } => Ok(value.clone()),
|
||||
PrimitiveType::Function => match self {
|
||||
KclValue::Function { .. } => Ok(self.clone()),
|
||||
_ => Err(self.into()),
|
||||
},
|
||||
PrimitiveType::TagId => match value {
|
||||
KclValue::TagIdentifier { .. } => Ok(value.clone()),
|
||||
PrimitiveType::TagId => match self {
|
||||
KclValue::TagIdentifier { .. } => Ok(self.clone()),
|
||||
_ => Err(self.into()),
|
||||
},
|
||||
PrimitiveType::Tag => match value {
|
||||
PrimitiveType::Tag => match self {
|
||||
KclValue::TagDeclarator { .. } | KclValue::TagIdentifier { .. } | KclValue::Uuid { .. } => {
|
||||
Ok(value.clone())
|
||||
Ok(self.clone())
|
||||
}
|
||||
s @ KclValue::String { value, .. } if ["start", "end", "START", "END"].contains(&&**value) => {
|
||||
Ok(s.clone())
|
||||
@ -1366,10 +1389,7 @@ impl KclValue {
|
||||
value: Vec::new(),
|
||||
ty: ty.clone(),
|
||||
}),
|
||||
_ if len.satisfied(1, false).is_some() => Ok(KclValue::HomArray {
|
||||
value: vec![self.coerce(ty, convert_units, exec_state)?],
|
||||
ty: ty.clone(),
|
||||
}),
|
||||
_ if len.satisfied(1, false).is_some() => self.coerce(ty, convert_units, exec_state),
|
||||
_ => Err(self.into()),
|
||||
}
|
||||
}
|
||||
@ -1396,10 +1416,7 @@ impl KclValue {
|
||||
value: Vec::new(),
|
||||
meta: meta.clone(),
|
||||
}),
|
||||
value if tys.len() == 1 && value.has_type(&tys[0]) => Ok(KclValue::Tuple {
|
||||
value: vec![value.clone()],
|
||||
meta: Vec::new(),
|
||||
}),
|
||||
_ if tys.len() == 1 => self.coerce(&tys[0], convert_units, exec_state),
|
||||
_ => Err(self.into()),
|
||||
}
|
||||
}
|
||||
@ -1531,7 +1548,8 @@ mod test {
|
||||
exec_state: &mut ExecState,
|
||||
) {
|
||||
let is_subtype = value == expected_value;
|
||||
assert_eq!(&value.coerce(super_type, true, exec_state).unwrap(), expected_value);
|
||||
let actual = value.coerce(super_type, true, exec_state).unwrap();
|
||||
assert_eq!(&actual, expected_value);
|
||||
assert_eq!(
|
||||
is_subtype,
|
||||
value.principal_type().is_some() && value.principal_type().unwrap().subtype(super_type),
|
||||
@ -1566,7 +1584,7 @@ mod test {
|
||||
let aty0 = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Minimum(1));
|
||||
|
||||
match v {
|
||||
KclValue::Tuple { .. } | KclValue::HomArray { .. } => {
|
||||
KclValue::HomArray { .. } => {
|
||||
// These will not get wrapped if possible.
|
||||
assert_coerce_results(
|
||||
v,
|
||||
@ -1577,53 +1595,22 @@ mod test {
|
||||
},
|
||||
&mut exec_state,
|
||||
);
|
||||
// Coercing an empty tuple or array to an array of length 1
|
||||
// Coercing an empty array to an array of length 1
|
||||
// should fail.
|
||||
v.coerce(&aty1, true, &mut exec_state).unwrap_err();
|
||||
// Coercing an empty tuple or array to an array that's
|
||||
// Coercing an empty array to an array that's
|
||||
// non-empty should fail.
|
||||
v.coerce(&aty0, true, &mut exec_state).unwrap_err();
|
||||
}
|
||||
KclValue::Tuple { .. } => {}
|
||||
_ => {
|
||||
assert_coerce_results(
|
||||
v,
|
||||
&aty,
|
||||
&KclValue::HomArray {
|
||||
value: vec![v.clone()],
|
||||
ty: ty.clone(),
|
||||
},
|
||||
&mut exec_state,
|
||||
);
|
||||
assert_coerce_results(
|
||||
v,
|
||||
&aty1,
|
||||
&KclValue::HomArray {
|
||||
value: vec![v.clone()],
|
||||
ty: ty.clone(),
|
||||
},
|
||||
&mut exec_state,
|
||||
);
|
||||
assert_coerce_results(
|
||||
v,
|
||||
&aty0,
|
||||
&KclValue::HomArray {
|
||||
value: vec![v.clone()],
|
||||
ty: ty.clone(),
|
||||
},
|
||||
&mut exec_state,
|
||||
);
|
||||
assert_coerce_results(v, &aty, v, &mut exec_state);
|
||||
assert_coerce_results(v, &aty1, v, &mut exec_state);
|
||||
assert_coerce_results(v, &aty0, v, &mut exec_state);
|
||||
|
||||
// Tuple subtype
|
||||
let tty = RuntimeType::Tuple(vec![ty.clone()]);
|
||||
assert_coerce_results(
|
||||
v,
|
||||
&tty,
|
||||
&KclValue::Tuple {
|
||||
value: vec![v.clone()],
|
||||
meta: Vec::new(),
|
||||
},
|
||||
&mut exec_state,
|
||||
);
|
||||
assert_coerce_results(v, &tty, v, &mut exec_state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user