kcl_lib/execution/
types.rs

1use std::{collections::HashMap, str::FromStr};
2
3use anyhow::Result;
4use kittycad_modeling_cmds::units::{UnitAngle, UnitLength};
5use serde::{Deserialize, Serialize};
6
7use crate::{
8    CompilationError, KclError, SourceRange,
9    errors::KclErrorDetails,
10    execution::{
11        ExecState, Plane, PlaneInfo, Point3d, annotations,
12        kcl_value::{KclValue, TypeDef},
13        memory::{self},
14    },
15    fmt,
16    parsing::{
17        ast::types::{PrimitiveType as AstPrimitiveType, Type},
18        token::NumericSuffix,
19    },
20    std::args::{FromKclValue, TyF64},
21};
22
23#[derive(Debug, Clone, PartialEq)]
24pub enum RuntimeType {
25    Primitive(PrimitiveType),
26    Array(Box<RuntimeType>, ArrayLen),
27    Union(Vec<RuntimeType>),
28    Tuple(Vec<RuntimeType>),
29    Object(Vec<(String, RuntimeType)>, bool),
30}
31
32impl RuntimeType {
33    pub fn any() -> Self {
34        RuntimeType::Primitive(PrimitiveType::Any)
35    }
36
37    pub fn any_array() -> Self {
38        RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::None)
39    }
40
41    pub fn edge() -> Self {
42        RuntimeType::Primitive(PrimitiveType::Edge)
43    }
44
45    pub fn function() -> Self {
46        RuntimeType::Primitive(PrimitiveType::Function)
47    }
48
49    pub fn sketch() -> Self {
50        RuntimeType::Primitive(PrimitiveType::Sketch)
51    }
52
53    pub fn sketch_or_surface() -> Self {
54        RuntimeType::Union(vec![Self::sketch(), Self::plane(), Self::face()])
55    }
56
57    /// `[Sketch; 1+]`
58    pub fn sketches() -> Self {
59        RuntimeType::Array(
60            Box::new(RuntimeType::Primitive(PrimitiveType::Sketch)),
61            ArrayLen::Minimum(1),
62        )
63    }
64
65    /// `[Solid; 1+]`
66    pub fn solids() -> Self {
67        RuntimeType::Array(
68            Box::new(RuntimeType::Primitive(PrimitiveType::Solid)),
69            ArrayLen::Minimum(1),
70        )
71    }
72
73    pub fn solid() -> Self {
74        RuntimeType::Primitive(PrimitiveType::Solid)
75    }
76
77    pub fn helix() -> Self {
78        RuntimeType::Primitive(PrimitiveType::Helix)
79    }
80
81    pub fn plane() -> Self {
82        RuntimeType::Primitive(PrimitiveType::Plane)
83    }
84
85    pub fn face() -> Self {
86        RuntimeType::Primitive(PrimitiveType::Face)
87    }
88
89    pub fn tag_decl() -> Self {
90        RuntimeType::Primitive(PrimitiveType::TagDecl)
91    }
92
93    pub fn tagged_face() -> Self {
94        RuntimeType::Primitive(PrimitiveType::TaggedFace)
95    }
96
97    pub fn tagged_edge() -> Self {
98        RuntimeType::Primitive(PrimitiveType::TaggedEdge)
99    }
100
101    pub fn bool() -> Self {
102        RuntimeType::Primitive(PrimitiveType::Boolean)
103    }
104
105    pub fn string() -> Self {
106        RuntimeType::Primitive(PrimitiveType::String)
107    }
108
109    pub fn imported() -> Self {
110        RuntimeType::Primitive(PrimitiveType::ImportedGeometry)
111    }
112
113    /// `[number; 2]`
114    pub fn point2d() -> Self {
115        RuntimeType::Array(Box::new(RuntimeType::length()), ArrayLen::Known(2))
116    }
117
118    /// `[number; 3]`
119    pub fn point3d() -> Self {
120        RuntimeType::Array(Box::new(RuntimeType::length()), ArrayLen::Known(3))
121    }
122
123    pub fn length() -> Self {
124        RuntimeType::Primitive(PrimitiveType::Number(NumericType::Known(UnitType::GenericLength)))
125    }
126
127    pub fn known_length(len: UnitLength) -> Self {
128        RuntimeType::Primitive(PrimitiveType::Number(NumericType::Known(UnitType::Length(len))))
129    }
130
131    pub fn angle() -> Self {
132        RuntimeType::Primitive(PrimitiveType::Number(NumericType::Known(UnitType::GenericAngle)))
133    }
134
135    pub fn radians() -> Self {
136        RuntimeType::Primitive(PrimitiveType::Number(NumericType::Known(UnitType::Angle(
137            UnitAngle::Radians,
138        ))))
139    }
140
141    pub fn degrees() -> Self {
142        RuntimeType::Primitive(PrimitiveType::Number(NumericType::Known(UnitType::Angle(
143            UnitAngle::Degrees,
144        ))))
145    }
146
147    pub fn count() -> Self {
148        RuntimeType::Primitive(PrimitiveType::Number(NumericType::Known(UnitType::Count)))
149    }
150
151    pub fn num_any() -> Self {
152        RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any))
153    }
154
155    pub fn from_parsed(
156        value: Type,
157        exec_state: &mut ExecState,
158        source_range: SourceRange,
159        constrainable: bool,
160    ) -> Result<Self, CompilationError> {
161        match value {
162            Type::Primitive(pt) => Self::from_parsed_primitive(pt, exec_state, source_range),
163            Type::Array { ty, len } => Self::from_parsed(*ty, exec_state, source_range, constrainable)
164                .map(|t| RuntimeType::Array(Box::new(t), len)),
165            Type::Union { tys } => tys
166                .into_iter()
167                .map(|t| Self::from_parsed(t.inner, exec_state, source_range, constrainable))
168                .collect::<Result<Vec<_>, CompilationError>>()
169                .map(RuntimeType::Union),
170            Type::Object { properties } => properties
171                .into_iter()
172                .map(|(id, ty)| {
173                    RuntimeType::from_parsed(ty.inner, exec_state, source_range, constrainable)
174                        .map(|ty| (id.name.clone(), ty))
175                })
176                .collect::<Result<Vec<_>, CompilationError>>()
177                .map(|values| RuntimeType::Object(values, constrainable)),
178        }
179    }
180
181    fn from_parsed_primitive(
182        value: AstPrimitiveType,
183        exec_state: &mut ExecState,
184        source_range: SourceRange,
185    ) -> Result<Self, CompilationError> {
186        Ok(match value {
187            AstPrimitiveType::Any => RuntimeType::Primitive(PrimitiveType::Any),
188            AstPrimitiveType::None => RuntimeType::Primitive(PrimitiveType::None),
189            AstPrimitiveType::String => RuntimeType::Primitive(PrimitiveType::String),
190            AstPrimitiveType::Boolean => RuntimeType::Primitive(PrimitiveType::Boolean),
191            AstPrimitiveType::Number(suffix) => {
192                let ty = match suffix {
193                    NumericSuffix::None => NumericType::Any,
194                    _ => NumericType::from_parsed(suffix, &exec_state.mod_local.settings),
195                };
196                RuntimeType::Primitive(PrimitiveType::Number(ty))
197            }
198            AstPrimitiveType::Named { id } => Self::from_alias(&id.name, exec_state, source_range)?,
199            AstPrimitiveType::TagDecl => RuntimeType::Primitive(PrimitiveType::TagDecl),
200            AstPrimitiveType::ImportedGeometry => RuntimeType::Primitive(PrimitiveType::ImportedGeometry),
201            AstPrimitiveType::Function(_) => RuntimeType::Primitive(PrimitiveType::Function),
202        })
203    }
204
205    pub fn from_alias(
206        alias: &str,
207        exec_state: &mut ExecState,
208        source_range: SourceRange,
209    ) -> Result<Self, CompilationError> {
210        let ty_val = exec_state
211            .stack()
212            .get(&format!("{}{}", memory::TYPE_PREFIX, alias), source_range)
213            .map_err(|_| CompilationError::err(source_range, format!("Unknown type: {alias}")))?;
214
215        Ok(match ty_val {
216            KclValue::Type {
217                value, experimental, ..
218            } => {
219                let result = match value {
220                    TypeDef::RustRepr(ty, _) => RuntimeType::Primitive(ty.clone()),
221                    TypeDef::Alias(ty) => ty.clone(),
222                };
223                if *experimental {
224                    exec_state.warn_experimental(&format!("the type `{alias}`"), source_range);
225                }
226                result
227            }
228            _ => unreachable!(),
229        })
230    }
231
232    pub fn human_friendly_type(&self) -> String {
233        match self {
234            RuntimeType::Primitive(ty) => ty.to_string(),
235            RuntimeType::Array(ty, ArrayLen::None | ArrayLen::Minimum(0)) => {
236                format!("an array of {}", ty.display_multiple())
237            }
238            RuntimeType::Array(ty, ArrayLen::Minimum(1)) => format!("one or more {}", ty.display_multiple()),
239            RuntimeType::Array(ty, ArrayLen::Minimum(n)) => {
240                format!("an array of {n} or more {}", ty.display_multiple())
241            }
242            RuntimeType::Array(ty, ArrayLen::Known(n)) => format!("an array of {n} {}", ty.display_multiple()),
243            RuntimeType::Union(tys) => tys
244                .iter()
245                .map(Self::human_friendly_type)
246                .collect::<Vec<_>>()
247                .join(" or "),
248            RuntimeType::Tuple(tys) => format!(
249                "a tuple with values of types ({})",
250                tys.iter().map(Self::human_friendly_type).collect::<Vec<_>>().join(", ")
251            ),
252            RuntimeType::Object(..) => format!("an object with fields {self}"),
253        }
254    }
255
256    // Subtype with no coercion, including refining numeric types.
257    pub(crate) fn subtype(&self, sup: &RuntimeType) -> bool {
258        use RuntimeType::*;
259
260        match (self, sup) {
261            (_, Primitive(PrimitiveType::Any)) => true,
262            (Primitive(t1), Primitive(t2)) => t1.subtype(t2),
263            (Array(t1, l1), Array(t2, l2)) => t1.subtype(t2) && l1.subtype(*l2),
264            (Tuple(t1), Tuple(t2)) => t1.len() == t2.len() && t1.iter().zip(t2).all(|(t1, t2)| t1.subtype(t2)),
265
266            (Union(ts1), Union(ts2)) => ts1.iter().all(|t| ts2.contains(t)),
267            (t1, Union(ts2)) => ts2.iter().any(|t| t1.subtype(t)),
268
269            (Object(t1, _), Object(t2, _)) => t2
270                .iter()
271                .all(|(f, t)| t1.iter().any(|(ff, tt)| f == ff && tt.subtype(t))),
272
273            // Equivalence between singleton types and single-item arrays/tuples of the same type (plus transitivity with the array subtyping).
274            (t1, RuntimeType::Array(t2, l)) if t1.subtype(t2) && ArrayLen::Known(1).subtype(*l) => true,
275            (RuntimeType::Array(t1, ArrayLen::Known(1)), t2) if t1.subtype(t2) => true,
276            (t1, RuntimeType::Tuple(t2)) if !t2.is_empty() && t1.subtype(&t2[0]) => true,
277            (RuntimeType::Tuple(t1), t2) if t1.len() == 1 && t1[0].subtype(t2) => true,
278
279            // Equivalence between Axis types and their object representation.
280            (Object(t1, _), Primitive(PrimitiveType::Axis2d)) => {
281                t1.iter()
282                    .any(|(n, t)| n == "origin" && t.subtype(&RuntimeType::point2d()))
283                    && t1
284                        .iter()
285                        .any(|(n, t)| n == "direction" && t.subtype(&RuntimeType::point2d()))
286            }
287            (Object(t1, _), Primitive(PrimitiveType::Axis3d)) => {
288                t1.iter()
289                    .any(|(n, t)| n == "origin" && t.subtype(&RuntimeType::point3d()))
290                    && t1
291                        .iter()
292                        .any(|(n, t)| n == "direction" && t.subtype(&RuntimeType::point3d()))
293            }
294            (Primitive(PrimitiveType::Axis2d), Object(t2, _)) => {
295                t2.iter()
296                    .any(|(n, t)| n == "origin" && t.subtype(&RuntimeType::point2d()))
297                    && t2
298                        .iter()
299                        .any(|(n, t)| n == "direction" && t.subtype(&RuntimeType::point2d()))
300            }
301            (Primitive(PrimitiveType::Axis3d), Object(t2, _)) => {
302                t2.iter()
303                    .any(|(n, t)| n == "origin" && t.subtype(&RuntimeType::point3d()))
304                    && t2
305                        .iter()
306                        .any(|(n, t)| n == "direction" && t.subtype(&RuntimeType::point3d()))
307            }
308            _ => false,
309        }
310    }
311
312    fn display_multiple(&self) -> String {
313        match self {
314            RuntimeType::Primitive(ty) => ty.display_multiple(),
315            RuntimeType::Array(..) => "arrays".to_owned(),
316            RuntimeType::Union(tys) => tys
317                .iter()
318                .map(|t| t.display_multiple())
319                .collect::<Vec<_>>()
320                .join(" or "),
321            RuntimeType::Tuple(_) => "tuples".to_owned(),
322            RuntimeType::Object(..) => format!("objects with fields {self}"),
323        }
324    }
325}
326
327impl std::fmt::Display for RuntimeType {
328    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
329        match self {
330            RuntimeType::Primitive(t) => t.fmt(f),
331            RuntimeType::Array(t, l) => match l {
332                ArrayLen::None => write!(f, "[{t}]"),
333                ArrayLen::Minimum(n) => write!(f, "[{t}; {n}+]"),
334                ArrayLen::Known(n) => write!(f, "[{t}; {n}]"),
335            },
336            RuntimeType::Tuple(ts) => write!(
337                f,
338                "({})",
339                ts.iter().map(|t| t.to_string()).collect::<Vec<_>>().join(", ")
340            ),
341            RuntimeType::Union(ts) => write!(
342                f,
343                "{}",
344                ts.iter().map(|t| t.to_string()).collect::<Vec<_>>().join(" | ")
345            ),
346            RuntimeType::Object(items, _) => write!(
347                f,
348                "{{ {} }}",
349                items
350                    .iter()
351                    .map(|(n, t)| format!("{n}: {t}"))
352                    .collect::<Vec<_>>()
353                    .join(", ")
354            ),
355        }
356    }
357}
358
359#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, ts_rs::TS)]
360pub enum ArrayLen {
361    None,
362    Minimum(usize),
363    Known(usize),
364}
365
366impl ArrayLen {
367    pub fn subtype(self, other: ArrayLen) -> bool {
368        match (self, other) {
369            (_, ArrayLen::None) => true,
370            (ArrayLen::Minimum(s1), ArrayLen::Minimum(s2)) if s1 >= s2 => true,
371            (ArrayLen::Known(s1), ArrayLen::Minimum(s2)) if s1 >= s2 => true,
372            (ArrayLen::None, ArrayLen::Minimum(0)) => true,
373            (ArrayLen::Known(s1), ArrayLen::Known(s2)) if s1 == s2 => true,
374            _ => false,
375        }
376    }
377
378    /// True if the length constraint is satisfied by the supplied length.
379    pub fn satisfied(self, len: usize, allow_shrink: bool) -> Option<usize> {
380        match self {
381            ArrayLen::None => Some(len),
382            ArrayLen::Minimum(s) => (len >= s).then_some(len),
383            ArrayLen::Known(s) => (if allow_shrink { len >= s } else { len == s }).then_some(s),
384        }
385    }
386
387    pub fn human_friendly_type(self) -> String {
388        match self {
389            ArrayLen::None | ArrayLen::Minimum(0) => "any number of elements".to_owned(),
390            ArrayLen::Minimum(1) => "at least 1 element".to_owned(),
391            ArrayLen::Minimum(n) => format!("at least {n} elements"),
392            ArrayLen::Known(0) => "no elements".to_owned(),
393            ArrayLen::Known(1) => "exactly 1 element".to_owned(),
394            ArrayLen::Known(n) => format!("exactly {n} elements"),
395        }
396    }
397}
398
399#[derive(Debug, Clone, PartialEq)]
400pub enum PrimitiveType {
401    Any,
402    None,
403    Number(NumericType),
404    String,
405    Boolean,
406    TaggedEdge,
407    TaggedFace,
408    TagDecl,
409    GdtAnnotation,
410    Sketch,
411    Solid,
412    Plane,
413    Helix,
414    Face,
415    Edge,
416    Axis2d,
417    Axis3d,
418    ImportedGeometry,
419    Function,
420}
421
422impl PrimitiveType {
423    fn display_multiple(&self) -> String {
424        match self {
425            PrimitiveType::Any => "any values".to_owned(),
426            PrimitiveType::None => "none values".to_owned(),
427            PrimitiveType::Number(NumericType::Known(unit)) => format!("numbers({unit})"),
428            PrimitiveType::Number(_) => "numbers".to_owned(),
429            PrimitiveType::String => "strings".to_owned(),
430            PrimitiveType::Boolean => "bools".to_owned(),
431            PrimitiveType::GdtAnnotation => "GD&T Annotations".to_owned(),
432            PrimitiveType::Sketch => "Sketches".to_owned(),
433            PrimitiveType::Solid => "Solids".to_owned(),
434            PrimitiveType::Plane => "Planes".to_owned(),
435            PrimitiveType::Helix => "Helices".to_owned(),
436            PrimitiveType::Face => "Faces".to_owned(),
437            PrimitiveType::Edge => "Edges".to_owned(),
438            PrimitiveType::Axis2d => "2d axes".to_owned(),
439            PrimitiveType::Axis3d => "3d axes".to_owned(),
440            PrimitiveType::ImportedGeometry => "imported geometries".to_owned(),
441            PrimitiveType::Function => "functions".to_owned(),
442            PrimitiveType::TagDecl => "tag declarators".to_owned(),
443            PrimitiveType::TaggedEdge => "tagged edges".to_owned(),
444            PrimitiveType::TaggedFace => "tagged faces".to_owned(),
445        }
446    }
447
448    fn subtype(&self, other: &PrimitiveType) -> bool {
449        match (self, other) {
450            (_, PrimitiveType::Any) => true,
451            (PrimitiveType::Number(n1), PrimitiveType::Number(n2)) => n1.subtype(n2),
452            (PrimitiveType::TaggedEdge, PrimitiveType::TaggedFace)
453            | (PrimitiveType::TaggedEdge, PrimitiveType::Edge) => true,
454            (t1, t2) => t1 == t2,
455        }
456    }
457}
458
459impl std::fmt::Display for PrimitiveType {
460    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
461        match self {
462            PrimitiveType::Any => write!(f, "any"),
463            PrimitiveType::None => write!(f, "none"),
464            PrimitiveType::Number(NumericType::Known(unit)) => write!(f, "number({unit})"),
465            PrimitiveType::Number(NumericType::Unknown) => write!(f, "number(unknown units)"),
466            PrimitiveType::Number(NumericType::Default { .. }) => write!(f, "number"),
467            PrimitiveType::Number(NumericType::Any) => write!(f, "number(any units)"),
468            PrimitiveType::String => write!(f, "string"),
469            PrimitiveType::Boolean => write!(f, "bool"),
470            PrimitiveType::TagDecl => write!(f, "tag declarator"),
471            PrimitiveType::TaggedEdge => write!(f, "tagged edge"),
472            PrimitiveType::TaggedFace => write!(f, "tagged face"),
473            PrimitiveType::GdtAnnotation => write!(f, "GD&T Annotation"),
474            PrimitiveType::Sketch => write!(f, "Sketch"),
475            PrimitiveType::Solid => write!(f, "Solid"),
476            PrimitiveType::Plane => write!(f, "Plane"),
477            PrimitiveType::Face => write!(f, "Face"),
478            PrimitiveType::Edge => write!(f, "Edge"),
479            PrimitiveType::Axis2d => write!(f, "Axis2d"),
480            PrimitiveType::Axis3d => write!(f, "Axis3d"),
481            PrimitiveType::Helix => write!(f, "Helix"),
482            PrimitiveType::ImportedGeometry => write!(f, "ImportedGeometry"),
483            PrimitiveType::Function => write!(f, "fn"),
484        }
485    }
486}
487
488#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, ts_rs::TS)]
489#[ts(export)]
490#[serde(tag = "type")]
491pub enum NumericType {
492    // Specified by the user (directly or indirectly)
493    Known(UnitType),
494    // Unspecified, using defaults
495    Default { len: UnitLength, angle: UnitAngle },
496    // Exceeded the ability of the type system to track.
497    Unknown,
498    // Type info has been explicitly cast away.
499    Any,
500}
501
502impl Default for NumericType {
503    fn default() -> Self {
504        NumericType::Default {
505            len: UnitLength::Millimeters,
506            angle: UnitAngle::Degrees,
507        }
508    }
509}
510
511impl NumericType {
512    pub const fn count() -> Self {
513        NumericType::Known(UnitType::Count)
514    }
515
516    pub const fn mm() -> Self {
517        NumericType::Known(UnitType::Length(UnitLength::Millimeters))
518    }
519
520    pub const fn radians() -> Self {
521        NumericType::Known(UnitType::Angle(UnitAngle::Radians))
522    }
523
524    pub const fn degrees() -> Self {
525        NumericType::Known(UnitType::Angle(UnitAngle::Degrees))
526    }
527
528    /// Combine two types when we expect them to be equal, erring on the side of less coercion. To be
529    /// precise, only adjusting one number or the other when they are of known types.
530    ///
531    /// This combinator function is suitable for comparisons where uncertainty should
532    /// be handled by the user.
533    pub fn combine_eq(
534        a: TyF64,
535        b: TyF64,
536        exec_state: &mut ExecState,
537        source_range: SourceRange,
538    ) -> (f64, f64, NumericType) {
539        use NumericType::*;
540        match (a.ty, b.ty) {
541            (at, bt) if at == bt => (a.n, b.n, at),
542            (at, Any) => (a.n, b.n, at),
543            (Any, bt) => (a.n, b.n, bt),
544
545            (t @ Known(UnitType::Length(l1)), Known(UnitType::Length(l2))) => (a.n, adjust_length(l2, b.n, l1).0, t),
546            (t @ Known(UnitType::Angle(a1)), Known(UnitType::Angle(a2))) => (a.n, adjust_angle(a2, b.n, a1).0, t),
547
548            (t @ Known(UnitType::Length(_)), Known(UnitType::GenericLength)) => (a.n, b.n, t),
549            (Known(UnitType::GenericLength), t @ Known(UnitType::Length(_))) => (a.n, b.n, t),
550            (t @ Known(UnitType::Angle(_)), Known(UnitType::GenericAngle)) => (a.n, b.n, t),
551            (Known(UnitType::GenericAngle), t @ Known(UnitType::Angle(_))) => (a.n, b.n, t),
552
553            (Known(UnitType::Count), Default { .. }) | (Default { .. }, Known(UnitType::Count)) => {
554                (a.n, b.n, Known(UnitType::Count))
555            }
556            (t @ Known(UnitType::Length(l1)), Default { len: l2, .. }) if l1 == l2 => (a.n, b.n, t),
557            (Default { len: l1, .. }, t @ Known(UnitType::Length(l2))) if l1 == l2 => (a.n, b.n, t),
558            (t @ Known(UnitType::Angle(a1)), Default { angle: a2, .. }) if a1 == a2 => {
559                if b.n != 0.0 {
560                    exec_state.warn(
561                        CompilationError::err(source_range, "Prefer to use explicit units for angles"),
562                        annotations::WARN_ANGLE_UNITS,
563                    );
564                }
565                (a.n, b.n, t)
566            }
567            (Default { angle: a1, .. }, t @ Known(UnitType::Angle(a2))) if a1 == a2 => {
568                if a.n != 0.0 {
569                    exec_state.warn(
570                        CompilationError::err(source_range, "Prefer to use explicit units for angles"),
571                        annotations::WARN_ANGLE_UNITS,
572                    );
573                }
574                (a.n, b.n, t)
575            }
576
577            _ => (a.n, b.n, Unknown),
578        }
579    }
580
581    /// Combine two types when we expect them to be equal, erring on the side of more coercion. Including adjusting when
582    /// we are certain about only one type.
583    ///
584    /// This combinator function is suitable for situations where the user would almost certainly want the types to be
585    /// coerced together, for example two arguments to the same function or two numbers in an array being used as a point.
586    ///
587    /// Prefer to use `combine_eq` if possible since using that prioritises correctness over ergonomics.
588    pub fn combine_eq_coerce(
589        a: TyF64,
590        b: TyF64,
591        for_errs: Option<(&mut ExecState, SourceRange)>,
592    ) -> (f64, f64, NumericType) {
593        use NumericType::*;
594        match (a.ty, b.ty) {
595            (at, bt) if at == bt => (a.n, b.n, at),
596            (at, Any) => (a.n, b.n, at),
597            (Any, bt) => (a.n, b.n, bt),
598
599            // Known types and compatible, but needs adjustment.
600            (t @ Known(UnitType::Length(l1)), Known(UnitType::Length(l2))) => (a.n, adjust_length(l2, b.n, l1).0, t),
601            (t @ Known(UnitType::Angle(a1)), Known(UnitType::Angle(a2))) => (a.n, adjust_angle(a2, b.n, a1).0, t),
602
603            (t @ Known(UnitType::Length(_)), Known(UnitType::GenericLength)) => (a.n, b.n, t),
604            (Known(UnitType::GenericLength), t @ Known(UnitType::Length(_))) => (a.n, b.n, t),
605            (t @ Known(UnitType::Angle(_)), Known(UnitType::GenericAngle)) => (a.n, b.n, t),
606            (Known(UnitType::GenericAngle), t @ Known(UnitType::Angle(_))) => (a.n, b.n, t),
607
608            // Known and unknown => we assume the known one, possibly with adjustment
609            (Known(UnitType::Count), Default { .. }) | (Default { .. }, Known(UnitType::Count)) => {
610                (a.n, b.n, Known(UnitType::Count))
611            }
612
613            (t @ Known(UnitType::Length(l1)), Default { len: l2, .. }) => (a.n, adjust_length(l2, b.n, l1).0, t),
614            (Default { len: l1, .. }, t @ Known(UnitType::Length(l2))) => (adjust_length(l1, a.n, l2).0, b.n, t),
615            (t @ Known(UnitType::Angle(a1)), Default { angle: a2, .. }) => {
616                if let Some((exec_state, source_range)) = for_errs
617                    && b.n != 0.0
618                {
619                    exec_state.warn(
620                        CompilationError::err(source_range, "Prefer to use explicit units for angles"),
621                        annotations::WARN_ANGLE_UNITS,
622                    );
623                }
624                (a.n, adjust_angle(a2, b.n, a1).0, t)
625            }
626            (Default { angle: a1, .. }, t @ Known(UnitType::Angle(a2))) => {
627                if let Some((exec_state, source_range)) = for_errs
628                    && a.n != 0.0
629                {
630                    exec_state.warn(
631                        CompilationError::err(source_range, "Prefer to use explicit units for angles"),
632                        annotations::WARN_ANGLE_UNITS,
633                    );
634                }
635                (adjust_angle(a1, a.n, a2).0, b.n, t)
636            }
637
638            (Default { len: l1, .. }, Known(UnitType::GenericLength)) => (a.n, b.n, l1.into()),
639            (Known(UnitType::GenericLength), Default { len: l2, .. }) => (a.n, b.n, l2.into()),
640            (Default { angle: a1, .. }, Known(UnitType::GenericAngle)) => {
641                if let Some((exec_state, source_range)) = for_errs
642                    && b.n != 0.0
643                {
644                    exec_state.warn(
645                        CompilationError::err(source_range, "Prefer to use explicit units for angles"),
646                        annotations::WARN_ANGLE_UNITS,
647                    );
648                }
649                (a.n, b.n, a1.into())
650            }
651            (Known(UnitType::GenericAngle), Default { angle: a2, .. }) => {
652                if let Some((exec_state, source_range)) = for_errs
653                    && a.n != 0.0
654                {
655                    exec_state.warn(
656                        CompilationError::err(source_range, "Prefer to use explicit units for angles"),
657                        annotations::WARN_ANGLE_UNITS,
658                    );
659                }
660                (a.n, b.n, a2.into())
661            }
662
663            (Known(_), Known(_)) | (Default { .. }, Default { .. }) | (_, Unknown) | (Unknown, _) => {
664                (a.n, b.n, Unknown)
665            }
666        }
667    }
668
669    pub fn combine_eq_array(input: &[TyF64]) -> (Vec<f64>, NumericType) {
670        use NumericType::*;
671        let result = input.iter().map(|t| t.n).collect();
672
673        let mut ty = Any;
674        for i in input {
675            if i.ty == Any || ty == i.ty {
676                continue;
677            }
678
679            // The cases where we check the values for 0.0 are so we don't crash out where a conversion would always be safe
680            match (&ty, &i.ty) {
681                (Any, Default { .. }) if i.n == 0.0 => {}
682                (Any, t) => {
683                    ty = *t;
684                }
685                (_, Unknown) | (Default { .. }, Default { .. }) => return (result, Unknown),
686
687                (Known(UnitType::Count), Default { .. }) | (Default { .. }, Known(UnitType::Count)) => {
688                    ty = Known(UnitType::Count);
689                }
690
691                (Known(UnitType::Length(l1)), Default { len: l2, .. }) if l1 == l2 || i.n == 0.0 => {}
692                (Known(UnitType::Angle(a1)), Default { angle: a2, .. }) if a1 == a2 || i.n == 0.0 => {}
693
694                (Default { len: l1, .. }, Known(UnitType::Length(l2))) if l1 == l2 => {
695                    ty = Known(UnitType::Length(*l2));
696                }
697                (Default { angle: a1, .. }, Known(UnitType::Angle(a2))) if a1 == a2 => {
698                    ty = Known(UnitType::Angle(*a2));
699                }
700
701                _ => return (result, Unknown),
702            }
703        }
704
705        if ty == Any && !input.is_empty() {
706            ty = input[0].ty;
707        }
708
709        (result, ty)
710    }
711
712    /// Combine two types for multiplication-like operations.
713    pub fn combine_mul(a: TyF64, b: TyF64) -> (f64, f64, NumericType) {
714        use NumericType::*;
715        match (a.ty, b.ty) {
716            (at @ Default { .. }, bt @ Default { .. }) if at == bt => (a.n, b.n, at),
717            (Default { .. }, Default { .. }) => (a.n, b.n, Unknown),
718            (Known(UnitType::Count), bt) => (a.n, b.n, bt),
719            (at, Known(UnitType::Count)) => (a.n, b.n, at),
720            (at @ Known(_), Default { .. }) | (Default { .. }, at @ Known(_)) => (a.n, b.n, at),
721            (Any, Any) => (a.n, b.n, Any),
722            _ => (a.n, b.n, Unknown),
723        }
724    }
725
726    /// Combine two types for division-like operations.
727    pub fn combine_div(a: TyF64, b: TyF64) -> (f64, f64, NumericType) {
728        use NumericType::*;
729        match (a.ty, b.ty) {
730            (at @ Default { .. }, bt @ Default { .. }) if at == bt => (a.n, b.n, at),
731            (at, bt) if at == bt => (a.n, b.n, Known(UnitType::Count)),
732            (Default { .. }, Default { .. }) => (a.n, b.n, Unknown),
733            (at, Known(UnitType::Count) | Any) => (a.n, b.n, at),
734            (at @ Known(_), Default { .. }) => (a.n, b.n, at),
735            (Known(UnitType::Count), _) => (a.n, b.n, Known(UnitType::Count)),
736            _ => (a.n, b.n, Unknown),
737        }
738    }
739
740    /// Combine two types for modulo-like operations.
741    pub fn combine_mod(a: TyF64, b: TyF64) -> (f64, f64, NumericType) {
742        use NumericType::*;
743        match (a.ty, b.ty) {
744            (at @ Default { .. }, bt @ Default { .. }) if at == bt => (a.n, b.n, at),
745            (at, bt) if at == bt => (a.n, b.n, at),
746            (Default { .. }, Default { .. }) => (a.n, b.n, Unknown),
747            (at, Known(UnitType::Count) | Any) => (a.n, b.n, at),
748            (at @ Known(_), Default { .. }) => (a.n, b.n, at),
749            (Known(UnitType::Count), _) => (a.n, b.n, Known(UnitType::Count)),
750            _ => (a.n, b.n, Unknown),
751        }
752    }
753
754    /// Combine two types for range operations.
755    ///
756    /// This combinator function is suitable for ranges where uncertainty should
757    /// be handled by the user, and it doesn't make sense to convert units. So
758    /// this is one of th most conservative ways to combine types.
759    pub fn combine_range(
760        a: TyF64,
761        b: TyF64,
762        exec_state: &mut ExecState,
763        source_range: SourceRange,
764    ) -> Result<(f64, f64, NumericType), KclError> {
765        use NumericType::*;
766        match (a.ty, b.ty) {
767            (at, bt) if at == bt => Ok((a.n, b.n, at)),
768            (at, Any) => Ok((a.n, b.n, at)),
769            (Any, bt) => Ok((a.n, b.n, bt)),
770
771            (Known(UnitType::Length(l1)), Known(UnitType::Length(l2))) => {
772                Err(KclError::new_semantic(KclErrorDetails::new(
773                    format!("Range start and range end have incompatible units: {l1} and {l2}"),
774                    vec![source_range],
775                )))
776            }
777            (Known(UnitType::Angle(a1)), Known(UnitType::Angle(a2))) => {
778                Err(KclError::new_semantic(KclErrorDetails::new(
779                    format!("Range start and range end have incompatible units: {a1} and {a2}"),
780                    vec![source_range],
781                )))
782            }
783
784            (t @ Known(UnitType::Length(_)), Known(UnitType::GenericLength)) => Ok((a.n, b.n, t)),
785            (Known(UnitType::GenericLength), t @ Known(UnitType::Length(_))) => Ok((a.n, b.n, t)),
786            (t @ Known(UnitType::Angle(_)), Known(UnitType::GenericAngle)) => Ok((a.n, b.n, t)),
787            (Known(UnitType::GenericAngle), t @ Known(UnitType::Angle(_))) => Ok((a.n, b.n, t)),
788
789            (Known(UnitType::Count), Default { .. }) | (Default { .. }, Known(UnitType::Count)) => {
790                Ok((a.n, b.n, Known(UnitType::Count)))
791            }
792            (t @ Known(UnitType::Length(l1)), Default { len: l2, .. }) if l1 == l2 => Ok((a.n, b.n, t)),
793            (Default { len: l1, .. }, t @ Known(UnitType::Length(l2))) if l1 == l2 => Ok((a.n, b.n, t)),
794            (t @ Known(UnitType::Angle(a1)), Default { angle: a2, .. }) if a1 == a2 => {
795                if b.n != 0.0 {
796                    exec_state.warn(
797                        CompilationError::err(source_range, "Prefer to use explicit units for angles"),
798                        annotations::WARN_ANGLE_UNITS,
799                    );
800                }
801                Ok((a.n, b.n, t))
802            }
803            (Default { angle: a1, .. }, t @ Known(UnitType::Angle(a2))) if a1 == a2 => {
804                if a.n != 0.0 {
805                    exec_state.warn(
806                        CompilationError::err(source_range, "Prefer to use explicit units for angles"),
807                        annotations::WARN_ANGLE_UNITS,
808                    );
809                }
810                Ok((a.n, b.n, t))
811            }
812
813            _ => {
814                let a = fmt::human_display_number(a.n, a.ty);
815                let b = fmt::human_display_number(b.n, b.ty);
816                Err(KclError::new_semantic(KclErrorDetails::new(
817                    format!(
818                        "Range start and range end must be of the same type and have compatible units, but found {a} and {b}",
819                    ),
820                    vec![source_range],
821                )))
822            }
823        }
824    }
825
826    pub fn from_parsed(suffix: NumericSuffix, settings: &super::MetaSettings) -> Self {
827        match suffix {
828            NumericSuffix::None => NumericType::Default {
829                len: settings.default_length_units,
830                angle: settings.default_angle_units,
831            },
832            NumericSuffix::Count => NumericType::Known(UnitType::Count),
833            NumericSuffix::Length => NumericType::Known(UnitType::GenericLength),
834            NumericSuffix::Angle => NumericType::Known(UnitType::GenericAngle),
835            NumericSuffix::Mm => NumericType::Known(UnitType::Length(UnitLength::Millimeters)),
836            NumericSuffix::Cm => NumericType::Known(UnitType::Length(UnitLength::Centimeters)),
837            NumericSuffix::M => NumericType::Known(UnitType::Length(UnitLength::Meters)),
838            NumericSuffix::Inch => NumericType::Known(UnitType::Length(UnitLength::Inches)),
839            NumericSuffix::Ft => NumericType::Known(UnitType::Length(UnitLength::Feet)),
840            NumericSuffix::Yd => NumericType::Known(UnitType::Length(UnitLength::Yards)),
841            NumericSuffix::Deg => NumericType::Known(UnitType::Angle(UnitAngle::Degrees)),
842            NumericSuffix::Rad => NumericType::Known(UnitType::Angle(UnitAngle::Radians)),
843            NumericSuffix::Unknown => NumericType::Unknown,
844        }
845    }
846
847    fn subtype(&self, other: &NumericType) -> bool {
848        use NumericType::*;
849
850        match (self, other) {
851            (_, Any) => true,
852            (a, b) if a == b => true,
853            (
854                NumericType::Known(UnitType::Length(_))
855                | NumericType::Known(UnitType::GenericLength)
856                | NumericType::Default { .. },
857                NumericType::Known(UnitType::GenericLength),
858            )
859            | (
860                NumericType::Known(UnitType::Angle(_))
861                | NumericType::Known(UnitType::GenericAngle)
862                | NumericType::Default { .. },
863                NumericType::Known(UnitType::GenericAngle),
864            ) => true,
865            (Unknown, _) | (_, Unknown) => false,
866            (_, _) => false,
867        }
868    }
869
870    fn is_unknown(&self) -> bool {
871        matches!(
872            self,
873            NumericType::Unknown
874                | NumericType::Known(UnitType::GenericAngle)
875                | NumericType::Known(UnitType::GenericLength)
876        )
877    }
878
879    pub fn is_fully_specified(&self) -> bool {
880        !matches!(
881            self,
882            NumericType::Unknown
883                | NumericType::Known(UnitType::GenericAngle)
884                | NumericType::Known(UnitType::GenericLength)
885                | NumericType::Any
886                | NumericType::Default { .. }
887        )
888    }
889
890    fn example_ty(&self) -> Option<String> {
891        match self {
892            Self::Known(t) if !self.is_unknown() => Some(t.to_string()),
893            Self::Default { len, .. } => Some(len.to_string()),
894            _ => None,
895        }
896    }
897
898    fn coerce(&self, val: &KclValue) -> Result<KclValue, CoercionError> {
899        let KclValue::Number { value, ty, meta } = val else {
900            return Err(val.into());
901        };
902
903        if ty.subtype(self) {
904            return Ok(KclValue::Number {
905                value: *value,
906                ty: *ty,
907                meta: meta.clone(),
908            });
909        }
910
911        // Not subtypes, but might be able to coerce
912        use NumericType::*;
913        match (ty, self) {
914            // We don't have enough information to coerce.
915            (Unknown, _) => Err(CoercionError::from(val).with_explicit(self.example_ty().unwrap_or("mm".to_owned()))),
916            (_, Unknown) => Err(val.into()),
917
918            (Any, _) => Ok(KclValue::Number {
919                value: *value,
920                ty: *self,
921                meta: meta.clone(),
922            }),
923
924            // If we're coercing to a default, we treat this as coercing to Any since leaving the numeric type unspecified in a coercion situation
925            // means accept any number rather than force the current default.
926            (_, Default { .. }) => Ok(KclValue::Number {
927                value: *value,
928                ty: *ty,
929                meta: meta.clone(),
930            }),
931
932            // Known types and compatible, but needs adjustment.
933            (Known(UnitType::Length(l1)), Known(UnitType::Length(l2))) => {
934                let (value, ty) = adjust_length(*l1, *value, *l2);
935                Ok(KclValue::Number {
936                    value,
937                    ty: Known(UnitType::Length(ty)),
938                    meta: meta.clone(),
939                })
940            }
941            (Known(UnitType::Angle(a1)), Known(UnitType::Angle(a2))) => {
942                let (value, ty) = adjust_angle(*a1, *value, *a2);
943                Ok(KclValue::Number {
944                    value,
945                    ty: Known(UnitType::Angle(ty)),
946                    meta: meta.clone(),
947                })
948            }
949
950            // Known but incompatible.
951            (Known(_), Known(_)) => Err(val.into()),
952
953            // Known and unknown => we assume the rhs, possibly with adjustment
954            (Default { .. }, Known(UnitType::Count)) => Ok(KclValue::Number {
955                value: *value,
956                ty: Known(UnitType::Count),
957                meta: meta.clone(),
958            }),
959
960            (Default { len: l1, .. }, Known(UnitType::Length(l2))) => {
961                let (value, ty) = adjust_length(*l1, *value, *l2);
962                Ok(KclValue::Number {
963                    value,
964                    ty: Known(UnitType::Length(ty)),
965                    meta: meta.clone(),
966                })
967            }
968
969            (Default { angle: a1, .. }, Known(UnitType::Angle(a2))) => {
970                let (value, ty) = adjust_angle(*a1, *value, *a2);
971                Ok(KclValue::Number {
972                    value,
973                    ty: Known(UnitType::Angle(ty)),
974                    meta: meta.clone(),
975                })
976            }
977
978            (_, _) => unreachable!(),
979        }
980    }
981
982    pub fn as_length(&self) -> Option<UnitLength> {
983        match self {
984            Self::Known(UnitType::Length(len)) | Self::Default { len, .. } => Some(*len),
985            _ => None,
986        }
987    }
988}
989
990impl From<NumericType> for RuntimeType {
991    fn from(t: NumericType) -> RuntimeType {
992        RuntimeType::Primitive(PrimitiveType::Number(t))
993    }
994}
995
996impl From<UnitLength> for NumericType {
997    fn from(value: UnitLength) -> Self {
998        NumericType::Known(UnitType::Length(value))
999    }
1000}
1001
1002impl From<Option<UnitLength>> for NumericType {
1003    fn from(value: Option<UnitLength>) -> Self {
1004        match value {
1005            Some(v) => v.into(),
1006            None => NumericType::Unknown,
1007        }
1008    }
1009}
1010
1011impl From<UnitAngle> for NumericType {
1012    fn from(value: UnitAngle) -> Self {
1013        NumericType::Known(UnitType::Angle(value))
1014    }
1015}
1016
1017#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, ts_rs::TS)]
1018#[ts(export)]
1019#[serde(tag = "type")]
1020pub enum UnitType {
1021    Count,
1022    Length(UnitLength),
1023    GenericLength,
1024    Angle(UnitAngle),
1025    GenericAngle,
1026}
1027
1028impl UnitType {
1029    pub(crate) fn to_suffix(self) -> Option<String> {
1030        match self {
1031            UnitType::Count => Some("_".to_owned()),
1032            UnitType::GenericLength | UnitType::GenericAngle => None,
1033            UnitType::Length(l) => Some(l.to_string()),
1034            UnitType::Angle(a) => Some(a.to_string()),
1035        }
1036    }
1037}
1038
1039impl std::fmt::Display for UnitType {
1040    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1041        match self {
1042            UnitType::Count => write!(f, "Count"),
1043            UnitType::Length(l) => l.fmt(f),
1044            UnitType::GenericLength => write!(f, "Length"),
1045            UnitType::Angle(a) => a.fmt(f),
1046            UnitType::GenericAngle => write!(f, "Angle"),
1047        }
1048    }
1049}
1050
1051pub fn adjust_length(from: UnitLength, value: f64, to: UnitLength) -> (f64, UnitLength) {
1052    use UnitLength::*;
1053
1054    if from == to {
1055        return (value, to);
1056    }
1057
1058    let (base, base_unit) = match from {
1059        Millimeters => (value, Millimeters),
1060        Centimeters => (value * 10.0, Millimeters),
1061        Meters => (value * 1000.0, Millimeters),
1062        Inches => (value, Inches),
1063        Feet => (value * 12.0, Inches),
1064        Yards => (value * 36.0, Inches),
1065    };
1066    let (base, base_unit) = match (base_unit, to) {
1067        (Millimeters, Inches) | (Millimeters, Feet) | (Millimeters, Yards) => (base / 25.4, Inches),
1068        (Inches, Millimeters) | (Inches, Centimeters) | (Inches, Meters) => (base * 25.4, Millimeters),
1069        _ => (base, base_unit),
1070    };
1071
1072    let value = match (base_unit, to) {
1073        (Millimeters, Millimeters) => base,
1074        (Millimeters, Centimeters) => base / 10.0,
1075        (Millimeters, Meters) => base / 1000.0,
1076        (Inches, Inches) => base,
1077        (Inches, Feet) => base / 12.0,
1078        (Inches, Yards) => base / 36.0,
1079        _ => unreachable!(),
1080    };
1081
1082    (value, to)
1083}
1084
1085pub fn adjust_angle(from: UnitAngle, value: f64, to: UnitAngle) -> (f64, UnitAngle) {
1086    use std::f64::consts::PI;
1087
1088    use UnitAngle::*;
1089
1090    let value = match (from, to) {
1091        (Degrees, Degrees) => value,
1092        (Degrees, Radians) => (value / 180.0) * PI,
1093        (Radians, Degrees) => 180.0 * value / PI,
1094        (Radians, Radians) => value,
1095    };
1096
1097    (value, to)
1098}
1099
1100pub(super) fn length_from_str(s: &str, source_range: SourceRange) -> Result<UnitLength, KclError> {
1101    // We don't use `from_str` here because we want to be more flexible about the input we accept.
1102    match s {
1103        "mm" => Ok(UnitLength::Millimeters),
1104        "cm" => Ok(UnitLength::Centimeters),
1105        "m" => Ok(UnitLength::Meters),
1106        "inch" | "in" => Ok(UnitLength::Inches),
1107        "ft" => Ok(UnitLength::Feet),
1108        "yd" => Ok(UnitLength::Yards),
1109        value => Err(KclError::new_semantic(KclErrorDetails::new(
1110            format!("Unexpected value for length units: `{value}`; expected one of `mm`, `cm`, `m`, `in`, `ft`, `yd`"),
1111            vec![source_range],
1112        ))),
1113    }
1114}
1115
1116pub(super) fn angle_from_str(s: &str, source_range: SourceRange) -> Result<UnitAngle, KclError> {
1117    UnitAngle::from_str(s).map_err(|_| {
1118        KclError::new_semantic(KclErrorDetails::new(
1119            format!("Unexpected value for angle units: `{s}`; expected one of `deg`, `rad`"),
1120            vec![source_range],
1121        ))
1122    })
1123}
1124
1125#[derive(Debug, Clone)]
1126pub struct CoercionError {
1127    pub found: Option<RuntimeType>,
1128    pub explicit_coercion: Option<String>,
1129}
1130
1131impl CoercionError {
1132    fn with_explicit(mut self, c: String) -> Self {
1133        self.explicit_coercion = Some(c);
1134        self
1135    }
1136}
1137
1138impl From<&'_ KclValue> for CoercionError {
1139    fn from(value: &'_ KclValue) -> Self {
1140        CoercionError {
1141            found: value.principal_type(),
1142            explicit_coercion: None,
1143        }
1144    }
1145}
1146
1147impl KclValue {
1148    /// True if `self` has a type which is a subtype of `ty` without coercion.
1149    pub fn has_type(&self, ty: &RuntimeType) -> bool {
1150        let Some(self_ty) = self.principal_type() else {
1151            return false;
1152        };
1153
1154        self_ty.subtype(ty)
1155    }
1156
1157    /// Coerce `self` to a new value which has `ty` as its closest supertype.
1158    ///
1159    /// If the result is Ok, then:
1160    ///   - result.principal_type().unwrap().subtype(ty)
1161    ///
1162    /// If self.principal_type() == ty then result == self
1163    pub fn coerce(
1164        &self,
1165        ty: &RuntimeType,
1166        convert_units: bool,
1167        exec_state: &mut ExecState,
1168    ) -> Result<KclValue, CoercionError> {
1169        match self {
1170            KclValue::Tuple { value, .. }
1171                if value.len() == 1
1172                    && !matches!(ty, RuntimeType::Primitive(PrimitiveType::Any) | RuntimeType::Tuple(..)) =>
1173            {
1174                if let Ok(coerced) = value[0].coerce(ty, convert_units, exec_state) {
1175                    return Ok(coerced);
1176                }
1177            }
1178            KclValue::HomArray { value, .. }
1179                if value.len() == 1
1180                    && !matches!(ty, RuntimeType::Primitive(PrimitiveType::Any) | RuntimeType::Array(..)) =>
1181            {
1182                if let Ok(coerced) = value[0].coerce(ty, convert_units, exec_state) {
1183                    return Ok(coerced);
1184                }
1185            }
1186            _ => {}
1187        }
1188
1189        match ty {
1190            RuntimeType::Primitive(ty) => self.coerce_to_primitive_type(ty, convert_units, exec_state),
1191            RuntimeType::Array(ty, len) => self.coerce_to_array_type(ty, convert_units, *len, exec_state, false),
1192            RuntimeType::Tuple(tys) => self.coerce_to_tuple_type(tys, convert_units, exec_state),
1193            RuntimeType::Union(tys) => self.coerce_to_union_type(tys, convert_units, exec_state),
1194            RuntimeType::Object(tys, constrainable) => {
1195                self.coerce_to_object_type(tys, *constrainable, convert_units, exec_state)
1196            }
1197        }
1198    }
1199
1200    fn coerce_to_primitive_type(
1201        &self,
1202        ty: &PrimitiveType,
1203        convert_units: bool,
1204        exec_state: &mut ExecState,
1205    ) -> Result<KclValue, CoercionError> {
1206        match ty {
1207            PrimitiveType::Any => Ok(self.clone()),
1208            PrimitiveType::None => match self {
1209                KclValue::KclNone { .. } => Ok(self.clone()),
1210                _ => Err(self.into()),
1211            },
1212            PrimitiveType::Number(ty) => {
1213                if convert_units {
1214                    return ty.coerce(self);
1215                }
1216
1217                // Instead of converting units, reinterpret the number as having
1218                // different units.
1219                //
1220                // If the user is explicitly specifying units, treat the value
1221                // as having had its units erased, rather than forcing the user
1222                // to explicitly erase them.
1223                if let KclValue::Number { value: n, meta, .. } = &self
1224                    && ty.is_fully_specified()
1225                {
1226                    let value = KclValue::Number {
1227                        ty: NumericType::Any,
1228                        value: *n,
1229                        meta: meta.clone(),
1230                    };
1231                    return ty.coerce(&value);
1232                }
1233                ty.coerce(self)
1234            }
1235            PrimitiveType::String => match self {
1236                KclValue::String { .. } => Ok(self.clone()),
1237                _ => Err(self.into()),
1238            },
1239            PrimitiveType::Boolean => match self {
1240                KclValue::Bool { .. } => Ok(self.clone()),
1241                _ => Err(self.into()),
1242            },
1243            PrimitiveType::GdtAnnotation => match self {
1244                KclValue::GdtAnnotation { .. } => Ok(self.clone()),
1245                _ => Err(self.into()),
1246            },
1247            PrimitiveType::Sketch => match self {
1248                KclValue::Sketch { .. } => Ok(self.clone()),
1249                _ => Err(self.into()),
1250            },
1251            PrimitiveType::Solid => match self {
1252                KclValue::Solid { .. } => Ok(self.clone()),
1253                _ => Err(self.into()),
1254            },
1255            PrimitiveType::Plane => {
1256                match self {
1257                    KclValue::String { value: s, .. }
1258                        if [
1259                            "xy", "xz", "yz", "-xy", "-xz", "-yz", "XY", "XZ", "YZ", "-XY", "-XZ", "-YZ",
1260                        ]
1261                        .contains(&&**s) =>
1262                    {
1263                        Ok(self.clone())
1264                    }
1265                    KclValue::Plane { .. } => Ok(self.clone()),
1266                    KclValue::Object { value, meta, .. } => {
1267                        let origin = value
1268                            .get("origin")
1269                            .and_then(Point3d::from_kcl_val)
1270                            .ok_or(CoercionError::from(self))?;
1271                        let x_axis = value
1272                            .get("xAxis")
1273                            .and_then(Point3d::from_kcl_val)
1274                            .ok_or(CoercionError::from(self))?;
1275                        let y_axis = value
1276                            .get("yAxis")
1277                            .and_then(Point3d::from_kcl_val)
1278                            .ok_or(CoercionError::from(self))?;
1279                        let z_axis = x_axis.axes_cross_product(&y_axis);
1280
1281                        if value.get("zAxis").is_some() {
1282                            exec_state.warn(CompilationError::err(
1283                            self.into(),
1284                            "Object with a zAxis field is being coerced into a plane, but the zAxis is ignored.",
1285                        ), annotations::WARN_IGNORED_Z_AXIS);
1286                        }
1287
1288                        let id = exec_state.mod_local.id_generator.next_uuid();
1289                        let plane = Plane {
1290                            id,
1291                            artifact_id: id.into(),
1292                            info: PlaneInfo {
1293                                origin,
1294                                x_axis: x_axis.normalize(),
1295                                y_axis: y_axis.normalize(),
1296                                z_axis: z_axis.normalize(),
1297                            },
1298                            value: super::PlaneType::Uninit,
1299                            meta: meta.clone(),
1300                        };
1301
1302                        Ok(KclValue::Plane { value: Box::new(plane) })
1303                    }
1304                    _ => Err(self.into()),
1305                }
1306            }
1307            PrimitiveType::Face => match self {
1308                KclValue::Face { .. } => Ok(self.clone()),
1309                _ => Err(self.into()),
1310            },
1311            PrimitiveType::Helix => match self {
1312                KclValue::Helix { .. } => Ok(self.clone()),
1313                _ => Err(self.into()),
1314            },
1315            PrimitiveType::Edge => match self {
1316                KclValue::Uuid { .. } => Ok(self.clone()),
1317                KclValue::TagIdentifier { .. } => Ok(self.clone()),
1318                _ => Err(self.into()),
1319            },
1320            PrimitiveType::TaggedEdge => match self {
1321                KclValue::TagIdentifier { .. } => Ok(self.clone()),
1322                _ => Err(self.into()),
1323            },
1324            PrimitiveType::TaggedFace => match self {
1325                KclValue::TagIdentifier { .. } => Ok(self.clone()),
1326                s @ KclValue::String { value, .. } if ["start", "end", "START", "END"].contains(&&**value) => {
1327                    Ok(s.clone())
1328                }
1329                _ => Err(self.into()),
1330            },
1331            PrimitiveType::Axis2d => match self {
1332                KclValue::Object {
1333                    value: values, meta, ..
1334                } => {
1335                    if values
1336                        .get("origin")
1337                        .ok_or(CoercionError::from(self))?
1338                        .has_type(&RuntimeType::point2d())
1339                        && values
1340                            .get("direction")
1341                            .ok_or(CoercionError::from(self))?
1342                            .has_type(&RuntimeType::point2d())
1343                    {
1344                        return Ok(self.clone());
1345                    }
1346
1347                    let origin = values.get("origin").ok_or(self.into()).and_then(|p| {
1348                        p.coerce_to_array_type(
1349                            &RuntimeType::length(),
1350                            convert_units,
1351                            ArrayLen::Known(2),
1352                            exec_state,
1353                            true,
1354                        )
1355                    })?;
1356                    let direction = values.get("direction").ok_or(self.into()).and_then(|p| {
1357                        p.coerce_to_array_type(
1358                            &RuntimeType::length(),
1359                            convert_units,
1360                            ArrayLen::Known(2),
1361                            exec_state,
1362                            true,
1363                        )
1364                    })?;
1365
1366                    Ok(KclValue::Object {
1367                        value: [("origin".to_owned(), origin), ("direction".to_owned(), direction)].into(),
1368                        meta: meta.clone(),
1369                        constrainable: false,
1370                    })
1371                }
1372                _ => Err(self.into()),
1373            },
1374            PrimitiveType::Axis3d => match self {
1375                KclValue::Object {
1376                    value: values, meta, ..
1377                } => {
1378                    if values
1379                        .get("origin")
1380                        .ok_or(CoercionError::from(self))?
1381                        .has_type(&RuntimeType::point3d())
1382                        && values
1383                            .get("direction")
1384                            .ok_or(CoercionError::from(self))?
1385                            .has_type(&RuntimeType::point3d())
1386                    {
1387                        return Ok(self.clone());
1388                    }
1389
1390                    let origin = values.get("origin").ok_or(self.into()).and_then(|p| {
1391                        p.coerce_to_array_type(
1392                            &RuntimeType::length(),
1393                            convert_units,
1394                            ArrayLen::Known(3),
1395                            exec_state,
1396                            true,
1397                        )
1398                    })?;
1399                    let direction = values.get("direction").ok_or(self.into()).and_then(|p| {
1400                        p.coerce_to_array_type(
1401                            &RuntimeType::length(),
1402                            convert_units,
1403                            ArrayLen::Known(3),
1404                            exec_state,
1405                            true,
1406                        )
1407                    })?;
1408
1409                    Ok(KclValue::Object {
1410                        value: [("origin".to_owned(), origin), ("direction".to_owned(), direction)].into(),
1411                        meta: meta.clone(),
1412                        constrainable: false,
1413                    })
1414                }
1415                _ => Err(self.into()),
1416            },
1417            PrimitiveType::ImportedGeometry => match self {
1418                KclValue::ImportedGeometry { .. } => Ok(self.clone()),
1419                _ => Err(self.into()),
1420            },
1421            PrimitiveType::Function => match self {
1422                KclValue::Function { .. } => Ok(self.clone()),
1423                _ => Err(self.into()),
1424            },
1425            PrimitiveType::TagDecl => match self {
1426                KclValue::TagDeclarator { .. } => Ok(self.clone()),
1427                _ => Err(self.into()),
1428            },
1429        }
1430    }
1431
1432    fn coerce_to_array_type(
1433        &self,
1434        ty: &RuntimeType,
1435        convert_units: bool,
1436        len: ArrayLen,
1437        exec_state: &mut ExecState,
1438        allow_shrink: bool,
1439    ) -> Result<KclValue, CoercionError> {
1440        match self {
1441            KclValue::HomArray { value, ty: aty, .. } => {
1442                let satisfied_len = len.satisfied(value.len(), allow_shrink);
1443
1444                if aty.subtype(ty) {
1445                    // If the element type is a subtype of the target type and
1446                    // the length constraint is satisfied, we can just return
1447                    // the values unchanged, only adjusting the length. The new
1448                    // array element type should preserve its type because the
1449                    // target type oftentimes includes an unknown type as a way
1450                    // to say that the caller doesn't care.
1451                    return satisfied_len
1452                        .map(|len| KclValue::HomArray {
1453                            value: value[..len].to_vec(),
1454                            ty: aty.clone(),
1455                        })
1456                        .ok_or(self.into());
1457                }
1458
1459                // Ignore the array type, and coerce the elements of the array.
1460                if let Some(satisfied_len) = satisfied_len {
1461                    let value_result = value
1462                        .iter()
1463                        .take(satisfied_len)
1464                        .map(|v| v.coerce(ty, convert_units, exec_state))
1465                        .collect::<Result<Vec<_>, _>>();
1466
1467                    if let Ok(value) = value_result {
1468                        // We were able to coerce all the elements.
1469                        return Ok(KclValue::HomArray { value, ty: ty.clone() });
1470                    }
1471                }
1472
1473                // As a last resort, try to flatten the array.
1474                let mut values = Vec::new();
1475                for item in value {
1476                    if let KclValue::HomArray { value: inner_value, .. } = item {
1477                        // Flatten elements.
1478                        for item in inner_value {
1479                            values.push(item.coerce(ty, convert_units, exec_state)?);
1480                        }
1481                    } else {
1482                        values.push(item.coerce(ty, convert_units, exec_state)?);
1483                    }
1484                }
1485
1486                let len = len
1487                    .satisfied(values.len(), allow_shrink)
1488                    .ok_or(CoercionError::from(self))?;
1489
1490                if len > values.len() {
1491                    let message = format!(
1492                        "Internal: Expected coerced array length {len} to be less than or equal to original length {}",
1493                        values.len()
1494                    );
1495                    exec_state.err(CompilationError::err(self.into(), message.clone()));
1496                    #[cfg(debug_assertions)]
1497                    panic!("{message}");
1498                }
1499                values.truncate(len);
1500
1501                Ok(KclValue::HomArray {
1502                    value: values,
1503                    ty: ty.clone(),
1504                })
1505            }
1506            KclValue::Tuple { value, .. } => {
1507                let len = len
1508                    .satisfied(value.len(), allow_shrink)
1509                    .ok_or(CoercionError::from(self))?;
1510                let value = value
1511                    .iter()
1512                    .map(|item| item.coerce(ty, convert_units, exec_state))
1513                    .take(len)
1514                    .collect::<Result<Vec<_>, _>>()?;
1515
1516                Ok(KclValue::HomArray { value, ty: ty.clone() })
1517            }
1518            KclValue::KclNone { .. } if len.satisfied(0, false).is_some() => Ok(KclValue::HomArray {
1519                value: Vec::new(),
1520                ty: ty.clone(),
1521            }),
1522            _ if len.satisfied(1, false).is_some() => self.coerce(ty, convert_units, exec_state),
1523            _ => Err(self.into()),
1524        }
1525    }
1526
1527    fn coerce_to_tuple_type(
1528        &self,
1529        tys: &[RuntimeType],
1530        convert_units: bool,
1531        exec_state: &mut ExecState,
1532    ) -> Result<KclValue, CoercionError> {
1533        match self {
1534            KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } if value.len() == tys.len() => {
1535                let mut result = Vec::new();
1536                for (i, t) in tys.iter().enumerate() {
1537                    result.push(value[i].coerce(t, convert_units, exec_state)?);
1538                }
1539
1540                Ok(KclValue::Tuple {
1541                    value: result,
1542                    meta: Vec::new(),
1543                })
1544            }
1545            KclValue::KclNone { meta, .. } if tys.is_empty() => Ok(KclValue::Tuple {
1546                value: Vec::new(),
1547                meta: meta.clone(),
1548            }),
1549            _ if tys.len() == 1 => self.coerce(&tys[0], convert_units, exec_state),
1550            _ => Err(self.into()),
1551        }
1552    }
1553
1554    fn coerce_to_union_type(
1555        &self,
1556        tys: &[RuntimeType],
1557        convert_units: bool,
1558        exec_state: &mut ExecState,
1559    ) -> Result<KclValue, CoercionError> {
1560        for t in tys {
1561            if let Ok(v) = self.coerce(t, convert_units, exec_state) {
1562                return Ok(v);
1563            }
1564        }
1565
1566        Err(self.into())
1567    }
1568
1569    fn coerce_to_object_type(
1570        &self,
1571        tys: &[(String, RuntimeType)],
1572        constrainable: bool,
1573        _convert_units: bool,
1574        _exec_state: &mut ExecState,
1575    ) -> Result<KclValue, CoercionError> {
1576        match self {
1577            KclValue::Object { value, meta, .. } => {
1578                for (s, t) in tys {
1579                    // TODO coerce fields
1580                    if !value.get(s).ok_or(CoercionError::from(self))?.has_type(t) {
1581                        return Err(self.into());
1582                    }
1583                }
1584                // TODO remove non-required fields
1585                Ok(KclValue::Object {
1586                    value: value.clone(),
1587                    meta: meta.clone(),
1588                    // Note that we don't check for constrainability, coercing to a constrainable object
1589                    // adds that property.
1590                    constrainable,
1591                })
1592            }
1593            KclValue::KclNone { meta, .. } if tys.is_empty() => Ok(KclValue::Object {
1594                value: HashMap::new(),
1595                meta: meta.clone(),
1596                constrainable,
1597            }),
1598            _ => Err(self.into()),
1599        }
1600    }
1601
1602    pub fn principal_type(&self) -> Option<RuntimeType> {
1603        match self {
1604            KclValue::Bool { .. } => Some(RuntimeType::Primitive(PrimitiveType::Boolean)),
1605            KclValue::Number { ty, .. } => Some(RuntimeType::Primitive(PrimitiveType::Number(*ty))),
1606            KclValue::String { .. } => Some(RuntimeType::Primitive(PrimitiveType::String)),
1607            KclValue::SketchVar { value, .. } => Some(RuntimeType::Primitive(PrimitiveType::Number(value.ty))),
1608            KclValue::Object {
1609                value, constrainable, ..
1610            } => {
1611                let properties = value
1612                    .iter()
1613                    .map(|(k, v)| v.principal_type().map(|t| (k.clone(), t)))
1614                    .collect::<Option<Vec<_>>>()?;
1615                Some(RuntimeType::Object(properties, *constrainable))
1616            }
1617            KclValue::GdtAnnotation { .. } => Some(RuntimeType::Primitive(PrimitiveType::GdtAnnotation)),
1618            KclValue::Plane { .. } => Some(RuntimeType::Primitive(PrimitiveType::Plane)),
1619            KclValue::Sketch { .. } => Some(RuntimeType::Primitive(PrimitiveType::Sketch)),
1620            KclValue::Solid { .. } => Some(RuntimeType::Primitive(PrimitiveType::Solid)),
1621            KclValue::Face { .. } => Some(RuntimeType::Primitive(PrimitiveType::Face)),
1622            KclValue::Helix { .. } => Some(RuntimeType::Primitive(PrimitiveType::Helix)),
1623            KclValue::ImportedGeometry(..) => Some(RuntimeType::Primitive(PrimitiveType::ImportedGeometry)),
1624            KclValue::Tuple { value, .. } => Some(RuntimeType::Tuple(
1625                value.iter().map(|v| v.principal_type()).collect::<Option<Vec<_>>>()?,
1626            )),
1627            KclValue::HomArray { ty, value, .. } => {
1628                Some(RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Known(value.len())))
1629            }
1630            KclValue::TagIdentifier(_) => Some(RuntimeType::Primitive(PrimitiveType::TaggedEdge)),
1631            KclValue::TagDeclarator(_) => Some(RuntimeType::Primitive(PrimitiveType::TagDecl)),
1632            KclValue::Uuid { .. } => Some(RuntimeType::Primitive(PrimitiveType::Edge)),
1633            KclValue::Function { .. } => Some(RuntimeType::Primitive(PrimitiveType::Function)),
1634            KclValue::KclNone { .. } => Some(RuntimeType::Primitive(PrimitiveType::None)),
1635            KclValue::Module { .. } | KclValue::Type { .. } => None,
1636        }
1637    }
1638
1639    pub fn principal_type_string(&self) -> String {
1640        if let Some(ty) = self.principal_type() {
1641            return format!("`{ty}`");
1642        }
1643
1644        match self {
1645            KclValue::Module { .. } => "module",
1646            KclValue::KclNone { .. } => "none",
1647            KclValue::Type { .. } => "type",
1648            _ => {
1649                debug_assert!(false);
1650                "<unexpected type>"
1651            }
1652        }
1653        .to_owned()
1654    }
1655}
1656
1657#[cfg(test)]
1658mod test {
1659    use super::*;
1660    use crate::execution::{ExecTestResults, parse_execute};
1661
1662    async fn new_exec_state() -> (crate::ExecutorContext, ExecState) {
1663        let ctx = crate::ExecutorContext::new_mock(None).await;
1664        let exec_state = ExecState::new(&ctx);
1665        (ctx, exec_state)
1666    }
1667
1668    fn values(exec_state: &mut ExecState) -> Vec<KclValue> {
1669        vec![
1670            KclValue::Bool {
1671                value: true,
1672                meta: Vec::new(),
1673            },
1674            KclValue::Number {
1675                value: 1.0,
1676                ty: NumericType::count(),
1677                meta: Vec::new(),
1678            },
1679            KclValue::String {
1680                value: "hello".to_owned(),
1681                meta: Vec::new(),
1682            },
1683            KclValue::Tuple {
1684                value: Vec::new(),
1685                meta: Vec::new(),
1686            },
1687            KclValue::HomArray {
1688                value: Vec::new(),
1689                ty: RuntimeType::solid(),
1690            },
1691            KclValue::Object {
1692                value: crate::execution::KclObjectFields::new(),
1693                meta: Vec::new(),
1694                constrainable: false,
1695            },
1696            KclValue::TagIdentifier(Box::new("foo".parse().unwrap())),
1697            KclValue::TagDeclarator(Box::new(crate::parsing::ast::types::TagDeclarator::new("foo"))),
1698            KclValue::Plane {
1699                value: Box::new(Plane::from_plane_data(crate::std::sketch::PlaneData::XY, exec_state).unwrap()),
1700            },
1701            // No easy way to make a Face, Sketch, Solid, or Helix
1702            KclValue::ImportedGeometry(crate::execution::ImportedGeometry::new(
1703                uuid::Uuid::nil(),
1704                Vec::new(),
1705                Vec::new(),
1706            )),
1707            // Other values don't have types
1708        ]
1709    }
1710
1711    #[track_caller]
1712    fn assert_coerce_results(
1713        value: &KclValue,
1714        super_type: &RuntimeType,
1715        expected_value: &KclValue,
1716        exec_state: &mut ExecState,
1717    ) {
1718        let is_subtype = value == expected_value;
1719        let actual = value.coerce(super_type, true, exec_state).unwrap();
1720        assert_eq!(&actual, expected_value);
1721        assert_eq!(
1722            is_subtype,
1723            value.principal_type().is_some() && value.principal_type().unwrap().subtype(super_type),
1724            "{:?} <: {super_type:?} should be {is_subtype}",
1725            value.principal_type().unwrap()
1726        );
1727        assert!(
1728            expected_value.principal_type().unwrap().subtype(super_type),
1729            "{} <: {super_type}",
1730            expected_value.principal_type().unwrap()
1731        )
1732    }
1733
1734    #[tokio::test(flavor = "multi_thread")]
1735    async fn coerce_idempotent() {
1736        let (ctx, mut exec_state) = new_exec_state().await;
1737        let values = values(&mut exec_state);
1738        for v in &values {
1739            // Identity subtype
1740            let ty = v.principal_type().unwrap();
1741            assert_coerce_results(v, &ty, v, &mut exec_state);
1742
1743            // Union subtype
1744            let uty1 = RuntimeType::Union(vec![ty.clone()]);
1745            let uty2 = RuntimeType::Union(vec![ty.clone(), RuntimeType::Primitive(PrimitiveType::Boolean)]);
1746            assert_coerce_results(v, &uty1, v, &mut exec_state);
1747            assert_coerce_results(v, &uty2, v, &mut exec_state);
1748
1749            // Array subtypes
1750            let aty = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::None);
1751            let aty1 = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Known(1));
1752            let aty0 = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Minimum(1));
1753
1754            match v {
1755                KclValue::HomArray { .. } => {
1756                    // These will not get wrapped if possible.
1757                    assert_coerce_results(
1758                        v,
1759                        &aty,
1760                        &KclValue::HomArray {
1761                            value: vec![],
1762                            ty: ty.clone(),
1763                        },
1764                        &mut exec_state,
1765                    );
1766                    // Coercing an empty array to an array of length 1
1767                    // should fail.
1768                    v.coerce(&aty1, true, &mut exec_state).unwrap_err();
1769                    // Coercing an empty array to an array that's
1770                    // non-empty should fail.
1771                    v.coerce(&aty0, true, &mut exec_state).unwrap_err();
1772                }
1773                KclValue::Tuple { .. } => {}
1774                _ => {
1775                    assert_coerce_results(v, &aty, v, &mut exec_state);
1776                    assert_coerce_results(v, &aty1, v, &mut exec_state);
1777                    assert_coerce_results(v, &aty0, v, &mut exec_state);
1778
1779                    // Tuple subtype
1780                    let tty = RuntimeType::Tuple(vec![ty.clone()]);
1781                    assert_coerce_results(v, &tty, v, &mut exec_state);
1782                }
1783            }
1784        }
1785
1786        for v in &values[1..] {
1787            // Not a subtype
1788            v.coerce(&RuntimeType::Primitive(PrimitiveType::Boolean), true, &mut exec_state)
1789                .unwrap_err();
1790        }
1791        ctx.close().await;
1792    }
1793
1794    #[tokio::test(flavor = "multi_thread")]
1795    async fn coerce_none() {
1796        let (ctx, mut exec_state) = new_exec_state().await;
1797        let none = KclValue::KclNone {
1798            value: crate::parsing::ast::types::KclNone::new(),
1799            meta: Vec::new(),
1800        };
1801
1802        let aty = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::None);
1803        let aty0 = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::Known(0));
1804        let aty1 = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::Known(1));
1805        let aty1p = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::Minimum(1));
1806        assert_coerce_results(
1807            &none,
1808            &aty,
1809            &KclValue::HomArray {
1810                value: Vec::new(),
1811                ty: RuntimeType::solid(),
1812            },
1813            &mut exec_state,
1814        );
1815        assert_coerce_results(
1816            &none,
1817            &aty0,
1818            &KclValue::HomArray {
1819                value: Vec::new(),
1820                ty: RuntimeType::solid(),
1821            },
1822            &mut exec_state,
1823        );
1824        none.coerce(&aty1, true, &mut exec_state).unwrap_err();
1825        none.coerce(&aty1p, true, &mut exec_state).unwrap_err();
1826
1827        let tty = RuntimeType::Tuple(vec![]);
1828        let tty1 = RuntimeType::Tuple(vec![RuntimeType::solid()]);
1829        assert_coerce_results(
1830            &none,
1831            &tty,
1832            &KclValue::Tuple {
1833                value: Vec::new(),
1834                meta: Vec::new(),
1835            },
1836            &mut exec_state,
1837        );
1838        none.coerce(&tty1, true, &mut exec_state).unwrap_err();
1839
1840        let oty = RuntimeType::Object(vec![], false);
1841        assert_coerce_results(
1842            &none,
1843            &oty,
1844            &KclValue::Object {
1845                value: HashMap::new(),
1846                meta: Vec::new(),
1847                constrainable: false,
1848            },
1849            &mut exec_state,
1850        );
1851        ctx.close().await;
1852    }
1853
1854    #[tokio::test(flavor = "multi_thread")]
1855    async fn coerce_record() {
1856        let (ctx, mut exec_state) = new_exec_state().await;
1857
1858        let obj0 = KclValue::Object {
1859            value: HashMap::new(),
1860            meta: Vec::new(),
1861            constrainable: false,
1862        };
1863        let obj1 = KclValue::Object {
1864            value: [(
1865                "foo".to_owned(),
1866                KclValue::Bool {
1867                    value: true,
1868                    meta: Vec::new(),
1869                },
1870            )]
1871            .into(),
1872            meta: Vec::new(),
1873            constrainable: false,
1874        };
1875        let obj2 = KclValue::Object {
1876            value: [
1877                (
1878                    "foo".to_owned(),
1879                    KclValue::Bool {
1880                        value: true,
1881                        meta: Vec::new(),
1882                    },
1883                ),
1884                (
1885                    "bar".to_owned(),
1886                    KclValue::Number {
1887                        value: 0.0,
1888                        ty: NumericType::count(),
1889                        meta: Vec::new(),
1890                    },
1891                ),
1892                (
1893                    "baz".to_owned(),
1894                    KclValue::Number {
1895                        value: 42.0,
1896                        ty: NumericType::count(),
1897                        meta: Vec::new(),
1898                    },
1899                ),
1900            ]
1901            .into(),
1902            meta: Vec::new(),
1903            constrainable: false,
1904        };
1905
1906        let ty0 = RuntimeType::Object(vec![], false);
1907        assert_coerce_results(&obj0, &ty0, &obj0, &mut exec_state);
1908        assert_coerce_results(&obj1, &ty0, &obj1, &mut exec_state);
1909        assert_coerce_results(&obj2, &ty0, &obj2, &mut exec_state);
1910
1911        let ty1 = RuntimeType::Object(
1912            vec![("foo".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean))],
1913            false,
1914        );
1915        obj0.coerce(&ty1, true, &mut exec_state).unwrap_err();
1916        assert_coerce_results(&obj1, &ty1, &obj1, &mut exec_state);
1917        assert_coerce_results(&obj2, &ty1, &obj2, &mut exec_state);
1918
1919        // Different ordering, (TODO - test for covariance once implemented)
1920        let ty2 = RuntimeType::Object(
1921            vec![
1922                (
1923                    "bar".to_owned(),
1924                    RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1925                ),
1926                ("foo".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean)),
1927            ],
1928            false,
1929        );
1930        obj0.coerce(&ty2, true, &mut exec_state).unwrap_err();
1931        obj1.coerce(&ty2, true, &mut exec_state).unwrap_err();
1932        assert_coerce_results(&obj2, &ty2, &obj2, &mut exec_state);
1933
1934        // field not present
1935        let tyq = RuntimeType::Object(
1936            vec![("qux".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean))],
1937            false,
1938        );
1939        obj0.coerce(&tyq, true, &mut exec_state).unwrap_err();
1940        obj1.coerce(&tyq, true, &mut exec_state).unwrap_err();
1941        obj2.coerce(&tyq, true, &mut exec_state).unwrap_err();
1942
1943        // field with different type
1944        let ty1 = RuntimeType::Object(
1945            vec![("bar".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean))],
1946            false,
1947        );
1948        obj2.coerce(&ty1, true, &mut exec_state).unwrap_err();
1949        ctx.close().await;
1950    }
1951
1952    #[tokio::test(flavor = "multi_thread")]
1953    async fn coerce_array() {
1954        let (ctx, mut exec_state) = new_exec_state().await;
1955
1956        let hom_arr = KclValue::HomArray {
1957            value: vec![
1958                KclValue::Number {
1959                    value: 0.0,
1960                    ty: NumericType::count(),
1961                    meta: Vec::new(),
1962                },
1963                KclValue::Number {
1964                    value: 1.0,
1965                    ty: NumericType::count(),
1966                    meta: Vec::new(),
1967                },
1968                KclValue::Number {
1969                    value: 2.0,
1970                    ty: NumericType::count(),
1971                    meta: Vec::new(),
1972                },
1973                KclValue::Number {
1974                    value: 3.0,
1975                    ty: NumericType::count(),
1976                    meta: Vec::new(),
1977                },
1978            ],
1979            ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1980        };
1981        let mixed1 = KclValue::Tuple {
1982            value: vec![
1983                KclValue::Number {
1984                    value: 0.0,
1985                    ty: NumericType::count(),
1986                    meta: Vec::new(),
1987                },
1988                KclValue::Number {
1989                    value: 1.0,
1990                    ty: NumericType::count(),
1991                    meta: Vec::new(),
1992                },
1993            ],
1994            meta: Vec::new(),
1995        };
1996        let mixed2 = KclValue::Tuple {
1997            value: vec![
1998                KclValue::Number {
1999                    value: 0.0,
2000                    ty: NumericType::count(),
2001                    meta: Vec::new(),
2002                },
2003                KclValue::Bool {
2004                    value: true,
2005                    meta: Vec::new(),
2006                },
2007            ],
2008            meta: Vec::new(),
2009        };
2010
2011        // Principal types
2012        let tyh = RuntimeType::Array(
2013            Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2014            ArrayLen::Known(4),
2015        );
2016        let tym1 = RuntimeType::Tuple(vec![
2017            RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2018            RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2019        ]);
2020        let tym2 = RuntimeType::Tuple(vec![
2021            RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2022            RuntimeType::Primitive(PrimitiveType::Boolean),
2023        ]);
2024        assert_coerce_results(&hom_arr, &tyh, &hom_arr, &mut exec_state);
2025        assert_coerce_results(&mixed1, &tym1, &mixed1, &mut exec_state);
2026        assert_coerce_results(&mixed2, &tym2, &mixed2, &mut exec_state);
2027        mixed1.coerce(&tym2, true, &mut exec_state).unwrap_err();
2028        mixed2.coerce(&tym1, true, &mut exec_state).unwrap_err();
2029
2030        // Length subtyping
2031        let tyhn = RuntimeType::Array(
2032            Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2033            ArrayLen::None,
2034        );
2035        let tyh1 = RuntimeType::Array(
2036            Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2037            ArrayLen::Minimum(1),
2038        );
2039        let tyh3 = RuntimeType::Array(
2040            Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2041            ArrayLen::Known(3),
2042        );
2043        let tyhm3 = RuntimeType::Array(
2044            Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2045            ArrayLen::Minimum(3),
2046        );
2047        let tyhm5 = RuntimeType::Array(
2048            Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2049            ArrayLen::Minimum(5),
2050        );
2051        assert_coerce_results(&hom_arr, &tyhn, &hom_arr, &mut exec_state);
2052        assert_coerce_results(&hom_arr, &tyh1, &hom_arr, &mut exec_state);
2053        hom_arr.coerce(&tyh3, true, &mut exec_state).unwrap_err();
2054        assert_coerce_results(&hom_arr, &tyhm3, &hom_arr, &mut exec_state);
2055        hom_arr.coerce(&tyhm5, true, &mut exec_state).unwrap_err();
2056
2057        let hom_arr0 = KclValue::HomArray {
2058            value: vec![],
2059            ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2060        };
2061        assert_coerce_results(&hom_arr0, &tyhn, &hom_arr0, &mut exec_state);
2062        hom_arr0.coerce(&tyh1, true, &mut exec_state).unwrap_err();
2063        hom_arr0.coerce(&tyh3, true, &mut exec_state).unwrap_err();
2064
2065        // Covariance
2066        // let tyh = RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any))), ArrayLen::Known(4));
2067        let tym1 = RuntimeType::Tuple(vec![
2068            RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2069            RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2070        ]);
2071        let tym2 = RuntimeType::Tuple(vec![
2072            RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2073            RuntimeType::Primitive(PrimitiveType::Boolean),
2074        ]);
2075        // TODO implement covariance for homogeneous arrays
2076        // assert_coerce_results(&hom_arr, &tyh, &hom_arr, &mut exec_state);
2077        assert_coerce_results(&mixed1, &tym1, &mixed1, &mut exec_state);
2078        assert_coerce_results(&mixed2, &tym2, &mixed2, &mut exec_state);
2079
2080        // Mixed to homogeneous
2081        let hom_arr_2 = KclValue::HomArray {
2082            value: vec![
2083                KclValue::Number {
2084                    value: 0.0,
2085                    ty: NumericType::count(),
2086                    meta: Vec::new(),
2087                },
2088                KclValue::Number {
2089                    value: 1.0,
2090                    ty: NumericType::count(),
2091                    meta: Vec::new(),
2092                },
2093            ],
2094            ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2095        };
2096        let mixed0 = KclValue::Tuple {
2097            value: vec![],
2098            meta: Vec::new(),
2099        };
2100        assert_coerce_results(&mixed1, &tyhn, &hom_arr_2, &mut exec_state);
2101        assert_coerce_results(&mixed1, &tyh1, &hom_arr_2, &mut exec_state);
2102        assert_coerce_results(&mixed0, &tyhn, &hom_arr0, &mut exec_state);
2103        mixed0.coerce(&tyh, true, &mut exec_state).unwrap_err();
2104        mixed0.coerce(&tyh1, true, &mut exec_state).unwrap_err();
2105
2106        // Homogehous to mixed
2107        assert_coerce_results(&hom_arr_2, &tym1, &mixed1, &mut exec_state);
2108        hom_arr.coerce(&tym1, true, &mut exec_state).unwrap_err();
2109        hom_arr_2.coerce(&tym2, true, &mut exec_state).unwrap_err();
2110
2111        mixed0.coerce(&tym1, true, &mut exec_state).unwrap_err();
2112        mixed0.coerce(&tym2, true, &mut exec_state).unwrap_err();
2113        ctx.close().await;
2114    }
2115
2116    #[tokio::test(flavor = "multi_thread")]
2117    async fn coerce_union() {
2118        let (ctx, mut exec_state) = new_exec_state().await;
2119
2120        // Subtyping smaller unions
2121        assert!(RuntimeType::Union(vec![]).subtype(&RuntimeType::Union(vec![
2122            RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2123            RuntimeType::Primitive(PrimitiveType::Boolean)
2124        ])));
2125        assert!(
2126            RuntimeType::Union(vec![RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any))]).subtype(
2127                &RuntimeType::Union(vec![
2128                    RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2129                    RuntimeType::Primitive(PrimitiveType::Boolean)
2130                ])
2131            )
2132        );
2133        assert!(
2134            RuntimeType::Union(vec![
2135                RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2136                RuntimeType::Primitive(PrimitiveType::Boolean)
2137            ])
2138            .subtype(&RuntimeType::Union(vec![
2139                RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2140                RuntimeType::Primitive(PrimitiveType::Boolean)
2141            ]))
2142        );
2143
2144        // Covariance
2145        let count = KclValue::Number {
2146            value: 1.0,
2147            ty: NumericType::count(),
2148            meta: Vec::new(),
2149        };
2150
2151        let tya = RuntimeType::Union(vec![RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any))]);
2152        let tya2 = RuntimeType::Union(vec![
2153            RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2154            RuntimeType::Primitive(PrimitiveType::Boolean),
2155        ]);
2156        assert_coerce_results(&count, &tya, &count, &mut exec_state);
2157        assert_coerce_results(&count, &tya2, &count, &mut exec_state);
2158
2159        // No matching type
2160        let tyb = RuntimeType::Union(vec![RuntimeType::Primitive(PrimitiveType::Boolean)]);
2161        let tyb2 = RuntimeType::Union(vec![
2162            RuntimeType::Primitive(PrimitiveType::Boolean),
2163            RuntimeType::Primitive(PrimitiveType::String),
2164        ]);
2165        count.coerce(&tyb, true, &mut exec_state).unwrap_err();
2166        count.coerce(&tyb2, true, &mut exec_state).unwrap_err();
2167        ctx.close().await;
2168    }
2169
2170    #[tokio::test(flavor = "multi_thread")]
2171    async fn coerce_axes() {
2172        let (ctx, mut exec_state) = new_exec_state().await;
2173
2174        // Subtyping
2175        assert!(RuntimeType::Primitive(PrimitiveType::Axis2d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis2d)));
2176        assert!(RuntimeType::Primitive(PrimitiveType::Axis3d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis3d)));
2177        assert!(!RuntimeType::Primitive(PrimitiveType::Axis3d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis2d)));
2178        assert!(!RuntimeType::Primitive(PrimitiveType::Axis2d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis3d)));
2179
2180        // Coercion
2181        let a2d = KclValue::Object {
2182            value: [
2183                (
2184                    "origin".to_owned(),
2185                    KclValue::HomArray {
2186                        value: vec![
2187                            KclValue::Number {
2188                                value: 0.0,
2189                                ty: NumericType::mm(),
2190                                meta: Vec::new(),
2191                            },
2192                            KclValue::Number {
2193                                value: 0.0,
2194                                ty: NumericType::mm(),
2195                                meta: Vec::new(),
2196                            },
2197                        ],
2198                        ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
2199                    },
2200                ),
2201                (
2202                    "direction".to_owned(),
2203                    KclValue::HomArray {
2204                        value: vec![
2205                            KclValue::Number {
2206                                value: 1.0,
2207                                ty: NumericType::mm(),
2208                                meta: Vec::new(),
2209                            },
2210                            KclValue::Number {
2211                                value: 0.0,
2212                                ty: NumericType::mm(),
2213                                meta: Vec::new(),
2214                            },
2215                        ],
2216                        ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
2217                    },
2218                ),
2219            ]
2220            .into(),
2221            meta: Vec::new(),
2222            constrainable: false,
2223        };
2224        let a3d = KclValue::Object {
2225            value: [
2226                (
2227                    "origin".to_owned(),
2228                    KclValue::HomArray {
2229                        value: vec![
2230                            KclValue::Number {
2231                                value: 0.0,
2232                                ty: NumericType::mm(),
2233                                meta: Vec::new(),
2234                            },
2235                            KclValue::Number {
2236                                value: 0.0,
2237                                ty: NumericType::mm(),
2238                                meta: Vec::new(),
2239                            },
2240                            KclValue::Number {
2241                                value: 0.0,
2242                                ty: NumericType::mm(),
2243                                meta: Vec::new(),
2244                            },
2245                        ],
2246                        ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
2247                    },
2248                ),
2249                (
2250                    "direction".to_owned(),
2251                    KclValue::HomArray {
2252                        value: vec![
2253                            KclValue::Number {
2254                                value: 1.0,
2255                                ty: NumericType::mm(),
2256                                meta: Vec::new(),
2257                            },
2258                            KclValue::Number {
2259                                value: 0.0,
2260                                ty: NumericType::mm(),
2261                                meta: Vec::new(),
2262                            },
2263                            KclValue::Number {
2264                                value: 1.0,
2265                                ty: NumericType::mm(),
2266                                meta: Vec::new(),
2267                            },
2268                        ],
2269                        ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
2270                    },
2271                ),
2272            ]
2273            .into(),
2274            meta: Vec::new(),
2275            constrainable: false,
2276        };
2277
2278        let ty2d = RuntimeType::Primitive(PrimitiveType::Axis2d);
2279        let ty3d = RuntimeType::Primitive(PrimitiveType::Axis3d);
2280
2281        assert_coerce_results(&a2d, &ty2d, &a2d, &mut exec_state);
2282        assert_coerce_results(&a3d, &ty3d, &a3d, &mut exec_state);
2283        assert_coerce_results(&a3d, &ty2d, &a2d, &mut exec_state);
2284        a2d.coerce(&ty3d, true, &mut exec_state).unwrap_err();
2285        ctx.close().await;
2286    }
2287
2288    #[tokio::test(flavor = "multi_thread")]
2289    async fn coerce_numeric() {
2290        let (ctx, mut exec_state) = new_exec_state().await;
2291
2292        let count = KclValue::Number {
2293            value: 1.0,
2294            ty: NumericType::count(),
2295            meta: Vec::new(),
2296        };
2297        let mm = KclValue::Number {
2298            value: 1.0,
2299            ty: NumericType::mm(),
2300            meta: Vec::new(),
2301        };
2302        let inches = KclValue::Number {
2303            value: 1.0,
2304            ty: NumericType::Known(UnitType::Length(UnitLength::Inches)),
2305            meta: Vec::new(),
2306        };
2307        let rads = KclValue::Number {
2308            value: 1.0,
2309            ty: NumericType::Known(UnitType::Angle(UnitAngle::Radians)),
2310            meta: Vec::new(),
2311        };
2312        let default = KclValue::Number {
2313            value: 1.0,
2314            ty: NumericType::default(),
2315            meta: Vec::new(),
2316        };
2317        let any = KclValue::Number {
2318            value: 1.0,
2319            ty: NumericType::Any,
2320            meta: Vec::new(),
2321        };
2322        let unknown = KclValue::Number {
2323            value: 1.0,
2324            ty: NumericType::Unknown,
2325            meta: Vec::new(),
2326        };
2327
2328        // Trivial coercions
2329        assert_coerce_results(&count, &NumericType::count().into(), &count, &mut exec_state);
2330        assert_coerce_results(&mm, &NumericType::mm().into(), &mm, &mut exec_state);
2331        assert_coerce_results(&any, &NumericType::Any.into(), &any, &mut exec_state);
2332        assert_coerce_results(&unknown, &NumericType::Unknown.into(), &unknown, &mut exec_state);
2333        assert_coerce_results(&default, &NumericType::default().into(), &default, &mut exec_state);
2334
2335        assert_coerce_results(&count, &NumericType::Any.into(), &count, &mut exec_state);
2336        assert_coerce_results(&mm, &NumericType::Any.into(), &mm, &mut exec_state);
2337        assert_coerce_results(&unknown, &NumericType::Any.into(), &unknown, &mut exec_state);
2338        assert_coerce_results(&default, &NumericType::Any.into(), &default, &mut exec_state);
2339
2340        assert_eq!(
2341            default
2342                .coerce(
2343                    &NumericType::Default {
2344                        len: UnitLength::Yards,
2345                        angle: UnitAngle::Degrees,
2346                    }
2347                    .into(),
2348                    true,
2349                    &mut exec_state
2350                )
2351                .unwrap(),
2352            default
2353        );
2354
2355        // No coercion
2356        count
2357            .coerce(&NumericType::mm().into(), true, &mut exec_state)
2358            .unwrap_err();
2359        mm.coerce(&NumericType::count().into(), true, &mut exec_state)
2360            .unwrap_err();
2361        unknown
2362            .coerce(&NumericType::mm().into(), true, &mut exec_state)
2363            .unwrap_err();
2364        unknown
2365            .coerce(&NumericType::default().into(), true, &mut exec_state)
2366            .unwrap_err();
2367
2368        count
2369            .coerce(&NumericType::Unknown.into(), true, &mut exec_state)
2370            .unwrap_err();
2371        mm.coerce(&NumericType::Unknown.into(), true, &mut exec_state)
2372            .unwrap_err();
2373        default
2374            .coerce(&NumericType::Unknown.into(), true, &mut exec_state)
2375            .unwrap_err();
2376
2377        assert_eq!(
2378            inches
2379                .coerce(&NumericType::mm().into(), true, &mut exec_state)
2380                .unwrap()
2381                .as_f64()
2382                .unwrap()
2383                .round(),
2384            25.0
2385        );
2386        assert_eq!(
2387            rads.coerce(
2388                &NumericType::Known(UnitType::Angle(UnitAngle::Degrees)).into(),
2389                true,
2390                &mut exec_state
2391            )
2392            .unwrap()
2393            .as_f64()
2394            .unwrap()
2395            .round(),
2396            57.0
2397        );
2398        assert_eq!(
2399            inches
2400                .coerce(&NumericType::default().into(), true, &mut exec_state)
2401                .unwrap()
2402                .as_f64()
2403                .unwrap()
2404                .round(),
2405            1.0
2406        );
2407        assert_eq!(
2408            rads.coerce(&NumericType::default().into(), true, &mut exec_state)
2409                .unwrap()
2410                .as_f64()
2411                .unwrap()
2412                .round(),
2413            1.0
2414        );
2415        ctx.close().await;
2416    }
2417
2418    #[track_caller]
2419    fn assert_value_and_type(name: &str, result: &ExecTestResults, expected: f64, expected_ty: NumericType) {
2420        let mem = result.exec_state.stack();
2421        match mem
2422            .memory
2423            .get_from(name, result.mem_env, SourceRange::default(), 0)
2424            .unwrap()
2425        {
2426            KclValue::Number { value, ty, .. } => {
2427                assert_eq!(value.round(), expected);
2428                assert_eq!(*ty, expected_ty);
2429            }
2430            _ => unreachable!(),
2431        }
2432    }
2433
2434    #[tokio::test(flavor = "multi_thread")]
2435    async fn combine_numeric() {
2436        let program = r#"a = 5 + 4
2437b = 5 - 2
2438c = 5mm - 2mm + 10mm
2439d = 5mm - 2 + 10
2440e = 5 - 2mm + 10
2441f = 30mm - 1inch
2442
2443g = 2 * 10
2444h = 2 * 10mm
2445i = 2mm * 10mm
2446j = 2_ * 10
2447k = 2_ * 3mm * 3mm
2448
2449l = 1 / 10
2450m = 2mm / 1mm
2451n = 10inch / 2mm
2452o = 3mm / 3
2453p = 3_ / 4
2454q = 4inch / 2_
2455
2456r = min([0, 3, 42])
2457s = min([0, 3mm, -42])
2458t = min([100, 3in, 142mm])
2459u = min([3rad, 4in])
2460"#;
2461
2462        let result = parse_execute(program).await.unwrap();
2463        assert_eq!(
2464            result.exec_state.errors().len(),
2465            5,
2466            "errors: {:?}",
2467            result.exec_state.errors()
2468        );
2469
2470        assert_value_and_type("a", &result, 9.0, NumericType::default());
2471        assert_value_and_type("b", &result, 3.0, NumericType::default());
2472        assert_value_and_type("c", &result, 13.0, NumericType::mm());
2473        assert_value_and_type("d", &result, 13.0, NumericType::mm());
2474        assert_value_and_type("e", &result, 13.0, NumericType::mm());
2475        assert_value_and_type("f", &result, 5.0, NumericType::mm());
2476
2477        assert_value_and_type("g", &result, 20.0, NumericType::default());
2478        assert_value_and_type("h", &result, 20.0, NumericType::mm());
2479        assert_value_and_type("i", &result, 20.0, NumericType::Unknown);
2480        assert_value_and_type("j", &result, 20.0, NumericType::default());
2481        assert_value_and_type("k", &result, 18.0, NumericType::Unknown);
2482
2483        assert_value_and_type("l", &result, 0.0, NumericType::default());
2484        assert_value_and_type("m", &result, 2.0, NumericType::count());
2485        assert_value_and_type("n", &result, 5.0, NumericType::Unknown);
2486        assert_value_and_type("o", &result, 1.0, NumericType::mm());
2487        assert_value_and_type("p", &result, 1.0, NumericType::count());
2488        assert_value_and_type(
2489            "q",
2490            &result,
2491            2.0,
2492            NumericType::Known(UnitType::Length(UnitLength::Inches)),
2493        );
2494
2495        assert_value_and_type("r", &result, 0.0, NumericType::default());
2496        assert_value_and_type("s", &result, -42.0, NumericType::mm());
2497        assert_value_and_type("t", &result, 3.0, NumericType::Unknown);
2498        assert_value_and_type("u", &result, 3.0, NumericType::Unknown);
2499    }
2500
2501    #[tokio::test(flavor = "multi_thread")]
2502    async fn bad_typed_arithmetic() {
2503        let program = r#"
2504a = 1rad
2505b = 180 / PI * a + 360
2506"#;
2507
2508        let result = parse_execute(program).await.unwrap();
2509
2510        assert_value_and_type("a", &result, 1.0, NumericType::radians());
2511        assert_value_and_type("b", &result, 417.0, NumericType::Unknown);
2512    }
2513
2514    #[tokio::test(flavor = "multi_thread")]
2515    async fn cos_coercions() {
2516        let program = r#"
2517a = cos(units::toRadians(30deg))
2518b = 3 / a
2519c = cos(30deg)
2520d = cos(1rad)
2521"#;
2522
2523        let result = parse_execute(program).await.unwrap();
2524        assert!(
2525            result.exec_state.errors().is_empty(),
2526            "{:?}",
2527            result.exec_state.errors()
2528        );
2529
2530        assert_value_and_type("a", &result, 1.0, NumericType::default());
2531        assert_value_and_type("b", &result, 3.0, NumericType::default());
2532        assert_value_and_type("c", &result, 1.0, NumericType::default());
2533        assert_value_and_type("d", &result, 1.0, NumericType::default());
2534    }
2535
2536    #[tokio::test(flavor = "multi_thread")]
2537    async fn coerce_nested_array() {
2538        let (ctx, mut exec_state) = new_exec_state().await;
2539
2540        let mixed1 = KclValue::HomArray {
2541            value: vec![
2542                KclValue::Number {
2543                    value: 0.0,
2544                    ty: NumericType::count(),
2545                    meta: Vec::new(),
2546                },
2547                KclValue::Number {
2548                    value: 1.0,
2549                    ty: NumericType::count(),
2550                    meta: Vec::new(),
2551                },
2552                KclValue::HomArray {
2553                    value: vec![
2554                        KclValue::Number {
2555                            value: 2.0,
2556                            ty: NumericType::count(),
2557                            meta: Vec::new(),
2558                        },
2559                        KclValue::Number {
2560                            value: 3.0,
2561                            ty: NumericType::count(),
2562                            meta: Vec::new(),
2563                        },
2564                    ],
2565                    ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2566                },
2567            ],
2568            ty: RuntimeType::any(),
2569        };
2570
2571        // Principal types
2572        let tym1 = RuntimeType::Array(
2573            Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2574            ArrayLen::Minimum(1),
2575        );
2576
2577        let result = KclValue::HomArray {
2578            value: vec![
2579                KclValue::Number {
2580                    value: 0.0,
2581                    ty: NumericType::count(),
2582                    meta: Vec::new(),
2583                },
2584                KclValue::Number {
2585                    value: 1.0,
2586                    ty: NumericType::count(),
2587                    meta: Vec::new(),
2588                },
2589                KclValue::Number {
2590                    value: 2.0,
2591                    ty: NumericType::count(),
2592                    meta: Vec::new(),
2593                },
2594                KclValue::Number {
2595                    value: 3.0,
2596                    ty: NumericType::count(),
2597                    meta: Vec::new(),
2598                },
2599            ],
2600            ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2601        };
2602        assert_coerce_results(&mixed1, &tym1, &result, &mut exec_state);
2603        ctx.close().await;
2604    }
2605}