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, Point3d,
12    },
13    parsing::{
14        ast::types::{PrimitiveType as AstPrimitiveType, Type},
15        token::NumericSuffix,
16    },
17    std::args::FromKclValue,
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 sketch() -> Self {
32        RuntimeType::Primitive(PrimitiveType::Sketch)
33    }
34
35    /// `[Sketch; 1+]`
36    pub fn sketches() -> Self {
37        RuntimeType::Array(
38            Box::new(RuntimeType::Primitive(PrimitiveType::Sketch)),
39            ArrayLen::NonEmpty,
40        )
41    }
42
43    /// `[Solid; 1+]`
44    pub fn solids() -> Self {
45        RuntimeType::Array(
46            Box::new(RuntimeType::Primitive(PrimitiveType::Solid)),
47            ArrayLen::NonEmpty,
48        )
49    }
50
51    pub fn solid() -> Self {
52        RuntimeType::Primitive(PrimitiveType::Solid)
53    }
54
55    pub fn imported() -> Self {
56        RuntimeType::Primitive(PrimitiveType::ImportedGeometry)
57    }
58
59    pub fn from_parsed(
60        value: Type,
61        exec_state: &mut ExecState,
62        source_range: SourceRange,
63    ) -> Result<Self, CompilationError> {
64        match value {
65            Type::Primitive(pt) => Self::from_parsed_primitive(pt, exec_state, source_range),
66            Type::Array { ty, len } => {
67                Self::from_parsed_primitive(ty, exec_state, source_range).map(|t| RuntimeType::Array(Box::new(t), len))
68            }
69            Type::Union { tys } => tys
70                .into_iter()
71                .map(|t| Self::from_parsed_primitive(t.inner, exec_state, source_range))
72                .collect::<Result<Vec<_>, CompilationError>>()
73                .map(RuntimeType::Union),
74            Type::Object { properties } => properties
75                .into_iter()
76                .map(|p| {
77                    RuntimeType::from_parsed(p.type_.unwrap().inner, exec_state, source_range)
78                        .map(|ty| (p.identifier.inner.name, ty))
79                })
80                .collect::<Result<Vec<_>, CompilationError>>()
81                .map(RuntimeType::Object),
82        }
83    }
84
85    fn from_parsed_primitive(
86        value: AstPrimitiveType,
87        exec_state: &mut ExecState,
88        source_range: SourceRange,
89    ) -> Result<Self, CompilationError> {
90        Ok(match value {
91            AstPrimitiveType::String => RuntimeType::Primitive(PrimitiveType::String),
92            AstPrimitiveType::Boolean => RuntimeType::Primitive(PrimitiveType::Boolean),
93            AstPrimitiveType::Number(suffix) => RuntimeType::Primitive(PrimitiveType::Number(
94                NumericType::from_parsed(suffix, &exec_state.mod_local.settings),
95            )),
96            AstPrimitiveType::Named(name) => {
97                let ty_val = exec_state
98                    .stack()
99                    .get(&format!("{}{}", memory::TYPE_PREFIX, name.name), source_range)
100                    .map_err(|_| CompilationError::err(source_range, format!("Unknown type: {}", name.name)))?;
101
102                match ty_val {
103                    KclValue::Type { value, .. } => match value {
104                        TypeDef::RustRepr(ty, _) => RuntimeType::Primitive(ty.clone()),
105                        TypeDef::Alias(ty) => ty.clone(),
106                    },
107                    _ => unreachable!(),
108                }
109            }
110            AstPrimitiveType::Tag => RuntimeType::Primitive(PrimitiveType::Tag),
111        })
112    }
113
114    pub fn human_friendly_type(&self) -> String {
115        match self {
116            RuntimeType::Primitive(ty) => ty.to_string(),
117            RuntimeType::Array(ty, ArrayLen::None) => format!("an array of {}", ty.display_multiple()),
118            RuntimeType::Array(ty, ArrayLen::NonEmpty) => format!("one or more {}", ty.display_multiple()),
119            RuntimeType::Array(ty, ArrayLen::Known(n)) => format!("an array of {n} {}", ty.display_multiple()),
120            RuntimeType::Union(tys) => tys
121                .iter()
122                .map(Self::human_friendly_type)
123                .collect::<Vec<_>>()
124                .join(" or "),
125            RuntimeType::Tuple(tys) => format!(
126                "an array with values of types ({})",
127                tys.iter().map(Self::human_friendly_type).collect::<Vec<_>>().join(", ")
128            ),
129            RuntimeType::Object(_) => format!("an object with fields {}", self),
130        }
131    }
132
133    // Subtype with no coercion, including refining numeric types.
134    fn subtype(&self, sup: &RuntimeType) -> bool {
135        use RuntimeType::*;
136
137        match (self, sup) {
138            (Primitive(t1), Primitive(t2)) => t1.subtype(t2),
139            (Array(t1, l1), Array(t2, l2)) => t1.subtype(t2) && l1.subtype(*l2),
140            (Tuple(t1), Tuple(t2)) => t1.len() == t2.len() && t1.iter().zip(t2).all(|(t1, t2)| t1.subtype(t2)),
141            (Union(ts1), Union(ts2)) => ts1.iter().all(|t| ts2.contains(t)),
142            (t1, Union(ts2)) => ts2.iter().any(|t| t1.subtype(t)),
143            (Object(t1), Object(t2)) => t2
144                .iter()
145                .all(|(f, t)| t1.iter().any(|(ff, tt)| f == ff && tt.subtype(t))),
146            _ => false,
147        }
148    }
149
150    fn display_multiple(&self) -> String {
151        match self {
152            RuntimeType::Primitive(ty) => ty.display_multiple(),
153            RuntimeType::Array(..) => "arrays".to_owned(),
154            RuntimeType::Union(tys) => tys
155                .iter()
156                .map(|t| t.display_multiple())
157                .collect::<Vec<_>>()
158                .join(" or "),
159            RuntimeType::Tuple(_) => "arrays".to_owned(),
160            RuntimeType::Object(_) => format!("objects with fields {self}"),
161        }
162    }
163}
164
165impl fmt::Display for RuntimeType {
166    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
167        match self {
168            RuntimeType::Primitive(t) => t.fmt(f),
169            RuntimeType::Array(t, l) => match l {
170                ArrayLen::None => write!(f, "[{t}]"),
171                ArrayLen::NonEmpty => write!(f, "[{t}; 1+]"),
172                ArrayLen::Known(n) => write!(f, "[{t}; {n}]"),
173            },
174            RuntimeType::Tuple(ts) => write!(
175                f,
176                "[{}]",
177                ts.iter().map(|t| t.to_string()).collect::<Vec<_>>().join(", ")
178            ),
179            RuntimeType::Union(ts) => write!(
180                f,
181                "{}",
182                ts.iter().map(|t| t.to_string()).collect::<Vec<_>>().join(" | ")
183            ),
184            RuntimeType::Object(items) => write!(
185                f,
186                "{{ {} }}",
187                items
188                    .iter()
189                    .map(|(n, t)| format!("{n}: {t}"))
190                    .collect::<Vec<_>>()
191                    .join(", ")
192            ),
193        }
194    }
195}
196
197#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, ts_rs::TS, JsonSchema)]
198pub enum ArrayLen {
199    None,
200    NonEmpty,
201    Known(usize),
202}
203
204impl ArrayLen {
205    pub fn subtype(self, other: ArrayLen) -> bool {
206        match (self, other) {
207            (_, ArrayLen::None) => true,
208            (ArrayLen::NonEmpty, ArrayLen::NonEmpty) => true,
209            (ArrayLen::Known(size), ArrayLen::NonEmpty) if size > 0 => true,
210            (ArrayLen::Known(s1), ArrayLen::Known(s2)) if s1 == s2 => true,
211            _ => false,
212        }
213    }
214
215    /// True if the length constraint is satisfied by the supplied length.
216    fn satisfied(self, len: usize) -> bool {
217        match self {
218            ArrayLen::None => true,
219            ArrayLen::NonEmpty => len > 0,
220            ArrayLen::Known(s) => len == s,
221        }
222    }
223}
224
225#[derive(Debug, Clone, PartialEq)]
226pub enum PrimitiveType {
227    Number(NumericType),
228    String,
229    Boolean,
230    Tag,
231    Sketch,
232    Solid,
233    Plane,
234    Helix,
235    Face,
236    ImportedGeometry,
237}
238
239impl PrimitiveType {
240    fn display_multiple(&self) -> String {
241        match self {
242            PrimitiveType::Number(NumericType::Known(unit)) => format!("numbers({unit})"),
243            PrimitiveType::Number(_) => "numbers".to_owned(),
244            PrimitiveType::String => "strings".to_owned(),
245            PrimitiveType::Boolean => "bools".to_owned(),
246            PrimitiveType::Sketch => "Sketches".to_owned(),
247            PrimitiveType::Solid => "Solids".to_owned(),
248            PrimitiveType::Plane => "Planes".to_owned(),
249            PrimitiveType::Helix => "Helices".to_owned(),
250            PrimitiveType::Face => "Faces".to_owned(),
251            PrimitiveType::ImportedGeometry => "imported geometries".to_owned(),
252            PrimitiveType::Tag => "tags".to_owned(),
253        }
254    }
255
256    fn subtype(&self, other: &PrimitiveType) -> bool {
257        match (self, other) {
258            (PrimitiveType::Number(n1), PrimitiveType::Number(n2)) => n1.subtype(n2),
259            (t1, t2) => t1 == t2,
260        }
261    }
262}
263
264impl fmt::Display for PrimitiveType {
265    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
266        match self {
267            PrimitiveType::Number(NumericType::Known(unit)) => write!(f, "number({unit})"),
268            PrimitiveType::Number(_) => write!(f, "number"),
269            PrimitiveType::String => write!(f, "string"),
270            PrimitiveType::Boolean => write!(f, "bool"),
271            PrimitiveType::Tag => write!(f, "tag"),
272            PrimitiveType::Sketch => write!(f, "Sketch"),
273            PrimitiveType::Solid => write!(f, "Solid"),
274            PrimitiveType::Plane => write!(f, "Plane"),
275            PrimitiveType::Face => write!(f, "Face"),
276            PrimitiveType::Helix => write!(f, "Helix"),
277            PrimitiveType::ImportedGeometry => write!(f, "imported geometry"),
278        }
279    }
280}
281
282#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
283#[ts(export)]
284#[serde(tag = "type")]
285pub enum NumericType {
286    // Specified by the user (directly or indirectly)
287    Known(UnitType),
288    // Unspecified, using defaults
289    Default { len: UnitLen, angle: UnitAngle },
290    // Exceeded the ability of the type system to track.
291    Unknown,
292    // Type info has been explicitly cast away.
293    Any,
294}
295
296impl NumericType {
297    pub fn count() -> Self {
298        NumericType::Known(UnitType::Count)
299    }
300
301    /// Combine two types when we expect them to be equal.
302    pub fn combine_eq(self, other: &NumericType) -> NumericType {
303        if &self == other {
304            self
305        } else {
306            NumericType::Unknown
307        }
308    }
309
310    /// Combine n types when we expect them to be equal.
311    ///
312    /// Precondition: tys.len() > 0
313    pub fn combine_n_eq(tys: &[NumericType]) -> NumericType {
314        let ty0 = tys[0].clone();
315        for t in &tys[1..] {
316            if t != &ty0 {
317                return NumericType::Unknown;
318            }
319        }
320        ty0
321    }
322
323    /// Combine two types in addition-like operations.
324    pub fn combine_add(a: NumericType, b: NumericType) -> NumericType {
325        if a == b {
326            return a;
327        }
328        NumericType::Unknown
329    }
330
331    /// Combine two types in multiplication-like operations.
332    pub fn combine_mul(a: NumericType, b: NumericType) -> NumericType {
333        if a == NumericType::count() {
334            return b;
335        }
336        if b == NumericType::count() {
337            return a;
338        }
339        NumericType::Unknown
340    }
341
342    /// Combine two types in division-like operations.
343    pub fn combine_div(a: NumericType, b: NumericType) -> NumericType {
344        if b == NumericType::count() {
345            return a;
346        }
347        NumericType::Unknown
348    }
349
350    pub fn from_parsed(suffix: NumericSuffix, settings: &super::MetaSettings) -> Self {
351        match suffix {
352            NumericSuffix::None => NumericType::Default {
353                len: settings.default_length_units,
354                angle: settings.default_angle_units,
355            },
356            NumericSuffix::Count => NumericType::Known(UnitType::Count),
357            NumericSuffix::Mm => NumericType::Known(UnitType::Length(UnitLen::Mm)),
358            NumericSuffix::Cm => NumericType::Known(UnitType::Length(UnitLen::Cm)),
359            NumericSuffix::M => NumericType::Known(UnitType::Length(UnitLen::M)),
360            NumericSuffix::Inch => NumericType::Known(UnitType::Length(UnitLen::Inches)),
361            NumericSuffix::Ft => NumericType::Known(UnitType::Length(UnitLen::Feet)),
362            NumericSuffix::Yd => NumericType::Known(UnitType::Length(UnitLen::Yards)),
363            NumericSuffix::Deg => NumericType::Known(UnitType::Angle(UnitAngle::Degrees)),
364            NumericSuffix::Rad => NumericType::Known(UnitType::Angle(UnitAngle::Radians)),
365        }
366    }
367
368    fn subtype(&self, other: &NumericType) -> bool {
369        use NumericType::*;
370
371        match (self, other) {
372            (Unknown, _) | (_, Unknown) => false,
373            (a, b) if a == b => true,
374            (_, Any) => true,
375            (_, _) => false,
376        }
377    }
378}
379
380impl From<UnitLen> for NumericType {
381    fn from(value: UnitLen) -> Self {
382        NumericType::Known(UnitType::Length(value))
383    }
384}
385
386impl From<UnitAngle> for NumericType {
387    fn from(value: UnitAngle) -> Self {
388        NumericType::Known(UnitType::Angle(value))
389    }
390}
391
392#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, ts_rs::TS, JsonSchema)]
393#[ts(export)]
394#[serde(tag = "type")]
395pub enum UnitType {
396    Count,
397    Length(UnitLen),
398    Angle(UnitAngle),
399}
400
401impl std::fmt::Display for UnitType {
402    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
403        match self {
404            UnitType::Count => write!(f, "_"),
405            UnitType::Length(l) => l.fmt(f),
406            UnitType::Angle(a) => a.fmt(f),
407        }
408    }
409}
410
411// TODO called UnitLen so as not to clash with UnitLength in settings)
412/// A unit of length.
413#[derive(Debug, Default, Clone, Copy, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Eq)]
414#[ts(export)]
415#[serde(tag = "type")]
416pub enum UnitLen {
417    #[default]
418    Mm,
419    Cm,
420    M,
421    Inches,
422    Feet,
423    Yards,
424}
425
426impl std::fmt::Display for UnitLen {
427    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
428        match self {
429            UnitLen::Mm => write!(f, "mm"),
430            UnitLen::Cm => write!(f, "cm"),
431            UnitLen::M => write!(f, "m"),
432            UnitLen::Inches => write!(f, "in"),
433            UnitLen::Feet => write!(f, "ft"),
434            UnitLen::Yards => write!(f, "yd"),
435        }
436    }
437}
438
439impl TryFrom<NumericSuffix> for UnitLen {
440    type Error = ();
441
442    fn try_from(suffix: NumericSuffix) -> std::result::Result<Self, Self::Error> {
443        match suffix {
444            NumericSuffix::Mm => Ok(Self::Mm),
445            NumericSuffix::Cm => Ok(Self::Cm),
446            NumericSuffix::M => Ok(Self::M),
447            NumericSuffix::Inch => Ok(Self::Inches),
448            NumericSuffix::Ft => Ok(Self::Feet),
449            NumericSuffix::Yd => Ok(Self::Yards),
450            _ => Err(()),
451        }
452    }
453}
454
455impl From<crate::UnitLength> for UnitLen {
456    fn from(unit: crate::UnitLength) -> Self {
457        match unit {
458            crate::UnitLength::Cm => UnitLen::Cm,
459            crate::UnitLength::Ft => UnitLen::Feet,
460            crate::UnitLength::In => UnitLen::Inches,
461            crate::UnitLength::M => UnitLen::M,
462            crate::UnitLength::Mm => UnitLen::Mm,
463            crate::UnitLength::Yd => UnitLen::Yards,
464        }
465    }
466}
467
468impl From<UnitLen> for crate::UnitLength {
469    fn from(unit: UnitLen) -> Self {
470        match unit {
471            UnitLen::Cm => crate::UnitLength::Cm,
472            UnitLen::Feet => crate::UnitLength::Ft,
473            UnitLen::Inches => crate::UnitLength::In,
474            UnitLen::M => crate::UnitLength::M,
475            UnitLen::Mm => crate::UnitLength::Mm,
476            UnitLen::Yards => crate::UnitLength::Yd,
477        }
478    }
479}
480
481impl From<UnitLen> for kittycad_modeling_cmds::units::UnitLength {
482    fn from(unit: UnitLen) -> Self {
483        match unit {
484            UnitLen::Cm => kittycad_modeling_cmds::units::UnitLength::Centimeters,
485            UnitLen::Feet => kittycad_modeling_cmds::units::UnitLength::Feet,
486            UnitLen::Inches => kittycad_modeling_cmds::units::UnitLength::Inches,
487            UnitLen::M => kittycad_modeling_cmds::units::UnitLength::Meters,
488            UnitLen::Mm => kittycad_modeling_cmds::units::UnitLength::Millimeters,
489            UnitLen::Yards => kittycad_modeling_cmds::units::UnitLength::Yards,
490        }
491    }
492}
493
494/// A unit of angle.
495#[derive(Debug, Default, Clone, Copy, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Eq)]
496#[ts(export)]
497#[serde(tag = "type")]
498pub enum UnitAngle {
499    #[default]
500    Degrees,
501    Radians,
502}
503
504impl std::fmt::Display for UnitAngle {
505    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
506        match self {
507            UnitAngle::Degrees => write!(f, "deg"),
508            UnitAngle::Radians => write!(f, "rad"),
509        }
510    }
511}
512
513impl TryFrom<NumericSuffix> for UnitAngle {
514    type Error = ();
515
516    fn try_from(suffix: NumericSuffix) -> std::result::Result<Self, Self::Error> {
517        match suffix {
518            NumericSuffix::Deg => Ok(Self::Degrees),
519            NumericSuffix::Rad => Ok(Self::Radians),
520            _ => Err(()),
521        }
522    }
523}
524
525impl KclValue {
526    /// True if `self` has a type which is a subtype of `ty` without coercion.
527    pub fn has_type(&self, ty: &RuntimeType) -> bool {
528        let Some(self_ty) = self.principal_type() else {
529            return false;
530        };
531
532        self_ty.subtype(ty)
533    }
534
535    /// Coerce `self` to a new value which has `ty` as it's closest supertype.
536    ///
537    /// If the result is Some, then:
538    ///   - result.principal_type().unwrap().subtype(ty)
539    ///
540    /// If self.principal_type() == ty then result == self
541    pub fn coerce(&self, ty: &RuntimeType, exec_state: &mut ExecState) -> Option<KclValue> {
542        match ty {
543            RuntimeType::Primitive(ty) => self.coerce_to_primitive_type(ty, exec_state),
544            RuntimeType::Array(ty, len) => self.coerce_to_array_type(ty, *len, exec_state),
545            RuntimeType::Tuple(tys) => self.coerce_to_tuple_type(tys, exec_state),
546            RuntimeType::Union(tys) => self.coerce_to_union_type(tys, exec_state),
547            RuntimeType::Object(tys) => self.coerce_to_object_type(tys, exec_state),
548        }
549    }
550
551    fn coerce_to_primitive_type(&self, ty: &PrimitiveType, exec_state: &mut ExecState) -> Option<KclValue> {
552        let value = match self {
553            KclValue::MixedArray { value, .. } | KclValue::HomArray { value, .. } if value.len() == 1 => &value[0],
554            _ => self,
555        };
556        match ty {
557            // TODO numeric type coercions
558            PrimitiveType::Number(_ty) => match value {
559                KclValue::Number { .. } => Some(value.clone()),
560                _ => None,
561            },
562            PrimitiveType::String => match value {
563                KclValue::String { .. } => Some(value.clone()),
564                _ => None,
565            },
566            PrimitiveType::Boolean => match value {
567                KclValue::Bool { .. } => Some(value.clone()),
568                _ => None,
569            },
570            PrimitiveType::Sketch => match value {
571                KclValue::Sketch { .. } => Some(value.clone()),
572                _ => None,
573            },
574            PrimitiveType::Solid => match value {
575                KclValue::Solid { .. } => Some(value.clone()),
576                _ => None,
577            },
578            PrimitiveType::Plane => match value {
579                KclValue::Plane { .. } => Some(value.clone()),
580                KclValue::Object { value, meta } => {
581                    let origin = value.get("origin").and_then(Point3d::from_kcl_val)?;
582                    let x_axis = value.get("xAxis").and_then(Point3d::from_kcl_val)?;
583                    let y_axis = value.get("yAxis").and_then(Point3d::from_kcl_val)?;
584                    let z_axis = value.get("zAxis").and_then(Point3d::from_kcl_val)?;
585
586                    let id = exec_state.mod_local.id_generator.next_uuid();
587                    let plane = Plane {
588                        id,
589                        artifact_id: id.into(),
590                        origin,
591                        x_axis,
592                        y_axis,
593                        z_axis,
594                        value: super::PlaneType::Uninit,
595                        // TODO use length unit from origin
596                        units: exec_state.length_unit(),
597                        meta: meta.clone(),
598                    };
599
600                    Some(KclValue::Plane { value: Box::new(plane) })
601                }
602                _ => None,
603            },
604            PrimitiveType::Face => match value {
605                KclValue::Face { .. } => Some(value.clone()),
606                _ => None,
607            },
608            PrimitiveType::Helix => match value {
609                KclValue::Helix { .. } => Some(value.clone()),
610                _ => None,
611            },
612            PrimitiveType::ImportedGeometry => match value {
613                KclValue::ImportedGeometry { .. } => Some(value.clone()),
614                _ => None,
615            },
616            PrimitiveType::Tag => match value {
617                KclValue::TagDeclarator { .. } => Some(value.clone()),
618                KclValue::TagIdentifier { .. } => Some(value.clone()),
619                _ => None,
620            },
621        }
622    }
623
624    fn coerce_to_array_type(&self, ty: &RuntimeType, len: ArrayLen, exec_state: &mut ExecState) -> Option<KclValue> {
625        match self {
626            KclValue::HomArray { value, ty: aty } if aty == ty => {
627                let value = match len {
628                    ArrayLen::None => value.clone(),
629                    ArrayLen::NonEmpty => {
630                        if value.is_empty() {
631                            return None;
632                        }
633
634                        value.clone()
635                    }
636                    ArrayLen::Known(n) => {
637                        if n != value.len() {
638                            return None;
639                        }
640
641                        value[..n].to_vec()
642                    }
643                };
644
645                Some(KclValue::HomArray { value, ty: ty.clone() })
646            }
647            value if len.satisfied(1) && value.has_type(ty) => Some(KclValue::HomArray {
648                value: vec![value.clone()],
649                ty: ty.clone(),
650            }),
651            KclValue::MixedArray { value, .. } => {
652                let value = match len {
653                    ArrayLen::None => value.clone(),
654                    ArrayLen::NonEmpty => {
655                        if value.is_empty() {
656                            return None;
657                        }
658
659                        value.clone()
660                    }
661                    ArrayLen::Known(n) => {
662                        if n != value.len() {
663                            return None;
664                        }
665
666                        value[..n].to_vec()
667                    }
668                };
669
670                let value = value
671                    .iter()
672                    .map(|v| v.coerce(ty, exec_state))
673                    .collect::<Option<Vec<_>>>()?;
674
675                Some(KclValue::HomArray { value, ty: ty.clone() })
676            }
677            KclValue::KclNone { .. } if len.satisfied(0) => Some(KclValue::HomArray {
678                value: Vec::new(),
679                ty: ty.clone(),
680            }),
681            _ => None,
682        }
683    }
684
685    fn coerce_to_tuple_type(&self, tys: &[RuntimeType], exec_state: &mut ExecState) -> Option<KclValue> {
686        match self {
687            KclValue::MixedArray { value, .. } | KclValue::HomArray { value, .. } if value.len() == tys.len() => {
688                let mut result = Vec::new();
689                for (i, t) in tys.iter().enumerate() {
690                    result.push(value[i].coerce(t, exec_state)?);
691                }
692
693                Some(KclValue::MixedArray {
694                    value: result,
695                    meta: Vec::new(),
696                })
697            }
698            KclValue::KclNone { meta, .. } if tys.is_empty() => Some(KclValue::MixedArray {
699                value: Vec::new(),
700                meta: meta.clone(),
701            }),
702            value if tys.len() == 1 && value.has_type(&tys[0]) => Some(KclValue::MixedArray {
703                value: vec![value.clone()],
704                meta: Vec::new(),
705            }),
706            _ => None,
707        }
708    }
709
710    fn coerce_to_union_type(&self, tys: &[RuntimeType], exec_state: &mut ExecState) -> Option<KclValue> {
711        for t in tys {
712            if let Some(v) = self.coerce(t, exec_state) {
713                return Some(v);
714            }
715        }
716
717        None
718    }
719
720    fn coerce_to_object_type(&self, tys: &[(String, RuntimeType)], _exec_state: &mut ExecState) -> Option<KclValue> {
721        match self {
722            KclValue::Object { value, .. } => {
723                for (s, t) in tys {
724                    // TODO coerce fields
725                    if !value.get(s)?.has_type(t) {
726                        return None;
727                    }
728                }
729                // TODO remove non-required fields
730                Some(self.clone())
731            }
732            KclValue::KclNone { meta, .. } if tys.is_empty() => Some(KclValue::Object {
733                value: HashMap::new(),
734                meta: meta.clone(),
735            }),
736            _ => None,
737        }
738    }
739
740    pub fn principal_type(&self) -> Option<RuntimeType> {
741        match self {
742            KclValue::Bool { .. } => Some(RuntimeType::Primitive(PrimitiveType::Boolean)),
743            KclValue::Number { ty, .. } => Some(RuntimeType::Primitive(PrimitiveType::Number(ty.clone()))),
744            KclValue::String { .. } => Some(RuntimeType::Primitive(PrimitiveType::String)),
745            KclValue::Object { value, .. } => {
746                let properties = value
747                    .iter()
748                    .map(|(k, v)| v.principal_type().map(|t| (k.clone(), t)))
749                    .collect::<Option<Vec<_>>>()?;
750                Some(RuntimeType::Object(properties))
751            }
752            KclValue::Plane { .. } => Some(RuntimeType::Primitive(PrimitiveType::Plane)),
753            KclValue::Sketch { .. } => Some(RuntimeType::Primitive(PrimitiveType::Sketch)),
754            KclValue::Solid { .. } => Some(RuntimeType::Primitive(PrimitiveType::Solid)),
755            KclValue::Face { .. } => Some(RuntimeType::Primitive(PrimitiveType::Face)),
756            KclValue::Helix { .. } => Some(RuntimeType::Primitive(PrimitiveType::Helix)),
757            KclValue::ImportedGeometry(..) => Some(RuntimeType::Primitive(PrimitiveType::ImportedGeometry)),
758            KclValue::MixedArray { value, .. } => Some(RuntimeType::Tuple(
759                value.iter().map(|v| v.principal_type()).collect::<Option<Vec<_>>>()?,
760            )),
761            KclValue::HomArray { ty, value, .. } => {
762                Some(RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Known(value.len())))
763            }
764            KclValue::TagIdentifier(_) | KclValue::TagDeclarator(_) => Some(RuntimeType::Primitive(PrimitiveType::Tag)),
765            KclValue::Function { .. }
766            | KclValue::Module { .. }
767            | KclValue::KclNone { .. }
768            | KclValue::Type { .. }
769            | KclValue::Uuid { .. } => None,
770        }
771    }
772}
773
774#[cfg(test)]
775mod test {
776    use super::*;
777
778    fn values(exec_state: &mut ExecState) -> Vec<KclValue> {
779        vec![
780            KclValue::Bool {
781                value: true,
782                meta: Vec::new(),
783            },
784            KclValue::Number {
785                value: 1.0,
786                ty: NumericType::count(),
787                meta: Vec::new(),
788            },
789            KclValue::String {
790                value: "hello".to_owned(),
791                meta: Vec::new(),
792            },
793            KclValue::MixedArray {
794                value: Vec::new(),
795                meta: Vec::new(),
796            },
797            KclValue::HomArray {
798                value: Vec::new(),
799                ty: RuntimeType::solid(),
800            },
801            KclValue::Object {
802                value: crate::execution::KclObjectFields::new(),
803                meta: Vec::new(),
804            },
805            KclValue::TagIdentifier(Box::new("foo".parse().unwrap())),
806            KclValue::TagDeclarator(Box::new(crate::parsing::ast::types::TagDeclarator::new("foo"))),
807            KclValue::Plane {
808                value: Box::new(Plane::from_plane_data(crate::std::sketch::PlaneData::XY, exec_state)),
809            },
810            // No easy way to make a Face, Sketch, Solid, or Helix
811            KclValue::ImportedGeometry(crate::execution::ImportedGeometry {
812                id: uuid::Uuid::nil(),
813                value: Vec::new(),
814                meta: Vec::new(),
815            }),
816            // Other values don't have types
817        ]
818    }
819
820    #[track_caller]
821    fn assert_coerce_results(
822        value: &KclValue,
823        super_type: &RuntimeType,
824        expected_value: &KclValue,
825        exec_state: &mut ExecState,
826    ) {
827        let is_subtype = value == expected_value;
828        assert_eq!(&value.coerce(super_type, exec_state).unwrap(), expected_value);
829        assert_eq!(
830            is_subtype,
831            value.principal_type().is_some() && value.principal_type().unwrap().subtype(super_type),
832            "{:?} <: {super_type:?} should be {is_subtype}",
833            value.principal_type().unwrap()
834        );
835        assert!(
836            expected_value.principal_type().unwrap().subtype(super_type),
837            "{} <: {super_type}",
838            expected_value.principal_type().unwrap()
839        )
840    }
841
842    #[tokio::test(flavor = "multi_thread")]
843    async fn coerce_idempotent() {
844        let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock().await);
845        let values = values(&mut exec_state);
846        for v in &values {
847            // Identity subtype
848            let ty = v.principal_type().unwrap();
849            assert_coerce_results(v, &ty, v, &mut exec_state);
850
851            // Union subtype
852            let uty1 = RuntimeType::Union(vec![ty.clone()]);
853            let uty2 = RuntimeType::Union(vec![ty.clone(), RuntimeType::Primitive(PrimitiveType::Boolean)]);
854            assert_coerce_results(v, &uty1, v, &mut exec_state);
855            assert_coerce_results(v, &uty2, v, &mut exec_state);
856
857            // Array subtypes
858            let aty = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::None);
859            let aty1 = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Known(1));
860            let aty0 = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::NonEmpty);
861
862            assert_coerce_results(
863                v,
864                &aty,
865                &KclValue::HomArray {
866                    value: vec![v.clone()],
867                    ty: ty.clone(),
868                },
869                &mut exec_state,
870            );
871            assert_coerce_results(
872                v,
873                &aty1,
874                &KclValue::HomArray {
875                    value: vec![v.clone()],
876                    ty: ty.clone(),
877                },
878                &mut exec_state,
879            );
880            assert_coerce_results(
881                v,
882                &aty0,
883                &KclValue::HomArray {
884                    value: vec![v.clone()],
885                    ty: ty.clone(),
886                },
887                &mut exec_state,
888            );
889
890            // Tuple subtype
891            let tty = RuntimeType::Tuple(vec![ty.clone()]);
892            assert_coerce_results(
893                v,
894                &tty,
895                &KclValue::MixedArray {
896                    value: vec![v.clone()],
897                    meta: Vec::new(),
898                },
899                &mut exec_state,
900            );
901        }
902
903        for v in &values[1..] {
904            // Not a subtype
905            assert!(v
906                .coerce(&RuntimeType::Primitive(PrimitiveType::Boolean), &mut exec_state)
907                .is_none());
908        }
909    }
910
911    #[tokio::test(flavor = "multi_thread")]
912    async fn coerce_none() {
913        let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock().await);
914        let none = KclValue::KclNone {
915            value: crate::parsing::ast::types::KclNone::new(),
916            meta: Vec::new(),
917        };
918
919        let aty = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::None);
920        let aty0 = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::Known(0));
921        let aty1 = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::Known(1));
922        let aty1p = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::NonEmpty);
923        assert_coerce_results(
924            &none,
925            &aty,
926            &KclValue::HomArray {
927                value: Vec::new(),
928                ty: RuntimeType::solid(),
929            },
930            &mut exec_state,
931        );
932        assert_coerce_results(
933            &none,
934            &aty0,
935            &KclValue::HomArray {
936                value: Vec::new(),
937                ty: RuntimeType::solid(),
938            },
939            &mut exec_state,
940        );
941        assert!(none.coerce(&aty1, &mut exec_state).is_none());
942        assert!(none.coerce(&aty1p, &mut exec_state).is_none());
943
944        let tty = RuntimeType::Tuple(vec![]);
945        let tty1 = RuntimeType::Tuple(vec![RuntimeType::solid()]);
946        assert_coerce_results(
947            &none,
948            &tty,
949            &KclValue::MixedArray {
950                value: Vec::new(),
951                meta: Vec::new(),
952            },
953            &mut exec_state,
954        );
955        assert!(none.coerce(&tty1, &mut exec_state).is_none());
956
957        let oty = RuntimeType::Object(vec![]);
958        assert_coerce_results(
959            &none,
960            &oty,
961            &KclValue::Object {
962                value: HashMap::new(),
963                meta: Vec::new(),
964            },
965            &mut exec_state,
966        );
967    }
968
969    #[tokio::test(flavor = "multi_thread")]
970    async fn coerce_record() {
971        let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock().await);
972
973        let obj0 = KclValue::Object {
974            value: HashMap::new(),
975            meta: Vec::new(),
976        };
977        let obj1 = KclValue::Object {
978            value: [(
979                "foo".to_owned(),
980                KclValue::Bool {
981                    value: true,
982                    meta: Vec::new(),
983                },
984            )]
985            .into(),
986            meta: Vec::new(),
987        };
988        let obj2 = KclValue::Object {
989            value: [
990                (
991                    "foo".to_owned(),
992                    KclValue::Bool {
993                        value: true,
994                        meta: Vec::new(),
995                    },
996                ),
997                (
998                    "bar".to_owned(),
999                    KclValue::Number {
1000                        value: 0.0,
1001                        ty: NumericType::count(),
1002                        meta: Vec::new(),
1003                    },
1004                ),
1005                (
1006                    "baz".to_owned(),
1007                    KclValue::Number {
1008                        value: 42.0,
1009                        ty: NumericType::count(),
1010                        meta: Vec::new(),
1011                    },
1012                ),
1013            ]
1014            .into(),
1015            meta: Vec::new(),
1016        };
1017
1018        let ty0 = RuntimeType::Object(vec![]);
1019        assert_coerce_results(&obj0, &ty0, &obj0, &mut exec_state);
1020        assert_coerce_results(&obj1, &ty0, &obj1, &mut exec_state);
1021        assert_coerce_results(&obj2, &ty0, &obj2, &mut exec_state);
1022
1023        let ty1 = RuntimeType::Object(vec![("foo".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean))]);
1024        assert!(&obj0.coerce(&ty1, &mut exec_state).is_none());
1025        assert_coerce_results(&obj1, &ty1, &obj1, &mut exec_state);
1026        assert_coerce_results(&obj2, &ty1, &obj2, &mut exec_state);
1027
1028        // Different ordering, (TODO - test for covariance once implemented)
1029        let ty2 = RuntimeType::Object(vec![
1030            (
1031                "bar".to_owned(),
1032                RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1033            ),
1034            ("foo".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean)),
1035        ]);
1036        assert!(&obj0.coerce(&ty2, &mut exec_state).is_none());
1037        assert!(&obj1.coerce(&ty2, &mut exec_state).is_none());
1038        assert_coerce_results(&obj2, &ty2, &obj2, &mut exec_state);
1039
1040        // field not present
1041        let tyq = RuntimeType::Object(vec![("qux".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean))]);
1042        assert!(&obj0.coerce(&tyq, &mut exec_state).is_none());
1043        assert!(&obj1.coerce(&tyq, &mut exec_state).is_none());
1044        assert!(&obj2.coerce(&tyq, &mut exec_state).is_none());
1045
1046        // field with different type
1047        let ty1 = RuntimeType::Object(vec![("bar".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean))]);
1048        assert!(&obj2.coerce(&ty1, &mut exec_state).is_none());
1049    }
1050
1051    #[tokio::test(flavor = "multi_thread")]
1052    async fn coerce_array() {
1053        let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock().await);
1054
1055        let hom_arr = KclValue::HomArray {
1056            value: vec![
1057                KclValue::Number {
1058                    value: 0.0,
1059                    ty: NumericType::count(),
1060                    meta: Vec::new(),
1061                },
1062                KclValue::Number {
1063                    value: 1.0,
1064                    ty: NumericType::count(),
1065                    meta: Vec::new(),
1066                },
1067                KclValue::Number {
1068                    value: 2.0,
1069                    ty: NumericType::count(),
1070                    meta: Vec::new(),
1071                },
1072                KclValue::Number {
1073                    value: 3.0,
1074                    ty: NumericType::count(),
1075                    meta: Vec::new(),
1076                },
1077            ],
1078            ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1079        };
1080        let mixed1 = KclValue::MixedArray {
1081            value: vec![
1082                KclValue::Number {
1083                    value: 0.0,
1084                    ty: NumericType::count(),
1085                    meta: Vec::new(),
1086                },
1087                KclValue::Number {
1088                    value: 1.0,
1089                    ty: NumericType::count(),
1090                    meta: Vec::new(),
1091                },
1092            ],
1093            meta: Vec::new(),
1094        };
1095        let mixed2 = KclValue::MixedArray {
1096            value: vec![
1097                KclValue::Number {
1098                    value: 0.0,
1099                    ty: NumericType::count(),
1100                    meta: Vec::new(),
1101                },
1102                KclValue::Bool {
1103                    value: true,
1104                    meta: Vec::new(),
1105                },
1106            ],
1107            meta: Vec::new(),
1108        };
1109
1110        // Principal types
1111        let tyh = RuntimeType::Array(
1112            Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
1113            ArrayLen::Known(4),
1114        );
1115        let tym1 = RuntimeType::Tuple(vec![
1116            RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1117            RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1118        ]);
1119        let tym2 = RuntimeType::Tuple(vec![
1120            RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1121            RuntimeType::Primitive(PrimitiveType::Boolean),
1122        ]);
1123        assert_coerce_results(&hom_arr, &tyh, &hom_arr, &mut exec_state);
1124        assert_coerce_results(&mixed1, &tym1, &mixed1, &mut exec_state);
1125        assert_coerce_results(&mixed2, &tym2, &mixed2, &mut exec_state);
1126        assert!(&mixed1.coerce(&tym2, &mut exec_state).is_none());
1127        assert!(&mixed2.coerce(&tym1, &mut exec_state).is_none());
1128
1129        // Length subtyping
1130        let tyhn = RuntimeType::Array(
1131            Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
1132            ArrayLen::None,
1133        );
1134        let tyh1 = RuntimeType::Array(
1135            Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
1136            ArrayLen::NonEmpty,
1137        );
1138        let tyh3 = RuntimeType::Array(
1139            Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
1140            ArrayLen::Known(3),
1141        );
1142        assert_coerce_results(&hom_arr, &tyhn, &hom_arr, &mut exec_state);
1143        assert_coerce_results(&hom_arr, &tyh1, &hom_arr, &mut exec_state);
1144        assert!(&hom_arr.coerce(&tyh3, &mut exec_state).is_none());
1145
1146        let hom_arr0 = KclValue::HomArray {
1147            value: vec![],
1148            ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1149        };
1150        assert_coerce_results(&hom_arr0, &tyhn, &hom_arr0, &mut exec_state);
1151        assert!(&hom_arr0.coerce(&tyh1, &mut exec_state).is_none());
1152        assert!(&hom_arr0.coerce(&tyh3, &mut exec_state).is_none());
1153
1154        // Covariance
1155        // let tyh = RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any))), ArrayLen::Known(4));
1156        let tym1 = RuntimeType::Tuple(vec![
1157            RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
1158            RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1159        ]);
1160        let tym2 = RuntimeType::Tuple(vec![
1161            RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
1162            RuntimeType::Primitive(PrimitiveType::Boolean),
1163        ]);
1164        // TODO implement covariance for homogeneous arrays
1165        // assert_coerce_results(&hom_arr, &tyh, &hom_arr, &mut exec_state);
1166        assert_coerce_results(&mixed1, &tym1, &mixed1, &mut exec_state);
1167        assert_coerce_results(&mixed2, &tym2, &mixed2, &mut exec_state);
1168
1169        // Mixed to homogeneous
1170        let hom_arr_2 = KclValue::HomArray {
1171            value: vec![
1172                KclValue::Number {
1173                    value: 0.0,
1174                    ty: NumericType::count(),
1175                    meta: Vec::new(),
1176                },
1177                KclValue::Number {
1178                    value: 1.0,
1179                    ty: NumericType::count(),
1180                    meta: Vec::new(),
1181                },
1182            ],
1183            ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1184        };
1185        let mixed0 = KclValue::MixedArray {
1186            value: vec![],
1187            meta: Vec::new(),
1188        };
1189        assert_coerce_results(&mixed1, &tyhn, &hom_arr_2, &mut exec_state);
1190        assert_coerce_results(&mixed1, &tyh1, &hom_arr_2, &mut exec_state);
1191        assert_coerce_results(&mixed0, &tyhn, &hom_arr0, &mut exec_state);
1192        assert!(&mixed0.coerce(&tyh, &mut exec_state).is_none());
1193        assert!(&mixed0.coerce(&tyh1, &mut exec_state).is_none());
1194
1195        // Homogehous to mixed
1196        assert_coerce_results(&hom_arr_2, &tym1, &mixed1, &mut exec_state);
1197        assert!(&hom_arr.coerce(&tym1, &mut exec_state).is_none());
1198        assert!(&hom_arr_2.coerce(&tym2, &mut exec_state).is_none());
1199
1200        assert!(&mixed0.coerce(&tym1, &mut exec_state).is_none());
1201        assert!(&mixed0.coerce(&tym2, &mut exec_state).is_none());
1202    }
1203
1204    #[tokio::test(flavor = "multi_thread")]
1205    async fn coerce_union() {
1206        let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock().await);
1207
1208        // Subtyping smaller unions
1209        assert!(RuntimeType::Union(vec![]).subtype(&RuntimeType::Union(vec![
1210            RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
1211            RuntimeType::Primitive(PrimitiveType::Boolean)
1212        ])));
1213        assert!(
1214            RuntimeType::Union(vec![RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any))]).subtype(
1215                &RuntimeType::Union(vec![
1216                    RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
1217                    RuntimeType::Primitive(PrimitiveType::Boolean)
1218                ])
1219            )
1220        );
1221        assert!(RuntimeType::Union(vec![
1222            RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
1223            RuntimeType::Primitive(PrimitiveType::Boolean)
1224        ])
1225        .subtype(&RuntimeType::Union(vec![
1226            RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
1227            RuntimeType::Primitive(PrimitiveType::Boolean)
1228        ])));
1229
1230        // Covariance
1231        let count = KclValue::Number {
1232            value: 1.0,
1233            ty: NumericType::count(),
1234            meta: Vec::new(),
1235        };
1236
1237        let tya = RuntimeType::Union(vec![RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any))]);
1238        let tya2 = RuntimeType::Union(vec![
1239            RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
1240            RuntimeType::Primitive(PrimitiveType::Boolean),
1241        ]);
1242        assert_coerce_results(&count, &tya, &count, &mut exec_state);
1243        assert_coerce_results(&count, &tya2, &count, &mut exec_state);
1244
1245        // No matching type
1246        let tyb = RuntimeType::Union(vec![RuntimeType::Primitive(PrimitiveType::Boolean)]);
1247        let tyb2 = RuntimeType::Union(vec![
1248            RuntimeType::Primitive(PrimitiveType::Boolean),
1249            RuntimeType::Primitive(PrimitiveType::String),
1250        ]);
1251        assert!(count.coerce(&tyb, &mut exec_state).is_none());
1252        assert!(count.coerce(&tyb2, &mut exec_state).is_none());
1253    }
1254}