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    fn values(exec_state: &mut ExecState) -> Vec<KclValue> {
1590        vec![
1591            KclValue::Bool {
1592                value: true,
1593                meta: Vec::new(),
1594            },
1595            KclValue::Number {
1596                value: 1.0,
1597                ty: NumericType::count(),
1598                meta: Vec::new(),
1599            },
1600            KclValue::String {
1601                value: "hello".to_owned(),
1602                meta: Vec::new(),
1603            },
1604            KclValue::Tuple {
1605                value: Vec::new(),
1606                meta: Vec::new(),
1607            },
1608            KclValue::HomArray {
1609                value: Vec::new(),
1610                ty: RuntimeType::solid(),
1611            },
1612            KclValue::Object {
1613                value: crate::execution::KclObjectFields::new(),
1614                meta: Vec::new(),
1615                constrainable: false,
1616            },
1617            KclValue::TagIdentifier(Box::new("foo".parse().unwrap())),
1618            KclValue::TagDeclarator(Box::new(crate::parsing::ast::types::TagDeclarator::new("foo"))),
1619            KclValue::Plane {
1620                value: Box::new(Plane::from_plane_data(crate::std::sketch::PlaneData::XY, exec_state).unwrap()),
1621            },
1622            // No easy way to make a Face, Sketch, Solid, or Helix
1623            KclValue::ImportedGeometry(crate::execution::ImportedGeometry::new(
1624                uuid::Uuid::nil(),
1625                Vec::new(),
1626                Vec::new(),
1627            )),
1628            // Other values don't have types
1629        ]
1630    }
1631
1632    #[track_caller]
1633    fn assert_coerce_results(
1634        value: &KclValue,
1635        super_type: &RuntimeType,
1636        expected_value: &KclValue,
1637        exec_state: &mut ExecState,
1638    ) {
1639        let is_subtype = value == expected_value;
1640        let actual = value.coerce(super_type, true, exec_state).unwrap();
1641        assert_eq!(&actual, expected_value);
1642        assert_eq!(
1643            is_subtype,
1644            value.principal_type().is_some() && value.principal_type().unwrap().subtype(super_type),
1645            "{:?} <: {super_type:?} should be {is_subtype}",
1646            value.principal_type().unwrap()
1647        );
1648        assert!(
1649            expected_value.principal_type().unwrap().subtype(super_type),
1650            "{} <: {super_type}",
1651            expected_value.principal_type().unwrap()
1652        )
1653    }
1654
1655    #[tokio::test(flavor = "multi_thread")]
1656    async fn coerce_idempotent() {
1657        let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock(None).await);
1658        let values = values(&mut exec_state);
1659        for v in &values {
1660            // Identity subtype
1661            let ty = v.principal_type().unwrap();
1662            assert_coerce_results(v, &ty, v, &mut exec_state);
1663
1664            // Union subtype
1665            let uty1 = RuntimeType::Union(vec![ty.clone()]);
1666            let uty2 = RuntimeType::Union(vec![ty.clone(), RuntimeType::Primitive(PrimitiveType::Boolean)]);
1667            assert_coerce_results(v, &uty1, v, &mut exec_state);
1668            assert_coerce_results(v, &uty2, v, &mut exec_state);
1669
1670            // Array subtypes
1671            let aty = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::None);
1672            let aty1 = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Known(1));
1673            let aty0 = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Minimum(1));
1674
1675            match v {
1676                KclValue::HomArray { .. } => {
1677                    // These will not get wrapped if possible.
1678                    assert_coerce_results(
1679                        v,
1680                        &aty,
1681                        &KclValue::HomArray {
1682                            value: vec![],
1683                            ty: ty.clone(),
1684                        },
1685                        &mut exec_state,
1686                    );
1687                    // Coercing an empty array to an array of length 1
1688                    // should fail.
1689                    v.coerce(&aty1, true, &mut exec_state).unwrap_err();
1690                    // Coercing an empty array to an array that's
1691                    // non-empty should fail.
1692                    v.coerce(&aty0, true, &mut exec_state).unwrap_err();
1693                }
1694                KclValue::Tuple { .. } => {}
1695                _ => {
1696                    assert_coerce_results(v, &aty, v, &mut exec_state);
1697                    assert_coerce_results(v, &aty1, v, &mut exec_state);
1698                    assert_coerce_results(v, &aty0, v, &mut exec_state);
1699
1700                    // Tuple subtype
1701                    let tty = RuntimeType::Tuple(vec![ty.clone()]);
1702                    assert_coerce_results(v, &tty, v, &mut exec_state);
1703                }
1704            }
1705        }
1706
1707        for v in &values[1..] {
1708            // Not a subtype
1709            v.coerce(&RuntimeType::Primitive(PrimitiveType::Boolean), true, &mut exec_state)
1710                .unwrap_err();
1711        }
1712    }
1713
1714    #[tokio::test(flavor = "multi_thread")]
1715    async fn coerce_none() {
1716        let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock(None).await);
1717        let none = KclValue::KclNone {
1718            value: crate::parsing::ast::types::KclNone::new(),
1719            meta: Vec::new(),
1720        };
1721
1722        let aty = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::None);
1723        let aty0 = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::Known(0));
1724        let aty1 = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::Known(1));
1725        let aty1p = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::Minimum(1));
1726        assert_coerce_results(
1727            &none,
1728            &aty,
1729            &KclValue::HomArray {
1730                value: Vec::new(),
1731                ty: RuntimeType::solid(),
1732            },
1733            &mut exec_state,
1734        );
1735        assert_coerce_results(
1736            &none,
1737            &aty0,
1738            &KclValue::HomArray {
1739                value: Vec::new(),
1740                ty: RuntimeType::solid(),
1741            },
1742            &mut exec_state,
1743        );
1744        none.coerce(&aty1, true, &mut exec_state).unwrap_err();
1745        none.coerce(&aty1p, true, &mut exec_state).unwrap_err();
1746
1747        let tty = RuntimeType::Tuple(vec![]);
1748        let tty1 = RuntimeType::Tuple(vec![RuntimeType::solid()]);
1749        assert_coerce_results(
1750            &none,
1751            &tty,
1752            &KclValue::Tuple {
1753                value: Vec::new(),
1754                meta: Vec::new(),
1755            },
1756            &mut exec_state,
1757        );
1758        none.coerce(&tty1, true, &mut exec_state).unwrap_err();
1759
1760        let oty = RuntimeType::Object(vec![], false);
1761        assert_coerce_results(
1762            &none,
1763            &oty,
1764            &KclValue::Object {
1765                value: HashMap::new(),
1766                meta: Vec::new(),
1767                constrainable: false,
1768            },
1769            &mut exec_state,
1770        );
1771    }
1772
1773    #[tokio::test(flavor = "multi_thread")]
1774    async fn coerce_record() {
1775        let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock(None).await);
1776
1777        let obj0 = KclValue::Object {
1778            value: HashMap::new(),
1779            meta: Vec::new(),
1780            constrainable: false,
1781        };
1782        let obj1 = KclValue::Object {
1783            value: [(
1784                "foo".to_owned(),
1785                KclValue::Bool {
1786                    value: true,
1787                    meta: Vec::new(),
1788                },
1789            )]
1790            .into(),
1791            meta: Vec::new(),
1792            constrainable: false,
1793        };
1794        let obj2 = KclValue::Object {
1795            value: [
1796                (
1797                    "foo".to_owned(),
1798                    KclValue::Bool {
1799                        value: true,
1800                        meta: Vec::new(),
1801                    },
1802                ),
1803                (
1804                    "bar".to_owned(),
1805                    KclValue::Number {
1806                        value: 0.0,
1807                        ty: NumericType::count(),
1808                        meta: Vec::new(),
1809                    },
1810                ),
1811                (
1812                    "baz".to_owned(),
1813                    KclValue::Number {
1814                        value: 42.0,
1815                        ty: NumericType::count(),
1816                        meta: Vec::new(),
1817                    },
1818                ),
1819            ]
1820            .into(),
1821            meta: Vec::new(),
1822            constrainable: false,
1823        };
1824
1825        let ty0 = RuntimeType::Object(vec![], false);
1826        assert_coerce_results(&obj0, &ty0, &obj0, &mut exec_state);
1827        assert_coerce_results(&obj1, &ty0, &obj1, &mut exec_state);
1828        assert_coerce_results(&obj2, &ty0, &obj2, &mut exec_state);
1829
1830        let ty1 = RuntimeType::Object(
1831            vec![("foo".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean))],
1832            false,
1833        );
1834        obj0.coerce(&ty1, true, &mut exec_state).unwrap_err();
1835        assert_coerce_results(&obj1, &ty1, &obj1, &mut exec_state);
1836        assert_coerce_results(&obj2, &ty1, &obj2, &mut exec_state);
1837
1838        // Different ordering, (TODO - test for covariance once implemented)
1839        let ty2 = RuntimeType::Object(
1840            vec![
1841                (
1842                    "bar".to_owned(),
1843                    RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1844                ),
1845                ("foo".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean)),
1846            ],
1847            false,
1848        );
1849        obj0.coerce(&ty2, true, &mut exec_state).unwrap_err();
1850        obj1.coerce(&ty2, true, &mut exec_state).unwrap_err();
1851        assert_coerce_results(&obj2, &ty2, &obj2, &mut exec_state);
1852
1853        // field not present
1854        let tyq = RuntimeType::Object(
1855            vec![("qux".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean))],
1856            false,
1857        );
1858        obj0.coerce(&tyq, true, &mut exec_state).unwrap_err();
1859        obj1.coerce(&tyq, true, &mut exec_state).unwrap_err();
1860        obj2.coerce(&tyq, true, &mut exec_state).unwrap_err();
1861
1862        // field with different type
1863        let ty1 = RuntimeType::Object(
1864            vec![("bar".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean))],
1865            false,
1866        );
1867        obj2.coerce(&ty1, true, &mut exec_state).unwrap_err();
1868    }
1869
1870    #[tokio::test(flavor = "multi_thread")]
1871    async fn coerce_array() {
1872        let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock(None).await);
1873
1874        let hom_arr = KclValue::HomArray {
1875            value: vec![
1876                KclValue::Number {
1877                    value: 0.0,
1878                    ty: NumericType::count(),
1879                    meta: Vec::new(),
1880                },
1881                KclValue::Number {
1882                    value: 1.0,
1883                    ty: NumericType::count(),
1884                    meta: Vec::new(),
1885                },
1886                KclValue::Number {
1887                    value: 2.0,
1888                    ty: NumericType::count(),
1889                    meta: Vec::new(),
1890                },
1891                KclValue::Number {
1892                    value: 3.0,
1893                    ty: NumericType::count(),
1894                    meta: Vec::new(),
1895                },
1896            ],
1897            ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1898        };
1899        let mixed1 = KclValue::Tuple {
1900            value: vec![
1901                KclValue::Number {
1902                    value: 0.0,
1903                    ty: NumericType::count(),
1904                    meta: Vec::new(),
1905                },
1906                KclValue::Number {
1907                    value: 1.0,
1908                    ty: NumericType::count(),
1909                    meta: Vec::new(),
1910                },
1911            ],
1912            meta: Vec::new(),
1913        };
1914        let mixed2 = KclValue::Tuple {
1915            value: vec![
1916                KclValue::Number {
1917                    value: 0.0,
1918                    ty: NumericType::count(),
1919                    meta: Vec::new(),
1920                },
1921                KclValue::Bool {
1922                    value: true,
1923                    meta: Vec::new(),
1924                },
1925            ],
1926            meta: Vec::new(),
1927        };
1928
1929        // Principal types
1930        let tyh = RuntimeType::Array(
1931            Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
1932            ArrayLen::Known(4),
1933        );
1934        let tym1 = RuntimeType::Tuple(vec![
1935            RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1936            RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1937        ]);
1938        let tym2 = RuntimeType::Tuple(vec![
1939            RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1940            RuntimeType::Primitive(PrimitiveType::Boolean),
1941        ]);
1942        assert_coerce_results(&hom_arr, &tyh, &hom_arr, &mut exec_state);
1943        assert_coerce_results(&mixed1, &tym1, &mixed1, &mut exec_state);
1944        assert_coerce_results(&mixed2, &tym2, &mixed2, &mut exec_state);
1945        mixed1.coerce(&tym2, true, &mut exec_state).unwrap_err();
1946        mixed2.coerce(&tym1, true, &mut exec_state).unwrap_err();
1947
1948        // Length subtyping
1949        let tyhn = RuntimeType::Array(
1950            Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
1951            ArrayLen::None,
1952        );
1953        let tyh1 = RuntimeType::Array(
1954            Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
1955            ArrayLen::Minimum(1),
1956        );
1957        let tyh3 = RuntimeType::Array(
1958            Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
1959            ArrayLen::Known(3),
1960        );
1961        let tyhm3 = RuntimeType::Array(
1962            Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
1963            ArrayLen::Minimum(3),
1964        );
1965        let tyhm5 = RuntimeType::Array(
1966            Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
1967            ArrayLen::Minimum(5),
1968        );
1969        assert_coerce_results(&hom_arr, &tyhn, &hom_arr, &mut exec_state);
1970        assert_coerce_results(&hom_arr, &tyh1, &hom_arr, &mut exec_state);
1971        hom_arr.coerce(&tyh3, true, &mut exec_state).unwrap_err();
1972        assert_coerce_results(&hom_arr, &tyhm3, &hom_arr, &mut exec_state);
1973        hom_arr.coerce(&tyhm5, true, &mut exec_state).unwrap_err();
1974
1975        let hom_arr0 = KclValue::HomArray {
1976            value: vec![],
1977            ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1978        };
1979        assert_coerce_results(&hom_arr0, &tyhn, &hom_arr0, &mut exec_state);
1980        hom_arr0.coerce(&tyh1, true, &mut exec_state).unwrap_err();
1981        hom_arr0.coerce(&tyh3, true, &mut exec_state).unwrap_err();
1982
1983        // Covariance
1984        // let tyh = RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any))), ArrayLen::Known(4));
1985        let tym1 = RuntimeType::Tuple(vec![
1986            RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
1987            RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1988        ]);
1989        let tym2 = RuntimeType::Tuple(vec![
1990            RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
1991            RuntimeType::Primitive(PrimitiveType::Boolean),
1992        ]);
1993        // TODO implement covariance for homogeneous arrays
1994        // assert_coerce_results(&hom_arr, &tyh, &hom_arr, &mut exec_state);
1995        assert_coerce_results(&mixed1, &tym1, &mixed1, &mut exec_state);
1996        assert_coerce_results(&mixed2, &tym2, &mixed2, &mut exec_state);
1997
1998        // Mixed to homogeneous
1999        let hom_arr_2 = KclValue::HomArray {
2000            value: vec![
2001                KclValue::Number {
2002                    value: 0.0,
2003                    ty: NumericType::count(),
2004                    meta: Vec::new(),
2005                },
2006                KclValue::Number {
2007                    value: 1.0,
2008                    ty: NumericType::count(),
2009                    meta: Vec::new(),
2010                },
2011            ],
2012            ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2013        };
2014        let mixed0 = KclValue::Tuple {
2015            value: vec![],
2016            meta: Vec::new(),
2017        };
2018        assert_coerce_results(&mixed1, &tyhn, &hom_arr_2, &mut exec_state);
2019        assert_coerce_results(&mixed1, &tyh1, &hom_arr_2, &mut exec_state);
2020        assert_coerce_results(&mixed0, &tyhn, &hom_arr0, &mut exec_state);
2021        mixed0.coerce(&tyh, true, &mut exec_state).unwrap_err();
2022        mixed0.coerce(&tyh1, true, &mut exec_state).unwrap_err();
2023
2024        // Homogehous to mixed
2025        assert_coerce_results(&hom_arr_2, &tym1, &mixed1, &mut exec_state);
2026        hom_arr.coerce(&tym1, true, &mut exec_state).unwrap_err();
2027        hom_arr_2.coerce(&tym2, true, &mut exec_state).unwrap_err();
2028
2029        mixed0.coerce(&tym1, true, &mut exec_state).unwrap_err();
2030        mixed0.coerce(&tym2, true, &mut exec_state).unwrap_err();
2031    }
2032
2033    #[tokio::test(flavor = "multi_thread")]
2034    async fn coerce_union() {
2035        let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock(None).await);
2036
2037        // Subtyping smaller unions
2038        assert!(RuntimeType::Union(vec![]).subtype(&RuntimeType::Union(vec![
2039            RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2040            RuntimeType::Primitive(PrimitiveType::Boolean)
2041        ])));
2042        assert!(
2043            RuntimeType::Union(vec![RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any))]).subtype(
2044                &RuntimeType::Union(vec![
2045                    RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2046                    RuntimeType::Primitive(PrimitiveType::Boolean)
2047                ])
2048            )
2049        );
2050        assert!(
2051            RuntimeType::Union(vec![
2052                RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2053                RuntimeType::Primitive(PrimitiveType::Boolean)
2054            ])
2055            .subtype(&RuntimeType::Union(vec![
2056                RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2057                RuntimeType::Primitive(PrimitiveType::Boolean)
2058            ]))
2059        );
2060
2061        // Covariance
2062        let count = KclValue::Number {
2063            value: 1.0,
2064            ty: NumericType::count(),
2065            meta: Vec::new(),
2066        };
2067
2068        let tya = RuntimeType::Union(vec![RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any))]);
2069        let tya2 = RuntimeType::Union(vec![
2070            RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2071            RuntimeType::Primitive(PrimitiveType::Boolean),
2072        ]);
2073        assert_coerce_results(&count, &tya, &count, &mut exec_state);
2074        assert_coerce_results(&count, &tya2, &count, &mut exec_state);
2075
2076        // No matching type
2077        let tyb = RuntimeType::Union(vec![RuntimeType::Primitive(PrimitiveType::Boolean)]);
2078        let tyb2 = RuntimeType::Union(vec![
2079            RuntimeType::Primitive(PrimitiveType::Boolean),
2080            RuntimeType::Primitive(PrimitiveType::String),
2081        ]);
2082        count.coerce(&tyb, true, &mut exec_state).unwrap_err();
2083        count.coerce(&tyb2, true, &mut exec_state).unwrap_err();
2084    }
2085
2086    #[tokio::test(flavor = "multi_thread")]
2087    async fn coerce_axes() {
2088        let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock(None).await);
2089
2090        // Subtyping
2091        assert!(RuntimeType::Primitive(PrimitiveType::Axis2d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis2d)));
2092        assert!(RuntimeType::Primitive(PrimitiveType::Axis3d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis3d)));
2093        assert!(!RuntimeType::Primitive(PrimitiveType::Axis3d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis2d)));
2094        assert!(!RuntimeType::Primitive(PrimitiveType::Axis2d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis3d)));
2095
2096        // Coercion
2097        let a2d = KclValue::Object {
2098            value: [
2099                (
2100                    "origin".to_owned(),
2101                    KclValue::HomArray {
2102                        value: vec![
2103                            KclValue::Number {
2104                                value: 0.0,
2105                                ty: NumericType::mm(),
2106                                meta: Vec::new(),
2107                            },
2108                            KclValue::Number {
2109                                value: 0.0,
2110                                ty: NumericType::mm(),
2111                                meta: Vec::new(),
2112                            },
2113                        ],
2114                        ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
2115                    },
2116                ),
2117                (
2118                    "direction".to_owned(),
2119                    KclValue::HomArray {
2120                        value: vec![
2121                            KclValue::Number {
2122                                value: 1.0,
2123                                ty: NumericType::mm(),
2124                                meta: Vec::new(),
2125                            },
2126                            KclValue::Number {
2127                                value: 0.0,
2128                                ty: NumericType::mm(),
2129                                meta: Vec::new(),
2130                            },
2131                        ],
2132                        ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
2133                    },
2134                ),
2135            ]
2136            .into(),
2137            meta: Vec::new(),
2138            constrainable: false,
2139        };
2140        let a3d = KclValue::Object {
2141            value: [
2142                (
2143                    "origin".to_owned(),
2144                    KclValue::HomArray {
2145                        value: vec![
2146                            KclValue::Number {
2147                                value: 0.0,
2148                                ty: NumericType::mm(),
2149                                meta: Vec::new(),
2150                            },
2151                            KclValue::Number {
2152                                value: 0.0,
2153                                ty: NumericType::mm(),
2154                                meta: Vec::new(),
2155                            },
2156                            KclValue::Number {
2157                                value: 0.0,
2158                                ty: NumericType::mm(),
2159                                meta: Vec::new(),
2160                            },
2161                        ],
2162                        ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
2163                    },
2164                ),
2165                (
2166                    "direction".to_owned(),
2167                    KclValue::HomArray {
2168                        value: vec![
2169                            KclValue::Number {
2170                                value: 1.0,
2171                                ty: NumericType::mm(),
2172                                meta: Vec::new(),
2173                            },
2174                            KclValue::Number {
2175                                value: 0.0,
2176                                ty: NumericType::mm(),
2177                                meta: Vec::new(),
2178                            },
2179                            KclValue::Number {
2180                                value: 1.0,
2181                                ty: NumericType::mm(),
2182                                meta: Vec::new(),
2183                            },
2184                        ],
2185                        ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
2186                    },
2187                ),
2188            ]
2189            .into(),
2190            meta: Vec::new(),
2191            constrainable: false,
2192        };
2193
2194        let ty2d = RuntimeType::Primitive(PrimitiveType::Axis2d);
2195        let ty3d = RuntimeType::Primitive(PrimitiveType::Axis3d);
2196
2197        assert_coerce_results(&a2d, &ty2d, &a2d, &mut exec_state);
2198        assert_coerce_results(&a3d, &ty3d, &a3d, &mut exec_state);
2199        assert_coerce_results(&a3d, &ty2d, &a2d, &mut exec_state);
2200        a2d.coerce(&ty3d, true, &mut exec_state).unwrap_err();
2201    }
2202
2203    #[tokio::test(flavor = "multi_thread")]
2204    async fn coerce_numeric() {
2205        let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock(None).await);
2206
2207        let count = KclValue::Number {
2208            value: 1.0,
2209            ty: NumericType::count(),
2210            meta: Vec::new(),
2211        };
2212        let mm = KclValue::Number {
2213            value: 1.0,
2214            ty: NumericType::mm(),
2215            meta: Vec::new(),
2216        };
2217        let inches = KclValue::Number {
2218            value: 1.0,
2219            ty: NumericType::Known(UnitType::Length(UnitLength::Inches)),
2220            meta: Vec::new(),
2221        };
2222        let rads = KclValue::Number {
2223            value: 1.0,
2224            ty: NumericType::Known(UnitType::Angle(UnitAngle::Radians)),
2225            meta: Vec::new(),
2226        };
2227        let default = KclValue::Number {
2228            value: 1.0,
2229            ty: NumericType::default(),
2230            meta: Vec::new(),
2231        };
2232        let any = KclValue::Number {
2233            value: 1.0,
2234            ty: NumericType::Any,
2235            meta: Vec::new(),
2236        };
2237        let unknown = KclValue::Number {
2238            value: 1.0,
2239            ty: NumericType::Unknown,
2240            meta: Vec::new(),
2241        };
2242
2243        // Trivial coercions
2244        assert_coerce_results(&count, &NumericType::count().into(), &count, &mut exec_state);
2245        assert_coerce_results(&mm, &NumericType::mm().into(), &mm, &mut exec_state);
2246        assert_coerce_results(&any, &NumericType::Any.into(), &any, &mut exec_state);
2247        assert_coerce_results(&unknown, &NumericType::Unknown.into(), &unknown, &mut exec_state);
2248        assert_coerce_results(&default, &NumericType::default().into(), &default, &mut exec_state);
2249
2250        assert_coerce_results(&count, &NumericType::Any.into(), &count, &mut exec_state);
2251        assert_coerce_results(&mm, &NumericType::Any.into(), &mm, &mut exec_state);
2252        assert_coerce_results(&unknown, &NumericType::Any.into(), &unknown, &mut exec_state);
2253        assert_coerce_results(&default, &NumericType::Any.into(), &default, &mut exec_state);
2254
2255        assert_eq!(
2256            default
2257                .coerce(
2258                    &NumericType::Default {
2259                        len: UnitLength::Yards,
2260                        angle: UnitAngle::Degrees,
2261                    }
2262                    .into(),
2263                    true,
2264                    &mut exec_state
2265                )
2266                .unwrap(),
2267            default
2268        );
2269
2270        // No coercion
2271        count
2272            .coerce(&NumericType::mm().into(), true, &mut exec_state)
2273            .unwrap_err();
2274        mm.coerce(&NumericType::count().into(), true, &mut exec_state)
2275            .unwrap_err();
2276        unknown
2277            .coerce(&NumericType::mm().into(), true, &mut exec_state)
2278            .unwrap_err();
2279        unknown
2280            .coerce(&NumericType::default().into(), true, &mut exec_state)
2281            .unwrap_err();
2282
2283        count
2284            .coerce(&NumericType::Unknown.into(), true, &mut exec_state)
2285            .unwrap_err();
2286        mm.coerce(&NumericType::Unknown.into(), true, &mut exec_state)
2287            .unwrap_err();
2288        default
2289            .coerce(&NumericType::Unknown.into(), true, &mut exec_state)
2290            .unwrap_err();
2291
2292        assert_eq!(
2293            inches
2294                .coerce(&NumericType::mm().into(), true, &mut exec_state)
2295                .unwrap()
2296                .as_f64()
2297                .unwrap()
2298                .round(),
2299            25.0
2300        );
2301        assert_eq!(
2302            rads.coerce(
2303                &NumericType::Known(UnitType::Angle(UnitAngle::Degrees)).into(),
2304                true,
2305                &mut exec_state
2306            )
2307            .unwrap()
2308            .as_f64()
2309            .unwrap()
2310            .round(),
2311            57.0
2312        );
2313        assert_eq!(
2314            inches
2315                .coerce(&NumericType::default().into(), true, &mut exec_state)
2316                .unwrap()
2317                .as_f64()
2318                .unwrap()
2319                .round(),
2320            1.0
2321        );
2322        assert_eq!(
2323            rads.coerce(&NumericType::default().into(), true, &mut exec_state)
2324                .unwrap()
2325                .as_f64()
2326                .unwrap()
2327                .round(),
2328            1.0
2329        );
2330    }
2331
2332    #[track_caller]
2333    fn assert_value_and_type(name: &str, result: &ExecTestResults, expected: f64, expected_ty: NumericType) {
2334        let mem = result.exec_state.stack();
2335        match mem
2336            .memory
2337            .get_from(name, result.mem_env, SourceRange::default(), 0)
2338            .unwrap()
2339        {
2340            KclValue::Number { value, ty, .. } => {
2341                assert_eq!(value.round(), expected);
2342                assert_eq!(*ty, expected_ty);
2343            }
2344            _ => unreachable!(),
2345        }
2346    }
2347
2348    #[tokio::test(flavor = "multi_thread")]
2349    async fn combine_numeric() {
2350        let program = r#"a = 5 + 4
2351b = 5 - 2
2352c = 5mm - 2mm + 10mm
2353d = 5mm - 2 + 10
2354e = 5 - 2mm + 10
2355f = 30mm - 1inch
2356
2357g = 2 * 10
2358h = 2 * 10mm
2359i = 2mm * 10mm
2360j = 2_ * 10
2361k = 2_ * 3mm * 3mm
2362
2363l = 1 / 10
2364m = 2mm / 1mm
2365n = 10inch / 2mm
2366o = 3mm / 3
2367p = 3_ / 4
2368q = 4inch / 2_
2369
2370r = min([0, 3, 42])
2371s = min([0, 3mm, -42])
2372t = min([100, 3in, 142mm])
2373u = min([3rad, 4in])
2374"#;
2375
2376        let result = parse_execute(program).await.unwrap();
2377        assert_eq!(
2378            result.exec_state.errors().len(),
2379            5,
2380            "errors: {:?}",
2381            result.exec_state.errors()
2382        );
2383
2384        assert_value_and_type("a", &result, 9.0, NumericType::default());
2385        assert_value_and_type("b", &result, 3.0, NumericType::default());
2386        assert_value_and_type("c", &result, 13.0, NumericType::mm());
2387        assert_value_and_type("d", &result, 13.0, NumericType::mm());
2388        assert_value_and_type("e", &result, 13.0, NumericType::mm());
2389        assert_value_and_type("f", &result, 5.0, NumericType::mm());
2390
2391        assert_value_and_type("g", &result, 20.0, NumericType::default());
2392        assert_value_and_type("h", &result, 20.0, NumericType::mm());
2393        assert_value_and_type("i", &result, 20.0, NumericType::Unknown);
2394        assert_value_and_type("j", &result, 20.0, NumericType::default());
2395        assert_value_and_type("k", &result, 18.0, NumericType::Unknown);
2396
2397        assert_value_and_type("l", &result, 0.0, NumericType::default());
2398        assert_value_and_type("m", &result, 2.0, NumericType::count());
2399        assert_value_and_type("n", &result, 5.0, NumericType::Unknown);
2400        assert_value_and_type("o", &result, 1.0, NumericType::mm());
2401        assert_value_and_type("p", &result, 1.0, NumericType::count());
2402        assert_value_and_type(
2403            "q",
2404            &result,
2405            2.0,
2406            NumericType::Known(UnitType::Length(UnitLength::Inches)),
2407        );
2408
2409        assert_value_and_type("r", &result, 0.0, NumericType::default());
2410        assert_value_and_type("s", &result, -42.0, NumericType::mm());
2411        assert_value_and_type("t", &result, 3.0, NumericType::Unknown);
2412        assert_value_and_type("u", &result, 3.0, NumericType::Unknown);
2413    }
2414
2415    #[tokio::test(flavor = "multi_thread")]
2416    async fn bad_typed_arithmetic() {
2417        let program = r#"
2418a = 1rad
2419b = 180 / PI * a + 360
2420"#;
2421
2422        let result = parse_execute(program).await.unwrap();
2423
2424        assert_value_and_type("a", &result, 1.0, NumericType::radians());
2425        assert_value_and_type("b", &result, 417.0, NumericType::Unknown);
2426    }
2427
2428    #[tokio::test(flavor = "multi_thread")]
2429    async fn cos_coercions() {
2430        let program = r#"
2431a = cos(units::toRadians(30deg))
2432b = 3 / a
2433c = cos(30deg)
2434d = cos(1rad)
2435"#;
2436
2437        let result = parse_execute(program).await.unwrap();
2438        assert!(
2439            result.exec_state.errors().is_empty(),
2440            "{:?}",
2441            result.exec_state.errors()
2442        );
2443
2444        assert_value_and_type("a", &result, 1.0, NumericType::default());
2445        assert_value_and_type("b", &result, 3.0, NumericType::default());
2446        assert_value_and_type("c", &result, 1.0, NumericType::default());
2447        assert_value_and_type("d", &result, 1.0, NumericType::default());
2448    }
2449
2450    #[tokio::test(flavor = "multi_thread")]
2451    async fn coerce_nested_array() {
2452        let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock(None).await);
2453
2454        let mixed1 = KclValue::HomArray {
2455            value: vec![
2456                KclValue::Number {
2457                    value: 0.0,
2458                    ty: NumericType::count(),
2459                    meta: Vec::new(),
2460                },
2461                KclValue::Number {
2462                    value: 1.0,
2463                    ty: NumericType::count(),
2464                    meta: Vec::new(),
2465                },
2466                KclValue::HomArray {
2467                    value: vec![
2468                        KclValue::Number {
2469                            value: 2.0,
2470                            ty: NumericType::count(),
2471                            meta: Vec::new(),
2472                        },
2473                        KclValue::Number {
2474                            value: 3.0,
2475                            ty: NumericType::count(),
2476                            meta: Vec::new(),
2477                        },
2478                    ],
2479                    ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2480                },
2481            ],
2482            ty: RuntimeType::any(),
2483        };
2484
2485        // Principal types
2486        let tym1 = RuntimeType::Array(
2487            Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2488            ArrayLen::Minimum(1),
2489        );
2490
2491        let result = KclValue::HomArray {
2492            value: vec![
2493                KclValue::Number {
2494                    value: 0.0,
2495                    ty: NumericType::count(),
2496                    meta: Vec::new(),
2497                },
2498                KclValue::Number {
2499                    value: 1.0,
2500                    ty: NumericType::count(),
2501                    meta: Vec::new(),
2502                },
2503                KclValue::Number {
2504                    value: 2.0,
2505                    ty: NumericType::count(),
2506                    meta: Vec::new(),
2507                },
2508                KclValue::Number {
2509                    value: 3.0,
2510                    ty: NumericType::count(),
2511                    meta: Vec::new(),
2512                },
2513            ],
2514            ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2515        };
2516        assert_coerce_results(&mixed1, &tym1, &result, &mut exec_state);
2517    }
2518}