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