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