Tests for type coercion and subtyping
Signed-off-by: Nick Cameron <nrc@ncameron.org>
This commit is contained in:
@ -1,17 +1,14 @@
|
||||
use std::fmt;
|
||||
use std::{collections::HashMap, fmt};
|
||||
|
||||
use anyhow::Result;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::{
|
||||
memory::{self},
|
||||
Point3d,
|
||||
};
|
||||
use crate::{
|
||||
execution::{
|
||||
kcl_value::{KclValue, TypeDef},
|
||||
ExecState, Plane,
|
||||
memory::{self},
|
||||
ExecState, Plane, Point3d,
|
||||
},
|
||||
parsing::{
|
||||
ast::types::{PrimitiveType as AstPrimitiveType, Type},
|
||||
@ -134,15 +131,14 @@ impl RuntimeType {
|
||||
use RuntimeType::*;
|
||||
|
||||
match (self, sup) {
|
||||
(Primitive(t1), Primitive(t2)) => t1 == t2,
|
||||
// TODO arrays could be covariant
|
||||
(Array(t1, l1), Array(t2, l2)) => t1 == t2 && l1.subtype(*l2),
|
||||
(Tuple(t1), Tuple(t2)) => t1 == t2,
|
||||
(Tuple(t1), Array(t2, l2)) => (l2.satisfied(t1.len())) && t1.iter().all(|t| t == &**t2),
|
||||
(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.contains(t1),
|
||||
// TODO record subtyping - subtype can be larger, fields can be covariant.
|
||||
(Object(t1), Object(t2)) => t1 == t2,
|
||||
(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))),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
@ -252,6 +248,13 @@ impl PrimitiveType {
|
||||
PrimitiveType::Tag => "tags".to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
fn subtype(&self, other: &PrimitiveType) -> bool {
|
||||
match (self, other) {
|
||||
(PrimitiveType::Number(n1), PrimitiveType::Number(n2)) => n1.subtype(n2),
|
||||
(t1, t2) => t1 == t2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for PrimitiveType {
|
||||
@ -357,6 +360,17 @@ impl NumericType {
|
||||
NumericSuffix::Rad => NumericType::Known(UnitType::Angle(UnitAngle::Radians)),
|
||||
}
|
||||
}
|
||||
|
||||
fn subtype(&self, other: &NumericType) -> bool {
|
||||
use NumericType::*;
|
||||
|
||||
match (self, other) {
|
||||
(Unknown, _) | (_, Unknown) => false,
|
||||
(a, b) if a == b => true,
|
||||
(_, Any) => true,
|
||||
(_, _) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<UnitLen> for NumericType {
|
||||
@ -605,12 +619,7 @@ impl KclValue {
|
||||
|
||||
fn coerce_to_array_type(&self, ty: &RuntimeType, len: ArrayLen, exec_state: &mut ExecState) -> Option<KclValue> {
|
||||
match self {
|
||||
KclValue::HomArray { value, ty: aty } => {
|
||||
// TODO could check types of values individually
|
||||
if aty != ty {
|
||||
return None;
|
||||
}
|
||||
|
||||
KclValue::HomArray { value, ty: aty } if aty == ty => {
|
||||
let value = match len {
|
||||
ArrayLen::None => value.clone(),
|
||||
ArrayLen::NonEmpty => {
|
||||
@ -631,6 +640,10 @@ impl KclValue {
|
||||
|
||||
Some(KclValue::HomArray { value, ty: ty.clone() })
|
||||
}
|
||||
value if len.satisfied(1) && value.has_type(ty) => Some(KclValue::HomArray {
|
||||
value: vec![value.clone()],
|
||||
ty: ty.clone(),
|
||||
}),
|
||||
KclValue::MixedArray { value, .. } => {
|
||||
let value = match len {
|
||||
ArrayLen::None => value.clone(),
|
||||
@ -661,26 +674,13 @@ impl KclValue {
|
||||
value: Vec::new(),
|
||||
ty: ty.clone(),
|
||||
}),
|
||||
value if len.satisfied(1) => {
|
||||
if value.has_type(ty) {
|
||||
Some(KclValue::HomArray {
|
||||
value: vec![value.clone()],
|
||||
ty: ty.clone(),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn coerce_to_tuple_type(&self, tys: &[RuntimeType], exec_state: &mut ExecState) -> Option<KclValue> {
|
||||
match self {
|
||||
KclValue::MixedArray { value, .. } | KclValue::HomArray { value, .. } => {
|
||||
if value.len() < tys.len() {
|
||||
return None;
|
||||
}
|
||||
KclValue::MixedArray { value, .. } | KclValue::HomArray { value, .. } if value.len() == tys.len() => {
|
||||
let mut result = Vec::new();
|
||||
for (i, t) in tys.iter().enumerate() {
|
||||
result.push(value[i].coerce(t, exec_state)?);
|
||||
@ -695,16 +695,10 @@ impl KclValue {
|
||||
value: Vec::new(),
|
||||
meta: meta.clone(),
|
||||
}),
|
||||
value if tys.len() == 1 => {
|
||||
if value.has_type(&tys[0]) {
|
||||
Some(KclValue::MixedArray {
|
||||
value: vec![value.clone()],
|
||||
meta: Vec::new(),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
value if tys.len() == 1 && value.has_type(&tys[0]) => Some(KclValue::MixedArray {
|
||||
value: vec![value.clone()],
|
||||
meta: Vec::new(),
|
||||
}),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@ -731,6 +725,10 @@ impl KclValue {
|
||||
// TODO remove non-required fields
|
||||
Some(self.clone())
|
||||
}
|
||||
KclValue::KclNone { meta, .. } if tys.is_empty() => Some(KclValue::Object {
|
||||
value: HashMap::new(),
|
||||
meta: meta.clone(),
|
||||
}),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@ -768,3 +766,485 @@ impl KclValue {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
fn values(exec_state: &mut ExecState) -> Vec<KclValue> {
|
||||
vec![
|
||||
KclValue::Bool {
|
||||
value: true,
|
||||
meta: Vec::new(),
|
||||
},
|
||||
KclValue::Number {
|
||||
value: 1.0,
|
||||
ty: NumericType::count(),
|
||||
meta: Vec::new(),
|
||||
},
|
||||
KclValue::String {
|
||||
value: "hello".to_owned(),
|
||||
meta: Vec::new(),
|
||||
},
|
||||
KclValue::MixedArray {
|
||||
value: Vec::new(),
|
||||
meta: Vec::new(),
|
||||
},
|
||||
KclValue::HomArray {
|
||||
value: Vec::new(),
|
||||
ty: RuntimeType::solid(),
|
||||
},
|
||||
KclValue::Object {
|
||||
value: crate::execution::KclObjectFields::new(),
|
||||
meta: Vec::new(),
|
||||
},
|
||||
KclValue::TagIdentifier(Box::new("foo".parse().unwrap())),
|
||||
KclValue::TagDeclarator(Box::new(crate::parsing::ast::types::TagDeclarator::new("foo"))),
|
||||
KclValue::Plane {
|
||||
value: Box::new(Plane::from_plane_data(crate::std::sketch::PlaneData::XY, exec_state)),
|
||||
},
|
||||
// No easy way to make a Face, Sketch, Solid, or Helix
|
||||
KclValue::ImportedGeometry(crate::execution::ImportedGeometry {
|
||||
id: uuid::Uuid::nil(),
|
||||
value: Vec::new(),
|
||||
meta: Vec::new(),
|
||||
}),
|
||||
// Other values don't have types
|
||||
]
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
fn assert_coerce_results(
|
||||
value: &KclValue,
|
||||
super_type: &RuntimeType,
|
||||
expected_value: &KclValue,
|
||||
exec_state: &mut ExecState,
|
||||
) {
|
||||
let is_subtype = value == expected_value;
|
||||
assert_eq!(&value.coerce(&super_type, exec_state).unwrap(), expected_value);
|
||||
assert_eq!(
|
||||
is_subtype,
|
||||
value.principal_type().is_some() && value.principal_type().unwrap().subtype(super_type),
|
||||
"{:?} <: {super_type:?} should be {is_subtype}",
|
||||
value.principal_type().unwrap()
|
||||
);
|
||||
assert!(
|
||||
expected_value.principal_type().unwrap().subtype(super_type),
|
||||
"{} <: {super_type}",
|
||||
expected_value.principal_type().unwrap()
|
||||
)
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn coerce_idempotent() {
|
||||
let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock().await);
|
||||
let values = values(&mut exec_state);
|
||||
for v in &values {
|
||||
// Identity subtype
|
||||
let ty = v.principal_type().unwrap();
|
||||
assert_coerce_results(v, &ty, v, &mut exec_state);
|
||||
|
||||
// Union subtype
|
||||
let uty1 = RuntimeType::Union(vec![ty.clone()]);
|
||||
let uty2 = RuntimeType::Union(vec![ty.clone(), RuntimeType::Primitive(PrimitiveType::Boolean)]);
|
||||
assert_coerce_results(v, &uty1, v, &mut exec_state);
|
||||
assert_coerce_results(v, &uty2, v, &mut exec_state);
|
||||
|
||||
// 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);
|
||||
|
||||
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,
|
||||
);
|
||||
|
||||
// Tuple subtype
|
||||
let tty = RuntimeType::Tuple(vec![ty.clone()]);
|
||||
assert_coerce_results(
|
||||
v,
|
||||
&tty,
|
||||
&KclValue::MixedArray {
|
||||
value: vec![v.clone()],
|
||||
meta: Vec::new(),
|
||||
},
|
||||
&mut exec_state,
|
||||
);
|
||||
}
|
||||
|
||||
for v in &values[1..] {
|
||||
// Not a subtype
|
||||
assert!(v
|
||||
.coerce(&RuntimeType::Primitive(PrimitiveType::Boolean), &mut exec_state)
|
||||
.is_none());
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn coerce_none() {
|
||||
let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock().await);
|
||||
let none = KclValue::KclNone {
|
||||
value: crate::parsing::ast::types::KclNone::new(),
|
||||
meta: Vec::new(),
|
||||
};
|
||||
|
||||
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);
|
||||
assert_coerce_results(
|
||||
&none,
|
||||
&aty,
|
||||
&KclValue::HomArray {
|
||||
value: Vec::new(),
|
||||
ty: RuntimeType::solid(),
|
||||
},
|
||||
&mut exec_state,
|
||||
);
|
||||
assert_coerce_results(
|
||||
&none,
|
||||
&aty0,
|
||||
&KclValue::HomArray {
|
||||
value: Vec::new(),
|
||||
ty: RuntimeType::solid(),
|
||||
},
|
||||
&mut exec_state,
|
||||
);
|
||||
assert!(none.coerce(&aty1, &mut exec_state).is_none());
|
||||
assert!(none.coerce(&aty1p, &mut exec_state).is_none());
|
||||
|
||||
let tty = RuntimeType::Tuple(vec![]);
|
||||
let tty1 = RuntimeType::Tuple(vec![RuntimeType::solid()]);
|
||||
assert_coerce_results(
|
||||
&none,
|
||||
&tty,
|
||||
&KclValue::MixedArray {
|
||||
value: Vec::new(),
|
||||
meta: Vec::new(),
|
||||
},
|
||||
&mut exec_state,
|
||||
);
|
||||
assert!(none.coerce(&tty1, &mut exec_state).is_none());
|
||||
|
||||
let oty = RuntimeType::Object(vec![]);
|
||||
assert_coerce_results(
|
||||
&none,
|
||||
&oty,
|
||||
&KclValue::Object {
|
||||
value: HashMap::new(),
|
||||
meta: Vec::new(),
|
||||
},
|
||||
&mut exec_state,
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn coerce_record() {
|
||||
let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock().await);
|
||||
|
||||
let obj0 = KclValue::Object {
|
||||
value: HashMap::new(),
|
||||
meta: Vec::new(),
|
||||
};
|
||||
let obj1 = KclValue::Object {
|
||||
value: [(
|
||||
"foo".to_owned(),
|
||||
KclValue::Bool {
|
||||
value: true,
|
||||
meta: Vec::new(),
|
||||
},
|
||||
)]
|
||||
.into(),
|
||||
meta: Vec::new(),
|
||||
};
|
||||
let obj2 = KclValue::Object {
|
||||
value: [
|
||||
(
|
||||
"foo".to_owned(),
|
||||
KclValue::Bool {
|
||||
value: true,
|
||||
meta: Vec::new(),
|
||||
},
|
||||
),
|
||||
(
|
||||
"bar".to_owned(),
|
||||
KclValue::Number {
|
||||
value: 0.0,
|
||||
ty: NumericType::count(),
|
||||
meta: Vec::new(),
|
||||
},
|
||||
),
|
||||
(
|
||||
"baz".to_owned(),
|
||||
KclValue::Number {
|
||||
value: 42.0,
|
||||
ty: NumericType::count(),
|
||||
meta: Vec::new(),
|
||||
},
|
||||
),
|
||||
]
|
||||
.into(),
|
||||
meta: Vec::new(),
|
||||
};
|
||||
|
||||
let ty0 = RuntimeType::Object(vec![]);
|
||||
assert_coerce_results(&obj0, &ty0, &obj0, &mut exec_state);
|
||||
assert_coerce_results(&obj1, &ty0, &obj1, &mut exec_state);
|
||||
assert_coerce_results(&obj2, &ty0, &obj2, &mut exec_state);
|
||||
|
||||
let ty1 = RuntimeType::Object(vec![("foo".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean))]);
|
||||
assert!(&obj0.coerce(&ty1, &mut exec_state).is_none());
|
||||
assert_coerce_results(&obj1, &ty1, &obj1, &mut exec_state);
|
||||
assert_coerce_results(&obj2, &ty1, &obj2, &mut exec_state);
|
||||
|
||||
// Different ordering, (TODO - test for covariance once implemented)
|
||||
let ty2 = RuntimeType::Object(vec![
|
||||
(
|
||||
"bar".to_owned(),
|
||||
RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
|
||||
),
|
||||
("foo".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean)),
|
||||
]);
|
||||
assert!(&obj0.coerce(&ty2, &mut exec_state).is_none());
|
||||
assert!(&obj1.coerce(&ty2, &mut exec_state).is_none());
|
||||
assert_coerce_results(&obj2, &ty2, &obj2, &mut exec_state);
|
||||
|
||||
// field not present
|
||||
let tyq = RuntimeType::Object(vec![("qux".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean))]);
|
||||
assert!(&obj0.coerce(&tyq, &mut exec_state).is_none());
|
||||
assert!(&obj1.coerce(&tyq, &mut exec_state).is_none());
|
||||
assert!(&obj2.coerce(&tyq, &mut exec_state).is_none());
|
||||
|
||||
// field with different type
|
||||
let ty1 = RuntimeType::Object(vec![("bar".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean))]);
|
||||
assert!(&obj2.coerce(&ty1, &mut exec_state).is_none());
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn coerce_array() {
|
||||
let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock().await);
|
||||
|
||||
let hom_arr = KclValue::HomArray {
|
||||
value: vec![
|
||||
KclValue::Number {
|
||||
value: 0.0,
|
||||
ty: NumericType::count(),
|
||||
meta: Vec::new(),
|
||||
},
|
||||
KclValue::Number {
|
||||
value: 1.0,
|
||||
ty: NumericType::count(),
|
||||
meta: Vec::new(),
|
||||
},
|
||||
KclValue::Number {
|
||||
value: 2.0,
|
||||
ty: NumericType::count(),
|
||||
meta: Vec::new(),
|
||||
},
|
||||
KclValue::Number {
|
||||
value: 3.0,
|
||||
ty: NumericType::count(),
|
||||
meta: Vec::new(),
|
||||
},
|
||||
],
|
||||
ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
|
||||
};
|
||||
let mixed1 = KclValue::MixedArray {
|
||||
value: vec![
|
||||
KclValue::Number {
|
||||
value: 0.0,
|
||||
ty: NumericType::count(),
|
||||
meta: Vec::new(),
|
||||
},
|
||||
KclValue::Number {
|
||||
value: 1.0,
|
||||
ty: NumericType::count(),
|
||||
meta: Vec::new(),
|
||||
},
|
||||
],
|
||||
meta: Vec::new(),
|
||||
};
|
||||
let mixed2 = KclValue::MixedArray {
|
||||
value: vec![
|
||||
KclValue::Number {
|
||||
value: 0.0,
|
||||
ty: NumericType::count(),
|
||||
meta: Vec::new(),
|
||||
},
|
||||
KclValue::Bool {
|
||||
value: true,
|
||||
meta: Vec::new(),
|
||||
},
|
||||
],
|
||||
meta: Vec::new(),
|
||||
};
|
||||
|
||||
// Principal types
|
||||
let tyh = RuntimeType::Array(
|
||||
Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
|
||||
ArrayLen::Known(4),
|
||||
);
|
||||
let tym1 = RuntimeType::Tuple(vec![
|
||||
RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
|
||||
RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
|
||||
]);
|
||||
let tym2 = RuntimeType::Tuple(vec![
|
||||
RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
|
||||
RuntimeType::Primitive(PrimitiveType::Boolean),
|
||||
]);
|
||||
assert_coerce_results(&hom_arr, &tyh, &hom_arr, &mut exec_state);
|
||||
assert_coerce_results(&mixed1, &tym1, &mixed1, &mut exec_state);
|
||||
assert_coerce_results(&mixed2, &tym2, &mixed2, &mut exec_state);
|
||||
assert!(&mixed1.coerce(&tym2, &mut exec_state).is_none());
|
||||
assert!(&mixed2.coerce(&tym1, &mut exec_state).is_none());
|
||||
|
||||
// Length subtyping
|
||||
let tyhn = RuntimeType::Array(
|
||||
Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
|
||||
ArrayLen::None,
|
||||
);
|
||||
let tyh1 = RuntimeType::Array(
|
||||
Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
|
||||
ArrayLen::NonEmpty,
|
||||
);
|
||||
let tyh3 = RuntimeType::Array(
|
||||
Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
|
||||
ArrayLen::Known(3),
|
||||
);
|
||||
assert_coerce_results(&hom_arr, &tyhn, &hom_arr, &mut exec_state);
|
||||
assert_coerce_results(&hom_arr, &tyh1, &hom_arr, &mut exec_state);
|
||||
assert!(&hom_arr.coerce(&tyh3, &mut exec_state).is_none());
|
||||
|
||||
let hom_arr0 = KclValue::HomArray {
|
||||
value: vec![],
|
||||
ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
|
||||
};
|
||||
assert_coerce_results(&hom_arr0, &tyhn, &hom_arr0, &mut exec_state);
|
||||
assert!(&hom_arr0.coerce(&tyh1, &mut exec_state).is_none());
|
||||
assert!(&hom_arr0.coerce(&tyh3, &mut exec_state).is_none());
|
||||
|
||||
// Covariance
|
||||
// let tyh = RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any))), ArrayLen::Known(4));
|
||||
let tym1 = RuntimeType::Tuple(vec![
|
||||
RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
|
||||
RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
|
||||
]);
|
||||
let tym2 = RuntimeType::Tuple(vec![
|
||||
RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
|
||||
RuntimeType::Primitive(PrimitiveType::Boolean),
|
||||
]);
|
||||
// TODO implement covariance for homogenous arrays
|
||||
// assert_coerce_results(&hom_arr, &tyh, &hom_arr, &mut exec_state);
|
||||
assert_coerce_results(&mixed1, &tym1, &mixed1, &mut exec_state);
|
||||
assert_coerce_results(&mixed2, &tym2, &mixed2, &mut exec_state);
|
||||
|
||||
// Mixed to homogenous
|
||||
let hom_arr_2 = KclValue::HomArray {
|
||||
value: vec![
|
||||
KclValue::Number {
|
||||
value: 0.0,
|
||||
ty: NumericType::count(),
|
||||
meta: Vec::new(),
|
||||
},
|
||||
KclValue::Number {
|
||||
value: 1.0,
|
||||
ty: NumericType::count(),
|
||||
meta: Vec::new(),
|
||||
},
|
||||
],
|
||||
ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
|
||||
};
|
||||
let mixed0 = KclValue::MixedArray {
|
||||
value: vec![],
|
||||
meta: Vec::new(),
|
||||
};
|
||||
assert_coerce_results(&mixed1, &tyhn, &hom_arr_2, &mut exec_state);
|
||||
assert_coerce_results(&mixed1, &tyh1, &hom_arr_2, &mut exec_state);
|
||||
assert_coerce_results(&mixed0, &tyhn, &hom_arr0, &mut exec_state);
|
||||
assert!(&mixed0.coerce(&tyh, &mut exec_state).is_none());
|
||||
assert!(&mixed0.coerce(&tyh1, &mut exec_state).is_none());
|
||||
|
||||
// Homogehous to mixed
|
||||
assert_coerce_results(&hom_arr_2, &tym1, &mixed1, &mut exec_state);
|
||||
assert!(&hom_arr.coerce(&tym1, &mut exec_state).is_none());
|
||||
assert!(&hom_arr_2.coerce(&tym2, &mut exec_state).is_none());
|
||||
|
||||
assert!(&mixed0.coerce(&tym1, &mut exec_state).is_none());
|
||||
assert!(&mixed0.coerce(&tym2, &mut exec_state).is_none());
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn coerce_union() {
|
||||
let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock().await);
|
||||
|
||||
// Subtyping smaller unions
|
||||
assert!(RuntimeType::Union(vec![]).subtype(&RuntimeType::Union(vec![
|
||||
RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
|
||||
RuntimeType::Primitive(PrimitiveType::Boolean)
|
||||
])));
|
||||
assert!(
|
||||
RuntimeType::Union(vec![RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any))]).subtype(
|
||||
&RuntimeType::Union(vec![
|
||||
RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
|
||||
RuntimeType::Primitive(PrimitiveType::Boolean)
|
||||
])
|
||||
)
|
||||
);
|
||||
assert!(RuntimeType::Union(vec![
|
||||
RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
|
||||
RuntimeType::Primitive(PrimitiveType::Boolean)
|
||||
])
|
||||
.subtype(&RuntimeType::Union(vec![
|
||||
RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
|
||||
RuntimeType::Primitive(PrimitiveType::Boolean)
|
||||
])));
|
||||
|
||||
// Covariance
|
||||
let count = KclValue::Number {
|
||||
value: 1.0,
|
||||
ty: NumericType::count(),
|
||||
meta: Vec::new(),
|
||||
};
|
||||
|
||||
let tya = RuntimeType::Union(vec![RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any))]);
|
||||
let tya2 = RuntimeType::Union(vec![
|
||||
RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
|
||||
RuntimeType::Primitive(PrimitiveType::Boolean),
|
||||
]);
|
||||
assert_coerce_results(&count, &tya, &count, &mut exec_state);
|
||||
assert_coerce_results(&count, &tya2, &count, &mut exec_state);
|
||||
|
||||
// No matching type
|
||||
let tyb = RuntimeType::Union(vec![RuntimeType::Primitive(PrimitiveType::Boolean)]);
|
||||
let tyb2 = RuntimeType::Union(vec![
|
||||
RuntimeType::Primitive(PrimitiveType::Boolean),
|
||||
RuntimeType::Primitive(PrimitiveType::String),
|
||||
]);
|
||||
assert!(count.coerce(&tyb, &mut exec_state).is_none());
|
||||
assert!(count.coerce(&tyb2, &mut exec_state).is_none());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user