use std::collections::HashMap;
use std::str::FromStr;
use anyhow::Result;
use kittycad_modeling_cmds::units::UnitAngle;
use kittycad_modeling_cmds::units::UnitLength;
use serde::Deserialize;
use serde::Serialize;
use crate::CompilationIssue;
use crate::KclError;
use crate::SourceRange;
use crate::errors::KclErrorDetails;
use crate::exec::PlaneKind;
use crate::execution::ExecState;
use crate::execution::Plane;
use crate::execution::PlaneInfo;
use crate::execution::Point3d;
use crate::execution::SKETCH_OBJECT_META;
use crate::execution::SKETCH_OBJECT_META_SKETCH;
use crate::execution::annotations;
use crate::execution::kcl_value::KclValue;
use crate::execution::kcl_value::TypeDef;
use crate::execution::memory::{self};
use crate::fmt;
use crate::parsing::ast::types::PrimitiveType as AstPrimitiveType;
use crate::parsing::ast::types::Type;
use crate::parsing::token::NumericSuffix;
use crate::std::args::FromKclValue;
use crate::std::args::TyF64;
#[derive(Debug, Clone, PartialEq)]
pub enum RuntimeType {
Primitive(PrimitiveType),
Array(Box<RuntimeType>, ArrayLen),
Union(Vec<RuntimeType>),
Tuple(Vec<RuntimeType>),
Object(Vec<(String, RuntimeType)>, bool),
}
impl RuntimeType {
pub fn any() -> Self {
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)
}
pub fn function() -> Self {
RuntimeType::Primitive(PrimitiveType::Function)
}
pub fn segment() -> Self {
RuntimeType::Primitive(PrimitiveType::Segment)
}
pub fn segments() -> Self {
RuntimeType::Array(Box::new(Self::segment()), ArrayLen::Minimum(1))
}
pub fn sketch() -> Self {
RuntimeType::Primitive(PrimitiveType::Sketch)
}
pub fn sketch_or_surface() -> Self {
RuntimeType::Union(vec![Self::sketch(), Self::plane(), Self::face()])
}
pub fn sketches() -> Self {
RuntimeType::Array(
Box::new(RuntimeType::Primitive(PrimitiveType::Sketch)),
ArrayLen::Minimum(1),
)
}
pub fn faces() -> Self {
RuntimeType::Array(
Box::new(RuntimeType::Primitive(PrimitiveType::Face)),
ArrayLen::Minimum(1),
)
}
pub fn tagged_faces() -> Self {
RuntimeType::Array(
Box::new(RuntimeType::Primitive(PrimitiveType::TaggedFace)),
ArrayLen::Minimum(1),
)
}
pub fn solids() -> Self {
RuntimeType::Array(
Box::new(RuntimeType::Primitive(PrimitiveType::Solid)),
ArrayLen::Minimum(1),
)
}
pub fn solid() -> Self {
RuntimeType::Primitive(PrimitiveType::Solid)
}
pub fn helices() -> Self {
RuntimeType::Array(
Box::new(RuntimeType::Primitive(PrimitiveType::Helix)),
ArrayLen::Minimum(1),
)
}
pub fn helix() -> Self {
RuntimeType::Primitive(PrimitiveType::Helix)
}
pub fn plane() -> Self {
RuntimeType::Primitive(PrimitiveType::Plane)
}
pub fn face() -> Self {
RuntimeType::Primitive(PrimitiveType::Face)
}
pub fn tag_decl() -> Self {
RuntimeType::Primitive(PrimitiveType::TagDecl)
}
pub fn tagged_face() -> Self {
RuntimeType::Primitive(PrimitiveType::TaggedFace)
}
pub fn tagged_face_or_segment() -> Self {
RuntimeType::Union(vec![
RuntimeType::Primitive(PrimitiveType::TaggedFace),
RuntimeType::Primitive(PrimitiveType::Segment),
])
}
pub fn tagged_edge() -> Self {
RuntimeType::Primitive(PrimitiveType::TaggedEdge)
}
pub fn bool() -> Self {
RuntimeType::Primitive(PrimitiveType::Boolean)
}
pub fn string() -> Self {
RuntimeType::Primitive(PrimitiveType::String)
}
pub fn imported() -> Self {
RuntimeType::Primitive(PrimitiveType::ImportedGeometry)
}
pub fn point2d() -> Self {
RuntimeType::Array(Box::new(RuntimeType::length()), ArrayLen::Known(2))
}
pub fn point3d() -> Self {
RuntimeType::Array(Box::new(RuntimeType::length()), ArrayLen::Known(3))
}
pub fn length() -> Self {
RuntimeType::Primitive(PrimitiveType::Number(NumericType::Known(UnitType::GenericLength)))
}
pub fn known_length(len: UnitLength) -> Self {
RuntimeType::Primitive(PrimitiveType::Number(NumericType::Known(UnitType::Length(len))))
}
pub fn angle() -> Self {
RuntimeType::Primitive(PrimitiveType::Number(NumericType::Known(UnitType::GenericAngle)))
}
pub fn radians() -> Self {
RuntimeType::Primitive(PrimitiveType::Number(NumericType::Known(UnitType::Angle(
UnitAngle::Radians,
))))
}
pub fn degrees() -> Self {
RuntimeType::Primitive(PrimitiveType::Number(NumericType::Known(UnitType::Angle(
UnitAngle::Degrees,
))))
}
pub fn count() -> Self {
RuntimeType::Primitive(PrimitiveType::Number(NumericType::Known(UnitType::Count)))
}
pub fn num_any() -> Self {
RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any))
}
pub fn from_parsed(
value: Type,
exec_state: &mut ExecState,
source_range: SourceRange,
constrainable: bool,
suppress_warnings: bool,
) -> Result<Self, CompilationIssue> {
match value {
Type::Primitive(pt) => Self::from_parsed_primitive(pt, exec_state, source_range, suppress_warnings),
Type::Array { ty, len } => {
Self::from_parsed(*ty, exec_state, source_range, constrainable, suppress_warnings)
.map(|t| RuntimeType::Array(Box::new(t), len))
}
Type::Union { tys } => tys
.into_iter()
.map(|t| Self::from_parsed(t.inner, exec_state, source_range, constrainable, suppress_warnings))
.collect::<Result<Vec<_>, CompilationIssue>>()
.map(RuntimeType::Union),
Type::Object { properties } => properties
.into_iter()
.map(|(id, ty)| {
RuntimeType::from_parsed(ty.inner, exec_state, source_range, constrainable, suppress_warnings)
.map(|ty| (id.name.clone(), ty))
})
.collect::<Result<Vec<_>, CompilationIssue>>()
.map(|values| RuntimeType::Object(values, constrainable)),
}
}
fn from_parsed_primitive(
value: AstPrimitiveType,
exec_state: &mut ExecState,
source_range: SourceRange,
suppress_warnings: bool,
) -> Result<Self, CompilationIssue> {
Ok(match value {
AstPrimitiveType::Any => RuntimeType::Primitive(PrimitiveType::Any),
AstPrimitiveType::None => RuntimeType::Primitive(PrimitiveType::None),
AstPrimitiveType::String => RuntimeType::Primitive(PrimitiveType::String),
AstPrimitiveType::Boolean => RuntimeType::Primitive(PrimitiveType::Boolean),
AstPrimitiveType::Number(suffix) => {
let ty = match suffix {
NumericSuffix::None => NumericType::Any,
_ => NumericType::from_parsed(suffix, &exec_state.mod_local.settings),
};
RuntimeType::Primitive(PrimitiveType::Number(ty))
}
AstPrimitiveType::Named { id } => Self::from_alias(&id.name, exec_state, source_range, suppress_warnings)?,
AstPrimitiveType::TagDecl => RuntimeType::Primitive(PrimitiveType::TagDecl),
AstPrimitiveType::ImportedGeometry => RuntimeType::Primitive(PrimitiveType::ImportedGeometry),
AstPrimitiveType::Function(_) => RuntimeType::Primitive(PrimitiveType::Function),
})
}
pub fn from_alias(
alias: &str,
exec_state: &mut ExecState,
source_range: SourceRange,
suppress_warnings: bool,
) -> Result<Self, CompilationIssue> {
let ty_val = exec_state
.stack()
.get(&format!("{}{}", memory::TYPE_PREFIX, alias), source_range)
.map_err(|_| CompilationIssue::err(source_range, format!("Unknown type: {alias}")))?;
Ok(match ty_val {
KclValue::Type {
value, experimental, ..
} => {
let result = match value {
TypeDef::RustRepr(ty, _) => RuntimeType::Primitive(ty.clone()),
TypeDef::Alias(ty) => ty.clone(),
};
if *experimental && !suppress_warnings {
exec_state.warn_experimental(&format!("the type `{alias}`"), source_range);
}
result
}
_ => unreachable!(),
})
}
pub fn human_friendly_type(&self) -> String {
match self {
RuntimeType::Primitive(ty) => ty.to_string(),
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()
.map(Self::human_friendly_type)
.collect::<Vec<_>>()
.join(" or "),
RuntimeType::Tuple(tys) => format!(
"a tuple with values of types ({})",
tys.iter().map(Self::human_friendly_type).collect::<Vec<_>>().join(", ")
),
RuntimeType::Object(..) => format!("an object with fields {self}"),
}
}
pub(crate) fn subtype(&self, sup: &RuntimeType) -> bool {
use RuntimeType::*;
match (self, sup) {
(_, Primitive(PrimitiveType::Any)) => true,
(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))),
(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,
(Object(t1, _), Primitive(PrimitiveType::Axis2d)) => {
t1.iter()
.any(|(n, t)| n == "origin" && t.subtype(&RuntimeType::point2d()))
&& t1
.iter()
.any(|(n, t)| n == "direction" && t.subtype(&RuntimeType::point2d()))
}
(Object(t1, _), Primitive(PrimitiveType::Axis3d)) => {
t1.iter()
.any(|(n, t)| n == "origin" && t.subtype(&RuntimeType::point3d()))
&& t1
.iter()
.any(|(n, t)| n == "direction" && t.subtype(&RuntimeType::point3d()))
}
(Primitive(PrimitiveType::Axis2d), Object(t2, _)) => {
t2.iter()
.any(|(n, t)| n == "origin" && t.subtype(&RuntimeType::point2d()))
&& t2
.iter()
.any(|(n, t)| n == "direction" && t.subtype(&RuntimeType::point2d()))
}
(Primitive(PrimitiveType::Axis3d), Object(t2, _)) => {
t2.iter()
.any(|(n, t)| n == "origin" && t.subtype(&RuntimeType::point3d()))
&& t2
.iter()
.any(|(n, t)| n == "direction" && t.subtype(&RuntimeType::point3d()))
}
_ => false,
}
}
fn display_multiple(&self) -> String {
match self {
RuntimeType::Primitive(ty) => ty.display_multiple(),
RuntimeType::Array(..) => "arrays".to_owned(),
RuntimeType::Union(tys) => tys
.iter()
.map(|t| t.display_multiple())
.collect::<Vec<_>>()
.join(" or "),
RuntimeType::Tuple(_) => "tuples".to_owned(),
RuntimeType::Object(..) => format!("objects with fields {self}"),
}
}
}
impl std::fmt::Display for RuntimeType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
RuntimeType::Primitive(t) => t.fmt(f),
RuntimeType::Array(t, l) => match l {
ArrayLen::None => write!(f, "[{t}]"),
ArrayLen::Minimum(n) => write!(f, "[{t}; {n}+]"),
ArrayLen::Known(n) => write!(f, "[{t}; {n}]"),
},
RuntimeType::Tuple(ts) => write!(
f,
"({})",
ts.iter().map(|t| t.to_string()).collect::<Vec<_>>().join(", ")
),
RuntimeType::Union(ts) => write!(
f,
"{}",
ts.iter().map(|t| t.to_string()).collect::<Vec<_>>().join(" | ")
),
RuntimeType::Object(items, _) => write!(
f,
"{{ {} }}",
items
.iter()
.map(|(n, t)| format!("{n}: {t}"))
.collect::<Vec<_>>()
.join(", ")
),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, ts_rs::TS)]
pub enum ArrayLen {
None,
Minimum(usize),
Known(usize),
}
impl ArrayLen {
pub fn subtype(self, other: ArrayLen) -> bool {
match (self, other) {
(_, ArrayLen::None) => 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,
}
}
pub fn satisfied(self, len: usize, allow_shrink: bool) -> Option<usize> {
match self {
ArrayLen::None => Some(len),
ArrayLen::Minimum(s) => (len >= s).then_some(len),
ArrayLen::Known(s) => (if allow_shrink { len >= s } else { len == s }).then_some(s),
}
}
pub fn human_friendly_type(self) -> String {
match self {
ArrayLen::None | ArrayLen::Minimum(0) => "any number of elements".to_owned(),
ArrayLen::Minimum(1) => "at least 1 element".to_owned(),
ArrayLen::Minimum(n) => format!("at least {n} elements"),
ArrayLen::Known(0) => "no elements".to_owned(),
ArrayLen::Known(1) => "exactly 1 element".to_owned(),
ArrayLen::Known(n) => format!("exactly {n} elements"),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum PrimitiveType {
Any,
None,
Number(NumericType),
String,
Boolean,
TaggedEdge,
TaggedFace,
TagDecl,
GdtAnnotation,
Segment,
Sketch,
Constraint,
Solid,
Plane,
Helix,
Face,
Edge,
BoundedEdge,
Axis2d,
Axis3d,
ImportedGeometry,
Function,
}
impl PrimitiveType {
fn display_multiple(&self) -> String {
match self {
PrimitiveType::Any => "any values".to_owned(),
PrimitiveType::None => "none values".to_owned(),
PrimitiveType::Number(NumericType::Known(unit)) => format!("numbers({unit})"),
PrimitiveType::Number(_) => "numbers".to_owned(),
PrimitiveType::String => "strings".to_owned(),
PrimitiveType::Boolean => "bools".to_owned(),
PrimitiveType::GdtAnnotation => "GD&T Annotations".to_owned(),
PrimitiveType::Segment => "Segments".to_owned(),
PrimitiveType::Sketch => "Sketches".to_owned(),
PrimitiveType::Constraint => "Constraints".to_owned(),
PrimitiveType::Solid => "Solids".to_owned(),
PrimitiveType::Plane => "Planes".to_owned(),
PrimitiveType::Helix => "Helices".to_owned(),
PrimitiveType::Face => "Faces".to_owned(),
PrimitiveType::Edge => "Edges".to_owned(),
PrimitiveType::BoundedEdge => "BoundedEdges".to_owned(),
PrimitiveType::Axis2d => "2d axes".to_owned(),
PrimitiveType::Axis3d => "3d axes".to_owned(),
PrimitiveType::ImportedGeometry => "imported geometries".to_owned(),
PrimitiveType::Function => "functions".to_owned(),
PrimitiveType::TagDecl => "tag declarators".to_owned(),
PrimitiveType::TaggedEdge => "tagged edges".to_owned(),
PrimitiveType::TaggedFace => "tagged faces".to_owned(),
}
}
fn subtype(&self, other: &PrimitiveType) -> bool {
match (self, other) {
(_, PrimitiveType::Any) => true,
(PrimitiveType::Number(n1), PrimitiveType::Number(n2)) => n1.subtype(n2),
(PrimitiveType::TaggedEdge, PrimitiveType::TaggedFace)
| (PrimitiveType::TaggedEdge, PrimitiveType::Edge) => true,
(t1, t2) => t1 == t2,
}
}
}
impl std::fmt::Display for PrimitiveType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
PrimitiveType::Any => write!(f, "any"),
PrimitiveType::None => write!(f, "none"),
PrimitiveType::Number(NumericType::Known(unit)) => write!(f, "number({unit})"),
PrimitiveType::Number(NumericType::Unknown) => write!(f, "number(unknown units)"),
PrimitiveType::Number(NumericType::Default { .. }) => write!(f, "number"),
PrimitiveType::Number(NumericType::Any) => write!(f, "number(any units)"),
PrimitiveType::String => write!(f, "string"),
PrimitiveType::Boolean => write!(f, "bool"),
PrimitiveType::TagDecl => write!(f, "tag declarator"),
PrimitiveType::TaggedEdge => write!(f, "tagged edge"),
PrimitiveType::TaggedFace => write!(f, "tagged face"),
PrimitiveType::GdtAnnotation => write!(f, "GD&T Annotation"),
PrimitiveType::Segment => write!(f, "Segment"),
PrimitiveType::Sketch => write!(f, "Sketch"),
PrimitiveType::Constraint => write!(f, "Constraint"),
PrimitiveType::Solid => write!(f, "Solid"),
PrimitiveType::Plane => write!(f, "Plane"),
PrimitiveType::Face => write!(f, "Face"),
PrimitiveType::Edge => write!(f, "Edge"),
PrimitiveType::BoundedEdge => write!(f, "BoundedEdge"),
PrimitiveType::Axis2d => write!(f, "Axis2d"),
PrimitiveType::Axis3d => write!(f, "Axis3d"),
PrimitiveType::Helix => write!(f, "Helix"),
PrimitiveType::ImportedGeometry => write!(f, "ImportedGeometry"),
PrimitiveType::Function => write!(f, "fn"),
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, ts_rs::TS)]
#[ts(export)]
#[serde(tag = "type")]
pub enum NumericType {
Known(UnitType),
Default { len: UnitLength, angle: UnitAngle },
Unknown,
Any,
}
impl Default for NumericType {
fn default() -> Self {
NumericType::Default {
len: UnitLength::Millimeters,
angle: UnitAngle::Degrees,
}
}
}
impl NumericType {
pub const fn count() -> Self {
NumericType::Known(UnitType::Count)
}
pub const fn mm() -> Self {
NumericType::Known(UnitType::Length(UnitLength::Millimeters))
}
pub const fn radians() -> Self {
NumericType::Known(UnitType::Angle(UnitAngle::Radians))
}
pub const fn degrees() -> Self {
NumericType::Known(UnitType::Angle(UnitAngle::Degrees))
}
pub fn combine_eq(
a: TyF64,
b: TyF64,
exec_state: &mut ExecState,
source_range: SourceRange,
) -> (f64, f64, NumericType) {
use NumericType::*;
match (a.ty, b.ty) {
(at, bt) if at == bt => (a.n, b.n, at),
(at, Any) => (a.n, b.n, at),
(Any, bt) => (a.n, b.n, bt),
(t @ Known(UnitType::Length(l1)), Known(UnitType::Length(l2))) => (a.n, adjust_length(l2, b.n, l1).0, t),
(t @ Known(UnitType::Angle(a1)), Known(UnitType::Angle(a2))) => (a.n, adjust_angle(a2, b.n, a1).0, t),
(t @ Known(UnitType::Length(_)), Known(UnitType::GenericLength)) => (a.n, b.n, t),
(Known(UnitType::GenericLength), t @ Known(UnitType::Length(_))) => (a.n, b.n, t),
(t @ Known(UnitType::Angle(_)), Known(UnitType::GenericAngle)) => (a.n, b.n, t),
(Known(UnitType::GenericAngle), t @ Known(UnitType::Angle(_))) => (a.n, b.n, t),
(Known(UnitType::Count), Default { .. }) | (Default { .. }, Known(UnitType::Count)) => {
(a.n, b.n, Known(UnitType::Count))
}
(t @ Known(UnitType::Length(l1)), Default { len: l2, .. }) if l1 == l2 => (a.n, b.n, t),
(Default { len: l1, .. }, t @ Known(UnitType::Length(l2))) if l1 == l2 => (a.n, b.n, t),
(t @ Known(UnitType::Angle(a1)), Default { angle: a2, .. }) if a1 == a2 => {
if b.n != 0.0 {
exec_state.warn(
CompilationIssue::err(source_range, "Prefer to use explicit units for angles"),
annotations::WARN_ANGLE_UNITS,
);
}
(a.n, b.n, t)
}
(Default { angle: a1, .. }, t @ Known(UnitType::Angle(a2))) if a1 == a2 => {
if a.n != 0.0 {
exec_state.warn(
CompilationIssue::err(source_range, "Prefer to use explicit units for angles"),
annotations::WARN_ANGLE_UNITS,
);
}
(a.n, b.n, t)
}
_ => (a.n, b.n, Unknown),
}
}
pub fn combine_eq_coerce(
a: TyF64,
b: TyF64,
for_errs: Option<(&mut ExecState, SourceRange)>,
) -> (f64, f64, NumericType) {
use NumericType::*;
match (a.ty, b.ty) {
(at, bt) if at == bt => (a.n, b.n, at),
(at, Any) => (a.n, b.n, at),
(Any, bt) => (a.n, b.n, bt),
(t @ Known(UnitType::Length(l1)), Known(UnitType::Length(l2))) => (a.n, adjust_length(l2, b.n, l1).0, t),
(t @ Known(UnitType::Angle(a1)), Known(UnitType::Angle(a2))) => (a.n, adjust_angle(a2, b.n, a1).0, t),
(t @ Known(UnitType::Length(_)), Known(UnitType::GenericLength)) => (a.n, b.n, t),
(Known(UnitType::GenericLength), t @ Known(UnitType::Length(_))) => (a.n, b.n, t),
(t @ Known(UnitType::Angle(_)), Known(UnitType::GenericAngle)) => (a.n, b.n, t),
(Known(UnitType::GenericAngle), t @ Known(UnitType::Angle(_))) => (a.n, b.n, t),
(Known(UnitType::Count), Default { .. }) | (Default { .. }, Known(UnitType::Count)) => {
(a.n, b.n, Known(UnitType::Count))
}
(t @ Known(UnitType::Length(l1)), Default { len: l2, .. }) => (a.n, adjust_length(l2, b.n, l1).0, t),
(Default { len: l1, .. }, t @ Known(UnitType::Length(l2))) => (adjust_length(l1, a.n, l2).0, b.n, t),
(t @ Known(UnitType::Angle(a1)), Default { angle: a2, .. }) => {
if let Some((exec_state, source_range)) = for_errs
&& b.n != 0.0
{
exec_state.warn(
CompilationIssue::err(source_range, "Prefer to use explicit units for angles"),
annotations::WARN_ANGLE_UNITS,
);
}
(a.n, adjust_angle(a2, b.n, a1).0, t)
}
(Default { angle: a1, .. }, t @ Known(UnitType::Angle(a2))) => {
if let Some((exec_state, source_range)) = for_errs
&& a.n != 0.0
{
exec_state.warn(
CompilationIssue::err(source_range, "Prefer to use explicit units for angles"),
annotations::WARN_ANGLE_UNITS,
);
}
(adjust_angle(a1, a.n, a2).0, b.n, t)
}
(Default { len: l1, .. }, Known(UnitType::GenericLength)) => (a.n, b.n, l1.into()),
(Known(UnitType::GenericLength), Default { len: l2, .. }) => (a.n, b.n, l2.into()),
(Default { angle: a1, .. }, Known(UnitType::GenericAngle)) => {
if let Some((exec_state, source_range)) = for_errs
&& b.n != 0.0
{
exec_state.warn(
CompilationIssue::err(source_range, "Prefer to use explicit units for angles"),
annotations::WARN_ANGLE_UNITS,
);
}
(a.n, b.n, a1.into())
}
(Known(UnitType::GenericAngle), Default { angle: a2, .. }) => {
if let Some((exec_state, source_range)) = for_errs
&& a.n != 0.0
{
exec_state.warn(
CompilationIssue::err(source_range, "Prefer to use explicit units for angles"),
annotations::WARN_ANGLE_UNITS,
);
}
(a.n, b.n, a2.into())
}
(Known(_), Known(_)) | (Default { .. }, Default { .. }) | (_, Unknown) | (Unknown, _) => {
(a.n, b.n, Unknown)
}
}
}
pub fn combine_eq_array(input: &[TyF64]) -> (Vec<f64>, NumericType) {
use NumericType::*;
let result = input.iter().map(|t| t.n).collect();
let mut ty = Any;
for i in input {
if i.ty == Any || ty == i.ty {
continue;
}
match (&ty, &i.ty) {
(Any, Default { .. }) if i.n == 0.0 => {}
(Any, t) => {
ty = *t;
}
(_, Unknown) | (Default { .. }, Default { .. }) => return (result, Unknown),
(Known(UnitType::Count), Default { .. }) | (Default { .. }, Known(UnitType::Count)) => {
ty = Known(UnitType::Count);
}
(Known(UnitType::Length(l1)), Default { len: l2, .. }) if l1 == l2 || i.n == 0.0 => {}
(Known(UnitType::Angle(a1)), Default { angle: a2, .. }) if a1 == a2 || i.n == 0.0 => {}
(Default { len: l1, .. }, Known(UnitType::Length(l2))) if l1 == l2 => {
ty = Known(UnitType::Length(*l2));
}
(Default { angle: a1, .. }, Known(UnitType::Angle(a2))) if a1 == a2 => {
ty = Known(UnitType::Angle(*a2));
}
_ => return (result, Unknown),
}
}
if ty == Any && !input.is_empty() {
ty = input[0].ty;
}
(result, ty)
}
pub fn combine_mul(a: TyF64, b: TyF64) -> (f64, f64, NumericType) {
use NumericType::*;
match (a.ty, b.ty) {
(at @ Default { .. }, bt @ Default { .. }) if at == bt => (a.n, b.n, at),
(Default { .. }, Default { .. }) => (a.n, b.n, Unknown),
(Known(UnitType::Count), bt) => (a.n, b.n, bt),
(at, Known(UnitType::Count)) => (a.n, b.n, at),
(at @ Known(_), Default { .. }) | (Default { .. }, at @ Known(_)) => (a.n, b.n, at),
(Any, Any) => (a.n, b.n, Any),
_ => (a.n, b.n, Unknown),
}
}
pub fn combine_div(a: TyF64, b: TyF64) -> (f64, f64, NumericType) {
use NumericType::*;
match (a.ty, b.ty) {
(at @ Default { .. }, bt @ Default { .. }) if at == bt => (a.n, b.n, at),
(at, bt) if at == bt => (a.n, b.n, Known(UnitType::Count)),
(Default { .. }, Default { .. }) => (a.n, b.n, Unknown),
(at, Known(UnitType::Count) | Any) => (a.n, b.n, at),
(at @ Known(_), Default { .. }) => (a.n, b.n, at),
(Known(UnitType::Count), _) => (a.n, b.n, Known(UnitType::Count)),
_ => (a.n, b.n, Unknown),
}
}
pub fn combine_mod(a: TyF64, b: TyF64) -> (f64, f64, NumericType) {
use NumericType::*;
match (a.ty, b.ty) {
(at @ Default { .. }, bt @ Default { .. }) if at == bt => (a.n, b.n, at),
(at, bt) if at == bt => (a.n, b.n, at),
(Default { .. }, Default { .. }) => (a.n, b.n, Unknown),
(at, Known(UnitType::Count) | Any) => (a.n, b.n, at),
(at @ Known(_), Default { .. }) => (a.n, b.n, at),
(Known(UnitType::Count), _) => (a.n, b.n, Known(UnitType::Count)),
_ => (a.n, b.n, Unknown),
}
}
pub fn combine_range(
a: TyF64,
b: TyF64,
exec_state: &mut ExecState,
source_range: SourceRange,
) -> Result<(f64, f64, NumericType), KclError> {
use NumericType::*;
match (a.ty, b.ty) {
(at, bt) if at == bt => Ok((a.n, b.n, at)),
(at, Any) => Ok((a.n, b.n, at)),
(Any, bt) => Ok((a.n, b.n, bt)),
(Known(UnitType::Length(l1)), Known(UnitType::Length(l2))) => {
Err(KclError::new_semantic(KclErrorDetails::new(
format!("Range start and range end have incompatible units: {l1} and {l2}"),
vec![source_range],
)))
}
(Known(UnitType::Angle(a1)), Known(UnitType::Angle(a2))) => {
Err(KclError::new_semantic(KclErrorDetails::new(
format!("Range start and range end have incompatible units: {a1} and {a2}"),
vec![source_range],
)))
}
(t @ Known(UnitType::Length(_)), Known(UnitType::GenericLength)) => Ok((a.n, b.n, t)),
(Known(UnitType::GenericLength), t @ Known(UnitType::Length(_))) => Ok((a.n, b.n, t)),
(t @ Known(UnitType::Angle(_)), Known(UnitType::GenericAngle)) => Ok((a.n, b.n, t)),
(Known(UnitType::GenericAngle), t @ Known(UnitType::Angle(_))) => Ok((a.n, b.n, t)),
(Known(UnitType::Count), Default { .. }) | (Default { .. }, Known(UnitType::Count)) => {
Ok((a.n, b.n, Known(UnitType::Count)))
}
(t @ Known(UnitType::Length(l1)), Default { len: l2, .. }) if l1 == l2 => Ok((a.n, b.n, t)),
(Default { len: l1, .. }, t @ Known(UnitType::Length(l2))) if l1 == l2 => Ok((a.n, b.n, t)),
(t @ Known(UnitType::Angle(a1)), Default { angle: a2, .. }) if a1 == a2 => {
if b.n != 0.0 {
exec_state.warn(
CompilationIssue::err(source_range, "Prefer to use explicit units for angles"),
annotations::WARN_ANGLE_UNITS,
);
}
Ok((a.n, b.n, t))
}
(Default { angle: a1, .. }, t @ Known(UnitType::Angle(a2))) if a1 == a2 => {
if a.n != 0.0 {
exec_state.warn(
CompilationIssue::err(source_range, "Prefer to use explicit units for angles"),
annotations::WARN_ANGLE_UNITS,
);
}
Ok((a.n, b.n, t))
}
_ => {
let a = fmt::human_display_number(a.n, a.ty);
let b = fmt::human_display_number(b.n, b.ty);
Err(KclError::new_semantic(KclErrorDetails::new(
format!(
"Range start and range end must be of the same type and have compatible units, but found {a} and {b}",
),
vec![source_range],
)))
}
}
}
pub fn from_parsed(suffix: NumericSuffix, settings: &super::MetaSettings) -> Self {
match suffix {
NumericSuffix::None => NumericType::Default {
len: settings.default_length_units,
angle: settings.default_angle_units,
},
NumericSuffix::Count => NumericType::Known(UnitType::Count),
NumericSuffix::Length => NumericType::Known(UnitType::GenericLength),
NumericSuffix::Angle => NumericType::Known(UnitType::GenericAngle),
NumericSuffix::Mm => NumericType::Known(UnitType::Length(UnitLength::Millimeters)),
NumericSuffix::Cm => NumericType::Known(UnitType::Length(UnitLength::Centimeters)),
NumericSuffix::M => NumericType::Known(UnitType::Length(UnitLength::Meters)),
NumericSuffix::Inch => NumericType::Known(UnitType::Length(UnitLength::Inches)),
NumericSuffix::Ft => NumericType::Known(UnitType::Length(UnitLength::Feet)),
NumericSuffix::Yd => NumericType::Known(UnitType::Length(UnitLength::Yards)),
NumericSuffix::Deg => NumericType::Known(UnitType::Angle(UnitAngle::Degrees)),
NumericSuffix::Rad => NumericType::Known(UnitType::Angle(UnitAngle::Radians)),
NumericSuffix::Unknown => NumericType::Unknown,
}
}
fn subtype(&self, other: &NumericType) -> bool {
use NumericType::*;
match (self, other) {
(_, Any) => true,
(a, b) if a == b => true,
(
NumericType::Known(UnitType::Length(_))
| NumericType::Known(UnitType::GenericLength)
| NumericType::Default { .. },
NumericType::Known(UnitType::GenericLength),
)
| (
NumericType::Known(UnitType::Angle(_))
| NumericType::Known(UnitType::GenericAngle)
| NumericType::Default { .. },
NumericType::Known(UnitType::GenericAngle),
) => true,
(Unknown, _) | (_, Unknown) => false,
(_, _) => false,
}
}
fn is_unknown(&self) -> bool {
matches!(
self,
NumericType::Unknown
| NumericType::Known(UnitType::GenericAngle)
| NumericType::Known(UnitType::GenericLength)
)
}
pub fn is_fully_specified(&self) -> bool {
!matches!(
self,
NumericType::Unknown
| NumericType::Known(UnitType::GenericAngle)
| NumericType::Known(UnitType::GenericLength)
| NumericType::Any
| NumericType::Default { .. }
)
}
fn example_ty(&self) -> Option<String> {
match self {
Self::Known(t) if !self.is_unknown() => Some(t.to_string()),
Self::Default { len, .. } => Some(len.to_string()),
_ => None,
}
}
fn coerce(&self, val: &KclValue) -> Result<KclValue, CoercionError> {
let (value, ty, meta) = match val {
KclValue::Number { value, ty, meta } => (value, ty, meta),
KclValue::SketchVar { .. } => return Ok(val.clone()),
_ => return Err(val.into()),
};
if ty.subtype(self) {
return Ok(KclValue::Number {
value: *value,
ty: *ty,
meta: meta.clone(),
});
}
use NumericType::*;
match (ty, self) {
(Unknown, _) => Err(CoercionError::from(val).with_explicit(self.example_ty().unwrap_or("mm".to_owned()))),
(_, Unknown) => Err(val.into()),
(Any, _) => Ok(KclValue::Number {
value: *value,
ty: *self,
meta: meta.clone(),
}),
(_, Default { .. }) => Ok(KclValue::Number {
value: *value,
ty: *ty,
meta: meta.clone(),
}),
(Known(UnitType::Length(l1)), Known(UnitType::Length(l2))) => {
let (value, ty) = adjust_length(*l1, *value, *l2);
Ok(KclValue::Number {
value,
ty: Known(UnitType::Length(ty)),
meta: meta.clone(),
})
}
(Known(UnitType::Angle(a1)), Known(UnitType::Angle(a2))) => {
let (value, ty) = adjust_angle(*a1, *value, *a2);
Ok(KclValue::Number {
value,
ty: Known(UnitType::Angle(ty)),
meta: meta.clone(),
})
}
(Known(_), Known(_)) => Err(val.into()),
(Default { .. }, Known(UnitType::Count)) => Ok(KclValue::Number {
value: *value,
ty: Known(UnitType::Count),
meta: meta.clone(),
}),
(Default { len: l1, .. }, Known(UnitType::Length(l2))) => {
let (value, ty) = adjust_length(*l1, *value, *l2);
Ok(KclValue::Number {
value,
ty: Known(UnitType::Length(ty)),
meta: meta.clone(),
})
}
(Default { angle: a1, .. }, Known(UnitType::Angle(a2))) => {
let (value, ty) = adjust_angle(*a1, *value, *a2);
Ok(KclValue::Number {
value,
ty: Known(UnitType::Angle(ty)),
meta: meta.clone(),
})
}
(_, _) => unreachable!(),
}
}
pub fn as_length(&self) -> Option<UnitLength> {
match self {
Self::Known(UnitType::Length(len)) | Self::Default { len, .. } => Some(*len),
_ => None,
}
}
}
impl From<NumericType> for RuntimeType {
fn from(t: NumericType) -> RuntimeType {
RuntimeType::Primitive(PrimitiveType::Number(t))
}
}
impl From<UnitLength> for NumericType {
fn from(value: UnitLength) -> Self {
NumericType::Known(UnitType::Length(value))
}
}
impl From<Option<UnitLength>> for NumericType {
fn from(value: Option<UnitLength>) -> Self {
match value {
Some(v) => v.into(),
None => NumericType::Unknown,
}
}
}
impl From<UnitAngle> for NumericType {
fn from(value: UnitAngle) -> Self {
NumericType::Known(UnitType::Angle(value))
}
}
impl From<UnitLength> for NumericSuffix {
fn from(value: UnitLength) -> Self {
match value {
UnitLength::Millimeters => NumericSuffix::Mm,
UnitLength::Centimeters => NumericSuffix::Cm,
UnitLength::Meters => NumericSuffix::M,
UnitLength::Inches => NumericSuffix::Inch,
UnitLength::Feet => NumericSuffix::Ft,
UnitLength::Yards => NumericSuffix::Yd,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, ts_rs::TS)]
pub struct NumericSuffixTypeConvertError;
impl TryFrom<NumericType> for NumericSuffix {
type Error = NumericSuffixTypeConvertError;
fn try_from(value: NumericType) -> Result<Self, Self::Error> {
match value {
NumericType::Known(UnitType::Count) => Ok(NumericSuffix::Count),
NumericType::Known(UnitType::Length(unit_length)) => Ok(NumericSuffix::from(unit_length)),
NumericType::Known(UnitType::GenericLength) => Ok(NumericSuffix::Length),
NumericType::Known(UnitType::Angle(UnitAngle::Degrees)) => Ok(NumericSuffix::Deg),
NumericType::Known(UnitType::Angle(UnitAngle::Radians)) => Ok(NumericSuffix::Rad),
NumericType::Known(UnitType::GenericAngle) => Ok(NumericSuffix::Angle),
NumericType::Default { .. } => Ok(NumericSuffix::None),
NumericType::Unknown => Ok(NumericSuffix::Unknown),
NumericType::Any => Err(NumericSuffixTypeConvertError),
}
}
}
#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, ts_rs::TS)]
#[ts(export)]
#[serde(tag = "type")]
pub enum UnitType {
Count,
Length(UnitLength),
GenericLength,
Angle(UnitAngle),
GenericAngle,
}
impl UnitType {
pub(crate) fn to_suffix(self) -> Option<String> {
match self {
UnitType::Count => Some("_".to_owned()),
UnitType::GenericLength | UnitType::GenericAngle => None,
UnitType::Length(l) => Some(l.to_string()),
UnitType::Angle(a) => Some(a.to_string()),
}
}
}
impl std::fmt::Display for UnitType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
UnitType::Count => write!(f, "Count"),
UnitType::Length(l) => l.fmt(f),
UnitType::GenericLength => write!(f, "Length"),
UnitType::Angle(a) => a.fmt(f),
UnitType::GenericAngle => write!(f, "Angle"),
}
}
}
pub fn adjust_length(from: UnitLength, value: f64, to: UnitLength) -> (f64, UnitLength) {
use UnitLength::*;
if from == to {
return (value, to);
}
let (base, base_unit) = match from {
Millimeters => (value, Millimeters),
Centimeters => (value * 10.0, Millimeters),
Meters => (value * 1000.0, Millimeters),
Inches => (value, Inches),
Feet => (value * 12.0, Inches),
Yards => (value * 36.0, Inches),
};
let (base, base_unit) = match (base_unit, to) {
(Millimeters, Inches) | (Millimeters, Feet) | (Millimeters, Yards) => (base / 25.4, Inches),
(Inches, Millimeters) | (Inches, Centimeters) | (Inches, Meters) => (base * 25.4, Millimeters),
_ => (base, base_unit),
};
let value = match (base_unit, to) {
(Millimeters, Millimeters) => base,
(Millimeters, Centimeters) => base / 10.0,
(Millimeters, Meters) => base / 1000.0,
(Inches, Inches) => base,
(Inches, Feet) => base / 12.0,
(Inches, Yards) => base / 36.0,
_ => unreachable!(),
};
(value, to)
}
pub fn adjust_angle(from: UnitAngle, value: f64, to: UnitAngle) -> (f64, UnitAngle) {
use std::f64::consts::PI;
use UnitAngle::*;
let value = match (from, to) {
(Degrees, Degrees) => value,
(Degrees, Radians) => (value / 180.0) * PI,
(Radians, Degrees) => 180.0 * value / PI,
(Radians, Radians) => value,
};
(value, to)
}
pub(super) fn length_from_str(s: &str, source_range: SourceRange) -> Result<UnitLength, KclError> {
match s {
"mm" => Ok(UnitLength::Millimeters),
"cm" => Ok(UnitLength::Centimeters),
"m" => Ok(UnitLength::Meters),
"inch" | "in" => Ok(UnitLength::Inches),
"ft" => Ok(UnitLength::Feet),
"yd" => Ok(UnitLength::Yards),
value => Err(KclError::new_semantic(KclErrorDetails::new(
format!("Unexpected value for length units: `{value}`; expected one of `mm`, `cm`, `m`, `in`, `ft`, `yd`"),
vec![source_range],
))),
}
}
pub(super) fn angle_from_str(s: &str, source_range: SourceRange) -> Result<UnitAngle, KclError> {
UnitAngle::from_str(s).map_err(|_| {
KclError::new_semantic(KclErrorDetails::new(
format!("Unexpected value for angle units: `{s}`; expected one of `deg`, `rad`"),
vec![source_range],
))
})
}
#[derive(Debug, Clone)]
pub struct CoercionError {
pub found: Option<RuntimeType>,
pub explicit_coercion: Option<String>,
}
impl CoercionError {
fn with_explicit(mut self, c: String) -> Self {
self.explicit_coercion = Some(c);
self
}
}
impl From<&'_ KclValue> for CoercionError {
fn from(value: &'_ KclValue) -> Self {
CoercionError {
found: value.principal_type(),
explicit_coercion: None,
}
}
}
impl KclValue {
pub fn has_type(&self, ty: &RuntimeType) -> bool {
let Some(self_ty) = self.principal_type() else {
return false;
};
self_ty.subtype(ty)
}
pub fn coerce(
&self,
ty: &RuntimeType,
convert_units: bool,
exec_state: &mut ExecState,
) -> Result<KclValue, CoercionError> {
match self {
KclValue::Tuple { value, .. }
if value.len() == 1
&& !matches!(ty, RuntimeType::Primitive(PrimitiveType::Any) | 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::Primitive(PrimitiveType::Any) | 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),
RuntimeType::Tuple(tys) => self.coerce_to_tuple_type(tys, convert_units, exec_state),
RuntimeType::Union(tys) => self.coerce_to_union_type(tys, convert_units, exec_state),
RuntimeType::Object(tys, constrainable) => {
self.coerce_to_object_type(tys, *constrainable, convert_units, exec_state)
}
}
}
fn coerce_to_primitive_type(
&self,
ty: &PrimitiveType,
convert_units: bool,
exec_state: &mut ExecState,
) -> Result<KclValue, CoercionError> {
match ty {
PrimitiveType::Any => Ok(self.clone()),
PrimitiveType::None => match self {
KclValue::KclNone { .. } => Ok(self.clone()),
_ => Err(self.into()),
},
PrimitiveType::Number(ty) => {
if convert_units {
return ty.coerce(self);
}
if let KclValue::Number { value: n, meta, .. } = &self
&& ty.is_fully_specified()
{
let value = KclValue::Number {
ty: NumericType::Any,
value: *n,
meta: meta.clone(),
};
return ty.coerce(&value);
}
ty.coerce(self)
}
PrimitiveType::String => match self {
KclValue::String { .. } => Ok(self.clone()),
_ => Err(self.into()),
},
PrimitiveType::Boolean => match self {
KclValue::Bool { .. } => Ok(self.clone()),
_ => Err(self.into()),
},
PrimitiveType::GdtAnnotation => match self {
KclValue::GdtAnnotation { .. } => Ok(self.clone()),
_ => Err(self.into()),
},
PrimitiveType::Segment => match self {
KclValue::Segment { .. } => Ok(self.clone()),
_ => Err(self.into()),
},
PrimitiveType::Sketch => match self {
KclValue::Sketch { .. } => Ok(self.clone()),
KclValue::Object { value, .. } => {
let Some(meta) = value.get(SKETCH_OBJECT_META) else {
return Err(self.into());
};
let KclValue::Object { value: meta_map, .. } = meta else {
return Err(self.into());
};
let Some(sketch) = meta_map.get(SKETCH_OBJECT_META_SKETCH).and_then(KclValue::as_sketch) else {
return Err(self.into());
};
Ok(KclValue::Sketch {
value: Box::new(sketch.clone()),
})
}
_ => Err(self.into()),
},
PrimitiveType::Constraint => match self {
KclValue::SketchConstraint { .. } => Ok(self.clone()),
_ => Err(self.into()),
},
PrimitiveType::Solid => match self {
KclValue::Solid { .. } => Ok(self.clone()),
_ => Err(self.into()),
},
PrimitiveType::Plane => {
match self {
KclValue::String { value: s, .. }
if [
"xy", "xz", "yz", "-xy", "-xz", "-yz", "XY", "XZ", "YZ", "-XY", "-XZ", "-YZ",
]
.contains(&&**s) =>
{
Ok(self.clone())
}
KclValue::Plane { .. } => Ok(self.clone()),
KclValue::Object { value, meta, .. } => {
let origin = value
.get("origin")
.and_then(Point3d::from_kcl_val)
.ok_or(CoercionError::from(self))?;
let x_axis = value
.get("xAxis")
.and_then(Point3d::from_kcl_val)
.ok_or(CoercionError::from(self))?;
let y_axis = value
.get("yAxis")
.and_then(Point3d::from_kcl_val)
.ok_or(CoercionError::from(self))?;
let z_axis = x_axis.axes_cross_product(&y_axis);
if value.get("zAxis").is_some() {
exec_state.warn(CompilationIssue::err(
self.into(),
"Object with a zAxis field is being coerced into a plane, but the zAxis is ignored.",
), annotations::WARN_IGNORED_Z_AXIS);
}
let id = exec_state.mod_local.id_generator.next_uuid();
let info = PlaneInfo {
origin,
x_axis: x_axis.normalize(),
y_axis: y_axis.normalize(),
z_axis: z_axis.normalize(),
};
let plane = Plane {
id,
artifact_id: id.into(),
object_id: None,
kind: PlaneKind::from(&info),
info,
meta: meta.clone(),
};
Ok(KclValue::Plane { value: Box::new(plane) })
}
_ => Err(self.into()),
}
}
PrimitiveType::Face => match self {
KclValue::Face { .. } => Ok(self.clone()),
_ => Err(self.into()),
},
PrimitiveType::Helix => match self {
KclValue::Helix { .. } => Ok(self.clone()),
_ => Err(self.into()),
},
PrimitiveType::Edge => match self {
KclValue::Uuid { .. } => Ok(self.clone()),
KclValue::TagIdentifier { .. } => Ok(self.clone()),
_ => Err(self.into()),
},
PrimitiveType::BoundedEdge => match self {
KclValue::BoundedEdge { .. } => Ok(self.clone()),
_ => Err(self.into()),
},
PrimitiveType::TaggedEdge => match self {
KclValue::TagIdentifier { .. } => Ok(self.clone()),
_ => Err(self.into()),
},
PrimitiveType::TaggedFace => match self {
KclValue::TagIdentifier { .. } => Ok(self.clone()),
s @ KclValue::String { value, .. } if ["start", "end", "START", "END"].contains(&&**value) => {
Ok(s.clone())
}
_ => Err(self.into()),
},
PrimitiveType::Axis2d => match self {
KclValue::Object {
value: values, meta, ..
} => {
if values
.get("origin")
.ok_or(CoercionError::from(self))?
.has_type(&RuntimeType::point2d())
&& values
.get("direction")
.ok_or(CoercionError::from(self))?
.has_type(&RuntimeType::point2d())
{
return Ok(self.clone());
}
let origin = values.get("origin").ok_or(self.into()).and_then(|p| {
p.coerce_to_array_type(
&RuntimeType::length(),
convert_units,
ArrayLen::Known(2),
exec_state,
true,
)
})?;
let direction = values.get("direction").ok_or(self.into()).and_then(|p| {
p.coerce_to_array_type(
&RuntimeType::length(),
convert_units,
ArrayLen::Known(2),
exec_state,
true,
)
})?;
Ok(KclValue::Object {
value: [("origin".to_owned(), origin), ("direction".to_owned(), direction)].into(),
meta: meta.clone(),
constrainable: false,
})
}
_ => Err(self.into()),
},
PrimitiveType::Axis3d => match self {
KclValue::Object {
value: values, meta, ..
} => {
if values
.get("origin")
.ok_or(CoercionError::from(self))?
.has_type(&RuntimeType::point3d())
&& values
.get("direction")
.ok_or(CoercionError::from(self))?
.has_type(&RuntimeType::point3d())
{
return Ok(self.clone());
}
let origin = values.get("origin").ok_or(self.into()).and_then(|p| {
p.coerce_to_array_type(
&RuntimeType::length(),
convert_units,
ArrayLen::Known(3),
exec_state,
true,
)
})?;
let direction = values.get("direction").ok_or(self.into()).and_then(|p| {
p.coerce_to_array_type(
&RuntimeType::length(),
convert_units,
ArrayLen::Known(3),
exec_state,
true,
)
})?;
Ok(KclValue::Object {
value: [("origin".to_owned(), origin), ("direction".to_owned(), direction)].into(),
meta: meta.clone(),
constrainable: false,
})
}
_ => Err(self.into()),
},
PrimitiveType::ImportedGeometry => match self {
KclValue::ImportedGeometry { .. } => Ok(self.clone()),
_ => Err(self.into()),
},
PrimitiveType::Function => match self {
KclValue::Function { .. } => Ok(self.clone()),
_ => Err(self.into()),
},
PrimitiveType::TagDecl => match self {
KclValue::TagDeclarator { .. } => Ok(self.clone()),
_ => Err(self.into()),
},
}
}
fn coerce_to_array_type(
&self,
ty: &RuntimeType,
convert_units: bool,
len: ArrayLen,
exec_state: &mut ExecState,
allow_shrink: bool,
) -> Result<KclValue, CoercionError> {
match self {
KclValue::HomArray { value, ty: aty, .. } => {
let satisfied_len = len.satisfied(value.len(), allow_shrink);
if aty.subtype(ty) {
return satisfied_len
.map(|len| KclValue::HomArray {
value: value[..len].to_vec(),
ty: aty.clone(),
})
.ok_or(self.into());
}
if let Some(satisfied_len) = satisfied_len {
let value_result = value
.iter()
.take(satisfied_len)
.map(|v| v.coerce(ty, convert_units, exec_state))
.collect::<Result<Vec<_>, _>>();
if let Ok(value) = value_result {
return Ok(KclValue::HomArray { value, ty: ty.clone() });
}
}
let mut values = Vec::new();
for item in value {
if let KclValue::HomArray { value: inner_value, .. } = item {
for item in inner_value {
values.push(item.coerce(ty, convert_units, exec_state)?);
}
} else {
values.push(item.coerce(ty, convert_units, exec_state)?);
}
}
let len = len
.satisfied(values.len(), allow_shrink)
.ok_or(CoercionError::from(self))?;
if len > values.len() {
let message = format!(
"Internal: Expected coerced array length {len} to be less than or equal to original length {}",
values.len()
);
exec_state.err(CompilationIssue::err(self.into(), message.clone()));
#[cfg(debug_assertions)]
panic!("{message}");
}
values.truncate(len);
Ok(KclValue::HomArray {
value: values,
ty: ty.clone(),
})
}
KclValue::Tuple { value, .. } => {
let len = len
.satisfied(value.len(), allow_shrink)
.ok_or(CoercionError::from(self))?;
let value = value
.iter()
.map(|item| item.coerce(ty, convert_units, exec_state))
.take(len)
.collect::<Result<Vec<_>, _>>()?;
Ok(KclValue::HomArray { value, ty: ty.clone() })
}
KclValue::KclNone { .. } if len.satisfied(0, false).is_some() => Ok(KclValue::HomArray {
value: Vec::new(),
ty: ty.clone(),
}),
_ if len.satisfied(1, false).is_some() => self.coerce(ty, convert_units, exec_state),
_ => Err(self.into()),
}
}
fn coerce_to_tuple_type(
&self,
tys: &[RuntimeType],
convert_units: bool,
exec_state: &mut ExecState,
) -> Result<KclValue, CoercionError> {
match self {
KclValue::Tuple { 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, convert_units, exec_state)?);
}
Ok(KclValue::Tuple {
value: result,
meta: Vec::new(),
})
}
KclValue::KclNone { meta, .. } if tys.is_empty() => Ok(KclValue::Tuple {
value: Vec::new(),
meta: meta.clone(),
}),
_ if tys.len() == 1 => self.coerce(&tys[0], convert_units, exec_state),
_ => Err(self.into()),
}
}
fn coerce_to_union_type(
&self,
tys: &[RuntimeType],
convert_units: bool,
exec_state: &mut ExecState,
) -> Result<KclValue, CoercionError> {
for t in tys {
if let Ok(v) = self.coerce(t, convert_units, exec_state) {
return Ok(v);
}
}
Err(self.into())
}
fn coerce_to_object_type(
&self,
tys: &[(String, RuntimeType)],
constrainable: bool,
_convert_units: bool,
_exec_state: &mut ExecState,
) -> Result<KclValue, CoercionError> {
match self {
KclValue::Object { value, meta, .. } => {
for (s, t) in tys {
if !value.get(s).ok_or(CoercionError::from(self))?.has_type(t) {
return Err(self.into());
}
}
Ok(KclValue::Object {
value: value.clone(),
meta: meta.clone(),
constrainable,
})
}
KclValue::KclNone { meta, .. } if tys.is_empty() => Ok(KclValue::Object {
value: HashMap::new(),
meta: meta.clone(),
constrainable,
}),
_ => Err(self.into()),
}
}
pub fn principal_type(&self) -> Option<RuntimeType> {
match self {
KclValue::Bool { .. } => Some(RuntimeType::Primitive(PrimitiveType::Boolean)),
KclValue::Number { ty, .. } => Some(RuntimeType::Primitive(PrimitiveType::Number(*ty))),
KclValue::String { .. } => Some(RuntimeType::Primitive(PrimitiveType::String)),
KclValue::SketchVar { value, .. } => Some(RuntimeType::Primitive(PrimitiveType::Number(value.ty))),
KclValue::SketchConstraint { .. } => Some(RuntimeType::Primitive(PrimitiveType::Constraint)),
KclValue::Object {
value, constrainable, ..
} => {
let properties = value
.iter()
.map(|(k, v)| v.principal_type().map(|t| (k.clone(), t)))
.collect::<Option<Vec<_>>>()?;
Some(RuntimeType::Object(properties, *constrainable))
}
KclValue::GdtAnnotation { .. } => Some(RuntimeType::Primitive(PrimitiveType::GdtAnnotation)),
KclValue::Plane { .. } => Some(RuntimeType::Primitive(PrimitiveType::Plane)),
KclValue::Sketch { .. } => Some(RuntimeType::Primitive(PrimitiveType::Sketch)),
KclValue::Solid { .. } => Some(RuntimeType::Primitive(PrimitiveType::Solid)),
KclValue::Face { .. } => Some(RuntimeType::Primitive(PrimitiveType::Face)),
KclValue::Segment { .. } => Some(RuntimeType::Primitive(PrimitiveType::Segment)),
KclValue::Helix { .. } => Some(RuntimeType::Primitive(PrimitiveType::Helix)),
KclValue::ImportedGeometry(..) => Some(RuntimeType::Primitive(PrimitiveType::ImportedGeometry)),
KclValue::Tuple { value, .. } => Some(RuntimeType::Tuple(
value.iter().map(|v| v.principal_type()).collect::<Option<Vec<_>>>()?,
)),
KclValue::HomArray { ty, value, .. } => {
Some(RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Known(value.len())))
}
KclValue::TagIdentifier(_) => Some(RuntimeType::Primitive(PrimitiveType::TaggedEdge)),
KclValue::TagDeclarator(_) => Some(RuntimeType::Primitive(PrimitiveType::TagDecl)),
KclValue::Uuid { .. } => Some(RuntimeType::Primitive(PrimitiveType::Edge)),
KclValue::Function { .. } => Some(RuntimeType::Primitive(PrimitiveType::Function)),
KclValue::KclNone { .. } => Some(RuntimeType::Primitive(PrimitiveType::None)),
KclValue::Module { .. } | KclValue::Type { .. } => None,
KclValue::BoundedEdge { .. } => Some(RuntimeType::Primitive(PrimitiveType::BoundedEdge)),
}
}
pub fn principal_type_string(&self) -> String {
if let Some(ty) = self.principal_type() {
return format!("`{ty}`");
}
match self {
KclValue::Module { .. } => "module",
KclValue::KclNone { .. } => "none",
KclValue::Type { .. } => "type",
_ => {
debug_assert!(false);
"<unexpected type>"
}
}
.to_owned()
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::execution::ExecTestResults;
use crate::execution::parse_execute;
async fn new_exec_state() -> (crate::ExecutorContext, ExecState) {
let ctx = crate::ExecutorContext::new_mock(None).await;
let exec_state = ExecState::new(&ctx);
(ctx, exec_state)
}
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::Tuple {
value: Vec::new(),
meta: Vec::new(),
},
KclValue::HomArray {
value: Vec::new(),
ty: RuntimeType::solid(),
},
KclValue::Object {
value: crate::execution::KclObjectFields::new(),
meta: Vec::new(),
constrainable: false,
},
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_skipping_engine(crate::std::sketch::PlaneData::XY, exec_state).unwrap(),
),
},
KclValue::ImportedGeometry(crate::execution::ImportedGeometry::new(
uuid::Uuid::nil(),
Vec::new(),
Vec::new(),
)),
]
}
#[track_caller]
fn assert_coerce_results(
value: &KclValue,
super_type: &RuntimeType,
expected_value: &KclValue,
exec_state: &mut ExecState,
) {
let is_subtype = value == 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),
"{:?} <: {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 (ctx, mut exec_state) = new_exec_state().await;
let values = values(&mut exec_state);
for v in &values {
let ty = v.principal_type().unwrap();
assert_coerce_results(v, &ty, v, &mut exec_state);
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);
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::Minimum(1));
match v {
KclValue::HomArray { .. } => {
assert_coerce_results(
v,
&aty,
&KclValue::HomArray {
value: vec![],
ty: ty.clone(),
},
&mut exec_state,
);
v.coerce(&aty1, true, &mut exec_state).unwrap_err();
v.coerce(&aty0, true, &mut exec_state).unwrap_err();
}
KclValue::Tuple { .. } => {}
_ => {
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);
let tty = RuntimeType::Tuple(vec![ty.clone()]);
assert_coerce_results(v, &tty, v, &mut exec_state);
}
}
}
for v in &values[1..] {
v.coerce(&RuntimeType::Primitive(PrimitiveType::Boolean), true, &mut exec_state)
.unwrap_err();
}
ctx.close().await;
}
#[tokio::test(flavor = "multi_thread")]
async fn coerce_none() {
let (ctx, mut exec_state) = new_exec_state().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::Minimum(1));
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,
);
none.coerce(&aty1, true, &mut exec_state).unwrap_err();
none.coerce(&aty1p, true, &mut exec_state).unwrap_err();
let tty = RuntimeType::Tuple(vec![]);
let tty1 = RuntimeType::Tuple(vec![RuntimeType::solid()]);
assert_coerce_results(
&none,
&tty,
&KclValue::Tuple {
value: Vec::new(),
meta: Vec::new(),
},
&mut exec_state,
);
none.coerce(&tty1, true, &mut exec_state).unwrap_err();
let oty = RuntimeType::Object(vec![], false);
assert_coerce_results(
&none,
&oty,
&KclValue::Object {
value: HashMap::new(),
meta: Vec::new(),
constrainable: false,
},
&mut exec_state,
);
ctx.close().await;
}
#[tokio::test(flavor = "multi_thread")]
async fn coerce_record() {
let (ctx, mut exec_state) = new_exec_state().await;
let obj0 = KclValue::Object {
value: HashMap::new(),
meta: Vec::new(),
constrainable: false,
};
let obj1 = KclValue::Object {
value: [(
"foo".to_owned(),
KclValue::Bool {
value: true,
meta: Vec::new(),
},
)]
.into(),
meta: Vec::new(),
constrainable: false,
};
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(),
constrainable: false,
};
let ty0 = RuntimeType::Object(vec![], false);
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))],
false,
);
obj0.coerce(&ty1, true, &mut exec_state).unwrap_err();
assert_coerce_results(&obj1, &ty1, &obj1, &mut exec_state);
assert_coerce_results(&obj2, &ty1, &obj2, &mut exec_state);
let ty2 = RuntimeType::Object(
vec![
(
"bar".to_owned(),
RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
),
("foo".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean)),
],
false,
);
obj0.coerce(&ty2, true, &mut exec_state).unwrap_err();
obj1.coerce(&ty2, true, &mut exec_state).unwrap_err();
assert_coerce_results(&obj2, &ty2, &obj2, &mut exec_state);
let tyq = RuntimeType::Object(
vec![("qux".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean))],
false,
);
obj0.coerce(&tyq, true, &mut exec_state).unwrap_err();
obj1.coerce(&tyq, true, &mut exec_state).unwrap_err();
obj2.coerce(&tyq, true, &mut exec_state).unwrap_err();
let ty1 = RuntimeType::Object(
vec![("bar".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean))],
false,
);
obj2.coerce(&ty1, true, &mut exec_state).unwrap_err();
ctx.close().await;
}
#[tokio::test(flavor = "multi_thread")]
async fn coerce_array() {
let (ctx, mut exec_state) = new_exec_state().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::Tuple {
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::Tuple {
value: vec![
KclValue::Number {
value: 0.0,
ty: NumericType::count(),
meta: Vec::new(),
},
KclValue::Bool {
value: true,
meta: Vec::new(),
},
],
meta: Vec::new(),
};
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);
mixed1.coerce(&tym2, true, &mut exec_state).unwrap_err();
mixed2.coerce(&tym1, true, &mut exec_state).unwrap_err();
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::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![],
ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
};
assert_coerce_results(&hom_arr0, &tyhn, &hom_arr0, &mut exec_state);
hom_arr0.coerce(&tyh1, true, &mut exec_state).unwrap_err();
hom_arr0.coerce(&tyh3, true, &mut exec_state).unwrap_err();
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),
]);
assert_coerce_results(&mixed1, &tym1, &mixed1, &mut exec_state);
assert_coerce_results(&mixed2, &tym2, &mixed2, &mut exec_state);
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::Tuple {
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);
mixed0.coerce(&tyh, true, &mut exec_state).unwrap_err();
mixed0.coerce(&tyh1, true, &mut exec_state).unwrap_err();
assert_coerce_results(&hom_arr_2, &tym1, &mixed1, &mut exec_state);
hom_arr.coerce(&tym1, true, &mut exec_state).unwrap_err();
hom_arr_2.coerce(&tym2, true, &mut exec_state).unwrap_err();
mixed0.coerce(&tym1, true, &mut exec_state).unwrap_err();
mixed0.coerce(&tym2, true, &mut exec_state).unwrap_err();
ctx.close().await;
}
#[tokio::test(flavor = "multi_thread")]
async fn coerce_union() {
let (ctx, mut exec_state) = new_exec_state().await;
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)
]))
);
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);
let tyb = RuntimeType::Union(vec![RuntimeType::Primitive(PrimitiveType::Boolean)]);
let tyb2 = RuntimeType::Union(vec![
RuntimeType::Primitive(PrimitiveType::Boolean),
RuntimeType::Primitive(PrimitiveType::String),
]);
count.coerce(&tyb, true, &mut exec_state).unwrap_err();
count.coerce(&tyb2, true, &mut exec_state).unwrap_err();
ctx.close().await;
}
#[tokio::test(flavor = "multi_thread")]
async fn coerce_axes() {
let (ctx, mut exec_state) = new_exec_state().await;
assert!(RuntimeType::Primitive(PrimitiveType::Axis2d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis2d)));
assert!(RuntimeType::Primitive(PrimitiveType::Axis3d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis3d)));
assert!(!RuntimeType::Primitive(PrimitiveType::Axis3d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis2d)));
assert!(!RuntimeType::Primitive(PrimitiveType::Axis2d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis3d)));
let a2d = KclValue::Object {
value: [
(
"origin".to_owned(),
KclValue::HomArray {
value: vec![
KclValue::Number {
value: 0.0,
ty: NumericType::mm(),
meta: Vec::new(),
},
KclValue::Number {
value: 0.0,
ty: NumericType::mm(),
meta: Vec::new(),
},
],
ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
},
),
(
"direction".to_owned(),
KclValue::HomArray {
value: vec![
KclValue::Number {
value: 1.0,
ty: NumericType::mm(),
meta: Vec::new(),
},
KclValue::Number {
value: 0.0,
ty: NumericType::mm(),
meta: Vec::new(),
},
],
ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
},
),
]
.into(),
meta: Vec::new(),
constrainable: false,
};
let a3d = KclValue::Object {
value: [
(
"origin".to_owned(),
KclValue::HomArray {
value: vec![
KclValue::Number {
value: 0.0,
ty: NumericType::mm(),
meta: Vec::new(),
},
KclValue::Number {
value: 0.0,
ty: NumericType::mm(),
meta: Vec::new(),
},
KclValue::Number {
value: 0.0,
ty: NumericType::mm(),
meta: Vec::new(),
},
],
ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
},
),
(
"direction".to_owned(),
KclValue::HomArray {
value: vec![
KclValue::Number {
value: 1.0,
ty: NumericType::mm(),
meta: Vec::new(),
},
KclValue::Number {
value: 0.0,
ty: NumericType::mm(),
meta: Vec::new(),
},
KclValue::Number {
value: 1.0,
ty: NumericType::mm(),
meta: Vec::new(),
},
],
ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
},
),
]
.into(),
meta: Vec::new(),
constrainable: false,
};
let ty2d = RuntimeType::Primitive(PrimitiveType::Axis2d);
let ty3d = RuntimeType::Primitive(PrimitiveType::Axis3d);
assert_coerce_results(&a2d, &ty2d, &a2d, &mut exec_state);
assert_coerce_results(&a3d, &ty3d, &a3d, &mut exec_state);
assert_coerce_results(&a3d, &ty2d, &a2d, &mut exec_state);
a2d.coerce(&ty3d, true, &mut exec_state).unwrap_err();
ctx.close().await;
}
#[tokio::test(flavor = "multi_thread")]
async fn coerce_numeric() {
let (ctx, mut exec_state) = new_exec_state().await;
let count = KclValue::Number {
value: 1.0,
ty: NumericType::count(),
meta: Vec::new(),
};
let mm = KclValue::Number {
value: 1.0,
ty: NumericType::mm(),
meta: Vec::new(),
};
let inches = KclValue::Number {
value: 1.0,
ty: NumericType::Known(UnitType::Length(UnitLength::Inches)),
meta: Vec::new(),
};
let rads = KclValue::Number {
value: 1.0,
ty: NumericType::Known(UnitType::Angle(UnitAngle::Radians)),
meta: Vec::new(),
};
let default = KclValue::Number {
value: 1.0,
ty: NumericType::default(),
meta: Vec::new(),
};
let any = KclValue::Number {
value: 1.0,
ty: NumericType::Any,
meta: Vec::new(),
};
let unknown = KclValue::Number {
value: 1.0,
ty: NumericType::Unknown,
meta: Vec::new(),
};
assert_coerce_results(&count, &NumericType::count().into(), &count, &mut exec_state);
assert_coerce_results(&mm, &NumericType::mm().into(), &mm, &mut exec_state);
assert_coerce_results(&any, &NumericType::Any.into(), &any, &mut exec_state);
assert_coerce_results(&unknown, &NumericType::Unknown.into(), &unknown, &mut exec_state);
assert_coerce_results(&default, &NumericType::default().into(), &default, &mut exec_state);
assert_coerce_results(&count, &NumericType::Any.into(), &count, &mut exec_state);
assert_coerce_results(&mm, &NumericType::Any.into(), &mm, &mut exec_state);
assert_coerce_results(&unknown, &NumericType::Any.into(), &unknown, &mut exec_state);
assert_coerce_results(&default, &NumericType::Any.into(), &default, &mut exec_state);
assert_eq!(
default
.coerce(
&NumericType::Default {
len: UnitLength::Yards,
angle: UnitAngle::Degrees,
}
.into(),
true,
&mut exec_state
)
.unwrap(),
default
);
count
.coerce(&NumericType::mm().into(), true, &mut exec_state)
.unwrap_err();
mm.coerce(&NumericType::count().into(), true, &mut exec_state)
.unwrap_err();
unknown
.coerce(&NumericType::mm().into(), true, &mut exec_state)
.unwrap_err();
unknown
.coerce(&NumericType::default().into(), true, &mut exec_state)
.unwrap_err();
count
.coerce(&NumericType::Unknown.into(), true, &mut exec_state)
.unwrap_err();
mm.coerce(&NumericType::Unknown.into(), true, &mut exec_state)
.unwrap_err();
default
.coerce(&NumericType::Unknown.into(), true, &mut exec_state)
.unwrap_err();
assert_eq!(
inches
.coerce(&NumericType::mm().into(), true, &mut exec_state)
.unwrap()
.as_f64()
.unwrap()
.round(),
25.0
);
assert_eq!(
rads.coerce(
&NumericType::Known(UnitType::Angle(UnitAngle::Degrees)).into(),
true,
&mut exec_state
)
.unwrap()
.as_f64()
.unwrap()
.round(),
57.0
);
assert_eq!(
inches
.coerce(&NumericType::default().into(), true, &mut exec_state)
.unwrap()
.as_f64()
.unwrap()
.round(),
1.0
);
assert_eq!(
rads.coerce(&NumericType::default().into(), true, &mut exec_state)
.unwrap()
.as_f64()
.unwrap()
.round(),
1.0
);
ctx.close().await;
}
#[track_caller]
fn assert_value_and_type(name: &str, result: &ExecTestResults, expected: f64, expected_ty: NumericType) {
let mem = result.exec_state.stack();
match mem
.memory
.get_from(name, result.mem_env, SourceRange::default(), 0)
.unwrap()
{
KclValue::Number { value, ty, .. } => {
assert_eq!(value.round(), expected);
assert_eq!(*ty, expected_ty);
}
_ => unreachable!(),
}
}
#[tokio::test(flavor = "multi_thread")]
async fn combine_numeric() {
let program = r#"a = 5 + 4
b = 5 - 2
c = 5mm - 2mm + 10mm
d = 5mm - 2 + 10
e = 5 - 2mm + 10
f = 30mm - 1inch
g = 2 * 10
h = 2 * 10mm
i = 2mm * 10mm
j = 2_ * 10
k = 2_ * 3mm * 3mm
l = 1 / 10
m = 2mm / 1mm
n = 10inch / 2mm
o = 3mm / 3
p = 3_ / 4
q = 4inch / 2_
r = min([0, 3, 42])
s = min([0, 3mm, -42])
t = min([100, 3in, 142mm])
u = min([3rad, 4in])
"#;
let result = parse_execute(program).await.unwrap();
assert_eq!(
result.exec_state.issues().len(),
5,
"errors: {:?}",
result.exec_state.issues()
);
assert_value_and_type("a", &result, 9.0, NumericType::default());
assert_value_and_type("b", &result, 3.0, NumericType::default());
assert_value_and_type("c", &result, 13.0, NumericType::mm());
assert_value_and_type("d", &result, 13.0, NumericType::mm());
assert_value_and_type("e", &result, 13.0, NumericType::mm());
assert_value_and_type("f", &result, 5.0, NumericType::mm());
assert_value_and_type("g", &result, 20.0, NumericType::default());
assert_value_and_type("h", &result, 20.0, NumericType::mm());
assert_value_and_type("i", &result, 20.0, NumericType::Unknown);
assert_value_and_type("j", &result, 20.0, NumericType::default());
assert_value_and_type("k", &result, 18.0, NumericType::Unknown);
assert_value_and_type("l", &result, 0.0, NumericType::default());
assert_value_and_type("m", &result, 2.0, NumericType::count());
assert_value_and_type("n", &result, 5.0, NumericType::Unknown);
assert_value_and_type("o", &result, 1.0, NumericType::mm());
assert_value_and_type("p", &result, 1.0, NumericType::count());
assert_value_and_type(
"q",
&result,
2.0,
NumericType::Known(UnitType::Length(UnitLength::Inches)),
);
assert_value_and_type("r", &result, 0.0, NumericType::default());
assert_value_and_type("s", &result, -42.0, NumericType::mm());
assert_value_and_type("t", &result, 3.0, NumericType::Unknown);
assert_value_and_type("u", &result, 3.0, NumericType::Unknown);
}
#[tokio::test(flavor = "multi_thread")]
async fn bad_typed_arithmetic() {
let program = r#"
a = 1rad
b = 180 / PI * a + 360
"#;
let result = parse_execute(program).await.unwrap();
assert_value_and_type("a", &result, 1.0, NumericType::radians());
assert_value_and_type("b", &result, 417.0, NumericType::Unknown);
}
#[tokio::test(flavor = "multi_thread")]
async fn cos_coercions() {
let program = r#"
a = cos(units::toRadians(30deg))
b = 3 / a
c = cos(30deg)
d = cos(1rad)
"#;
let result = parse_execute(program).await.unwrap();
assert!(
result.exec_state.issues().is_empty(),
"{:?}",
result.exec_state.issues()
);
assert_value_and_type("a", &result, 1.0, NumericType::default());
assert_value_and_type("b", &result, 3.0, NumericType::default());
assert_value_and_type("c", &result, 1.0, NumericType::default());
assert_value_and_type("d", &result, 1.0, NumericType::default());
}
#[tokio::test(flavor = "multi_thread")]
async fn coerce_nested_array() {
let (ctx, mut exec_state) = new_exec_state().await;
let mixed1 = 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::HomArray {
value: vec![
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())),
},
],
ty: RuntimeType::any(),
};
let tym1 = RuntimeType::Array(
Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
ArrayLen::Minimum(1),
);
let result = 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())),
};
assert_coerce_results(&mixed1, &tym1, &result, &mut exec_state);
ctx.close().await;
}
}