Skip to main content

kcl_lib/execution/
types.rs

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