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;
6use kcl_api::NumericSuffix;
7pub use kcl_api::NumericType;
8use kcl_api::UnitAngle;
9use kcl_api::UnitLength;
10pub use kcl_api::UnitType;
11use serde::Deserialize;
12use serde::Serialize;
13
14use crate::CompilationIssue;
15use crate::KclError;
16use crate::SourceRange;
17use crate::errors::KclErrorDetails;
18use crate::exec::PlaneKind;
19use crate::execution::ExecState;
20use crate::execution::ExecutorContext;
21use crate::execution::Plane;
22use crate::execution::PlaneInfo;
23use crate::execution::Point3d;
24use crate::execution::SKETCH_OBJECT_META;
25use crate::execution::SKETCH_OBJECT_META_SKETCH;
26use crate::execution::annotations;
27use crate::execution::kcl_value::EnumTypeId;
28use crate::execution::kcl_value::KclValue;
29use crate::execution::kcl_value::TypeDef;
30use crate::execution::memory::{self};
31use crate::fmt;
32use crate::modules::ModuleItems;
33use crate::modules::ModulePath;
34use crate::parsing::ast::types::ABSOLUTE_PATHS_NOT_SUPPORTED;
35use crate::parsing::ast::types::Identifier;
36use crate::parsing::ast::types::Name;
37use crate::parsing::ast::types::Node;
38use crate::parsing::ast::types::PrimitiveType as AstPrimitiveType;
39use crate::parsing::ast::types::Type;
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
1306pub fn adjust_length(from: UnitLength, value: f64, to: UnitLength) -> (f64, UnitLength) {
1307    use UnitLength::*;
1308
1309    if from == to {
1310        return (value, to);
1311    }
1312
1313    let (base, base_unit) = match from {
1314        Millimeters => (value, Millimeters),
1315        Centimeters => (value * 10.0, Millimeters),
1316        Meters => (value * 1000.0, Millimeters),
1317        Inches => (value, Inches),
1318        Feet => (value * 12.0, Inches),
1319        Yards => (value * 36.0, Inches),
1320    };
1321    let (base, base_unit) = match (base_unit, to) {
1322        (Millimeters, Inches) | (Millimeters, Feet) | (Millimeters, Yards) => (base / 25.4, Inches),
1323        (Inches, Millimeters) | (Inches, Centimeters) | (Inches, Meters) => (base * 25.4, Millimeters),
1324        _ => (base, base_unit),
1325    };
1326
1327    let value = match (base_unit, to) {
1328        (Millimeters, Millimeters) => base,
1329        (Millimeters, Centimeters) => base / 10.0,
1330        (Millimeters, Meters) => base / 1000.0,
1331        (Inches, Inches) => base,
1332        (Inches, Feet) => base / 12.0,
1333        (Inches, Yards) => base / 36.0,
1334        _ => unreachable!(),
1335    };
1336
1337    (value, to)
1338}
1339
1340pub fn adjust_angle(from: UnitAngle, value: f64, to: UnitAngle) -> (f64, UnitAngle) {
1341    use std::f64::consts::PI;
1342
1343    use UnitAngle::*;
1344
1345    let value = match (from, to) {
1346        (Degrees, Degrees) => value,
1347        (Degrees, Radians) => (value / 180.0) * PI,
1348        (Radians, Degrees) => 180.0 * value / PI,
1349        (Radians, Radians) => value,
1350    };
1351
1352    (value, to)
1353}
1354
1355pub(super) fn length_from_str(s: &str, source_range: SourceRange) -> Result<UnitLength, KclError> {
1356    // We don't use `from_str` here because we want to be more flexible about the input we accept.
1357    match s {
1358        "mm" => Ok(UnitLength::Millimeters),
1359        "cm" => Ok(UnitLength::Centimeters),
1360        "m" => Ok(UnitLength::Meters),
1361        "inch" | "in" => Ok(UnitLength::Inches),
1362        "ft" => Ok(UnitLength::Feet),
1363        "yd" => Ok(UnitLength::Yards),
1364        value => Err(KclError::new_semantic(KclErrorDetails::new(
1365            format!("Unexpected value for length units: `{value}`; expected one of `mm`, `cm`, `m`, `in`, `ft`, `yd`"),
1366            vec![source_range],
1367        ))),
1368    }
1369}
1370
1371pub(super) fn angle_from_str(s: &str, source_range: SourceRange) -> Result<UnitAngle, KclError> {
1372    UnitAngle::from_str(s).map_err(|_| {
1373        KclError::new_semantic(KclErrorDetails::new(
1374            format!("Unexpected value for angle units: `{s}`; expected one of `deg`, `rad`"),
1375            vec![source_range],
1376        ))
1377    })
1378}
1379
1380/// Which value-changing conversions a coercion is allowed to perform. Separate
1381/// from the question of which types it accepts, which never varies.
1382///
1383/// The two constructors are the only two modes the language has: a value
1384/// crossing a boundary the user did not write, and a type the user wrote down.
1385#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1386pub struct CoercionMode {
1387    convert_units: bool,
1388    project_enums: bool,
1389}
1390
1391impl CoercionMode {
1392    /// A boundary the user did not write: an argument, a return, or a
1393    /// Rust-implemented function reading its arguments. Numbers convert to the
1394    /// target's units. Enums do not project, because an implicit projection
1395    /// would defeat nominal checking exactly where it matters.
1396    pub fn implicit() -> Self {
1397        CoercionMode {
1398            convert_units: true,
1399            project_enums: false,
1400        }
1401    }
1402
1403    /// A type the user wrote down, as in `expr: Type`. Numbers are reinterpreted
1404    /// as having the target's units rather than converted, and an enum projects
1405    /// to its declared representation.
1406    pub fn explicit() -> Self {
1407        CoercionMode {
1408            convert_units: false,
1409            project_enums: true,
1410        }
1411    }
1412
1413    pub(crate) fn convert_units(self) -> bool {
1414        self.convert_units
1415    }
1416
1417    pub(crate) fn project_enums(self) -> bool {
1418        self.project_enums
1419    }
1420
1421    /// The same mode with projection off, so that a union can look for an exact
1422    /// match before it considers projecting.
1423    pub(crate) fn without_projection(self) -> Self {
1424        CoercionMode {
1425            project_enums: false,
1426            ..self
1427        }
1428    }
1429}
1430
1431#[derive(Debug, Clone)]
1432pub struct CoercionError {
1433    pub found: Option<RuntimeType>,
1434    pub explicit_coercion: Option<String>,
1435    /// Set when the generic "could not coerce" wording would describe the wrong
1436    /// problem, and the caller should report this instead.
1437    pub message: Option<String>,
1438}
1439
1440impl CoercionError {
1441    fn with_explicit(mut self, c: String) -> Self {
1442        self.explicit_coercion = Some(c);
1443        self
1444    }
1445
1446    fn with_message(mut self, message: String) -> Self {
1447        self.message = Some(message);
1448        self
1449    }
1450}
1451
1452impl From<&'_ KclValue> for CoercionError {
1453    fn from(value: &'_ KclValue) -> Self {
1454        CoercionError {
1455            found: value.principal_type(),
1456            explicit_coercion: None,
1457            message: None,
1458        }
1459    }
1460}
1461
1462impl KclValue {
1463    /// True if `self` has a type which is a subtype of `ty` without coercion.
1464    pub fn has_type(&self, ty: &RuntimeType) -> bool {
1465        let Some(self_ty) = self.principal_type() else {
1466            return false;
1467        };
1468
1469        self_ty.subtype(ty)
1470    }
1471
1472    /// Coerce `self` to a new value which has `ty` as its closest supertype.
1473    ///
1474    /// If the result is Ok, then:
1475    ///   - result.principal_type().unwrap().subtype(ty)
1476    ///
1477    /// If self.principal_type() == ty then result == self
1478    pub fn coerce(
1479        &self,
1480        ty: &RuntimeType,
1481        mode: CoercionMode,
1482        exec_state: &mut ExecState,
1483    ) -> Result<KclValue, CoercionError> {
1484        match self {
1485            KclValue::Tuple { value, .. }
1486                if value.len() == 1
1487                    && !matches!(ty, RuntimeType::Primitive(PrimitiveType::Any) | RuntimeType::Tuple(..)) =>
1488            {
1489                if let Ok(coerced) = value[0].coerce(ty, mode, exec_state) {
1490                    return Ok(coerced);
1491                }
1492            }
1493            KclValue::HomArray { value, .. }
1494                if value.len() == 1
1495                    && !matches!(ty, RuntimeType::Primitive(PrimitiveType::Any) | RuntimeType::Array(..)) =>
1496            {
1497                if let Ok(coerced) = value[0].coerce(ty, mode, exec_state) {
1498                    return Ok(coerced);
1499                }
1500            }
1501            _ => {}
1502        }
1503
1504        match ty {
1505            RuntimeType::Primitive(ty) => self.coerce_to_primitive_type(ty, mode, exec_state),
1506            RuntimeType::Array(ty, len) => self.coerce_to_array_type(ty, mode, *len, exec_state, false),
1507            RuntimeType::Tuple(tys) => self.coerce_to_tuple_type(tys, mode, exec_state),
1508            RuntimeType::Union(tys) => self.coerce_to_union_type(tys, mode, exec_state),
1509            RuntimeType::Object(tys, constrainable) => {
1510                self.coerce_to_object_type(tys, *constrainable, mode, exec_state)
1511            }
1512            RuntimeType::Enum(id) => self.coerce_to_enum_type(id),
1513        }
1514    }
1515
1516    /// Enums are nominal, so the only value that coerces to an enum type is a
1517    /// value of that same enum, and it is returned unchanged. Projection out of
1518    /// an enum (`Color::Red: string`) is explicit ascription, not coercion, and
1519    /// is handled on its own path.
1520    fn coerce_to_enum_type(&self, id: &EnumTypeId) -> Result<KclValue, CoercionError> {
1521        match self {
1522            KclValue::Enum { value } if value.enum_id() == id => Ok(self.clone()),
1523            _ => Err(self.into()),
1524        }
1525    }
1526
1527    fn coerce_to_primitive_type(
1528        &self,
1529        ty: &PrimitiveType,
1530        mode: CoercionMode,
1531        exec_state: &mut ExecState,
1532    ) -> Result<KclValue, CoercionError> {
1533        match ty {
1534            PrimitiveType::Any => Ok(self.clone()),
1535            PrimitiveType::Never => Err(self.into()),
1536            PrimitiveType::None => match self {
1537                KclValue::KclNone { .. } => Ok(self.clone()),
1538                _ => Err(self.into()),
1539            },
1540            PrimitiveType::Number(ty) => {
1541                // `Color::Red: number(_)` is a projection the user asked for and
1542                // V1 cannot perform. Reporting the numeric "expected a number"
1543                // here would describe the wrong problem: the value is a working
1544                // enum, not a broken number.
1545                if let KclValue::Enum { value } = self
1546                    && mode.project_enums()
1547                {
1548                    return Err(CoercionError::from(self).with_message(format!(
1549                        "Cannot project enum `{}` to a number. An enum projects to `string`; projecting to a number is not supported yet.",
1550                        value.enum_id().declared_name()
1551                    )));
1552                }
1553
1554                if mode.convert_units() {
1555                    return ty.coerce(self);
1556                }
1557
1558                // Instead of converting units, reinterpret the number as having
1559                // different units.
1560                //
1561                // If the user is explicitly specifying units, treat the value
1562                // as having had its units erased, rather than forcing the user
1563                // to explicitly erase them.
1564                if let KclValue::Number { value: n, meta, .. } = &self
1565                    && ty.is_fully_specified()
1566                {
1567                    let value = KclValue::Number {
1568                        ty: NumericType::Any,
1569                        value: *n,
1570                        meta: meta.clone(),
1571                    };
1572                    return ty.coerce(&value);
1573                }
1574                ty.coerce(self)
1575            }
1576            PrimitiveType::String => match self {
1577                KclValue::String { .. } => Ok(self.clone()),
1578                // The one projection V1 performs, and only where the user wrote
1579                // the type: see `CoercionMode`.
1580                KclValue::Enum { value } if mode.project_enums() => Ok(KclValue::String {
1581                    value: value.declared_string_repr(),
1582                    meta: value.meta().to_vec(),
1583                }),
1584                _ => Err(self.into()),
1585            },
1586            PrimitiveType::Boolean => match self {
1587                KclValue::Bool { .. } => Ok(self.clone()),
1588                _ => Err(self.into()),
1589            },
1590            PrimitiveType::GdtAnnotation => match self {
1591                KclValue::GdtAnnotation { .. } => Ok(self.clone()),
1592                _ => Err(self.into()),
1593            },
1594            PrimitiveType::CameraView => match self {
1595                KclValue::CameraView { .. } => Ok(self.clone()),
1596                _ => Err(self.into()),
1597            },
1598            PrimitiveType::NamedView => match self {
1599                KclValue::NamedView { .. } => Ok(self.clone()),
1600                _ => Err(self.into()),
1601            },
1602            PrimitiveType::Segment => match self {
1603                KclValue::Segment { .. } => Ok(self.clone()),
1604                _ => Err(self.into()),
1605            },
1606            PrimitiveType::Sketch => match self {
1607                KclValue::Sketch { .. } => Ok(self.clone()),
1608                KclValue::Object { value, .. } => {
1609                    let Some(meta) = value.get(SKETCH_OBJECT_META) else {
1610                        return Err(self.into());
1611                    };
1612                    let KclValue::Object { value: meta_map, .. } = meta else {
1613                        return Err(self.into());
1614                    };
1615                    let Some(sketch) = meta_map.get(SKETCH_OBJECT_META_SKETCH).and_then(KclValue::as_sketch) else {
1616                        return Err(self.into());
1617                    };
1618
1619                    Ok(KclValue::Sketch {
1620                        value: Box::new(sketch.clone()),
1621                    })
1622                }
1623                _ => Err(self.into()),
1624            },
1625            PrimitiveType::Constraint => match self {
1626                KclValue::SketchConstraint { .. } => Ok(self.clone()),
1627                _ => Err(self.into()),
1628            },
1629            PrimitiveType::Solid => match self {
1630                KclValue::Solid { .. } => Ok(self.clone()),
1631                _ => Err(self.into()),
1632            },
1633            PrimitiveType::Plane => {
1634                match self {
1635                    KclValue::String { value: s, .. }
1636                        if [
1637                            "xy", "xz", "yz", "-xy", "-xz", "-yz", "XY", "XZ", "YZ", "-XY", "-XZ", "-YZ",
1638                        ]
1639                        .contains(&&**s) =>
1640                    {
1641                        Ok(self.clone())
1642                    }
1643                    KclValue::Plane { .. } => Ok(self.clone()),
1644                    KclValue::Object { value, meta, .. } => {
1645                        let origin = value
1646                            .get("origin")
1647                            .and_then(Point3d::from_kcl_val)
1648                            .ok_or(CoercionError::from(self))?;
1649                        let x_axis = value
1650                            .get("xAxis")
1651                            .and_then(Point3d::from_kcl_val)
1652                            .ok_or(CoercionError::from(self))?;
1653                        let y_axis = value
1654                            .get("yAxis")
1655                            .and_then(Point3d::from_kcl_val)
1656                            .ok_or(CoercionError::from(self))?;
1657                        let z_axis = x_axis.axes_cross_product(&y_axis);
1658
1659                        if value.get("zAxis").is_some() {
1660                            exec_state.warn(CompilationIssue::err(
1661                            self.into(),
1662                            "Object with a zAxis field is being coerced into a plane, but the zAxis is ignored.",
1663                        ), annotations::WARN_IGNORED_Z_AXIS);
1664                        }
1665
1666                        let id = exec_state.mod_local.id_generator.next_uuid();
1667                        let info = PlaneInfo {
1668                            origin,
1669                            x_axis: x_axis.normalize(),
1670                            y_axis: y_axis.normalize(),
1671                            z_axis: z_axis.normalize(),
1672                        };
1673                        let plane = Plane {
1674                            id,
1675                            artifact_id: id.into(),
1676                            object_id: None,
1677                            kind: PlaneKind::from(&info),
1678                            info,
1679                            meta: meta.clone(),
1680                        };
1681
1682                        Ok(KclValue::Plane { value: Box::new(plane) })
1683                    }
1684                    _ => Err(self.into()),
1685                }
1686            }
1687            PrimitiveType::Face => match self {
1688                KclValue::Face { .. } => Ok(self.clone()),
1689                _ => Err(self.into()),
1690            },
1691            PrimitiveType::Helix => match self {
1692                KclValue::Helix { .. } => Ok(self.clone()),
1693                _ => Err(self.into()),
1694            },
1695            PrimitiveType::Edge => match self {
1696                KclValue::Uuid { .. } => Ok(self.clone()),
1697                KclValue::TagIdentifier { .. } => Ok(self.clone()),
1698                _ => Err(self.into()),
1699            },
1700            PrimitiveType::BoundedEdge => match self {
1701                KclValue::BoundedEdge { .. } => Ok(self.clone()),
1702                _ => Err(self.into()),
1703            },
1704            PrimitiveType::TaggedEdge => match self {
1705                KclValue::TagIdentifier { .. } => Ok(self.clone()),
1706                _ => Err(self.into()),
1707            },
1708            PrimitiveType::TaggedFace => match self {
1709                KclValue::TagIdentifier { .. } => Ok(self.clone()),
1710                s @ KclValue::String { value, .. } if ["start", "end", "START", "END"].contains(&&**value) => {
1711                    Ok(s.clone())
1712                }
1713                _ => Err(self.into()),
1714            },
1715            PrimitiveType::Axis2d => match self {
1716                KclValue::Object {
1717                    value: values, meta, ..
1718                } => {
1719                    if values
1720                        .get("origin")
1721                        .ok_or(CoercionError::from(self))?
1722                        .has_type(&RuntimeType::point2d())
1723                        && values
1724                            .get("direction")
1725                            .ok_or(CoercionError::from(self))?
1726                            .has_type(&RuntimeType::point2d())
1727                    {
1728                        return Ok(self.clone());
1729                    }
1730
1731                    let origin = values.get("origin").ok_or(self.into()).and_then(|p| {
1732                        p.coerce_to_array_type(&RuntimeType::length(), mode, ArrayLen::Known(2), exec_state, true)
1733                    })?;
1734                    let direction = values.get("direction").ok_or(self.into()).and_then(|p| {
1735                        p.coerce_to_array_type(&RuntimeType::length(), mode, ArrayLen::Known(2), exec_state, true)
1736                    })?;
1737
1738                    Ok(KclValue::Object {
1739                        value: [("origin".to_owned(), origin), ("direction".to_owned(), direction)].into(),
1740                        meta: meta.clone(),
1741                        constrainable: false,
1742                        object_kind: Default::default(),
1743                    })
1744                }
1745                _ => Err(self.into()),
1746            },
1747            PrimitiveType::Axis3d => match self {
1748                KclValue::Object {
1749                    value: values, meta, ..
1750                } => {
1751                    if values
1752                        .get("origin")
1753                        .ok_or(CoercionError::from(self))?
1754                        .has_type(&RuntimeType::point3d())
1755                        && values
1756                            .get("direction")
1757                            .ok_or(CoercionError::from(self))?
1758                            .has_type(&RuntimeType::point3d())
1759                    {
1760                        return Ok(self.clone());
1761                    }
1762
1763                    let origin = values.get("origin").ok_or(self.into()).and_then(|p| {
1764                        p.coerce_to_array_type(&RuntimeType::length(), mode, ArrayLen::Known(3), exec_state, true)
1765                    })?;
1766                    let direction = values.get("direction").ok_or(self.into()).and_then(|p| {
1767                        p.coerce_to_array_type(&RuntimeType::length(), mode, ArrayLen::Known(3), exec_state, true)
1768                    })?;
1769
1770                    Ok(KclValue::Object {
1771                        value: [("origin".to_owned(), origin), ("direction".to_owned(), direction)].into(),
1772                        meta: meta.clone(),
1773                        constrainable: false,
1774                        object_kind: Default::default(),
1775                    })
1776                }
1777                _ => Err(self.into()),
1778            },
1779            PrimitiveType::ImportedGeometry => match self {
1780                KclValue::ImportedGeometry { .. } => Ok(self.clone()),
1781                _ => Err(self.into()),
1782            },
1783            PrimitiveType::Function => match self {
1784                KclValue::Function { .. } => Ok(self.clone()),
1785                _ => Err(self.into()),
1786            },
1787            PrimitiveType::TagDecl => match self {
1788                KclValue::TagDeclarator { .. } => Ok(self.clone()),
1789                _ => Err(self.into()),
1790            },
1791        }
1792    }
1793
1794    fn coerce_to_array_type(
1795        &self,
1796        ty: &RuntimeType,
1797        mode: CoercionMode,
1798        len: ArrayLen,
1799        exec_state: &mut ExecState,
1800        allow_shrink: bool,
1801    ) -> Result<KclValue, CoercionError> {
1802        match self {
1803            KclValue::HomArray { value, ty: aty, .. } => {
1804                let satisfied_len = len.satisfied(value.len(), allow_shrink);
1805
1806                if aty.subtype(ty) {
1807                    // If the element type is a subtype of the target type and
1808                    // the length constraint is satisfied, we can just return
1809                    // the values unchanged, only adjusting the length. The new
1810                    // array element type should preserve its type because the
1811                    // target type oftentimes includes an unknown type as a way
1812                    // to say that the caller doesn't care.
1813                    return satisfied_len
1814                        .map(|len| KclValue::HomArray {
1815                            value: value[..len].to_vec(),
1816                            ty: aty.clone(),
1817                        })
1818                        .ok_or(self.into());
1819                }
1820
1821                // Ignore the array type, and coerce the elements of the array.
1822                if let Some(satisfied_len) = satisfied_len {
1823                    let value_result = value
1824                        .iter()
1825                        .take(satisfied_len)
1826                        .map(|v| v.coerce(ty, mode, exec_state))
1827                        .collect::<Result<Vec<_>, _>>();
1828
1829                    if let Ok(value) = value_result {
1830                        // We were able to coerce all the elements.
1831                        return Ok(KclValue::HomArray { value, ty: ty.clone() });
1832                    }
1833                }
1834
1835                // As a last resort, try to flatten the array.
1836                let mut values = Vec::new();
1837                for item in value {
1838                    if let KclValue::HomArray { value: inner_value, .. } = item {
1839                        // Flatten elements.
1840                        for item in inner_value {
1841                            values.push(item.coerce(ty, mode, exec_state)?);
1842                        }
1843                    } else {
1844                        values.push(item.coerce(ty, mode, exec_state)?);
1845                    }
1846                }
1847
1848                let len = len
1849                    .satisfied(values.len(), allow_shrink)
1850                    .ok_or(CoercionError::from(self))?;
1851
1852                if len > values.len() {
1853                    let message = format!(
1854                        "Internal: Expected coerced array length {len} to be less than or equal to original length {}",
1855                        values.len()
1856                    );
1857                    exec_state.err(CompilationIssue::err(self.into(), message.clone()));
1858                    #[cfg(debug_assertions)]
1859                    panic!("{message}");
1860                }
1861                values.truncate(len);
1862
1863                Ok(KclValue::HomArray {
1864                    value: values,
1865                    ty: ty.clone(),
1866                })
1867            }
1868            KclValue::Tuple { value, .. } => {
1869                let len = len
1870                    .satisfied(value.len(), allow_shrink)
1871                    .ok_or(CoercionError::from(self))?;
1872                let value = value
1873                    .iter()
1874                    .map(|item| item.coerce(ty, mode, exec_state))
1875                    .take(len)
1876                    .collect::<Result<Vec<_>, _>>()?;
1877
1878                Ok(KclValue::HomArray { value, ty: ty.clone() })
1879            }
1880            KclValue::KclNone { .. } if len.satisfied(0, false).is_some() => Ok(KclValue::HomArray {
1881                value: Vec::new(),
1882                ty: ty.clone(),
1883            }),
1884            _ if len.satisfied(1, false).is_some() => self.coerce(ty, mode, exec_state),
1885            _ => Err(self.into()),
1886        }
1887    }
1888
1889    fn coerce_to_tuple_type(
1890        &self,
1891        tys: &[RuntimeType],
1892        mode: CoercionMode,
1893        exec_state: &mut ExecState,
1894    ) -> Result<KclValue, CoercionError> {
1895        match self {
1896            KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } if value.len() == tys.len() => {
1897                let mut result = Vec::new();
1898                for (i, t) in tys.iter().enumerate() {
1899                    result.push(value[i].coerce(t, mode, exec_state)?);
1900                }
1901
1902                Ok(KclValue::Tuple {
1903                    value: result,
1904                    meta: Vec::new(),
1905                })
1906            }
1907            KclValue::KclNone { meta, .. } if tys.is_empty() => Ok(KclValue::Tuple {
1908                value: Vec::new(),
1909                meta: meta.clone(),
1910            }),
1911            _ if tys.len() == 1 => self.coerce(&tys[0], mode, exec_state),
1912            _ => Err(self.into()),
1913        }
1914    }
1915
1916    fn coerce_to_union_type(
1917        &self,
1918        tys: &[RuntimeType],
1919        mode: CoercionMode,
1920        exec_state: &mut ExecState,
1921    ) -> Result<KclValue, CoercionError> {
1922        // A member that accepts the value as it is must win over one that would
1923        // change it, whichever order the union was written in. Without this pass
1924        // `Color::Red: Color | string` would keep the enum while
1925        // `Color::Red: string | Color` would project it, making the meaning of a
1926        // union depend on how the author happened to spell it.
1927        if mode.project_enums() {
1928            let exact = mode.without_projection();
1929            for t in tys {
1930                if let Ok(v) = self.coerce(t, exact, exec_state) {
1931                    return Ok(v);
1932                }
1933            }
1934        }
1935
1936        for t in tys {
1937            if let Ok(v) = self.coerce(t, mode, exec_state) {
1938                return Ok(v);
1939            }
1940        }
1941
1942        Err(self.into())
1943    }
1944
1945    fn coerce_to_object_type(
1946        &self,
1947        tys: &[(String, RuntimeType)],
1948        constrainable: bool,
1949        _mode: CoercionMode,
1950        _exec_state: &mut ExecState,
1951    ) -> Result<KclValue, CoercionError> {
1952        match self {
1953            KclValue::Object { value, meta, .. } => {
1954                for (s, t) in tys {
1955                    // TODO coerce fields
1956                    if !value.get(s).ok_or(CoercionError::from(self))?.has_type(t) {
1957                        return Err(self.into());
1958                    }
1959                }
1960                // TODO remove non-required fields
1961                Ok(KclValue::Object {
1962                    value: value.clone(),
1963                    meta: meta.clone(),
1964                    // Note that we don't check for constrainability, coercing to a constrainable object
1965                    // adds that property.
1966                    constrainable,
1967                    object_kind: Default::default(),
1968                })
1969            }
1970            KclValue::KclNone { meta, .. } if tys.is_empty() => Ok(KclValue::Object {
1971                value: HashMap::new(),
1972                meta: meta.clone(),
1973                constrainable,
1974                object_kind: Default::default(),
1975            }),
1976            _ => Err(self.into()),
1977        }
1978    }
1979
1980    pub fn principal_type(&self) -> Option<RuntimeType> {
1981        match self {
1982            KclValue::Bool { .. } => Some(RuntimeType::Primitive(PrimitiveType::Boolean)),
1983            KclValue::Number { ty, .. } => Some(RuntimeType::Primitive(PrimitiveType::Number(*ty))),
1984            KclValue::String { .. } => Some(RuntimeType::Primitive(PrimitiveType::String)),
1985            KclValue::Enum { value } => Some(RuntimeType::Enum(value.enum_id().clone())),
1986            KclValue::SketchVar { value, .. } => Some(RuntimeType::Primitive(PrimitiveType::Number(value.ty))),
1987            KclValue::SketchConstraint { .. } => Some(RuntimeType::Primitive(PrimitiveType::Constraint)),
1988            KclValue::Object {
1989                value, constrainable, ..
1990            } => {
1991                let properties = value
1992                    .iter()
1993                    .map(|(k, v)| v.principal_type().map(|t| (k.clone(), t)))
1994                    .collect::<Option<Vec<_>>>()?;
1995                Some(RuntimeType::Object(properties, *constrainable))
1996            }
1997            KclValue::GdtAnnotation { .. } => Some(RuntimeType::Primitive(PrimitiveType::GdtAnnotation)),
1998            KclValue::CameraView { .. } => Some(RuntimeType::Primitive(PrimitiveType::CameraView)),
1999            KclValue::NamedView { .. } => Some(RuntimeType::Primitive(PrimitiveType::NamedView)),
2000            KclValue::Plane { .. } => Some(RuntimeType::Primitive(PrimitiveType::Plane)),
2001            KclValue::Sketch { .. } => Some(RuntimeType::Primitive(PrimitiveType::Sketch)),
2002            KclValue::Solid { .. } => Some(RuntimeType::Primitive(PrimitiveType::Solid)),
2003            KclValue::Face { .. } => Some(RuntimeType::Primitive(PrimitiveType::Face)),
2004            KclValue::Segment { .. } => Some(RuntimeType::Primitive(PrimitiveType::Segment)),
2005            KclValue::Helix { .. } => Some(RuntimeType::Primitive(PrimitiveType::Helix)),
2006            KclValue::ImportedGeometry(..) => Some(RuntimeType::Primitive(PrimitiveType::ImportedGeometry)),
2007            KclValue::Tuple { value, .. } => Some(RuntimeType::Tuple(
2008                value.iter().map(|v| v.principal_type()).collect::<Option<Vec<_>>>()?,
2009            )),
2010            KclValue::HomArray { ty, value, .. } => {
2011                Some(RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Known(value.len())))
2012            }
2013            KclValue::TagIdentifier(_) => Some(RuntimeType::Primitive(PrimitiveType::TaggedEdge)),
2014            KclValue::TagDeclarator(_) => Some(RuntimeType::Primitive(PrimitiveType::TagDecl)),
2015            KclValue::Uuid { .. } => Some(RuntimeType::Primitive(PrimitiveType::Edge)),
2016            KclValue::Function { .. } => Some(RuntimeType::Primitive(PrimitiveType::Function)),
2017            KclValue::KclNone { .. } => Some(RuntimeType::Primitive(PrimitiveType::None)),
2018            KclValue::Module { .. } | KclValue::Type { .. } => None,
2019            KclValue::BoundedEdge { .. } => Some(RuntimeType::Primitive(PrimitiveType::BoundedEdge)),
2020        }
2021    }
2022
2023    pub fn principal_type_string(&self) -> String {
2024        if let Some(ty) = self.principal_type() {
2025            return format!("`{ty}`");
2026        }
2027
2028        match self {
2029            KclValue::Module { .. } => "module",
2030            KclValue::KclNone { .. } => "none",
2031            KclValue::Type { .. } => "type",
2032            _ => {
2033                debug_assert!(false);
2034                "<unexpected type>"
2035            }
2036        }
2037        .to_owned()
2038    }
2039}
2040
2041#[cfg(test)]
2042mod test {
2043    use std::sync::Arc;
2044
2045    use super::*;
2046    use crate::ModuleId;
2047    use crate::execution::ExecTestResults;
2048    use crate::execution::kcl_value::EnumTypeDef;
2049    use crate::execution::kcl_value::EnumValue;
2050    use crate::execution::parse_execute;
2051
2052    async fn new_exec_state() -> (crate::ExecutorContext, ExecState) {
2053        let ctx = crate::ExecutorContext::new_mock(None).await;
2054        let exec_state = ExecState::new(&ctx);
2055        (ctx, exec_state)
2056    }
2057
2058    fn values(exec_state: &mut ExecState) -> Vec<KclValue> {
2059        vec![
2060            KclValue::Bool {
2061                value: true,
2062                meta: Vec::new(),
2063            },
2064            KclValue::Number {
2065                value: 1.0,
2066                ty: NumericType::count(),
2067                meta: Vec::new(),
2068            },
2069            KclValue::String {
2070                value: "hello".to_owned(),
2071                meta: Vec::new(),
2072            },
2073            KclValue::Tuple {
2074                value: Vec::new(),
2075                meta: Vec::new(),
2076            },
2077            KclValue::HomArray {
2078                value: Vec::new(),
2079                ty: RuntimeType::solid(),
2080            },
2081            KclValue::Object {
2082                value: crate::execution::KclObjectFields::new(),
2083                meta: Vec::new(),
2084                constrainable: false,
2085                object_kind: Default::default(),
2086            },
2087            KclValue::TagIdentifier(Box::new("foo".parse().unwrap())),
2088            KclValue::TagDeclarator(crate::parsing::ast::types::BoxNode::new(
2089                crate::parsing::ast::types::TagDeclarator::new("foo"),
2090            )),
2091            KclValue::Plane {
2092                value: Box::new(
2093                    Plane::from_plane_data_skipping_engine(crate::std::sketch::PlaneData::XY, exec_state).unwrap(),
2094                ),
2095            },
2096            // No easy way to make a Face, Sketch, Solid, or Helix
2097            KclValue::ImportedGeometry(crate::execution::ImportedGeometry::new(
2098                uuid::Uuid::nil(),
2099                Vec::new(),
2100                Vec::new(),
2101            )),
2102            // Other values don't have types
2103        ]
2104    }
2105
2106    #[track_caller]
2107    fn assert_coerce_results(
2108        value: &KclValue,
2109        super_type: &RuntimeType,
2110        expected_value: &KclValue,
2111        exec_state: &mut ExecState,
2112    ) {
2113        let is_subtype = value == expected_value;
2114        let actual = value.coerce(super_type, CoercionMode::implicit(), exec_state).unwrap();
2115        assert_eq!(&actual, expected_value);
2116        assert_eq!(
2117            is_subtype,
2118            value.principal_type().is_some() && value.principal_type().unwrap().subtype(super_type),
2119            "{:?} <: {super_type:?} should be {is_subtype}",
2120            value.principal_type().unwrap()
2121        );
2122        assert!(
2123            expected_value.principal_type().unwrap().subtype(super_type),
2124            "{} <: {super_type}",
2125            expected_value.principal_type().unwrap()
2126        )
2127    }
2128
2129    #[tokio::test(flavor = "multi_thread")]
2130    async fn coerce_idempotent() {
2131        let (ctx, mut exec_state) = new_exec_state().await;
2132        let values = values(&mut exec_state);
2133        for v in &values {
2134            // Identity subtype
2135            let ty = v.principal_type().unwrap();
2136            assert_coerce_results(v, &ty, v, &mut exec_state);
2137
2138            // Union subtype
2139            let uty1 = RuntimeType::Union(vec![ty.clone()]);
2140            let uty2 = RuntimeType::Union(vec![ty.clone(), RuntimeType::Primitive(PrimitiveType::Boolean)]);
2141            assert_coerce_results(v, &uty1, v, &mut exec_state);
2142            assert_coerce_results(v, &uty2, v, &mut exec_state);
2143
2144            // Array subtypes
2145            let aty = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::None);
2146            let aty1 = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Known(1));
2147            let aty0 = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Minimum(1));
2148
2149            match v {
2150                KclValue::HomArray { .. } => {
2151                    // These will not get wrapped if possible.
2152                    assert_coerce_results(
2153                        v,
2154                        &aty,
2155                        &KclValue::HomArray {
2156                            value: vec![],
2157                            ty: ty.clone(),
2158                        },
2159                        &mut exec_state,
2160                    );
2161                    // Coercing an empty array to an array of length 1
2162                    // should fail.
2163                    v.coerce(&aty1, CoercionMode::implicit(), &mut exec_state).unwrap_err();
2164                    // Coercing an empty array to an array that's
2165                    // non-empty should fail.
2166                    v.coerce(&aty0, CoercionMode::implicit(), &mut exec_state).unwrap_err();
2167                }
2168                KclValue::Tuple { .. } => {}
2169                _ => {
2170                    assert_coerce_results(v, &aty, v, &mut exec_state);
2171                    assert_coerce_results(v, &aty1, v, &mut exec_state);
2172                    assert_coerce_results(v, &aty0, v, &mut exec_state);
2173
2174                    // Tuple subtype
2175                    let tty = RuntimeType::Tuple(vec![ty.clone()]);
2176                    assert_coerce_results(v, &tty, v, &mut exec_state);
2177                }
2178            }
2179        }
2180
2181        for v in &values[1..] {
2182            // Not a subtype
2183            v.coerce(
2184                &RuntimeType::Primitive(PrimitiveType::Boolean),
2185                CoercionMode::implicit(),
2186                &mut exec_state,
2187            )
2188            .unwrap_err();
2189        }
2190        ctx.close().await;
2191    }
2192
2193    #[tokio::test(flavor = "multi_thread")]
2194    async fn coerce_none() {
2195        let (ctx, mut exec_state) = new_exec_state().await;
2196        let none = KclValue::KclNone {
2197            value: crate::parsing::ast::types::KclNone::new(),
2198            meta: Vec::new(),
2199        };
2200
2201        let aty = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::None);
2202        let aty0 = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::Known(0));
2203        let aty1 = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::Known(1));
2204        let aty1p = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::Minimum(1));
2205        assert_coerce_results(
2206            &none,
2207            &aty,
2208            &KclValue::HomArray {
2209                value: Vec::new(),
2210                ty: RuntimeType::solid(),
2211            },
2212            &mut exec_state,
2213        );
2214        assert_coerce_results(
2215            &none,
2216            &aty0,
2217            &KclValue::HomArray {
2218                value: Vec::new(),
2219                ty: RuntimeType::solid(),
2220            },
2221            &mut exec_state,
2222        );
2223        none.coerce(&aty1, CoercionMode::implicit(), &mut exec_state)
2224            .unwrap_err();
2225        none.coerce(&aty1p, CoercionMode::implicit(), &mut exec_state)
2226            .unwrap_err();
2227
2228        let tty = RuntimeType::Tuple(vec![]);
2229        let tty1 = RuntimeType::Tuple(vec![RuntimeType::solid()]);
2230        assert_coerce_results(
2231            &none,
2232            &tty,
2233            &KclValue::Tuple {
2234                value: Vec::new(),
2235                meta: Vec::new(),
2236            },
2237            &mut exec_state,
2238        );
2239        none.coerce(&tty1, CoercionMode::implicit(), &mut exec_state)
2240            .unwrap_err();
2241
2242        let oty = RuntimeType::Object(vec![], false);
2243        assert_coerce_results(
2244            &none,
2245            &oty,
2246            &KclValue::Object {
2247                value: HashMap::new(),
2248                meta: Vec::new(),
2249                constrainable: false,
2250                object_kind: Default::default(),
2251            },
2252            &mut exec_state,
2253        );
2254        ctx.close().await;
2255    }
2256
2257    #[tokio::test(flavor = "multi_thread")]
2258    async fn coerce_record() {
2259        let (ctx, mut exec_state) = new_exec_state().await;
2260
2261        let obj0 = KclValue::Object {
2262            value: HashMap::new(),
2263            meta: Vec::new(),
2264            constrainable: false,
2265            object_kind: Default::default(),
2266        };
2267        let obj1 = KclValue::Object {
2268            value: [(
2269                "foo".to_owned(),
2270                KclValue::Bool {
2271                    value: true,
2272                    meta: Vec::new(),
2273                },
2274            )]
2275            .into(),
2276            meta: Vec::new(),
2277            constrainable: false,
2278            object_kind: Default::default(),
2279        };
2280        let obj2 = KclValue::Object {
2281            value: [
2282                (
2283                    "foo".to_owned(),
2284                    KclValue::Bool {
2285                        value: true,
2286                        meta: Vec::new(),
2287                    },
2288                ),
2289                (
2290                    "bar".to_owned(),
2291                    KclValue::Number {
2292                        value: 0.0,
2293                        ty: NumericType::count(),
2294                        meta: Vec::new(),
2295                    },
2296                ),
2297                (
2298                    "baz".to_owned(),
2299                    KclValue::Number {
2300                        value: 42.0,
2301                        ty: NumericType::count(),
2302                        meta: Vec::new(),
2303                    },
2304                ),
2305            ]
2306            .into(),
2307            meta: Vec::new(),
2308            constrainable: false,
2309            object_kind: Default::default(),
2310        };
2311
2312        let ty0 = RuntimeType::Object(vec![], false);
2313        assert_coerce_results(&obj0, &ty0, &obj0, &mut exec_state);
2314        assert_coerce_results(&obj1, &ty0, &obj1, &mut exec_state);
2315        assert_coerce_results(&obj2, &ty0, &obj2, &mut exec_state);
2316
2317        let ty1 = RuntimeType::Object(
2318            vec![("foo".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean))],
2319            false,
2320        );
2321        obj0.coerce(&ty1, CoercionMode::implicit(), &mut exec_state)
2322            .unwrap_err();
2323        assert_coerce_results(&obj1, &ty1, &obj1, &mut exec_state);
2324        assert_coerce_results(&obj2, &ty1, &obj2, &mut exec_state);
2325
2326        // Different ordering, (TODO - test for covariance once implemented)
2327        let ty2 = RuntimeType::Object(
2328            vec![
2329                (
2330                    "bar".to_owned(),
2331                    RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2332                ),
2333                ("foo".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean)),
2334            ],
2335            false,
2336        );
2337        obj0.coerce(&ty2, CoercionMode::implicit(), &mut exec_state)
2338            .unwrap_err();
2339        obj1.coerce(&ty2, CoercionMode::implicit(), &mut exec_state)
2340            .unwrap_err();
2341        assert_coerce_results(&obj2, &ty2, &obj2, &mut exec_state);
2342
2343        // field not present
2344        let tyq = RuntimeType::Object(
2345            vec![("qux".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean))],
2346            false,
2347        );
2348        obj0.coerce(&tyq, CoercionMode::implicit(), &mut exec_state)
2349            .unwrap_err();
2350        obj1.coerce(&tyq, CoercionMode::implicit(), &mut exec_state)
2351            .unwrap_err();
2352        obj2.coerce(&tyq, CoercionMode::implicit(), &mut exec_state)
2353            .unwrap_err();
2354
2355        // field with different type
2356        let ty1 = RuntimeType::Object(
2357            vec![("bar".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean))],
2358            false,
2359        );
2360        obj2.coerce(&ty1, CoercionMode::implicit(), &mut exec_state)
2361            .unwrap_err();
2362        ctx.close().await;
2363    }
2364
2365    #[tokio::test(flavor = "multi_thread")]
2366    async fn coerce_array() {
2367        let (ctx, mut exec_state) = new_exec_state().await;
2368
2369        let hom_arr = KclValue::HomArray {
2370            value: vec![
2371                KclValue::Number {
2372                    value: 0.0,
2373                    ty: NumericType::count(),
2374                    meta: Vec::new(),
2375                },
2376                KclValue::Number {
2377                    value: 1.0,
2378                    ty: NumericType::count(),
2379                    meta: Vec::new(),
2380                },
2381                KclValue::Number {
2382                    value: 2.0,
2383                    ty: NumericType::count(),
2384                    meta: Vec::new(),
2385                },
2386                KclValue::Number {
2387                    value: 3.0,
2388                    ty: NumericType::count(),
2389                    meta: Vec::new(),
2390                },
2391            ],
2392            ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2393        };
2394        let mixed1 = KclValue::Tuple {
2395            value: vec![
2396                KclValue::Number {
2397                    value: 0.0,
2398                    ty: NumericType::count(),
2399                    meta: Vec::new(),
2400                },
2401                KclValue::Number {
2402                    value: 1.0,
2403                    ty: NumericType::count(),
2404                    meta: Vec::new(),
2405                },
2406            ],
2407            meta: Vec::new(),
2408        };
2409        let mixed2 = KclValue::Tuple {
2410            value: vec![
2411                KclValue::Number {
2412                    value: 0.0,
2413                    ty: NumericType::count(),
2414                    meta: Vec::new(),
2415                },
2416                KclValue::Bool {
2417                    value: true,
2418                    meta: Vec::new(),
2419                },
2420            ],
2421            meta: Vec::new(),
2422        };
2423
2424        // Principal types
2425        let tyh = RuntimeType::Array(
2426            Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2427            ArrayLen::Known(4),
2428        );
2429        let tym1 = RuntimeType::Tuple(vec![
2430            RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2431            RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2432        ]);
2433        let tym2 = RuntimeType::Tuple(vec![
2434            RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2435            RuntimeType::Primitive(PrimitiveType::Boolean),
2436        ]);
2437        assert_coerce_results(&hom_arr, &tyh, &hom_arr, &mut exec_state);
2438        assert_coerce_results(&mixed1, &tym1, &mixed1, &mut exec_state);
2439        assert_coerce_results(&mixed2, &tym2, &mixed2, &mut exec_state);
2440        mixed1
2441            .coerce(&tym2, CoercionMode::implicit(), &mut exec_state)
2442            .unwrap_err();
2443        mixed2
2444            .coerce(&tym1, CoercionMode::implicit(), &mut exec_state)
2445            .unwrap_err();
2446
2447        // Length subtyping
2448        let tyhn = RuntimeType::Array(
2449            Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2450            ArrayLen::None,
2451        );
2452        let tyh1 = RuntimeType::Array(
2453            Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2454            ArrayLen::Minimum(1),
2455        );
2456        let tyh3 = RuntimeType::Array(
2457            Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2458            ArrayLen::Known(3),
2459        );
2460        let tyhm3 = RuntimeType::Array(
2461            Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2462            ArrayLen::Minimum(3),
2463        );
2464        let tyhm5 = RuntimeType::Array(
2465            Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2466            ArrayLen::Minimum(5),
2467        );
2468        assert_coerce_results(&hom_arr, &tyhn, &hom_arr, &mut exec_state);
2469        assert_coerce_results(&hom_arr, &tyh1, &hom_arr, &mut exec_state);
2470        hom_arr
2471            .coerce(&tyh3, CoercionMode::implicit(), &mut exec_state)
2472            .unwrap_err();
2473        assert_coerce_results(&hom_arr, &tyhm3, &hom_arr, &mut exec_state);
2474        hom_arr
2475            .coerce(&tyhm5, CoercionMode::implicit(), &mut exec_state)
2476            .unwrap_err();
2477
2478        let hom_arr0 = KclValue::HomArray {
2479            value: vec![],
2480            ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2481        };
2482        assert_coerce_results(&hom_arr0, &tyhn, &hom_arr0, &mut exec_state);
2483        hom_arr0
2484            .coerce(&tyh1, CoercionMode::implicit(), &mut exec_state)
2485            .unwrap_err();
2486        hom_arr0
2487            .coerce(&tyh3, CoercionMode::implicit(), &mut exec_state)
2488            .unwrap_err();
2489
2490        // Covariance
2491        // let tyh = RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any))), ArrayLen::Known(4));
2492        let tym1 = RuntimeType::Tuple(vec![
2493            RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2494            RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2495        ]);
2496        let tym2 = RuntimeType::Tuple(vec![
2497            RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2498            RuntimeType::Primitive(PrimitiveType::Boolean),
2499        ]);
2500        // TODO implement covariance for homogeneous arrays
2501        // assert_coerce_results(&hom_arr, &tyh, &hom_arr, &mut exec_state);
2502        assert_coerce_results(&mixed1, &tym1, &mixed1, &mut exec_state);
2503        assert_coerce_results(&mixed2, &tym2, &mixed2, &mut exec_state);
2504
2505        // Mixed to homogeneous
2506        let hom_arr_2 = KclValue::HomArray {
2507            value: vec![
2508                KclValue::Number {
2509                    value: 0.0,
2510                    ty: NumericType::count(),
2511                    meta: Vec::new(),
2512                },
2513                KclValue::Number {
2514                    value: 1.0,
2515                    ty: NumericType::count(),
2516                    meta: Vec::new(),
2517                },
2518            ],
2519            ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2520        };
2521        let mixed0 = KclValue::Tuple {
2522            value: vec![],
2523            meta: Vec::new(),
2524        };
2525        assert_coerce_results(&mixed1, &tyhn, &hom_arr_2, &mut exec_state);
2526        assert_coerce_results(&mixed1, &tyh1, &hom_arr_2, &mut exec_state);
2527        assert_coerce_results(&mixed0, &tyhn, &hom_arr0, &mut exec_state);
2528        mixed0
2529            .coerce(&tyh, CoercionMode::implicit(), &mut exec_state)
2530            .unwrap_err();
2531        mixed0
2532            .coerce(&tyh1, CoercionMode::implicit(), &mut exec_state)
2533            .unwrap_err();
2534
2535        // Homogehous to mixed
2536        assert_coerce_results(&hom_arr_2, &tym1, &mixed1, &mut exec_state);
2537        hom_arr
2538            .coerce(&tym1, CoercionMode::implicit(), &mut exec_state)
2539            .unwrap_err();
2540        hom_arr_2
2541            .coerce(&tym2, CoercionMode::implicit(), &mut exec_state)
2542            .unwrap_err();
2543
2544        mixed0
2545            .coerce(&tym1, CoercionMode::implicit(), &mut exec_state)
2546            .unwrap_err();
2547        mixed0
2548            .coerce(&tym2, CoercionMode::implicit(), &mut exec_state)
2549            .unwrap_err();
2550        ctx.close().await;
2551    }
2552
2553    #[tokio::test(flavor = "multi_thread")]
2554    async fn coerce_union() {
2555        let (ctx, mut exec_state) = new_exec_state().await;
2556
2557        // Subtyping smaller unions
2558        assert!(RuntimeType::Union(vec![]).subtype(&RuntimeType::Union(vec![
2559            RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2560            RuntimeType::Primitive(PrimitiveType::Boolean)
2561        ])));
2562        assert!(
2563            RuntimeType::Union(vec![RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any))]).subtype(
2564                &RuntimeType::Union(vec![
2565                    RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2566                    RuntimeType::Primitive(PrimitiveType::Boolean)
2567                ])
2568            )
2569        );
2570        assert!(
2571            RuntimeType::Union(vec![
2572                RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2573                RuntimeType::Primitive(PrimitiveType::Boolean)
2574            ])
2575            .subtype(&RuntimeType::Union(vec![
2576                RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2577                RuntimeType::Primitive(PrimitiveType::Boolean)
2578            ]))
2579        );
2580
2581        // Covariance
2582        let count = KclValue::Number {
2583            value: 1.0,
2584            ty: NumericType::count(),
2585            meta: Vec::new(),
2586        };
2587
2588        let tya = RuntimeType::Union(vec![RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any))]);
2589        let tya2 = RuntimeType::Union(vec![
2590            RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2591            RuntimeType::Primitive(PrimitiveType::Boolean),
2592        ]);
2593        assert_coerce_results(&count, &tya, &count, &mut exec_state);
2594        assert_coerce_results(&count, &tya2, &count, &mut exec_state);
2595
2596        // No matching type
2597        let tyb = RuntimeType::Union(vec![RuntimeType::Primitive(PrimitiveType::Boolean)]);
2598        let tyb2 = RuntimeType::Union(vec![
2599            RuntimeType::Primitive(PrimitiveType::Boolean),
2600            RuntimeType::Primitive(PrimitiveType::String),
2601        ]);
2602        count
2603            .coerce(&tyb, CoercionMode::implicit(), &mut exec_state)
2604            .unwrap_err();
2605        count
2606            .coerce(&tyb2, CoercionMode::implicit(), &mut exec_state)
2607            .unwrap_err();
2608        ctx.close().await;
2609    }
2610
2611    #[test]
2612    fn union_subtyping_uses_member_subtyping() {
2613        let tagged_edge = RuntimeType::Primitive(PrimitiveType::TaggedEdge);
2614        let edge = RuntimeType::Primitive(PrimitiveType::Edge);
2615        let string = RuntimeType::string();
2616        let boolean = RuntimeType::bool();
2617
2618        let tagged_edge_or_string = RuntimeType::Union(vec![tagged_edge.clone(), string.clone()]);
2619        let edge_or_string = RuntimeType::Union(vec![edge.clone(), string.clone()]);
2620
2621        // TaggedEdge | string <: Edge | string
2622        assert!(tagged_edge_or_string.subtype(&edge_or_string));
2623        // Edge | string is not a subtype of TaggedEdge | string.
2624        assert!(!edge_or_string.subtype(&tagged_edge_or_string));
2625
2626        // TaggedEdge | Edge <: Edge
2627        assert!(RuntimeType::Union(vec![tagged_edge.clone(), edge.clone()]).subtype(&edge));
2628        // TaggedEdge | bool is not a subtype of Edge.
2629        assert!(!RuntimeType::Union(vec![tagged_edge, boolean]).subtype(&edge));
2630
2631        // The empty union is a subtype of string.
2632        assert!(RuntimeType::Union(vec![]).subtype(&string));
2633    }
2634
2635    #[test]
2636    fn nested_union_subtyping_is_associative_and_recursive() {
2637        let tagged_edge = RuntimeType::Primitive(PrimitiveType::TaggedEdge);
2638        let edge = RuntimeType::Primitive(PrimitiveType::Edge);
2639        let string = RuntimeType::string();
2640        let boolean = RuntimeType::bool();
2641
2642        let left_associative = RuntimeType::Union(vec![
2643            RuntimeType::Union(vec![string.clone(), boolean.clone()]),
2644            edge.clone(),
2645        ]);
2646        let right_associative =
2647            RuntimeType::Union(vec![string, RuntimeType::Union(vec![boolean.clone(), edge.clone()])]);
2648
2649        // (string | bool) | Edge <: string | (bool | Edge)
2650        assert!(left_associative.subtype(&right_associative));
2651        // string | (bool | Edge) <: (string | bool) | Edge
2652        assert!(right_associative.subtype(&left_associative));
2653
2654        let nested_edges = RuntimeType::Union(vec![
2655            RuntimeType::Union(vec![tagged_edge.clone(), edge.clone()]),
2656            tagged_edge.clone(),
2657        ]);
2658        // (TaggedEdge | Edge) | TaggedEdge <: Edge
2659        assert!(nested_edges.subtype(&edge));
2660
2661        let nested_with_bool = RuntimeType::Union(vec![RuntimeType::Union(vec![tagged_edge, boolean]), edge.clone()]);
2662        // (TaggedEdge | bool) | Edge is not a subtype of Edge.
2663        assert!(!nested_with_bool.subtype(&edge));
2664    }
2665
2666    fn enum_ty(module_id: u32, name: &str) -> RuntimeType {
2667        RuntimeType::Enum(EnumTypeId::new(ModuleId::from_usize(module_id as usize), name))
2668    }
2669
2670    /// A value holds its declaration, so building one by hand needs a
2671    /// declaration rather than just an id.
2672    fn enum_def(module_id: u32, name: &str, variants: &[&str]) -> Arc<EnumTypeDef> {
2673        Arc::new(
2674            EnumTypeDef::new(
2675                EnumTypeId::new(ModuleId::from_usize(module_id as usize), name),
2676                variants.iter().map(|v| (*v).to_owned()).collect(),
2677                false,
2678            )
2679            .unwrap(),
2680        )
2681    }
2682
2683    #[test]
2684    fn enum_subtyping_is_nominal() {
2685        let color = enum_ty(0, "Color");
2686        let shape = enum_ty(0, "Shape");
2687
2688        // An enum is a subtype of itself. Without a dedicated arm the catch-all
2689        // in `subtype` would answer false here and break reflexivity.
2690        assert!(color.subtype(&color));
2691        // Distinct declarations are unrelated, in both directions.
2692        assert!(!color.subtype(&shape));
2693        assert!(!shape.subtype(&color));
2694    }
2695
2696    #[test]
2697    fn enum_identity_is_module_plus_declared_name() {
2698        // Same declared name in two modules is two different types.
2699        assert!(!enum_ty(0, "Color").subtype(&enum_ty(1, "Color")));
2700        // Same declaration reached from anywhere is one type; an import alias
2701        // renames the binding, never the identity recorded here.
2702        assert!(enum_ty(1, "Color").subtype(&enum_ty(1, "Color")));
2703    }
2704
2705    #[test]
2706    fn enum_participates_in_the_general_type_rules() {
2707        let color = enum_ty(0, "Color");
2708
2709        // `any` and `never` keep their universal behaviour.
2710        assert!(color.subtype(&RuntimeType::any()));
2711        assert!(RuntimeType::never().subtype(&color));
2712        assert!(!color.subtype(&RuntimeType::never()));
2713
2714        // Unions and the singleton/array equivalences reach the enum arm by
2715        // recursion, so they work without enum-specific code.
2716        assert!(color.subtype(&RuntimeType::Union(vec![color.clone(), RuntimeType::string()])));
2717        assert!(!color.subtype(&RuntimeType::Union(vec![RuntimeType::string(), enum_ty(0, "Shape")])));
2718        assert!(color.subtype(&RuntimeType::Array(Box::new(color.clone()), ArrayLen::Known(1))));
2719        assert!(RuntimeType::Array(Box::new(color.clone()), ArrayLen::Known(1)).subtype(&color));
2720
2721        // An enum is unrelated to the primitives it could later project to.
2722        assert!(!color.subtype(&RuntimeType::string()));
2723        assert!(!RuntimeType::string().subtype(&color));
2724    }
2725
2726    #[test]
2727    fn enum_values_report_their_own_type() {
2728        let red = KclValue::Enum {
2729            value: Box::new(EnumValue::new(enum_def(0, "Color", &["Red"]), "Red", Vec::new())),
2730        };
2731
2732        assert_eq!(red.principal_type(), Some(enum_ty(0, "Color")));
2733        assert!(red.has_type(&enum_ty(0, "Color")));
2734        // Nominal identity, not the variant name, decides the type.
2735        assert!(!red.has_type(&enum_ty(0, "Shape")));
2736        assert!(!red.has_type(&RuntimeType::string()));
2737    }
2738
2739    #[test]
2740    fn enum_types_display_by_declared_name() {
2741        let color = enum_ty(0, "Color");
2742
2743        assert_eq!(color.to_string(), "Color");
2744        assert_eq!(color.human_friendly_type(), "Color");
2745        assert_eq!(
2746            RuntimeType::Array(Box::new(color), ArrayLen::Minimum(1)).human_friendly_type(),
2747            "one or more `Color` values"
2748        );
2749    }
2750
2751    /// The seam Gate 4 will build on: a registered enum declaration resolves to
2752    /// its nominal runtime type when named in a type position.
2753    #[tokio::test(flavor = "multi_thread")]
2754    async fn from_alias_resolves_a_declared_enum_to_its_nominal_type() {
2755        // Gate 4 registers enums during execution; until then, bind one by hand
2756        // into a real environment to exercise the resolution path.
2757        let result = parse_execute("x = 1").await.unwrap();
2758        let ctx = result.exec_ctxt;
2759        let mut exec_state = result.exec_state;
2760        let id = EnumTypeId::new(ModuleId::default(), "Color");
2761        let source_range = SourceRange::default();
2762
2763        // Execution has finished, so there is no current environment to bind into.
2764        exec_state.mut_stack().push_new_root_env(true).unwrap();
2765        exec_state
2766            .mut_stack()
2767            .add(
2768                format!("{}Color", memory::TYPE_PREFIX),
2769                KclValue::Type {
2770                    value: TypeDef::Enum(Arc::new(
2771                        EnumTypeDef::new(id.clone(), vec!["Red".to_owned()], false).unwrap(),
2772                    )),
2773                    experimental: false,
2774                    meta: vec![],
2775                },
2776                source_range,
2777            )
2778            .unwrap();
2779
2780        assert_eq!(
2781            RuntimeType::from_alias(&Name::new("Color"), &mut exec_state, &ctx, source_range, false)
2782                .await
2783                .unwrap(),
2784            RuntimeType::Enum(id)
2785        );
2786        // An unregistered name is still an unknown type, not a silent enum.
2787        RuntimeType::from_alias(&Name::new("Shape"), &mut exec_state, &ctx, source_range, false)
2788            .await
2789            .unwrap_err();
2790    }
2791
2792    #[tokio::test(flavor = "multi_thread")]
2793    async fn enum_coercion_requires_the_same_declaration() {
2794        let (ctx, mut exec_state) = new_exec_state().await;
2795        let red = KclValue::Enum {
2796            value: Box::new(EnumValue::new(enum_def(0, "Color", &["Red"]), "Red", Vec::new())),
2797        };
2798
2799        // Coercing to its own type is identity-preserving.
2800        assert_eq!(
2801            red.coerce(&enum_ty(0, "Color"), CoercionMode::implicit(), &mut exec_state)
2802                .unwrap(),
2803            red
2804        );
2805        // Everything else is rejected, including projection to string, which is
2806        // explicit ascription rather than coercion.
2807        red.coerce(&enum_ty(0, "Shape"), CoercionMode::implicit(), &mut exec_state)
2808            .unwrap_err();
2809        red.coerce(&enum_ty(1, "Color"), CoercionMode::implicit(), &mut exec_state)
2810            .unwrap_err();
2811        red.coerce(&RuntimeType::string(), CoercionMode::implicit(), &mut exec_state)
2812            .unwrap_err();
2813        // A non-enum value never satisfies an enum type.
2814        let string = KclValue::String {
2815            value: "Red".to_owned(),
2816            meta: Vec::new(),
2817        };
2818        string
2819            .coerce(&enum_ty(0, "Color"), CoercionMode::implicit(), &mut exec_state)
2820            .unwrap_err();
2821
2822        ctx.close().await;
2823    }
2824
2825    fn enum_value(module_id: u32, name: &str, variants: &[&str], variant: &str) -> KclValue {
2826        KclValue::Enum {
2827            value: Box::new(EnumValue::new(enum_def(module_id, name, variants), variant, Vec::new())),
2828        }
2829    }
2830
2831    fn string_value(value: &str) -> KclValue {
2832        KclValue::String {
2833            value: value.to_owned(),
2834            meta: Vec::new(),
2835        }
2836    }
2837
2838    /// Every row states the outcome under BOTH modes, so the table pins the whole
2839    /// matrix of target shape against mode rather than one half of it. `None`
2840    /// means the coercion must fail.
2841    ///
2842    /// The pattern to read off it: projection happens wherever the type walk
2843    /// reaches, and only when the user wrote the type.
2844    #[tokio::test(flavor = "multi_thread")]
2845    async fn enum_projects_by_target_shape() {
2846        let (ctx, mut exec_state) = new_exec_state().await;
2847        let variants = &["Red", "Green"];
2848        let red = enum_value(0, "Color", variants, "Red");
2849        let green = enum_value(0, "Color", variants, "Green");
2850        let color = enum_ty(0, "Color");
2851        let string = RuntimeType::string();
2852        let strings = RuntimeType::Array(Box::new(string.clone()), ArrayLen::None);
2853        let array = |value: Vec<KclValue>, ty: RuntimeType| KclValue::HomArray { value, ty };
2854        let tuple = |value: Vec<KclValue>| KclValue::Tuple {
2855            value,
2856            meta: Vec::new(),
2857        };
2858
2859        #[allow(clippy::type_complexity)]
2860        let rows: Vec<(&str, KclValue, RuntimeType, Option<KclValue>, Option<KclValue>)> = vec![
2861            (
2862                "a bare enum",
2863                red.clone(),
2864                string.clone(),
2865                Some(string_value("Red")),
2866                None,
2867            ),
2868            (
2869                "an array, element by element",
2870                array(vec![red.clone(), green.clone()], color.clone()),
2871                strings.clone(),
2872                Some(array(vec![string_value("Red"), string_value("Green")], string.clone())),
2873                None,
2874            ),
2875            (
2876                "an array of arrays, so more than one level down",
2877                array(vec![array(vec![green.clone()], RuntimeType::any())], RuntimeType::any()),
2878                RuntimeType::Array(Box::new(strings.clone()), ArrayLen::None),
2879                Some(array(
2880                    vec![array(vec![string_value("Green")], string.clone())],
2881                    strings.clone(),
2882                )),
2883                None,
2884            ),
2885            (
2886                // KCL has no tuple type syntax, so this shape is only reachable here.
2887                "a tuple, positionally, beside a value that needs nothing done",
2888                tuple(vec![red.clone(), string_value("plain")]),
2889                RuntimeType::Tuple(vec![string.clone(), string.clone()]),
2890                Some(tuple(vec![string_value("Red"), string_value("plain")])),
2891                None,
2892            ),
2893            (
2894                // The existing singleton/array equivalence carries projection with
2895                // it, the same way it carries numeric coercion.
2896                "a one-element array against a bare string",
2897                array(vec![red.clone()], RuntimeType::any()),
2898                string.clone(),
2899                Some(string_value("Red")),
2900                None,
2901            ),
2902            (
2903                // Object coercion checks fields with `has_type` and converts
2904                // nothing: see the `TODO coerce fields` in `coerce_to_object_type`.
2905                // Inherited behavior rather than an enum rule, so when field
2906                // coercion is implemented this row should start expecting a
2907                // projection instead of being deleted.
2908                "an object field, which projects nothing",
2909                KclValue::Object {
2910                    value: HashMap::from([("c".to_owned(), red.clone())]),
2911                    constrainable: false,
2912                    object_kind: Default::default(),
2913                    meta: Vec::new(),
2914                },
2915                RuntimeType::Object(vec![("c".to_owned(), string.clone())], false),
2916                None,
2917                None,
2918            ),
2919            (
2920                "its own type, which is a check rather than a conversion",
2921                red.clone(),
2922                color.clone(),
2923                Some(red.clone()),
2924                Some(red.clone()),
2925            ),
2926            (
2927                "another declaration, which projection is not a way around",
2928                red.clone(),
2929                enum_ty(0, "Shade"),
2930                None,
2931                None,
2932            ),
2933        ];
2934
2935        for (case, value, target, explicit, implicit) in rows {
2936            assert_eq!(
2937                value.coerce(&target, CoercionMode::explicit(), &mut exec_state).ok(),
2938                explicit,
2939                "explicit mode, case: {case}"
2940            );
2941            assert_eq!(
2942                value.coerce(&target, CoercionMode::implicit(), &mut exec_state).ok(),
2943                implicit,
2944                "implicit mode, case: {case}"
2945            );
2946        }
2947
2948        ctx.close().await;
2949    }
2950
2951    /// A member that accepts the value unchanged wins over one that would change
2952    /// it, whichever order the union was written in. Each pair of rows below is
2953    /// the same union spelled both ways, so a rule that depended on order would
2954    /// fail one row of the pair.
2955    #[tokio::test(flavor = "multi_thread")]
2956    async fn enum_projection_ignores_the_order_a_union_was_written_in() {
2957        let (ctx, mut exec_state) = new_exec_state().await;
2958        let red = enum_value(0, "Color", &["Red"], "Red");
2959        let string = RuntimeType::string();
2960        let color = enum_ty(0, "Color");
2961        let shade = enum_ty(0, "Shade");
2962
2963        let rows: Vec<(&str, Vec<RuntimeType>, Option<KclValue>)> = vec![
2964            ("the enum first", vec![color.clone(), string.clone()], Some(red.clone())),
2965            ("the enum last", vec![string.clone(), color.clone()], Some(red.clone())),
2966            (
2967                "no member accepts an enum, so projection is what satisfies it",
2968                vec![RuntimeType::bool(), string.clone()],
2969                Some(string_value("Red")),
2970            ),
2971            (
2972                "a different enum is not a match, so this projects too",
2973                vec![shade.clone(), string.clone()],
2974                Some(string_value("Red")),
2975            ),
2976            (
2977                "a different enum with no string member is unsatisfiable",
2978                vec![shade, RuntimeType::bool()],
2979                None,
2980            ),
2981        ];
2982
2983        for (case, tys, expected) in rows {
2984            let union = RuntimeType::Union(tys);
2985            assert_eq!(
2986                red.coerce(&union, CoercionMode::explicit(), &mut exec_state).ok(),
2987                expected,
2988                "case: {case} ({union})"
2989            );
2990        }
2991
2992        ctx.close().await;
2993    }
2994
2995    /// The numeric target reports what the user asked for and cannot have; the
2996    /// implicit boundary keeps the numeric wording, because nobody asked for a
2997    /// projection there.
2998    #[tokio::test(flavor = "multi_thread")]
2999    async fn enum_projection_to_a_number_explains_itself() {
3000        let (ctx, mut exec_state) = new_exec_state().await;
3001        let red = enum_value(0, "Color", &["Red"], "Red");
3002        let message = "Cannot project enum `Color` to a number. An enum projects to `string`; projecting to a number is not supported yet.";
3003
3004        for (case, mode, expected) in [
3005            ("explicit", CoercionMode::explicit(), Some(message)),
3006            ("implicit", CoercionMode::implicit(), None),
3007        ] {
3008            let err = red.coerce(&RuntimeType::count(), mode, &mut exec_state).unwrap_err();
3009            assert_eq!(err.message.as_deref(), expected, "case: {case}");
3010        }
3011
3012        ctx.close().await;
3013    }
3014
3015    #[tokio::test(flavor = "multi_thread")]
3016    async fn never_is_bottom_and_uninhabited() {
3017        let (ctx, mut exec_state) = new_exec_state().await;
3018        let never = RuntimeType::never();
3019        let string = RuntimeType::string();
3020
3021        for ty in [
3022            RuntimeType::any(),
3023            string.clone(),
3024            RuntimeType::Array(Box::new(string.clone()), ArrayLen::None),
3025            RuntimeType::Tuple(vec![string.clone()]),
3026            RuntimeType::Object(vec![("value".to_owned(), string.clone())], false),
3027            RuntimeType::Union(vec![string.clone(), RuntimeType::bool()]),
3028        ] {
3029            assert!(never.subtype(&ty), "`never` should be a subtype of {ty}");
3030        }
3031
3032        assert!(!string.subtype(&never));
3033        assert!(RuntimeType::Union(vec![never.clone(), string.clone()]).subtype(&string));
3034
3035        for value in values(&mut exec_state) {
3036            value
3037                .coerce(&never, CoercionMode::implicit(), &mut exec_state)
3038                .unwrap_err();
3039        }
3040        ctx.close().await;
3041    }
3042
3043    #[tokio::test(flavor = "multi_thread")]
3044    async fn coerce_axes() {
3045        let (ctx, mut exec_state) = new_exec_state().await;
3046
3047        // Subtyping
3048        assert!(RuntimeType::Primitive(PrimitiveType::Axis2d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis2d)));
3049        assert!(RuntimeType::Primitive(PrimitiveType::Axis3d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis3d)));
3050        assert!(!RuntimeType::Primitive(PrimitiveType::Axis3d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis2d)));
3051        assert!(!RuntimeType::Primitive(PrimitiveType::Axis2d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis3d)));
3052
3053        // Coercion
3054        let a2d = KclValue::Object {
3055            value: [
3056                (
3057                    "origin".to_owned(),
3058                    KclValue::HomArray {
3059                        value: vec![
3060                            KclValue::Number {
3061                                value: 0.0,
3062                                ty: NumericType::mm(),
3063                                meta: Vec::new(),
3064                            },
3065                            KclValue::Number {
3066                                value: 0.0,
3067                                ty: NumericType::mm(),
3068                                meta: Vec::new(),
3069                            },
3070                        ],
3071                        ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
3072                    },
3073                ),
3074                (
3075                    "direction".to_owned(),
3076                    KclValue::HomArray {
3077                        value: vec![
3078                            KclValue::Number {
3079                                value: 1.0,
3080                                ty: NumericType::mm(),
3081                                meta: Vec::new(),
3082                            },
3083                            KclValue::Number {
3084                                value: 0.0,
3085                                ty: NumericType::mm(),
3086                                meta: Vec::new(),
3087                            },
3088                        ],
3089                        ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
3090                    },
3091                ),
3092            ]
3093            .into(),
3094            meta: Vec::new(),
3095            constrainable: false,
3096            object_kind: Default::default(),
3097        };
3098        let a3d = KclValue::Object {
3099            value: [
3100                (
3101                    "origin".to_owned(),
3102                    KclValue::HomArray {
3103                        value: vec![
3104                            KclValue::Number {
3105                                value: 0.0,
3106                                ty: NumericType::mm(),
3107                                meta: Vec::new(),
3108                            },
3109                            KclValue::Number {
3110                                value: 0.0,
3111                                ty: NumericType::mm(),
3112                                meta: Vec::new(),
3113                            },
3114                            KclValue::Number {
3115                                value: 0.0,
3116                                ty: NumericType::mm(),
3117                                meta: Vec::new(),
3118                            },
3119                        ],
3120                        ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
3121                    },
3122                ),
3123                (
3124                    "direction".to_owned(),
3125                    KclValue::HomArray {
3126                        value: vec![
3127                            KclValue::Number {
3128                                value: 1.0,
3129                                ty: NumericType::mm(),
3130                                meta: Vec::new(),
3131                            },
3132                            KclValue::Number {
3133                                value: 0.0,
3134                                ty: NumericType::mm(),
3135                                meta: Vec::new(),
3136                            },
3137                            KclValue::Number {
3138                                value: 1.0,
3139                                ty: NumericType::mm(),
3140                                meta: Vec::new(),
3141                            },
3142                        ],
3143                        ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
3144                    },
3145                ),
3146            ]
3147            .into(),
3148            meta: Vec::new(),
3149            constrainable: false,
3150            object_kind: Default::default(),
3151        };
3152
3153        let ty2d = RuntimeType::Primitive(PrimitiveType::Axis2d);
3154        let ty3d = RuntimeType::Primitive(PrimitiveType::Axis3d);
3155
3156        assert_coerce_results(&a2d, &ty2d, &a2d, &mut exec_state);
3157        assert_coerce_results(&a3d, &ty3d, &a3d, &mut exec_state);
3158        assert_coerce_results(&a3d, &ty2d, &a2d, &mut exec_state);
3159        a2d.coerce(&ty3d, CoercionMode::implicit(), &mut exec_state)
3160            .unwrap_err();
3161        ctx.close().await;
3162    }
3163
3164    #[tokio::test(flavor = "multi_thread")]
3165    async fn coerce_numeric() {
3166        let (ctx, mut exec_state) = new_exec_state().await;
3167
3168        let count = KclValue::Number {
3169            value: 1.0,
3170            ty: NumericType::count(),
3171            meta: Vec::new(),
3172        };
3173        let mm = KclValue::Number {
3174            value: 1.0,
3175            ty: NumericType::mm(),
3176            meta: Vec::new(),
3177        };
3178        let inches = KclValue::Number {
3179            value: 1.0,
3180            ty: NumericType::Known(UnitType::Length(UnitLength::Inches)),
3181            meta: Vec::new(),
3182        };
3183        let rads = KclValue::Number {
3184            value: 1.0,
3185            ty: NumericType::Known(UnitType::Angle(UnitAngle::Radians)),
3186            meta: Vec::new(),
3187        };
3188        let default = KclValue::Number {
3189            value: 1.0,
3190            ty: NumericType::default(),
3191            meta: Vec::new(),
3192        };
3193        let any = KclValue::Number {
3194            value: 1.0,
3195            ty: NumericType::Any,
3196            meta: Vec::new(),
3197        };
3198        let unknown = KclValue::Number {
3199            value: 1.0,
3200            ty: NumericType::Unknown,
3201            meta: Vec::new(),
3202        };
3203
3204        // Trivial coercions
3205        assert_coerce_results(&count, &NumericType::count().into(), &count, &mut exec_state);
3206        assert_coerce_results(&mm, &NumericType::mm().into(), &mm, &mut exec_state);
3207        assert_coerce_results(&any, &NumericType::Any.into(), &any, &mut exec_state);
3208        assert_coerce_results(&unknown, &NumericType::Unknown.into(), &unknown, &mut exec_state);
3209        assert_coerce_results(&default, &NumericType::default().into(), &default, &mut exec_state);
3210
3211        assert_coerce_results(&count, &NumericType::Any.into(), &count, &mut exec_state);
3212        assert_coerce_results(&mm, &NumericType::Any.into(), &mm, &mut exec_state);
3213        assert_coerce_results(&unknown, &NumericType::Any.into(), &unknown, &mut exec_state);
3214        assert_coerce_results(&default, &NumericType::Any.into(), &default, &mut exec_state);
3215
3216        assert_eq!(
3217            default
3218                .coerce(
3219                    &NumericType::Default {
3220                        len: UnitLength::Yards,
3221                        angle: UnitAngle::Degrees,
3222                    }
3223                    .into(),
3224                    CoercionMode::implicit(),
3225                    &mut exec_state
3226                )
3227                .unwrap(),
3228            default
3229        );
3230
3231        // No coercion
3232        count
3233            .coerce(&NumericType::mm().into(), CoercionMode::implicit(), &mut exec_state)
3234            .unwrap_err();
3235        mm.coerce(&NumericType::count().into(), CoercionMode::implicit(), &mut exec_state)
3236            .unwrap_err();
3237        unknown
3238            .coerce(&NumericType::mm().into(), CoercionMode::implicit(), &mut exec_state)
3239            .unwrap_err();
3240        unknown
3241            .coerce(
3242                &NumericType::default().into(),
3243                CoercionMode::implicit(),
3244                &mut exec_state,
3245            )
3246            .unwrap_err();
3247
3248        count
3249            .coerce(&NumericType::Unknown.into(), CoercionMode::implicit(), &mut exec_state)
3250            .unwrap_err();
3251        mm.coerce(&NumericType::Unknown.into(), CoercionMode::implicit(), &mut exec_state)
3252            .unwrap_err();
3253        default
3254            .coerce(&NumericType::Unknown.into(), CoercionMode::implicit(), &mut exec_state)
3255            .unwrap_err();
3256
3257        assert_eq!(
3258            inches
3259                .coerce(&NumericType::mm().into(), CoercionMode::implicit(), &mut exec_state)
3260                .unwrap()
3261                .as_f64()
3262                .unwrap()
3263                .round(),
3264            25.0
3265        );
3266        assert_eq!(
3267            rads.coerce(
3268                &NumericType::Known(UnitType::Angle(UnitAngle::Degrees)).into(),
3269                CoercionMode::implicit(),
3270                &mut exec_state
3271            )
3272            .unwrap()
3273            .as_f64()
3274            .unwrap()
3275            .round(),
3276            57.0
3277        );
3278        assert_eq!(
3279            inches
3280                .coerce(
3281                    &NumericType::default().into(),
3282                    CoercionMode::implicit(),
3283                    &mut exec_state
3284                )
3285                .unwrap()
3286                .as_f64()
3287                .unwrap()
3288                .round(),
3289            1.0
3290        );
3291        assert_eq!(
3292            rads.coerce(
3293                &NumericType::default().into(),
3294                CoercionMode::implicit(),
3295                &mut exec_state
3296            )
3297            .unwrap()
3298            .as_f64()
3299            .unwrap()
3300            .round(),
3301            1.0
3302        );
3303        ctx.close().await;
3304    }
3305
3306    #[track_caller]
3307    fn assert_value_and_type(name: &str, result: &ExecTestResults, expected: f64, expected_ty: NumericType) {
3308        let mem = result.exec_state.stack();
3309        match mem
3310            .memory
3311            .get_from_owned(name, result.mem_env, SourceRange::default(), 0)
3312            .unwrap()
3313        {
3314            KclValue::Number { value, ty, .. } => {
3315                assert_eq!(value.round(), expected);
3316                assert_eq!(ty, expected_ty);
3317            }
3318            _ => unreachable!(),
3319        }
3320    }
3321
3322    #[tokio::test(flavor = "multi_thread")]
3323    async fn combine_numeric() {
3324        let program = r#"a = 5 + 4
3325b = 5 - 2
3326c = 5mm - 2mm + 10mm
3327d = 5mm - 2 + 10
3328e = 5 - 2mm + 10
3329f = 30mm - 1inch
3330
3331g = 2 * 10
3332h = 2 * 10mm
3333i = 2mm * 10mm
3334j = 2_ * 10
3335k = 2_ * 3mm * 3mm
3336
3337l = 1 / 10
3338m = 2mm / 1mm
3339n = 10inch / 2mm
3340o = 3mm / 3
3341p = 3_ / 4
3342q = 4inch / 2_
3343
3344r = min([0, 3, 42])
3345s = min([0, 3mm, -42])
3346t = min([100, 3in, 142mm])
3347u = min([3rad, 4in])
3348"#;
3349
3350        let result = parse_execute(program).await.unwrap();
3351        assert_eq!(
3352            result.exec_state.issues().len(),
3353            5,
3354            "errors: {:?}",
3355            result.exec_state.issues()
3356        );
3357
3358        assert_value_and_type("a", &result, 9.0, NumericType::default());
3359        assert_value_and_type("b", &result, 3.0, NumericType::default());
3360        assert_value_and_type("c", &result, 13.0, NumericType::mm());
3361        assert_value_and_type("d", &result, 13.0, NumericType::mm());
3362        assert_value_and_type("e", &result, 13.0, NumericType::mm());
3363        assert_value_and_type("f", &result, 5.0, NumericType::mm());
3364
3365        assert_value_and_type("g", &result, 20.0, NumericType::default());
3366        assert_value_and_type("h", &result, 20.0, NumericType::mm());
3367        assert_value_and_type("i", &result, 20.0, NumericType::Unknown);
3368        assert_value_and_type("j", &result, 20.0, NumericType::default());
3369        assert_value_and_type("k", &result, 18.0, NumericType::Unknown);
3370
3371        assert_value_and_type("l", &result, 0.0, NumericType::default());
3372        assert_value_and_type("m", &result, 2.0, NumericType::count());
3373        assert_value_and_type("n", &result, 5.0, NumericType::Unknown);
3374        assert_value_and_type("o", &result, 1.0, NumericType::mm());
3375        assert_value_and_type("p", &result, 1.0, NumericType::count());
3376        assert_value_and_type(
3377            "q",
3378            &result,
3379            2.0,
3380            NumericType::Known(UnitType::Length(UnitLength::Inches)),
3381        );
3382
3383        assert_value_and_type("r", &result, 0.0, NumericType::default());
3384        assert_value_and_type("s", &result, -42.0, NumericType::mm());
3385        assert_value_and_type("t", &result, 3.0, NumericType::Unknown);
3386        assert_value_and_type("u", &result, 3.0, NumericType::Unknown);
3387    }
3388
3389    #[tokio::test(flavor = "multi_thread")]
3390    async fn bad_typed_arithmetic() {
3391        let program = r#"
3392a = 1rad
3393b = 180 / PI * a + 360
3394"#;
3395
3396        let result = parse_execute(program).await.unwrap();
3397
3398        assert_value_and_type("a", &result, 1.0, NumericType::radians());
3399        assert_value_and_type("b", &result, 417.0, NumericType::Unknown);
3400    }
3401
3402    #[tokio::test(flavor = "multi_thread")]
3403    async fn cos_coercions() {
3404        let program = r#"
3405a = cos(units::toRadians(30deg))
3406b = 3 / a
3407c = cos(30deg)
3408d = cos(1rad)
3409"#;
3410
3411        let result = parse_execute(program).await.unwrap();
3412        assert!(
3413            result.exec_state.issues().is_empty(),
3414            "{:?}",
3415            result.exec_state.issues()
3416        );
3417
3418        assert_value_and_type("a", &result, 1.0, NumericType::default());
3419        assert_value_and_type("b", &result, 3.0, NumericType::default());
3420        assert_value_and_type("c", &result, 1.0, NumericType::default());
3421        assert_value_and_type("d", &result, 1.0, NumericType::default());
3422    }
3423
3424    #[tokio::test(flavor = "multi_thread")]
3425    async fn coerce_nested_array() {
3426        let (ctx, mut exec_state) = new_exec_state().await;
3427
3428        let mixed1 = KclValue::HomArray {
3429            value: vec![
3430                KclValue::Number {
3431                    value: 0.0,
3432                    ty: NumericType::count(),
3433                    meta: Vec::new(),
3434                },
3435                KclValue::Number {
3436                    value: 1.0,
3437                    ty: NumericType::count(),
3438                    meta: Vec::new(),
3439                },
3440                KclValue::HomArray {
3441                    value: vec![
3442                        KclValue::Number {
3443                            value: 2.0,
3444                            ty: NumericType::count(),
3445                            meta: Vec::new(),
3446                        },
3447                        KclValue::Number {
3448                            value: 3.0,
3449                            ty: NumericType::count(),
3450                            meta: Vec::new(),
3451                        },
3452                    ],
3453                    ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
3454                },
3455            ],
3456            ty: RuntimeType::any(),
3457        };
3458
3459        // Principal types
3460        let tym1 = RuntimeType::Array(
3461            Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
3462            ArrayLen::Minimum(1),
3463        );
3464
3465        let result = KclValue::HomArray {
3466            value: vec![
3467                KclValue::Number {
3468                    value: 0.0,
3469                    ty: NumericType::count(),
3470                    meta: Vec::new(),
3471                },
3472                KclValue::Number {
3473                    value: 1.0,
3474                    ty: NumericType::count(),
3475                    meta: Vec::new(),
3476                },
3477                KclValue::Number {
3478                    value: 2.0,
3479                    ty: NumericType::count(),
3480                    meta: Vec::new(),
3481                },
3482                KclValue::Number {
3483                    value: 3.0,
3484                    ty: NumericType::count(),
3485                    meta: Vec::new(),
3486                },
3487            ],
3488            ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
3489        };
3490        assert_coerce_results(&mixed1, &tym1, &result, &mut exec_state);
3491        ctx.close().await;
3492    }
3493}