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