Skip to main content

kcl_lib/execution/
types.rs

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