Skip to main content

kcl_lib/execution/
types.rs

1use std::{collections::HashMap, str::FromStr};
2
3use anyhow::Result;
4use kittycad_modeling_cmds::units::{UnitAngle, UnitLength};
5use serde::{Deserialize, Serialize};
6
7use crate::{
8    CompilationError, KclError, SourceRange,
9    errors::KclErrorDetails,
10    exec::PlaneKind,
11    execution::{
12        ExecState, Plane, PlaneInfo, Point3d, annotations,
13        kcl_value::{KclValue, TypeDef},
14        memory::{self},
15    },
16    fmt,
17    parsing::{
18        ast::types::{PrimitiveType as AstPrimitiveType, Type},
19        token::NumericSuffix,
20    },
21    std::args::{FromKclValue, TyF64},
22};
23
24#[derive(Debug, Clone, PartialEq)]
25pub enum RuntimeType {
26    Primitive(PrimitiveType),
27    Array(Box<RuntimeType>, ArrayLen),
28    Union(Vec<RuntimeType>),
29    Tuple(Vec<RuntimeType>),
30    Object(Vec<(String, RuntimeType)>, bool),
31}
32
33impl RuntimeType {
34    pub fn any() -> Self {
35        RuntimeType::Primitive(PrimitiveType::Any)
36    }
37
38    pub fn any_array() -> Self {
39        RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::None)
40    }
41
42    pub fn edge() -> Self {
43        RuntimeType::Primitive(PrimitiveType::Edge)
44    }
45
46    pub fn function() -> Self {
47        RuntimeType::Primitive(PrimitiveType::Function)
48    }
49
50    pub fn segment() -> Self {
51        RuntimeType::Primitive(PrimitiveType::Segment)
52    }
53
54    /// `[Segment; 1+]`
55    pub fn segments() -> Self {
56        RuntimeType::Array(Box::new(Self::segment()), ArrayLen::Minimum(1))
57    }
58
59    pub fn sketch() -> Self {
60        RuntimeType::Primitive(PrimitiveType::Sketch)
61    }
62
63    pub fn sketch_or_surface() -> Self {
64        RuntimeType::Union(vec![Self::sketch(), Self::plane(), Self::face()])
65    }
66
67    /// `[Sketch; 1+]`
68    pub fn sketches() -> Self {
69        RuntimeType::Array(
70            Box::new(RuntimeType::Primitive(PrimitiveType::Sketch)),
71            ArrayLen::Minimum(1),
72        )
73    }
74
75    /// `[Face; 1+]`
76    pub fn faces() -> Self {
77        RuntimeType::Array(
78            Box::new(RuntimeType::Primitive(PrimitiveType::Face)),
79            ArrayLen::Minimum(1),
80        )
81    }
82
83    /// `[TaggedFace; 1+]`
84    pub fn tagged_faces() -> Self {
85        RuntimeType::Array(
86            Box::new(RuntimeType::Primitive(PrimitiveType::TaggedFace)),
87            ArrayLen::Minimum(1),
88        )
89    }
90
91    /// `[Solid; 1+]`
92    pub fn solids() -> Self {
93        RuntimeType::Array(
94            Box::new(RuntimeType::Primitive(PrimitiveType::Solid)),
95            ArrayLen::Minimum(1),
96        )
97    }
98
99    pub fn solid() -> Self {
100        RuntimeType::Primitive(PrimitiveType::Solid)
101    }
102
103    /// `[Helix; 1+]`
104    pub fn helices() -> Self {
105        RuntimeType::Array(
106            Box::new(RuntimeType::Primitive(PrimitiveType::Helix)),
107            ArrayLen::Minimum(1),
108        )
109    }
110    pub fn helix() -> Self {
111        RuntimeType::Primitive(PrimitiveType::Helix)
112    }
113
114    pub fn plane() -> Self {
115        RuntimeType::Primitive(PrimitiveType::Plane)
116    }
117
118    pub fn face() -> Self {
119        RuntimeType::Primitive(PrimitiveType::Face)
120    }
121
122    pub fn tag_decl() -> Self {
123        RuntimeType::Primitive(PrimitiveType::TagDecl)
124    }
125
126    pub fn tagged_face() -> Self {
127        RuntimeType::Primitive(PrimitiveType::TaggedFace)
128    }
129
130    pub fn tagged_edge() -> Self {
131        RuntimeType::Primitive(PrimitiveType::TaggedEdge)
132    }
133
134    pub fn bool() -> Self {
135        RuntimeType::Primitive(PrimitiveType::Boolean)
136    }
137
138    pub fn string() -> Self {
139        RuntimeType::Primitive(PrimitiveType::String)
140    }
141
142    pub fn imported() -> Self {
143        RuntimeType::Primitive(PrimitiveType::ImportedGeometry)
144    }
145
146    /// `[number; 2]`
147    pub fn point2d() -> Self {
148        RuntimeType::Array(Box::new(RuntimeType::length()), ArrayLen::Known(2))
149    }
150
151    /// `[number; 3]`
152    pub fn point3d() -> Self {
153        RuntimeType::Array(Box::new(RuntimeType::length()), ArrayLen::Known(3))
154    }
155
156    pub fn length() -> Self {
157        RuntimeType::Primitive(PrimitiveType::Number(NumericType::Known(UnitType::GenericLength)))
158    }
159
160    pub fn known_length(len: UnitLength) -> Self {
161        RuntimeType::Primitive(PrimitiveType::Number(NumericType::Known(UnitType::Length(len))))
162    }
163
164    pub fn angle() -> Self {
165        RuntimeType::Primitive(PrimitiveType::Number(NumericType::Known(UnitType::GenericAngle)))
166    }
167
168    pub fn radians() -> Self {
169        RuntimeType::Primitive(PrimitiveType::Number(NumericType::Known(UnitType::Angle(
170            UnitAngle::Radians,
171        ))))
172    }
173
174    pub fn degrees() -> Self {
175        RuntimeType::Primitive(PrimitiveType::Number(NumericType::Known(UnitType::Angle(
176            UnitAngle::Degrees,
177        ))))
178    }
179
180    pub fn count() -> Self {
181        RuntimeType::Primitive(PrimitiveType::Number(NumericType::Known(UnitType::Count)))
182    }
183
184    pub fn num_any() -> Self {
185        RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any))
186    }
187
188    pub fn from_parsed(
189        value: Type,
190        exec_state: &mut ExecState,
191        source_range: SourceRange,
192        constrainable: bool,
193    ) -> Result<Self, CompilationError> {
194        match value {
195            Type::Primitive(pt) => Self::from_parsed_primitive(pt, exec_state, source_range),
196            Type::Array { ty, len } => Self::from_parsed(*ty, exec_state, source_range, constrainable)
197                .map(|t| RuntimeType::Array(Box::new(t), len)),
198            Type::Union { tys } => tys
199                .into_iter()
200                .map(|t| Self::from_parsed(t.inner, exec_state, source_range, constrainable))
201                .collect::<Result<Vec<_>, CompilationError>>()
202                .map(RuntimeType::Union),
203            Type::Object { properties } => properties
204                .into_iter()
205                .map(|(id, ty)| {
206                    RuntimeType::from_parsed(ty.inner, exec_state, source_range, constrainable)
207                        .map(|ty| (id.name.clone(), ty))
208                })
209                .collect::<Result<Vec<_>, CompilationError>>()
210                .map(|values| RuntimeType::Object(values, constrainable)),
211        }
212    }
213
214    fn from_parsed_primitive(
215        value: AstPrimitiveType,
216        exec_state: &mut ExecState,
217        source_range: SourceRange,
218    ) -> Result<Self, CompilationError> {
219        Ok(match value {
220            AstPrimitiveType::Any => RuntimeType::Primitive(PrimitiveType::Any),
221            AstPrimitiveType::None => RuntimeType::Primitive(PrimitiveType::None),
222            AstPrimitiveType::String => RuntimeType::Primitive(PrimitiveType::String),
223            AstPrimitiveType::Boolean => RuntimeType::Primitive(PrimitiveType::Boolean),
224            AstPrimitiveType::Number(suffix) => {
225                let ty = match suffix {
226                    NumericSuffix::None => NumericType::Any,
227                    _ => NumericType::from_parsed(suffix, &exec_state.mod_local.settings),
228                };
229                RuntimeType::Primitive(PrimitiveType::Number(ty))
230            }
231            AstPrimitiveType::Named { id } => Self::from_alias(&id.name, exec_state, source_range)?,
232            AstPrimitiveType::TagDecl => RuntimeType::Primitive(PrimitiveType::TagDecl),
233            AstPrimitiveType::ImportedGeometry => RuntimeType::Primitive(PrimitiveType::ImportedGeometry),
234            AstPrimitiveType::Function(_) => RuntimeType::Primitive(PrimitiveType::Function),
235        })
236    }
237
238    pub fn from_alias(
239        alias: &str,
240        exec_state: &mut ExecState,
241        source_range: SourceRange,
242    ) -> Result<Self, CompilationError> {
243        let ty_val = exec_state
244            .stack()
245            .get(&format!("{}{}", memory::TYPE_PREFIX, alias), source_range)
246            .map_err(|_| CompilationError::err(source_range, format!("Unknown type: {alias}")))?;
247
248        Ok(match ty_val {
249            KclValue::Type {
250                value, experimental, ..
251            } => {
252                let result = match value {
253                    TypeDef::RustRepr(ty, _) => RuntimeType::Primitive(ty.clone()),
254                    TypeDef::Alias(ty) => ty.clone(),
255                };
256                if *experimental {
257                    exec_state.warn_experimental(&format!("the type `{alias}`"), source_range);
258                }
259                result
260            }
261            _ => unreachable!(),
262        })
263    }
264
265    pub fn human_friendly_type(&self) -> String {
266        match self {
267            RuntimeType::Primitive(ty) => ty.to_string(),
268            RuntimeType::Array(ty, ArrayLen::None | ArrayLen::Minimum(0)) => {
269                format!("an array of {}", ty.display_multiple())
270            }
271            RuntimeType::Array(ty, ArrayLen::Minimum(1)) => format!("one or more {}", ty.display_multiple()),
272            RuntimeType::Array(ty, ArrayLen::Minimum(n)) => {
273                format!("an array of {n} or more {}", ty.display_multiple())
274            }
275            RuntimeType::Array(ty, ArrayLen::Known(n)) => format!("an array of {n} {}", ty.display_multiple()),
276            RuntimeType::Union(tys) => tys
277                .iter()
278                .map(Self::human_friendly_type)
279                .collect::<Vec<_>>()
280                .join(" or "),
281            RuntimeType::Tuple(tys) => format!(
282                "a tuple with values of types ({})",
283                tys.iter().map(Self::human_friendly_type).collect::<Vec<_>>().join(", ")
284            ),
285            RuntimeType::Object(..) => format!("an object with fields {self}"),
286        }
287    }
288
289    // Subtype with no coercion, including refining numeric types.
290    pub(crate) fn subtype(&self, sup: &RuntimeType) -> bool {
291        use RuntimeType::*;
292
293        match (self, sup) {
294            (_, Primitive(PrimitiveType::Any)) => true,
295            (Primitive(t1), Primitive(t2)) => t1.subtype(t2),
296            (Array(t1, l1), Array(t2, l2)) => t1.subtype(t2) && l1.subtype(*l2),
297            (Tuple(t1), Tuple(t2)) => t1.len() == t2.len() && t1.iter().zip(t2).all(|(t1, t2)| t1.subtype(t2)),
298
299            (Union(ts1), Union(ts2)) => ts1.iter().all(|t| ts2.contains(t)),
300            (t1, Union(ts2)) => ts2.iter().any(|t| t1.subtype(t)),
301
302            (Object(t1, _), Object(t2, _)) => t2
303                .iter()
304                .all(|(f, t)| t1.iter().any(|(ff, tt)| f == ff && tt.subtype(t))),
305
306            // Equivalence between singleton types and single-item arrays/tuples of the same type (plus transitivity with the array subtyping).
307            (t1, RuntimeType::Array(t2, l)) if t1.subtype(t2) && ArrayLen::Known(1).subtype(*l) => true,
308            (RuntimeType::Array(t1, ArrayLen::Known(1)), t2) if t1.subtype(t2) => true,
309            (t1, RuntimeType::Tuple(t2)) if !t2.is_empty() && t1.subtype(&t2[0]) => true,
310            (RuntimeType::Tuple(t1), t2) if t1.len() == 1 && t1[0].subtype(t2) => true,
311
312            // Equivalence between Axis types and their object representation.
313            (Object(t1, _), Primitive(PrimitiveType::Axis2d)) => {
314                t1.iter()
315                    .any(|(n, t)| n == "origin" && t.subtype(&RuntimeType::point2d()))
316                    && t1
317                        .iter()
318                        .any(|(n, t)| n == "direction" && t.subtype(&RuntimeType::point2d()))
319            }
320            (Object(t1, _), Primitive(PrimitiveType::Axis3d)) => {
321                t1.iter()
322                    .any(|(n, t)| n == "origin" && t.subtype(&RuntimeType::point3d()))
323                    && t1
324                        .iter()
325                        .any(|(n, t)| n == "direction" && t.subtype(&RuntimeType::point3d()))
326            }
327            (Primitive(PrimitiveType::Axis2d), Object(t2, _)) => {
328                t2.iter()
329                    .any(|(n, t)| n == "origin" && t.subtype(&RuntimeType::point2d()))
330                    && t2
331                        .iter()
332                        .any(|(n, t)| n == "direction" && t.subtype(&RuntimeType::point2d()))
333            }
334            (Primitive(PrimitiveType::Axis3d), Object(t2, _)) => {
335                t2.iter()
336                    .any(|(n, t)| n == "origin" && t.subtype(&RuntimeType::point3d()))
337                    && t2
338                        .iter()
339                        .any(|(n, t)| n == "direction" && t.subtype(&RuntimeType::point3d()))
340            }
341            _ => false,
342        }
343    }
344
345    fn display_multiple(&self) -> String {
346        match self {
347            RuntimeType::Primitive(ty) => ty.display_multiple(),
348            RuntimeType::Array(..) => "arrays".to_owned(),
349            RuntimeType::Union(tys) => tys
350                .iter()
351                .map(|t| t.display_multiple())
352                .collect::<Vec<_>>()
353                .join(" or "),
354            RuntimeType::Tuple(_) => "tuples".to_owned(),
355            RuntimeType::Object(..) => format!("objects with fields {self}"),
356        }
357    }
358}
359
360impl std::fmt::Display for RuntimeType {
361    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
362        match self {
363            RuntimeType::Primitive(t) => t.fmt(f),
364            RuntimeType::Array(t, l) => match l {
365                ArrayLen::None => write!(f, "[{t}]"),
366                ArrayLen::Minimum(n) => write!(f, "[{t}; {n}+]"),
367                ArrayLen::Known(n) => write!(f, "[{t}; {n}]"),
368            },
369            RuntimeType::Tuple(ts) => write!(
370                f,
371                "({})",
372                ts.iter().map(|t| t.to_string()).collect::<Vec<_>>().join(", ")
373            ),
374            RuntimeType::Union(ts) => write!(
375                f,
376                "{}",
377                ts.iter().map(|t| t.to_string()).collect::<Vec<_>>().join(" | ")
378            ),
379            RuntimeType::Object(items, _) => write!(
380                f,
381                "{{ {} }}",
382                items
383                    .iter()
384                    .map(|(n, t)| format!("{n}: {t}"))
385                    .collect::<Vec<_>>()
386                    .join(", ")
387            ),
388        }
389    }
390}
391
392#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, ts_rs::TS)]
393pub enum ArrayLen {
394    None,
395    Minimum(usize),
396    Known(usize),
397}
398
399impl ArrayLen {
400    pub fn subtype(self, other: ArrayLen) -> bool {
401        match (self, other) {
402            (_, ArrayLen::None) => true,
403            (ArrayLen::Minimum(s1), ArrayLen::Minimum(s2)) if s1 >= s2 => true,
404            (ArrayLen::Known(s1), ArrayLen::Minimum(s2)) if s1 >= s2 => true,
405            (ArrayLen::None, ArrayLen::Minimum(0)) => true,
406            (ArrayLen::Known(s1), ArrayLen::Known(s2)) if s1 == s2 => true,
407            _ => false,
408        }
409    }
410
411    /// True if the length constraint is satisfied by the supplied length.
412    pub fn satisfied(self, len: usize, allow_shrink: bool) -> Option<usize> {
413        match self {
414            ArrayLen::None => Some(len),
415            ArrayLen::Minimum(s) => (len >= s).then_some(len),
416            ArrayLen::Known(s) => (if allow_shrink { len >= s } else { len == s }).then_some(s),
417        }
418    }
419
420    pub fn human_friendly_type(self) -> String {
421        match self {
422            ArrayLen::None | ArrayLen::Minimum(0) => "any number of elements".to_owned(),
423            ArrayLen::Minimum(1) => "at least 1 element".to_owned(),
424            ArrayLen::Minimum(n) => format!("at least {n} elements"),
425            ArrayLen::Known(0) => "no elements".to_owned(),
426            ArrayLen::Known(1) => "exactly 1 element".to_owned(),
427            ArrayLen::Known(n) => format!("exactly {n} elements"),
428        }
429    }
430}
431
432#[derive(Debug, Clone, PartialEq)]
433pub enum PrimitiveType {
434    Any,
435    None,
436    Number(NumericType),
437    String,
438    Boolean,
439    TaggedEdge,
440    TaggedFace,
441    TagDecl,
442    GdtAnnotation,
443    Segment,
444    Sketch,
445    Constraint,
446    Solid,
447    Plane,
448    Helix,
449    Face,
450    Edge,
451    Axis2d,
452    Axis3d,
453    ImportedGeometry,
454    Function,
455}
456
457impl PrimitiveType {
458    fn display_multiple(&self) -> String {
459        match self {
460            PrimitiveType::Any => "any values".to_owned(),
461            PrimitiveType::None => "none values".to_owned(),
462            PrimitiveType::Number(NumericType::Known(unit)) => format!("numbers({unit})"),
463            PrimitiveType::Number(_) => "numbers".to_owned(),
464            PrimitiveType::String => "strings".to_owned(),
465            PrimitiveType::Boolean => "bools".to_owned(),
466            PrimitiveType::GdtAnnotation => "GD&T Annotations".to_owned(),
467            PrimitiveType::Segment => "Segments".to_owned(),
468            PrimitiveType::Sketch => "Sketches".to_owned(),
469            PrimitiveType::Constraint => "Constraints".to_owned(),
470            PrimitiveType::Solid => "Solids".to_owned(),
471            PrimitiveType::Plane => "Planes".to_owned(),
472            PrimitiveType::Helix => "Helices".to_owned(),
473            PrimitiveType::Face => "Faces".to_owned(),
474            PrimitiveType::Edge => "Edges".to_owned(),
475            PrimitiveType::Axis2d => "2d axes".to_owned(),
476            PrimitiveType::Axis3d => "3d axes".to_owned(),
477            PrimitiveType::ImportedGeometry => "imported geometries".to_owned(),
478            PrimitiveType::Function => "functions".to_owned(),
479            PrimitiveType::TagDecl => "tag declarators".to_owned(),
480            PrimitiveType::TaggedEdge => "tagged edges".to_owned(),
481            PrimitiveType::TaggedFace => "tagged faces".to_owned(),
482        }
483    }
484
485    fn subtype(&self, other: &PrimitiveType) -> bool {
486        match (self, other) {
487            (_, PrimitiveType::Any) => true,
488            (PrimitiveType::Number(n1), PrimitiveType::Number(n2)) => n1.subtype(n2),
489            (PrimitiveType::TaggedEdge, PrimitiveType::TaggedFace)
490            | (PrimitiveType::TaggedEdge, PrimitiveType::Edge) => true,
491            (t1, t2) => t1 == t2,
492        }
493    }
494}
495
496impl std::fmt::Display for PrimitiveType {
497    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
498        match self {
499            PrimitiveType::Any => write!(f, "any"),
500            PrimitiveType::None => write!(f, "none"),
501            PrimitiveType::Number(NumericType::Known(unit)) => write!(f, "number({unit})"),
502            PrimitiveType::Number(NumericType::Unknown) => write!(f, "number(unknown units)"),
503            PrimitiveType::Number(NumericType::Default { .. }) => write!(f, "number"),
504            PrimitiveType::Number(NumericType::Any) => write!(f, "number(any units)"),
505            PrimitiveType::String => write!(f, "string"),
506            PrimitiveType::Boolean => write!(f, "bool"),
507            PrimitiveType::TagDecl => write!(f, "tag declarator"),
508            PrimitiveType::TaggedEdge => write!(f, "tagged edge"),
509            PrimitiveType::TaggedFace => write!(f, "tagged face"),
510            PrimitiveType::GdtAnnotation => write!(f, "GD&T Annotation"),
511            PrimitiveType::Segment => write!(f, "Segment"),
512            PrimitiveType::Sketch => write!(f, "Sketch"),
513            PrimitiveType::Constraint => write!(f, "Constraint"),
514            PrimitiveType::Solid => write!(f, "Solid"),
515            PrimitiveType::Plane => write!(f, "Plane"),
516            PrimitiveType::Face => write!(f, "Face"),
517            PrimitiveType::Edge => write!(f, "Edge"),
518            PrimitiveType::Axis2d => write!(f, "Axis2d"),
519            PrimitiveType::Axis3d => write!(f, "Axis3d"),
520            PrimitiveType::Helix => write!(f, "Helix"),
521            PrimitiveType::ImportedGeometry => write!(f, "ImportedGeometry"),
522            PrimitiveType::Function => write!(f, "fn"),
523        }
524    }
525}
526
527#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, ts_rs::TS)]
528#[ts(export)]
529#[serde(tag = "type")]
530pub enum NumericType {
531    // Specified by the user (directly or indirectly)
532    Known(UnitType),
533    // Unspecified, using defaults
534    Default { len: UnitLength, angle: UnitAngle },
535    // Exceeded the ability of the type system to track.
536    Unknown,
537    // Type info has been explicitly cast away.
538    Any,
539}
540
541impl Default for NumericType {
542    fn default() -> Self {
543        NumericType::Default {
544            len: UnitLength::Millimeters,
545            angle: UnitAngle::Degrees,
546        }
547    }
548}
549
550impl NumericType {
551    pub const fn count() -> Self {
552        NumericType::Known(UnitType::Count)
553    }
554
555    pub const fn mm() -> Self {
556        NumericType::Known(UnitType::Length(UnitLength::Millimeters))
557    }
558
559    pub const fn radians() -> Self {
560        NumericType::Known(UnitType::Angle(UnitAngle::Radians))
561    }
562
563    pub const fn degrees() -> Self {
564        NumericType::Known(UnitType::Angle(UnitAngle::Degrees))
565    }
566
567    /// Combine two types when we expect them to be equal, erring on the side of less coercion. To be
568    /// precise, only adjusting one number or the other when they are of known types.
569    ///
570    /// This combinator function is suitable for comparisons where uncertainty should
571    /// be handled by the user.
572    pub fn combine_eq(
573        a: TyF64,
574        b: TyF64,
575        exec_state: &mut ExecState,
576        source_range: SourceRange,
577    ) -> (f64, f64, NumericType) {
578        use NumericType::*;
579        match (a.ty, b.ty) {
580            (at, bt) if at == bt => (a.n, b.n, at),
581            (at, Any) => (a.n, b.n, at),
582            (Any, bt) => (a.n, b.n, bt),
583
584            (t @ Known(UnitType::Length(l1)), Known(UnitType::Length(l2))) => (a.n, adjust_length(l2, b.n, l1).0, t),
585            (t @ Known(UnitType::Angle(a1)), Known(UnitType::Angle(a2))) => (a.n, adjust_angle(a2, b.n, a1).0, t),
586
587            (t @ Known(UnitType::Length(_)), Known(UnitType::GenericLength)) => (a.n, b.n, t),
588            (Known(UnitType::GenericLength), t @ Known(UnitType::Length(_))) => (a.n, b.n, t),
589            (t @ Known(UnitType::Angle(_)), Known(UnitType::GenericAngle)) => (a.n, b.n, t),
590            (Known(UnitType::GenericAngle), t @ Known(UnitType::Angle(_))) => (a.n, b.n, t),
591
592            (Known(UnitType::Count), Default { .. }) | (Default { .. }, Known(UnitType::Count)) => {
593                (a.n, b.n, Known(UnitType::Count))
594            }
595            (t @ Known(UnitType::Length(l1)), Default { len: l2, .. }) if l1 == l2 => (a.n, b.n, t),
596            (Default { len: l1, .. }, t @ Known(UnitType::Length(l2))) if l1 == l2 => (a.n, b.n, t),
597            (t @ Known(UnitType::Angle(a1)), Default { angle: a2, .. }) if a1 == a2 => {
598                if b.n != 0.0 {
599                    exec_state.warn(
600                        CompilationError::err(source_range, "Prefer to use explicit units for angles"),
601                        annotations::WARN_ANGLE_UNITS,
602                    );
603                }
604                (a.n, b.n, t)
605            }
606            (Default { angle: a1, .. }, t @ Known(UnitType::Angle(a2))) if a1 == a2 => {
607                if a.n != 0.0 {
608                    exec_state.warn(
609                        CompilationError::err(source_range, "Prefer to use explicit units for angles"),
610                        annotations::WARN_ANGLE_UNITS,
611                    );
612                }
613                (a.n, b.n, t)
614            }
615
616            _ => (a.n, b.n, Unknown),
617        }
618    }
619
620    /// Combine two types when we expect them to be equal, erring on the side of more coercion. Including adjusting when
621    /// we are certain about only one type.
622    ///
623    /// This combinator function is suitable for situations where the user would almost certainly want the types to be
624    /// coerced together, for example two arguments to the same function or two numbers in an array being used as a point.
625    ///
626    /// Prefer to use `combine_eq` if possible since using that prioritises correctness over ergonomics.
627    pub fn combine_eq_coerce(
628        a: TyF64,
629        b: TyF64,
630        for_errs: Option<(&mut ExecState, SourceRange)>,
631    ) -> (f64, f64, NumericType) {
632        use NumericType::*;
633        match (a.ty, b.ty) {
634            (at, bt) if at == bt => (a.n, b.n, at),
635            (at, Any) => (a.n, b.n, at),
636            (Any, bt) => (a.n, b.n, bt),
637
638            // Known types and compatible, but needs adjustment.
639            (t @ Known(UnitType::Length(l1)), Known(UnitType::Length(l2))) => (a.n, adjust_length(l2, b.n, l1).0, t),
640            (t @ Known(UnitType::Angle(a1)), Known(UnitType::Angle(a2))) => (a.n, adjust_angle(a2, b.n, a1).0, t),
641
642            (t @ Known(UnitType::Length(_)), Known(UnitType::GenericLength)) => (a.n, b.n, t),
643            (Known(UnitType::GenericLength), t @ Known(UnitType::Length(_))) => (a.n, b.n, t),
644            (t @ Known(UnitType::Angle(_)), Known(UnitType::GenericAngle)) => (a.n, b.n, t),
645            (Known(UnitType::GenericAngle), t @ Known(UnitType::Angle(_))) => (a.n, b.n, t),
646
647            // Known and unknown => we assume the known one, possibly with adjustment
648            (Known(UnitType::Count), Default { .. }) | (Default { .. }, Known(UnitType::Count)) => {
649                (a.n, b.n, Known(UnitType::Count))
650            }
651
652            (t @ Known(UnitType::Length(l1)), Default { len: l2, .. }) => (a.n, adjust_length(l2, b.n, l1).0, t),
653            (Default { len: l1, .. }, t @ Known(UnitType::Length(l2))) => (adjust_length(l1, a.n, l2).0, b.n, t),
654            (t @ Known(UnitType::Angle(a1)), Default { angle: a2, .. }) => {
655                if let Some((exec_state, source_range)) = for_errs
656                    && b.n != 0.0
657                {
658                    exec_state.warn(
659                        CompilationError::err(source_range, "Prefer to use explicit units for angles"),
660                        annotations::WARN_ANGLE_UNITS,
661                    );
662                }
663                (a.n, adjust_angle(a2, b.n, a1).0, t)
664            }
665            (Default { angle: a1, .. }, t @ Known(UnitType::Angle(a2))) => {
666                if let Some((exec_state, source_range)) = for_errs
667                    && a.n != 0.0
668                {
669                    exec_state.warn(
670                        CompilationError::err(source_range, "Prefer to use explicit units for angles"),
671                        annotations::WARN_ANGLE_UNITS,
672                    );
673                }
674                (adjust_angle(a1, a.n, a2).0, b.n, t)
675            }
676
677            (Default { len: l1, .. }, Known(UnitType::GenericLength)) => (a.n, b.n, l1.into()),
678            (Known(UnitType::GenericLength), Default { len: l2, .. }) => (a.n, b.n, l2.into()),
679            (Default { angle: a1, .. }, Known(UnitType::GenericAngle)) => {
680                if let Some((exec_state, source_range)) = for_errs
681                    && b.n != 0.0
682                {
683                    exec_state.warn(
684                        CompilationError::err(source_range, "Prefer to use explicit units for angles"),
685                        annotations::WARN_ANGLE_UNITS,
686                    );
687                }
688                (a.n, b.n, a1.into())
689            }
690            (Known(UnitType::GenericAngle), Default { angle: a2, .. }) => {
691                if let Some((exec_state, source_range)) = for_errs
692                    && a.n != 0.0
693                {
694                    exec_state.warn(
695                        CompilationError::err(source_range, "Prefer to use explicit units for angles"),
696                        annotations::WARN_ANGLE_UNITS,
697                    );
698                }
699                (a.n, b.n, a2.into())
700            }
701
702            (Known(_), Known(_)) | (Default { .. }, Default { .. }) | (_, Unknown) | (Unknown, _) => {
703                (a.n, b.n, Unknown)
704            }
705        }
706    }
707
708    pub fn combine_eq_array(input: &[TyF64]) -> (Vec<f64>, NumericType) {
709        use NumericType::*;
710        let result = input.iter().map(|t| t.n).collect();
711
712        let mut ty = Any;
713        for i in input {
714            if i.ty == Any || ty == i.ty {
715                continue;
716            }
717
718            // The cases where we check the values for 0.0 are so we don't crash out where a conversion would always be safe
719            match (&ty, &i.ty) {
720                (Any, Default { .. }) if i.n == 0.0 => {}
721                (Any, t) => {
722                    ty = *t;
723                }
724                (_, Unknown) | (Default { .. }, Default { .. }) => return (result, Unknown),
725
726                (Known(UnitType::Count), Default { .. }) | (Default { .. }, Known(UnitType::Count)) => {
727                    ty = Known(UnitType::Count);
728                }
729
730                (Known(UnitType::Length(l1)), Default { len: l2, .. }) if l1 == l2 || i.n == 0.0 => {}
731                (Known(UnitType::Angle(a1)), Default { angle: a2, .. }) if a1 == a2 || i.n == 0.0 => {}
732
733                (Default { len: l1, .. }, Known(UnitType::Length(l2))) if l1 == l2 => {
734                    ty = Known(UnitType::Length(*l2));
735                }
736                (Default { angle: a1, .. }, Known(UnitType::Angle(a2))) if a1 == a2 => {
737                    ty = Known(UnitType::Angle(*a2));
738                }
739
740                _ => return (result, Unknown),
741            }
742        }
743
744        if ty == Any && !input.is_empty() {
745            ty = input[0].ty;
746        }
747
748        (result, ty)
749    }
750
751    /// Combine two types for multiplication-like operations.
752    pub fn combine_mul(a: TyF64, b: TyF64) -> (f64, f64, NumericType) {
753        use NumericType::*;
754        match (a.ty, b.ty) {
755            (at @ Default { .. }, bt @ Default { .. }) if at == bt => (a.n, b.n, at),
756            (Default { .. }, Default { .. }) => (a.n, b.n, Unknown),
757            (Known(UnitType::Count), bt) => (a.n, b.n, bt),
758            (at, Known(UnitType::Count)) => (a.n, b.n, at),
759            (at @ Known(_), Default { .. }) | (Default { .. }, at @ Known(_)) => (a.n, b.n, at),
760            (Any, Any) => (a.n, b.n, Any),
761            _ => (a.n, b.n, Unknown),
762        }
763    }
764
765    /// Combine two types for division-like operations.
766    pub fn combine_div(a: TyF64, b: TyF64) -> (f64, f64, NumericType) {
767        use NumericType::*;
768        match (a.ty, b.ty) {
769            (at @ Default { .. }, bt @ Default { .. }) if at == bt => (a.n, b.n, at),
770            (at, bt) if at == bt => (a.n, b.n, Known(UnitType::Count)),
771            (Default { .. }, Default { .. }) => (a.n, b.n, Unknown),
772            (at, Known(UnitType::Count) | Any) => (a.n, b.n, at),
773            (at @ Known(_), Default { .. }) => (a.n, b.n, at),
774            (Known(UnitType::Count), _) => (a.n, b.n, Known(UnitType::Count)),
775            _ => (a.n, b.n, Unknown),
776        }
777    }
778
779    /// Combine two types for modulo-like operations.
780    pub fn combine_mod(a: TyF64, b: TyF64) -> (f64, f64, NumericType) {
781        use NumericType::*;
782        match (a.ty, b.ty) {
783            (at @ Default { .. }, bt @ Default { .. }) if at == bt => (a.n, b.n, at),
784            (at, bt) if at == bt => (a.n, b.n, at),
785            (Default { .. }, Default { .. }) => (a.n, b.n, Unknown),
786            (at, Known(UnitType::Count) | Any) => (a.n, b.n, at),
787            (at @ Known(_), Default { .. }) => (a.n, b.n, at),
788            (Known(UnitType::Count), _) => (a.n, b.n, Known(UnitType::Count)),
789            _ => (a.n, b.n, Unknown),
790        }
791    }
792
793    /// Combine two types for range operations.
794    ///
795    /// This combinator function is suitable for ranges where uncertainty should
796    /// be handled by the user, and it doesn't make sense to convert units. So
797    /// this is one of th most conservative ways to combine types.
798    pub fn combine_range(
799        a: TyF64,
800        b: TyF64,
801        exec_state: &mut ExecState,
802        source_range: SourceRange,
803    ) -> Result<(f64, f64, NumericType), KclError> {
804        use NumericType::*;
805        match (a.ty, b.ty) {
806            (at, bt) if at == bt => Ok((a.n, b.n, at)),
807            (at, Any) => Ok((a.n, b.n, at)),
808            (Any, bt) => Ok((a.n, b.n, bt)),
809
810            (Known(UnitType::Length(l1)), Known(UnitType::Length(l2))) => {
811                Err(KclError::new_semantic(KclErrorDetails::new(
812                    format!("Range start and range end have incompatible units: {l1} and {l2}"),
813                    vec![source_range],
814                )))
815            }
816            (Known(UnitType::Angle(a1)), Known(UnitType::Angle(a2))) => {
817                Err(KclError::new_semantic(KclErrorDetails::new(
818                    format!("Range start and range end have incompatible units: {a1} and {a2}"),
819                    vec![source_range],
820                )))
821            }
822
823            (t @ Known(UnitType::Length(_)), Known(UnitType::GenericLength)) => Ok((a.n, b.n, t)),
824            (Known(UnitType::GenericLength), t @ Known(UnitType::Length(_))) => Ok((a.n, b.n, t)),
825            (t @ Known(UnitType::Angle(_)), Known(UnitType::GenericAngle)) => Ok((a.n, b.n, t)),
826            (Known(UnitType::GenericAngle), t @ Known(UnitType::Angle(_))) => Ok((a.n, b.n, t)),
827
828            (Known(UnitType::Count), Default { .. }) | (Default { .. }, Known(UnitType::Count)) => {
829                Ok((a.n, b.n, Known(UnitType::Count)))
830            }
831            (t @ Known(UnitType::Length(l1)), Default { len: l2, .. }) if l1 == l2 => Ok((a.n, b.n, t)),
832            (Default { len: l1, .. }, t @ Known(UnitType::Length(l2))) if l1 == l2 => Ok((a.n, b.n, t)),
833            (t @ Known(UnitType::Angle(a1)), Default { angle: a2, .. }) if a1 == a2 => {
834                if b.n != 0.0 {
835                    exec_state.warn(
836                        CompilationError::err(source_range, "Prefer to use explicit units for angles"),
837                        annotations::WARN_ANGLE_UNITS,
838                    );
839                }
840                Ok((a.n, b.n, t))
841            }
842            (Default { angle: a1, .. }, t @ Known(UnitType::Angle(a2))) if a1 == a2 => {
843                if a.n != 0.0 {
844                    exec_state.warn(
845                        CompilationError::err(source_range, "Prefer to use explicit units for angles"),
846                        annotations::WARN_ANGLE_UNITS,
847                    );
848                }
849                Ok((a.n, b.n, t))
850            }
851
852            _ => {
853                let a = fmt::human_display_number(a.n, a.ty);
854                let b = fmt::human_display_number(b.n, b.ty);
855                Err(KclError::new_semantic(KclErrorDetails::new(
856                    format!(
857                        "Range start and range end must be of the same type and have compatible units, but found {a} and {b}",
858                    ),
859                    vec![source_range],
860                )))
861            }
862        }
863    }
864
865    pub fn from_parsed(suffix: NumericSuffix, settings: &super::MetaSettings) -> Self {
866        match suffix {
867            NumericSuffix::None => NumericType::Default {
868                len: settings.default_length_units,
869                angle: settings.default_angle_units,
870            },
871            NumericSuffix::Count => NumericType::Known(UnitType::Count),
872            NumericSuffix::Length => NumericType::Known(UnitType::GenericLength),
873            NumericSuffix::Angle => NumericType::Known(UnitType::GenericAngle),
874            NumericSuffix::Mm => NumericType::Known(UnitType::Length(UnitLength::Millimeters)),
875            NumericSuffix::Cm => NumericType::Known(UnitType::Length(UnitLength::Centimeters)),
876            NumericSuffix::M => NumericType::Known(UnitType::Length(UnitLength::Meters)),
877            NumericSuffix::Inch => NumericType::Known(UnitType::Length(UnitLength::Inches)),
878            NumericSuffix::Ft => NumericType::Known(UnitType::Length(UnitLength::Feet)),
879            NumericSuffix::Yd => NumericType::Known(UnitType::Length(UnitLength::Yards)),
880            NumericSuffix::Deg => NumericType::Known(UnitType::Angle(UnitAngle::Degrees)),
881            NumericSuffix::Rad => NumericType::Known(UnitType::Angle(UnitAngle::Radians)),
882            NumericSuffix::Unknown => NumericType::Unknown,
883        }
884    }
885
886    fn subtype(&self, other: &NumericType) -> bool {
887        use NumericType::*;
888
889        match (self, other) {
890            (_, Any) => true,
891            (a, b) if a == b => true,
892            (
893                NumericType::Known(UnitType::Length(_))
894                | NumericType::Known(UnitType::GenericLength)
895                | NumericType::Default { .. },
896                NumericType::Known(UnitType::GenericLength),
897            )
898            | (
899                NumericType::Known(UnitType::Angle(_))
900                | NumericType::Known(UnitType::GenericAngle)
901                | NumericType::Default { .. },
902                NumericType::Known(UnitType::GenericAngle),
903            ) => true,
904            (Unknown, _) | (_, Unknown) => false,
905            (_, _) => false,
906        }
907    }
908
909    fn is_unknown(&self) -> bool {
910        matches!(
911            self,
912            NumericType::Unknown
913                | NumericType::Known(UnitType::GenericAngle)
914                | NumericType::Known(UnitType::GenericLength)
915        )
916    }
917
918    pub fn is_fully_specified(&self) -> bool {
919        !matches!(
920            self,
921            NumericType::Unknown
922                | NumericType::Known(UnitType::GenericAngle)
923                | NumericType::Known(UnitType::GenericLength)
924                | NumericType::Any
925                | NumericType::Default { .. }
926        )
927    }
928
929    fn example_ty(&self) -> Option<String> {
930        match self {
931            Self::Known(t) if !self.is_unknown() => Some(t.to_string()),
932            Self::Default { len, .. } => Some(len.to_string()),
933            _ => None,
934        }
935    }
936
937    fn coerce(&self, val: &KclValue) -> Result<KclValue, CoercionError> {
938        let (value, ty, meta) = match val {
939            KclValue::Number { value, ty, meta } => (value, ty, meta),
940            // For coercion purposes, sketch vars pass through unchanged since
941            // they will be resolved later to a number. We need the sketch var
942            // ID.
943            KclValue::SketchVar { .. } => return Ok(val.clone()),
944            _ => return Err(val.into()),
945        };
946
947        if ty.subtype(self) {
948            return Ok(KclValue::Number {
949                value: *value,
950                ty: *ty,
951                meta: meta.clone(),
952            });
953        }
954
955        // Not subtypes, but might be able to coerce
956        use NumericType::*;
957        match (ty, self) {
958            // We don't have enough information to coerce.
959            (Unknown, _) => Err(CoercionError::from(val).with_explicit(self.example_ty().unwrap_or("mm".to_owned()))),
960            (_, Unknown) => Err(val.into()),
961
962            (Any, _) => Ok(KclValue::Number {
963                value: *value,
964                ty: *self,
965                meta: meta.clone(),
966            }),
967
968            // If we're coercing to a default, we treat this as coercing to Any since leaving the numeric type unspecified in a coercion situation
969            // means accept any number rather than force the current default.
970            (_, Default { .. }) => Ok(KclValue::Number {
971                value: *value,
972                ty: *ty,
973                meta: meta.clone(),
974            }),
975
976            // Known types and compatible, but needs adjustment.
977            (Known(UnitType::Length(l1)), Known(UnitType::Length(l2))) => {
978                let (value, ty) = adjust_length(*l1, *value, *l2);
979                Ok(KclValue::Number {
980                    value,
981                    ty: Known(UnitType::Length(ty)),
982                    meta: meta.clone(),
983                })
984            }
985            (Known(UnitType::Angle(a1)), Known(UnitType::Angle(a2))) => {
986                let (value, ty) = adjust_angle(*a1, *value, *a2);
987                Ok(KclValue::Number {
988                    value,
989                    ty: Known(UnitType::Angle(ty)),
990                    meta: meta.clone(),
991                })
992            }
993
994            // Known but incompatible.
995            (Known(_), Known(_)) => Err(val.into()),
996
997            // Known and unknown => we assume the rhs, possibly with adjustment
998            (Default { .. }, Known(UnitType::Count)) => Ok(KclValue::Number {
999                value: *value,
1000                ty: Known(UnitType::Count),
1001                meta: meta.clone(),
1002            }),
1003
1004            (Default { len: l1, .. }, Known(UnitType::Length(l2))) => {
1005                let (value, ty) = adjust_length(*l1, *value, *l2);
1006                Ok(KclValue::Number {
1007                    value,
1008                    ty: Known(UnitType::Length(ty)),
1009                    meta: meta.clone(),
1010                })
1011            }
1012
1013            (Default { angle: a1, .. }, Known(UnitType::Angle(a2))) => {
1014                let (value, ty) = adjust_angle(*a1, *value, *a2);
1015                Ok(KclValue::Number {
1016                    value,
1017                    ty: Known(UnitType::Angle(ty)),
1018                    meta: meta.clone(),
1019                })
1020            }
1021
1022            (_, _) => unreachable!(),
1023        }
1024    }
1025
1026    pub fn as_length(&self) -> Option<UnitLength> {
1027        match self {
1028            Self::Known(UnitType::Length(len)) | Self::Default { len, .. } => Some(*len),
1029            _ => None,
1030        }
1031    }
1032}
1033
1034impl From<NumericType> for RuntimeType {
1035    fn from(t: NumericType) -> RuntimeType {
1036        RuntimeType::Primitive(PrimitiveType::Number(t))
1037    }
1038}
1039
1040impl From<UnitLength> for NumericType {
1041    fn from(value: UnitLength) -> Self {
1042        NumericType::Known(UnitType::Length(value))
1043    }
1044}
1045
1046impl From<Option<UnitLength>> for NumericType {
1047    fn from(value: Option<UnitLength>) -> Self {
1048        match value {
1049            Some(v) => v.into(),
1050            None => NumericType::Unknown,
1051        }
1052    }
1053}
1054
1055impl From<UnitAngle> for NumericType {
1056    fn from(value: UnitAngle) -> Self {
1057        NumericType::Known(UnitType::Angle(value))
1058    }
1059}
1060
1061impl From<UnitLength> for NumericSuffix {
1062    fn from(value: UnitLength) -> Self {
1063        match value {
1064            UnitLength::Millimeters => NumericSuffix::Mm,
1065            UnitLength::Centimeters => NumericSuffix::Cm,
1066            UnitLength::Meters => NumericSuffix::M,
1067            UnitLength::Inches => NumericSuffix::Inch,
1068            UnitLength::Feet => NumericSuffix::Ft,
1069            UnitLength::Yards => NumericSuffix::Yd,
1070        }
1071    }
1072}
1073
1074#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, ts_rs::TS)]
1075pub struct NumericSuffixTypeConvertError;
1076
1077impl TryFrom<NumericType> for NumericSuffix {
1078    type Error = NumericSuffixTypeConvertError;
1079
1080    fn try_from(value: NumericType) -> Result<Self, Self::Error> {
1081        match value {
1082            NumericType::Known(UnitType::Count) => Ok(NumericSuffix::Count),
1083            NumericType::Known(UnitType::Length(unit_length)) => Ok(NumericSuffix::from(unit_length)),
1084            NumericType::Known(UnitType::GenericLength) => Ok(NumericSuffix::Length),
1085            NumericType::Known(UnitType::Angle(UnitAngle::Degrees)) => Ok(NumericSuffix::Deg),
1086            NumericType::Known(UnitType::Angle(UnitAngle::Radians)) => Ok(NumericSuffix::Rad),
1087            NumericType::Known(UnitType::GenericAngle) => Ok(NumericSuffix::Angle),
1088            NumericType::Default { .. } => Ok(NumericSuffix::None),
1089            NumericType::Unknown => Ok(NumericSuffix::Unknown),
1090            NumericType::Any => Err(NumericSuffixTypeConvertError),
1091        }
1092    }
1093}
1094
1095#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, ts_rs::TS)]
1096#[ts(export)]
1097#[serde(tag = "type")]
1098pub enum UnitType {
1099    Count,
1100    Length(UnitLength),
1101    GenericLength,
1102    Angle(UnitAngle),
1103    GenericAngle,
1104}
1105
1106impl UnitType {
1107    pub(crate) fn to_suffix(self) -> Option<String> {
1108        match self {
1109            UnitType::Count => Some("_".to_owned()),
1110            UnitType::GenericLength | UnitType::GenericAngle => None,
1111            UnitType::Length(l) => Some(l.to_string()),
1112            UnitType::Angle(a) => Some(a.to_string()),
1113        }
1114    }
1115}
1116
1117impl std::fmt::Display for UnitType {
1118    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1119        match self {
1120            UnitType::Count => write!(f, "Count"),
1121            UnitType::Length(l) => l.fmt(f),
1122            UnitType::GenericLength => write!(f, "Length"),
1123            UnitType::Angle(a) => a.fmt(f),
1124            UnitType::GenericAngle => write!(f, "Angle"),
1125        }
1126    }
1127}
1128
1129pub fn adjust_length(from: UnitLength, value: f64, to: UnitLength) -> (f64, UnitLength) {
1130    use UnitLength::*;
1131
1132    if from == to {
1133        return (value, to);
1134    }
1135
1136    let (base, base_unit) = match from {
1137        Millimeters => (value, Millimeters),
1138        Centimeters => (value * 10.0, Millimeters),
1139        Meters => (value * 1000.0, Millimeters),
1140        Inches => (value, Inches),
1141        Feet => (value * 12.0, Inches),
1142        Yards => (value * 36.0, Inches),
1143    };
1144    let (base, base_unit) = match (base_unit, to) {
1145        (Millimeters, Inches) | (Millimeters, Feet) | (Millimeters, Yards) => (base / 25.4, Inches),
1146        (Inches, Millimeters) | (Inches, Centimeters) | (Inches, Meters) => (base * 25.4, Millimeters),
1147        _ => (base, base_unit),
1148    };
1149
1150    let value = match (base_unit, to) {
1151        (Millimeters, Millimeters) => base,
1152        (Millimeters, Centimeters) => base / 10.0,
1153        (Millimeters, Meters) => base / 1000.0,
1154        (Inches, Inches) => base,
1155        (Inches, Feet) => base / 12.0,
1156        (Inches, Yards) => base / 36.0,
1157        _ => unreachable!(),
1158    };
1159
1160    (value, to)
1161}
1162
1163pub fn adjust_angle(from: UnitAngle, value: f64, to: UnitAngle) -> (f64, UnitAngle) {
1164    use std::f64::consts::PI;
1165
1166    use UnitAngle::*;
1167
1168    let value = match (from, to) {
1169        (Degrees, Degrees) => value,
1170        (Degrees, Radians) => (value / 180.0) * PI,
1171        (Radians, Degrees) => 180.0 * value / PI,
1172        (Radians, Radians) => value,
1173    };
1174
1175    (value, to)
1176}
1177
1178pub(super) fn length_from_str(s: &str, source_range: SourceRange) -> Result<UnitLength, KclError> {
1179    // We don't use `from_str` here because we want to be more flexible about the input we accept.
1180    match s {
1181        "mm" => Ok(UnitLength::Millimeters),
1182        "cm" => Ok(UnitLength::Centimeters),
1183        "m" => Ok(UnitLength::Meters),
1184        "inch" | "in" => Ok(UnitLength::Inches),
1185        "ft" => Ok(UnitLength::Feet),
1186        "yd" => Ok(UnitLength::Yards),
1187        value => Err(KclError::new_semantic(KclErrorDetails::new(
1188            format!("Unexpected value for length units: `{value}`; expected one of `mm`, `cm`, `m`, `in`, `ft`, `yd`"),
1189            vec![source_range],
1190        ))),
1191    }
1192}
1193
1194pub(super) fn angle_from_str(s: &str, source_range: SourceRange) -> Result<UnitAngle, KclError> {
1195    UnitAngle::from_str(s).map_err(|_| {
1196        KclError::new_semantic(KclErrorDetails::new(
1197            format!("Unexpected value for angle units: `{s}`; expected one of `deg`, `rad`"),
1198            vec![source_range],
1199        ))
1200    })
1201}
1202
1203#[derive(Debug, Clone)]
1204pub struct CoercionError {
1205    pub found: Option<RuntimeType>,
1206    pub explicit_coercion: Option<String>,
1207}
1208
1209impl CoercionError {
1210    fn with_explicit(mut self, c: String) -> Self {
1211        self.explicit_coercion = Some(c);
1212        self
1213    }
1214}
1215
1216impl From<&'_ KclValue> for CoercionError {
1217    fn from(value: &'_ KclValue) -> Self {
1218        CoercionError {
1219            found: value.principal_type(),
1220            explicit_coercion: None,
1221        }
1222    }
1223}
1224
1225impl KclValue {
1226    /// True if `self` has a type which is a subtype of `ty` without coercion.
1227    pub fn has_type(&self, ty: &RuntimeType) -> bool {
1228        let Some(self_ty) = self.principal_type() else {
1229            return false;
1230        };
1231
1232        self_ty.subtype(ty)
1233    }
1234
1235    /// Coerce `self` to a new value which has `ty` as its closest supertype.
1236    ///
1237    /// If the result is Ok, then:
1238    ///   - result.principal_type().unwrap().subtype(ty)
1239    ///
1240    /// If self.principal_type() == ty then result == self
1241    pub fn coerce(
1242        &self,
1243        ty: &RuntimeType,
1244        convert_units: bool,
1245        exec_state: &mut ExecState,
1246    ) -> Result<KclValue, CoercionError> {
1247        match self {
1248            KclValue::Tuple { value, .. }
1249                if value.len() == 1
1250                    && !matches!(ty, RuntimeType::Primitive(PrimitiveType::Any) | RuntimeType::Tuple(..)) =>
1251            {
1252                if let Ok(coerced) = value[0].coerce(ty, convert_units, exec_state) {
1253                    return Ok(coerced);
1254                }
1255            }
1256            KclValue::HomArray { value, .. }
1257                if value.len() == 1
1258                    && !matches!(ty, RuntimeType::Primitive(PrimitiveType::Any) | RuntimeType::Array(..)) =>
1259            {
1260                if let Ok(coerced) = value[0].coerce(ty, convert_units, exec_state) {
1261                    return Ok(coerced);
1262                }
1263            }
1264            _ => {}
1265        }
1266
1267        match ty {
1268            RuntimeType::Primitive(ty) => self.coerce_to_primitive_type(ty, convert_units, exec_state),
1269            RuntimeType::Array(ty, len) => self.coerce_to_array_type(ty, convert_units, *len, exec_state, false),
1270            RuntimeType::Tuple(tys) => self.coerce_to_tuple_type(tys, convert_units, exec_state),
1271            RuntimeType::Union(tys) => self.coerce_to_union_type(tys, convert_units, exec_state),
1272            RuntimeType::Object(tys, constrainable) => {
1273                self.coerce_to_object_type(tys, *constrainable, convert_units, exec_state)
1274            }
1275        }
1276    }
1277
1278    fn coerce_to_primitive_type(
1279        &self,
1280        ty: &PrimitiveType,
1281        convert_units: bool,
1282        exec_state: &mut ExecState,
1283    ) -> Result<KclValue, CoercionError> {
1284        match ty {
1285            PrimitiveType::Any => Ok(self.clone()),
1286            PrimitiveType::None => match self {
1287                KclValue::KclNone { .. } => Ok(self.clone()),
1288                _ => Err(self.into()),
1289            },
1290            PrimitiveType::Number(ty) => {
1291                if convert_units {
1292                    return ty.coerce(self);
1293                }
1294
1295                // Instead of converting units, reinterpret the number as having
1296                // different units.
1297                //
1298                // If the user is explicitly specifying units, treat the value
1299                // as having had its units erased, rather than forcing the user
1300                // to explicitly erase them.
1301                if let KclValue::Number { value: n, meta, .. } = &self
1302                    && ty.is_fully_specified()
1303                {
1304                    let value = KclValue::Number {
1305                        ty: NumericType::Any,
1306                        value: *n,
1307                        meta: meta.clone(),
1308                    };
1309                    return ty.coerce(&value);
1310                }
1311                ty.coerce(self)
1312            }
1313            PrimitiveType::String => match self {
1314                KclValue::String { .. } => Ok(self.clone()),
1315                _ => Err(self.into()),
1316            },
1317            PrimitiveType::Boolean => match self {
1318                KclValue::Bool { .. } => Ok(self.clone()),
1319                _ => Err(self.into()),
1320            },
1321            PrimitiveType::GdtAnnotation => match self {
1322                KclValue::GdtAnnotation { .. } => Ok(self.clone()),
1323                _ => Err(self.into()),
1324            },
1325            PrimitiveType::Segment => match self {
1326                KclValue::Segment { .. } => Ok(self.clone()),
1327                _ => Err(self.into()),
1328            },
1329            PrimitiveType::Sketch => match self {
1330                KclValue::Sketch { .. } => Ok(self.clone()),
1331                _ => Err(self.into()),
1332            },
1333            PrimitiveType::Constraint => match self {
1334                KclValue::SketchConstraint { .. } => Ok(self.clone()),
1335                _ => Err(self.into()),
1336            },
1337            PrimitiveType::Solid => match self {
1338                KclValue::Solid { .. } => Ok(self.clone()),
1339                _ => Err(self.into()),
1340            },
1341            PrimitiveType::Plane => {
1342                match self {
1343                    KclValue::String { value: s, .. }
1344                        if [
1345                            "xy", "xz", "yz", "-xy", "-xz", "-yz", "XY", "XZ", "YZ", "-XY", "-XZ", "-YZ",
1346                        ]
1347                        .contains(&&**s) =>
1348                    {
1349                        Ok(self.clone())
1350                    }
1351                    KclValue::Plane { .. } => Ok(self.clone()),
1352                    KclValue::Object { value, meta, .. } => {
1353                        let origin = value
1354                            .get("origin")
1355                            .and_then(Point3d::from_kcl_val)
1356                            .ok_or(CoercionError::from(self))?;
1357                        let x_axis = value
1358                            .get("xAxis")
1359                            .and_then(Point3d::from_kcl_val)
1360                            .ok_or(CoercionError::from(self))?;
1361                        let y_axis = value
1362                            .get("yAxis")
1363                            .and_then(Point3d::from_kcl_val)
1364                            .ok_or(CoercionError::from(self))?;
1365                        let z_axis = x_axis.axes_cross_product(&y_axis);
1366
1367                        if value.get("zAxis").is_some() {
1368                            exec_state.warn(CompilationError::err(
1369                            self.into(),
1370                            "Object with a zAxis field is being coerced into a plane, but the zAxis is ignored.",
1371                        ), annotations::WARN_IGNORED_Z_AXIS);
1372                        }
1373
1374                        let id = exec_state.mod_local.id_generator.next_uuid();
1375                        let info = PlaneInfo {
1376                            origin,
1377                            x_axis: x_axis.normalize(),
1378                            y_axis: y_axis.normalize(),
1379                            z_axis: z_axis.normalize(),
1380                        };
1381                        let plane = Plane {
1382                            id,
1383                            artifact_id: id.into(),
1384                            object_id: None,
1385                            kind: PlaneKind::from(&info),
1386                            info,
1387                            meta: meta.clone(),
1388                        };
1389
1390                        Ok(KclValue::Plane { value: Box::new(plane) })
1391                    }
1392                    _ => Err(self.into()),
1393                }
1394            }
1395            PrimitiveType::Face => match self {
1396                KclValue::Face { .. } => Ok(self.clone()),
1397                _ => Err(self.into()),
1398            },
1399            PrimitiveType::Helix => match self {
1400                KclValue::Helix { .. } => Ok(self.clone()),
1401                _ => Err(self.into()),
1402            },
1403            PrimitiveType::Edge => match self {
1404                KclValue::Uuid { .. } => Ok(self.clone()),
1405                KclValue::TagIdentifier { .. } => Ok(self.clone()),
1406                _ => Err(self.into()),
1407            },
1408            PrimitiveType::TaggedEdge => match self {
1409                KclValue::TagIdentifier { .. } => Ok(self.clone()),
1410                _ => Err(self.into()),
1411            },
1412            PrimitiveType::TaggedFace => match self {
1413                KclValue::TagIdentifier { .. } => Ok(self.clone()),
1414                s @ KclValue::String { value, .. } if ["start", "end", "START", "END"].contains(&&**value) => {
1415                    Ok(s.clone())
1416                }
1417                _ => Err(self.into()),
1418            },
1419            PrimitiveType::Axis2d => match self {
1420                KclValue::Object {
1421                    value: values, meta, ..
1422                } => {
1423                    if values
1424                        .get("origin")
1425                        .ok_or(CoercionError::from(self))?
1426                        .has_type(&RuntimeType::point2d())
1427                        && values
1428                            .get("direction")
1429                            .ok_or(CoercionError::from(self))?
1430                            .has_type(&RuntimeType::point2d())
1431                    {
1432                        return Ok(self.clone());
1433                    }
1434
1435                    let origin = values.get("origin").ok_or(self.into()).and_then(|p| {
1436                        p.coerce_to_array_type(
1437                            &RuntimeType::length(),
1438                            convert_units,
1439                            ArrayLen::Known(2),
1440                            exec_state,
1441                            true,
1442                        )
1443                    })?;
1444                    let direction = values.get("direction").ok_or(self.into()).and_then(|p| {
1445                        p.coerce_to_array_type(
1446                            &RuntimeType::length(),
1447                            convert_units,
1448                            ArrayLen::Known(2),
1449                            exec_state,
1450                            true,
1451                        )
1452                    })?;
1453
1454                    Ok(KclValue::Object {
1455                        value: [("origin".to_owned(), origin), ("direction".to_owned(), direction)].into(),
1456                        meta: meta.clone(),
1457                        constrainable: false,
1458                    })
1459                }
1460                _ => Err(self.into()),
1461            },
1462            PrimitiveType::Axis3d => match self {
1463                KclValue::Object {
1464                    value: values, meta, ..
1465                } => {
1466                    if values
1467                        .get("origin")
1468                        .ok_or(CoercionError::from(self))?
1469                        .has_type(&RuntimeType::point3d())
1470                        && values
1471                            .get("direction")
1472                            .ok_or(CoercionError::from(self))?
1473                            .has_type(&RuntimeType::point3d())
1474                    {
1475                        return Ok(self.clone());
1476                    }
1477
1478                    let origin = values.get("origin").ok_or(self.into()).and_then(|p| {
1479                        p.coerce_to_array_type(
1480                            &RuntimeType::length(),
1481                            convert_units,
1482                            ArrayLen::Known(3),
1483                            exec_state,
1484                            true,
1485                        )
1486                    })?;
1487                    let direction = values.get("direction").ok_or(self.into()).and_then(|p| {
1488                        p.coerce_to_array_type(
1489                            &RuntimeType::length(),
1490                            convert_units,
1491                            ArrayLen::Known(3),
1492                            exec_state,
1493                            true,
1494                        )
1495                    })?;
1496
1497                    Ok(KclValue::Object {
1498                        value: [("origin".to_owned(), origin), ("direction".to_owned(), direction)].into(),
1499                        meta: meta.clone(),
1500                        constrainable: false,
1501                    })
1502                }
1503                _ => Err(self.into()),
1504            },
1505            PrimitiveType::ImportedGeometry => match self {
1506                KclValue::ImportedGeometry { .. } => Ok(self.clone()),
1507                _ => Err(self.into()),
1508            },
1509            PrimitiveType::Function => match self {
1510                KclValue::Function { .. } => Ok(self.clone()),
1511                _ => Err(self.into()),
1512            },
1513            PrimitiveType::TagDecl => match self {
1514                KclValue::TagDeclarator { .. } => Ok(self.clone()),
1515                _ => Err(self.into()),
1516            },
1517        }
1518    }
1519
1520    fn coerce_to_array_type(
1521        &self,
1522        ty: &RuntimeType,
1523        convert_units: bool,
1524        len: ArrayLen,
1525        exec_state: &mut ExecState,
1526        allow_shrink: bool,
1527    ) -> Result<KclValue, CoercionError> {
1528        match self {
1529            KclValue::HomArray { value, ty: aty, .. } => {
1530                let satisfied_len = len.satisfied(value.len(), allow_shrink);
1531
1532                if aty.subtype(ty) {
1533                    // If the element type is a subtype of the target type and
1534                    // the length constraint is satisfied, we can just return
1535                    // the values unchanged, only adjusting the length. The new
1536                    // array element type should preserve its type because the
1537                    // target type oftentimes includes an unknown type as a way
1538                    // to say that the caller doesn't care.
1539                    return satisfied_len
1540                        .map(|len| KclValue::HomArray {
1541                            value: value[..len].to_vec(),
1542                            ty: aty.clone(),
1543                        })
1544                        .ok_or(self.into());
1545                }
1546
1547                // Ignore the array type, and coerce the elements of the array.
1548                if let Some(satisfied_len) = satisfied_len {
1549                    let value_result = value
1550                        .iter()
1551                        .take(satisfied_len)
1552                        .map(|v| v.coerce(ty, convert_units, exec_state))
1553                        .collect::<Result<Vec<_>, _>>();
1554
1555                    if let Ok(value) = value_result {
1556                        // We were able to coerce all the elements.
1557                        return Ok(KclValue::HomArray { value, ty: ty.clone() });
1558                    }
1559                }
1560
1561                // As a last resort, try to flatten the array.
1562                let mut values = Vec::new();
1563                for item in value {
1564                    if let KclValue::HomArray { value: inner_value, .. } = item {
1565                        // Flatten elements.
1566                        for item in inner_value {
1567                            values.push(item.coerce(ty, convert_units, exec_state)?);
1568                        }
1569                    } else {
1570                        values.push(item.coerce(ty, convert_units, exec_state)?);
1571                    }
1572                }
1573
1574                let len = len
1575                    .satisfied(values.len(), allow_shrink)
1576                    .ok_or(CoercionError::from(self))?;
1577
1578                if len > values.len() {
1579                    let message = format!(
1580                        "Internal: Expected coerced array length {len} to be less than or equal to original length {}",
1581                        values.len()
1582                    );
1583                    exec_state.err(CompilationError::err(self.into(), message.clone()));
1584                    #[cfg(debug_assertions)]
1585                    panic!("{message}");
1586                }
1587                values.truncate(len);
1588
1589                Ok(KclValue::HomArray {
1590                    value: values,
1591                    ty: ty.clone(),
1592                })
1593            }
1594            KclValue::Tuple { value, .. } => {
1595                let len = len
1596                    .satisfied(value.len(), allow_shrink)
1597                    .ok_or(CoercionError::from(self))?;
1598                let value = value
1599                    .iter()
1600                    .map(|item| item.coerce(ty, convert_units, exec_state))
1601                    .take(len)
1602                    .collect::<Result<Vec<_>, _>>()?;
1603
1604                Ok(KclValue::HomArray { value, ty: ty.clone() })
1605            }
1606            KclValue::KclNone { .. } if len.satisfied(0, false).is_some() => Ok(KclValue::HomArray {
1607                value: Vec::new(),
1608                ty: ty.clone(),
1609            }),
1610            _ if len.satisfied(1, false).is_some() => self.coerce(ty, convert_units, exec_state),
1611            _ => Err(self.into()),
1612        }
1613    }
1614
1615    fn coerce_to_tuple_type(
1616        &self,
1617        tys: &[RuntimeType],
1618        convert_units: bool,
1619        exec_state: &mut ExecState,
1620    ) -> Result<KclValue, CoercionError> {
1621        match self {
1622            KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } if value.len() == tys.len() => {
1623                let mut result = Vec::new();
1624                for (i, t) in tys.iter().enumerate() {
1625                    result.push(value[i].coerce(t, convert_units, exec_state)?);
1626                }
1627
1628                Ok(KclValue::Tuple {
1629                    value: result,
1630                    meta: Vec::new(),
1631                })
1632            }
1633            KclValue::KclNone { meta, .. } if tys.is_empty() => Ok(KclValue::Tuple {
1634                value: Vec::new(),
1635                meta: meta.clone(),
1636            }),
1637            _ if tys.len() == 1 => self.coerce(&tys[0], convert_units, exec_state),
1638            _ => Err(self.into()),
1639        }
1640    }
1641
1642    fn coerce_to_union_type(
1643        &self,
1644        tys: &[RuntimeType],
1645        convert_units: bool,
1646        exec_state: &mut ExecState,
1647    ) -> Result<KclValue, CoercionError> {
1648        for t in tys {
1649            if let Ok(v) = self.coerce(t, convert_units, exec_state) {
1650                return Ok(v);
1651            }
1652        }
1653
1654        Err(self.into())
1655    }
1656
1657    fn coerce_to_object_type(
1658        &self,
1659        tys: &[(String, RuntimeType)],
1660        constrainable: bool,
1661        _convert_units: bool,
1662        _exec_state: &mut ExecState,
1663    ) -> Result<KclValue, CoercionError> {
1664        match self {
1665            KclValue::Object { value, meta, .. } => {
1666                for (s, t) in tys {
1667                    // TODO coerce fields
1668                    if !value.get(s).ok_or(CoercionError::from(self))?.has_type(t) {
1669                        return Err(self.into());
1670                    }
1671                }
1672                // TODO remove non-required fields
1673                Ok(KclValue::Object {
1674                    value: value.clone(),
1675                    meta: meta.clone(),
1676                    // Note that we don't check for constrainability, coercing to a constrainable object
1677                    // adds that property.
1678                    constrainable,
1679                })
1680            }
1681            KclValue::KclNone { meta, .. } if tys.is_empty() => Ok(KclValue::Object {
1682                value: HashMap::new(),
1683                meta: meta.clone(),
1684                constrainable,
1685            }),
1686            _ => Err(self.into()),
1687        }
1688    }
1689
1690    pub fn principal_type(&self) -> Option<RuntimeType> {
1691        match self {
1692            KclValue::Bool { .. } => Some(RuntimeType::Primitive(PrimitiveType::Boolean)),
1693            KclValue::Number { ty, .. } => Some(RuntimeType::Primitive(PrimitiveType::Number(*ty))),
1694            KclValue::String { .. } => Some(RuntimeType::Primitive(PrimitiveType::String)),
1695            KclValue::SketchVar { value, .. } => Some(RuntimeType::Primitive(PrimitiveType::Number(value.ty))),
1696            KclValue::SketchConstraint { .. } => Some(RuntimeType::Primitive(PrimitiveType::Constraint)),
1697            KclValue::Object {
1698                value, constrainable, ..
1699            } => {
1700                let properties = value
1701                    .iter()
1702                    .map(|(k, v)| v.principal_type().map(|t| (k.clone(), t)))
1703                    .collect::<Option<Vec<_>>>()?;
1704                Some(RuntimeType::Object(properties, *constrainable))
1705            }
1706            KclValue::GdtAnnotation { .. } => Some(RuntimeType::Primitive(PrimitiveType::GdtAnnotation)),
1707            KclValue::Plane { .. } => Some(RuntimeType::Primitive(PrimitiveType::Plane)),
1708            KclValue::Sketch { .. } => Some(RuntimeType::Primitive(PrimitiveType::Sketch)),
1709            KclValue::Solid { .. } => Some(RuntimeType::Primitive(PrimitiveType::Solid)),
1710            KclValue::Face { .. } => Some(RuntimeType::Primitive(PrimitiveType::Face)),
1711            KclValue::Segment { .. } => Some(RuntimeType::Primitive(PrimitiveType::Segment)),
1712            KclValue::Helix { .. } => Some(RuntimeType::Primitive(PrimitiveType::Helix)),
1713            KclValue::ImportedGeometry(..) => Some(RuntimeType::Primitive(PrimitiveType::ImportedGeometry)),
1714            KclValue::Tuple { value, .. } => Some(RuntimeType::Tuple(
1715                value.iter().map(|v| v.principal_type()).collect::<Option<Vec<_>>>()?,
1716            )),
1717            KclValue::HomArray { ty, value, .. } => {
1718                Some(RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Known(value.len())))
1719            }
1720            KclValue::TagIdentifier(_) => Some(RuntimeType::Primitive(PrimitiveType::TaggedEdge)),
1721            KclValue::TagDeclarator(_) => Some(RuntimeType::Primitive(PrimitiveType::TagDecl)),
1722            KclValue::Uuid { .. } => Some(RuntimeType::Primitive(PrimitiveType::Edge)),
1723            KclValue::Function { .. } => Some(RuntimeType::Primitive(PrimitiveType::Function)),
1724            KclValue::KclNone { .. } => Some(RuntimeType::Primitive(PrimitiveType::None)),
1725            KclValue::Module { .. } | KclValue::Type { .. } => None,
1726        }
1727    }
1728
1729    pub fn principal_type_string(&self) -> String {
1730        if let Some(ty) = self.principal_type() {
1731            return format!("`{ty}`");
1732        }
1733
1734        match self {
1735            KclValue::Module { .. } => "module",
1736            KclValue::KclNone { .. } => "none",
1737            KclValue::Type { .. } => "type",
1738            _ => {
1739                debug_assert!(false);
1740                "<unexpected type>"
1741            }
1742        }
1743        .to_owned()
1744    }
1745}
1746
1747#[cfg(test)]
1748mod test {
1749    use super::*;
1750    use crate::execution::{ExecTestResults, parse_execute};
1751
1752    async fn new_exec_state() -> (crate::ExecutorContext, ExecState) {
1753        let ctx = crate::ExecutorContext::new_mock(None).await;
1754        let exec_state = ExecState::new(&ctx);
1755        (ctx, exec_state)
1756    }
1757
1758    fn values(exec_state: &mut ExecState) -> Vec<KclValue> {
1759        vec![
1760            KclValue::Bool {
1761                value: true,
1762                meta: Vec::new(),
1763            },
1764            KclValue::Number {
1765                value: 1.0,
1766                ty: NumericType::count(),
1767                meta: Vec::new(),
1768            },
1769            KclValue::String {
1770                value: "hello".to_owned(),
1771                meta: Vec::new(),
1772            },
1773            KclValue::Tuple {
1774                value: Vec::new(),
1775                meta: Vec::new(),
1776            },
1777            KclValue::HomArray {
1778                value: Vec::new(),
1779                ty: RuntimeType::solid(),
1780            },
1781            KclValue::Object {
1782                value: crate::execution::KclObjectFields::new(),
1783                meta: Vec::new(),
1784                constrainable: false,
1785            },
1786            KclValue::TagIdentifier(Box::new("foo".parse().unwrap())),
1787            KclValue::TagDeclarator(Box::new(crate::parsing::ast::types::TagDeclarator::new("foo"))),
1788            KclValue::Plane {
1789                value: Box::new(
1790                    Plane::from_plane_data_skipping_engine(crate::std::sketch::PlaneData::XY, exec_state).unwrap(),
1791                ),
1792            },
1793            // No easy way to make a Face, Sketch, Solid, or Helix
1794            KclValue::ImportedGeometry(crate::execution::ImportedGeometry::new(
1795                uuid::Uuid::nil(),
1796                Vec::new(),
1797                Vec::new(),
1798            )),
1799            // Other values don't have types
1800        ]
1801    }
1802
1803    #[track_caller]
1804    fn assert_coerce_results(
1805        value: &KclValue,
1806        super_type: &RuntimeType,
1807        expected_value: &KclValue,
1808        exec_state: &mut ExecState,
1809    ) {
1810        let is_subtype = value == expected_value;
1811        let actual = value.coerce(super_type, true, exec_state).unwrap();
1812        assert_eq!(&actual, expected_value);
1813        assert_eq!(
1814            is_subtype,
1815            value.principal_type().is_some() && value.principal_type().unwrap().subtype(super_type),
1816            "{:?} <: {super_type:?} should be {is_subtype}",
1817            value.principal_type().unwrap()
1818        );
1819        assert!(
1820            expected_value.principal_type().unwrap().subtype(super_type),
1821            "{} <: {super_type}",
1822            expected_value.principal_type().unwrap()
1823        )
1824    }
1825
1826    #[tokio::test(flavor = "multi_thread")]
1827    async fn coerce_idempotent() {
1828        let (ctx, mut exec_state) = new_exec_state().await;
1829        let values = values(&mut exec_state);
1830        for v in &values {
1831            // Identity subtype
1832            let ty = v.principal_type().unwrap();
1833            assert_coerce_results(v, &ty, v, &mut exec_state);
1834
1835            // Union subtype
1836            let uty1 = RuntimeType::Union(vec![ty.clone()]);
1837            let uty2 = RuntimeType::Union(vec![ty.clone(), RuntimeType::Primitive(PrimitiveType::Boolean)]);
1838            assert_coerce_results(v, &uty1, v, &mut exec_state);
1839            assert_coerce_results(v, &uty2, v, &mut exec_state);
1840
1841            // Array subtypes
1842            let aty = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::None);
1843            let aty1 = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Known(1));
1844            let aty0 = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Minimum(1));
1845
1846            match v {
1847                KclValue::HomArray { .. } => {
1848                    // These will not get wrapped if possible.
1849                    assert_coerce_results(
1850                        v,
1851                        &aty,
1852                        &KclValue::HomArray {
1853                            value: vec![],
1854                            ty: ty.clone(),
1855                        },
1856                        &mut exec_state,
1857                    );
1858                    // Coercing an empty array to an array of length 1
1859                    // should fail.
1860                    v.coerce(&aty1, true, &mut exec_state).unwrap_err();
1861                    // Coercing an empty array to an array that's
1862                    // non-empty should fail.
1863                    v.coerce(&aty0, true, &mut exec_state).unwrap_err();
1864                }
1865                KclValue::Tuple { .. } => {}
1866                _ => {
1867                    assert_coerce_results(v, &aty, v, &mut exec_state);
1868                    assert_coerce_results(v, &aty1, v, &mut exec_state);
1869                    assert_coerce_results(v, &aty0, v, &mut exec_state);
1870
1871                    // Tuple subtype
1872                    let tty = RuntimeType::Tuple(vec![ty.clone()]);
1873                    assert_coerce_results(v, &tty, v, &mut exec_state);
1874                }
1875            }
1876        }
1877
1878        for v in &values[1..] {
1879            // Not a subtype
1880            v.coerce(&RuntimeType::Primitive(PrimitiveType::Boolean), true, &mut exec_state)
1881                .unwrap_err();
1882        }
1883        ctx.close().await;
1884    }
1885
1886    #[tokio::test(flavor = "multi_thread")]
1887    async fn coerce_none() {
1888        let (ctx, mut exec_state) = new_exec_state().await;
1889        let none = KclValue::KclNone {
1890            value: crate::parsing::ast::types::KclNone::new(),
1891            meta: Vec::new(),
1892        };
1893
1894        let aty = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::None);
1895        let aty0 = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::Known(0));
1896        let aty1 = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::Known(1));
1897        let aty1p = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::Minimum(1));
1898        assert_coerce_results(
1899            &none,
1900            &aty,
1901            &KclValue::HomArray {
1902                value: Vec::new(),
1903                ty: RuntimeType::solid(),
1904            },
1905            &mut exec_state,
1906        );
1907        assert_coerce_results(
1908            &none,
1909            &aty0,
1910            &KclValue::HomArray {
1911                value: Vec::new(),
1912                ty: RuntimeType::solid(),
1913            },
1914            &mut exec_state,
1915        );
1916        none.coerce(&aty1, true, &mut exec_state).unwrap_err();
1917        none.coerce(&aty1p, true, &mut exec_state).unwrap_err();
1918
1919        let tty = RuntimeType::Tuple(vec![]);
1920        let tty1 = RuntimeType::Tuple(vec![RuntimeType::solid()]);
1921        assert_coerce_results(
1922            &none,
1923            &tty,
1924            &KclValue::Tuple {
1925                value: Vec::new(),
1926                meta: Vec::new(),
1927            },
1928            &mut exec_state,
1929        );
1930        none.coerce(&tty1, true, &mut exec_state).unwrap_err();
1931
1932        let oty = RuntimeType::Object(vec![], false);
1933        assert_coerce_results(
1934            &none,
1935            &oty,
1936            &KclValue::Object {
1937                value: HashMap::new(),
1938                meta: Vec::new(),
1939                constrainable: false,
1940            },
1941            &mut exec_state,
1942        );
1943        ctx.close().await;
1944    }
1945
1946    #[tokio::test(flavor = "multi_thread")]
1947    async fn coerce_record() {
1948        let (ctx, mut exec_state) = new_exec_state().await;
1949
1950        let obj0 = KclValue::Object {
1951            value: HashMap::new(),
1952            meta: Vec::new(),
1953            constrainable: false,
1954        };
1955        let obj1 = KclValue::Object {
1956            value: [(
1957                "foo".to_owned(),
1958                KclValue::Bool {
1959                    value: true,
1960                    meta: Vec::new(),
1961                },
1962            )]
1963            .into(),
1964            meta: Vec::new(),
1965            constrainable: false,
1966        };
1967        let obj2 = KclValue::Object {
1968            value: [
1969                (
1970                    "foo".to_owned(),
1971                    KclValue::Bool {
1972                        value: true,
1973                        meta: Vec::new(),
1974                    },
1975                ),
1976                (
1977                    "bar".to_owned(),
1978                    KclValue::Number {
1979                        value: 0.0,
1980                        ty: NumericType::count(),
1981                        meta: Vec::new(),
1982                    },
1983                ),
1984                (
1985                    "baz".to_owned(),
1986                    KclValue::Number {
1987                        value: 42.0,
1988                        ty: NumericType::count(),
1989                        meta: Vec::new(),
1990                    },
1991                ),
1992            ]
1993            .into(),
1994            meta: Vec::new(),
1995            constrainable: false,
1996        };
1997
1998        let ty0 = RuntimeType::Object(vec![], false);
1999        assert_coerce_results(&obj0, &ty0, &obj0, &mut exec_state);
2000        assert_coerce_results(&obj1, &ty0, &obj1, &mut exec_state);
2001        assert_coerce_results(&obj2, &ty0, &obj2, &mut exec_state);
2002
2003        let ty1 = RuntimeType::Object(
2004            vec![("foo".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean))],
2005            false,
2006        );
2007        obj0.coerce(&ty1, true, &mut exec_state).unwrap_err();
2008        assert_coerce_results(&obj1, &ty1, &obj1, &mut exec_state);
2009        assert_coerce_results(&obj2, &ty1, &obj2, &mut exec_state);
2010
2011        // Different ordering, (TODO - test for covariance once implemented)
2012        let ty2 = RuntimeType::Object(
2013            vec![
2014                (
2015                    "bar".to_owned(),
2016                    RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2017                ),
2018                ("foo".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean)),
2019            ],
2020            false,
2021        );
2022        obj0.coerce(&ty2, true, &mut exec_state).unwrap_err();
2023        obj1.coerce(&ty2, true, &mut exec_state).unwrap_err();
2024        assert_coerce_results(&obj2, &ty2, &obj2, &mut exec_state);
2025
2026        // field not present
2027        let tyq = RuntimeType::Object(
2028            vec![("qux".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean))],
2029            false,
2030        );
2031        obj0.coerce(&tyq, true, &mut exec_state).unwrap_err();
2032        obj1.coerce(&tyq, true, &mut exec_state).unwrap_err();
2033        obj2.coerce(&tyq, true, &mut exec_state).unwrap_err();
2034
2035        // field with different type
2036        let ty1 = RuntimeType::Object(
2037            vec![("bar".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean))],
2038            false,
2039        );
2040        obj2.coerce(&ty1, true, &mut exec_state).unwrap_err();
2041        ctx.close().await;
2042    }
2043
2044    #[tokio::test(flavor = "multi_thread")]
2045    async fn coerce_array() {
2046        let (ctx, mut exec_state) = new_exec_state().await;
2047
2048        let hom_arr = KclValue::HomArray {
2049            value: vec![
2050                KclValue::Number {
2051                    value: 0.0,
2052                    ty: NumericType::count(),
2053                    meta: Vec::new(),
2054                },
2055                KclValue::Number {
2056                    value: 1.0,
2057                    ty: NumericType::count(),
2058                    meta: Vec::new(),
2059                },
2060                KclValue::Number {
2061                    value: 2.0,
2062                    ty: NumericType::count(),
2063                    meta: Vec::new(),
2064                },
2065                KclValue::Number {
2066                    value: 3.0,
2067                    ty: NumericType::count(),
2068                    meta: Vec::new(),
2069                },
2070            ],
2071            ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2072        };
2073        let mixed1 = KclValue::Tuple {
2074            value: vec![
2075                KclValue::Number {
2076                    value: 0.0,
2077                    ty: NumericType::count(),
2078                    meta: Vec::new(),
2079                },
2080                KclValue::Number {
2081                    value: 1.0,
2082                    ty: NumericType::count(),
2083                    meta: Vec::new(),
2084                },
2085            ],
2086            meta: Vec::new(),
2087        };
2088        let mixed2 = KclValue::Tuple {
2089            value: vec![
2090                KclValue::Number {
2091                    value: 0.0,
2092                    ty: NumericType::count(),
2093                    meta: Vec::new(),
2094                },
2095                KclValue::Bool {
2096                    value: true,
2097                    meta: Vec::new(),
2098                },
2099            ],
2100            meta: Vec::new(),
2101        };
2102
2103        // Principal types
2104        let tyh = RuntimeType::Array(
2105            Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2106            ArrayLen::Known(4),
2107        );
2108        let tym1 = RuntimeType::Tuple(vec![
2109            RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2110            RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2111        ]);
2112        let tym2 = RuntimeType::Tuple(vec![
2113            RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2114            RuntimeType::Primitive(PrimitiveType::Boolean),
2115        ]);
2116        assert_coerce_results(&hom_arr, &tyh, &hom_arr, &mut exec_state);
2117        assert_coerce_results(&mixed1, &tym1, &mixed1, &mut exec_state);
2118        assert_coerce_results(&mixed2, &tym2, &mixed2, &mut exec_state);
2119        mixed1.coerce(&tym2, true, &mut exec_state).unwrap_err();
2120        mixed2.coerce(&tym1, true, &mut exec_state).unwrap_err();
2121
2122        // Length subtyping
2123        let tyhn = RuntimeType::Array(
2124            Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2125            ArrayLen::None,
2126        );
2127        let tyh1 = RuntimeType::Array(
2128            Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2129            ArrayLen::Minimum(1),
2130        );
2131        let tyh3 = RuntimeType::Array(
2132            Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2133            ArrayLen::Known(3),
2134        );
2135        let tyhm3 = RuntimeType::Array(
2136            Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2137            ArrayLen::Minimum(3),
2138        );
2139        let tyhm5 = RuntimeType::Array(
2140            Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2141            ArrayLen::Minimum(5),
2142        );
2143        assert_coerce_results(&hom_arr, &tyhn, &hom_arr, &mut exec_state);
2144        assert_coerce_results(&hom_arr, &tyh1, &hom_arr, &mut exec_state);
2145        hom_arr.coerce(&tyh3, true, &mut exec_state).unwrap_err();
2146        assert_coerce_results(&hom_arr, &tyhm3, &hom_arr, &mut exec_state);
2147        hom_arr.coerce(&tyhm5, true, &mut exec_state).unwrap_err();
2148
2149        let hom_arr0 = KclValue::HomArray {
2150            value: vec![],
2151            ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2152        };
2153        assert_coerce_results(&hom_arr0, &tyhn, &hom_arr0, &mut exec_state);
2154        hom_arr0.coerce(&tyh1, true, &mut exec_state).unwrap_err();
2155        hom_arr0.coerce(&tyh3, true, &mut exec_state).unwrap_err();
2156
2157        // Covariance
2158        // let tyh = RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any))), ArrayLen::Known(4));
2159        let tym1 = RuntimeType::Tuple(vec![
2160            RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2161            RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2162        ]);
2163        let tym2 = RuntimeType::Tuple(vec![
2164            RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2165            RuntimeType::Primitive(PrimitiveType::Boolean),
2166        ]);
2167        // TODO implement covariance for homogeneous arrays
2168        // assert_coerce_results(&hom_arr, &tyh, &hom_arr, &mut exec_state);
2169        assert_coerce_results(&mixed1, &tym1, &mixed1, &mut exec_state);
2170        assert_coerce_results(&mixed2, &tym2, &mixed2, &mut exec_state);
2171
2172        // Mixed to homogeneous
2173        let hom_arr_2 = KclValue::HomArray {
2174            value: vec![
2175                KclValue::Number {
2176                    value: 0.0,
2177                    ty: NumericType::count(),
2178                    meta: Vec::new(),
2179                },
2180                KclValue::Number {
2181                    value: 1.0,
2182                    ty: NumericType::count(),
2183                    meta: Vec::new(),
2184                },
2185            ],
2186            ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2187        };
2188        let mixed0 = KclValue::Tuple {
2189            value: vec![],
2190            meta: Vec::new(),
2191        };
2192        assert_coerce_results(&mixed1, &tyhn, &hom_arr_2, &mut exec_state);
2193        assert_coerce_results(&mixed1, &tyh1, &hom_arr_2, &mut exec_state);
2194        assert_coerce_results(&mixed0, &tyhn, &hom_arr0, &mut exec_state);
2195        mixed0.coerce(&tyh, true, &mut exec_state).unwrap_err();
2196        mixed0.coerce(&tyh1, true, &mut exec_state).unwrap_err();
2197
2198        // Homogehous to mixed
2199        assert_coerce_results(&hom_arr_2, &tym1, &mixed1, &mut exec_state);
2200        hom_arr.coerce(&tym1, true, &mut exec_state).unwrap_err();
2201        hom_arr_2.coerce(&tym2, true, &mut exec_state).unwrap_err();
2202
2203        mixed0.coerce(&tym1, true, &mut exec_state).unwrap_err();
2204        mixed0.coerce(&tym2, true, &mut exec_state).unwrap_err();
2205        ctx.close().await;
2206    }
2207
2208    #[tokio::test(flavor = "multi_thread")]
2209    async fn coerce_union() {
2210        let (ctx, mut exec_state) = new_exec_state().await;
2211
2212        // Subtyping smaller unions
2213        assert!(RuntimeType::Union(vec![]).subtype(&RuntimeType::Union(vec![
2214            RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2215            RuntimeType::Primitive(PrimitiveType::Boolean)
2216        ])));
2217        assert!(
2218            RuntimeType::Union(vec![RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any))]).subtype(
2219                &RuntimeType::Union(vec![
2220                    RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2221                    RuntimeType::Primitive(PrimitiveType::Boolean)
2222                ])
2223            )
2224        );
2225        assert!(
2226            RuntimeType::Union(vec![
2227                RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2228                RuntimeType::Primitive(PrimitiveType::Boolean)
2229            ])
2230            .subtype(&RuntimeType::Union(vec![
2231                RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2232                RuntimeType::Primitive(PrimitiveType::Boolean)
2233            ]))
2234        );
2235
2236        // Covariance
2237        let count = KclValue::Number {
2238            value: 1.0,
2239            ty: NumericType::count(),
2240            meta: Vec::new(),
2241        };
2242
2243        let tya = RuntimeType::Union(vec![RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any))]);
2244        let tya2 = RuntimeType::Union(vec![
2245            RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2246            RuntimeType::Primitive(PrimitiveType::Boolean),
2247        ]);
2248        assert_coerce_results(&count, &tya, &count, &mut exec_state);
2249        assert_coerce_results(&count, &tya2, &count, &mut exec_state);
2250
2251        // No matching type
2252        let tyb = RuntimeType::Union(vec![RuntimeType::Primitive(PrimitiveType::Boolean)]);
2253        let tyb2 = RuntimeType::Union(vec![
2254            RuntimeType::Primitive(PrimitiveType::Boolean),
2255            RuntimeType::Primitive(PrimitiveType::String),
2256        ]);
2257        count.coerce(&tyb, true, &mut exec_state).unwrap_err();
2258        count.coerce(&tyb2, true, &mut exec_state).unwrap_err();
2259        ctx.close().await;
2260    }
2261
2262    #[tokio::test(flavor = "multi_thread")]
2263    async fn coerce_axes() {
2264        let (ctx, mut exec_state) = new_exec_state().await;
2265
2266        // Subtyping
2267        assert!(RuntimeType::Primitive(PrimitiveType::Axis2d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis2d)));
2268        assert!(RuntimeType::Primitive(PrimitiveType::Axis3d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis3d)));
2269        assert!(!RuntimeType::Primitive(PrimitiveType::Axis3d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis2d)));
2270        assert!(!RuntimeType::Primitive(PrimitiveType::Axis2d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis3d)));
2271
2272        // Coercion
2273        let a2d = KclValue::Object {
2274            value: [
2275                (
2276                    "origin".to_owned(),
2277                    KclValue::HomArray {
2278                        value: vec![
2279                            KclValue::Number {
2280                                value: 0.0,
2281                                ty: NumericType::mm(),
2282                                meta: Vec::new(),
2283                            },
2284                            KclValue::Number {
2285                                value: 0.0,
2286                                ty: NumericType::mm(),
2287                                meta: Vec::new(),
2288                            },
2289                        ],
2290                        ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
2291                    },
2292                ),
2293                (
2294                    "direction".to_owned(),
2295                    KclValue::HomArray {
2296                        value: vec![
2297                            KclValue::Number {
2298                                value: 1.0,
2299                                ty: NumericType::mm(),
2300                                meta: Vec::new(),
2301                            },
2302                            KclValue::Number {
2303                                value: 0.0,
2304                                ty: NumericType::mm(),
2305                                meta: Vec::new(),
2306                            },
2307                        ],
2308                        ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
2309                    },
2310                ),
2311            ]
2312            .into(),
2313            meta: Vec::new(),
2314            constrainable: false,
2315        };
2316        let a3d = KclValue::Object {
2317            value: [
2318                (
2319                    "origin".to_owned(),
2320                    KclValue::HomArray {
2321                        value: vec![
2322                            KclValue::Number {
2323                                value: 0.0,
2324                                ty: NumericType::mm(),
2325                                meta: Vec::new(),
2326                            },
2327                            KclValue::Number {
2328                                value: 0.0,
2329                                ty: NumericType::mm(),
2330                                meta: Vec::new(),
2331                            },
2332                            KclValue::Number {
2333                                value: 0.0,
2334                                ty: NumericType::mm(),
2335                                meta: Vec::new(),
2336                            },
2337                        ],
2338                        ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
2339                    },
2340                ),
2341                (
2342                    "direction".to_owned(),
2343                    KclValue::HomArray {
2344                        value: vec![
2345                            KclValue::Number {
2346                                value: 1.0,
2347                                ty: NumericType::mm(),
2348                                meta: Vec::new(),
2349                            },
2350                            KclValue::Number {
2351                                value: 0.0,
2352                                ty: NumericType::mm(),
2353                                meta: Vec::new(),
2354                            },
2355                            KclValue::Number {
2356                                value: 1.0,
2357                                ty: NumericType::mm(),
2358                                meta: Vec::new(),
2359                            },
2360                        ],
2361                        ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
2362                    },
2363                ),
2364            ]
2365            .into(),
2366            meta: Vec::new(),
2367            constrainable: false,
2368        };
2369
2370        let ty2d = RuntimeType::Primitive(PrimitiveType::Axis2d);
2371        let ty3d = RuntimeType::Primitive(PrimitiveType::Axis3d);
2372
2373        assert_coerce_results(&a2d, &ty2d, &a2d, &mut exec_state);
2374        assert_coerce_results(&a3d, &ty3d, &a3d, &mut exec_state);
2375        assert_coerce_results(&a3d, &ty2d, &a2d, &mut exec_state);
2376        a2d.coerce(&ty3d, true, &mut exec_state).unwrap_err();
2377        ctx.close().await;
2378    }
2379
2380    #[tokio::test(flavor = "multi_thread")]
2381    async fn coerce_numeric() {
2382        let (ctx, mut exec_state) = new_exec_state().await;
2383
2384        let count = KclValue::Number {
2385            value: 1.0,
2386            ty: NumericType::count(),
2387            meta: Vec::new(),
2388        };
2389        let mm = KclValue::Number {
2390            value: 1.0,
2391            ty: NumericType::mm(),
2392            meta: Vec::new(),
2393        };
2394        let inches = KclValue::Number {
2395            value: 1.0,
2396            ty: NumericType::Known(UnitType::Length(UnitLength::Inches)),
2397            meta: Vec::new(),
2398        };
2399        let rads = KclValue::Number {
2400            value: 1.0,
2401            ty: NumericType::Known(UnitType::Angle(UnitAngle::Radians)),
2402            meta: Vec::new(),
2403        };
2404        let default = KclValue::Number {
2405            value: 1.0,
2406            ty: NumericType::default(),
2407            meta: Vec::new(),
2408        };
2409        let any = KclValue::Number {
2410            value: 1.0,
2411            ty: NumericType::Any,
2412            meta: Vec::new(),
2413        };
2414        let unknown = KclValue::Number {
2415            value: 1.0,
2416            ty: NumericType::Unknown,
2417            meta: Vec::new(),
2418        };
2419
2420        // Trivial coercions
2421        assert_coerce_results(&count, &NumericType::count().into(), &count, &mut exec_state);
2422        assert_coerce_results(&mm, &NumericType::mm().into(), &mm, &mut exec_state);
2423        assert_coerce_results(&any, &NumericType::Any.into(), &any, &mut exec_state);
2424        assert_coerce_results(&unknown, &NumericType::Unknown.into(), &unknown, &mut exec_state);
2425        assert_coerce_results(&default, &NumericType::default().into(), &default, &mut exec_state);
2426
2427        assert_coerce_results(&count, &NumericType::Any.into(), &count, &mut exec_state);
2428        assert_coerce_results(&mm, &NumericType::Any.into(), &mm, &mut exec_state);
2429        assert_coerce_results(&unknown, &NumericType::Any.into(), &unknown, &mut exec_state);
2430        assert_coerce_results(&default, &NumericType::Any.into(), &default, &mut exec_state);
2431
2432        assert_eq!(
2433            default
2434                .coerce(
2435                    &NumericType::Default {
2436                        len: UnitLength::Yards,
2437                        angle: UnitAngle::Degrees,
2438                    }
2439                    .into(),
2440                    true,
2441                    &mut exec_state
2442                )
2443                .unwrap(),
2444            default
2445        );
2446
2447        // No coercion
2448        count
2449            .coerce(&NumericType::mm().into(), true, &mut exec_state)
2450            .unwrap_err();
2451        mm.coerce(&NumericType::count().into(), true, &mut exec_state)
2452            .unwrap_err();
2453        unknown
2454            .coerce(&NumericType::mm().into(), true, &mut exec_state)
2455            .unwrap_err();
2456        unknown
2457            .coerce(&NumericType::default().into(), true, &mut exec_state)
2458            .unwrap_err();
2459
2460        count
2461            .coerce(&NumericType::Unknown.into(), true, &mut exec_state)
2462            .unwrap_err();
2463        mm.coerce(&NumericType::Unknown.into(), true, &mut exec_state)
2464            .unwrap_err();
2465        default
2466            .coerce(&NumericType::Unknown.into(), true, &mut exec_state)
2467            .unwrap_err();
2468
2469        assert_eq!(
2470            inches
2471                .coerce(&NumericType::mm().into(), true, &mut exec_state)
2472                .unwrap()
2473                .as_f64()
2474                .unwrap()
2475                .round(),
2476            25.0
2477        );
2478        assert_eq!(
2479            rads.coerce(
2480                &NumericType::Known(UnitType::Angle(UnitAngle::Degrees)).into(),
2481                true,
2482                &mut exec_state
2483            )
2484            .unwrap()
2485            .as_f64()
2486            .unwrap()
2487            .round(),
2488            57.0
2489        );
2490        assert_eq!(
2491            inches
2492                .coerce(&NumericType::default().into(), true, &mut exec_state)
2493                .unwrap()
2494                .as_f64()
2495                .unwrap()
2496                .round(),
2497            1.0
2498        );
2499        assert_eq!(
2500            rads.coerce(&NumericType::default().into(), true, &mut exec_state)
2501                .unwrap()
2502                .as_f64()
2503                .unwrap()
2504                .round(),
2505            1.0
2506        );
2507        ctx.close().await;
2508    }
2509
2510    #[track_caller]
2511    fn assert_value_and_type(name: &str, result: &ExecTestResults, expected: f64, expected_ty: NumericType) {
2512        let mem = result.exec_state.stack();
2513        match mem
2514            .memory
2515            .get_from(name, result.mem_env, SourceRange::default(), 0)
2516            .unwrap()
2517        {
2518            KclValue::Number { value, ty, .. } => {
2519                assert_eq!(value.round(), expected);
2520                assert_eq!(*ty, expected_ty);
2521            }
2522            _ => unreachable!(),
2523        }
2524    }
2525
2526    #[tokio::test(flavor = "multi_thread")]
2527    async fn combine_numeric() {
2528        let program = r#"a = 5 + 4
2529b = 5 - 2
2530c = 5mm - 2mm + 10mm
2531d = 5mm - 2 + 10
2532e = 5 - 2mm + 10
2533f = 30mm - 1inch
2534
2535g = 2 * 10
2536h = 2 * 10mm
2537i = 2mm * 10mm
2538j = 2_ * 10
2539k = 2_ * 3mm * 3mm
2540
2541l = 1 / 10
2542m = 2mm / 1mm
2543n = 10inch / 2mm
2544o = 3mm / 3
2545p = 3_ / 4
2546q = 4inch / 2_
2547
2548r = min([0, 3, 42])
2549s = min([0, 3mm, -42])
2550t = min([100, 3in, 142mm])
2551u = min([3rad, 4in])
2552"#;
2553
2554        let result = parse_execute(program).await.unwrap();
2555        assert_eq!(
2556            result.exec_state.errors().len(),
2557            5,
2558            "errors: {:?}",
2559            result.exec_state.errors()
2560        );
2561
2562        assert_value_and_type("a", &result, 9.0, NumericType::default());
2563        assert_value_and_type("b", &result, 3.0, NumericType::default());
2564        assert_value_and_type("c", &result, 13.0, NumericType::mm());
2565        assert_value_and_type("d", &result, 13.0, NumericType::mm());
2566        assert_value_and_type("e", &result, 13.0, NumericType::mm());
2567        assert_value_and_type("f", &result, 5.0, NumericType::mm());
2568
2569        assert_value_and_type("g", &result, 20.0, NumericType::default());
2570        assert_value_and_type("h", &result, 20.0, NumericType::mm());
2571        assert_value_and_type("i", &result, 20.0, NumericType::Unknown);
2572        assert_value_and_type("j", &result, 20.0, NumericType::default());
2573        assert_value_and_type("k", &result, 18.0, NumericType::Unknown);
2574
2575        assert_value_and_type("l", &result, 0.0, NumericType::default());
2576        assert_value_and_type("m", &result, 2.0, NumericType::count());
2577        assert_value_and_type("n", &result, 5.0, NumericType::Unknown);
2578        assert_value_and_type("o", &result, 1.0, NumericType::mm());
2579        assert_value_and_type("p", &result, 1.0, NumericType::count());
2580        assert_value_and_type(
2581            "q",
2582            &result,
2583            2.0,
2584            NumericType::Known(UnitType::Length(UnitLength::Inches)),
2585        );
2586
2587        assert_value_and_type("r", &result, 0.0, NumericType::default());
2588        assert_value_and_type("s", &result, -42.0, NumericType::mm());
2589        assert_value_and_type("t", &result, 3.0, NumericType::Unknown);
2590        assert_value_and_type("u", &result, 3.0, NumericType::Unknown);
2591    }
2592
2593    #[tokio::test(flavor = "multi_thread")]
2594    async fn bad_typed_arithmetic() {
2595        let program = r#"
2596a = 1rad
2597b = 180 / PI * a + 360
2598"#;
2599
2600        let result = parse_execute(program).await.unwrap();
2601
2602        assert_value_and_type("a", &result, 1.0, NumericType::radians());
2603        assert_value_and_type("b", &result, 417.0, NumericType::Unknown);
2604    }
2605
2606    #[tokio::test(flavor = "multi_thread")]
2607    async fn cos_coercions() {
2608        let program = r#"
2609a = cos(units::toRadians(30deg))
2610b = 3 / a
2611c = cos(30deg)
2612d = cos(1rad)
2613"#;
2614
2615        let result = parse_execute(program).await.unwrap();
2616        assert!(
2617            result.exec_state.errors().is_empty(),
2618            "{:?}",
2619            result.exec_state.errors()
2620        );
2621
2622        assert_value_and_type("a", &result, 1.0, NumericType::default());
2623        assert_value_and_type("b", &result, 3.0, NumericType::default());
2624        assert_value_and_type("c", &result, 1.0, NumericType::default());
2625        assert_value_and_type("d", &result, 1.0, NumericType::default());
2626    }
2627
2628    #[tokio::test(flavor = "multi_thread")]
2629    async fn coerce_nested_array() {
2630        let (ctx, mut exec_state) = new_exec_state().await;
2631
2632        let mixed1 = KclValue::HomArray {
2633            value: vec![
2634                KclValue::Number {
2635                    value: 0.0,
2636                    ty: NumericType::count(),
2637                    meta: Vec::new(),
2638                },
2639                KclValue::Number {
2640                    value: 1.0,
2641                    ty: NumericType::count(),
2642                    meta: Vec::new(),
2643                },
2644                KclValue::HomArray {
2645                    value: vec![
2646                        KclValue::Number {
2647                            value: 2.0,
2648                            ty: NumericType::count(),
2649                            meta: Vec::new(),
2650                        },
2651                        KclValue::Number {
2652                            value: 3.0,
2653                            ty: NumericType::count(),
2654                            meta: Vec::new(),
2655                        },
2656                    ],
2657                    ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2658                },
2659            ],
2660            ty: RuntimeType::any(),
2661        };
2662
2663        // Principal types
2664        let tym1 = RuntimeType::Array(
2665            Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2666            ArrayLen::Minimum(1),
2667        );
2668
2669        let result = KclValue::HomArray {
2670            value: vec![
2671                KclValue::Number {
2672                    value: 0.0,
2673                    ty: NumericType::count(),
2674                    meta: Vec::new(),
2675                },
2676                KclValue::Number {
2677                    value: 1.0,
2678                    ty: NumericType::count(),
2679                    meta: Vec::new(),
2680                },
2681                KclValue::Number {
2682                    value: 2.0,
2683                    ty: NumericType::count(),
2684                    meta: Vec::new(),
2685                },
2686                KclValue::Number {
2687                    value: 3.0,
2688                    ty: NumericType::count(),
2689                    meta: Vec::new(),
2690                },
2691            ],
2692            ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2693        };
2694        assert_coerce_results(&mixed1, &tym1, &result, &mut exec_state);
2695        ctx.close().await;
2696    }
2697}