kcl_lib/execution/
types.rs

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