kcl_lib/execution/
types.rs

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