kcl_lib/execution/
types.rs

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