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