Skip to main content

kcl_lib/execution/
types.rs

1use std::collections::HashMap;
2use std::str::FromStr;
3
4use anyhow::Result;
5pub use kcl_api::NumericType;
6use kcl_api::UnitAngle;
7use kcl_api::UnitLength;
8pub use kcl_api::UnitType;
9use serde::Deserialize;
10use serde::Serialize;
11
12use crate::CompilationIssue;
13use crate::KclError;
14use crate::SourceRange;
15use crate::errors::KclErrorDetails;
16use crate::exec::PlaneKind;
17use crate::execution::ExecState;
18use crate::execution::Plane;
19use crate::execution::PlaneInfo;
20use crate::execution::Point3d;
21use crate::execution::SKETCH_OBJECT_META;
22use crate::execution::SKETCH_OBJECT_META_SKETCH;
23use crate::execution::annotations;
24use crate::execution::kcl_value::EnumTypeId;
25use crate::execution::kcl_value::KclValue;
26use crate::execution::kcl_value::TypeDef;
27use crate::execution::memory::{self};
28use crate::fmt;
29use crate::parsing::ast::types::PrimitiveType as AstPrimitiveType;
30use crate::parsing::ast::types::Type;
31use crate::parsing::token::NumericSuffix;
32use crate::std::args::FromKclValue;
33use crate::std::args::TyF64;
34
35#[derive(Debug, Clone, PartialEq)]
36pub enum RuntimeType {
37    Primitive(PrimitiveType),
38    Array(Box<RuntimeType>, ArrayLen),
39    Union(Vec<RuntimeType>),
40    Tuple(Vec<RuntimeType>),
41    Object(Vec<(String, RuntimeType)>, bool),
42    /// A user-declared nominal enum, identified by its declaration rather than
43    /// its structure. Kept out of `PrimitiveType`, which is the closed set of
44    /// built-in types that `std_ty` can name.
45    Enum(EnumTypeId),
46}
47
48impl RuntimeType {
49    pub fn any() -> Self {
50        RuntimeType::Primitive(PrimitiveType::Any)
51    }
52
53    pub fn never() -> Self {
54        RuntimeType::Primitive(PrimitiveType::Never)
55    }
56
57    pub fn any_array() -> Self {
58        RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::None)
59    }
60
61    pub fn edge() -> Self {
62        RuntimeType::Primitive(PrimitiveType::Edge)
63    }
64
65    pub fn function() -> Self {
66        RuntimeType::Primitive(PrimitiveType::Function)
67    }
68
69    pub fn segment() -> Self {
70        RuntimeType::Primitive(PrimitiveType::Segment)
71    }
72
73    /// `[Segment; 1+]`
74    pub fn segments() -> Self {
75        RuntimeType::Array(Box::new(Self::segment()), ArrayLen::Minimum(1))
76    }
77
78    pub fn sketch() -> Self {
79        RuntimeType::Primitive(PrimitiveType::Sketch)
80    }
81
82    pub fn sketch_or_surface() -> Self {
83        RuntimeType::Union(vec![Self::sketch(), Self::plane(), Self::face()])
84    }
85
86    /// `[Sketch; 1+]`
87    pub fn sketches() -> Self {
88        RuntimeType::Array(
89            Box::new(RuntimeType::Primitive(PrimitiveType::Sketch)),
90            ArrayLen::Minimum(1),
91        )
92    }
93
94    /// `[Face; 1+]`
95    pub fn faces() -> Self {
96        RuntimeType::Array(
97            Box::new(RuntimeType::Primitive(PrimitiveType::Face)),
98            ArrayLen::Minimum(1),
99        )
100    }
101
102    /// `[TaggedFace; 1+]`
103    pub fn tagged_faces() -> Self {
104        RuntimeType::Array(
105            Box::new(RuntimeType::Primitive(PrimitiveType::TaggedFace)),
106            ArrayLen::Minimum(1),
107        )
108    }
109
110    /// `[Solid; 1+]`
111    pub fn solids() -> Self {
112        RuntimeType::Array(
113            Box::new(RuntimeType::Primitive(PrimitiveType::Solid)),
114            ArrayLen::Minimum(1),
115        )
116    }
117
118    pub fn solid() -> Self {
119        RuntimeType::Primitive(PrimitiveType::Solid)
120    }
121
122    pub fn gdt() -> Self {
123        RuntimeType::Primitive(PrimitiveType::GdtAnnotation)
124    }
125
126    /// `[GdtAnnotation; 1+]`
127    pub fn gdts() -> Self {
128        RuntimeType::Array(
129            Box::new(RuntimeType::Primitive(PrimitiveType::GdtAnnotation)),
130            ArrayLen::Minimum(1),
131        )
132    }
133
134    /// `[Helix; 1+]`
135    pub fn helices() -> Self {
136        RuntimeType::Array(
137            Box::new(RuntimeType::Primitive(PrimitiveType::Helix)),
138            ArrayLen::Minimum(1),
139        )
140    }
141    pub fn helix() -> Self {
142        RuntimeType::Primitive(PrimitiveType::Helix)
143    }
144
145    pub fn plane() -> Self {
146        RuntimeType::Primitive(PrimitiveType::Plane)
147    }
148
149    /// `[Plane; 1+]`
150    pub fn planes() -> Self {
151        RuntimeType::Array(
152            Box::new(RuntimeType::Primitive(PrimitiveType::Plane)),
153            ArrayLen::Minimum(1),
154        )
155    }
156
157    pub fn face() -> Self {
158        RuntimeType::Primitive(PrimitiveType::Face)
159    }
160
161    pub fn tag_decl() -> Self {
162        RuntimeType::Primitive(PrimitiveType::TagDecl)
163    }
164
165    pub fn tagged_face() -> Self {
166        RuntimeType::Primitive(PrimitiveType::TaggedFace)
167    }
168
169    pub fn tagged_face_or_segment() -> Self {
170        RuntimeType::Union(vec![
171            RuntimeType::Primitive(PrimitiveType::TaggedFace),
172            RuntimeType::Primitive(PrimitiveType::Segment),
173        ])
174    }
175
176    pub fn tagged_edge() -> Self {
177        RuntimeType::Primitive(PrimitiveType::TaggedEdge)
178    }
179
180    pub fn bool() -> Self {
181        RuntimeType::Primitive(PrimitiveType::Boolean)
182    }
183
184    pub fn string() -> Self {
185        RuntimeType::Primitive(PrimitiveType::String)
186    }
187
188    pub fn imported() -> Self {
189        RuntimeType::Primitive(PrimitiveType::ImportedGeometry)
190    }
191
192    /// `[number; 2]`
193    pub fn point2d() -> Self {
194        RuntimeType::Array(Box::new(RuntimeType::length()), ArrayLen::Known(2))
195    }
196
197    /// `[number; 3]`
198    pub fn point3d() -> Self {
199        RuntimeType::Array(Box::new(RuntimeType::length()), ArrayLen::Known(3))
200    }
201
202    pub fn length() -> Self {
203        RuntimeType::Primitive(PrimitiveType::Number(NumericType::Known(UnitType::GenericLength)))
204    }
205
206    pub fn known_length(len: UnitLength) -> Self {
207        RuntimeType::Primitive(PrimitiveType::Number(NumericType::Known(UnitType::Length(len))))
208    }
209
210    pub fn angle() -> Self {
211        RuntimeType::Primitive(PrimitiveType::Number(NumericType::Known(UnitType::GenericAngle)))
212    }
213
214    pub fn radians() -> Self {
215        RuntimeType::Primitive(PrimitiveType::Number(NumericType::Known(UnitType::Angle(
216            UnitAngle::Radians,
217        ))))
218    }
219
220    pub fn degrees() -> Self {
221        RuntimeType::Primitive(PrimitiveType::Number(NumericType::Known(UnitType::Angle(
222            UnitAngle::Degrees,
223        ))))
224    }
225
226    pub fn count() -> Self {
227        RuntimeType::Primitive(PrimitiveType::Number(NumericType::Known(UnitType::Count)))
228    }
229
230    pub fn num_any() -> Self {
231        RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any))
232    }
233
234    pub fn from_parsed(
235        value: Type,
236        exec_state: &mut ExecState,
237        source_range: SourceRange,
238        constrainable: bool,
239        suppress_warnings: bool,
240    ) -> Result<Self, CompilationIssue> {
241        match value {
242            Type::Primitive(pt) => Self::from_parsed_primitive(pt, exec_state, source_range, suppress_warnings),
243            Type::Array { ty, len } => {
244                Self::from_parsed(*ty, exec_state, source_range, constrainable, suppress_warnings)
245                    .map(|t| RuntimeType::Array(Box::new(t), len))
246            }
247            Type::Union { tys } => tys
248                .into_iter()
249                .map(|t| Self::from_parsed(t.inner, exec_state, source_range, constrainable, suppress_warnings))
250                .collect::<Result<Vec<_>, CompilationIssue>>()
251                .map(RuntimeType::Union),
252            Type::Object { properties } => properties
253                .into_iter()
254                .map(|(id, ty)| {
255                    RuntimeType::from_parsed(ty.inner, exec_state, source_range, constrainable, suppress_warnings)
256                        .map(|ty| (id.name.clone(), ty))
257                })
258                .collect::<Result<Vec<_>, CompilationIssue>>()
259                .map(|values| RuntimeType::Object(values, constrainable)),
260        }
261    }
262
263    fn from_parsed_primitive(
264        value: AstPrimitiveType,
265        exec_state: &mut ExecState,
266        source_range: SourceRange,
267        suppress_warnings: bool,
268    ) -> Result<Self, CompilationIssue> {
269        Ok(match value {
270            AstPrimitiveType::Any => RuntimeType::Primitive(PrimitiveType::Any),
271            AstPrimitiveType::Never => RuntimeType::never(),
272            AstPrimitiveType::None => RuntimeType::Primitive(PrimitiveType::None),
273            AstPrimitiveType::String => RuntimeType::Primitive(PrimitiveType::String),
274            AstPrimitiveType::Boolean => RuntimeType::Primitive(PrimitiveType::Boolean),
275            AstPrimitiveType::Number(suffix) => {
276                let ty = match suffix {
277                    NumericSuffix::None => NumericType::Any,
278                    _ => NumericType::from_parsed(suffix, &exec_state.mod_local.settings),
279                };
280                RuntimeType::Primitive(PrimitiveType::Number(ty))
281            }
282            AstPrimitiveType::Named { id } => Self::from_alias(&id.name, exec_state, source_range, suppress_warnings)?,
283            AstPrimitiveType::TagDecl => RuntimeType::Primitive(PrimitiveType::TagDecl),
284            AstPrimitiveType::ImportedGeometry => RuntimeType::Primitive(PrimitiveType::ImportedGeometry),
285            AstPrimitiveType::Function(_) => RuntimeType::Primitive(PrimitiveType::Function),
286        })
287    }
288
289    pub fn from_alias(
290        alias: &str,
291        exec_state: &mut ExecState,
292        source_range: SourceRange,
293        suppress_warnings: bool,
294    ) -> Result<Self, CompilationIssue> {
295        let ty_val = exec_state
296            .stack()
297            .get(&format!("{}{}", memory::TYPE_PREFIX, alias), source_range)
298            .map_err(|_| CompilationIssue::err(source_range, format!("Unknown type: {alias}")))?;
299
300        Ok(match ty_val {
301            KclValue::Type {
302                value, experimental, ..
303            } => {
304                let result = match value {
305                    TypeDef::RustRepr(ty, _) => RuntimeType::Primitive(ty),
306                    TypeDef::Alias(ty) => ty,
307                    TypeDef::Enum(def) => RuntimeType::Enum(def.id().clone()),
308                };
309                if experimental && !suppress_warnings {
310                    exec_state.warn_experimental(&format!("the type `{alias}`"), source_range);
311                }
312                result
313            }
314            _ => unreachable!(),
315        })
316    }
317
318    pub fn human_friendly_type(&self) -> String {
319        match self {
320            RuntimeType::Primitive(ty) => ty.to_string(),
321            RuntimeType::Array(ty, ArrayLen::None | ArrayLen::Minimum(0)) => {
322                format!("an array of {}", ty.display_multiple())
323            }
324            RuntimeType::Array(ty, ArrayLen::Minimum(1)) => format!("one or more {}", ty.display_multiple()),
325            RuntimeType::Array(ty, ArrayLen::Minimum(n)) => {
326                format!("an array of {n} or more {}", ty.display_multiple())
327            }
328            RuntimeType::Array(ty, ArrayLen::Known(n)) => format!("an array of {n} {}", ty.display_multiple()),
329            RuntimeType::Union(tys) => tys
330                .iter()
331                .map(Self::human_friendly_type)
332                .collect::<Vec<_>>()
333                .join(" or "),
334            RuntimeType::Tuple(tys) => format!(
335                "a tuple with values of types ({})",
336                tys.iter().map(Self::human_friendly_type).collect::<Vec<_>>().join(", ")
337            ),
338            RuntimeType::Object(..) => format!("an object with fields {self}"),
339            RuntimeType::Enum(id) => id.declared_name().to_owned(),
340        }
341    }
342
343    // Subtype with no coercion, including refining numeric types.
344    pub(crate) fn subtype(&self, sup: &RuntimeType) -> bool {
345        use RuntimeType::*;
346
347        match (self, sup) {
348            (Primitive(PrimitiveType::Never), _) => true,
349            (_, Primitive(PrimitiveType::Any)) => true,
350            (Primitive(t1), Primitive(t2)) => t1.subtype(t2),
351            (Array(t1, l1), Array(t2, l2)) => t1.subtype(t2) && l1.subtype(*l2),
352            (Tuple(t1), Tuple(t2)) => t1.len() == t2.len() && t1.iter().zip(t2).all(|(t1, t2)| t1.subtype(t2)),
353
354            (Union(ts1), t2) => ts1.iter().all(|t| t.subtype(t2)),
355            (t1, Union(ts2)) => ts2.iter().any(|t| t1.subtype(t)),
356
357            (Object(t1, _), Object(t2, _)) => t2
358                .iter()
359                .all(|(f, t)| t1.iter().any(|(ff, tt)| f == ff && tt.subtype(t))),
360
361            // Enums are nominal, so an enum is a subtype of itself and nothing
362            // else. This arm is load-bearing: the catch-all below would answer
363            // `false` for two identical enums and quietly break reflexivity.
364            (Enum(id1), Enum(id2)) => id1 == id2,
365
366            // Equivalence between singleton types and single-item arrays/tuples of the same type (plus transitivity with the array subtyping).
367            (t1, RuntimeType::Array(t2, l)) if t1.subtype(t2) && ArrayLen::Known(1).subtype(*l) => true,
368            (RuntimeType::Array(t1, ArrayLen::Known(1)), t2) if t1.subtype(t2) => true,
369            (t1, RuntimeType::Tuple(t2)) if !t2.is_empty() && t1.subtype(&t2[0]) => true,
370            (RuntimeType::Tuple(t1), t2) if t1.len() == 1 && t1[0].subtype(t2) => true,
371
372            // Equivalence between Axis types and their object representation.
373            (Object(t1, _), Primitive(PrimitiveType::Axis2d)) => {
374                t1.iter()
375                    .any(|(n, t)| n == "origin" && t.subtype(&RuntimeType::point2d()))
376                    && t1
377                        .iter()
378                        .any(|(n, t)| n == "direction" && t.subtype(&RuntimeType::point2d()))
379            }
380            (Object(t1, _), Primitive(PrimitiveType::Axis3d)) => {
381                t1.iter()
382                    .any(|(n, t)| n == "origin" && t.subtype(&RuntimeType::point3d()))
383                    && t1
384                        .iter()
385                        .any(|(n, t)| n == "direction" && t.subtype(&RuntimeType::point3d()))
386            }
387            (Primitive(PrimitiveType::Axis2d), Object(t2, _)) => {
388                t2.iter()
389                    .any(|(n, t)| n == "origin" && t.subtype(&RuntimeType::point2d()))
390                    && t2
391                        .iter()
392                        .any(|(n, t)| n == "direction" && t.subtype(&RuntimeType::point2d()))
393            }
394            (Primitive(PrimitiveType::Axis3d), Object(t2, _)) => {
395                t2.iter()
396                    .any(|(n, t)| n == "origin" && t.subtype(&RuntimeType::point3d()))
397                    && t2
398                        .iter()
399                        .any(|(n, t)| n == "direction" && t.subtype(&RuntimeType::point3d()))
400            }
401            _ => false,
402        }
403    }
404
405    fn display_multiple(&self) -> String {
406        match self {
407            RuntimeType::Primitive(ty) => ty.display_multiple(),
408            RuntimeType::Array(..) => "arrays".to_owned(),
409            RuntimeType::Union(tys) => tys
410                .iter()
411                .map(|t| t.display_multiple())
412                .collect::<Vec<_>>()
413                .join(" or "),
414            RuntimeType::Tuple(_) => "tuples".to_owned(),
415            RuntimeType::Object(..) => format!("objects with fields {self}"),
416            RuntimeType::Enum(id) => format!("`{}` values", id.declared_name()),
417        }
418    }
419}
420
421impl std::fmt::Display for RuntimeType {
422    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
423        match self {
424            RuntimeType::Primitive(t) => t.fmt(f),
425            RuntimeType::Array(t, l) => match l {
426                ArrayLen::None => write!(f, "[{t}]"),
427                ArrayLen::Minimum(n) => write!(f, "[{t}; {n}+]"),
428                ArrayLen::Known(n) => write!(f, "[{t}; {n}]"),
429            },
430            RuntimeType::Tuple(ts) => write!(
431                f,
432                "({})",
433                ts.iter().map(|t| t.to_string()).collect::<Vec<_>>().join(", ")
434            ),
435            RuntimeType::Union(ts) => write!(
436                f,
437                "{}",
438                ts.iter().map(|t| t.to_string()).collect::<Vec<_>>().join(" | ")
439            ),
440            RuntimeType::Object(items, _) => write!(
441                f,
442                "{{ {} }}",
443                items
444                    .iter()
445                    .map(|(n, t)| format!("{n}: {t}"))
446                    .collect::<Vec<_>>()
447                    .join(", ")
448            ),
449            RuntimeType::Enum(id) => write!(f, "{}", id.declared_name()),
450        }
451    }
452}
453
454#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, ts_rs::TS)]
455pub enum ArrayLen {
456    None,
457    Minimum(usize),
458    Known(usize),
459}
460
461impl ArrayLen {
462    pub fn subtype(self, other: ArrayLen) -> bool {
463        match (self, other) {
464            (_, ArrayLen::None) => true,
465            (ArrayLen::Minimum(s1), ArrayLen::Minimum(s2)) if s1 >= s2 => true,
466            (ArrayLen::Known(s1), ArrayLen::Minimum(s2)) if s1 >= s2 => true,
467            (ArrayLen::None, ArrayLen::Minimum(0)) => true,
468            (ArrayLen::Known(s1), ArrayLen::Known(s2)) if s1 == s2 => true,
469            _ => false,
470        }
471    }
472
473    /// True if the length constraint is satisfied by the supplied length.
474    pub fn satisfied(self, len: usize, allow_shrink: bool) -> Option<usize> {
475        match self {
476            ArrayLen::None => Some(len),
477            ArrayLen::Minimum(s) => (len >= s).then_some(len),
478            ArrayLen::Known(s) => (if allow_shrink { len >= s } else { len == s }).then_some(s),
479        }
480    }
481
482    pub fn human_friendly_type(self) -> String {
483        match self {
484            ArrayLen::None | ArrayLen::Minimum(0) => "any number of elements".to_owned(),
485            ArrayLen::Minimum(1) => "at least 1 element".to_owned(),
486            ArrayLen::Minimum(n) => format!("at least {n} elements"),
487            ArrayLen::Known(0) => "no elements".to_owned(),
488            ArrayLen::Known(1) => "exactly 1 element".to_owned(),
489            ArrayLen::Known(n) => format!("exactly {n} elements"),
490        }
491    }
492}
493
494#[derive(Debug, Clone, PartialEq)]
495pub enum PrimitiveType {
496    Any,
497    Never,
498    None,
499    Number(NumericType),
500    String,
501    Boolean,
502    TaggedEdge,
503    TaggedFace,
504    TagDecl,
505    GdtAnnotation,
506    Segment,
507    Sketch,
508    Constraint,
509    Solid,
510    Plane,
511    Helix,
512    Face,
513    Edge,
514    BoundedEdge,
515    Axis2d,
516    Axis3d,
517    ImportedGeometry,
518    Function,
519}
520
521impl PrimitiveType {
522    fn display_multiple(&self) -> String {
523        match self {
524            PrimitiveType::Any => "any values".to_owned(),
525            PrimitiveType::Never => "values of type `never`".to_owned(),
526            PrimitiveType::None => "none values".to_owned(),
527            PrimitiveType::Number(NumericType::Known(unit)) => format!("numbers({unit})"),
528            PrimitiveType::Number(_) => "numbers".to_owned(),
529            PrimitiveType::String => "strings".to_owned(),
530            PrimitiveType::Boolean => "bools".to_owned(),
531            PrimitiveType::GdtAnnotation => "GD&T Annotations".to_owned(),
532            PrimitiveType::Segment => "Segments".to_owned(),
533            PrimitiveType::Sketch => "Sketches".to_owned(),
534            PrimitiveType::Constraint => "Constraints".to_owned(),
535            PrimitiveType::Solid => "Solids".to_owned(),
536            PrimitiveType::Plane => "Planes".to_owned(),
537            PrimitiveType::Helix => "Helices".to_owned(),
538            PrimitiveType::Face => "Faces".to_owned(),
539            PrimitiveType::Edge => "Edges".to_owned(),
540            PrimitiveType::BoundedEdge => "BoundedEdges".to_owned(),
541            PrimitiveType::Axis2d => "2d axes".to_owned(),
542            PrimitiveType::Axis3d => "3d axes".to_owned(),
543            PrimitiveType::ImportedGeometry => "imported geometries".to_owned(),
544            PrimitiveType::Function => "functions".to_owned(),
545            PrimitiveType::TagDecl => "tag declarators".to_owned(),
546            PrimitiveType::TaggedEdge => "tagged edges".to_owned(),
547            PrimitiveType::TaggedFace => "tagged faces".to_owned(),
548        }
549    }
550
551    fn subtype(&self, other: &PrimitiveType) -> bool {
552        match (self, other) {
553            (PrimitiveType::Never, _) => true,
554            (_, PrimitiveType::Any) => true,
555            (PrimitiveType::Number(n1), PrimitiveType::Number(n2)) => n1.subtype(n2),
556            (PrimitiveType::TaggedEdge, PrimitiveType::TaggedFace)
557            | (PrimitiveType::TaggedEdge, PrimitiveType::Edge) => true,
558            (t1, t2) => t1 == t2,
559        }
560    }
561}
562
563impl std::fmt::Display for PrimitiveType {
564    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
565        match self {
566            PrimitiveType::Any => write!(f, "any"),
567            PrimitiveType::Never => write!(f, "never"),
568            PrimitiveType::None => write!(f, "none"),
569            PrimitiveType::Number(NumericType::Known(unit)) => write!(f, "number({unit})"),
570            PrimitiveType::Number(NumericType::Unknown) => write!(f, "number(unknown units)"),
571            PrimitiveType::Number(NumericType::Default { .. }) => write!(f, "number"),
572            PrimitiveType::Number(NumericType::Any) => write!(f, "number(any units)"),
573            PrimitiveType::String => write!(f, "string"),
574            PrimitiveType::Boolean => write!(f, "bool"),
575            PrimitiveType::TagDecl => write!(f, "tag declarator"),
576            PrimitiveType::TaggedEdge => write!(f, "tagged edge"),
577            PrimitiveType::TaggedFace => write!(f, "tagged face"),
578            PrimitiveType::GdtAnnotation => write!(f, "GD&T Annotation"),
579            PrimitiveType::Segment => write!(f, "Segment"),
580            PrimitiveType::Sketch => write!(f, "Sketch"),
581            PrimitiveType::Constraint => write!(f, "Constraint"),
582            PrimitiveType::Solid => write!(f, "Solid"),
583            PrimitiveType::Plane => write!(f, "Plane"),
584            PrimitiveType::Face => write!(f, "Face"),
585            PrimitiveType::Edge => write!(f, "Edge"),
586            PrimitiveType::BoundedEdge => write!(f, "BoundedEdge"),
587            PrimitiveType::Axis2d => write!(f, "Axis2d"),
588            PrimitiveType::Axis3d => write!(f, "Axis3d"),
589            PrimitiveType::Helix => write!(f, "Helix"),
590            PrimitiveType::ImportedGeometry => write!(f, "ImportedGeometry"),
591            PrimitiveType::Function => write!(f, "fn"),
592        }
593    }
594}
595
596pub trait NumericTypeExt {
597    fn count() -> Self;
598
599    fn mm() -> Self;
600
601    fn radians() -> Self;
602
603    fn degrees() -> Self;
604
605    fn length(unit: UnitLength) -> Self;
606
607    fn optional_length(unit: Option<UnitLength>) -> Self;
608
609    fn angle(unit: UnitAngle) -> Self;
610
611    /// Combine two types when we expect them to be equal, erring on the side of less coercion. To be
612    /// precise, only adjusting one number or the other when they are of known types.
613    ///
614    /// This combinator function is suitable for comparisons where uncertainty should
615    /// be handled by the user.
616    fn combine_eq(a: TyF64, b: TyF64, exec_state: &mut ExecState, source_range: SourceRange)
617    -> (f64, f64, NumericType);
618
619    /// Combine two types when we expect them to be equal, erring on the side of more coercion. Including adjusting when
620    /// we are certain about only one type.
621    ///
622    /// This combinator function is suitable for situations where the user would almost certainly want the types to be
623    /// coerced together, for example two arguments to the same function or two numbers in an array being used as a point.
624    ///
625    /// Prefer to use `combine_eq` if possible since using that prioritises correctness over ergonomics.
626    fn combine_eq_coerce(
627        a: TyF64,
628        b: TyF64,
629        for_errs: Option<(&mut ExecState, SourceRange)>,
630    ) -> (f64, f64, NumericType);
631
632    fn combine_eq_array(input: &[TyF64]) -> (Vec<f64>, NumericType);
633
634    /// Combine two types for multiplication-like operations.
635    fn combine_mul(a: TyF64, b: TyF64) -> (f64, f64, NumericType);
636
637    /// Combine two types for division-like operations.
638    fn combine_div(a: TyF64, b: TyF64) -> (f64, f64, NumericType);
639
640    /// Combine two types for modulo-like operations.
641    fn combine_mod(a: TyF64, b: TyF64) -> (f64, f64, NumericType);
642
643    /// Combine two types for range operations.
644    ///
645    /// This combinator function is suitable for ranges where uncertainty should
646    /// be handled by the user, and it doesn't make sense to convert units. So
647    /// this is one of th most conservative ways to combine types.
648    fn combine_range(
649        a: TyF64,
650        b: TyF64,
651        exec_state: &mut ExecState,
652        source_range: SourceRange,
653    ) -> Result<(f64, f64, NumericType), KclError>;
654
655    fn from_parsed(suffix: NumericSuffix, settings: &super::MetaSettings) -> Self;
656
657    fn subtype(&self, other: &NumericType) -> bool;
658
659    fn is_unknown(&self) -> bool;
660
661    fn is_fully_specified(&self) -> bool;
662
663    fn example_ty(&self) -> Option<String>;
664
665    fn coerce(&self, val: &KclValue) -> Result<KclValue, CoercionError>;
666
667    fn as_length(&self) -> Option<UnitLength>;
668}
669
670impl NumericTypeExt for NumericType {
671    fn count() -> Self {
672        NumericType::Known(UnitType::Count)
673    }
674
675    fn mm() -> Self {
676        NumericType::Known(UnitType::Length(UnitLength::Millimeters))
677    }
678
679    fn radians() -> Self {
680        NumericType::Known(UnitType::Angle(UnitAngle::Radians))
681    }
682
683    fn degrees() -> Self {
684        NumericType::Known(UnitType::Angle(UnitAngle::Degrees))
685    }
686
687    fn length(unit: UnitLength) -> Self {
688        NumericType::Known(UnitType::Length(unit))
689    }
690
691    fn optional_length(unit: Option<UnitLength>) -> Self {
692        match unit {
693            Some(unit) => Self::length(unit),
694            None => NumericType::Unknown,
695        }
696    }
697
698    fn angle(unit: UnitAngle) -> Self {
699        NumericType::Known(UnitType::Angle(unit))
700    }
701
702    /// Combine two types when we expect them to be equal, erring on the side of less coercion. To be
703    /// precise, only adjusting one number or the other when they are of known types.
704    ///
705    /// This combinator function is suitable for comparisons where uncertainty should
706    /// be handled by the user.
707    fn combine_eq(
708        a: TyF64,
709        b: TyF64,
710        exec_state: &mut ExecState,
711        source_range: SourceRange,
712    ) -> (f64, f64, NumericType) {
713        use NumericType::*;
714        match (a.ty, b.ty) {
715            (at, bt) if at == bt => (a.n, b.n, at),
716            (at, Any) => (a.n, b.n, at),
717            (Any, bt) => (a.n, b.n, bt),
718
719            (t @ Known(UnitType::Length(l1)), Known(UnitType::Length(l2))) => (a.n, adjust_length(l2, b.n, l1).0, t),
720            (t @ Known(UnitType::Angle(a1)), Known(UnitType::Angle(a2))) => (a.n, adjust_angle(a2, b.n, a1).0, t),
721
722            (t @ Known(UnitType::Length(_)), Known(UnitType::GenericLength)) => (a.n, b.n, t),
723            (Known(UnitType::GenericLength), t @ Known(UnitType::Length(_))) => (a.n, b.n, t),
724            (t @ Known(UnitType::Angle(_)), Known(UnitType::GenericAngle)) => (a.n, b.n, t),
725            (Known(UnitType::GenericAngle), t @ Known(UnitType::Angle(_))) => (a.n, b.n, t),
726
727            (Known(UnitType::Count), Default { .. }) | (Default { .. }, Known(UnitType::Count)) => {
728                (a.n, b.n, Known(UnitType::Count))
729            }
730            (t @ Known(UnitType::Length(l1)), Default { len: l2, .. }) if l1 == l2 => (a.n, b.n, t),
731            (Default { len: l1, .. }, t @ Known(UnitType::Length(l2))) if l1 == l2 => (a.n, b.n, t),
732            (t @ Known(UnitType::Angle(a1)), Default { angle: a2, .. }) if a1 == a2 => {
733                if b.n != 0.0 {
734                    exec_state.warn(
735                        CompilationIssue::err(source_range, "Prefer to use explicit units for angles"),
736                        annotations::WARN_ANGLE_UNITS,
737                    );
738                }
739                (a.n, b.n, t)
740            }
741            (Default { angle: a1, .. }, t @ Known(UnitType::Angle(a2))) if a1 == a2 => {
742                if a.n != 0.0 {
743                    exec_state.warn(
744                        CompilationIssue::err(source_range, "Prefer to use explicit units for angles"),
745                        annotations::WARN_ANGLE_UNITS,
746                    );
747                }
748                (a.n, b.n, t)
749            }
750
751            _ => (a.n, b.n, Unknown),
752        }
753    }
754
755    /// Combine two types when we expect them to be equal, erring on the side of more coercion. Including adjusting when
756    /// we are certain about only one type.
757    ///
758    /// This combinator function is suitable for situations where the user would almost certainly want the types to be
759    /// coerced together, for example two arguments to the same function or two numbers in an array being used as a point.
760    ///
761    /// Prefer to use `combine_eq` if possible since using that prioritises correctness over ergonomics.
762    fn combine_eq_coerce(
763        a: TyF64,
764        b: TyF64,
765        for_errs: Option<(&mut ExecState, SourceRange)>,
766    ) -> (f64, f64, NumericType) {
767        use NumericType::*;
768        match (a.ty, b.ty) {
769            (at, bt) if at == bt => (a.n, b.n, at),
770            (at, Any) => (a.n, b.n, at),
771            (Any, bt) => (a.n, b.n, bt),
772
773            // Known types and compatible, but needs adjustment.
774            (t @ Known(UnitType::Length(l1)), Known(UnitType::Length(l2))) => (a.n, adjust_length(l2, b.n, l1).0, t),
775            (t @ Known(UnitType::Angle(a1)), Known(UnitType::Angle(a2))) => (a.n, adjust_angle(a2, b.n, a1).0, t),
776
777            (t @ Known(UnitType::Length(_)), Known(UnitType::GenericLength)) => (a.n, b.n, t),
778            (Known(UnitType::GenericLength), t @ Known(UnitType::Length(_))) => (a.n, b.n, t),
779            (t @ Known(UnitType::Angle(_)), Known(UnitType::GenericAngle)) => (a.n, b.n, t),
780            (Known(UnitType::GenericAngle), t @ Known(UnitType::Angle(_))) => (a.n, b.n, t),
781
782            // Known and unknown => we assume the known one, possibly with adjustment
783            (Known(UnitType::Count), Default { .. }) | (Default { .. }, Known(UnitType::Count)) => {
784                (a.n, b.n, Known(UnitType::Count))
785            }
786
787            (t @ Known(UnitType::Length(l1)), Default { len: l2, .. }) => (a.n, adjust_length(l2, b.n, l1).0, t),
788            (Default { len: l1, .. }, t @ Known(UnitType::Length(l2))) => (adjust_length(l1, a.n, l2).0, b.n, t),
789            (t @ Known(UnitType::Angle(a1)), Default { angle: a2, .. }) => {
790                if let Some((exec_state, source_range)) = for_errs
791                    && b.n != 0.0
792                {
793                    exec_state.warn(
794                        CompilationIssue::err(source_range, "Prefer to use explicit units for angles"),
795                        annotations::WARN_ANGLE_UNITS,
796                    );
797                }
798                (a.n, adjust_angle(a2, b.n, a1).0, t)
799            }
800            (Default { angle: a1, .. }, t @ Known(UnitType::Angle(a2))) => {
801                if let Some((exec_state, source_range)) = for_errs
802                    && a.n != 0.0
803                {
804                    exec_state.warn(
805                        CompilationIssue::err(source_range, "Prefer to use explicit units for angles"),
806                        annotations::WARN_ANGLE_UNITS,
807                    );
808                }
809                (adjust_angle(a1, a.n, a2).0, b.n, t)
810            }
811
812            (Default { len: l1, .. }, Known(UnitType::GenericLength)) => (a.n, b.n, Self::length(l1)),
813            (Known(UnitType::GenericLength), Default { len: l2, .. }) => (a.n, b.n, Self::length(l2)),
814            (Default { angle: a1, .. }, Known(UnitType::GenericAngle)) => {
815                if let Some((exec_state, source_range)) = for_errs
816                    && b.n != 0.0
817                {
818                    exec_state.warn(
819                        CompilationIssue::err(source_range, "Prefer to use explicit units for angles"),
820                        annotations::WARN_ANGLE_UNITS,
821                    );
822                }
823                (a.n, b.n, Self::angle(a1))
824            }
825            (Known(UnitType::GenericAngle), Default { angle: a2, .. }) => {
826                if let Some((exec_state, source_range)) = for_errs
827                    && a.n != 0.0
828                {
829                    exec_state.warn(
830                        CompilationIssue::err(source_range, "Prefer to use explicit units for angles"),
831                        annotations::WARN_ANGLE_UNITS,
832                    );
833                }
834                (a.n, b.n, Self::angle(a2))
835            }
836
837            (Known(_), Known(_)) | (Default { .. }, Default { .. }) | (_, Unknown) | (Unknown, _) => {
838                (a.n, b.n, Unknown)
839            }
840        }
841    }
842
843    fn combine_eq_array(input: &[TyF64]) -> (Vec<f64>, NumericType) {
844        use NumericType::*;
845        let result = input.iter().map(|t| t.n).collect();
846
847        let mut ty = Any;
848        for i in input {
849            if i.ty == Any || ty == i.ty {
850                continue;
851            }
852
853            // The cases where we check the values for 0.0 are so we don't crash out where a conversion would always be safe
854            match (&ty, &i.ty) {
855                (Any, Default { .. }) if i.n == 0.0 => {}
856                (Any, t) => {
857                    ty = *t;
858                }
859                (_, Unknown) | (Default { .. }, Default { .. }) => return (result, Unknown),
860
861                (Known(UnitType::Count), Default { .. }) | (Default { .. }, Known(UnitType::Count)) => {
862                    ty = Known(UnitType::Count);
863                }
864
865                (Known(UnitType::Length(l1)), Default { len: l2, .. }) if l1 == l2 || i.n == 0.0 => {}
866                (Known(UnitType::Angle(a1)), Default { angle: a2, .. }) if a1 == a2 || i.n == 0.0 => {}
867
868                (Default { len: l1, .. }, Known(UnitType::Length(l2))) if l1 == l2 => {
869                    ty = Known(UnitType::Length(*l2));
870                }
871                (Default { angle: a1, .. }, Known(UnitType::Angle(a2))) if a1 == a2 => {
872                    ty = Known(UnitType::Angle(*a2));
873                }
874
875                _ => return (result, Unknown),
876            }
877        }
878
879        if ty == Any && !input.is_empty() {
880            ty = input[0].ty;
881        }
882
883        (result, ty)
884    }
885
886    /// Combine two types for multiplication-like operations.
887    fn combine_mul(a: TyF64, b: TyF64) -> (f64, f64, NumericType) {
888        use NumericType::*;
889        match (a.ty, b.ty) {
890            (at @ Default { .. }, bt @ Default { .. }) if at == bt => (a.n, b.n, at),
891            (Default { .. }, Default { .. }) => (a.n, b.n, Unknown),
892            (Known(UnitType::Count), bt) => (a.n, b.n, bt),
893            (at, Known(UnitType::Count)) => (a.n, b.n, at),
894            (at @ Known(_), Default { .. }) | (Default { .. }, at @ Known(_)) => (a.n, b.n, at),
895            (Any, Any) => (a.n, b.n, Any),
896            _ => (a.n, b.n, Unknown),
897        }
898    }
899
900    /// Combine two types for division-like operations.
901    fn combine_div(a: TyF64, b: TyF64) -> (f64, f64, NumericType) {
902        use NumericType::*;
903        match (a.ty, b.ty) {
904            (at @ Default { .. }, bt @ Default { .. }) if at == bt => (a.n, b.n, at),
905            (at, bt) if at == bt => (a.n, b.n, Known(UnitType::Count)),
906            (Default { .. }, Default { .. }) => (a.n, b.n, Unknown),
907            (at, Known(UnitType::Count) | Any) => (a.n, b.n, at),
908            (at @ Known(_), Default { .. }) => (a.n, b.n, at),
909            (Known(UnitType::Count), _) => (a.n, b.n, Known(UnitType::Count)),
910            _ => (a.n, b.n, Unknown),
911        }
912    }
913
914    /// Combine two types for modulo-like operations.
915    fn combine_mod(a: TyF64, b: TyF64) -> (f64, f64, NumericType) {
916        use NumericType::*;
917        match (a.ty, b.ty) {
918            (at @ Default { .. }, bt @ Default { .. }) if at == bt => (a.n, b.n, at),
919            (at, bt) if at == bt => (a.n, b.n, at),
920            (Default { .. }, Default { .. }) => (a.n, b.n, Unknown),
921            (at, Known(UnitType::Count) | Any) => (a.n, b.n, at),
922            (at @ Known(_), Default { .. }) => (a.n, b.n, at),
923            (Known(UnitType::Count), _) => (a.n, b.n, Known(UnitType::Count)),
924            _ => (a.n, b.n, Unknown),
925        }
926    }
927
928    /// Combine two types for range operations.
929    ///
930    /// This combinator function is suitable for ranges where uncertainty should
931    /// be handled by the user, and it doesn't make sense to convert units. So
932    /// this is one of th most conservative ways to combine types.
933    fn combine_range(
934        a: TyF64,
935        b: TyF64,
936        exec_state: &mut ExecState,
937        source_range: SourceRange,
938    ) -> Result<(f64, f64, NumericType), KclError> {
939        use NumericType::*;
940        match (a.ty, b.ty) {
941            (at, bt) if at == bt => Ok((a.n, b.n, at)),
942            (at, Any) => Ok((a.n, b.n, at)),
943            (Any, bt) => Ok((a.n, b.n, bt)),
944
945            (Known(UnitType::Length(l1)), Known(UnitType::Length(l2))) => {
946                Err(KclError::new_semantic(KclErrorDetails::new(
947                    format!("Range start and range end have incompatible units: {l1} and {l2}"),
948                    vec![source_range],
949                )))
950            }
951            (Known(UnitType::Angle(a1)), Known(UnitType::Angle(a2))) => {
952                Err(KclError::new_semantic(KclErrorDetails::new(
953                    format!("Range start and range end have incompatible units: {a1} and {a2}"),
954                    vec![source_range],
955                )))
956            }
957
958            (t @ Known(UnitType::Length(_)), Known(UnitType::GenericLength)) => Ok((a.n, b.n, t)),
959            (Known(UnitType::GenericLength), t @ Known(UnitType::Length(_))) => Ok((a.n, b.n, t)),
960            (t @ Known(UnitType::Angle(_)), Known(UnitType::GenericAngle)) => Ok((a.n, b.n, t)),
961            (Known(UnitType::GenericAngle), t @ Known(UnitType::Angle(_))) => Ok((a.n, b.n, t)),
962
963            (Known(UnitType::Count), Default { .. }) | (Default { .. }, Known(UnitType::Count)) => {
964                Ok((a.n, b.n, Known(UnitType::Count)))
965            }
966            (t @ Known(UnitType::Length(l1)), Default { len: l2, .. }) if l1 == l2 => Ok((a.n, b.n, t)),
967            (Default { len: l1, .. }, t @ Known(UnitType::Length(l2))) if l1 == l2 => Ok((a.n, b.n, t)),
968            (t @ Known(UnitType::Angle(a1)), Default { angle: a2, .. }) if a1 == a2 => {
969                if b.n != 0.0 {
970                    exec_state.warn(
971                        CompilationIssue::err(source_range, "Prefer to use explicit units for angles"),
972                        annotations::WARN_ANGLE_UNITS,
973                    );
974                }
975                Ok((a.n, b.n, t))
976            }
977            (Default { angle: a1, .. }, t @ Known(UnitType::Angle(a2))) if a1 == a2 => {
978                if a.n != 0.0 {
979                    exec_state.warn(
980                        CompilationIssue::err(source_range, "Prefer to use explicit units for angles"),
981                        annotations::WARN_ANGLE_UNITS,
982                    );
983                }
984                Ok((a.n, b.n, t))
985            }
986
987            _ => {
988                let a = fmt::human_display_number(a.n, a.ty);
989                let b = fmt::human_display_number(b.n, b.ty);
990                Err(KclError::new_semantic(KclErrorDetails::new(
991                    format!(
992                        "Range start and range end must be of the same type and have compatible units, but found {a} and {b}",
993                    ),
994                    vec![source_range],
995                )))
996            }
997        }
998    }
999
1000    fn from_parsed(suffix: NumericSuffix, settings: &super::MetaSettings) -> Self {
1001        match suffix {
1002            NumericSuffix::None => NumericType::Default {
1003                len: settings.default_length_units,
1004                angle: settings.default_angle_units,
1005            },
1006            NumericSuffix::Count => NumericType::Known(UnitType::Count),
1007            NumericSuffix::Length => NumericType::Known(UnitType::GenericLength),
1008            NumericSuffix::Angle => NumericType::Known(UnitType::GenericAngle),
1009            NumericSuffix::Mm => NumericType::Known(UnitType::Length(UnitLength::Millimeters)),
1010            NumericSuffix::Cm => NumericType::Known(UnitType::Length(UnitLength::Centimeters)),
1011            NumericSuffix::M => NumericType::Known(UnitType::Length(UnitLength::Meters)),
1012            NumericSuffix::Inch => NumericType::Known(UnitType::Length(UnitLength::Inches)),
1013            NumericSuffix::Ft => NumericType::Known(UnitType::Length(UnitLength::Feet)),
1014            NumericSuffix::Yd => NumericType::Known(UnitType::Length(UnitLength::Yards)),
1015            NumericSuffix::Deg => NumericType::Known(UnitType::Angle(UnitAngle::Degrees)),
1016            NumericSuffix::Rad => NumericType::Known(UnitType::Angle(UnitAngle::Radians)),
1017            NumericSuffix::Unknown => NumericType::Unknown,
1018        }
1019    }
1020
1021    fn subtype(&self, other: &NumericType) -> bool {
1022        use NumericType::*;
1023
1024        match (self, other) {
1025            (_, Any) => true,
1026            (a, b) if a == b => true,
1027            (
1028                NumericType::Known(UnitType::Length(_))
1029                | NumericType::Known(UnitType::GenericLength)
1030                | NumericType::Default { .. },
1031                NumericType::Known(UnitType::GenericLength),
1032            )
1033            | (
1034                NumericType::Known(UnitType::Angle(_))
1035                | NumericType::Known(UnitType::GenericAngle)
1036                | NumericType::Default { .. },
1037                NumericType::Known(UnitType::GenericAngle),
1038            ) => true,
1039            (Unknown, _) | (_, Unknown) => false,
1040            (_, _) => false,
1041        }
1042    }
1043
1044    fn is_unknown(&self) -> bool {
1045        matches!(
1046            self,
1047            NumericType::Unknown
1048                | NumericType::Known(UnitType::GenericAngle)
1049                | NumericType::Known(UnitType::GenericLength)
1050        )
1051    }
1052
1053    fn is_fully_specified(&self) -> bool {
1054        !matches!(
1055            self,
1056            NumericType::Unknown
1057                | NumericType::Known(UnitType::GenericAngle)
1058                | NumericType::Known(UnitType::GenericLength)
1059                | NumericType::Any
1060                | NumericType::Default { .. }
1061        )
1062    }
1063
1064    fn example_ty(&self) -> Option<String> {
1065        match self {
1066            Self::Known(t) if !self.is_unknown() => Some(t.to_string()),
1067            Self::Default { len, .. } => Some(len.to_string()),
1068            _ => None,
1069        }
1070    }
1071
1072    fn coerce(&self, val: &KclValue) -> Result<KclValue, CoercionError> {
1073        let (value, ty, meta) = match val {
1074            KclValue::Number { value, ty, meta } => (value, ty, meta),
1075            // For coercion purposes, sketch vars pass through unchanged since
1076            // they will be resolved later to a number. We need the sketch var
1077            // ID.
1078            KclValue::SketchVar { .. } => return Ok(val.clone()),
1079            _ => return Err(val.into()),
1080        };
1081
1082        if ty.subtype(self) {
1083            return Ok(KclValue::Number {
1084                value: *value,
1085                ty: *ty,
1086                meta: meta.clone(),
1087            });
1088        }
1089
1090        // Not subtypes, but might be able to coerce
1091        use NumericType::*;
1092        match (ty, self) {
1093            // We don't have enough information to coerce.
1094            (Unknown, _) => Err(CoercionError::from(val).with_explicit(self.example_ty().unwrap_or("mm".to_owned()))),
1095            (_, Unknown) => Err(val.into()),
1096
1097            (Any, _) => Ok(KclValue::Number {
1098                value: *value,
1099                ty: *self,
1100                meta: meta.clone(),
1101            }),
1102
1103            // If we're coercing to a default, we treat this as coercing to Any since leaving the numeric type unspecified in a coercion situation
1104            // means accept any number rather than force the current default.
1105            (_, Default { .. }) => Ok(KclValue::Number {
1106                value: *value,
1107                ty: *ty,
1108                meta: meta.clone(),
1109            }),
1110
1111            // Known types and compatible, but needs adjustment.
1112            (Known(UnitType::Length(l1)), Known(UnitType::Length(l2))) => {
1113                let (value, ty) = adjust_length(*l1, *value, *l2);
1114                Ok(KclValue::Number {
1115                    value,
1116                    ty: Known(UnitType::Length(ty)),
1117                    meta: meta.clone(),
1118                })
1119            }
1120            (Known(UnitType::Angle(a1)), Known(UnitType::Angle(a2))) => {
1121                let (value, ty) = adjust_angle(*a1, *value, *a2);
1122                Ok(KclValue::Number {
1123                    value,
1124                    ty: Known(UnitType::Angle(ty)),
1125                    meta: meta.clone(),
1126                })
1127            }
1128
1129            // Known but incompatible.
1130            (Known(_), Known(_)) => Err(val.into()),
1131
1132            // Known and unknown => we assume the rhs, possibly with adjustment
1133            (Default { .. }, Known(UnitType::Count)) => Ok(KclValue::Number {
1134                value: *value,
1135                ty: Known(UnitType::Count),
1136                meta: meta.clone(),
1137            }),
1138
1139            (Default { len: l1, .. }, Known(UnitType::Length(l2))) => {
1140                let (value, ty) = adjust_length(*l1, *value, *l2);
1141                Ok(KclValue::Number {
1142                    value,
1143                    ty: Known(UnitType::Length(ty)),
1144                    meta: meta.clone(),
1145                })
1146            }
1147
1148            (Default { angle: a1, .. }, Known(UnitType::Angle(a2))) => {
1149                let (value, ty) = adjust_angle(*a1, *value, *a2);
1150                Ok(KclValue::Number {
1151                    value,
1152                    ty: Known(UnitType::Angle(ty)),
1153                    meta: meta.clone(),
1154                })
1155            }
1156
1157            (_, _) => unreachable!(),
1158        }
1159    }
1160
1161    fn as_length(&self) -> Option<UnitLength> {
1162        match self {
1163            Self::Known(UnitType::Length(len)) | Self::Default { len, .. } => Some(*len),
1164            _ => None,
1165        }
1166    }
1167}
1168
1169impl From<NumericType> for RuntimeType {
1170    fn from(t: NumericType) -> RuntimeType {
1171        RuntimeType::Primitive(PrimitiveType::Number(t))
1172    }
1173}
1174
1175impl From<UnitLength> for NumericSuffix {
1176    fn from(value: UnitLength) -> Self {
1177        match value {
1178            UnitLength::Millimeters => NumericSuffix::Mm,
1179            UnitLength::Centimeters => NumericSuffix::Cm,
1180            UnitLength::Meters => NumericSuffix::M,
1181            UnitLength::Inches => NumericSuffix::Inch,
1182            UnitLength::Feet => NumericSuffix::Ft,
1183            UnitLength::Yards => NumericSuffix::Yd,
1184        }
1185    }
1186}
1187
1188#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, ts_rs::TS)]
1189pub struct NumericSuffixTypeConvertError;
1190
1191impl TryFrom<NumericType> for NumericSuffix {
1192    type Error = NumericSuffixTypeConvertError;
1193
1194    fn try_from(value: NumericType) -> Result<Self, Self::Error> {
1195        match value {
1196            NumericType::Known(UnitType::Count) => Ok(NumericSuffix::Count),
1197            NumericType::Known(UnitType::Length(unit_length)) => Ok(NumericSuffix::from(unit_length)),
1198            NumericType::Known(UnitType::GenericLength) => Ok(NumericSuffix::Length),
1199            NumericType::Known(UnitType::Angle(UnitAngle::Degrees)) => Ok(NumericSuffix::Deg),
1200            NumericType::Known(UnitType::Angle(UnitAngle::Radians)) => Ok(NumericSuffix::Rad),
1201            NumericType::Known(UnitType::GenericAngle) => Ok(NumericSuffix::Angle),
1202            NumericType::Default { .. } => Ok(NumericSuffix::None),
1203            NumericType::Unknown => Ok(NumericSuffix::Unknown),
1204            NumericType::Any => Err(NumericSuffixTypeConvertError),
1205        }
1206    }
1207}
1208
1209pub fn adjust_length(from: UnitLength, value: f64, to: UnitLength) -> (f64, UnitLength) {
1210    use UnitLength::*;
1211
1212    if from == to {
1213        return (value, to);
1214    }
1215
1216    let (base, base_unit) = match from {
1217        Millimeters => (value, Millimeters),
1218        Centimeters => (value * 10.0, Millimeters),
1219        Meters => (value * 1000.0, Millimeters),
1220        Inches => (value, Inches),
1221        Feet => (value * 12.0, Inches),
1222        Yards => (value * 36.0, Inches),
1223    };
1224    let (base, base_unit) = match (base_unit, to) {
1225        (Millimeters, Inches) | (Millimeters, Feet) | (Millimeters, Yards) => (base / 25.4, Inches),
1226        (Inches, Millimeters) | (Inches, Centimeters) | (Inches, Meters) => (base * 25.4, Millimeters),
1227        _ => (base, base_unit),
1228    };
1229
1230    let value = match (base_unit, to) {
1231        (Millimeters, Millimeters) => base,
1232        (Millimeters, Centimeters) => base / 10.0,
1233        (Millimeters, Meters) => base / 1000.0,
1234        (Inches, Inches) => base,
1235        (Inches, Feet) => base / 12.0,
1236        (Inches, Yards) => base / 36.0,
1237        _ => unreachable!(),
1238    };
1239
1240    (value, to)
1241}
1242
1243pub fn adjust_angle(from: UnitAngle, value: f64, to: UnitAngle) -> (f64, UnitAngle) {
1244    use std::f64::consts::PI;
1245
1246    use UnitAngle::*;
1247
1248    let value = match (from, to) {
1249        (Degrees, Degrees) => value,
1250        (Degrees, Radians) => (value / 180.0) * PI,
1251        (Radians, Degrees) => 180.0 * value / PI,
1252        (Radians, Radians) => value,
1253    };
1254
1255    (value, to)
1256}
1257
1258pub(super) fn length_from_str(s: &str, source_range: SourceRange) -> Result<UnitLength, KclError> {
1259    // We don't use `from_str` here because we want to be more flexible about the input we accept.
1260    match s {
1261        "mm" => Ok(UnitLength::Millimeters),
1262        "cm" => Ok(UnitLength::Centimeters),
1263        "m" => Ok(UnitLength::Meters),
1264        "inch" | "in" => Ok(UnitLength::Inches),
1265        "ft" => Ok(UnitLength::Feet),
1266        "yd" => Ok(UnitLength::Yards),
1267        value => Err(KclError::new_semantic(KclErrorDetails::new(
1268            format!("Unexpected value for length units: `{value}`; expected one of `mm`, `cm`, `m`, `in`, `ft`, `yd`"),
1269            vec![source_range],
1270        ))),
1271    }
1272}
1273
1274pub(super) fn angle_from_str(s: &str, source_range: SourceRange) -> Result<UnitAngle, KclError> {
1275    UnitAngle::from_str(s).map_err(|_| {
1276        KclError::new_semantic(KclErrorDetails::new(
1277            format!("Unexpected value for angle units: `{s}`; expected one of `deg`, `rad`"),
1278            vec![source_range],
1279        ))
1280    })
1281}
1282
1283/// Which value-changing conversions a coercion is allowed to perform. Separate
1284/// from the question of which types it accepts, which never varies.
1285///
1286/// The two constructors are the only two modes the language has: a value
1287/// crossing a boundary the user did not write, and a type the user wrote down.
1288#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1289pub struct CoercionMode {
1290    convert_units: bool,
1291    project_enums: bool,
1292}
1293
1294impl CoercionMode {
1295    /// A boundary the user did not write: an argument, a return, or a
1296    /// Rust-implemented function reading its arguments. Numbers convert to the
1297    /// target's units. Enums do not project, because an implicit projection
1298    /// would defeat nominal checking exactly where it matters.
1299    pub fn implicit() -> Self {
1300        CoercionMode {
1301            convert_units: true,
1302            project_enums: false,
1303        }
1304    }
1305
1306    /// A type the user wrote down, as in `expr: Type`. Numbers are reinterpreted
1307    /// as having the target's units rather than converted, and an enum projects
1308    /// to its declared representation.
1309    pub fn explicit() -> Self {
1310        CoercionMode {
1311            convert_units: false,
1312            project_enums: true,
1313        }
1314    }
1315
1316    pub(crate) fn convert_units(self) -> bool {
1317        self.convert_units
1318    }
1319
1320    pub(crate) fn project_enums(self) -> bool {
1321        self.project_enums
1322    }
1323
1324    /// The same mode with projection off, so that a union can look for an exact
1325    /// match before it considers projecting.
1326    pub(crate) fn without_projection(self) -> Self {
1327        CoercionMode {
1328            project_enums: false,
1329            ..self
1330        }
1331    }
1332}
1333
1334#[derive(Debug, Clone)]
1335pub struct CoercionError {
1336    pub found: Option<RuntimeType>,
1337    pub explicit_coercion: Option<String>,
1338    /// Set when the generic "could not coerce" wording would describe the wrong
1339    /// problem, and the caller should report this instead.
1340    pub message: Option<String>,
1341}
1342
1343impl CoercionError {
1344    fn with_explicit(mut self, c: String) -> Self {
1345        self.explicit_coercion = Some(c);
1346        self
1347    }
1348
1349    fn with_message(mut self, message: String) -> Self {
1350        self.message = Some(message);
1351        self
1352    }
1353}
1354
1355impl From<&'_ KclValue> for CoercionError {
1356    fn from(value: &'_ KclValue) -> Self {
1357        CoercionError {
1358            found: value.principal_type(),
1359            explicit_coercion: None,
1360            message: None,
1361        }
1362    }
1363}
1364
1365impl KclValue {
1366    /// True if `self` has a type which is a subtype of `ty` without coercion.
1367    pub fn has_type(&self, ty: &RuntimeType) -> bool {
1368        let Some(self_ty) = self.principal_type() else {
1369            return false;
1370        };
1371
1372        self_ty.subtype(ty)
1373    }
1374
1375    /// Coerce `self` to a new value which has `ty` as its closest supertype.
1376    ///
1377    /// If the result is Ok, then:
1378    ///   - result.principal_type().unwrap().subtype(ty)
1379    ///
1380    /// If self.principal_type() == ty then result == self
1381    pub fn coerce(
1382        &self,
1383        ty: &RuntimeType,
1384        mode: CoercionMode,
1385        exec_state: &mut ExecState,
1386    ) -> Result<KclValue, CoercionError> {
1387        match self {
1388            KclValue::Tuple { value, .. }
1389                if value.len() == 1
1390                    && !matches!(ty, RuntimeType::Primitive(PrimitiveType::Any) | RuntimeType::Tuple(..)) =>
1391            {
1392                if let Ok(coerced) = value[0].coerce(ty, mode, exec_state) {
1393                    return Ok(coerced);
1394                }
1395            }
1396            KclValue::HomArray { value, .. }
1397                if value.len() == 1
1398                    && !matches!(ty, RuntimeType::Primitive(PrimitiveType::Any) | RuntimeType::Array(..)) =>
1399            {
1400                if let Ok(coerced) = value[0].coerce(ty, mode, exec_state) {
1401                    return Ok(coerced);
1402                }
1403            }
1404            _ => {}
1405        }
1406
1407        match ty {
1408            RuntimeType::Primitive(ty) => self.coerce_to_primitive_type(ty, mode, exec_state),
1409            RuntimeType::Array(ty, len) => self.coerce_to_array_type(ty, mode, *len, exec_state, false),
1410            RuntimeType::Tuple(tys) => self.coerce_to_tuple_type(tys, mode, exec_state),
1411            RuntimeType::Union(tys) => self.coerce_to_union_type(tys, mode, exec_state),
1412            RuntimeType::Object(tys, constrainable) => {
1413                self.coerce_to_object_type(tys, *constrainable, mode, exec_state)
1414            }
1415            RuntimeType::Enum(id) => self.coerce_to_enum_type(id),
1416        }
1417    }
1418
1419    /// Enums are nominal, so the only value that coerces to an enum type is a
1420    /// value of that same enum, and it is returned unchanged. Projection out of
1421    /// an enum (`Color::Red: string`) is explicit ascription, not coercion, and
1422    /// is handled on its own path.
1423    fn coerce_to_enum_type(&self, id: &EnumTypeId) -> Result<KclValue, CoercionError> {
1424        match self {
1425            KclValue::Enum { value } if value.enum_id() == id => Ok(self.clone()),
1426            _ => Err(self.into()),
1427        }
1428    }
1429
1430    fn coerce_to_primitive_type(
1431        &self,
1432        ty: &PrimitiveType,
1433        mode: CoercionMode,
1434        exec_state: &mut ExecState,
1435    ) -> Result<KclValue, CoercionError> {
1436        match ty {
1437            PrimitiveType::Any => Ok(self.clone()),
1438            PrimitiveType::Never => Err(self.into()),
1439            PrimitiveType::None => match self {
1440                KclValue::KclNone { .. } => Ok(self.clone()),
1441                _ => Err(self.into()),
1442            },
1443            PrimitiveType::Number(ty) => {
1444                // `Color::Red: number(_)` is a projection the user asked for and
1445                // V1 cannot perform. Reporting the numeric "expected a number"
1446                // here would describe the wrong problem: the value is a working
1447                // enum, not a broken number.
1448                if let KclValue::Enum { value } = self
1449                    && mode.project_enums()
1450                {
1451                    return Err(CoercionError::from(self).with_message(format!(
1452                        "Cannot project enum `{}` to a number. An enum projects to `string`; projecting to a number is not supported yet.",
1453                        value.enum_id().declared_name()
1454                    )));
1455                }
1456
1457                if mode.convert_units() {
1458                    return ty.coerce(self);
1459                }
1460
1461                // Instead of converting units, reinterpret the number as having
1462                // different units.
1463                //
1464                // If the user is explicitly specifying units, treat the value
1465                // as having had its units erased, rather than forcing the user
1466                // to explicitly erase them.
1467                if let KclValue::Number { value: n, meta, .. } = &self
1468                    && ty.is_fully_specified()
1469                {
1470                    let value = KclValue::Number {
1471                        ty: NumericType::Any,
1472                        value: *n,
1473                        meta: meta.clone(),
1474                    };
1475                    return ty.coerce(&value);
1476                }
1477                ty.coerce(self)
1478            }
1479            PrimitiveType::String => match self {
1480                KclValue::String { .. } => Ok(self.clone()),
1481                // The one projection V1 performs, and only where the user wrote
1482                // the type: see `CoercionMode`.
1483                KclValue::Enum { value } if mode.project_enums() => Ok(KclValue::String {
1484                    value: value.declared_string_repr(),
1485                    meta: value.meta().to_vec(),
1486                }),
1487                _ => Err(self.into()),
1488            },
1489            PrimitiveType::Boolean => match self {
1490                KclValue::Bool { .. } => Ok(self.clone()),
1491                _ => Err(self.into()),
1492            },
1493            PrimitiveType::GdtAnnotation => match self {
1494                KclValue::GdtAnnotation { .. } => Ok(self.clone()),
1495                _ => Err(self.into()),
1496            },
1497            PrimitiveType::Segment => match self {
1498                KclValue::Segment { .. } => Ok(self.clone()),
1499                _ => Err(self.into()),
1500            },
1501            PrimitiveType::Sketch => match self {
1502                KclValue::Sketch { .. } => Ok(self.clone()),
1503                KclValue::Object { value, .. } => {
1504                    let Some(meta) = value.get(SKETCH_OBJECT_META) else {
1505                        return Err(self.into());
1506                    };
1507                    let KclValue::Object { value: meta_map, .. } = meta else {
1508                        return Err(self.into());
1509                    };
1510                    let Some(sketch) = meta_map.get(SKETCH_OBJECT_META_SKETCH).and_then(KclValue::as_sketch) else {
1511                        return Err(self.into());
1512                    };
1513
1514                    Ok(KclValue::Sketch {
1515                        value: Box::new(sketch.clone()),
1516                    })
1517                }
1518                _ => Err(self.into()),
1519            },
1520            PrimitiveType::Constraint => match self {
1521                KclValue::SketchConstraint { .. } => Ok(self.clone()),
1522                _ => Err(self.into()),
1523            },
1524            PrimitiveType::Solid => match self {
1525                KclValue::Solid { .. } => Ok(self.clone()),
1526                _ => Err(self.into()),
1527            },
1528            PrimitiveType::Plane => {
1529                match self {
1530                    KclValue::String { value: s, .. }
1531                        if [
1532                            "xy", "xz", "yz", "-xy", "-xz", "-yz", "XY", "XZ", "YZ", "-XY", "-XZ", "-YZ",
1533                        ]
1534                        .contains(&&**s) =>
1535                    {
1536                        Ok(self.clone())
1537                    }
1538                    KclValue::Plane { .. } => Ok(self.clone()),
1539                    KclValue::Object { value, meta, .. } => {
1540                        let origin = value
1541                            .get("origin")
1542                            .and_then(Point3d::from_kcl_val)
1543                            .ok_or(CoercionError::from(self))?;
1544                        let x_axis = value
1545                            .get("xAxis")
1546                            .and_then(Point3d::from_kcl_val)
1547                            .ok_or(CoercionError::from(self))?;
1548                        let y_axis = value
1549                            .get("yAxis")
1550                            .and_then(Point3d::from_kcl_val)
1551                            .ok_or(CoercionError::from(self))?;
1552                        let z_axis = x_axis.axes_cross_product(&y_axis);
1553
1554                        if value.get("zAxis").is_some() {
1555                            exec_state.warn(CompilationIssue::err(
1556                            self.into(),
1557                            "Object with a zAxis field is being coerced into a plane, but the zAxis is ignored.",
1558                        ), annotations::WARN_IGNORED_Z_AXIS);
1559                        }
1560
1561                        let id = exec_state.mod_local.id_generator.next_uuid();
1562                        let info = PlaneInfo {
1563                            origin,
1564                            x_axis: x_axis.normalize(),
1565                            y_axis: y_axis.normalize(),
1566                            z_axis: z_axis.normalize(),
1567                        };
1568                        let plane = Plane {
1569                            id,
1570                            artifact_id: id.into(),
1571                            object_id: None,
1572                            kind: PlaneKind::from(&info),
1573                            info,
1574                            meta: meta.clone(),
1575                        };
1576
1577                        Ok(KclValue::Plane { value: Box::new(plane) })
1578                    }
1579                    _ => Err(self.into()),
1580                }
1581            }
1582            PrimitiveType::Face => match self {
1583                KclValue::Face { .. } => Ok(self.clone()),
1584                _ => Err(self.into()),
1585            },
1586            PrimitiveType::Helix => match self {
1587                KclValue::Helix { .. } => Ok(self.clone()),
1588                _ => Err(self.into()),
1589            },
1590            PrimitiveType::Edge => match self {
1591                KclValue::Uuid { .. } => Ok(self.clone()),
1592                KclValue::TagIdentifier { .. } => Ok(self.clone()),
1593                _ => Err(self.into()),
1594            },
1595            PrimitiveType::BoundedEdge => match self {
1596                KclValue::BoundedEdge { .. } => Ok(self.clone()),
1597                _ => Err(self.into()),
1598            },
1599            PrimitiveType::TaggedEdge => match self {
1600                KclValue::TagIdentifier { .. } => Ok(self.clone()),
1601                _ => Err(self.into()),
1602            },
1603            PrimitiveType::TaggedFace => match self {
1604                KclValue::TagIdentifier { .. } => Ok(self.clone()),
1605                s @ KclValue::String { value, .. } if ["start", "end", "START", "END"].contains(&&**value) => {
1606                    Ok(s.clone())
1607                }
1608                _ => Err(self.into()),
1609            },
1610            PrimitiveType::Axis2d => match self {
1611                KclValue::Object {
1612                    value: values, meta, ..
1613                } => {
1614                    if values
1615                        .get("origin")
1616                        .ok_or(CoercionError::from(self))?
1617                        .has_type(&RuntimeType::point2d())
1618                        && values
1619                            .get("direction")
1620                            .ok_or(CoercionError::from(self))?
1621                            .has_type(&RuntimeType::point2d())
1622                    {
1623                        return Ok(self.clone());
1624                    }
1625
1626                    let origin = values.get("origin").ok_or(self.into()).and_then(|p| {
1627                        p.coerce_to_array_type(&RuntimeType::length(), mode, ArrayLen::Known(2), exec_state, true)
1628                    })?;
1629                    let direction = values.get("direction").ok_or(self.into()).and_then(|p| {
1630                        p.coerce_to_array_type(&RuntimeType::length(), mode, ArrayLen::Known(2), exec_state, true)
1631                    })?;
1632
1633                    Ok(KclValue::Object {
1634                        value: [("origin".to_owned(), origin), ("direction".to_owned(), direction)].into(),
1635                        meta: meta.clone(),
1636                        constrainable: false,
1637                        object_kind: Default::default(),
1638                    })
1639                }
1640                _ => Err(self.into()),
1641            },
1642            PrimitiveType::Axis3d => match self {
1643                KclValue::Object {
1644                    value: values, meta, ..
1645                } => {
1646                    if values
1647                        .get("origin")
1648                        .ok_or(CoercionError::from(self))?
1649                        .has_type(&RuntimeType::point3d())
1650                        && values
1651                            .get("direction")
1652                            .ok_or(CoercionError::from(self))?
1653                            .has_type(&RuntimeType::point3d())
1654                    {
1655                        return Ok(self.clone());
1656                    }
1657
1658                    let origin = values.get("origin").ok_or(self.into()).and_then(|p| {
1659                        p.coerce_to_array_type(&RuntimeType::length(), mode, ArrayLen::Known(3), exec_state, true)
1660                    })?;
1661                    let direction = values.get("direction").ok_or(self.into()).and_then(|p| {
1662                        p.coerce_to_array_type(&RuntimeType::length(), mode, ArrayLen::Known(3), exec_state, true)
1663                    })?;
1664
1665                    Ok(KclValue::Object {
1666                        value: [("origin".to_owned(), origin), ("direction".to_owned(), direction)].into(),
1667                        meta: meta.clone(),
1668                        constrainable: false,
1669                        object_kind: Default::default(),
1670                    })
1671                }
1672                _ => Err(self.into()),
1673            },
1674            PrimitiveType::ImportedGeometry => match self {
1675                KclValue::ImportedGeometry { .. } => Ok(self.clone()),
1676                _ => Err(self.into()),
1677            },
1678            PrimitiveType::Function => match self {
1679                KclValue::Function { .. } => Ok(self.clone()),
1680                _ => Err(self.into()),
1681            },
1682            PrimitiveType::TagDecl => match self {
1683                KclValue::TagDeclarator { .. } => Ok(self.clone()),
1684                _ => Err(self.into()),
1685            },
1686        }
1687    }
1688
1689    fn coerce_to_array_type(
1690        &self,
1691        ty: &RuntimeType,
1692        mode: CoercionMode,
1693        len: ArrayLen,
1694        exec_state: &mut ExecState,
1695        allow_shrink: bool,
1696    ) -> Result<KclValue, CoercionError> {
1697        match self {
1698            KclValue::HomArray { value, ty: aty, .. } => {
1699                let satisfied_len = len.satisfied(value.len(), allow_shrink);
1700
1701                if aty.subtype(ty) {
1702                    // If the element type is a subtype of the target type and
1703                    // the length constraint is satisfied, we can just return
1704                    // the values unchanged, only adjusting the length. The new
1705                    // array element type should preserve its type because the
1706                    // target type oftentimes includes an unknown type as a way
1707                    // to say that the caller doesn't care.
1708                    return satisfied_len
1709                        .map(|len| KclValue::HomArray {
1710                            value: value[..len].to_vec(),
1711                            ty: aty.clone(),
1712                        })
1713                        .ok_or(self.into());
1714                }
1715
1716                // Ignore the array type, and coerce the elements of the array.
1717                if let Some(satisfied_len) = satisfied_len {
1718                    let value_result = value
1719                        .iter()
1720                        .take(satisfied_len)
1721                        .map(|v| v.coerce(ty, mode, exec_state))
1722                        .collect::<Result<Vec<_>, _>>();
1723
1724                    if let Ok(value) = value_result {
1725                        // We were able to coerce all the elements.
1726                        return Ok(KclValue::HomArray { value, ty: ty.clone() });
1727                    }
1728                }
1729
1730                // As a last resort, try to flatten the array.
1731                let mut values = Vec::new();
1732                for item in value {
1733                    if let KclValue::HomArray { value: inner_value, .. } = item {
1734                        // Flatten elements.
1735                        for item in inner_value {
1736                            values.push(item.coerce(ty, mode, exec_state)?);
1737                        }
1738                    } else {
1739                        values.push(item.coerce(ty, mode, exec_state)?);
1740                    }
1741                }
1742
1743                let len = len
1744                    .satisfied(values.len(), allow_shrink)
1745                    .ok_or(CoercionError::from(self))?;
1746
1747                if len > values.len() {
1748                    let message = format!(
1749                        "Internal: Expected coerced array length {len} to be less than or equal to original length {}",
1750                        values.len()
1751                    );
1752                    exec_state.err(CompilationIssue::err(self.into(), message.clone()));
1753                    #[cfg(debug_assertions)]
1754                    panic!("{message}");
1755                }
1756                values.truncate(len);
1757
1758                Ok(KclValue::HomArray {
1759                    value: values,
1760                    ty: ty.clone(),
1761                })
1762            }
1763            KclValue::Tuple { value, .. } => {
1764                let len = len
1765                    .satisfied(value.len(), allow_shrink)
1766                    .ok_or(CoercionError::from(self))?;
1767                let value = value
1768                    .iter()
1769                    .map(|item| item.coerce(ty, mode, exec_state))
1770                    .take(len)
1771                    .collect::<Result<Vec<_>, _>>()?;
1772
1773                Ok(KclValue::HomArray { value, ty: ty.clone() })
1774            }
1775            KclValue::KclNone { .. } if len.satisfied(0, false).is_some() => Ok(KclValue::HomArray {
1776                value: Vec::new(),
1777                ty: ty.clone(),
1778            }),
1779            _ if len.satisfied(1, false).is_some() => self.coerce(ty, mode, exec_state),
1780            _ => Err(self.into()),
1781        }
1782    }
1783
1784    fn coerce_to_tuple_type(
1785        &self,
1786        tys: &[RuntimeType],
1787        mode: CoercionMode,
1788        exec_state: &mut ExecState,
1789    ) -> Result<KclValue, CoercionError> {
1790        match self {
1791            KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } if value.len() == tys.len() => {
1792                let mut result = Vec::new();
1793                for (i, t) in tys.iter().enumerate() {
1794                    result.push(value[i].coerce(t, mode, exec_state)?);
1795                }
1796
1797                Ok(KclValue::Tuple {
1798                    value: result,
1799                    meta: Vec::new(),
1800                })
1801            }
1802            KclValue::KclNone { meta, .. } if tys.is_empty() => Ok(KclValue::Tuple {
1803                value: Vec::new(),
1804                meta: meta.clone(),
1805            }),
1806            _ if tys.len() == 1 => self.coerce(&tys[0], mode, exec_state),
1807            _ => Err(self.into()),
1808        }
1809    }
1810
1811    fn coerce_to_union_type(
1812        &self,
1813        tys: &[RuntimeType],
1814        mode: CoercionMode,
1815        exec_state: &mut ExecState,
1816    ) -> Result<KclValue, CoercionError> {
1817        // A member that accepts the value as it is must win over one that would
1818        // change it, whichever order the union was written in. Without this pass
1819        // `Color::Red: Color | string` would keep the enum while
1820        // `Color::Red: string | Color` would project it, making the meaning of a
1821        // union depend on how the author happened to spell it.
1822        if mode.project_enums() {
1823            let exact = mode.without_projection();
1824            for t in tys {
1825                if let Ok(v) = self.coerce(t, exact, exec_state) {
1826                    return Ok(v);
1827                }
1828            }
1829        }
1830
1831        for t in tys {
1832            if let Ok(v) = self.coerce(t, mode, exec_state) {
1833                return Ok(v);
1834            }
1835        }
1836
1837        Err(self.into())
1838    }
1839
1840    fn coerce_to_object_type(
1841        &self,
1842        tys: &[(String, RuntimeType)],
1843        constrainable: bool,
1844        _mode: CoercionMode,
1845        _exec_state: &mut ExecState,
1846    ) -> Result<KclValue, CoercionError> {
1847        match self {
1848            KclValue::Object { value, meta, .. } => {
1849                for (s, t) in tys {
1850                    // TODO coerce fields
1851                    if !value.get(s).ok_or(CoercionError::from(self))?.has_type(t) {
1852                        return Err(self.into());
1853                    }
1854                }
1855                // TODO remove non-required fields
1856                Ok(KclValue::Object {
1857                    value: value.clone(),
1858                    meta: meta.clone(),
1859                    // Note that we don't check for constrainability, coercing to a constrainable object
1860                    // adds that property.
1861                    constrainable,
1862                    object_kind: Default::default(),
1863                })
1864            }
1865            KclValue::KclNone { meta, .. } if tys.is_empty() => Ok(KclValue::Object {
1866                value: HashMap::new(),
1867                meta: meta.clone(),
1868                constrainable,
1869                object_kind: Default::default(),
1870            }),
1871            _ => Err(self.into()),
1872        }
1873    }
1874
1875    pub fn principal_type(&self) -> Option<RuntimeType> {
1876        match self {
1877            KclValue::Bool { .. } => Some(RuntimeType::Primitive(PrimitiveType::Boolean)),
1878            KclValue::Number { ty, .. } => Some(RuntimeType::Primitive(PrimitiveType::Number(*ty))),
1879            KclValue::String { .. } => Some(RuntimeType::Primitive(PrimitiveType::String)),
1880            KclValue::Enum { value } => Some(RuntimeType::Enum(value.enum_id().clone())),
1881            KclValue::SketchVar { value, .. } => Some(RuntimeType::Primitive(PrimitiveType::Number(value.ty))),
1882            KclValue::SketchConstraint { .. } => Some(RuntimeType::Primitive(PrimitiveType::Constraint)),
1883            KclValue::Object {
1884                value, constrainable, ..
1885            } => {
1886                let properties = value
1887                    .iter()
1888                    .map(|(k, v)| v.principal_type().map(|t| (k.clone(), t)))
1889                    .collect::<Option<Vec<_>>>()?;
1890                Some(RuntimeType::Object(properties, *constrainable))
1891            }
1892            KclValue::GdtAnnotation { .. } => Some(RuntimeType::Primitive(PrimitiveType::GdtAnnotation)),
1893            KclValue::Plane { .. } => Some(RuntimeType::Primitive(PrimitiveType::Plane)),
1894            KclValue::Sketch { .. } => Some(RuntimeType::Primitive(PrimitiveType::Sketch)),
1895            KclValue::Solid { .. } => Some(RuntimeType::Primitive(PrimitiveType::Solid)),
1896            KclValue::Face { .. } => Some(RuntimeType::Primitive(PrimitiveType::Face)),
1897            KclValue::Segment { .. } => Some(RuntimeType::Primitive(PrimitiveType::Segment)),
1898            KclValue::Helix { .. } => Some(RuntimeType::Primitive(PrimitiveType::Helix)),
1899            KclValue::ImportedGeometry(..) => Some(RuntimeType::Primitive(PrimitiveType::ImportedGeometry)),
1900            KclValue::Tuple { value, .. } => Some(RuntimeType::Tuple(
1901                value.iter().map(|v| v.principal_type()).collect::<Option<Vec<_>>>()?,
1902            )),
1903            KclValue::HomArray { ty, value, .. } => {
1904                Some(RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Known(value.len())))
1905            }
1906            KclValue::TagIdentifier(_) => Some(RuntimeType::Primitive(PrimitiveType::TaggedEdge)),
1907            KclValue::TagDeclarator(_) => Some(RuntimeType::Primitive(PrimitiveType::TagDecl)),
1908            KclValue::Uuid { .. } => Some(RuntimeType::Primitive(PrimitiveType::Edge)),
1909            KclValue::Function { .. } => Some(RuntimeType::Primitive(PrimitiveType::Function)),
1910            KclValue::KclNone { .. } => Some(RuntimeType::Primitive(PrimitiveType::None)),
1911            KclValue::Module { .. } | KclValue::Type { .. } => None,
1912            KclValue::BoundedEdge { .. } => Some(RuntimeType::Primitive(PrimitiveType::BoundedEdge)),
1913        }
1914    }
1915
1916    pub fn principal_type_string(&self) -> String {
1917        if let Some(ty) = self.principal_type() {
1918            return format!("`{ty}`");
1919        }
1920
1921        match self {
1922            KclValue::Module { .. } => "module",
1923            KclValue::KclNone { .. } => "none",
1924            KclValue::Type { .. } => "type",
1925            _ => {
1926                debug_assert!(false);
1927                "<unexpected type>"
1928            }
1929        }
1930        .to_owned()
1931    }
1932}
1933
1934#[cfg(test)]
1935mod test {
1936    use std::sync::Arc;
1937
1938    use super::*;
1939    use crate::ModuleId;
1940    use crate::execution::ExecTestResults;
1941    use crate::execution::kcl_value::EnumTypeDef;
1942    use crate::execution::kcl_value::EnumValue;
1943    use crate::execution::parse_execute;
1944
1945    async fn new_exec_state() -> (crate::ExecutorContext, ExecState) {
1946        let ctx = crate::ExecutorContext::new_mock(None).await;
1947        let exec_state = ExecState::new(&ctx);
1948        (ctx, exec_state)
1949    }
1950
1951    fn values(exec_state: &mut ExecState) -> Vec<KclValue> {
1952        vec![
1953            KclValue::Bool {
1954                value: true,
1955                meta: Vec::new(),
1956            },
1957            KclValue::Number {
1958                value: 1.0,
1959                ty: NumericType::count(),
1960                meta: Vec::new(),
1961            },
1962            KclValue::String {
1963                value: "hello".to_owned(),
1964                meta: Vec::new(),
1965            },
1966            KclValue::Tuple {
1967                value: Vec::new(),
1968                meta: Vec::new(),
1969            },
1970            KclValue::HomArray {
1971                value: Vec::new(),
1972                ty: RuntimeType::solid(),
1973            },
1974            KclValue::Object {
1975                value: crate::execution::KclObjectFields::new(),
1976                meta: Vec::new(),
1977                constrainable: false,
1978                object_kind: Default::default(),
1979            },
1980            KclValue::TagIdentifier(Box::new("foo".parse().unwrap())),
1981            KclValue::TagDeclarator(Box::new(crate::parsing::ast::types::TagDeclarator::new("foo"))),
1982            KclValue::Plane {
1983                value: Box::new(
1984                    Plane::from_plane_data_skipping_engine(crate::std::sketch::PlaneData::XY, exec_state).unwrap(),
1985                ),
1986            },
1987            // No easy way to make a Face, Sketch, Solid, or Helix
1988            KclValue::ImportedGeometry(crate::execution::ImportedGeometry::new(
1989                uuid::Uuid::nil(),
1990                Vec::new(),
1991                Vec::new(),
1992            )),
1993            // Other values don't have types
1994        ]
1995    }
1996
1997    #[track_caller]
1998    fn assert_coerce_results(
1999        value: &KclValue,
2000        super_type: &RuntimeType,
2001        expected_value: &KclValue,
2002        exec_state: &mut ExecState,
2003    ) {
2004        let is_subtype = value == expected_value;
2005        let actual = value.coerce(super_type, CoercionMode::implicit(), exec_state).unwrap();
2006        assert_eq!(&actual, expected_value);
2007        assert_eq!(
2008            is_subtype,
2009            value.principal_type().is_some() && value.principal_type().unwrap().subtype(super_type),
2010            "{:?} <: {super_type:?} should be {is_subtype}",
2011            value.principal_type().unwrap()
2012        );
2013        assert!(
2014            expected_value.principal_type().unwrap().subtype(super_type),
2015            "{} <: {super_type}",
2016            expected_value.principal_type().unwrap()
2017        )
2018    }
2019
2020    #[tokio::test(flavor = "multi_thread")]
2021    async fn coerce_idempotent() {
2022        let (ctx, mut exec_state) = new_exec_state().await;
2023        let values = values(&mut exec_state);
2024        for v in &values {
2025            // Identity subtype
2026            let ty = v.principal_type().unwrap();
2027            assert_coerce_results(v, &ty, v, &mut exec_state);
2028
2029            // Union subtype
2030            let uty1 = RuntimeType::Union(vec![ty.clone()]);
2031            let uty2 = RuntimeType::Union(vec![ty.clone(), RuntimeType::Primitive(PrimitiveType::Boolean)]);
2032            assert_coerce_results(v, &uty1, v, &mut exec_state);
2033            assert_coerce_results(v, &uty2, v, &mut exec_state);
2034
2035            // Array subtypes
2036            let aty = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::None);
2037            let aty1 = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Known(1));
2038            let aty0 = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Minimum(1));
2039
2040            match v {
2041                KclValue::HomArray { .. } => {
2042                    // These will not get wrapped if possible.
2043                    assert_coerce_results(
2044                        v,
2045                        &aty,
2046                        &KclValue::HomArray {
2047                            value: vec![],
2048                            ty: ty.clone(),
2049                        },
2050                        &mut exec_state,
2051                    );
2052                    // Coercing an empty array to an array of length 1
2053                    // should fail.
2054                    v.coerce(&aty1, CoercionMode::implicit(), &mut exec_state).unwrap_err();
2055                    // Coercing an empty array to an array that's
2056                    // non-empty should fail.
2057                    v.coerce(&aty0, CoercionMode::implicit(), &mut exec_state).unwrap_err();
2058                }
2059                KclValue::Tuple { .. } => {}
2060                _ => {
2061                    assert_coerce_results(v, &aty, v, &mut exec_state);
2062                    assert_coerce_results(v, &aty1, v, &mut exec_state);
2063                    assert_coerce_results(v, &aty0, v, &mut exec_state);
2064
2065                    // Tuple subtype
2066                    let tty = RuntimeType::Tuple(vec![ty.clone()]);
2067                    assert_coerce_results(v, &tty, v, &mut exec_state);
2068                }
2069            }
2070        }
2071
2072        for v in &values[1..] {
2073            // Not a subtype
2074            v.coerce(
2075                &RuntimeType::Primitive(PrimitiveType::Boolean),
2076                CoercionMode::implicit(),
2077                &mut exec_state,
2078            )
2079            .unwrap_err();
2080        }
2081        ctx.close().await;
2082    }
2083
2084    #[tokio::test(flavor = "multi_thread")]
2085    async fn coerce_none() {
2086        let (ctx, mut exec_state) = new_exec_state().await;
2087        let none = KclValue::KclNone {
2088            value: crate::parsing::ast::types::KclNone::new(),
2089            meta: Vec::new(),
2090        };
2091
2092        let aty = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::None);
2093        let aty0 = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::Known(0));
2094        let aty1 = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::Known(1));
2095        let aty1p = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::Minimum(1));
2096        assert_coerce_results(
2097            &none,
2098            &aty,
2099            &KclValue::HomArray {
2100                value: Vec::new(),
2101                ty: RuntimeType::solid(),
2102            },
2103            &mut exec_state,
2104        );
2105        assert_coerce_results(
2106            &none,
2107            &aty0,
2108            &KclValue::HomArray {
2109                value: Vec::new(),
2110                ty: RuntimeType::solid(),
2111            },
2112            &mut exec_state,
2113        );
2114        none.coerce(&aty1, CoercionMode::implicit(), &mut exec_state)
2115            .unwrap_err();
2116        none.coerce(&aty1p, CoercionMode::implicit(), &mut exec_state)
2117            .unwrap_err();
2118
2119        let tty = RuntimeType::Tuple(vec![]);
2120        let tty1 = RuntimeType::Tuple(vec![RuntimeType::solid()]);
2121        assert_coerce_results(
2122            &none,
2123            &tty,
2124            &KclValue::Tuple {
2125                value: Vec::new(),
2126                meta: Vec::new(),
2127            },
2128            &mut exec_state,
2129        );
2130        none.coerce(&tty1, CoercionMode::implicit(), &mut exec_state)
2131            .unwrap_err();
2132
2133        let oty = RuntimeType::Object(vec![], false);
2134        assert_coerce_results(
2135            &none,
2136            &oty,
2137            &KclValue::Object {
2138                value: HashMap::new(),
2139                meta: Vec::new(),
2140                constrainable: false,
2141                object_kind: Default::default(),
2142            },
2143            &mut exec_state,
2144        );
2145        ctx.close().await;
2146    }
2147
2148    #[tokio::test(flavor = "multi_thread")]
2149    async fn coerce_record() {
2150        let (ctx, mut exec_state) = new_exec_state().await;
2151
2152        let obj0 = KclValue::Object {
2153            value: HashMap::new(),
2154            meta: Vec::new(),
2155            constrainable: false,
2156            object_kind: Default::default(),
2157        };
2158        let obj1 = KclValue::Object {
2159            value: [(
2160                "foo".to_owned(),
2161                KclValue::Bool {
2162                    value: true,
2163                    meta: Vec::new(),
2164                },
2165            )]
2166            .into(),
2167            meta: Vec::new(),
2168            constrainable: false,
2169            object_kind: Default::default(),
2170        };
2171        let obj2 = KclValue::Object {
2172            value: [
2173                (
2174                    "foo".to_owned(),
2175                    KclValue::Bool {
2176                        value: true,
2177                        meta: Vec::new(),
2178                    },
2179                ),
2180                (
2181                    "bar".to_owned(),
2182                    KclValue::Number {
2183                        value: 0.0,
2184                        ty: NumericType::count(),
2185                        meta: Vec::new(),
2186                    },
2187                ),
2188                (
2189                    "baz".to_owned(),
2190                    KclValue::Number {
2191                        value: 42.0,
2192                        ty: NumericType::count(),
2193                        meta: Vec::new(),
2194                    },
2195                ),
2196            ]
2197            .into(),
2198            meta: Vec::new(),
2199            constrainable: false,
2200            object_kind: Default::default(),
2201        };
2202
2203        let ty0 = RuntimeType::Object(vec![], false);
2204        assert_coerce_results(&obj0, &ty0, &obj0, &mut exec_state);
2205        assert_coerce_results(&obj1, &ty0, &obj1, &mut exec_state);
2206        assert_coerce_results(&obj2, &ty0, &obj2, &mut exec_state);
2207
2208        let ty1 = RuntimeType::Object(
2209            vec![("foo".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean))],
2210            false,
2211        );
2212        obj0.coerce(&ty1, CoercionMode::implicit(), &mut exec_state)
2213            .unwrap_err();
2214        assert_coerce_results(&obj1, &ty1, &obj1, &mut exec_state);
2215        assert_coerce_results(&obj2, &ty1, &obj2, &mut exec_state);
2216
2217        // Different ordering, (TODO - test for covariance once implemented)
2218        let ty2 = RuntimeType::Object(
2219            vec![
2220                (
2221                    "bar".to_owned(),
2222                    RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2223                ),
2224                ("foo".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean)),
2225            ],
2226            false,
2227        );
2228        obj0.coerce(&ty2, CoercionMode::implicit(), &mut exec_state)
2229            .unwrap_err();
2230        obj1.coerce(&ty2, CoercionMode::implicit(), &mut exec_state)
2231            .unwrap_err();
2232        assert_coerce_results(&obj2, &ty2, &obj2, &mut exec_state);
2233
2234        // field not present
2235        let tyq = RuntimeType::Object(
2236            vec![("qux".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean))],
2237            false,
2238        );
2239        obj0.coerce(&tyq, CoercionMode::implicit(), &mut exec_state)
2240            .unwrap_err();
2241        obj1.coerce(&tyq, CoercionMode::implicit(), &mut exec_state)
2242            .unwrap_err();
2243        obj2.coerce(&tyq, CoercionMode::implicit(), &mut exec_state)
2244            .unwrap_err();
2245
2246        // field with different type
2247        let ty1 = RuntimeType::Object(
2248            vec![("bar".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean))],
2249            false,
2250        );
2251        obj2.coerce(&ty1, CoercionMode::implicit(), &mut exec_state)
2252            .unwrap_err();
2253        ctx.close().await;
2254    }
2255
2256    #[tokio::test(flavor = "multi_thread")]
2257    async fn coerce_array() {
2258        let (ctx, mut exec_state) = new_exec_state().await;
2259
2260        let hom_arr = KclValue::HomArray {
2261            value: vec![
2262                KclValue::Number {
2263                    value: 0.0,
2264                    ty: NumericType::count(),
2265                    meta: Vec::new(),
2266                },
2267                KclValue::Number {
2268                    value: 1.0,
2269                    ty: NumericType::count(),
2270                    meta: Vec::new(),
2271                },
2272                KclValue::Number {
2273                    value: 2.0,
2274                    ty: NumericType::count(),
2275                    meta: Vec::new(),
2276                },
2277                KclValue::Number {
2278                    value: 3.0,
2279                    ty: NumericType::count(),
2280                    meta: Vec::new(),
2281                },
2282            ],
2283            ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2284        };
2285        let mixed1 = KclValue::Tuple {
2286            value: vec![
2287                KclValue::Number {
2288                    value: 0.0,
2289                    ty: NumericType::count(),
2290                    meta: Vec::new(),
2291                },
2292                KclValue::Number {
2293                    value: 1.0,
2294                    ty: NumericType::count(),
2295                    meta: Vec::new(),
2296                },
2297            ],
2298            meta: Vec::new(),
2299        };
2300        let mixed2 = KclValue::Tuple {
2301            value: vec![
2302                KclValue::Number {
2303                    value: 0.0,
2304                    ty: NumericType::count(),
2305                    meta: Vec::new(),
2306                },
2307                KclValue::Bool {
2308                    value: true,
2309                    meta: Vec::new(),
2310                },
2311            ],
2312            meta: Vec::new(),
2313        };
2314
2315        // Principal types
2316        let tyh = RuntimeType::Array(
2317            Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2318            ArrayLen::Known(4),
2319        );
2320        let tym1 = RuntimeType::Tuple(vec![
2321            RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2322            RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2323        ]);
2324        let tym2 = RuntimeType::Tuple(vec![
2325            RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2326            RuntimeType::Primitive(PrimitiveType::Boolean),
2327        ]);
2328        assert_coerce_results(&hom_arr, &tyh, &hom_arr, &mut exec_state);
2329        assert_coerce_results(&mixed1, &tym1, &mixed1, &mut exec_state);
2330        assert_coerce_results(&mixed2, &tym2, &mixed2, &mut exec_state);
2331        mixed1
2332            .coerce(&tym2, CoercionMode::implicit(), &mut exec_state)
2333            .unwrap_err();
2334        mixed2
2335            .coerce(&tym1, CoercionMode::implicit(), &mut exec_state)
2336            .unwrap_err();
2337
2338        // Length subtyping
2339        let tyhn = RuntimeType::Array(
2340            Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2341            ArrayLen::None,
2342        );
2343        let tyh1 = RuntimeType::Array(
2344            Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2345            ArrayLen::Minimum(1),
2346        );
2347        let tyh3 = RuntimeType::Array(
2348            Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2349            ArrayLen::Known(3),
2350        );
2351        let tyhm3 = RuntimeType::Array(
2352            Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2353            ArrayLen::Minimum(3),
2354        );
2355        let tyhm5 = RuntimeType::Array(
2356            Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2357            ArrayLen::Minimum(5),
2358        );
2359        assert_coerce_results(&hom_arr, &tyhn, &hom_arr, &mut exec_state);
2360        assert_coerce_results(&hom_arr, &tyh1, &hom_arr, &mut exec_state);
2361        hom_arr
2362            .coerce(&tyh3, CoercionMode::implicit(), &mut exec_state)
2363            .unwrap_err();
2364        assert_coerce_results(&hom_arr, &tyhm3, &hom_arr, &mut exec_state);
2365        hom_arr
2366            .coerce(&tyhm5, CoercionMode::implicit(), &mut exec_state)
2367            .unwrap_err();
2368
2369        let hom_arr0 = KclValue::HomArray {
2370            value: vec![],
2371            ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2372        };
2373        assert_coerce_results(&hom_arr0, &tyhn, &hom_arr0, &mut exec_state);
2374        hom_arr0
2375            .coerce(&tyh1, CoercionMode::implicit(), &mut exec_state)
2376            .unwrap_err();
2377        hom_arr0
2378            .coerce(&tyh3, CoercionMode::implicit(), &mut exec_state)
2379            .unwrap_err();
2380
2381        // Covariance
2382        // let tyh = RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any))), ArrayLen::Known(4));
2383        let tym1 = RuntimeType::Tuple(vec![
2384            RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2385            RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2386        ]);
2387        let tym2 = RuntimeType::Tuple(vec![
2388            RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2389            RuntimeType::Primitive(PrimitiveType::Boolean),
2390        ]);
2391        // TODO implement covariance for homogeneous arrays
2392        // assert_coerce_results(&hom_arr, &tyh, &hom_arr, &mut exec_state);
2393        assert_coerce_results(&mixed1, &tym1, &mixed1, &mut exec_state);
2394        assert_coerce_results(&mixed2, &tym2, &mixed2, &mut exec_state);
2395
2396        // Mixed to homogeneous
2397        let hom_arr_2 = KclValue::HomArray {
2398            value: vec![
2399                KclValue::Number {
2400                    value: 0.0,
2401                    ty: NumericType::count(),
2402                    meta: Vec::new(),
2403                },
2404                KclValue::Number {
2405                    value: 1.0,
2406                    ty: NumericType::count(),
2407                    meta: Vec::new(),
2408                },
2409            ],
2410            ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2411        };
2412        let mixed0 = KclValue::Tuple {
2413            value: vec![],
2414            meta: Vec::new(),
2415        };
2416        assert_coerce_results(&mixed1, &tyhn, &hom_arr_2, &mut exec_state);
2417        assert_coerce_results(&mixed1, &tyh1, &hom_arr_2, &mut exec_state);
2418        assert_coerce_results(&mixed0, &tyhn, &hom_arr0, &mut exec_state);
2419        mixed0
2420            .coerce(&tyh, CoercionMode::implicit(), &mut exec_state)
2421            .unwrap_err();
2422        mixed0
2423            .coerce(&tyh1, CoercionMode::implicit(), &mut exec_state)
2424            .unwrap_err();
2425
2426        // Homogehous to mixed
2427        assert_coerce_results(&hom_arr_2, &tym1, &mixed1, &mut exec_state);
2428        hom_arr
2429            .coerce(&tym1, CoercionMode::implicit(), &mut exec_state)
2430            .unwrap_err();
2431        hom_arr_2
2432            .coerce(&tym2, CoercionMode::implicit(), &mut exec_state)
2433            .unwrap_err();
2434
2435        mixed0
2436            .coerce(&tym1, CoercionMode::implicit(), &mut exec_state)
2437            .unwrap_err();
2438        mixed0
2439            .coerce(&tym2, CoercionMode::implicit(), &mut exec_state)
2440            .unwrap_err();
2441        ctx.close().await;
2442    }
2443
2444    #[tokio::test(flavor = "multi_thread")]
2445    async fn coerce_union() {
2446        let (ctx, mut exec_state) = new_exec_state().await;
2447
2448        // Subtyping smaller unions
2449        assert!(RuntimeType::Union(vec![]).subtype(&RuntimeType::Union(vec![
2450            RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2451            RuntimeType::Primitive(PrimitiveType::Boolean)
2452        ])));
2453        assert!(
2454            RuntimeType::Union(vec![RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any))]).subtype(
2455                &RuntimeType::Union(vec![
2456                    RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2457                    RuntimeType::Primitive(PrimitiveType::Boolean)
2458                ])
2459            )
2460        );
2461        assert!(
2462            RuntimeType::Union(vec![
2463                RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2464                RuntimeType::Primitive(PrimitiveType::Boolean)
2465            ])
2466            .subtype(&RuntimeType::Union(vec![
2467                RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2468                RuntimeType::Primitive(PrimitiveType::Boolean)
2469            ]))
2470        );
2471
2472        // Covariance
2473        let count = KclValue::Number {
2474            value: 1.0,
2475            ty: NumericType::count(),
2476            meta: Vec::new(),
2477        };
2478
2479        let tya = RuntimeType::Union(vec![RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any))]);
2480        let tya2 = RuntimeType::Union(vec![
2481            RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2482            RuntimeType::Primitive(PrimitiveType::Boolean),
2483        ]);
2484        assert_coerce_results(&count, &tya, &count, &mut exec_state);
2485        assert_coerce_results(&count, &tya2, &count, &mut exec_state);
2486
2487        // No matching type
2488        let tyb = RuntimeType::Union(vec![RuntimeType::Primitive(PrimitiveType::Boolean)]);
2489        let tyb2 = RuntimeType::Union(vec![
2490            RuntimeType::Primitive(PrimitiveType::Boolean),
2491            RuntimeType::Primitive(PrimitiveType::String),
2492        ]);
2493        count
2494            .coerce(&tyb, CoercionMode::implicit(), &mut exec_state)
2495            .unwrap_err();
2496        count
2497            .coerce(&tyb2, CoercionMode::implicit(), &mut exec_state)
2498            .unwrap_err();
2499        ctx.close().await;
2500    }
2501
2502    #[test]
2503    fn union_subtyping_uses_member_subtyping() {
2504        let tagged_edge = RuntimeType::Primitive(PrimitiveType::TaggedEdge);
2505        let edge = RuntimeType::Primitive(PrimitiveType::Edge);
2506        let string = RuntimeType::string();
2507        let boolean = RuntimeType::bool();
2508
2509        let tagged_edge_or_string = RuntimeType::Union(vec![tagged_edge.clone(), string.clone()]);
2510        let edge_or_string = RuntimeType::Union(vec![edge.clone(), string.clone()]);
2511
2512        // TaggedEdge | string <: Edge | string
2513        assert!(tagged_edge_or_string.subtype(&edge_or_string));
2514        // Edge | string is not a subtype of TaggedEdge | string.
2515        assert!(!edge_or_string.subtype(&tagged_edge_or_string));
2516
2517        // TaggedEdge | Edge <: Edge
2518        assert!(RuntimeType::Union(vec![tagged_edge.clone(), edge.clone()]).subtype(&edge));
2519        // TaggedEdge | bool is not a subtype of Edge.
2520        assert!(!RuntimeType::Union(vec![tagged_edge, boolean]).subtype(&edge));
2521
2522        // The empty union is a subtype of string.
2523        assert!(RuntimeType::Union(vec![]).subtype(&string));
2524    }
2525
2526    #[test]
2527    fn nested_union_subtyping_is_associative_and_recursive() {
2528        let tagged_edge = RuntimeType::Primitive(PrimitiveType::TaggedEdge);
2529        let edge = RuntimeType::Primitive(PrimitiveType::Edge);
2530        let string = RuntimeType::string();
2531        let boolean = RuntimeType::bool();
2532
2533        let left_associative = RuntimeType::Union(vec![
2534            RuntimeType::Union(vec![string.clone(), boolean.clone()]),
2535            edge.clone(),
2536        ]);
2537        let right_associative =
2538            RuntimeType::Union(vec![string, RuntimeType::Union(vec![boolean.clone(), edge.clone()])]);
2539
2540        // (string | bool) | Edge <: string | (bool | Edge)
2541        assert!(left_associative.subtype(&right_associative));
2542        // string | (bool | Edge) <: (string | bool) | Edge
2543        assert!(right_associative.subtype(&left_associative));
2544
2545        let nested_edges = RuntimeType::Union(vec![
2546            RuntimeType::Union(vec![tagged_edge.clone(), edge.clone()]),
2547            tagged_edge.clone(),
2548        ]);
2549        // (TaggedEdge | Edge) | TaggedEdge <: Edge
2550        assert!(nested_edges.subtype(&edge));
2551
2552        let nested_with_bool = RuntimeType::Union(vec![RuntimeType::Union(vec![tagged_edge, boolean]), edge.clone()]);
2553        // (TaggedEdge | bool) | Edge is not a subtype of Edge.
2554        assert!(!nested_with_bool.subtype(&edge));
2555    }
2556
2557    fn enum_ty(module_id: u32, name: &str) -> RuntimeType {
2558        RuntimeType::Enum(EnumTypeId::new(ModuleId::from_usize(module_id as usize), name))
2559    }
2560
2561    /// A value holds its declaration, so building one by hand needs a
2562    /// declaration rather than just an id.
2563    fn enum_def(module_id: u32, name: &str, variants: &[&str]) -> Arc<EnumTypeDef> {
2564        Arc::new(
2565            EnumTypeDef::new(
2566                EnumTypeId::new(ModuleId::from_usize(module_id as usize), name),
2567                variants.iter().map(|v| (*v).to_owned()).collect(),
2568            )
2569            .unwrap(),
2570        )
2571    }
2572
2573    #[test]
2574    fn enum_subtyping_is_nominal() {
2575        let color = enum_ty(0, "Color");
2576        let shape = enum_ty(0, "Shape");
2577
2578        // An enum is a subtype of itself. Without a dedicated arm the catch-all
2579        // in `subtype` would answer false here and break reflexivity.
2580        assert!(color.subtype(&color));
2581        // Distinct declarations are unrelated, in both directions.
2582        assert!(!color.subtype(&shape));
2583        assert!(!shape.subtype(&color));
2584    }
2585
2586    #[test]
2587    fn enum_identity_is_module_plus_declared_name() {
2588        // Same declared name in two modules is two different types.
2589        assert!(!enum_ty(0, "Color").subtype(&enum_ty(1, "Color")));
2590        // Same declaration reached from anywhere is one type; an import alias
2591        // renames the binding, never the identity recorded here.
2592        assert!(enum_ty(1, "Color").subtype(&enum_ty(1, "Color")));
2593    }
2594
2595    #[test]
2596    fn enum_participates_in_the_general_type_rules() {
2597        let color = enum_ty(0, "Color");
2598
2599        // `any` and `never` keep their universal behaviour.
2600        assert!(color.subtype(&RuntimeType::any()));
2601        assert!(RuntimeType::never().subtype(&color));
2602        assert!(!color.subtype(&RuntimeType::never()));
2603
2604        // Unions and the singleton/array equivalences reach the enum arm by
2605        // recursion, so they work without enum-specific code.
2606        assert!(color.subtype(&RuntimeType::Union(vec![color.clone(), RuntimeType::string()])));
2607        assert!(!color.subtype(&RuntimeType::Union(vec![RuntimeType::string(), enum_ty(0, "Shape")])));
2608        assert!(color.subtype(&RuntimeType::Array(Box::new(color.clone()), ArrayLen::Known(1))));
2609        assert!(RuntimeType::Array(Box::new(color.clone()), ArrayLen::Known(1)).subtype(&color));
2610
2611        // An enum is unrelated to the primitives it could later project to.
2612        assert!(!color.subtype(&RuntimeType::string()));
2613        assert!(!RuntimeType::string().subtype(&color));
2614    }
2615
2616    #[test]
2617    fn enum_values_report_their_own_type() {
2618        let red = KclValue::Enum {
2619            value: Box::new(EnumValue::new(enum_def(0, "Color", &["Red"]), "Red", Vec::new())),
2620        };
2621
2622        assert_eq!(red.principal_type(), Some(enum_ty(0, "Color")));
2623        assert!(red.has_type(&enum_ty(0, "Color")));
2624        // Nominal identity, not the variant name, decides the type.
2625        assert!(!red.has_type(&enum_ty(0, "Shape")));
2626        assert!(!red.has_type(&RuntimeType::string()));
2627    }
2628
2629    #[test]
2630    fn enum_types_display_by_declared_name() {
2631        let color = enum_ty(0, "Color");
2632
2633        assert_eq!(color.to_string(), "Color");
2634        assert_eq!(color.human_friendly_type(), "Color");
2635        assert_eq!(
2636            RuntimeType::Array(Box::new(color), ArrayLen::Minimum(1)).human_friendly_type(),
2637            "one or more `Color` values"
2638        );
2639    }
2640
2641    /// The seam Gate 4 will build on: a registered enum declaration resolves to
2642    /// its nominal runtime type when named in a type position.
2643    #[tokio::test(flavor = "multi_thread")]
2644    async fn from_alias_resolves_a_declared_enum_to_its_nominal_type() {
2645        // Gate 4 registers enums during execution; until then, bind one by hand
2646        // into a real environment to exercise the resolution path.
2647        let mut exec_state = parse_execute("x = 1").await.unwrap().exec_state;
2648        let id = EnumTypeId::new(ModuleId::default(), "Color");
2649        let source_range = SourceRange::default();
2650
2651        // Execution has finished, so there is no current environment to bind into.
2652        exec_state.mut_stack().push_new_root_env(true).unwrap();
2653        exec_state
2654            .mut_stack()
2655            .add(
2656                format!("{}Color", memory::TYPE_PREFIX),
2657                KclValue::Type {
2658                    value: TypeDef::Enum(Arc::new(EnumTypeDef::new(id.clone(), vec!["Red".to_owned()]).unwrap())),
2659                    experimental: false,
2660                    meta: vec![],
2661                },
2662                source_range,
2663            )
2664            .unwrap();
2665
2666        assert_eq!(
2667            RuntimeType::from_alias("Color", &mut exec_state, source_range, false).unwrap(),
2668            RuntimeType::Enum(id)
2669        );
2670        // An unregistered name is still an unknown type, not a silent enum.
2671        RuntimeType::from_alias("Shape", &mut exec_state, source_range, false).unwrap_err();
2672    }
2673
2674    #[tokio::test(flavor = "multi_thread")]
2675    async fn enum_coercion_requires_the_same_declaration() {
2676        let (ctx, mut exec_state) = new_exec_state().await;
2677        let red = KclValue::Enum {
2678            value: Box::new(EnumValue::new(enum_def(0, "Color", &["Red"]), "Red", Vec::new())),
2679        };
2680
2681        // Coercing to its own type is identity-preserving.
2682        assert_eq!(
2683            red.coerce(&enum_ty(0, "Color"), CoercionMode::implicit(), &mut exec_state)
2684                .unwrap(),
2685            red
2686        );
2687        // Everything else is rejected, including projection to string, which is
2688        // explicit ascription rather than coercion.
2689        red.coerce(&enum_ty(0, "Shape"), CoercionMode::implicit(), &mut exec_state)
2690            .unwrap_err();
2691        red.coerce(&enum_ty(1, "Color"), CoercionMode::implicit(), &mut exec_state)
2692            .unwrap_err();
2693        red.coerce(&RuntimeType::string(), CoercionMode::implicit(), &mut exec_state)
2694            .unwrap_err();
2695        // A non-enum value never satisfies an enum type.
2696        let string = KclValue::String {
2697            value: "Red".to_owned(),
2698            meta: Vec::new(),
2699        };
2700        string
2701            .coerce(&enum_ty(0, "Color"), CoercionMode::implicit(), &mut exec_state)
2702            .unwrap_err();
2703
2704        ctx.close().await;
2705    }
2706
2707    fn enum_value(module_id: u32, name: &str, variants: &[&str], variant: &str) -> KclValue {
2708        KclValue::Enum {
2709            value: Box::new(EnumValue::new(enum_def(module_id, name, variants), variant, Vec::new())),
2710        }
2711    }
2712
2713    fn string_value(value: &str) -> KclValue {
2714        KclValue::String {
2715            value: value.to_owned(),
2716            meta: Vec::new(),
2717        }
2718    }
2719
2720    /// Every row states the outcome under BOTH modes, so the table pins the whole
2721    /// matrix of target shape against mode rather than one half of it. `None`
2722    /// means the coercion must fail.
2723    ///
2724    /// The pattern to read off it: projection happens wherever the type walk
2725    /// reaches, and only when the user wrote the type.
2726    #[tokio::test(flavor = "multi_thread")]
2727    async fn enum_projects_by_target_shape() {
2728        let (ctx, mut exec_state) = new_exec_state().await;
2729        let variants = &["Red", "Green"];
2730        let red = enum_value(0, "Color", variants, "Red");
2731        let green = enum_value(0, "Color", variants, "Green");
2732        let color = enum_ty(0, "Color");
2733        let string = RuntimeType::string();
2734        let strings = RuntimeType::Array(Box::new(string.clone()), ArrayLen::None);
2735        let array = |value: Vec<KclValue>, ty: RuntimeType| KclValue::HomArray { value, ty };
2736        let tuple = |value: Vec<KclValue>| KclValue::Tuple {
2737            value,
2738            meta: Vec::new(),
2739        };
2740
2741        #[allow(clippy::type_complexity)]
2742        let rows: Vec<(&str, KclValue, RuntimeType, Option<KclValue>, Option<KclValue>)> = vec![
2743            (
2744                "a bare enum",
2745                red.clone(),
2746                string.clone(),
2747                Some(string_value("Red")),
2748                None,
2749            ),
2750            (
2751                "an array, element by element",
2752                array(vec![red.clone(), green.clone()], color.clone()),
2753                strings.clone(),
2754                Some(array(vec![string_value("Red"), string_value("Green")], string.clone())),
2755                None,
2756            ),
2757            (
2758                "an array of arrays, so more than one level down",
2759                array(vec![array(vec![green.clone()], RuntimeType::any())], RuntimeType::any()),
2760                RuntimeType::Array(Box::new(strings.clone()), ArrayLen::None),
2761                Some(array(
2762                    vec![array(vec![string_value("Green")], string.clone())],
2763                    strings.clone(),
2764                )),
2765                None,
2766            ),
2767            (
2768                // KCL has no tuple type syntax, so this shape is only reachable here.
2769                "a tuple, positionally, beside a value that needs nothing done",
2770                tuple(vec![red.clone(), string_value("plain")]),
2771                RuntimeType::Tuple(vec![string.clone(), string.clone()]),
2772                Some(tuple(vec![string_value("Red"), string_value("plain")])),
2773                None,
2774            ),
2775            (
2776                // The existing singleton/array equivalence carries projection with
2777                // it, the same way it carries numeric coercion.
2778                "a one-element array against a bare string",
2779                array(vec![red.clone()], RuntimeType::any()),
2780                string.clone(),
2781                Some(string_value("Red")),
2782                None,
2783            ),
2784            (
2785                // Object coercion checks fields with `has_type` and converts
2786                // nothing: see the `TODO coerce fields` in `coerce_to_object_type`.
2787                // Inherited behavior rather than an enum rule, so when field
2788                // coercion is implemented this row should start expecting a
2789                // projection instead of being deleted.
2790                "an object field, which projects nothing",
2791                KclValue::Object {
2792                    value: HashMap::from([("c".to_owned(), red.clone())]),
2793                    constrainable: false,
2794                    object_kind: Default::default(),
2795                    meta: Vec::new(),
2796                },
2797                RuntimeType::Object(vec![("c".to_owned(), string.clone())], false),
2798                None,
2799                None,
2800            ),
2801            (
2802                "its own type, which is a check rather than a conversion",
2803                red.clone(),
2804                color.clone(),
2805                Some(red.clone()),
2806                Some(red.clone()),
2807            ),
2808            (
2809                "another declaration, which projection is not a way around",
2810                red.clone(),
2811                enum_ty(0, "Shade"),
2812                None,
2813                None,
2814            ),
2815        ];
2816
2817        for (case, value, target, explicit, implicit) in rows {
2818            assert_eq!(
2819                value.coerce(&target, CoercionMode::explicit(), &mut exec_state).ok(),
2820                explicit,
2821                "explicit mode, case: {case}"
2822            );
2823            assert_eq!(
2824                value.coerce(&target, CoercionMode::implicit(), &mut exec_state).ok(),
2825                implicit,
2826                "implicit mode, case: {case}"
2827            );
2828        }
2829
2830        ctx.close().await;
2831    }
2832
2833    /// A member that accepts the value unchanged wins over one that would change
2834    /// it, whichever order the union was written in. Each pair of rows below is
2835    /// the same union spelled both ways, so a rule that depended on order would
2836    /// fail one row of the pair.
2837    #[tokio::test(flavor = "multi_thread")]
2838    async fn enum_projection_ignores_the_order_a_union_was_written_in() {
2839        let (ctx, mut exec_state) = new_exec_state().await;
2840        let red = enum_value(0, "Color", &["Red"], "Red");
2841        let string = RuntimeType::string();
2842        let color = enum_ty(0, "Color");
2843        let shade = enum_ty(0, "Shade");
2844
2845        let rows: Vec<(&str, Vec<RuntimeType>, Option<KclValue>)> = vec![
2846            ("the enum first", vec![color.clone(), string.clone()], Some(red.clone())),
2847            ("the enum last", vec![string.clone(), color.clone()], Some(red.clone())),
2848            (
2849                "no member accepts an enum, so projection is what satisfies it",
2850                vec![RuntimeType::bool(), string.clone()],
2851                Some(string_value("Red")),
2852            ),
2853            (
2854                "a different enum is not a match, so this projects too",
2855                vec![shade.clone(), string.clone()],
2856                Some(string_value("Red")),
2857            ),
2858            (
2859                "a different enum with no string member is unsatisfiable",
2860                vec![shade, RuntimeType::bool()],
2861                None,
2862            ),
2863        ];
2864
2865        for (case, tys, expected) in rows {
2866            let union = RuntimeType::Union(tys);
2867            assert_eq!(
2868                red.coerce(&union, CoercionMode::explicit(), &mut exec_state).ok(),
2869                expected,
2870                "case: {case} ({union})"
2871            );
2872        }
2873
2874        ctx.close().await;
2875    }
2876
2877    /// The numeric target reports what the user asked for and cannot have; the
2878    /// implicit boundary keeps the numeric wording, because nobody asked for a
2879    /// projection there.
2880    #[tokio::test(flavor = "multi_thread")]
2881    async fn enum_projection_to_a_number_explains_itself() {
2882        let (ctx, mut exec_state) = new_exec_state().await;
2883        let red = enum_value(0, "Color", &["Red"], "Red");
2884        let message = "Cannot project enum `Color` to a number. An enum projects to `string`; projecting to a number is not supported yet.";
2885
2886        for (case, mode, expected) in [
2887            ("explicit", CoercionMode::explicit(), Some(message)),
2888            ("implicit", CoercionMode::implicit(), None),
2889        ] {
2890            let err = red.coerce(&RuntimeType::count(), mode, &mut exec_state).unwrap_err();
2891            assert_eq!(err.message.as_deref(), expected, "case: {case}");
2892        }
2893
2894        ctx.close().await;
2895    }
2896
2897    #[tokio::test(flavor = "multi_thread")]
2898    async fn never_is_bottom_and_uninhabited() {
2899        let (ctx, mut exec_state) = new_exec_state().await;
2900        let never = RuntimeType::never();
2901        let string = RuntimeType::string();
2902
2903        for ty in [
2904            RuntimeType::any(),
2905            string.clone(),
2906            RuntimeType::Array(Box::new(string.clone()), ArrayLen::None),
2907            RuntimeType::Tuple(vec![string.clone()]),
2908            RuntimeType::Object(vec![("value".to_owned(), string.clone())], false),
2909            RuntimeType::Union(vec![string.clone(), RuntimeType::bool()]),
2910        ] {
2911            assert!(never.subtype(&ty), "`never` should be a subtype of {ty}");
2912        }
2913
2914        assert!(!string.subtype(&never));
2915        assert!(RuntimeType::Union(vec![never.clone(), string.clone()]).subtype(&string));
2916
2917        for value in values(&mut exec_state) {
2918            value
2919                .coerce(&never, CoercionMode::implicit(), &mut exec_state)
2920                .unwrap_err();
2921        }
2922        ctx.close().await;
2923    }
2924
2925    #[tokio::test(flavor = "multi_thread")]
2926    async fn coerce_axes() {
2927        let (ctx, mut exec_state) = new_exec_state().await;
2928
2929        // Subtyping
2930        assert!(RuntimeType::Primitive(PrimitiveType::Axis2d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis2d)));
2931        assert!(RuntimeType::Primitive(PrimitiveType::Axis3d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis3d)));
2932        assert!(!RuntimeType::Primitive(PrimitiveType::Axis3d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis2d)));
2933        assert!(!RuntimeType::Primitive(PrimitiveType::Axis2d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis3d)));
2934
2935        // Coercion
2936        let a2d = KclValue::Object {
2937            value: [
2938                (
2939                    "origin".to_owned(),
2940                    KclValue::HomArray {
2941                        value: vec![
2942                            KclValue::Number {
2943                                value: 0.0,
2944                                ty: NumericType::mm(),
2945                                meta: Vec::new(),
2946                            },
2947                            KclValue::Number {
2948                                value: 0.0,
2949                                ty: NumericType::mm(),
2950                                meta: Vec::new(),
2951                            },
2952                        ],
2953                        ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
2954                    },
2955                ),
2956                (
2957                    "direction".to_owned(),
2958                    KclValue::HomArray {
2959                        value: vec![
2960                            KclValue::Number {
2961                                value: 1.0,
2962                                ty: NumericType::mm(),
2963                                meta: Vec::new(),
2964                            },
2965                            KclValue::Number {
2966                                value: 0.0,
2967                                ty: NumericType::mm(),
2968                                meta: Vec::new(),
2969                            },
2970                        ],
2971                        ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
2972                    },
2973                ),
2974            ]
2975            .into(),
2976            meta: Vec::new(),
2977            constrainable: false,
2978            object_kind: Default::default(),
2979        };
2980        let a3d = KclValue::Object {
2981            value: [
2982                (
2983                    "origin".to_owned(),
2984                    KclValue::HomArray {
2985                        value: vec![
2986                            KclValue::Number {
2987                                value: 0.0,
2988                                ty: NumericType::mm(),
2989                                meta: Vec::new(),
2990                            },
2991                            KclValue::Number {
2992                                value: 0.0,
2993                                ty: NumericType::mm(),
2994                                meta: Vec::new(),
2995                            },
2996                            KclValue::Number {
2997                                value: 0.0,
2998                                ty: NumericType::mm(),
2999                                meta: Vec::new(),
3000                            },
3001                        ],
3002                        ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
3003                    },
3004                ),
3005                (
3006                    "direction".to_owned(),
3007                    KclValue::HomArray {
3008                        value: vec![
3009                            KclValue::Number {
3010                                value: 1.0,
3011                                ty: NumericType::mm(),
3012                                meta: Vec::new(),
3013                            },
3014                            KclValue::Number {
3015                                value: 0.0,
3016                                ty: NumericType::mm(),
3017                                meta: Vec::new(),
3018                            },
3019                            KclValue::Number {
3020                                value: 1.0,
3021                                ty: NumericType::mm(),
3022                                meta: Vec::new(),
3023                            },
3024                        ],
3025                        ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
3026                    },
3027                ),
3028            ]
3029            .into(),
3030            meta: Vec::new(),
3031            constrainable: false,
3032            object_kind: Default::default(),
3033        };
3034
3035        let ty2d = RuntimeType::Primitive(PrimitiveType::Axis2d);
3036        let ty3d = RuntimeType::Primitive(PrimitiveType::Axis3d);
3037
3038        assert_coerce_results(&a2d, &ty2d, &a2d, &mut exec_state);
3039        assert_coerce_results(&a3d, &ty3d, &a3d, &mut exec_state);
3040        assert_coerce_results(&a3d, &ty2d, &a2d, &mut exec_state);
3041        a2d.coerce(&ty3d, CoercionMode::implicit(), &mut exec_state)
3042            .unwrap_err();
3043        ctx.close().await;
3044    }
3045
3046    #[tokio::test(flavor = "multi_thread")]
3047    async fn coerce_numeric() {
3048        let (ctx, mut exec_state) = new_exec_state().await;
3049
3050        let count = KclValue::Number {
3051            value: 1.0,
3052            ty: NumericType::count(),
3053            meta: Vec::new(),
3054        };
3055        let mm = KclValue::Number {
3056            value: 1.0,
3057            ty: NumericType::mm(),
3058            meta: Vec::new(),
3059        };
3060        let inches = KclValue::Number {
3061            value: 1.0,
3062            ty: NumericType::Known(UnitType::Length(UnitLength::Inches)),
3063            meta: Vec::new(),
3064        };
3065        let rads = KclValue::Number {
3066            value: 1.0,
3067            ty: NumericType::Known(UnitType::Angle(UnitAngle::Radians)),
3068            meta: Vec::new(),
3069        };
3070        let default = KclValue::Number {
3071            value: 1.0,
3072            ty: NumericType::default(),
3073            meta: Vec::new(),
3074        };
3075        let any = KclValue::Number {
3076            value: 1.0,
3077            ty: NumericType::Any,
3078            meta: Vec::new(),
3079        };
3080        let unknown = KclValue::Number {
3081            value: 1.0,
3082            ty: NumericType::Unknown,
3083            meta: Vec::new(),
3084        };
3085
3086        // Trivial coercions
3087        assert_coerce_results(&count, &NumericType::count().into(), &count, &mut exec_state);
3088        assert_coerce_results(&mm, &NumericType::mm().into(), &mm, &mut exec_state);
3089        assert_coerce_results(&any, &NumericType::Any.into(), &any, &mut exec_state);
3090        assert_coerce_results(&unknown, &NumericType::Unknown.into(), &unknown, &mut exec_state);
3091        assert_coerce_results(&default, &NumericType::default().into(), &default, &mut exec_state);
3092
3093        assert_coerce_results(&count, &NumericType::Any.into(), &count, &mut exec_state);
3094        assert_coerce_results(&mm, &NumericType::Any.into(), &mm, &mut exec_state);
3095        assert_coerce_results(&unknown, &NumericType::Any.into(), &unknown, &mut exec_state);
3096        assert_coerce_results(&default, &NumericType::Any.into(), &default, &mut exec_state);
3097
3098        assert_eq!(
3099            default
3100                .coerce(
3101                    &NumericType::Default {
3102                        len: UnitLength::Yards,
3103                        angle: UnitAngle::Degrees,
3104                    }
3105                    .into(),
3106                    CoercionMode::implicit(),
3107                    &mut exec_state
3108                )
3109                .unwrap(),
3110            default
3111        );
3112
3113        // No coercion
3114        count
3115            .coerce(&NumericType::mm().into(), CoercionMode::implicit(), &mut exec_state)
3116            .unwrap_err();
3117        mm.coerce(&NumericType::count().into(), CoercionMode::implicit(), &mut exec_state)
3118            .unwrap_err();
3119        unknown
3120            .coerce(&NumericType::mm().into(), CoercionMode::implicit(), &mut exec_state)
3121            .unwrap_err();
3122        unknown
3123            .coerce(
3124                &NumericType::default().into(),
3125                CoercionMode::implicit(),
3126                &mut exec_state,
3127            )
3128            .unwrap_err();
3129
3130        count
3131            .coerce(&NumericType::Unknown.into(), CoercionMode::implicit(), &mut exec_state)
3132            .unwrap_err();
3133        mm.coerce(&NumericType::Unknown.into(), CoercionMode::implicit(), &mut exec_state)
3134            .unwrap_err();
3135        default
3136            .coerce(&NumericType::Unknown.into(), CoercionMode::implicit(), &mut exec_state)
3137            .unwrap_err();
3138
3139        assert_eq!(
3140            inches
3141                .coerce(&NumericType::mm().into(), CoercionMode::implicit(), &mut exec_state)
3142                .unwrap()
3143                .as_f64()
3144                .unwrap()
3145                .round(),
3146            25.0
3147        );
3148        assert_eq!(
3149            rads.coerce(
3150                &NumericType::Known(UnitType::Angle(UnitAngle::Degrees)).into(),
3151                CoercionMode::implicit(),
3152                &mut exec_state
3153            )
3154            .unwrap()
3155            .as_f64()
3156            .unwrap()
3157            .round(),
3158            57.0
3159        );
3160        assert_eq!(
3161            inches
3162                .coerce(
3163                    &NumericType::default().into(),
3164                    CoercionMode::implicit(),
3165                    &mut exec_state
3166                )
3167                .unwrap()
3168                .as_f64()
3169                .unwrap()
3170                .round(),
3171            1.0
3172        );
3173        assert_eq!(
3174            rads.coerce(
3175                &NumericType::default().into(),
3176                CoercionMode::implicit(),
3177                &mut exec_state
3178            )
3179            .unwrap()
3180            .as_f64()
3181            .unwrap()
3182            .round(),
3183            1.0
3184        );
3185        ctx.close().await;
3186    }
3187
3188    #[track_caller]
3189    fn assert_value_and_type(name: &str, result: &ExecTestResults, expected: f64, expected_ty: NumericType) {
3190        let mem = result.exec_state.stack();
3191        match mem
3192            .memory
3193            .get_from_owned(name, result.mem_env, SourceRange::default(), 0)
3194            .unwrap()
3195        {
3196            KclValue::Number { value, ty, .. } => {
3197                assert_eq!(value.round(), expected);
3198                assert_eq!(ty, expected_ty);
3199            }
3200            _ => unreachable!(),
3201        }
3202    }
3203
3204    #[tokio::test(flavor = "multi_thread")]
3205    async fn combine_numeric() {
3206        let program = r#"a = 5 + 4
3207b = 5 - 2
3208c = 5mm - 2mm + 10mm
3209d = 5mm - 2 + 10
3210e = 5 - 2mm + 10
3211f = 30mm - 1inch
3212
3213g = 2 * 10
3214h = 2 * 10mm
3215i = 2mm * 10mm
3216j = 2_ * 10
3217k = 2_ * 3mm * 3mm
3218
3219l = 1 / 10
3220m = 2mm / 1mm
3221n = 10inch / 2mm
3222o = 3mm / 3
3223p = 3_ / 4
3224q = 4inch / 2_
3225
3226r = min([0, 3, 42])
3227s = min([0, 3mm, -42])
3228t = min([100, 3in, 142mm])
3229u = min([3rad, 4in])
3230"#;
3231
3232        let result = parse_execute(program).await.unwrap();
3233        assert_eq!(
3234            result.exec_state.issues().len(),
3235            5,
3236            "errors: {:?}",
3237            result.exec_state.issues()
3238        );
3239
3240        assert_value_and_type("a", &result, 9.0, NumericType::default());
3241        assert_value_and_type("b", &result, 3.0, NumericType::default());
3242        assert_value_and_type("c", &result, 13.0, NumericType::mm());
3243        assert_value_and_type("d", &result, 13.0, NumericType::mm());
3244        assert_value_and_type("e", &result, 13.0, NumericType::mm());
3245        assert_value_and_type("f", &result, 5.0, NumericType::mm());
3246
3247        assert_value_and_type("g", &result, 20.0, NumericType::default());
3248        assert_value_and_type("h", &result, 20.0, NumericType::mm());
3249        assert_value_and_type("i", &result, 20.0, NumericType::Unknown);
3250        assert_value_and_type("j", &result, 20.0, NumericType::default());
3251        assert_value_and_type("k", &result, 18.0, NumericType::Unknown);
3252
3253        assert_value_and_type("l", &result, 0.0, NumericType::default());
3254        assert_value_and_type("m", &result, 2.0, NumericType::count());
3255        assert_value_and_type("n", &result, 5.0, NumericType::Unknown);
3256        assert_value_and_type("o", &result, 1.0, NumericType::mm());
3257        assert_value_and_type("p", &result, 1.0, NumericType::count());
3258        assert_value_and_type(
3259            "q",
3260            &result,
3261            2.0,
3262            NumericType::Known(UnitType::Length(UnitLength::Inches)),
3263        );
3264
3265        assert_value_and_type("r", &result, 0.0, NumericType::default());
3266        assert_value_and_type("s", &result, -42.0, NumericType::mm());
3267        assert_value_and_type("t", &result, 3.0, NumericType::Unknown);
3268        assert_value_and_type("u", &result, 3.0, NumericType::Unknown);
3269    }
3270
3271    #[tokio::test(flavor = "multi_thread")]
3272    async fn bad_typed_arithmetic() {
3273        let program = r#"
3274a = 1rad
3275b = 180 / PI * a + 360
3276"#;
3277
3278        let result = parse_execute(program).await.unwrap();
3279
3280        assert_value_and_type("a", &result, 1.0, NumericType::radians());
3281        assert_value_and_type("b", &result, 417.0, NumericType::Unknown);
3282    }
3283
3284    #[tokio::test(flavor = "multi_thread")]
3285    async fn cos_coercions() {
3286        let program = r#"
3287a = cos(units::toRadians(30deg))
3288b = 3 / a
3289c = cos(30deg)
3290d = cos(1rad)
3291"#;
3292
3293        let result = parse_execute(program).await.unwrap();
3294        assert!(
3295            result.exec_state.issues().is_empty(),
3296            "{:?}",
3297            result.exec_state.issues()
3298        );
3299
3300        assert_value_and_type("a", &result, 1.0, NumericType::default());
3301        assert_value_and_type("b", &result, 3.0, NumericType::default());
3302        assert_value_and_type("c", &result, 1.0, NumericType::default());
3303        assert_value_and_type("d", &result, 1.0, NumericType::default());
3304    }
3305
3306    #[tokio::test(flavor = "multi_thread")]
3307    async fn coerce_nested_array() {
3308        let (ctx, mut exec_state) = new_exec_state().await;
3309
3310        let mixed1 = KclValue::HomArray {
3311            value: vec![
3312                KclValue::Number {
3313                    value: 0.0,
3314                    ty: NumericType::count(),
3315                    meta: Vec::new(),
3316                },
3317                KclValue::Number {
3318                    value: 1.0,
3319                    ty: NumericType::count(),
3320                    meta: Vec::new(),
3321                },
3322                KclValue::HomArray {
3323                    value: vec![
3324                        KclValue::Number {
3325                            value: 2.0,
3326                            ty: NumericType::count(),
3327                            meta: Vec::new(),
3328                        },
3329                        KclValue::Number {
3330                            value: 3.0,
3331                            ty: NumericType::count(),
3332                            meta: Vec::new(),
3333                        },
3334                    ],
3335                    ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
3336                },
3337            ],
3338            ty: RuntimeType::any(),
3339        };
3340
3341        // Principal types
3342        let tym1 = RuntimeType::Array(
3343            Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
3344            ArrayLen::Minimum(1),
3345        );
3346
3347        let result = KclValue::HomArray {
3348            value: vec![
3349                KclValue::Number {
3350                    value: 0.0,
3351                    ty: NumericType::count(),
3352                    meta: Vec::new(),
3353                },
3354                KclValue::Number {
3355                    value: 1.0,
3356                    ty: NumericType::count(),
3357                    meta: Vec::new(),
3358                },
3359                KclValue::Number {
3360                    value: 2.0,
3361                    ty: NumericType::count(),
3362                    meta: Vec::new(),
3363                },
3364                KclValue::Number {
3365                    value: 3.0,
3366                    ty: NumericType::count(),
3367                    meta: Vec::new(),
3368                },
3369            ],
3370            ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
3371        };
3372        assert_coerce_results(&mixed1, &tym1, &result, &mut exec_state);
3373        ctx.close().await;
3374    }
3375}