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