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