Skip to main content

kcl_lib/execution/
types.rs

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