kcl_lib/execution/
types.rs

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