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