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                    })
1521                }
1522                _ => Err(self.into()),
1523            },
1524            PrimitiveType::Axis3d => match self {
1525                KclValue::Object {
1526                    value: values, meta, ..
1527                } => {
1528                    if values
1529                        .get("origin")
1530                        .ok_or(CoercionError::from(self))?
1531                        .has_type(&RuntimeType::point3d())
1532                        && values
1533                            .get("direction")
1534                            .ok_or(CoercionError::from(self))?
1535                            .has_type(&RuntimeType::point3d())
1536                    {
1537                        return Ok(self.clone());
1538                    }
1539
1540                    let origin = values.get("origin").ok_or(self.into()).and_then(|p| {
1541                        p.coerce_to_array_type(
1542                            &RuntimeType::length(),
1543                            convert_units,
1544                            ArrayLen::Known(3),
1545                            exec_state,
1546                            true,
1547                        )
1548                    })?;
1549                    let direction = values.get("direction").ok_or(self.into()).and_then(|p| {
1550                        p.coerce_to_array_type(
1551                            &RuntimeType::length(),
1552                            convert_units,
1553                            ArrayLen::Known(3),
1554                            exec_state,
1555                            true,
1556                        )
1557                    })?;
1558
1559                    Ok(KclValue::Object {
1560                        value: [("origin".to_owned(), origin), ("direction".to_owned(), direction)].into(),
1561                        meta: meta.clone(),
1562                        constrainable: false,
1563                    })
1564                }
1565                _ => Err(self.into()),
1566            },
1567            PrimitiveType::ImportedGeometry => match self {
1568                KclValue::ImportedGeometry { .. } => Ok(self.clone()),
1569                _ => Err(self.into()),
1570            },
1571            PrimitiveType::Function => match self {
1572                KclValue::Function { .. } => Ok(self.clone()),
1573                _ => Err(self.into()),
1574            },
1575            PrimitiveType::TagDecl => match self {
1576                KclValue::TagDeclarator { .. } => Ok(self.clone()),
1577                _ => Err(self.into()),
1578            },
1579        }
1580    }
1581
1582    fn coerce_to_array_type(
1583        &self,
1584        ty: &RuntimeType,
1585        convert_units: bool,
1586        len: ArrayLen,
1587        exec_state: &mut ExecState,
1588        allow_shrink: bool,
1589    ) -> Result<KclValue, CoercionError> {
1590        match self {
1591            KclValue::HomArray { value, ty: aty, .. } => {
1592                let satisfied_len = len.satisfied(value.len(), allow_shrink);
1593
1594                if aty.subtype(ty) {
1595                    // If the element type is a subtype of the target type and
1596                    // the length constraint is satisfied, we can just return
1597                    // the values unchanged, only adjusting the length. The new
1598                    // array element type should preserve its type because the
1599                    // target type oftentimes includes an unknown type as a way
1600                    // to say that the caller doesn't care.
1601                    return satisfied_len
1602                        .map(|len| KclValue::HomArray {
1603                            value: value[..len].to_vec(),
1604                            ty: aty.clone(),
1605                        })
1606                        .ok_or(self.into());
1607                }
1608
1609                // Ignore the array type, and coerce the elements of the array.
1610                if let Some(satisfied_len) = satisfied_len {
1611                    let value_result = value
1612                        .iter()
1613                        .take(satisfied_len)
1614                        .map(|v| v.coerce(ty, convert_units, exec_state))
1615                        .collect::<Result<Vec<_>, _>>();
1616
1617                    if let Ok(value) = value_result {
1618                        // We were able to coerce all the elements.
1619                        return Ok(KclValue::HomArray { value, ty: ty.clone() });
1620                    }
1621                }
1622
1623                // As a last resort, try to flatten the array.
1624                let mut values = Vec::new();
1625                for item in value {
1626                    if let KclValue::HomArray { value: inner_value, .. } = item {
1627                        // Flatten elements.
1628                        for item in inner_value {
1629                            values.push(item.coerce(ty, convert_units, exec_state)?);
1630                        }
1631                    } else {
1632                        values.push(item.coerce(ty, convert_units, exec_state)?);
1633                    }
1634                }
1635
1636                let len = len
1637                    .satisfied(values.len(), allow_shrink)
1638                    .ok_or(CoercionError::from(self))?;
1639
1640                if len > values.len() {
1641                    let message = format!(
1642                        "Internal: Expected coerced array length {len} to be less than or equal to original length {}",
1643                        values.len()
1644                    );
1645                    exec_state.err(CompilationIssue::err(self.into(), message.clone()));
1646                    #[cfg(debug_assertions)]
1647                    panic!("{message}");
1648                }
1649                values.truncate(len);
1650
1651                Ok(KclValue::HomArray {
1652                    value: values,
1653                    ty: ty.clone(),
1654                })
1655            }
1656            KclValue::Tuple { value, .. } => {
1657                let len = len
1658                    .satisfied(value.len(), allow_shrink)
1659                    .ok_or(CoercionError::from(self))?;
1660                let value = value
1661                    .iter()
1662                    .map(|item| item.coerce(ty, convert_units, exec_state))
1663                    .take(len)
1664                    .collect::<Result<Vec<_>, _>>()?;
1665
1666                Ok(KclValue::HomArray { value, ty: ty.clone() })
1667            }
1668            KclValue::KclNone { .. } if len.satisfied(0, false).is_some() => Ok(KclValue::HomArray {
1669                value: Vec::new(),
1670                ty: ty.clone(),
1671            }),
1672            _ if len.satisfied(1, false).is_some() => self.coerce(ty, convert_units, exec_state),
1673            _ => Err(self.into()),
1674        }
1675    }
1676
1677    fn coerce_to_tuple_type(
1678        &self,
1679        tys: &[RuntimeType],
1680        convert_units: bool,
1681        exec_state: &mut ExecState,
1682    ) -> Result<KclValue, CoercionError> {
1683        match self {
1684            KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } if value.len() == tys.len() => {
1685                let mut result = Vec::new();
1686                for (i, t) in tys.iter().enumerate() {
1687                    result.push(value[i].coerce(t, convert_units, exec_state)?);
1688                }
1689
1690                Ok(KclValue::Tuple {
1691                    value: result,
1692                    meta: Vec::new(),
1693                })
1694            }
1695            KclValue::KclNone { meta, .. } if tys.is_empty() => Ok(KclValue::Tuple {
1696                value: Vec::new(),
1697                meta: meta.clone(),
1698            }),
1699            _ if tys.len() == 1 => self.coerce(&tys[0], convert_units, exec_state),
1700            _ => Err(self.into()),
1701        }
1702    }
1703
1704    fn coerce_to_union_type(
1705        &self,
1706        tys: &[RuntimeType],
1707        convert_units: bool,
1708        exec_state: &mut ExecState,
1709    ) -> Result<KclValue, CoercionError> {
1710        for t in tys {
1711            if let Ok(v) = self.coerce(t, convert_units, exec_state) {
1712                return Ok(v);
1713            }
1714        }
1715
1716        Err(self.into())
1717    }
1718
1719    fn coerce_to_object_type(
1720        &self,
1721        tys: &[(String, RuntimeType)],
1722        constrainable: bool,
1723        _convert_units: bool,
1724        _exec_state: &mut ExecState,
1725    ) -> Result<KclValue, CoercionError> {
1726        match self {
1727            KclValue::Object { value, meta, .. } => {
1728                for (s, t) in tys {
1729                    // TODO coerce fields
1730                    if !value.get(s).ok_or(CoercionError::from(self))?.has_type(t) {
1731                        return Err(self.into());
1732                    }
1733                }
1734                // TODO remove non-required fields
1735                Ok(KclValue::Object {
1736                    value: value.clone(),
1737                    meta: meta.clone(),
1738                    // Note that we don't check for constrainability, coercing to a constrainable object
1739                    // adds that property.
1740                    constrainable,
1741                })
1742            }
1743            KclValue::KclNone { meta, .. } if tys.is_empty() => Ok(KclValue::Object {
1744                value: HashMap::new(),
1745                meta: meta.clone(),
1746                constrainable,
1747            }),
1748            _ => Err(self.into()),
1749        }
1750    }
1751
1752    pub fn principal_type(&self) -> Option<RuntimeType> {
1753        match self {
1754            KclValue::Bool { .. } => Some(RuntimeType::Primitive(PrimitiveType::Boolean)),
1755            KclValue::Number { ty, .. } => Some(RuntimeType::Primitive(PrimitiveType::Number(*ty))),
1756            KclValue::String { .. } => Some(RuntimeType::Primitive(PrimitiveType::String)),
1757            KclValue::SketchVar { value, .. } => Some(RuntimeType::Primitive(PrimitiveType::Number(value.ty))),
1758            KclValue::SketchConstraint { .. } => Some(RuntimeType::Primitive(PrimitiveType::Constraint)),
1759            KclValue::Object {
1760                value, constrainable, ..
1761            } => {
1762                let properties = value
1763                    .iter()
1764                    .map(|(k, v)| v.principal_type().map(|t| (k.clone(), t)))
1765                    .collect::<Option<Vec<_>>>()?;
1766                Some(RuntimeType::Object(properties, *constrainable))
1767            }
1768            KclValue::GdtAnnotation { .. } => Some(RuntimeType::Primitive(PrimitiveType::GdtAnnotation)),
1769            KclValue::Plane { .. } => Some(RuntimeType::Primitive(PrimitiveType::Plane)),
1770            KclValue::Sketch { .. } => Some(RuntimeType::Primitive(PrimitiveType::Sketch)),
1771            KclValue::Solid { .. } => Some(RuntimeType::Primitive(PrimitiveType::Solid)),
1772            KclValue::Face { .. } => Some(RuntimeType::Primitive(PrimitiveType::Face)),
1773            KclValue::Segment { .. } => Some(RuntimeType::Primitive(PrimitiveType::Segment)),
1774            KclValue::Helix { .. } => Some(RuntimeType::Primitive(PrimitiveType::Helix)),
1775            KclValue::ImportedGeometry(..) => Some(RuntimeType::Primitive(PrimitiveType::ImportedGeometry)),
1776            KclValue::Tuple { value, .. } => Some(RuntimeType::Tuple(
1777                value.iter().map(|v| v.principal_type()).collect::<Option<Vec<_>>>()?,
1778            )),
1779            KclValue::HomArray { ty, value, .. } => {
1780                Some(RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Known(value.len())))
1781            }
1782            KclValue::TagIdentifier(_) => Some(RuntimeType::Primitive(PrimitiveType::TaggedEdge)),
1783            KclValue::TagDeclarator(_) => Some(RuntimeType::Primitive(PrimitiveType::TagDecl)),
1784            KclValue::Uuid { .. } => Some(RuntimeType::Primitive(PrimitiveType::Edge)),
1785            KclValue::Function { .. } => Some(RuntimeType::Primitive(PrimitiveType::Function)),
1786            KclValue::KclNone { .. } => Some(RuntimeType::Primitive(PrimitiveType::None)),
1787            KclValue::Module { .. } | KclValue::Type { .. } => None,
1788            KclValue::BoundedEdge { .. } => Some(RuntimeType::Primitive(PrimitiveType::BoundedEdge)),
1789        }
1790    }
1791
1792    pub fn principal_type_string(&self) -> String {
1793        if let Some(ty) = self.principal_type() {
1794            return format!("`{ty}`");
1795        }
1796
1797        match self {
1798            KclValue::Module { .. } => "module",
1799            KclValue::KclNone { .. } => "none",
1800            KclValue::Type { .. } => "type",
1801            _ => {
1802                debug_assert!(false);
1803                "<unexpected type>"
1804            }
1805        }
1806        .to_owned()
1807    }
1808}
1809
1810#[cfg(test)]
1811mod test {
1812    use super::*;
1813    use crate::execution::ExecTestResults;
1814    use crate::execution::parse_execute;
1815
1816    async fn new_exec_state() -> (crate::ExecutorContext, ExecState) {
1817        let ctx = crate::ExecutorContext::new_mock(None).await;
1818        let exec_state = ExecState::new(&ctx);
1819        (ctx, exec_state)
1820    }
1821
1822    fn values(exec_state: &mut ExecState) -> Vec<KclValue> {
1823        vec![
1824            KclValue::Bool {
1825                value: true,
1826                meta: Vec::new(),
1827            },
1828            KclValue::Number {
1829                value: 1.0,
1830                ty: NumericType::count(),
1831                meta: Vec::new(),
1832            },
1833            KclValue::String {
1834                value: "hello".to_owned(),
1835                meta: Vec::new(),
1836            },
1837            KclValue::Tuple {
1838                value: Vec::new(),
1839                meta: Vec::new(),
1840            },
1841            KclValue::HomArray {
1842                value: Vec::new(),
1843                ty: RuntimeType::solid(),
1844            },
1845            KclValue::Object {
1846                value: crate::execution::KclObjectFields::new(),
1847                meta: Vec::new(),
1848                constrainable: false,
1849            },
1850            KclValue::TagIdentifier(Box::new("foo".parse().unwrap())),
1851            KclValue::TagDeclarator(Box::new(crate::parsing::ast::types::TagDeclarator::new("foo"))),
1852            KclValue::Plane {
1853                value: Box::new(
1854                    Plane::from_plane_data_skipping_engine(crate::std::sketch::PlaneData::XY, exec_state).unwrap(),
1855                ),
1856            },
1857            // No easy way to make a Face, Sketch, Solid, or Helix
1858            KclValue::ImportedGeometry(crate::execution::ImportedGeometry::new(
1859                uuid::Uuid::nil(),
1860                Vec::new(),
1861                Vec::new(),
1862            )),
1863            // Other values don't have types
1864        ]
1865    }
1866
1867    #[track_caller]
1868    fn assert_coerce_results(
1869        value: &KclValue,
1870        super_type: &RuntimeType,
1871        expected_value: &KclValue,
1872        exec_state: &mut ExecState,
1873    ) {
1874        let is_subtype = value == expected_value;
1875        let actual = value.coerce(super_type, true, exec_state).unwrap();
1876        assert_eq!(&actual, expected_value);
1877        assert_eq!(
1878            is_subtype,
1879            value.principal_type().is_some() && value.principal_type().unwrap().subtype(super_type),
1880            "{:?} <: {super_type:?} should be {is_subtype}",
1881            value.principal_type().unwrap()
1882        );
1883        assert!(
1884            expected_value.principal_type().unwrap().subtype(super_type),
1885            "{} <: {super_type}",
1886            expected_value.principal_type().unwrap()
1887        )
1888    }
1889
1890    #[tokio::test(flavor = "multi_thread")]
1891    async fn coerce_idempotent() {
1892        let (ctx, mut exec_state) = new_exec_state().await;
1893        let values = values(&mut exec_state);
1894        for v in &values {
1895            // Identity subtype
1896            let ty = v.principal_type().unwrap();
1897            assert_coerce_results(v, &ty, v, &mut exec_state);
1898
1899            // Union subtype
1900            let uty1 = RuntimeType::Union(vec![ty.clone()]);
1901            let uty2 = RuntimeType::Union(vec![ty.clone(), RuntimeType::Primitive(PrimitiveType::Boolean)]);
1902            assert_coerce_results(v, &uty1, v, &mut exec_state);
1903            assert_coerce_results(v, &uty2, v, &mut exec_state);
1904
1905            // Array subtypes
1906            let aty = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::None);
1907            let aty1 = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Known(1));
1908            let aty0 = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Minimum(1));
1909
1910            match v {
1911                KclValue::HomArray { .. } => {
1912                    // These will not get wrapped if possible.
1913                    assert_coerce_results(
1914                        v,
1915                        &aty,
1916                        &KclValue::HomArray {
1917                            value: vec![],
1918                            ty: ty.clone(),
1919                        },
1920                        &mut exec_state,
1921                    );
1922                    // Coercing an empty array to an array of length 1
1923                    // should fail.
1924                    v.coerce(&aty1, true, &mut exec_state).unwrap_err();
1925                    // Coercing an empty array to an array that's
1926                    // non-empty should fail.
1927                    v.coerce(&aty0, true, &mut exec_state).unwrap_err();
1928                }
1929                KclValue::Tuple { .. } => {}
1930                _ => {
1931                    assert_coerce_results(v, &aty, v, &mut exec_state);
1932                    assert_coerce_results(v, &aty1, v, &mut exec_state);
1933                    assert_coerce_results(v, &aty0, v, &mut exec_state);
1934
1935                    // Tuple subtype
1936                    let tty = RuntimeType::Tuple(vec![ty.clone()]);
1937                    assert_coerce_results(v, &tty, v, &mut exec_state);
1938                }
1939            }
1940        }
1941
1942        for v in &values[1..] {
1943            // Not a subtype
1944            v.coerce(&RuntimeType::Primitive(PrimitiveType::Boolean), true, &mut exec_state)
1945                .unwrap_err();
1946        }
1947        ctx.close().await;
1948    }
1949
1950    #[tokio::test(flavor = "multi_thread")]
1951    async fn coerce_none() {
1952        let (ctx, mut exec_state) = new_exec_state().await;
1953        let none = KclValue::KclNone {
1954            value: crate::parsing::ast::types::KclNone::new(),
1955            meta: Vec::new(),
1956        };
1957
1958        let aty = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::None);
1959        let aty0 = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::Known(0));
1960        let aty1 = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::Known(1));
1961        let aty1p = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::Minimum(1));
1962        assert_coerce_results(
1963            &none,
1964            &aty,
1965            &KclValue::HomArray {
1966                value: Vec::new(),
1967                ty: RuntimeType::solid(),
1968            },
1969            &mut exec_state,
1970        );
1971        assert_coerce_results(
1972            &none,
1973            &aty0,
1974            &KclValue::HomArray {
1975                value: Vec::new(),
1976                ty: RuntimeType::solid(),
1977            },
1978            &mut exec_state,
1979        );
1980        none.coerce(&aty1, true, &mut exec_state).unwrap_err();
1981        none.coerce(&aty1p, true, &mut exec_state).unwrap_err();
1982
1983        let tty = RuntimeType::Tuple(vec![]);
1984        let tty1 = RuntimeType::Tuple(vec![RuntimeType::solid()]);
1985        assert_coerce_results(
1986            &none,
1987            &tty,
1988            &KclValue::Tuple {
1989                value: Vec::new(),
1990                meta: Vec::new(),
1991            },
1992            &mut exec_state,
1993        );
1994        none.coerce(&tty1, true, &mut exec_state).unwrap_err();
1995
1996        let oty = RuntimeType::Object(vec![], false);
1997        assert_coerce_results(
1998            &none,
1999            &oty,
2000            &KclValue::Object {
2001                value: HashMap::new(),
2002                meta: Vec::new(),
2003                constrainable: false,
2004            },
2005            &mut exec_state,
2006        );
2007        ctx.close().await;
2008    }
2009
2010    #[tokio::test(flavor = "multi_thread")]
2011    async fn coerce_record() {
2012        let (ctx, mut exec_state) = new_exec_state().await;
2013
2014        let obj0 = KclValue::Object {
2015            value: HashMap::new(),
2016            meta: Vec::new(),
2017            constrainable: false,
2018        };
2019        let obj1 = KclValue::Object {
2020            value: [(
2021                "foo".to_owned(),
2022                KclValue::Bool {
2023                    value: true,
2024                    meta: Vec::new(),
2025                },
2026            )]
2027            .into(),
2028            meta: Vec::new(),
2029            constrainable: false,
2030        };
2031        let obj2 = KclValue::Object {
2032            value: [
2033                (
2034                    "foo".to_owned(),
2035                    KclValue::Bool {
2036                        value: true,
2037                        meta: Vec::new(),
2038                    },
2039                ),
2040                (
2041                    "bar".to_owned(),
2042                    KclValue::Number {
2043                        value: 0.0,
2044                        ty: NumericType::count(),
2045                        meta: Vec::new(),
2046                    },
2047                ),
2048                (
2049                    "baz".to_owned(),
2050                    KclValue::Number {
2051                        value: 42.0,
2052                        ty: NumericType::count(),
2053                        meta: Vec::new(),
2054                    },
2055                ),
2056            ]
2057            .into(),
2058            meta: Vec::new(),
2059            constrainable: false,
2060        };
2061
2062        let ty0 = RuntimeType::Object(vec![], false);
2063        assert_coerce_results(&obj0, &ty0, &obj0, &mut exec_state);
2064        assert_coerce_results(&obj1, &ty0, &obj1, &mut exec_state);
2065        assert_coerce_results(&obj2, &ty0, &obj2, &mut exec_state);
2066
2067        let ty1 = RuntimeType::Object(
2068            vec![("foo".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean))],
2069            false,
2070        );
2071        obj0.coerce(&ty1, true, &mut exec_state).unwrap_err();
2072        assert_coerce_results(&obj1, &ty1, &obj1, &mut exec_state);
2073        assert_coerce_results(&obj2, &ty1, &obj2, &mut exec_state);
2074
2075        // Different ordering, (TODO - test for covariance once implemented)
2076        let ty2 = RuntimeType::Object(
2077            vec![
2078                (
2079                    "bar".to_owned(),
2080                    RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2081                ),
2082                ("foo".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean)),
2083            ],
2084            false,
2085        );
2086        obj0.coerce(&ty2, true, &mut exec_state).unwrap_err();
2087        obj1.coerce(&ty2, true, &mut exec_state).unwrap_err();
2088        assert_coerce_results(&obj2, &ty2, &obj2, &mut exec_state);
2089
2090        // field not present
2091        let tyq = RuntimeType::Object(
2092            vec![("qux".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean))],
2093            false,
2094        );
2095        obj0.coerce(&tyq, true, &mut exec_state).unwrap_err();
2096        obj1.coerce(&tyq, true, &mut exec_state).unwrap_err();
2097        obj2.coerce(&tyq, true, &mut exec_state).unwrap_err();
2098
2099        // field with different type
2100        let ty1 = RuntimeType::Object(
2101            vec![("bar".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean))],
2102            false,
2103        );
2104        obj2.coerce(&ty1, true, &mut exec_state).unwrap_err();
2105        ctx.close().await;
2106    }
2107
2108    #[tokio::test(flavor = "multi_thread")]
2109    async fn coerce_array() {
2110        let (ctx, mut exec_state) = new_exec_state().await;
2111
2112        let hom_arr = KclValue::HomArray {
2113            value: vec![
2114                KclValue::Number {
2115                    value: 0.0,
2116                    ty: NumericType::count(),
2117                    meta: Vec::new(),
2118                },
2119                KclValue::Number {
2120                    value: 1.0,
2121                    ty: NumericType::count(),
2122                    meta: Vec::new(),
2123                },
2124                KclValue::Number {
2125                    value: 2.0,
2126                    ty: NumericType::count(),
2127                    meta: Vec::new(),
2128                },
2129                KclValue::Number {
2130                    value: 3.0,
2131                    ty: NumericType::count(),
2132                    meta: Vec::new(),
2133                },
2134            ],
2135            ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2136        };
2137        let mixed1 = KclValue::Tuple {
2138            value: vec![
2139                KclValue::Number {
2140                    value: 0.0,
2141                    ty: NumericType::count(),
2142                    meta: Vec::new(),
2143                },
2144                KclValue::Number {
2145                    value: 1.0,
2146                    ty: NumericType::count(),
2147                    meta: Vec::new(),
2148                },
2149            ],
2150            meta: Vec::new(),
2151        };
2152        let mixed2 = KclValue::Tuple {
2153            value: vec![
2154                KclValue::Number {
2155                    value: 0.0,
2156                    ty: NumericType::count(),
2157                    meta: Vec::new(),
2158                },
2159                KclValue::Bool {
2160                    value: true,
2161                    meta: Vec::new(),
2162                },
2163            ],
2164            meta: Vec::new(),
2165        };
2166
2167        // Principal types
2168        let tyh = RuntimeType::Array(
2169            Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2170            ArrayLen::Known(4),
2171        );
2172        let tym1 = RuntimeType::Tuple(vec![
2173            RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2174            RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2175        ]);
2176        let tym2 = RuntimeType::Tuple(vec![
2177            RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2178            RuntimeType::Primitive(PrimitiveType::Boolean),
2179        ]);
2180        assert_coerce_results(&hom_arr, &tyh, &hom_arr, &mut exec_state);
2181        assert_coerce_results(&mixed1, &tym1, &mixed1, &mut exec_state);
2182        assert_coerce_results(&mixed2, &tym2, &mixed2, &mut exec_state);
2183        mixed1.coerce(&tym2, true, &mut exec_state).unwrap_err();
2184        mixed2.coerce(&tym1, true, &mut exec_state).unwrap_err();
2185
2186        // Length subtyping
2187        let tyhn = RuntimeType::Array(
2188            Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2189            ArrayLen::None,
2190        );
2191        let tyh1 = RuntimeType::Array(
2192            Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2193            ArrayLen::Minimum(1),
2194        );
2195        let tyh3 = RuntimeType::Array(
2196            Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2197            ArrayLen::Known(3),
2198        );
2199        let tyhm3 = RuntimeType::Array(
2200            Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2201            ArrayLen::Minimum(3),
2202        );
2203        let tyhm5 = RuntimeType::Array(
2204            Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2205            ArrayLen::Minimum(5),
2206        );
2207        assert_coerce_results(&hom_arr, &tyhn, &hom_arr, &mut exec_state);
2208        assert_coerce_results(&hom_arr, &tyh1, &hom_arr, &mut exec_state);
2209        hom_arr.coerce(&tyh3, true, &mut exec_state).unwrap_err();
2210        assert_coerce_results(&hom_arr, &tyhm3, &hom_arr, &mut exec_state);
2211        hom_arr.coerce(&tyhm5, true, &mut exec_state).unwrap_err();
2212
2213        let hom_arr0 = KclValue::HomArray {
2214            value: vec![],
2215            ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2216        };
2217        assert_coerce_results(&hom_arr0, &tyhn, &hom_arr0, &mut exec_state);
2218        hom_arr0.coerce(&tyh1, true, &mut exec_state).unwrap_err();
2219        hom_arr0.coerce(&tyh3, true, &mut exec_state).unwrap_err();
2220
2221        // Covariance
2222        // let tyh = RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any))), ArrayLen::Known(4));
2223        let tym1 = RuntimeType::Tuple(vec![
2224            RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2225            RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2226        ]);
2227        let tym2 = RuntimeType::Tuple(vec![
2228            RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2229            RuntimeType::Primitive(PrimitiveType::Boolean),
2230        ]);
2231        // TODO implement covariance for homogeneous arrays
2232        // assert_coerce_results(&hom_arr, &tyh, &hom_arr, &mut exec_state);
2233        assert_coerce_results(&mixed1, &tym1, &mixed1, &mut exec_state);
2234        assert_coerce_results(&mixed2, &tym2, &mixed2, &mut exec_state);
2235
2236        // Mixed to homogeneous
2237        let hom_arr_2 = KclValue::HomArray {
2238            value: vec![
2239                KclValue::Number {
2240                    value: 0.0,
2241                    ty: NumericType::count(),
2242                    meta: Vec::new(),
2243                },
2244                KclValue::Number {
2245                    value: 1.0,
2246                    ty: NumericType::count(),
2247                    meta: Vec::new(),
2248                },
2249            ],
2250            ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2251        };
2252        let mixed0 = KclValue::Tuple {
2253            value: vec![],
2254            meta: Vec::new(),
2255        };
2256        assert_coerce_results(&mixed1, &tyhn, &hom_arr_2, &mut exec_state);
2257        assert_coerce_results(&mixed1, &tyh1, &hom_arr_2, &mut exec_state);
2258        assert_coerce_results(&mixed0, &tyhn, &hom_arr0, &mut exec_state);
2259        mixed0.coerce(&tyh, true, &mut exec_state).unwrap_err();
2260        mixed0.coerce(&tyh1, true, &mut exec_state).unwrap_err();
2261
2262        // Homogehous to mixed
2263        assert_coerce_results(&hom_arr_2, &tym1, &mixed1, &mut exec_state);
2264        hom_arr.coerce(&tym1, true, &mut exec_state).unwrap_err();
2265        hom_arr_2.coerce(&tym2, true, &mut exec_state).unwrap_err();
2266
2267        mixed0.coerce(&tym1, true, &mut exec_state).unwrap_err();
2268        mixed0.coerce(&tym2, true, &mut exec_state).unwrap_err();
2269        ctx.close().await;
2270    }
2271
2272    #[tokio::test(flavor = "multi_thread")]
2273    async fn coerce_union() {
2274        let (ctx, mut exec_state) = new_exec_state().await;
2275
2276        // Subtyping smaller unions
2277        assert!(RuntimeType::Union(vec![]).subtype(&RuntimeType::Union(vec![
2278            RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2279            RuntimeType::Primitive(PrimitiveType::Boolean)
2280        ])));
2281        assert!(
2282            RuntimeType::Union(vec![RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any))]).subtype(
2283                &RuntimeType::Union(vec![
2284                    RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2285                    RuntimeType::Primitive(PrimitiveType::Boolean)
2286                ])
2287            )
2288        );
2289        assert!(
2290            RuntimeType::Union(vec![
2291                RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2292                RuntimeType::Primitive(PrimitiveType::Boolean)
2293            ])
2294            .subtype(&RuntimeType::Union(vec![
2295                RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2296                RuntimeType::Primitive(PrimitiveType::Boolean)
2297            ]))
2298        );
2299
2300        // Covariance
2301        let count = KclValue::Number {
2302            value: 1.0,
2303            ty: NumericType::count(),
2304            meta: Vec::new(),
2305        };
2306
2307        let tya = RuntimeType::Union(vec![RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any))]);
2308        let tya2 = RuntimeType::Union(vec![
2309            RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2310            RuntimeType::Primitive(PrimitiveType::Boolean),
2311        ]);
2312        assert_coerce_results(&count, &tya, &count, &mut exec_state);
2313        assert_coerce_results(&count, &tya2, &count, &mut exec_state);
2314
2315        // No matching type
2316        let tyb = RuntimeType::Union(vec![RuntimeType::Primitive(PrimitiveType::Boolean)]);
2317        let tyb2 = RuntimeType::Union(vec![
2318            RuntimeType::Primitive(PrimitiveType::Boolean),
2319            RuntimeType::Primitive(PrimitiveType::String),
2320        ]);
2321        count.coerce(&tyb, true, &mut exec_state).unwrap_err();
2322        count.coerce(&tyb2, true, &mut exec_state).unwrap_err();
2323        ctx.close().await;
2324    }
2325
2326    #[tokio::test(flavor = "multi_thread")]
2327    async fn coerce_axes() {
2328        let (ctx, mut exec_state) = new_exec_state().await;
2329
2330        // Subtyping
2331        assert!(RuntimeType::Primitive(PrimitiveType::Axis2d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis2d)));
2332        assert!(RuntimeType::Primitive(PrimitiveType::Axis3d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis3d)));
2333        assert!(!RuntimeType::Primitive(PrimitiveType::Axis3d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis2d)));
2334        assert!(!RuntimeType::Primitive(PrimitiveType::Axis2d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis3d)));
2335
2336        // Coercion
2337        let a2d = KclValue::Object {
2338            value: [
2339                (
2340                    "origin".to_owned(),
2341                    KclValue::HomArray {
2342                        value: vec![
2343                            KclValue::Number {
2344                                value: 0.0,
2345                                ty: NumericType::mm(),
2346                                meta: Vec::new(),
2347                            },
2348                            KclValue::Number {
2349                                value: 0.0,
2350                                ty: NumericType::mm(),
2351                                meta: Vec::new(),
2352                            },
2353                        ],
2354                        ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
2355                    },
2356                ),
2357                (
2358                    "direction".to_owned(),
2359                    KclValue::HomArray {
2360                        value: vec![
2361                            KclValue::Number {
2362                                value: 1.0,
2363                                ty: NumericType::mm(),
2364                                meta: Vec::new(),
2365                            },
2366                            KclValue::Number {
2367                                value: 0.0,
2368                                ty: NumericType::mm(),
2369                                meta: Vec::new(),
2370                            },
2371                        ],
2372                        ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
2373                    },
2374                ),
2375            ]
2376            .into(),
2377            meta: Vec::new(),
2378            constrainable: false,
2379        };
2380        let a3d = KclValue::Object {
2381            value: [
2382                (
2383                    "origin".to_owned(),
2384                    KclValue::HomArray {
2385                        value: vec![
2386                            KclValue::Number {
2387                                value: 0.0,
2388                                ty: NumericType::mm(),
2389                                meta: Vec::new(),
2390                            },
2391                            KclValue::Number {
2392                                value: 0.0,
2393                                ty: NumericType::mm(),
2394                                meta: Vec::new(),
2395                            },
2396                            KclValue::Number {
2397                                value: 0.0,
2398                                ty: NumericType::mm(),
2399                                meta: Vec::new(),
2400                            },
2401                        ],
2402                        ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
2403                    },
2404                ),
2405                (
2406                    "direction".to_owned(),
2407                    KclValue::HomArray {
2408                        value: vec![
2409                            KclValue::Number {
2410                                value: 1.0,
2411                                ty: NumericType::mm(),
2412                                meta: Vec::new(),
2413                            },
2414                            KclValue::Number {
2415                                value: 0.0,
2416                                ty: NumericType::mm(),
2417                                meta: Vec::new(),
2418                            },
2419                            KclValue::Number {
2420                                value: 1.0,
2421                                ty: NumericType::mm(),
2422                                meta: Vec::new(),
2423                            },
2424                        ],
2425                        ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
2426                    },
2427                ),
2428            ]
2429            .into(),
2430            meta: Vec::new(),
2431            constrainable: false,
2432        };
2433
2434        let ty2d = RuntimeType::Primitive(PrimitiveType::Axis2d);
2435        let ty3d = RuntimeType::Primitive(PrimitiveType::Axis3d);
2436
2437        assert_coerce_results(&a2d, &ty2d, &a2d, &mut exec_state);
2438        assert_coerce_results(&a3d, &ty3d, &a3d, &mut exec_state);
2439        assert_coerce_results(&a3d, &ty2d, &a2d, &mut exec_state);
2440        a2d.coerce(&ty3d, true, &mut exec_state).unwrap_err();
2441        ctx.close().await;
2442    }
2443
2444    #[tokio::test(flavor = "multi_thread")]
2445    async fn coerce_numeric() {
2446        let (ctx, mut exec_state) = new_exec_state().await;
2447
2448        let count = KclValue::Number {
2449            value: 1.0,
2450            ty: NumericType::count(),
2451            meta: Vec::new(),
2452        };
2453        let mm = KclValue::Number {
2454            value: 1.0,
2455            ty: NumericType::mm(),
2456            meta: Vec::new(),
2457        };
2458        let inches = KclValue::Number {
2459            value: 1.0,
2460            ty: NumericType::Known(UnitType::Length(UnitLength::Inches)),
2461            meta: Vec::new(),
2462        };
2463        let rads = KclValue::Number {
2464            value: 1.0,
2465            ty: NumericType::Known(UnitType::Angle(UnitAngle::Radians)),
2466            meta: Vec::new(),
2467        };
2468        let default = KclValue::Number {
2469            value: 1.0,
2470            ty: NumericType::default(),
2471            meta: Vec::new(),
2472        };
2473        let any = KclValue::Number {
2474            value: 1.0,
2475            ty: NumericType::Any,
2476            meta: Vec::new(),
2477        };
2478        let unknown = KclValue::Number {
2479            value: 1.0,
2480            ty: NumericType::Unknown,
2481            meta: Vec::new(),
2482        };
2483
2484        // Trivial coercions
2485        assert_coerce_results(&count, &NumericType::count().into(), &count, &mut exec_state);
2486        assert_coerce_results(&mm, &NumericType::mm().into(), &mm, &mut exec_state);
2487        assert_coerce_results(&any, &NumericType::Any.into(), &any, &mut exec_state);
2488        assert_coerce_results(&unknown, &NumericType::Unknown.into(), &unknown, &mut exec_state);
2489        assert_coerce_results(&default, &NumericType::default().into(), &default, &mut exec_state);
2490
2491        assert_coerce_results(&count, &NumericType::Any.into(), &count, &mut exec_state);
2492        assert_coerce_results(&mm, &NumericType::Any.into(), &mm, &mut exec_state);
2493        assert_coerce_results(&unknown, &NumericType::Any.into(), &unknown, &mut exec_state);
2494        assert_coerce_results(&default, &NumericType::Any.into(), &default, &mut exec_state);
2495
2496        assert_eq!(
2497            default
2498                .coerce(
2499                    &NumericType::Default {
2500                        len: UnitLength::Yards,
2501                        angle: UnitAngle::Degrees,
2502                    }
2503                    .into(),
2504                    true,
2505                    &mut exec_state
2506                )
2507                .unwrap(),
2508            default
2509        );
2510
2511        // No coercion
2512        count
2513            .coerce(&NumericType::mm().into(), true, &mut exec_state)
2514            .unwrap_err();
2515        mm.coerce(&NumericType::count().into(), true, &mut exec_state)
2516            .unwrap_err();
2517        unknown
2518            .coerce(&NumericType::mm().into(), true, &mut exec_state)
2519            .unwrap_err();
2520        unknown
2521            .coerce(&NumericType::default().into(), true, &mut exec_state)
2522            .unwrap_err();
2523
2524        count
2525            .coerce(&NumericType::Unknown.into(), true, &mut exec_state)
2526            .unwrap_err();
2527        mm.coerce(&NumericType::Unknown.into(), true, &mut exec_state)
2528            .unwrap_err();
2529        default
2530            .coerce(&NumericType::Unknown.into(), true, &mut exec_state)
2531            .unwrap_err();
2532
2533        assert_eq!(
2534            inches
2535                .coerce(&NumericType::mm().into(), true, &mut exec_state)
2536                .unwrap()
2537                .as_f64()
2538                .unwrap()
2539                .round(),
2540            25.0
2541        );
2542        assert_eq!(
2543            rads.coerce(
2544                &NumericType::Known(UnitType::Angle(UnitAngle::Degrees)).into(),
2545                true,
2546                &mut exec_state
2547            )
2548            .unwrap()
2549            .as_f64()
2550            .unwrap()
2551            .round(),
2552            57.0
2553        );
2554        assert_eq!(
2555            inches
2556                .coerce(&NumericType::default().into(), true, &mut exec_state)
2557                .unwrap()
2558                .as_f64()
2559                .unwrap()
2560                .round(),
2561            1.0
2562        );
2563        assert_eq!(
2564            rads.coerce(&NumericType::default().into(), true, &mut exec_state)
2565                .unwrap()
2566                .as_f64()
2567                .unwrap()
2568                .round(),
2569            1.0
2570        );
2571        ctx.close().await;
2572    }
2573
2574    #[track_caller]
2575    fn assert_value_and_type(name: &str, result: &ExecTestResults, expected: f64, expected_ty: NumericType) {
2576        let mem = result.exec_state.stack();
2577        match mem
2578            .memory
2579            .get_from_owned(name, result.mem_env, SourceRange::default(), 0)
2580            .unwrap()
2581        {
2582            KclValue::Number { value, ty, .. } => {
2583                assert_eq!(value.round(), expected);
2584                assert_eq!(ty, expected_ty);
2585            }
2586            _ => unreachable!(),
2587        }
2588    }
2589
2590    #[tokio::test(flavor = "multi_thread")]
2591    async fn combine_numeric() {
2592        let program = r#"a = 5 + 4
2593b = 5 - 2
2594c = 5mm - 2mm + 10mm
2595d = 5mm - 2 + 10
2596e = 5 - 2mm + 10
2597f = 30mm - 1inch
2598
2599g = 2 * 10
2600h = 2 * 10mm
2601i = 2mm * 10mm
2602j = 2_ * 10
2603k = 2_ * 3mm * 3mm
2604
2605l = 1 / 10
2606m = 2mm / 1mm
2607n = 10inch / 2mm
2608o = 3mm / 3
2609p = 3_ / 4
2610q = 4inch / 2_
2611
2612r = min([0, 3, 42])
2613s = min([0, 3mm, -42])
2614t = min([100, 3in, 142mm])
2615u = min([3rad, 4in])
2616"#;
2617
2618        let result = parse_execute(program).await.unwrap();
2619        assert_eq!(
2620            result.exec_state.issues().len(),
2621            5,
2622            "errors: {:?}",
2623            result.exec_state.issues()
2624        );
2625
2626        assert_value_and_type("a", &result, 9.0, NumericType::default());
2627        assert_value_and_type("b", &result, 3.0, NumericType::default());
2628        assert_value_and_type("c", &result, 13.0, NumericType::mm());
2629        assert_value_and_type("d", &result, 13.0, NumericType::mm());
2630        assert_value_and_type("e", &result, 13.0, NumericType::mm());
2631        assert_value_and_type("f", &result, 5.0, NumericType::mm());
2632
2633        assert_value_and_type("g", &result, 20.0, NumericType::default());
2634        assert_value_and_type("h", &result, 20.0, NumericType::mm());
2635        assert_value_and_type("i", &result, 20.0, NumericType::Unknown);
2636        assert_value_and_type("j", &result, 20.0, NumericType::default());
2637        assert_value_and_type("k", &result, 18.0, NumericType::Unknown);
2638
2639        assert_value_and_type("l", &result, 0.0, NumericType::default());
2640        assert_value_and_type("m", &result, 2.0, NumericType::count());
2641        assert_value_and_type("n", &result, 5.0, NumericType::Unknown);
2642        assert_value_and_type("o", &result, 1.0, NumericType::mm());
2643        assert_value_and_type("p", &result, 1.0, NumericType::count());
2644        assert_value_and_type(
2645            "q",
2646            &result,
2647            2.0,
2648            NumericType::Known(UnitType::Length(UnitLength::Inches)),
2649        );
2650
2651        assert_value_and_type("r", &result, 0.0, NumericType::default());
2652        assert_value_and_type("s", &result, -42.0, NumericType::mm());
2653        assert_value_and_type("t", &result, 3.0, NumericType::Unknown);
2654        assert_value_and_type("u", &result, 3.0, NumericType::Unknown);
2655    }
2656
2657    #[tokio::test(flavor = "multi_thread")]
2658    async fn bad_typed_arithmetic() {
2659        let program = r#"
2660a = 1rad
2661b = 180 / PI * a + 360
2662"#;
2663
2664        let result = parse_execute(program).await.unwrap();
2665
2666        assert_value_and_type("a", &result, 1.0, NumericType::radians());
2667        assert_value_and_type("b", &result, 417.0, NumericType::Unknown);
2668    }
2669
2670    #[tokio::test(flavor = "multi_thread")]
2671    async fn cos_coercions() {
2672        let program = r#"
2673a = cos(units::toRadians(30deg))
2674b = 3 / a
2675c = cos(30deg)
2676d = cos(1rad)
2677"#;
2678
2679        let result = parse_execute(program).await.unwrap();
2680        assert!(
2681            result.exec_state.issues().is_empty(),
2682            "{:?}",
2683            result.exec_state.issues()
2684        );
2685
2686        assert_value_and_type("a", &result, 1.0, NumericType::default());
2687        assert_value_and_type("b", &result, 3.0, NumericType::default());
2688        assert_value_and_type("c", &result, 1.0, NumericType::default());
2689        assert_value_and_type("d", &result, 1.0, NumericType::default());
2690    }
2691
2692    #[tokio::test(flavor = "multi_thread")]
2693    async fn coerce_nested_array() {
2694        let (ctx, mut exec_state) = new_exec_state().await;
2695
2696        let mixed1 = KclValue::HomArray {
2697            value: vec![
2698                KclValue::Number {
2699                    value: 0.0,
2700                    ty: NumericType::count(),
2701                    meta: Vec::new(),
2702                },
2703                KclValue::Number {
2704                    value: 1.0,
2705                    ty: NumericType::count(),
2706                    meta: Vec::new(),
2707                },
2708                KclValue::HomArray {
2709                    value: vec![
2710                        KclValue::Number {
2711                            value: 2.0,
2712                            ty: NumericType::count(),
2713                            meta: Vec::new(),
2714                        },
2715                        KclValue::Number {
2716                            value: 3.0,
2717                            ty: NumericType::count(),
2718                            meta: Vec::new(),
2719                        },
2720                    ],
2721                    ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2722                },
2723            ],
2724            ty: RuntimeType::any(),
2725        };
2726
2727        // Principal types
2728        let tym1 = RuntimeType::Array(
2729            Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2730            ArrayLen::Minimum(1),
2731        );
2732
2733        let result = KclValue::HomArray {
2734            value: vec![
2735                KclValue::Number {
2736                    value: 0.0,
2737                    ty: NumericType::count(),
2738                    meta: Vec::new(),
2739                },
2740                KclValue::Number {
2741                    value: 1.0,
2742                    ty: NumericType::count(),
2743                    meta: Vec::new(),
2744                },
2745                KclValue::Number {
2746                    value: 2.0,
2747                    ty: NumericType::count(),
2748                    meta: Vec::new(),
2749                },
2750                KclValue::Number {
2751                    value: 3.0,
2752                    ty: NumericType::count(),
2753                    meta: Vec::new(),
2754                },
2755            ],
2756            ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2757        };
2758        assert_coerce_results(&mixed1, &tym1, &result, &mut exec_state);
2759        ctx.close().await;
2760    }
2761}