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