kcl_lib/execution/
types.rs

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