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, annotations,
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 => {
1169                match self {
1170                    KclValue::String { value: s, .. }
1171                        if [
1172                            "xy", "xz", "yz", "-xy", "-xz", "-yz", "XY", "XZ", "YZ", "-XY", "-XZ", "-YZ",
1173                        ]
1174                        .contains(&&**s) =>
1175                    {
1176                        Ok(self.clone())
1177                    }
1178                    KclValue::Plane { .. } => Ok(self.clone()),
1179                    KclValue::Object { value, meta } => {
1180                        let origin = value
1181                            .get("origin")
1182                            .and_then(Point3d::from_kcl_val)
1183                            .ok_or(CoercionError::from(self))?;
1184                        let x_axis = value
1185                            .get("xAxis")
1186                            .and_then(Point3d::from_kcl_val)
1187                            .ok_or(CoercionError::from(self))?;
1188                        let y_axis = value
1189                            .get("yAxis")
1190                            .and_then(Point3d::from_kcl_val)
1191                            .ok_or(CoercionError::from(self))?;
1192                        let z_axis = x_axis.axes_cross_product(&y_axis);
1193
1194                        if value.get("zAxis").is_some() {
1195                            exec_state.warn(CompilationError::err(
1196                            self.into(),
1197                            "Object with a zAxis field is being coerced into a plane, but the zAxis is ignored.",
1198                        ), annotations::WARN_IGNORED_Z_AXIS);
1199                        }
1200
1201                        let id = exec_state.mod_local.id_generator.next_uuid();
1202                        let plane = Plane {
1203                            id,
1204                            artifact_id: id.into(),
1205                            info: PlaneInfo {
1206                                origin,
1207                                x_axis: x_axis.normalize(),
1208                                y_axis: y_axis.normalize(),
1209                                z_axis: z_axis.normalize(),
1210                            },
1211                            value: super::PlaneType::Uninit,
1212                            meta: meta.clone(),
1213                        };
1214
1215                        Ok(KclValue::Plane { value: Box::new(plane) })
1216                    }
1217                    _ => Err(self.into()),
1218                }
1219            }
1220            PrimitiveType::Face => match self {
1221                KclValue::Face { .. } => Ok(self.clone()),
1222                _ => Err(self.into()),
1223            },
1224            PrimitiveType::Helix => match self {
1225                KclValue::Helix { .. } => Ok(self.clone()),
1226                _ => Err(self.into()),
1227            },
1228            PrimitiveType::Edge => match self {
1229                KclValue::Uuid { .. } => Ok(self.clone()),
1230                KclValue::TagIdentifier { .. } => Ok(self.clone()),
1231                _ => Err(self.into()),
1232            },
1233            PrimitiveType::TaggedEdge => match self {
1234                KclValue::TagIdentifier { .. } => Ok(self.clone()),
1235                _ => Err(self.into()),
1236            },
1237            PrimitiveType::TaggedFace => match self {
1238                KclValue::TagIdentifier { .. } => Ok(self.clone()),
1239                s @ KclValue::String { value, .. } if ["start", "end", "START", "END"].contains(&&**value) => {
1240                    Ok(s.clone())
1241                }
1242                _ => Err(self.into()),
1243            },
1244            PrimitiveType::Axis2d => match self {
1245                KclValue::Object { value: values, meta } => {
1246                    if values
1247                        .get("origin")
1248                        .ok_or(CoercionError::from(self))?
1249                        .has_type(&RuntimeType::point2d())
1250                        && values
1251                            .get("direction")
1252                            .ok_or(CoercionError::from(self))?
1253                            .has_type(&RuntimeType::point2d())
1254                    {
1255                        return Ok(self.clone());
1256                    }
1257
1258                    let origin = values.get("origin").ok_or(self.into()).and_then(|p| {
1259                        p.coerce_to_array_type(
1260                            &RuntimeType::length(),
1261                            convert_units,
1262                            ArrayLen::Known(2),
1263                            exec_state,
1264                            true,
1265                        )
1266                    })?;
1267                    let direction = values.get("direction").ok_or(self.into()).and_then(|p| {
1268                        p.coerce_to_array_type(
1269                            &RuntimeType::length(),
1270                            convert_units,
1271                            ArrayLen::Known(2),
1272                            exec_state,
1273                            true,
1274                        )
1275                    })?;
1276
1277                    Ok(KclValue::Object {
1278                        value: [("origin".to_owned(), origin), ("direction".to_owned(), direction)].into(),
1279                        meta: meta.clone(),
1280                    })
1281                }
1282                _ => Err(self.into()),
1283            },
1284            PrimitiveType::Axis3d => match self {
1285                KclValue::Object { value: values, meta } => {
1286                    if values
1287                        .get("origin")
1288                        .ok_or(CoercionError::from(self))?
1289                        .has_type(&RuntimeType::point3d())
1290                        && values
1291                            .get("direction")
1292                            .ok_or(CoercionError::from(self))?
1293                            .has_type(&RuntimeType::point3d())
1294                    {
1295                        return Ok(self.clone());
1296                    }
1297
1298                    let origin = values.get("origin").ok_or(self.into()).and_then(|p| {
1299                        p.coerce_to_array_type(
1300                            &RuntimeType::length(),
1301                            convert_units,
1302                            ArrayLen::Known(3),
1303                            exec_state,
1304                            true,
1305                        )
1306                    })?;
1307                    let direction = values.get("direction").ok_or(self.into()).and_then(|p| {
1308                        p.coerce_to_array_type(
1309                            &RuntimeType::length(),
1310                            convert_units,
1311                            ArrayLen::Known(3),
1312                            exec_state,
1313                            true,
1314                        )
1315                    })?;
1316
1317                    Ok(KclValue::Object {
1318                        value: [("origin".to_owned(), origin), ("direction".to_owned(), direction)].into(),
1319                        meta: meta.clone(),
1320                    })
1321                }
1322                _ => Err(self.into()),
1323            },
1324            PrimitiveType::ImportedGeometry => match self {
1325                KclValue::ImportedGeometry { .. } => Ok(self.clone()),
1326                _ => Err(self.into()),
1327            },
1328            PrimitiveType::Function => match self {
1329                KclValue::Function { .. } => Ok(self.clone()),
1330                _ => Err(self.into()),
1331            },
1332            PrimitiveType::TagDecl => match self {
1333                KclValue::TagDeclarator { .. } => Ok(self.clone()),
1334                _ => Err(self.into()),
1335            },
1336        }
1337    }
1338
1339    fn coerce_to_array_type(
1340        &self,
1341        ty: &RuntimeType,
1342        convert_units: bool,
1343        len: ArrayLen,
1344        exec_state: &mut ExecState,
1345        allow_shrink: bool,
1346    ) -> Result<KclValue, CoercionError> {
1347        match self {
1348            KclValue::HomArray { value, ty: aty, .. } => {
1349                let satisfied_len = len.satisfied(value.len(), allow_shrink);
1350
1351                if aty.subtype(ty) {
1352                    // If the element type is a subtype of the target type and
1353                    // the length constraint is satisfied, we can just return
1354                    // the values unchanged, only adjusting the length. The new
1355                    // array element type should preserve its type because the
1356                    // target type oftentimes includes an unknown type as a way
1357                    // to say that the caller doesn't care.
1358                    return satisfied_len
1359                        .map(|len| KclValue::HomArray {
1360                            value: value[..len].to_vec(),
1361                            ty: aty.clone(),
1362                        })
1363                        .ok_or(self.into());
1364                }
1365
1366                // Ignore the array type, and coerce the elements of the array.
1367                if let Some(satisfied_len) = satisfied_len {
1368                    let value_result = value
1369                        .iter()
1370                        .take(satisfied_len)
1371                        .map(|v| v.coerce(ty, convert_units, exec_state))
1372                        .collect::<Result<Vec<_>, _>>();
1373
1374                    if let Ok(value) = value_result {
1375                        // We were able to coerce all the elements.
1376                        return Ok(KclValue::HomArray { value, ty: ty.clone() });
1377                    }
1378                }
1379
1380                // As a last resort, try to flatten the array.
1381                let mut values = Vec::new();
1382                for item in value {
1383                    if let KclValue::HomArray { value: inner_value, .. } = item {
1384                        // Flatten elements.
1385                        for item in inner_value {
1386                            values.push(item.coerce(ty, convert_units, exec_state)?);
1387                        }
1388                    } else {
1389                        values.push(item.coerce(ty, convert_units, exec_state)?);
1390                    }
1391                }
1392
1393                let len = len
1394                    .satisfied(values.len(), allow_shrink)
1395                    .ok_or(CoercionError::from(self))?;
1396
1397                if len > values.len() {
1398                    let message = format!(
1399                        "Internal: Expected coerced array length {len} to be less than or equal to original length {}",
1400                        values.len()
1401                    );
1402                    exec_state.err(CompilationError::err(self.into(), message.clone()));
1403                    #[cfg(debug_assertions)]
1404                    panic!("{message}");
1405                }
1406                values.truncate(len);
1407
1408                Ok(KclValue::HomArray {
1409                    value: values,
1410                    ty: ty.clone(),
1411                })
1412            }
1413            KclValue::Tuple { value, .. } => {
1414                let len = len
1415                    .satisfied(value.len(), allow_shrink)
1416                    .ok_or(CoercionError::from(self))?;
1417                let value = value
1418                    .iter()
1419                    .map(|item| item.coerce(ty, convert_units, exec_state))
1420                    .take(len)
1421                    .collect::<Result<Vec<_>, _>>()?;
1422
1423                Ok(KclValue::HomArray { value, ty: ty.clone() })
1424            }
1425            KclValue::KclNone { .. } if len.satisfied(0, false).is_some() => Ok(KclValue::HomArray {
1426                value: Vec::new(),
1427                ty: ty.clone(),
1428            }),
1429            _ if len.satisfied(1, false).is_some() => self.coerce(ty, convert_units, exec_state),
1430            _ => Err(self.into()),
1431        }
1432    }
1433
1434    fn coerce_to_tuple_type(
1435        &self,
1436        tys: &[RuntimeType],
1437        convert_units: bool,
1438        exec_state: &mut ExecState,
1439    ) -> Result<KclValue, CoercionError> {
1440        match self {
1441            KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } if value.len() == tys.len() => {
1442                let mut result = Vec::new();
1443                for (i, t) in tys.iter().enumerate() {
1444                    result.push(value[i].coerce(t, convert_units, exec_state)?);
1445                }
1446
1447                Ok(KclValue::Tuple {
1448                    value: result,
1449                    meta: Vec::new(),
1450                })
1451            }
1452            KclValue::KclNone { meta, .. } if tys.is_empty() => Ok(KclValue::Tuple {
1453                value: Vec::new(),
1454                meta: meta.clone(),
1455            }),
1456            _ if tys.len() == 1 => self.coerce(&tys[0], convert_units, exec_state),
1457            _ => Err(self.into()),
1458        }
1459    }
1460
1461    fn coerce_to_union_type(
1462        &self,
1463        tys: &[RuntimeType],
1464        convert_units: bool,
1465        exec_state: &mut ExecState,
1466    ) -> Result<KclValue, CoercionError> {
1467        for t in tys {
1468            if let Ok(v) = self.coerce(t, convert_units, exec_state) {
1469                return Ok(v);
1470            }
1471        }
1472
1473        Err(self.into())
1474    }
1475
1476    fn coerce_to_object_type(
1477        &self,
1478        tys: &[(String, RuntimeType)],
1479        _convert_units: bool,
1480        _exec_state: &mut ExecState,
1481    ) -> Result<KclValue, CoercionError> {
1482        match self {
1483            KclValue::Object { value, .. } => {
1484                for (s, t) in tys {
1485                    // TODO coerce fields
1486                    if !value.get(s).ok_or(CoercionError::from(self))?.has_type(t) {
1487                        return Err(self.into());
1488                    }
1489                }
1490                // TODO remove non-required fields
1491                Ok(self.clone())
1492            }
1493            KclValue::KclNone { meta, .. } if tys.is_empty() => Ok(KclValue::Object {
1494                value: HashMap::new(),
1495                meta: meta.clone(),
1496            }),
1497            _ => Err(self.into()),
1498        }
1499    }
1500
1501    pub fn principal_type(&self) -> Option<RuntimeType> {
1502        match self {
1503            KclValue::Bool { .. } => Some(RuntimeType::Primitive(PrimitiveType::Boolean)),
1504            KclValue::Number { ty, .. } => Some(RuntimeType::Primitive(PrimitiveType::Number(*ty))),
1505            KclValue::String { .. } => Some(RuntimeType::Primitive(PrimitiveType::String)),
1506            KclValue::Object { value, .. } => {
1507                let properties = value
1508                    .iter()
1509                    .map(|(k, v)| v.principal_type().map(|t| (k.clone(), t)))
1510                    .collect::<Option<Vec<_>>>()?;
1511                Some(RuntimeType::Object(properties))
1512            }
1513            KclValue::Plane { .. } => Some(RuntimeType::Primitive(PrimitiveType::Plane)),
1514            KclValue::Sketch { .. } => Some(RuntimeType::Primitive(PrimitiveType::Sketch)),
1515            KclValue::Solid { .. } => Some(RuntimeType::Primitive(PrimitiveType::Solid)),
1516            KclValue::Face { .. } => Some(RuntimeType::Primitive(PrimitiveType::Face)),
1517            KclValue::Helix { .. } => Some(RuntimeType::Primitive(PrimitiveType::Helix)),
1518            KclValue::ImportedGeometry(..) => Some(RuntimeType::Primitive(PrimitiveType::ImportedGeometry)),
1519            KclValue::Tuple { value, .. } => Some(RuntimeType::Tuple(
1520                value.iter().map(|v| v.principal_type()).collect::<Option<Vec<_>>>()?,
1521            )),
1522            KclValue::HomArray { ty, value, .. } => {
1523                Some(RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Known(value.len())))
1524            }
1525            KclValue::TagIdentifier(_) => Some(RuntimeType::Primitive(PrimitiveType::TaggedEdge)),
1526            KclValue::TagDeclarator(_) => Some(RuntimeType::Primitive(PrimitiveType::TagDecl)),
1527            KclValue::Uuid { .. } => Some(RuntimeType::Primitive(PrimitiveType::Edge)),
1528            KclValue::Function { .. } => Some(RuntimeType::Primitive(PrimitiveType::Function)),
1529            KclValue::Module { .. } | KclValue::KclNone { .. } | KclValue::Type { .. } => None,
1530        }
1531    }
1532
1533    pub fn principal_type_string(&self) -> String {
1534        if let Some(ty) = self.principal_type() {
1535            return format!("`{ty}`");
1536        }
1537
1538        match self {
1539            KclValue::Module { .. } => "module",
1540            KclValue::KclNone { .. } => "none",
1541            KclValue::Type { .. } => "type",
1542            _ => {
1543                debug_assert!(false);
1544                "<unexpected type>"
1545            }
1546        }
1547        .to_owned()
1548    }
1549}
1550
1551#[cfg(test)]
1552mod test {
1553    use super::*;
1554    use crate::execution::{ExecTestResults, parse_execute};
1555
1556    fn values(exec_state: &mut ExecState) -> Vec<KclValue> {
1557        vec![
1558            KclValue::Bool {
1559                value: true,
1560                meta: Vec::new(),
1561            },
1562            KclValue::Number {
1563                value: 1.0,
1564                ty: NumericType::count(),
1565                meta: Vec::new(),
1566            },
1567            KclValue::String {
1568                value: "hello".to_owned(),
1569                meta: Vec::new(),
1570            },
1571            KclValue::Tuple {
1572                value: Vec::new(),
1573                meta: Vec::new(),
1574            },
1575            KclValue::HomArray {
1576                value: Vec::new(),
1577                ty: RuntimeType::solid(),
1578            },
1579            KclValue::Object {
1580                value: crate::execution::KclObjectFields::new(),
1581                meta: Vec::new(),
1582            },
1583            KclValue::TagIdentifier(Box::new("foo".parse().unwrap())),
1584            KclValue::TagDeclarator(Box::new(crate::parsing::ast::types::TagDeclarator::new("foo"))),
1585            KclValue::Plane {
1586                value: Box::new(Plane::from_plane_data(crate::std::sketch::PlaneData::XY, exec_state).unwrap()),
1587            },
1588            // No easy way to make a Face, Sketch, Solid, or Helix
1589            KclValue::ImportedGeometry(crate::execution::ImportedGeometry::new(
1590                uuid::Uuid::nil(),
1591                Vec::new(),
1592                Vec::new(),
1593            )),
1594            // Other values don't have types
1595        ]
1596    }
1597
1598    #[track_caller]
1599    fn assert_coerce_results(
1600        value: &KclValue,
1601        super_type: &RuntimeType,
1602        expected_value: &KclValue,
1603        exec_state: &mut ExecState,
1604    ) {
1605        let is_subtype = value == expected_value;
1606        let actual = value.coerce(super_type, true, exec_state).unwrap();
1607        assert_eq!(&actual, expected_value);
1608        assert_eq!(
1609            is_subtype,
1610            value.principal_type().is_some() && value.principal_type().unwrap().subtype(super_type),
1611            "{:?} <: {super_type:?} should be {is_subtype}",
1612            value.principal_type().unwrap()
1613        );
1614        assert!(
1615            expected_value.principal_type().unwrap().subtype(super_type),
1616            "{} <: {super_type}",
1617            expected_value.principal_type().unwrap()
1618        )
1619    }
1620
1621    #[tokio::test(flavor = "multi_thread")]
1622    async fn coerce_idempotent() {
1623        let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock(None).await);
1624        let values = values(&mut exec_state);
1625        for v in &values {
1626            // Identity subtype
1627            let ty = v.principal_type().unwrap();
1628            assert_coerce_results(v, &ty, v, &mut exec_state);
1629
1630            // Union subtype
1631            let uty1 = RuntimeType::Union(vec![ty.clone()]);
1632            let uty2 = RuntimeType::Union(vec![ty.clone(), RuntimeType::Primitive(PrimitiveType::Boolean)]);
1633            assert_coerce_results(v, &uty1, v, &mut exec_state);
1634            assert_coerce_results(v, &uty2, v, &mut exec_state);
1635
1636            // Array subtypes
1637            let aty = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::None);
1638            let aty1 = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Known(1));
1639            let aty0 = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Minimum(1));
1640
1641            match v {
1642                KclValue::HomArray { .. } => {
1643                    // These will not get wrapped if possible.
1644                    assert_coerce_results(
1645                        v,
1646                        &aty,
1647                        &KclValue::HomArray {
1648                            value: vec![],
1649                            ty: ty.clone(),
1650                        },
1651                        &mut exec_state,
1652                    );
1653                    // Coercing an empty array to an array of length 1
1654                    // should fail.
1655                    v.coerce(&aty1, true, &mut exec_state).unwrap_err();
1656                    // Coercing an empty array to an array that's
1657                    // non-empty should fail.
1658                    v.coerce(&aty0, true, &mut exec_state).unwrap_err();
1659                }
1660                KclValue::Tuple { .. } => {}
1661                _ => {
1662                    assert_coerce_results(v, &aty, v, &mut exec_state);
1663                    assert_coerce_results(v, &aty1, v, &mut exec_state);
1664                    assert_coerce_results(v, &aty0, v, &mut exec_state);
1665
1666                    // Tuple subtype
1667                    let tty = RuntimeType::Tuple(vec![ty.clone()]);
1668                    assert_coerce_results(v, &tty, v, &mut exec_state);
1669                }
1670            }
1671        }
1672
1673        for v in &values[1..] {
1674            // Not a subtype
1675            v.coerce(&RuntimeType::Primitive(PrimitiveType::Boolean), true, &mut exec_state)
1676                .unwrap_err();
1677        }
1678    }
1679
1680    #[tokio::test(flavor = "multi_thread")]
1681    async fn coerce_none() {
1682        let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock(None).await);
1683        let none = KclValue::KclNone {
1684            value: crate::parsing::ast::types::KclNone::new(),
1685            meta: Vec::new(),
1686        };
1687
1688        let aty = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::None);
1689        let aty0 = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::Known(0));
1690        let aty1 = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::Known(1));
1691        let aty1p = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::Minimum(1));
1692        assert_coerce_results(
1693            &none,
1694            &aty,
1695            &KclValue::HomArray {
1696                value: Vec::new(),
1697                ty: RuntimeType::solid(),
1698            },
1699            &mut exec_state,
1700        );
1701        assert_coerce_results(
1702            &none,
1703            &aty0,
1704            &KclValue::HomArray {
1705                value: Vec::new(),
1706                ty: RuntimeType::solid(),
1707            },
1708            &mut exec_state,
1709        );
1710        none.coerce(&aty1, true, &mut exec_state).unwrap_err();
1711        none.coerce(&aty1p, true, &mut exec_state).unwrap_err();
1712
1713        let tty = RuntimeType::Tuple(vec![]);
1714        let tty1 = RuntimeType::Tuple(vec![RuntimeType::solid()]);
1715        assert_coerce_results(
1716            &none,
1717            &tty,
1718            &KclValue::Tuple {
1719                value: Vec::new(),
1720                meta: Vec::new(),
1721            },
1722            &mut exec_state,
1723        );
1724        none.coerce(&tty1, true, &mut exec_state).unwrap_err();
1725
1726        let oty = RuntimeType::Object(vec![]);
1727        assert_coerce_results(
1728            &none,
1729            &oty,
1730            &KclValue::Object {
1731                value: HashMap::new(),
1732                meta: Vec::new(),
1733            },
1734            &mut exec_state,
1735        );
1736    }
1737
1738    #[tokio::test(flavor = "multi_thread")]
1739    async fn coerce_record() {
1740        let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock(None).await);
1741
1742        let obj0 = KclValue::Object {
1743            value: HashMap::new(),
1744            meta: Vec::new(),
1745        };
1746        let obj1 = KclValue::Object {
1747            value: [(
1748                "foo".to_owned(),
1749                KclValue::Bool {
1750                    value: true,
1751                    meta: Vec::new(),
1752                },
1753            )]
1754            .into(),
1755            meta: Vec::new(),
1756        };
1757        let obj2 = KclValue::Object {
1758            value: [
1759                (
1760                    "foo".to_owned(),
1761                    KclValue::Bool {
1762                        value: true,
1763                        meta: Vec::new(),
1764                    },
1765                ),
1766                (
1767                    "bar".to_owned(),
1768                    KclValue::Number {
1769                        value: 0.0,
1770                        ty: NumericType::count(),
1771                        meta: Vec::new(),
1772                    },
1773                ),
1774                (
1775                    "baz".to_owned(),
1776                    KclValue::Number {
1777                        value: 42.0,
1778                        ty: NumericType::count(),
1779                        meta: Vec::new(),
1780                    },
1781                ),
1782            ]
1783            .into(),
1784            meta: Vec::new(),
1785        };
1786
1787        let ty0 = RuntimeType::Object(vec![]);
1788        assert_coerce_results(&obj0, &ty0, &obj0, &mut exec_state);
1789        assert_coerce_results(&obj1, &ty0, &obj1, &mut exec_state);
1790        assert_coerce_results(&obj2, &ty0, &obj2, &mut exec_state);
1791
1792        let ty1 = RuntimeType::Object(vec![("foo".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean))]);
1793        obj0.coerce(&ty1, true, &mut exec_state).unwrap_err();
1794        assert_coerce_results(&obj1, &ty1, &obj1, &mut exec_state);
1795        assert_coerce_results(&obj2, &ty1, &obj2, &mut exec_state);
1796
1797        // Different ordering, (TODO - test for covariance once implemented)
1798        let ty2 = RuntimeType::Object(vec![
1799            (
1800                "bar".to_owned(),
1801                RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1802            ),
1803            ("foo".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean)),
1804        ]);
1805        obj0.coerce(&ty2, true, &mut exec_state).unwrap_err();
1806        obj1.coerce(&ty2, true, &mut exec_state).unwrap_err();
1807        assert_coerce_results(&obj2, &ty2, &obj2, &mut exec_state);
1808
1809        // field not present
1810        let tyq = RuntimeType::Object(vec![("qux".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean))]);
1811        obj0.coerce(&tyq, true, &mut exec_state).unwrap_err();
1812        obj1.coerce(&tyq, true, &mut exec_state).unwrap_err();
1813        obj2.coerce(&tyq, true, &mut exec_state).unwrap_err();
1814
1815        // field with different type
1816        let ty1 = RuntimeType::Object(vec![("bar".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean))]);
1817        obj2.coerce(&ty1, true, &mut exec_state).unwrap_err();
1818    }
1819
1820    #[tokio::test(flavor = "multi_thread")]
1821    async fn coerce_array() {
1822        let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock(None).await);
1823
1824        let hom_arr = KclValue::HomArray {
1825            value: vec![
1826                KclValue::Number {
1827                    value: 0.0,
1828                    ty: NumericType::count(),
1829                    meta: Vec::new(),
1830                },
1831                KclValue::Number {
1832                    value: 1.0,
1833                    ty: NumericType::count(),
1834                    meta: Vec::new(),
1835                },
1836                KclValue::Number {
1837                    value: 2.0,
1838                    ty: NumericType::count(),
1839                    meta: Vec::new(),
1840                },
1841                KclValue::Number {
1842                    value: 3.0,
1843                    ty: NumericType::count(),
1844                    meta: Vec::new(),
1845                },
1846            ],
1847            ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1848        };
1849        let mixed1 = KclValue::Tuple {
1850            value: vec![
1851                KclValue::Number {
1852                    value: 0.0,
1853                    ty: NumericType::count(),
1854                    meta: Vec::new(),
1855                },
1856                KclValue::Number {
1857                    value: 1.0,
1858                    ty: NumericType::count(),
1859                    meta: Vec::new(),
1860                },
1861            ],
1862            meta: Vec::new(),
1863        };
1864        let mixed2 = KclValue::Tuple {
1865            value: vec![
1866                KclValue::Number {
1867                    value: 0.0,
1868                    ty: NumericType::count(),
1869                    meta: Vec::new(),
1870                },
1871                KclValue::Bool {
1872                    value: true,
1873                    meta: Vec::new(),
1874                },
1875            ],
1876            meta: Vec::new(),
1877        };
1878
1879        // Principal types
1880        let tyh = RuntimeType::Array(
1881            Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
1882            ArrayLen::Known(4),
1883        );
1884        let tym1 = RuntimeType::Tuple(vec![
1885            RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1886            RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1887        ]);
1888        let tym2 = RuntimeType::Tuple(vec![
1889            RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1890            RuntimeType::Primitive(PrimitiveType::Boolean),
1891        ]);
1892        assert_coerce_results(&hom_arr, &tyh, &hom_arr, &mut exec_state);
1893        assert_coerce_results(&mixed1, &tym1, &mixed1, &mut exec_state);
1894        assert_coerce_results(&mixed2, &tym2, &mixed2, &mut exec_state);
1895        mixed1.coerce(&tym2, true, &mut exec_state).unwrap_err();
1896        mixed2.coerce(&tym1, true, &mut exec_state).unwrap_err();
1897
1898        // Length subtyping
1899        let tyhn = RuntimeType::Array(
1900            Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
1901            ArrayLen::None,
1902        );
1903        let tyh1 = RuntimeType::Array(
1904            Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
1905            ArrayLen::Minimum(1),
1906        );
1907        let tyh3 = RuntimeType::Array(
1908            Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
1909            ArrayLen::Known(3),
1910        );
1911        let tyhm3 = RuntimeType::Array(
1912            Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
1913            ArrayLen::Minimum(3),
1914        );
1915        let tyhm5 = RuntimeType::Array(
1916            Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
1917            ArrayLen::Minimum(5),
1918        );
1919        assert_coerce_results(&hom_arr, &tyhn, &hom_arr, &mut exec_state);
1920        assert_coerce_results(&hom_arr, &tyh1, &hom_arr, &mut exec_state);
1921        hom_arr.coerce(&tyh3, true, &mut exec_state).unwrap_err();
1922        assert_coerce_results(&hom_arr, &tyhm3, &hom_arr, &mut exec_state);
1923        hom_arr.coerce(&tyhm5, true, &mut exec_state).unwrap_err();
1924
1925        let hom_arr0 = KclValue::HomArray {
1926            value: vec![],
1927            ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1928        };
1929        assert_coerce_results(&hom_arr0, &tyhn, &hom_arr0, &mut exec_state);
1930        hom_arr0.coerce(&tyh1, true, &mut exec_state).unwrap_err();
1931        hom_arr0.coerce(&tyh3, true, &mut exec_state).unwrap_err();
1932
1933        // Covariance
1934        // let tyh = RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any))), ArrayLen::Known(4));
1935        let tym1 = RuntimeType::Tuple(vec![
1936            RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
1937            RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1938        ]);
1939        let tym2 = RuntimeType::Tuple(vec![
1940            RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
1941            RuntimeType::Primitive(PrimitiveType::Boolean),
1942        ]);
1943        // TODO implement covariance for homogeneous arrays
1944        // assert_coerce_results(&hom_arr, &tyh, &hom_arr, &mut exec_state);
1945        assert_coerce_results(&mixed1, &tym1, &mixed1, &mut exec_state);
1946        assert_coerce_results(&mixed2, &tym2, &mixed2, &mut exec_state);
1947
1948        // Mixed to homogeneous
1949        let hom_arr_2 = KclValue::HomArray {
1950            value: vec![
1951                KclValue::Number {
1952                    value: 0.0,
1953                    ty: NumericType::count(),
1954                    meta: Vec::new(),
1955                },
1956                KclValue::Number {
1957                    value: 1.0,
1958                    ty: NumericType::count(),
1959                    meta: Vec::new(),
1960                },
1961            ],
1962            ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1963        };
1964        let mixed0 = KclValue::Tuple {
1965            value: vec![],
1966            meta: Vec::new(),
1967        };
1968        assert_coerce_results(&mixed1, &tyhn, &hom_arr_2, &mut exec_state);
1969        assert_coerce_results(&mixed1, &tyh1, &hom_arr_2, &mut exec_state);
1970        assert_coerce_results(&mixed0, &tyhn, &hom_arr0, &mut exec_state);
1971        mixed0.coerce(&tyh, true, &mut exec_state).unwrap_err();
1972        mixed0.coerce(&tyh1, true, &mut exec_state).unwrap_err();
1973
1974        // Homogehous to mixed
1975        assert_coerce_results(&hom_arr_2, &tym1, &mixed1, &mut exec_state);
1976        hom_arr.coerce(&tym1, true, &mut exec_state).unwrap_err();
1977        hom_arr_2.coerce(&tym2, true, &mut exec_state).unwrap_err();
1978
1979        mixed0.coerce(&tym1, true, &mut exec_state).unwrap_err();
1980        mixed0.coerce(&tym2, true, &mut exec_state).unwrap_err();
1981    }
1982
1983    #[tokio::test(flavor = "multi_thread")]
1984    async fn coerce_union() {
1985        let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock(None).await);
1986
1987        // Subtyping smaller unions
1988        assert!(RuntimeType::Union(vec![]).subtype(&RuntimeType::Union(vec![
1989            RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
1990            RuntimeType::Primitive(PrimitiveType::Boolean)
1991        ])));
1992        assert!(
1993            RuntimeType::Union(vec![RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any))]).subtype(
1994                &RuntimeType::Union(vec![
1995                    RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
1996                    RuntimeType::Primitive(PrimitiveType::Boolean)
1997                ])
1998            )
1999        );
2000        assert!(
2001            RuntimeType::Union(vec![
2002                RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2003                RuntimeType::Primitive(PrimitiveType::Boolean)
2004            ])
2005            .subtype(&RuntimeType::Union(vec![
2006                RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2007                RuntimeType::Primitive(PrimitiveType::Boolean)
2008            ]))
2009        );
2010
2011        // Covariance
2012        let count = KclValue::Number {
2013            value: 1.0,
2014            ty: NumericType::count(),
2015            meta: Vec::new(),
2016        };
2017
2018        let tya = RuntimeType::Union(vec![RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any))]);
2019        let tya2 = RuntimeType::Union(vec![
2020            RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2021            RuntimeType::Primitive(PrimitiveType::Boolean),
2022        ]);
2023        assert_coerce_results(&count, &tya, &count, &mut exec_state);
2024        assert_coerce_results(&count, &tya2, &count, &mut exec_state);
2025
2026        // No matching type
2027        let tyb = RuntimeType::Union(vec![RuntimeType::Primitive(PrimitiveType::Boolean)]);
2028        let tyb2 = RuntimeType::Union(vec![
2029            RuntimeType::Primitive(PrimitiveType::Boolean),
2030            RuntimeType::Primitive(PrimitiveType::String),
2031        ]);
2032        count.coerce(&tyb, true, &mut exec_state).unwrap_err();
2033        count.coerce(&tyb2, true, &mut exec_state).unwrap_err();
2034    }
2035
2036    #[tokio::test(flavor = "multi_thread")]
2037    async fn coerce_axes() {
2038        let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock(None).await);
2039
2040        // Subtyping
2041        assert!(RuntimeType::Primitive(PrimitiveType::Axis2d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis2d)));
2042        assert!(RuntimeType::Primitive(PrimitiveType::Axis3d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis3d)));
2043        assert!(!RuntimeType::Primitive(PrimitiveType::Axis3d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis2d)));
2044        assert!(!RuntimeType::Primitive(PrimitiveType::Axis2d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis3d)));
2045
2046        // Coercion
2047        let a2d = KclValue::Object {
2048            value: [
2049                (
2050                    "origin".to_owned(),
2051                    KclValue::HomArray {
2052                        value: vec![
2053                            KclValue::Number {
2054                                value: 0.0,
2055                                ty: NumericType::mm(),
2056                                meta: Vec::new(),
2057                            },
2058                            KclValue::Number {
2059                                value: 0.0,
2060                                ty: NumericType::mm(),
2061                                meta: Vec::new(),
2062                            },
2063                        ],
2064                        ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
2065                    },
2066                ),
2067                (
2068                    "direction".to_owned(),
2069                    KclValue::HomArray {
2070                        value: vec![
2071                            KclValue::Number {
2072                                value: 1.0,
2073                                ty: NumericType::mm(),
2074                                meta: Vec::new(),
2075                            },
2076                            KclValue::Number {
2077                                value: 0.0,
2078                                ty: NumericType::mm(),
2079                                meta: Vec::new(),
2080                            },
2081                        ],
2082                        ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
2083                    },
2084                ),
2085            ]
2086            .into(),
2087            meta: Vec::new(),
2088        };
2089        let a3d = KclValue::Object {
2090            value: [
2091                (
2092                    "origin".to_owned(),
2093                    KclValue::HomArray {
2094                        value: vec![
2095                            KclValue::Number {
2096                                value: 0.0,
2097                                ty: NumericType::mm(),
2098                                meta: Vec::new(),
2099                            },
2100                            KclValue::Number {
2101                                value: 0.0,
2102                                ty: NumericType::mm(),
2103                                meta: Vec::new(),
2104                            },
2105                            KclValue::Number {
2106                                value: 0.0,
2107                                ty: NumericType::mm(),
2108                                meta: Vec::new(),
2109                            },
2110                        ],
2111                        ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
2112                    },
2113                ),
2114                (
2115                    "direction".to_owned(),
2116                    KclValue::HomArray {
2117                        value: vec![
2118                            KclValue::Number {
2119                                value: 1.0,
2120                                ty: NumericType::mm(),
2121                                meta: Vec::new(),
2122                            },
2123                            KclValue::Number {
2124                                value: 0.0,
2125                                ty: NumericType::mm(),
2126                                meta: Vec::new(),
2127                            },
2128                            KclValue::Number {
2129                                value: 1.0,
2130                                ty: NumericType::mm(),
2131                                meta: Vec::new(),
2132                            },
2133                        ],
2134                        ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
2135                    },
2136                ),
2137            ]
2138            .into(),
2139            meta: Vec::new(),
2140        };
2141
2142        let ty2d = RuntimeType::Primitive(PrimitiveType::Axis2d);
2143        let ty3d = RuntimeType::Primitive(PrimitiveType::Axis3d);
2144
2145        assert_coerce_results(&a2d, &ty2d, &a2d, &mut exec_state);
2146        assert_coerce_results(&a3d, &ty3d, &a3d, &mut exec_state);
2147        assert_coerce_results(&a3d, &ty2d, &a2d, &mut exec_state);
2148        a2d.coerce(&ty3d, true, &mut exec_state).unwrap_err();
2149    }
2150
2151    #[tokio::test(flavor = "multi_thread")]
2152    async fn coerce_numeric() {
2153        let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock(None).await);
2154
2155        let count = KclValue::Number {
2156            value: 1.0,
2157            ty: NumericType::count(),
2158            meta: Vec::new(),
2159        };
2160        let mm = KclValue::Number {
2161            value: 1.0,
2162            ty: NumericType::mm(),
2163            meta: Vec::new(),
2164        };
2165        let inches = KclValue::Number {
2166            value: 1.0,
2167            ty: NumericType::Known(UnitType::Length(UnitLen::Inches)),
2168            meta: Vec::new(),
2169        };
2170        let rads = KclValue::Number {
2171            value: 1.0,
2172            ty: NumericType::Known(UnitType::Angle(UnitAngle::Radians)),
2173            meta: Vec::new(),
2174        };
2175        let default = KclValue::Number {
2176            value: 1.0,
2177            ty: NumericType::default(),
2178            meta: Vec::new(),
2179        };
2180        let any = KclValue::Number {
2181            value: 1.0,
2182            ty: NumericType::Any,
2183            meta: Vec::new(),
2184        };
2185        let unknown = KclValue::Number {
2186            value: 1.0,
2187            ty: NumericType::Unknown,
2188            meta: Vec::new(),
2189        };
2190
2191        // Trivial coercions
2192        assert_coerce_results(&count, &NumericType::count().into(), &count, &mut exec_state);
2193        assert_coerce_results(&mm, &NumericType::mm().into(), &mm, &mut exec_state);
2194        assert_coerce_results(&any, &NumericType::Any.into(), &any, &mut exec_state);
2195        assert_coerce_results(&unknown, &NumericType::Unknown.into(), &unknown, &mut exec_state);
2196        assert_coerce_results(&default, &NumericType::default().into(), &default, &mut exec_state);
2197
2198        assert_coerce_results(&count, &NumericType::Any.into(), &count, &mut exec_state);
2199        assert_coerce_results(&mm, &NumericType::Any.into(), &mm, &mut exec_state);
2200        assert_coerce_results(&unknown, &NumericType::Any.into(), &unknown, &mut exec_state);
2201        assert_coerce_results(&default, &NumericType::Any.into(), &default, &mut exec_state);
2202
2203        assert_eq!(
2204            default
2205                .coerce(
2206                    &NumericType::Default {
2207                        len: UnitLen::Yards,
2208                        angle: UnitAngle::default()
2209                    }
2210                    .into(),
2211                    true,
2212                    &mut exec_state
2213                )
2214                .unwrap(),
2215            default
2216        );
2217
2218        // No coercion
2219        count
2220            .coerce(&NumericType::mm().into(), true, &mut exec_state)
2221            .unwrap_err();
2222        mm.coerce(&NumericType::count().into(), true, &mut exec_state)
2223            .unwrap_err();
2224        unknown
2225            .coerce(&NumericType::mm().into(), true, &mut exec_state)
2226            .unwrap_err();
2227        unknown
2228            .coerce(&NumericType::default().into(), true, &mut exec_state)
2229            .unwrap_err();
2230
2231        count
2232            .coerce(&NumericType::Unknown.into(), true, &mut exec_state)
2233            .unwrap_err();
2234        mm.coerce(&NumericType::Unknown.into(), true, &mut exec_state)
2235            .unwrap_err();
2236        default
2237            .coerce(&NumericType::Unknown.into(), true, &mut exec_state)
2238            .unwrap_err();
2239
2240        assert_eq!(
2241            inches
2242                .coerce(&NumericType::mm().into(), true, &mut exec_state)
2243                .unwrap()
2244                .as_f64()
2245                .unwrap()
2246                .round(),
2247            25.0
2248        );
2249        assert_eq!(
2250            rads.coerce(
2251                &NumericType::Known(UnitType::Angle(UnitAngle::Degrees)).into(),
2252                true,
2253                &mut exec_state
2254            )
2255            .unwrap()
2256            .as_f64()
2257            .unwrap()
2258            .round(),
2259            57.0
2260        );
2261        assert_eq!(
2262            inches
2263                .coerce(&NumericType::default().into(), true, &mut exec_state)
2264                .unwrap()
2265                .as_f64()
2266                .unwrap()
2267                .round(),
2268            1.0
2269        );
2270        assert_eq!(
2271            rads.coerce(&NumericType::default().into(), true, &mut exec_state)
2272                .unwrap()
2273                .as_f64()
2274                .unwrap()
2275                .round(),
2276            1.0
2277        );
2278    }
2279
2280    #[track_caller]
2281    fn assert_value_and_type(name: &str, result: &ExecTestResults, expected: f64, expected_ty: NumericType) {
2282        let mem = result.exec_state.stack();
2283        match mem
2284            .memory
2285            .get_from(name, result.mem_env, SourceRange::default(), 0)
2286            .unwrap()
2287        {
2288            KclValue::Number { value, ty, .. } => {
2289                assert_eq!(value.round(), expected);
2290                assert_eq!(*ty, expected_ty);
2291            }
2292            _ => unreachable!(),
2293        }
2294    }
2295
2296    #[tokio::test(flavor = "multi_thread")]
2297    async fn combine_numeric() {
2298        let program = r#"a = 5 + 4
2299b = 5 - 2
2300c = 5mm - 2mm + 10mm
2301d = 5mm - 2 + 10
2302e = 5 - 2mm + 10
2303f = 30mm - 1inch
2304
2305g = 2 * 10
2306h = 2 * 10mm
2307i = 2mm * 10mm
2308j = 2_ * 10
2309k = 2_ * 3mm * 3mm
2310
2311l = 1 / 10
2312m = 2mm / 1mm
2313n = 10inch / 2mm
2314o = 3mm / 3
2315p = 3_ / 4
2316q = 4inch / 2_
2317
2318r = min([0, 3, 42])
2319s = min([0, 3mm, -42])
2320t = min([100, 3in, 142mm])
2321u = min([3rad, 4in])
2322"#;
2323
2324        let result = parse_execute(program).await.unwrap();
2325        assert_eq!(
2326            result.exec_state.errors().len(),
2327            5,
2328            "errors: {:?}",
2329            result.exec_state.errors()
2330        );
2331
2332        assert_value_and_type("a", &result, 9.0, NumericType::default());
2333        assert_value_and_type("b", &result, 3.0, NumericType::default());
2334        assert_value_and_type("c", &result, 13.0, NumericType::mm());
2335        assert_value_and_type("d", &result, 13.0, NumericType::mm());
2336        assert_value_and_type("e", &result, 13.0, NumericType::mm());
2337        assert_value_and_type("f", &result, 5.0, NumericType::mm());
2338
2339        assert_value_and_type("g", &result, 20.0, NumericType::default());
2340        assert_value_and_type("h", &result, 20.0, NumericType::mm());
2341        assert_value_and_type("i", &result, 20.0, NumericType::Unknown);
2342        assert_value_and_type("j", &result, 20.0, NumericType::default());
2343        assert_value_and_type("k", &result, 18.0, NumericType::Unknown);
2344
2345        assert_value_and_type("l", &result, 0.0, NumericType::default());
2346        assert_value_and_type("m", &result, 2.0, NumericType::count());
2347        assert_value_and_type("n", &result, 5.0, NumericType::Unknown);
2348        assert_value_and_type("o", &result, 1.0, NumericType::mm());
2349        assert_value_and_type("p", &result, 1.0, NumericType::count());
2350        assert_value_and_type("q", &result, 2.0, NumericType::Known(UnitType::Length(UnitLen::Inches)));
2351
2352        assert_value_and_type("r", &result, 0.0, NumericType::default());
2353        assert_value_and_type("s", &result, -42.0, NumericType::mm());
2354        assert_value_and_type("t", &result, 3.0, NumericType::Unknown);
2355        assert_value_and_type("u", &result, 3.0, NumericType::Unknown);
2356    }
2357
2358    #[tokio::test(flavor = "multi_thread")]
2359    async fn bad_typed_arithmetic() {
2360        let program = r#"
2361a = 1rad
2362b = 180 / PI * a + 360
2363"#;
2364
2365        let result = parse_execute(program).await.unwrap();
2366
2367        assert_value_and_type("a", &result, 1.0, NumericType::radians());
2368        assert_value_and_type("b", &result, 417.0, NumericType::Unknown);
2369    }
2370
2371    #[tokio::test(flavor = "multi_thread")]
2372    async fn cos_coercions() {
2373        let program = r#"
2374a = cos(units::toRadians(30))
2375b = 3 / a
2376c = cos(30deg)
2377d = cos(30)
2378"#;
2379
2380        let result = parse_execute(program).await.unwrap();
2381        assert!(result.exec_state.errors().is_empty());
2382
2383        assert_value_and_type("a", &result, 1.0, NumericType::default());
2384        assert_value_and_type("b", &result, 3.0, NumericType::default());
2385        assert_value_and_type("c", &result, 1.0, NumericType::default());
2386        assert_value_and_type("d", &result, 1.0, NumericType::default());
2387    }
2388
2389    #[tokio::test(flavor = "multi_thread")]
2390    async fn coerce_nested_array() {
2391        let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock(None).await);
2392
2393        let mixed1 = KclValue::HomArray {
2394            value: vec![
2395                KclValue::Number {
2396                    value: 0.0,
2397                    ty: NumericType::count(),
2398                    meta: Vec::new(),
2399                },
2400                KclValue::Number {
2401                    value: 1.0,
2402                    ty: NumericType::count(),
2403                    meta: Vec::new(),
2404                },
2405                KclValue::HomArray {
2406                    value: vec![
2407                        KclValue::Number {
2408                            value: 2.0,
2409                            ty: NumericType::count(),
2410                            meta: Vec::new(),
2411                        },
2412                        KclValue::Number {
2413                            value: 3.0,
2414                            ty: NumericType::count(),
2415                            meta: Vec::new(),
2416                        },
2417                    ],
2418                    ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2419                },
2420            ],
2421            ty: RuntimeType::any(),
2422        };
2423
2424        // Principal types
2425        let tym1 = RuntimeType::Array(
2426            Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2427            ArrayLen::Minimum(1),
2428        );
2429
2430        let result = KclValue::HomArray {
2431            value: vec![
2432                KclValue::Number {
2433                    value: 0.0,
2434                    ty: NumericType::count(),
2435                    meta: Vec::new(),
2436                },
2437                KclValue::Number {
2438                    value: 1.0,
2439                    ty: NumericType::count(),
2440                    meta: Vec::new(),
2441                },
2442                KclValue::Number {
2443                    value: 2.0,
2444                    ty: NumericType::count(),
2445                    meta: Vec::new(),
2446                },
2447                KclValue::Number {
2448                    value: 3.0,
2449                    ty: NumericType::count(),
2450                    meta: Vec::new(),
2451                },
2452            ],
2453            ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2454        };
2455        assert_coerce_results(&mixed1, &tym1, &result, &mut exec_state);
2456    }
2457}