Skip to main content

kcl_lib/execution/
types.rs

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