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