kcl_lib/execution/
types.rs

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