Skip to main content

kcl_lib/execution/
types.rs

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