kcl_lib/execution/
types.rs

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