Skip to main content

kcl_lib/execution/
types.rs

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