kcl_lib/execution/
types.rs

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