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                        artifact_id: id.into(),
1116                        info: PlaneInfo {
1117                            origin,
1118                            x_axis: x_axis.normalize(),
1119                            y_axis: y_axis.normalize(),
1120                        },
1121                        value: super::PlaneType::Uninit,
1122                        meta: meta.clone(),
1123                    };
1124
1125                    Ok(KclValue::Plane { value: Box::new(plane) })
1126                }
1127                _ => Err(self.into()),
1128            },
1129            PrimitiveType::Face => match value {
1130                KclValue::Face { .. } => Ok(value.clone()),
1131                _ => Err(self.into()),
1132            },
1133            PrimitiveType::Helix => match value {
1134                KclValue::Helix { .. } => Ok(value.clone()),
1135                _ => Err(self.into()),
1136            },
1137            PrimitiveType::Edge => match value {
1138                KclValue::Uuid { .. } => Ok(value.clone()),
1139                KclValue::TagIdentifier { .. } => Ok(value.clone()),
1140                _ => Err(self.into()),
1141            },
1142            PrimitiveType::Axis2d => match value {
1143                KclValue::Object { value: values, meta } => {
1144                    if values
1145                        .get("origin")
1146                        .ok_or(CoercionError::from(self))?
1147                        .has_type(&RuntimeType::point2d())
1148                        && values
1149                            .get("direction")
1150                            .ok_or(CoercionError::from(self))?
1151                            .has_type(&RuntimeType::point2d())
1152                    {
1153                        return Ok(value.clone());
1154                    }
1155
1156                    let origin = values.get("origin").ok_or(self.into()).and_then(|p| {
1157                        p.coerce_to_array_type(&RuntimeType::length(), ArrayLen::Known(2), exec_state, true)
1158                    })?;
1159                    let direction = values.get("direction").ok_or(self.into()).and_then(|p| {
1160                        p.coerce_to_array_type(&RuntimeType::length(), ArrayLen::Known(2), exec_state, true)
1161                    })?;
1162
1163                    Ok(KclValue::Object {
1164                        value: [("origin".to_owned(), origin), ("direction".to_owned(), direction)].into(),
1165                        meta: meta.clone(),
1166                    })
1167                }
1168                _ => Err(self.into()),
1169            },
1170            PrimitiveType::Axis3d => match value {
1171                KclValue::Object { value: values, meta } => {
1172                    if values
1173                        .get("origin")
1174                        .ok_or(CoercionError::from(self))?
1175                        .has_type(&RuntimeType::point3d())
1176                        && values
1177                            .get("direction")
1178                            .ok_or(CoercionError::from(self))?
1179                            .has_type(&RuntimeType::point3d())
1180                    {
1181                        return Ok(value.clone());
1182                    }
1183
1184                    let origin = values.get("origin").ok_or(self.into()).and_then(|p| {
1185                        p.coerce_to_array_type(&RuntimeType::length(), ArrayLen::Known(3), exec_state, true)
1186                    })?;
1187                    let direction = values.get("direction").ok_or(self.into()).and_then(|p| {
1188                        p.coerce_to_array_type(&RuntimeType::length(), ArrayLen::Known(3), exec_state, true)
1189                    })?;
1190
1191                    Ok(KclValue::Object {
1192                        value: [("origin".to_owned(), origin), ("direction".to_owned(), direction)].into(),
1193                        meta: meta.clone(),
1194                    })
1195                }
1196                _ => Err(self.into()),
1197            },
1198            PrimitiveType::ImportedGeometry => match value {
1199                KclValue::ImportedGeometry { .. } => Ok(value.clone()),
1200                _ => Err(self.into()),
1201            },
1202            PrimitiveType::Function => match value {
1203                KclValue::Function { .. } => Ok(value.clone()),
1204                _ => Err(self.into()),
1205            },
1206            PrimitiveType::TagId => match value {
1207                KclValue::TagIdentifier { .. } => Ok(value.clone()),
1208                _ => Err(self.into()),
1209            },
1210            PrimitiveType::Tag => match value {
1211                KclValue::TagDeclarator { .. } | KclValue::TagIdentifier { .. } | KclValue::Uuid { .. } => {
1212                    Ok(value.clone())
1213                }
1214                s @ KclValue::String { value, .. } if ["start", "end", "START", "END"].contains(&&**value) => {
1215                    Ok(s.clone())
1216                }
1217                _ => Err(self.into()),
1218            },
1219        }
1220    }
1221
1222    fn coerce_to_array_type(
1223        &self,
1224        ty: &RuntimeType,
1225        len: ArrayLen,
1226        exec_state: &mut ExecState,
1227        allow_shrink: bool,
1228    ) -> Result<KclValue, CoercionError> {
1229        match self {
1230            KclValue::HomArray { value, ty: aty, .. } => {
1231                let satisfied_len = len.satisfied(value.len(), allow_shrink);
1232
1233                if aty.subtype(ty) {
1234                    // If the element type is a subtype of the target type and
1235                    // the length constraint is satisfied, we can just return
1236                    // the values unchanged, only adjusting the length. The new
1237                    // array element type should preserve its type because the
1238                    // target type oftentimes includes an unknown type as a way
1239                    // to say that the caller doesn't care.
1240                    return satisfied_len
1241                        .map(|len| KclValue::HomArray {
1242                            value: value[..len].to_vec(),
1243                            ty: aty.clone(),
1244                        })
1245                        .ok_or(self.into());
1246                }
1247
1248                // Ignore the array type, and coerce the elements of the array.
1249                if let Some(satisfied_len) = satisfied_len {
1250                    let value_result = value
1251                        .iter()
1252                        .take(satisfied_len)
1253                        .map(|v| v.coerce(ty, exec_state))
1254                        .collect::<Result<Vec<_>, _>>();
1255
1256                    if let Ok(value) = value_result {
1257                        // We were able to coerce all the elements.
1258                        return Ok(KclValue::HomArray { value, ty: ty.clone() });
1259                    }
1260                }
1261
1262                // As a last resort, try to flatten the array.
1263                let mut values = Vec::new();
1264                for item in value {
1265                    if let KclValue::HomArray { value: inner_value, .. } = item {
1266                        // Flatten elements.
1267                        for item in inner_value {
1268                            values.push(item.coerce(ty, exec_state)?);
1269                        }
1270                    } else {
1271                        values.push(item.coerce(ty, exec_state)?);
1272                    }
1273                }
1274
1275                let len = len
1276                    .satisfied(values.len(), allow_shrink)
1277                    .ok_or(CoercionError::from(self))?;
1278
1279                if len > values.len() {
1280                    let message = format!(
1281                        "Internal: Expected coerced array length {len} to be less than or equal to original length {}",
1282                        values.len()
1283                    );
1284                    exec_state.err(CompilationError::err(self.into(), message.clone()));
1285                    #[cfg(debug_assertions)]
1286                    panic!("{message}");
1287                }
1288                values.truncate(len);
1289
1290                Ok(KclValue::HomArray {
1291                    value: values,
1292                    ty: ty.clone(),
1293                })
1294            }
1295            KclValue::Tuple { value, .. } => {
1296                let len = len
1297                    .satisfied(value.len(), allow_shrink)
1298                    .ok_or(CoercionError::from(self))?;
1299                let value = value
1300                    .iter()
1301                    .map(|item| item.coerce(ty, exec_state))
1302                    .take(len)
1303                    .collect::<Result<Vec<_>, _>>()?;
1304
1305                Ok(KclValue::HomArray { value, ty: ty.clone() })
1306            }
1307            KclValue::KclNone { .. } if len.satisfied(0, false).is_some() => Ok(KclValue::HomArray {
1308                value: Vec::new(),
1309                ty: ty.clone(),
1310            }),
1311            _ if len.satisfied(1, false).is_some() => Ok(KclValue::HomArray {
1312                value: vec![self.coerce(ty, exec_state)?],
1313                ty: ty.clone(),
1314            }),
1315            _ => Err(self.into()),
1316        }
1317    }
1318
1319    fn coerce_to_tuple_type(&self, tys: &[RuntimeType], exec_state: &mut ExecState) -> Result<KclValue, CoercionError> {
1320        match self {
1321            KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } if value.len() == tys.len() => {
1322                let mut result = Vec::new();
1323                for (i, t) in tys.iter().enumerate() {
1324                    result.push(value[i].coerce(t, exec_state)?);
1325                }
1326
1327                Ok(KclValue::Tuple {
1328                    value: result,
1329                    meta: Vec::new(),
1330                })
1331            }
1332            KclValue::KclNone { meta, .. } if tys.is_empty() => Ok(KclValue::Tuple {
1333                value: Vec::new(),
1334                meta: meta.clone(),
1335            }),
1336            value if tys.len() == 1 && value.has_type(&tys[0]) => Ok(KclValue::Tuple {
1337                value: vec![value.clone()],
1338                meta: Vec::new(),
1339            }),
1340            _ => Err(self.into()),
1341        }
1342    }
1343
1344    fn coerce_to_union_type(&self, tys: &[RuntimeType], exec_state: &mut ExecState) -> Result<KclValue, CoercionError> {
1345        for t in tys {
1346            if let Ok(v) = self.coerce(t, exec_state) {
1347                return Ok(v);
1348            }
1349        }
1350
1351        Err(self.into())
1352    }
1353
1354    fn coerce_to_object_type(
1355        &self,
1356        tys: &[(String, RuntimeType)],
1357        _exec_state: &mut ExecState,
1358    ) -> Result<KclValue, CoercionError> {
1359        match self {
1360            KclValue::Object { value, .. } => {
1361                for (s, t) in tys {
1362                    // TODO coerce fields
1363                    if !value.get(s).ok_or(CoercionError::from(self))?.has_type(t) {
1364                        return Err(self.into());
1365                    }
1366                }
1367                // TODO remove non-required fields
1368                Ok(self.clone())
1369            }
1370            KclValue::KclNone { meta, .. } if tys.is_empty() => Ok(KclValue::Object {
1371                value: HashMap::new(),
1372                meta: meta.clone(),
1373            }),
1374            _ => Err(self.into()),
1375        }
1376    }
1377
1378    pub fn principal_type(&self) -> Option<RuntimeType> {
1379        match self {
1380            KclValue::Bool { .. } => Some(RuntimeType::Primitive(PrimitiveType::Boolean)),
1381            KclValue::Number { ty, .. } => Some(RuntimeType::Primitive(PrimitiveType::Number(ty.clone()))),
1382            KclValue::String { .. } => Some(RuntimeType::Primitive(PrimitiveType::String)),
1383            KclValue::Object { value, .. } => {
1384                let properties = value
1385                    .iter()
1386                    .map(|(k, v)| v.principal_type().map(|t| (k.clone(), t)))
1387                    .collect::<Option<Vec<_>>>()?;
1388                Some(RuntimeType::Object(properties))
1389            }
1390            KclValue::Plane { .. } => Some(RuntimeType::Primitive(PrimitiveType::Plane)),
1391            KclValue::Sketch { .. } => Some(RuntimeType::Primitive(PrimitiveType::Sketch)),
1392            KclValue::Solid { .. } => Some(RuntimeType::Primitive(PrimitiveType::Solid)),
1393            KclValue::Face { .. } => Some(RuntimeType::Primitive(PrimitiveType::Face)),
1394            KclValue::Helix { .. } => Some(RuntimeType::Primitive(PrimitiveType::Helix)),
1395            KclValue::ImportedGeometry(..) => Some(RuntimeType::Primitive(PrimitiveType::ImportedGeometry)),
1396            KclValue::Tuple { value, .. } => Some(RuntimeType::Tuple(
1397                value.iter().map(|v| v.principal_type()).collect::<Option<Vec<_>>>()?,
1398            )),
1399            KclValue::HomArray { ty, value, .. } => {
1400                Some(RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Known(value.len())))
1401            }
1402            KclValue::TagIdentifier(_) => Some(RuntimeType::Primitive(PrimitiveType::TagId)),
1403            KclValue::TagDeclarator(_) | KclValue::Uuid { .. } => Some(RuntimeType::Primitive(PrimitiveType::Tag)),
1404            KclValue::Function { .. } => Some(RuntimeType::Primitive(PrimitiveType::Function)),
1405            KclValue::Module { .. } | KclValue::KclNone { .. } | KclValue::Type { .. } => None,
1406        }
1407    }
1408}
1409
1410#[cfg(test)]
1411mod test {
1412    use super::*;
1413    use crate::execution::{parse_execute, ExecTestResults};
1414
1415    fn values(exec_state: &mut ExecState) -> Vec<KclValue> {
1416        vec![
1417            KclValue::Bool {
1418                value: true,
1419                meta: Vec::new(),
1420            },
1421            KclValue::Number {
1422                value: 1.0,
1423                ty: NumericType::count(),
1424                meta: Vec::new(),
1425            },
1426            KclValue::String {
1427                value: "hello".to_owned(),
1428                meta: Vec::new(),
1429            },
1430            KclValue::Tuple {
1431                value: Vec::new(),
1432                meta: Vec::new(),
1433            },
1434            KclValue::HomArray {
1435                value: Vec::new(),
1436                ty: RuntimeType::solid(),
1437            },
1438            KclValue::Object {
1439                value: crate::execution::KclObjectFields::new(),
1440                meta: Vec::new(),
1441            },
1442            KclValue::TagIdentifier(Box::new("foo".parse().unwrap())),
1443            KclValue::TagDeclarator(Box::new(crate::parsing::ast::types::TagDeclarator::new("foo"))),
1444            KclValue::Plane {
1445                value: Box::new(Plane::from_plane_data(crate::std::sketch::PlaneData::XY, exec_state).unwrap()),
1446            },
1447            // No easy way to make a Face, Sketch, Solid, or Helix
1448            KclValue::ImportedGeometry(crate::execution::ImportedGeometry::new(
1449                uuid::Uuid::nil(),
1450                Vec::new(),
1451                Vec::new(),
1452            )),
1453            // Other values don't have types
1454        ]
1455    }
1456
1457    #[track_caller]
1458    fn assert_coerce_results(
1459        value: &KclValue,
1460        super_type: &RuntimeType,
1461        expected_value: &KclValue,
1462        exec_state: &mut ExecState,
1463    ) {
1464        let is_subtype = value == expected_value;
1465        assert_eq!(&value.coerce(super_type, exec_state).unwrap(), expected_value);
1466        assert_eq!(
1467            is_subtype,
1468            value.principal_type().is_some() && value.principal_type().unwrap().subtype(super_type),
1469            "{:?} <: {super_type:?} should be {is_subtype}",
1470            value.principal_type().unwrap()
1471        );
1472        assert!(
1473            expected_value.principal_type().unwrap().subtype(super_type),
1474            "{} <: {super_type}",
1475            expected_value.principal_type().unwrap()
1476        )
1477    }
1478
1479    #[tokio::test(flavor = "multi_thread")]
1480    async fn coerce_idempotent() {
1481        let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock(None).await);
1482        let values = values(&mut exec_state);
1483        for v in &values {
1484            // Identity subtype
1485            let ty = v.principal_type().unwrap();
1486            assert_coerce_results(v, &ty, v, &mut exec_state);
1487
1488            // Union subtype
1489            let uty1 = RuntimeType::Union(vec![ty.clone()]);
1490            let uty2 = RuntimeType::Union(vec![ty.clone(), RuntimeType::Primitive(PrimitiveType::Boolean)]);
1491            assert_coerce_results(v, &uty1, v, &mut exec_state);
1492            assert_coerce_results(v, &uty2, v, &mut exec_state);
1493
1494            // Array subtypes
1495            let aty = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::None);
1496            let aty1 = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Known(1));
1497            let aty0 = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::NonEmpty);
1498
1499            match v {
1500                KclValue::Tuple { .. } | KclValue::HomArray { .. } => {
1501                    // These will not get wrapped if possible.
1502                    assert_coerce_results(
1503                        v,
1504                        &aty,
1505                        &KclValue::HomArray {
1506                            value: vec![],
1507                            ty: ty.clone(),
1508                        },
1509                        &mut exec_state,
1510                    );
1511                    // Coercing an empty tuple or array to an array of length 1
1512                    // should fail.
1513                    v.coerce(&aty1, &mut exec_state).unwrap_err();
1514                    // Coercing an empty tuple or array to an array that's
1515                    // non-empty should fail.
1516                    v.coerce(&aty0, &mut exec_state).unwrap_err();
1517                }
1518                _ => {
1519                    assert_coerce_results(
1520                        v,
1521                        &aty,
1522                        &KclValue::HomArray {
1523                            value: vec![v.clone()],
1524                            ty: ty.clone(),
1525                        },
1526                        &mut exec_state,
1527                    );
1528                    assert_coerce_results(
1529                        v,
1530                        &aty1,
1531                        &KclValue::HomArray {
1532                            value: vec![v.clone()],
1533                            ty: ty.clone(),
1534                        },
1535                        &mut exec_state,
1536                    );
1537                    assert_coerce_results(
1538                        v,
1539                        &aty0,
1540                        &KclValue::HomArray {
1541                            value: vec![v.clone()],
1542                            ty: ty.clone(),
1543                        },
1544                        &mut exec_state,
1545                    );
1546
1547                    // Tuple subtype
1548                    let tty = RuntimeType::Tuple(vec![ty.clone()]);
1549                    assert_coerce_results(
1550                        v,
1551                        &tty,
1552                        &KclValue::Tuple {
1553                            value: vec![v.clone()],
1554                            meta: Vec::new(),
1555                        },
1556                        &mut exec_state,
1557                    );
1558                }
1559            }
1560        }
1561
1562        for v in &values[1..] {
1563            // Not a subtype
1564            v.coerce(&RuntimeType::Primitive(PrimitiveType::Boolean), &mut exec_state)
1565                .unwrap_err();
1566        }
1567    }
1568
1569    #[tokio::test(flavor = "multi_thread")]
1570    async fn coerce_none() {
1571        let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock(None).await);
1572        let none = KclValue::KclNone {
1573            value: crate::parsing::ast::types::KclNone::new(),
1574            meta: Vec::new(),
1575        };
1576
1577        let aty = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::None);
1578        let aty0 = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::Known(0));
1579        let aty1 = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::Known(1));
1580        let aty1p = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::NonEmpty);
1581        assert_coerce_results(
1582            &none,
1583            &aty,
1584            &KclValue::HomArray {
1585                value: Vec::new(),
1586                ty: RuntimeType::solid(),
1587            },
1588            &mut exec_state,
1589        );
1590        assert_coerce_results(
1591            &none,
1592            &aty0,
1593            &KclValue::HomArray {
1594                value: Vec::new(),
1595                ty: RuntimeType::solid(),
1596            },
1597            &mut exec_state,
1598        );
1599        none.coerce(&aty1, &mut exec_state).unwrap_err();
1600        none.coerce(&aty1p, &mut exec_state).unwrap_err();
1601
1602        let tty = RuntimeType::Tuple(vec![]);
1603        let tty1 = RuntimeType::Tuple(vec![RuntimeType::solid()]);
1604        assert_coerce_results(
1605            &none,
1606            &tty,
1607            &KclValue::Tuple {
1608                value: Vec::new(),
1609                meta: Vec::new(),
1610            },
1611            &mut exec_state,
1612        );
1613        none.coerce(&tty1, &mut exec_state).unwrap_err();
1614
1615        let oty = RuntimeType::Object(vec![]);
1616        assert_coerce_results(
1617            &none,
1618            &oty,
1619            &KclValue::Object {
1620                value: HashMap::new(),
1621                meta: Vec::new(),
1622            },
1623            &mut exec_state,
1624        );
1625    }
1626
1627    #[tokio::test(flavor = "multi_thread")]
1628    async fn coerce_record() {
1629        let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock(None).await);
1630
1631        let obj0 = KclValue::Object {
1632            value: HashMap::new(),
1633            meta: Vec::new(),
1634        };
1635        let obj1 = KclValue::Object {
1636            value: [(
1637                "foo".to_owned(),
1638                KclValue::Bool {
1639                    value: true,
1640                    meta: Vec::new(),
1641                },
1642            )]
1643            .into(),
1644            meta: Vec::new(),
1645        };
1646        let obj2 = KclValue::Object {
1647            value: [
1648                (
1649                    "foo".to_owned(),
1650                    KclValue::Bool {
1651                        value: true,
1652                        meta: Vec::new(),
1653                    },
1654                ),
1655                (
1656                    "bar".to_owned(),
1657                    KclValue::Number {
1658                        value: 0.0,
1659                        ty: NumericType::count(),
1660                        meta: Vec::new(),
1661                    },
1662                ),
1663                (
1664                    "baz".to_owned(),
1665                    KclValue::Number {
1666                        value: 42.0,
1667                        ty: NumericType::count(),
1668                        meta: Vec::new(),
1669                    },
1670                ),
1671            ]
1672            .into(),
1673            meta: Vec::new(),
1674        };
1675
1676        let ty0 = RuntimeType::Object(vec![]);
1677        assert_coerce_results(&obj0, &ty0, &obj0, &mut exec_state);
1678        assert_coerce_results(&obj1, &ty0, &obj1, &mut exec_state);
1679        assert_coerce_results(&obj2, &ty0, &obj2, &mut exec_state);
1680
1681        let ty1 = RuntimeType::Object(vec![("foo".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean))]);
1682        obj0.coerce(&ty1, &mut exec_state).unwrap_err();
1683        assert_coerce_results(&obj1, &ty1, &obj1, &mut exec_state);
1684        assert_coerce_results(&obj2, &ty1, &obj2, &mut exec_state);
1685
1686        // Different ordering, (TODO - test for covariance once implemented)
1687        let ty2 = RuntimeType::Object(vec![
1688            (
1689                "bar".to_owned(),
1690                RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1691            ),
1692            ("foo".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean)),
1693        ]);
1694        obj0.coerce(&ty2, &mut exec_state).unwrap_err();
1695        obj1.coerce(&ty2, &mut exec_state).unwrap_err();
1696        assert_coerce_results(&obj2, &ty2, &obj2, &mut exec_state);
1697
1698        // field not present
1699        let tyq = RuntimeType::Object(vec![("qux".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean))]);
1700        obj0.coerce(&tyq, &mut exec_state).unwrap_err();
1701        obj1.coerce(&tyq, &mut exec_state).unwrap_err();
1702        obj2.coerce(&tyq, &mut exec_state).unwrap_err();
1703
1704        // field with different type
1705        let ty1 = RuntimeType::Object(vec![("bar".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean))]);
1706        obj2.coerce(&ty1, &mut exec_state).unwrap_err();
1707    }
1708
1709    #[tokio::test(flavor = "multi_thread")]
1710    async fn coerce_array() {
1711        let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock(None).await);
1712
1713        let hom_arr = KclValue::HomArray {
1714            value: vec![
1715                KclValue::Number {
1716                    value: 0.0,
1717                    ty: NumericType::count(),
1718                    meta: Vec::new(),
1719                },
1720                KclValue::Number {
1721                    value: 1.0,
1722                    ty: NumericType::count(),
1723                    meta: Vec::new(),
1724                },
1725                KclValue::Number {
1726                    value: 2.0,
1727                    ty: NumericType::count(),
1728                    meta: Vec::new(),
1729                },
1730                KclValue::Number {
1731                    value: 3.0,
1732                    ty: NumericType::count(),
1733                    meta: Vec::new(),
1734                },
1735            ],
1736            ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1737        };
1738        let mixed1 = KclValue::Tuple {
1739            value: vec![
1740                KclValue::Number {
1741                    value: 0.0,
1742                    ty: NumericType::count(),
1743                    meta: Vec::new(),
1744                },
1745                KclValue::Number {
1746                    value: 1.0,
1747                    ty: NumericType::count(),
1748                    meta: Vec::new(),
1749                },
1750            ],
1751            meta: Vec::new(),
1752        };
1753        let mixed2 = KclValue::Tuple {
1754            value: vec![
1755                KclValue::Number {
1756                    value: 0.0,
1757                    ty: NumericType::count(),
1758                    meta: Vec::new(),
1759                },
1760                KclValue::Bool {
1761                    value: true,
1762                    meta: Vec::new(),
1763                },
1764            ],
1765            meta: Vec::new(),
1766        };
1767
1768        // Principal types
1769        let tyh = RuntimeType::Array(
1770            Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
1771            ArrayLen::Known(4),
1772        );
1773        let tym1 = RuntimeType::Tuple(vec![
1774            RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1775            RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1776        ]);
1777        let tym2 = RuntimeType::Tuple(vec![
1778            RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1779            RuntimeType::Primitive(PrimitiveType::Boolean),
1780        ]);
1781        assert_coerce_results(&hom_arr, &tyh, &hom_arr, &mut exec_state);
1782        assert_coerce_results(&mixed1, &tym1, &mixed1, &mut exec_state);
1783        assert_coerce_results(&mixed2, &tym2, &mixed2, &mut exec_state);
1784        mixed1.coerce(&tym2, &mut exec_state).unwrap_err();
1785        mixed2.coerce(&tym1, &mut exec_state).unwrap_err();
1786
1787        // Length subtyping
1788        let tyhn = RuntimeType::Array(
1789            Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
1790            ArrayLen::None,
1791        );
1792        let tyh1 = RuntimeType::Array(
1793            Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
1794            ArrayLen::NonEmpty,
1795        );
1796        let tyh3 = RuntimeType::Array(
1797            Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
1798            ArrayLen::Known(3),
1799        );
1800        assert_coerce_results(&hom_arr, &tyhn, &hom_arr, &mut exec_state);
1801        assert_coerce_results(&hom_arr, &tyh1, &hom_arr, &mut exec_state);
1802        hom_arr.coerce(&tyh3, &mut exec_state).unwrap_err();
1803
1804        let hom_arr0 = KclValue::HomArray {
1805            value: vec![],
1806            ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1807        };
1808        assert_coerce_results(&hom_arr0, &tyhn, &hom_arr0, &mut exec_state);
1809        hom_arr0.coerce(&tyh1, &mut exec_state).unwrap_err();
1810        hom_arr0.coerce(&tyh3, &mut exec_state).unwrap_err();
1811
1812        // Covariance
1813        // let tyh = RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any))), ArrayLen::Known(4));
1814        let tym1 = RuntimeType::Tuple(vec![
1815            RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
1816            RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1817        ]);
1818        let tym2 = RuntimeType::Tuple(vec![
1819            RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
1820            RuntimeType::Primitive(PrimitiveType::Boolean),
1821        ]);
1822        // TODO implement covariance for homogeneous arrays
1823        // assert_coerce_results(&hom_arr, &tyh, &hom_arr, &mut exec_state);
1824        assert_coerce_results(&mixed1, &tym1, &mixed1, &mut exec_state);
1825        assert_coerce_results(&mixed2, &tym2, &mixed2, &mut exec_state);
1826
1827        // Mixed to homogeneous
1828        let hom_arr_2 = KclValue::HomArray {
1829            value: vec![
1830                KclValue::Number {
1831                    value: 0.0,
1832                    ty: NumericType::count(),
1833                    meta: Vec::new(),
1834                },
1835                KclValue::Number {
1836                    value: 1.0,
1837                    ty: NumericType::count(),
1838                    meta: Vec::new(),
1839                },
1840            ],
1841            ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1842        };
1843        let mixed0 = KclValue::Tuple {
1844            value: vec![],
1845            meta: Vec::new(),
1846        };
1847        assert_coerce_results(&mixed1, &tyhn, &hom_arr_2, &mut exec_state);
1848        assert_coerce_results(&mixed1, &tyh1, &hom_arr_2, &mut exec_state);
1849        assert_coerce_results(&mixed0, &tyhn, &hom_arr0, &mut exec_state);
1850        mixed0.coerce(&tyh, &mut exec_state).unwrap_err();
1851        mixed0.coerce(&tyh1, &mut exec_state).unwrap_err();
1852
1853        // Homogehous to mixed
1854        assert_coerce_results(&hom_arr_2, &tym1, &mixed1, &mut exec_state);
1855        hom_arr.coerce(&tym1, &mut exec_state).unwrap_err();
1856        hom_arr_2.coerce(&tym2, &mut exec_state).unwrap_err();
1857
1858        mixed0.coerce(&tym1, &mut exec_state).unwrap_err();
1859        mixed0.coerce(&tym2, &mut exec_state).unwrap_err();
1860    }
1861
1862    #[tokio::test(flavor = "multi_thread")]
1863    async fn coerce_union() {
1864        let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock(None).await);
1865
1866        // Subtyping smaller unions
1867        assert!(RuntimeType::Union(vec![]).subtype(&RuntimeType::Union(vec![
1868            RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
1869            RuntimeType::Primitive(PrimitiveType::Boolean)
1870        ])));
1871        assert!(
1872            RuntimeType::Union(vec![RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any))]).subtype(
1873                &RuntimeType::Union(vec![
1874                    RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
1875                    RuntimeType::Primitive(PrimitiveType::Boolean)
1876                ])
1877            )
1878        );
1879        assert!(RuntimeType::Union(vec![
1880            RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
1881            RuntimeType::Primitive(PrimitiveType::Boolean)
1882        ])
1883        .subtype(&RuntimeType::Union(vec![
1884            RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
1885            RuntimeType::Primitive(PrimitiveType::Boolean)
1886        ])));
1887
1888        // Covariance
1889        let count = KclValue::Number {
1890            value: 1.0,
1891            ty: NumericType::count(),
1892            meta: Vec::new(),
1893        };
1894
1895        let tya = RuntimeType::Union(vec![RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any))]);
1896        let tya2 = RuntimeType::Union(vec![
1897            RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
1898            RuntimeType::Primitive(PrimitiveType::Boolean),
1899        ]);
1900        assert_coerce_results(&count, &tya, &count, &mut exec_state);
1901        assert_coerce_results(&count, &tya2, &count, &mut exec_state);
1902
1903        // No matching type
1904        let tyb = RuntimeType::Union(vec![RuntimeType::Primitive(PrimitiveType::Boolean)]);
1905        let tyb2 = RuntimeType::Union(vec![
1906            RuntimeType::Primitive(PrimitiveType::Boolean),
1907            RuntimeType::Primitive(PrimitiveType::String),
1908        ]);
1909        count.coerce(&tyb, &mut exec_state).unwrap_err();
1910        count.coerce(&tyb2, &mut exec_state).unwrap_err();
1911    }
1912
1913    #[tokio::test(flavor = "multi_thread")]
1914    async fn coerce_axes() {
1915        let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock(None).await);
1916
1917        // Subtyping
1918        assert!(RuntimeType::Primitive(PrimitiveType::Axis2d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis2d)));
1919        assert!(RuntimeType::Primitive(PrimitiveType::Axis3d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis3d)));
1920        assert!(!RuntimeType::Primitive(PrimitiveType::Axis3d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis2d)));
1921        assert!(!RuntimeType::Primitive(PrimitiveType::Axis2d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis3d)));
1922
1923        // Coercion
1924        let a2d = KclValue::Object {
1925            value: [
1926                (
1927                    "origin".to_owned(),
1928                    KclValue::HomArray {
1929                        value: vec![
1930                            KclValue::Number {
1931                                value: 0.0,
1932                                ty: NumericType::mm(),
1933                                meta: Vec::new(),
1934                            },
1935                            KclValue::Number {
1936                                value: 0.0,
1937                                ty: NumericType::mm(),
1938                                meta: Vec::new(),
1939                            },
1940                        ],
1941                        ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
1942                    },
1943                ),
1944                (
1945                    "direction".to_owned(),
1946                    KclValue::HomArray {
1947                        value: vec![
1948                            KclValue::Number {
1949                                value: 1.0,
1950                                ty: NumericType::mm(),
1951                                meta: Vec::new(),
1952                            },
1953                            KclValue::Number {
1954                                value: 0.0,
1955                                ty: NumericType::mm(),
1956                                meta: Vec::new(),
1957                            },
1958                        ],
1959                        ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
1960                    },
1961                ),
1962            ]
1963            .into(),
1964            meta: Vec::new(),
1965        };
1966        let a3d = KclValue::Object {
1967            value: [
1968                (
1969                    "origin".to_owned(),
1970                    KclValue::HomArray {
1971                        value: vec![
1972                            KclValue::Number {
1973                                value: 0.0,
1974                                ty: NumericType::mm(),
1975                                meta: Vec::new(),
1976                            },
1977                            KclValue::Number {
1978                                value: 0.0,
1979                                ty: NumericType::mm(),
1980                                meta: Vec::new(),
1981                            },
1982                            KclValue::Number {
1983                                value: 0.0,
1984                                ty: NumericType::mm(),
1985                                meta: Vec::new(),
1986                            },
1987                        ],
1988                        ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
1989                    },
1990                ),
1991                (
1992                    "direction".to_owned(),
1993                    KclValue::HomArray {
1994                        value: vec![
1995                            KclValue::Number {
1996                                value: 1.0,
1997                                ty: NumericType::mm(),
1998                                meta: Vec::new(),
1999                            },
2000                            KclValue::Number {
2001                                value: 0.0,
2002                                ty: NumericType::mm(),
2003                                meta: Vec::new(),
2004                            },
2005                            KclValue::Number {
2006                                value: 1.0,
2007                                ty: NumericType::mm(),
2008                                meta: Vec::new(),
2009                            },
2010                        ],
2011                        ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
2012                    },
2013                ),
2014            ]
2015            .into(),
2016            meta: Vec::new(),
2017        };
2018
2019        let ty2d = RuntimeType::Primitive(PrimitiveType::Axis2d);
2020        let ty3d = RuntimeType::Primitive(PrimitiveType::Axis3d);
2021
2022        assert_coerce_results(&a2d, &ty2d, &a2d, &mut exec_state);
2023        assert_coerce_results(&a3d, &ty3d, &a3d, &mut exec_state);
2024        assert_coerce_results(&a3d, &ty2d, &a2d, &mut exec_state);
2025        a2d.coerce(&ty3d, &mut exec_state).unwrap_err();
2026    }
2027
2028    #[tokio::test(flavor = "multi_thread")]
2029    async fn coerce_numeric() {
2030        let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock(None).await);
2031
2032        let count = KclValue::Number {
2033            value: 1.0,
2034            ty: NumericType::count(),
2035            meta: Vec::new(),
2036        };
2037        let mm = KclValue::Number {
2038            value: 1.0,
2039            ty: NumericType::mm(),
2040            meta: Vec::new(),
2041        };
2042        let inches = KclValue::Number {
2043            value: 1.0,
2044            ty: NumericType::Known(UnitType::Length(UnitLen::Inches)),
2045            meta: Vec::new(),
2046        };
2047        let rads = KclValue::Number {
2048            value: 1.0,
2049            ty: NumericType::Known(UnitType::Angle(UnitAngle::Radians)),
2050            meta: Vec::new(),
2051        };
2052        let default = KclValue::Number {
2053            value: 1.0,
2054            ty: NumericType::default(),
2055            meta: Vec::new(),
2056        };
2057        let any = KclValue::Number {
2058            value: 1.0,
2059            ty: NumericType::Any,
2060            meta: Vec::new(),
2061        };
2062        let unknown = KclValue::Number {
2063            value: 1.0,
2064            ty: NumericType::Unknown,
2065            meta: Vec::new(),
2066        };
2067
2068        // Trivial coercions
2069        assert_coerce_results(&count, &NumericType::count().into(), &count, &mut exec_state);
2070        assert_coerce_results(&mm, &NumericType::mm().into(), &mm, &mut exec_state);
2071        assert_coerce_results(&any, &NumericType::Any.into(), &any, &mut exec_state);
2072        assert_coerce_results(&unknown, &NumericType::Unknown.into(), &unknown, &mut exec_state);
2073        assert_coerce_results(&default, &NumericType::default().into(), &default, &mut exec_state);
2074
2075        assert_coerce_results(&count, &NumericType::Any.into(), &count, &mut exec_state);
2076        assert_coerce_results(&mm, &NumericType::Any.into(), &mm, &mut exec_state);
2077        assert_coerce_results(&unknown, &NumericType::Any.into(), &unknown, &mut exec_state);
2078        assert_coerce_results(&default, &NumericType::Any.into(), &default, &mut exec_state);
2079
2080        assert_eq!(
2081            default
2082                .coerce(
2083                    &NumericType::Default {
2084                        len: UnitLen::Yards,
2085                        angle: UnitAngle::default()
2086                    }
2087                    .into(),
2088                    &mut exec_state
2089                )
2090                .unwrap(),
2091            default
2092        );
2093
2094        // No coercion
2095        count.coerce(&NumericType::mm().into(), &mut exec_state).unwrap_err();
2096        mm.coerce(&NumericType::count().into(), &mut exec_state).unwrap_err();
2097        unknown.coerce(&NumericType::mm().into(), &mut exec_state).unwrap_err();
2098        unknown
2099            .coerce(&NumericType::default().into(), &mut exec_state)
2100            .unwrap_err();
2101
2102        count.coerce(&NumericType::Unknown.into(), &mut exec_state).unwrap_err();
2103        mm.coerce(&NumericType::Unknown.into(), &mut exec_state).unwrap_err();
2104        default
2105            .coerce(&NumericType::Unknown.into(), &mut exec_state)
2106            .unwrap_err();
2107
2108        assert_eq!(
2109            inches
2110                .coerce(&NumericType::mm().into(), &mut exec_state)
2111                .unwrap()
2112                .as_f64()
2113                .unwrap()
2114                .round(),
2115            25.0
2116        );
2117        assert_eq!(
2118            rads.coerce(
2119                &NumericType::Known(UnitType::Angle(UnitAngle::Degrees)).into(),
2120                &mut exec_state
2121            )
2122            .unwrap()
2123            .as_f64()
2124            .unwrap()
2125            .round(),
2126            57.0
2127        );
2128        assert_eq!(
2129            inches
2130                .coerce(&NumericType::default().into(), &mut exec_state)
2131                .unwrap()
2132                .as_f64()
2133                .unwrap()
2134                .round(),
2135            1.0
2136        );
2137        assert_eq!(
2138            rads.coerce(&NumericType::default().into(), &mut exec_state)
2139                .unwrap()
2140                .as_f64()
2141                .unwrap()
2142                .round(),
2143            1.0
2144        );
2145    }
2146
2147    #[track_caller]
2148    fn assert_value_and_type(name: &str, result: &ExecTestResults, expected: f64, expected_ty: NumericType) {
2149        let mem = result.exec_state.stack();
2150        match mem
2151            .memory
2152            .get_from(name, result.mem_env, SourceRange::default(), 0)
2153            .unwrap()
2154        {
2155            KclValue::Number { value, ty, .. } => {
2156                assert_eq!(value.round(), expected);
2157                assert_eq!(*ty, expected_ty);
2158            }
2159            _ => unreachable!(),
2160        }
2161    }
2162
2163    #[tokio::test(flavor = "multi_thread")]
2164    async fn combine_numeric() {
2165        let program = r#"a = 5 + 4
2166b = 5 - 2
2167c = 5mm - 2mm + 10mm
2168d = 5mm - 2 + 10
2169e = 5 - 2mm + 10
2170f = 30mm - 1inch
2171
2172g = 2 * 10
2173h = 2 * 10mm
2174i = 2mm * 10mm
2175j = 2_ * 10
2176k = 2_ * 3mm * 3mm
2177
2178l = 1 / 10
2179m = 2mm / 1mm
2180n = 10inch / 2mm
2181o = 3mm / 3
2182p = 3_ / 4
2183q = 4inch / 2_
2184
2185r = min([0, 3, 42])
2186s = min([0, 3mm, -42])
2187t = min([100, 3in, 142mm])
2188u = min([3rad, 4in])
2189"#;
2190
2191        let result = parse_execute(program).await.unwrap();
2192        assert_eq!(
2193            result.exec_state.errors().len(),
2194            5,
2195            "errors: {:?}",
2196            result.exec_state.errors()
2197        );
2198
2199        assert_value_and_type("a", &result, 9.0, NumericType::default());
2200        assert_value_and_type("b", &result, 3.0, NumericType::default());
2201        assert_value_and_type("c", &result, 13.0, NumericType::mm());
2202        assert_value_and_type("d", &result, 13.0, NumericType::mm());
2203        assert_value_and_type("e", &result, 13.0, NumericType::mm());
2204        assert_value_and_type("f", &result, 5.0, NumericType::mm());
2205
2206        assert_value_and_type("g", &result, 20.0, NumericType::default());
2207        assert_value_and_type("h", &result, 20.0, NumericType::mm());
2208        assert_value_and_type("i", &result, 20.0, NumericType::Unknown);
2209        assert_value_and_type("j", &result, 20.0, NumericType::default());
2210        assert_value_and_type("k", &result, 18.0, NumericType::Unknown);
2211
2212        assert_value_and_type("l", &result, 0.0, NumericType::default());
2213        assert_value_and_type("m", &result, 2.0, NumericType::count());
2214        assert_value_and_type("n", &result, 5.0, NumericType::Unknown);
2215        assert_value_and_type("o", &result, 1.0, NumericType::mm());
2216        assert_value_and_type("p", &result, 1.0, NumericType::count());
2217        assert_value_and_type("q", &result, 2.0, NumericType::Known(UnitType::Length(UnitLen::Inches)));
2218
2219        assert_value_and_type("r", &result, 0.0, NumericType::default());
2220        assert_value_and_type("s", &result, -42.0, NumericType::mm());
2221        assert_value_and_type("t", &result, 3.0, NumericType::Unknown);
2222        assert_value_and_type("u", &result, 3.0, NumericType::Unknown);
2223    }
2224
2225    #[tokio::test(flavor = "multi_thread")]
2226    async fn bad_typed_arithmetic() {
2227        let program = r#"
2228a = 1rad
2229b = 180 / PI * a + 360
2230"#;
2231
2232        let result = parse_execute(program).await.unwrap();
2233
2234        assert_value_and_type("a", &result, 1.0, NumericType::radians());
2235        assert_value_and_type("b", &result, 417.0, NumericType::Unknown);
2236    }
2237
2238    #[tokio::test(flavor = "multi_thread")]
2239    async fn cos_coercions() {
2240        let program = r#"
2241a = cos(units::toRadians(30))
2242b = 3 / a
2243c = cos(30deg)
2244d = cos(30)
2245"#;
2246
2247        let result = parse_execute(program).await.unwrap();
2248        assert!(result.exec_state.errors().is_empty());
2249
2250        assert_value_and_type("a", &result, 1.0, NumericType::count());
2251        assert_value_and_type("b", &result, 3.0, NumericType::default());
2252        assert_value_and_type("c", &result, 1.0, NumericType::count());
2253        assert_value_and_type("d", &result, 1.0, NumericType::count());
2254    }
2255
2256    #[tokio::test(flavor = "multi_thread")]
2257    async fn coerce_nested_array() {
2258        let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock(None).await);
2259
2260        let mixed1 = KclValue::HomArray {
2261            value: vec![
2262                KclValue::Number {
2263                    value: 0.0,
2264                    ty: NumericType::count(),
2265                    meta: Vec::new(),
2266                },
2267                KclValue::Number {
2268                    value: 1.0,
2269                    ty: NumericType::count(),
2270                    meta: Vec::new(),
2271                },
2272                KclValue::HomArray {
2273                    value: vec![
2274                        KclValue::Number {
2275                            value: 2.0,
2276                            ty: NumericType::count(),
2277                            meta: Vec::new(),
2278                        },
2279                        KclValue::Number {
2280                            value: 3.0,
2281                            ty: NumericType::count(),
2282                            meta: Vec::new(),
2283                        },
2284                    ],
2285                    ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2286                },
2287            ],
2288            ty: RuntimeType::any(),
2289        };
2290
2291        // Principal types
2292        let tym1 = RuntimeType::Array(
2293            Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2294            ArrayLen::NonEmpty,
2295        );
2296
2297        let result = KclValue::HomArray {
2298            value: vec![
2299                KclValue::Number {
2300                    value: 0.0,
2301                    ty: NumericType::count(),
2302                    meta: Vec::new(),
2303                },
2304                KclValue::Number {
2305                    value: 1.0,
2306                    ty: NumericType::count(),
2307                    meta: Vec::new(),
2308                },
2309                KclValue::Number {
2310                    value: 2.0,
2311                    ty: NumericType::count(),
2312                    meta: Vec::new(),
2313                },
2314                KclValue::Number {
2315                    value: 3.0,
2316                    ty: NumericType::count(),
2317                    meta: Vec::new(),
2318                },
2319            ],
2320            ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2321        };
2322        assert_coerce_results(&mixed1, &tym1, &result, &mut exec_state);
2323    }
2324}