Skip to main content

kcl_lib/execution/
types.rs

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