kcl_lib/execution/
kcl_value.rs

1use std::{collections::HashMap, fmt};
2
3use anyhow::Result;
4use schemars::JsonSchema;
5use serde::{Deserialize, Serialize};
6
7use super::{
8    memory::{self, EnvironmentRef},
9    MetaSettings, Point3d,
10};
11use crate::{
12    errors::KclErrorDetails,
13    execution::{
14        ExecState, ExecutorContext, Face, Helix, ImportedGeometry, Metadata, Plane, Sketch, Solid, TagIdentifier,
15    },
16    parsing::{
17        ast::types::{
18            DefaultParamVal, FunctionExpression, KclNone, Literal, LiteralValue, Node,
19            PrimitiveType as AstPrimitiveType, TagDeclarator, TagNode, Type,
20        },
21        token::NumericSuffix,
22    },
23    std::{
24        args::{Arg, FromKclValue},
25        StdFnProps,
26    },
27    CompilationError, KclError, ModuleId, SourceRange,
28};
29
30pub type KclObjectFields = HashMap<String, KclValue>;
31
32/// Any KCL value.
33#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
34#[ts(export)]
35#[serde(tag = "type")]
36pub enum KclValue {
37    Uuid {
38        value: ::uuid::Uuid,
39        #[serde(skip)]
40        meta: Vec<Metadata>,
41    },
42    Bool {
43        value: bool,
44        #[serde(skip)]
45        meta: Vec<Metadata>,
46    },
47    Number {
48        value: f64,
49        ty: NumericType,
50        #[serde(skip)]
51        meta: Vec<Metadata>,
52    },
53    String {
54        value: String,
55        #[serde(skip)]
56        meta: Vec<Metadata>,
57    },
58    MixedArray {
59        value: Vec<KclValue>,
60        #[serde(skip)]
61        meta: Vec<Metadata>,
62    },
63    // An array where all values have a shared type (not necessarily the same principal type).
64    HomArray {
65        value: Vec<KclValue>,
66        // The type of values, not the array type.
67        #[serde(skip)]
68        ty: PrimitiveType,
69    },
70    Object {
71        value: KclObjectFields,
72        #[serde(skip)]
73        meta: Vec<Metadata>,
74    },
75    TagIdentifier(Box<TagIdentifier>),
76    TagDeclarator(crate::parsing::ast::types::BoxNode<TagDeclarator>),
77    Plane {
78        value: Box<Plane>,
79    },
80    Face {
81        value: Box<Face>,
82    },
83    Sketch {
84        value: Box<Sketch>,
85    },
86    Solid {
87        value: Box<Solid>,
88    },
89    Helix {
90        value: Box<Helix>,
91    },
92    ImportedGeometry(ImportedGeometry),
93    #[ts(skip)]
94    Function {
95        #[serde(skip)]
96        value: FunctionSource,
97        #[serde(skip)]
98        meta: Vec<Metadata>,
99    },
100    Module {
101        value: ModuleId,
102        #[serde(skip)]
103        meta: Vec<Metadata>,
104    },
105    #[ts(skip)]
106    Type {
107        #[serde(skip)]
108        value: Option<(PrimitiveType, StdFnProps)>,
109        #[serde(skip)]
110        meta: Vec<Metadata>,
111    },
112    KclNone {
113        value: KclNone,
114        #[serde(skip)]
115        meta: Vec<Metadata>,
116    },
117}
118
119#[derive(Debug, Clone, PartialEq, Default)]
120pub enum FunctionSource {
121    #[default]
122    None,
123    Std {
124        func: crate::std::StdFn,
125        props: StdFnProps,
126    },
127    User {
128        ast: crate::parsing::ast::types::BoxNode<FunctionExpression>,
129        settings: MetaSettings,
130        memory: EnvironmentRef,
131    },
132}
133
134impl JsonSchema for FunctionSource {
135    fn schema_name() -> String {
136        "FunctionSource".to_owned()
137    }
138
139    fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
140        // TODO: Actually generate a reasonable schema.
141        gen.subschema_for::<()>()
142    }
143}
144
145impl From<Vec<Sketch>> for KclValue {
146    fn from(mut eg: Vec<Sketch>) -> Self {
147        if eg.len() == 1 {
148            KclValue::Sketch {
149                value: Box::new(eg.pop().unwrap()),
150            }
151        } else {
152            KclValue::HomArray {
153                value: eg
154                    .into_iter()
155                    .map(|s| KclValue::Sketch { value: Box::new(s) })
156                    .collect(),
157                ty: crate::execution::PrimitiveType::Sketch,
158            }
159        }
160    }
161}
162
163impl From<Vec<Solid>> for KclValue {
164    fn from(mut eg: Vec<Solid>) -> Self {
165        if eg.len() == 1 {
166            KclValue::Solid {
167                value: Box::new(eg.pop().unwrap()),
168            }
169        } else {
170            KclValue::HomArray {
171                value: eg.into_iter().map(|s| KclValue::Solid { value: Box::new(s) }).collect(),
172                ty: crate::execution::PrimitiveType::Solid,
173            }
174        }
175    }
176}
177
178impl From<KclValue> for Vec<SourceRange> {
179    fn from(item: KclValue) -> Self {
180        match item {
181            KclValue::TagDeclarator(t) => vec![SourceRange::new(t.start, t.end, t.module_id)],
182            KclValue::TagIdentifier(t) => to_vec_sr(&t.meta),
183            KclValue::Solid { value } => to_vec_sr(&value.meta),
184            KclValue::Sketch { value } => to_vec_sr(&value.meta),
185            KclValue::Helix { value } => to_vec_sr(&value.meta),
186            KclValue::ImportedGeometry(i) => to_vec_sr(&i.meta),
187            KclValue::Function { meta, .. } => to_vec_sr(&meta),
188            KclValue::Plane { value } => to_vec_sr(&value.meta),
189            KclValue::Face { value } => to_vec_sr(&value.meta),
190            KclValue::Bool { meta, .. } => to_vec_sr(&meta),
191            KclValue::Number { meta, .. } => to_vec_sr(&meta),
192            KclValue::String { meta, .. } => to_vec_sr(&meta),
193            KclValue::MixedArray { meta, .. } => to_vec_sr(&meta),
194            KclValue::HomArray { value, .. } => value.iter().flat_map(Into::<Vec<SourceRange>>::into).collect(),
195            KclValue::Object { meta, .. } => to_vec_sr(&meta),
196            KclValue::Module { meta, .. } => to_vec_sr(&meta),
197            KclValue::Uuid { meta, .. } => to_vec_sr(&meta),
198            KclValue::Type { meta, .. } => to_vec_sr(&meta),
199            KclValue::KclNone { meta, .. } => to_vec_sr(&meta),
200        }
201    }
202}
203
204fn to_vec_sr(meta: &[Metadata]) -> Vec<SourceRange> {
205    meta.iter().map(|m| m.source_range).collect()
206}
207
208impl From<&KclValue> for Vec<SourceRange> {
209    fn from(item: &KclValue) -> Self {
210        match item {
211            KclValue::TagDeclarator(t) => vec![SourceRange::new(t.start, t.end, t.module_id)],
212            KclValue::TagIdentifier(t) => to_vec_sr(&t.meta),
213            KclValue::Solid { value } => to_vec_sr(&value.meta),
214            KclValue::Sketch { value } => to_vec_sr(&value.meta),
215            KclValue::Helix { value } => to_vec_sr(&value.meta),
216            KclValue::ImportedGeometry(i) => to_vec_sr(&i.meta),
217            KclValue::Function { meta, .. } => to_vec_sr(meta),
218            KclValue::Plane { value } => to_vec_sr(&value.meta),
219            KclValue::Face { value } => to_vec_sr(&value.meta),
220            KclValue::Bool { meta, .. } => to_vec_sr(meta),
221            KclValue::Number { meta, .. } => to_vec_sr(meta),
222            KclValue::String { meta, .. } => to_vec_sr(meta),
223            KclValue::Uuid { meta, .. } => to_vec_sr(meta),
224            KclValue::MixedArray { meta, .. } => to_vec_sr(meta),
225            KclValue::HomArray { value, .. } => value.iter().flat_map(Into::<Vec<SourceRange>>::into).collect(),
226            KclValue::Object { meta, .. } => to_vec_sr(meta),
227            KclValue::Module { meta, .. } => to_vec_sr(meta),
228            KclValue::KclNone { meta, .. } => to_vec_sr(meta),
229            KclValue::Type { meta, .. } => to_vec_sr(meta),
230        }
231    }
232}
233
234impl From<&KclValue> for SourceRange {
235    fn from(item: &KclValue) -> Self {
236        let v: Vec<_> = item.into();
237        v.into_iter().next().unwrap_or_default()
238    }
239}
240
241impl KclValue {
242    pub(crate) fn metadata(&self) -> Vec<Metadata> {
243        match self {
244            KclValue::Uuid { value: _, meta } => meta.clone(),
245            KclValue::Bool { value: _, meta } => meta.clone(),
246            KclValue::Number { meta, .. } => meta.clone(),
247            KclValue::String { value: _, meta } => meta.clone(),
248            KclValue::MixedArray { value: _, meta } => meta.clone(),
249            KclValue::HomArray { value, .. } => value.iter().flat_map(|v| v.metadata()).collect(),
250            KclValue::Object { value: _, meta } => meta.clone(),
251            KclValue::TagIdentifier(x) => x.meta.clone(),
252            KclValue::TagDeclarator(x) => vec![x.metadata()],
253            KclValue::Plane { value } => value.meta.clone(),
254            KclValue::Face { value } => value.meta.clone(),
255            KclValue::Sketch { value } => value.meta.clone(),
256            KclValue::Solid { value } => value.meta.clone(),
257            KclValue::Helix { value } => value.meta.clone(),
258            KclValue::ImportedGeometry(x) => x.meta.clone(),
259            KclValue::Function { meta, .. } => meta.clone(),
260            KclValue::Module { meta, .. } => meta.clone(),
261            KclValue::KclNone { meta, .. } => meta.clone(),
262            KclValue::Type { meta, .. } => meta.clone(),
263        }
264    }
265
266    pub(crate) fn function_def_source_range(&self) -> Option<SourceRange> {
267        let KclValue::Function {
268            value: FunctionSource::User { ast, .. },
269            ..
270        } = self
271        else {
272            return None;
273        };
274        // TODO: It would be nice if we could extract the source range starting
275        // at the fn, but that's the variable declaration.
276        Some(ast.as_source_range())
277    }
278
279    #[allow(unused)]
280    pub(crate) fn none() -> Self {
281        Self::KclNone {
282            value: Default::default(),
283            meta: Default::default(),
284        }
285    }
286
287    /// Human readable type name used in error messages.  Should not be relied
288    /// on for program logic.
289    pub(crate) fn human_friendly_type(&self) -> &'static str {
290        match self {
291            KclValue::Uuid { .. } => "Unique ID (uuid)",
292            KclValue::TagDeclarator(_) => "TagDeclarator",
293            KclValue::TagIdentifier(_) => "TagIdentifier",
294            KclValue::Solid { .. } => "Solid",
295            KclValue::Sketch { .. } => "Sketch",
296            KclValue::Helix { .. } => "Helix",
297            KclValue::ImportedGeometry(_) => "ImportedGeometry",
298            KclValue::Function { .. } => "Function",
299            KclValue::Plane { .. } => "Plane",
300            KclValue::Face { .. } => "Face",
301            KclValue::Bool { .. } => "boolean (true/false value)",
302            KclValue::Number { .. } => "number",
303            KclValue::String { .. } => "string (text)",
304            KclValue::MixedArray { .. } => "array (list)",
305            KclValue::HomArray { .. } => "array (list)",
306            KclValue::Object { .. } => "object",
307            KclValue::Module { .. } => "module",
308            KclValue::Type { .. } => "type",
309            KclValue::KclNone { .. } => "None",
310        }
311    }
312
313    pub(crate) fn from_literal(literal: Node<Literal>, settings: &MetaSettings) -> Self {
314        let meta = vec![literal.metadata()];
315        match literal.inner.value {
316            LiteralValue::Number { value, suffix } => KclValue::Number {
317                value,
318                meta,
319                ty: NumericType::from_parsed(suffix, settings),
320            },
321            LiteralValue::String(value) => KclValue::String { value, meta },
322            LiteralValue::Bool(value) => KclValue::Bool { value, meta },
323        }
324    }
325
326    pub(crate) fn from_default_param(param: DefaultParamVal, settings: &MetaSettings) -> Self {
327        match param {
328            DefaultParamVal::Literal(lit) => Self::from_literal(lit, settings),
329            DefaultParamVal::KclNone(none) => KclValue::KclNone {
330                value: none,
331                meta: Default::default(),
332            },
333        }
334    }
335
336    pub(crate) fn map_env_ref(&self, old_env: usize, new_env: usize) -> Self {
337        let mut result = self.clone();
338        if let KclValue::Function {
339            value: FunctionSource::User { ref mut memory, .. },
340            ..
341        } = result
342        {
343            memory.replace_env(old_env, new_env);
344        }
345        result
346    }
347
348    /// Put the number into a KCL value.
349    pub const fn from_number(f: f64, meta: Vec<Metadata>) -> Self {
350        Self::Number {
351            value: f,
352            meta,
353            ty: NumericType::Unknown,
354        }
355    }
356
357    pub const fn from_number_with_type(f: f64, ty: NumericType, meta: Vec<Metadata>) -> Self {
358        Self::Number { value: f, meta, ty }
359    }
360
361    /// Put the point into a KCL value.
362    pub fn from_point2d(p: [f64; 2], ty: NumericType, meta: Vec<Metadata>) -> Self {
363        Self::MixedArray {
364            value: vec![
365                Self::Number {
366                    value: p[0],
367                    meta: meta.clone(),
368                    ty: ty.clone(),
369                },
370                Self::Number {
371                    value: p[1],
372                    meta: meta.clone(),
373                    ty,
374                },
375            ],
376            meta,
377        }
378    }
379
380    pub(crate) fn as_usize(&self) -> Option<usize> {
381        match self {
382            KclValue::Number { value, .. } => crate::try_f64_to_usize(*value),
383            _ => None,
384        }
385    }
386
387    pub fn as_int(&self) -> Option<i64> {
388        match self {
389            KclValue::Number { value, .. } => crate::try_f64_to_i64(*value),
390            _ => None,
391        }
392    }
393
394    pub fn as_object(&self) -> Option<&KclObjectFields> {
395        if let KclValue::Object { value, meta: _ } = &self {
396            Some(value)
397        } else {
398            None
399        }
400    }
401
402    pub fn into_object(self) -> Option<KclObjectFields> {
403        if let KclValue::Object { value, meta: _ } = self {
404            Some(value)
405        } else {
406            None
407        }
408    }
409
410    pub fn as_str(&self) -> Option<&str> {
411        if let KclValue::String { value, meta: _ } = &self {
412            Some(value)
413        } else {
414            None
415        }
416    }
417
418    pub fn as_array(&self) -> Option<&[KclValue]> {
419        if let KclValue::MixedArray { value, meta: _ } = &self {
420            Some(value)
421        } else {
422            None
423        }
424    }
425
426    pub fn as_point2d(&self) -> Option<[f64; 2]> {
427        let arr = self.as_array()?;
428        if arr.len() != 2 {
429            return None;
430        }
431        let x = arr[0].as_f64()?;
432        let y = arr[1].as_f64()?;
433        Some([x, y])
434    }
435
436    pub fn as_uuid(&self) -> Option<uuid::Uuid> {
437        if let KclValue::Uuid { value, meta: _ } = &self {
438            Some(*value)
439        } else {
440            None
441        }
442    }
443
444    pub fn as_plane(&self) -> Option<&Plane> {
445        if let KclValue::Plane { value } = &self {
446            Some(value)
447        } else {
448            None
449        }
450    }
451
452    pub fn as_solid(&self) -> Option<&Solid> {
453        if let KclValue::Solid { value } = &self {
454            Some(value)
455        } else {
456            None
457        }
458    }
459
460    pub fn as_sketch(&self) -> Option<&Sketch> {
461        if let KclValue::Sketch { value } = self {
462            Some(value)
463        } else {
464            None
465        }
466    }
467
468    pub fn as_mut_sketch(&mut self) -> Option<&mut Sketch> {
469        if let KclValue::Sketch { value } = self {
470            Some(value)
471        } else {
472            None
473        }
474    }
475
476    pub fn as_mut_tag(&mut self) -> Option<&mut TagIdentifier> {
477        if let KclValue::TagIdentifier(value) = self {
478            Some(value)
479        } else {
480            None
481        }
482    }
483    pub fn as_f64(&self) -> Option<f64> {
484        if let KclValue::Number { value, .. } = &self {
485            Some(*value)
486        } else {
487            None
488        }
489    }
490
491    pub fn as_bool(&self) -> Option<bool> {
492        if let KclValue::Bool { value, meta: _ } = &self {
493            Some(*value)
494        } else {
495            None
496        }
497    }
498
499    /// If this value fits in a u32, return it.
500    pub fn get_u32(&self, source_ranges: Vec<SourceRange>) -> Result<u32, KclError> {
501        let u = self.as_int().and_then(|n| u64::try_from(n).ok()).ok_or_else(|| {
502            KclError::Semantic(KclErrorDetails {
503                message: "Expected an integer >= 0".to_owned(),
504                source_ranges: source_ranges.clone(),
505            })
506        })?;
507        u32::try_from(u).map_err(|_| {
508            KclError::Semantic(KclErrorDetails {
509                message: "Number was too big".to_owned(),
510                source_ranges,
511            })
512        })
513    }
514
515    /// If this value is of type function, return it.
516    pub fn get_function(&self) -> Option<&FunctionSource> {
517        match self {
518            KclValue::Function { value, .. } => Some(value),
519            _ => None,
520        }
521    }
522
523    /// Get a tag identifier from a memory item.
524    pub fn get_tag_identifier(&self) -> Result<TagIdentifier, KclError> {
525        match self {
526            KclValue::TagIdentifier(t) => Ok(*t.clone()),
527            _ => Err(KclError::Semantic(KclErrorDetails {
528                message: format!("Not a tag identifier: {:?}", self),
529                source_ranges: self.clone().into(),
530            })),
531        }
532    }
533
534    /// Get a tag declarator from a memory item.
535    pub fn get_tag_declarator(&self) -> Result<TagNode, KclError> {
536        match self {
537            KclValue::TagDeclarator(t) => Ok((**t).clone()),
538            _ => Err(KclError::Semantic(KclErrorDetails {
539                message: format!("Not a tag declarator: {:?}", self),
540                source_ranges: self.clone().into(),
541            })),
542        }
543    }
544
545    /// If this KCL value is a bool, retrieve it.
546    pub fn get_bool(&self) -> Result<bool, KclError> {
547        let Self::Bool { value: b, .. } = self else {
548            return Err(KclError::Type(KclErrorDetails {
549                source_ranges: self.into(),
550                message: format!("Expected bool, found {}", self.human_friendly_type()),
551            }));
552        };
553        Ok(*b)
554    }
555
556    /// True if `self` has a type which is a subtype of `ty` without coercion.
557    pub fn has_type(&self, ty: &RuntimeType) -> bool {
558        let Some(self_ty) = self.principal_type() else {
559            return false;
560        };
561
562        self_ty.subtype(ty)
563    }
564
565    /// Coerce `self` to a new value which has `ty` as it's closest supertype.
566    ///
567    /// If the result is Some, then:
568    ///   - result.principal_type().unwrap().subtype(ty)
569    ///
570    /// If self.principal_type() == ty then result == self
571    pub fn coerce(&self, ty: &RuntimeType, exec_state: &mut ExecState) -> Option<KclValue> {
572        match ty {
573            RuntimeType::Primitive(ty) => self.coerce_to_primitive_type(ty, exec_state),
574            RuntimeType::Array(ty, len) => self.coerce_to_array_type(ty, *len, exec_state),
575            RuntimeType::Tuple(tys) => self.coerce_to_tuple_type(tys, exec_state),
576            RuntimeType::Union(tys) => self.coerce_to_union_type(tys, exec_state),
577            RuntimeType::Object(tys) => self.coerce_to_object_type(tys, exec_state),
578        }
579    }
580
581    fn coerce_to_primitive_type(&self, ty: &PrimitiveType, exec_state: &mut ExecState) -> Option<KclValue> {
582        let value = match self {
583            KclValue::MixedArray { value, .. } | KclValue::HomArray { value, .. } if value.len() == 1 => &value[0],
584            _ => self,
585        };
586        match ty {
587            // TODO numeric type coercions
588            PrimitiveType::Number(_ty) => match value {
589                KclValue::Number { .. } => Some(value.clone()),
590                _ => None,
591            },
592            PrimitiveType::String => match value {
593                KclValue::String { .. } => Some(value.clone()),
594                _ => None,
595            },
596            PrimitiveType::Boolean => match value {
597                KclValue::Bool { .. } => Some(value.clone()),
598                _ => None,
599            },
600            PrimitiveType::Sketch => match value {
601                KclValue::Sketch { .. } => Some(value.clone()),
602                _ => None,
603            },
604            PrimitiveType::Solid => match value {
605                KclValue::Solid { .. } => Some(value.clone()),
606                _ => None,
607            },
608            PrimitiveType::Plane => match value {
609                KclValue::Plane { .. } => Some(value.clone()),
610                KclValue::Object { value, meta } => {
611                    let origin = value.get("origin").and_then(Point3d::from_kcl_val)?;
612                    let x_axis = value.get("xAxis").and_then(Point3d::from_kcl_val)?;
613                    let y_axis = value.get("yAxis").and_then(Point3d::from_kcl_val)?;
614                    let z_axis = value.get("zAxis").and_then(Point3d::from_kcl_val)?;
615
616                    let id = exec_state.mod_local.id_generator.next_uuid();
617                    let plane = Plane {
618                        id,
619                        artifact_id: id.into(),
620                        origin,
621                        x_axis,
622                        y_axis,
623                        z_axis,
624                        value: super::PlaneType::Uninit,
625                        // TODO use length unit from origin
626                        units: exec_state.length_unit(),
627                        meta: meta.clone(),
628                    };
629
630                    Some(KclValue::Plane { value: Box::new(plane) })
631                }
632                _ => None,
633            },
634            PrimitiveType::ImportedGeometry => match value {
635                KclValue::ImportedGeometry { .. } => Some(value.clone()),
636                _ => None,
637            },
638        }
639    }
640
641    fn coerce_to_array_type(&self, ty: &PrimitiveType, len: ArrayLen, exec_state: &mut ExecState) -> Option<KclValue> {
642        match self {
643            KclValue::HomArray { value, ty: aty } => {
644                // TODO could check types of values individually
645                if aty != ty {
646                    return None;
647                }
648
649                let value = match len {
650                    ArrayLen::None => value.clone(),
651                    ArrayLen::NonEmpty => {
652                        if value.is_empty() {
653                            return None;
654                        }
655
656                        value.clone()
657                    }
658                    ArrayLen::Known(n) => {
659                        if n != value.len() {
660                            return None;
661                        }
662
663                        value[..n].to_vec()
664                    }
665                };
666
667                Some(KclValue::HomArray { value, ty: ty.clone() })
668            }
669            KclValue::MixedArray { value, .. } => {
670                let value = match len {
671                    ArrayLen::None => value.clone(),
672                    ArrayLen::NonEmpty => {
673                        if value.is_empty() {
674                            return None;
675                        }
676
677                        value.clone()
678                    }
679                    ArrayLen::Known(n) => {
680                        if n != value.len() {
681                            return None;
682                        }
683
684                        value[..n].to_vec()
685                    }
686                };
687
688                let rt = RuntimeType::Primitive(ty.clone());
689                let value = value
690                    .iter()
691                    .map(|v| v.coerce(&rt, exec_state))
692                    .collect::<Option<Vec<_>>>()?;
693
694                Some(KclValue::HomArray { value, ty: ty.clone() })
695            }
696            KclValue::KclNone { .. } if len.satisfied(0) => Some(KclValue::HomArray {
697                value: Vec::new(),
698                ty: ty.clone(),
699            }),
700            value if len.satisfied(1) => {
701                if value.has_type(&RuntimeType::Primitive(ty.clone())) {
702                    Some(KclValue::HomArray {
703                        value: vec![value.clone()],
704                        ty: ty.clone(),
705                    })
706                } else {
707                    None
708                }
709            }
710            _ => None,
711        }
712    }
713
714    fn coerce_to_tuple_type(&self, tys: &[PrimitiveType], exec_state: &mut ExecState) -> Option<KclValue> {
715        match self {
716            KclValue::MixedArray { value, .. } | KclValue::HomArray { value, .. } => {
717                if value.len() < tys.len() {
718                    return None;
719                }
720                let mut result = Vec::new();
721                for (i, t) in tys.iter().enumerate() {
722                    result.push(value[i].coerce_to_primitive_type(t, exec_state)?);
723                }
724
725                Some(KclValue::MixedArray {
726                    value: result,
727                    meta: Vec::new(),
728                })
729            }
730            KclValue::KclNone { meta, .. } if tys.is_empty() => Some(KclValue::MixedArray {
731                value: Vec::new(),
732                meta: meta.clone(),
733            }),
734            value if tys.len() == 1 => {
735                if value.has_type(&RuntimeType::Primitive(tys[0].clone())) {
736                    Some(KclValue::MixedArray {
737                        value: vec![value.clone()],
738                        meta: Vec::new(),
739                    })
740                } else {
741                    None
742                }
743            }
744            _ => None,
745        }
746    }
747
748    fn coerce_to_union_type(&self, tys: &[RuntimeType], exec_state: &mut ExecState) -> Option<KclValue> {
749        for t in tys {
750            if let Some(v) = self.coerce(t, exec_state) {
751                return Some(v);
752            }
753        }
754
755        None
756    }
757
758    fn coerce_to_object_type(&self, tys: &[(String, RuntimeType)], _exec_state: &mut ExecState) -> Option<KclValue> {
759        match self {
760            KclValue::Object { value, .. } => {
761                for (s, t) in tys {
762                    // TODO coerce fields
763                    if !value.get(s)?.has_type(t) {
764                        return None;
765                    }
766                }
767                // TODO remove non-required fields
768                Some(self.clone())
769            }
770            _ => None,
771        }
772    }
773
774    pub fn principal_type(&self) -> Option<RuntimeType> {
775        match self {
776            KclValue::Bool { .. } => Some(RuntimeType::Primitive(PrimitiveType::Boolean)),
777            KclValue::Number { ty, .. } => Some(RuntimeType::Primitive(PrimitiveType::Number(ty.clone()))),
778            KclValue::String { .. } => Some(RuntimeType::Primitive(PrimitiveType::String)),
779            KclValue::Object { value, .. } => {
780                let properties = value
781                    .iter()
782                    .map(|(k, v)| v.principal_type().map(|t| (k.clone(), t)))
783                    .collect::<Option<Vec<_>>>()?;
784                Some(RuntimeType::Object(properties))
785            }
786            KclValue::Plane { .. } => Some(RuntimeType::Primitive(PrimitiveType::Plane)),
787            KclValue::Sketch { .. } => Some(RuntimeType::Primitive(PrimitiveType::Sketch)),
788            KclValue::Solid { .. } => Some(RuntimeType::Primitive(PrimitiveType::Solid)),
789            KclValue::ImportedGeometry(..) => Some(RuntimeType::Primitive(PrimitiveType::ImportedGeometry)),
790            KclValue::MixedArray { value, .. } => Some(RuntimeType::Tuple(
791                value
792                    .iter()
793                    .map(|v| v.principal_type().and_then(RuntimeType::primitive))
794                    .collect::<Option<Vec<_>>>()?,
795            )),
796            KclValue::HomArray { ty, value, .. } => Some(RuntimeType::Array(ty.clone(), ArrayLen::Known(value.len()))),
797            KclValue::Face { .. } => None,
798            KclValue::Helix { .. }
799            | KclValue::Function { .. }
800            | KclValue::Module { .. }
801            | KclValue::TagIdentifier(_)
802            | KclValue::TagDeclarator(_)
803            | KclValue::KclNone { .. }
804            | KclValue::Type { .. }
805            | KclValue::Uuid { .. } => None,
806        }
807    }
808
809    /// If this memory item is a function, call it with the given arguments, return its val as Ok.
810    /// If it's not a function, return Err.
811    pub async fn call_fn(
812        &self,
813        args: Vec<Arg>,
814        exec_state: &mut ExecState,
815        ctx: ExecutorContext,
816        source_range: SourceRange,
817    ) -> Result<Option<KclValue>, KclError> {
818        match self {
819            KclValue::Function {
820                value: FunctionSource::Std { func, props },
821                ..
822            } => {
823                if props.deprecated {
824                    exec_state.warn(CompilationError::err(
825                        source_range,
826                        format!(
827                            "`{}` is deprecated, see the docs for a recommended replacement",
828                            props.name
829                        ),
830                    ));
831                }
832                exec_state.mut_stack().push_new_env_for_rust_call();
833                let args = crate::std::Args::new(
834                    args,
835                    source_range,
836                    ctx.clone(),
837                    exec_state
838                        .mod_local
839                        .pipe_value
840                        .clone()
841                        .map(|v| Arg::new(v, source_range)),
842                );
843                let result = func(exec_state, args).await.map(Some);
844                exec_state.mut_stack().pop_env();
845                result
846            }
847            KclValue::Function {
848                value: FunctionSource::User { ast, memory, .. },
849                ..
850            } => crate::execution::exec_ast::call_user_defined_function(args, *memory, ast, exec_state, &ctx).await,
851            _ => Err(KclError::Semantic(KclErrorDetails {
852                message: "cannot call this because it isn't a function".to_string(),
853                source_ranges: vec![source_range],
854            })),
855        }
856    }
857
858    /// If this is a function, call it by applying keyword arguments.
859    /// If it's not a function, returns an error.
860    pub async fn call_fn_kw(
861        &self,
862        args: crate::std::Args,
863        exec_state: &mut ExecState,
864        ctx: ExecutorContext,
865        callsite: SourceRange,
866    ) -> Result<Option<KclValue>, KclError> {
867        match self {
868            KclValue::Function {
869                value: FunctionSource::Std { func: _, props },
870                ..
871            } => {
872                if props.deprecated {
873                    exec_state.warn(CompilationError::err(
874                        callsite,
875                        format!(
876                            "`{}` is deprecated, see the docs for a recommended replacement",
877                            props.name
878                        ),
879                    ));
880                }
881                todo!("Implement KCL stdlib fns with keyword args");
882            }
883            KclValue::Function {
884                value: FunctionSource::User { ast, memory, .. },
885                ..
886            } => {
887                crate::execution::exec_ast::call_user_defined_function_kw(args.kw_args, *memory, ast, exec_state, &ctx)
888                    .await
889            }
890            _ => Err(KclError::Semantic(KclErrorDetails {
891                message: "cannot call this because it isn't a function".to_string(),
892                source_ranges: vec![callsite],
893            })),
894        }
895    }
896
897    pub fn value_str(&self) -> Option<String> {
898        match self {
899            KclValue::Bool { value, .. } => Some(format!("{value}")),
900            KclValue::Number { value, .. } => Some(format!("{value}")),
901            KclValue::String { value, .. } => Some(format!("'{value}'")),
902            KclValue::Uuid { value, .. } => Some(format!("{value}")),
903            KclValue::TagDeclarator(tag) => Some(format!("${}", tag.name)),
904            KclValue::TagIdentifier(tag) => Some(format!("${}", tag.value)),
905            // TODO better Array and Object stringification
906            KclValue::MixedArray { .. } => Some("[...]".to_owned()),
907            KclValue::HomArray { .. } => Some("[...]".to_owned()),
908            KclValue::Object { .. } => Some("{ ... }".to_owned()),
909            KclValue::Module { .. }
910            | KclValue::Solid { .. }
911            | KclValue::Sketch { .. }
912            | KclValue::Helix { .. }
913            | KclValue::ImportedGeometry(_)
914            | KclValue::Function { .. }
915            | KclValue::Plane { .. }
916            | KclValue::Face { .. }
917            | KclValue::KclNone { .. }
918            | KclValue::Type { .. } => None,
919        }
920    }
921}
922
923#[derive(Debug, Clone, PartialEq)]
924pub enum RuntimeType {
925    Primitive(PrimitiveType),
926    Array(PrimitiveType, ArrayLen),
927    Union(Vec<RuntimeType>),
928    Tuple(Vec<PrimitiveType>),
929    Object(Vec<(String, RuntimeType)>),
930}
931
932impl RuntimeType {
933    pub fn from_parsed(
934        value: Type,
935        exec_state: &mut ExecState,
936        source_range: SourceRange,
937    ) -> Result<Option<Self>, CompilationError> {
938        Ok(match value {
939            Type::Primitive(pt) => {
940                PrimitiveType::from_parsed(pt, exec_state, source_range)?.map(RuntimeType::Primitive)
941            }
942            Type::Array(pt) => {
943                PrimitiveType::from_parsed(pt, exec_state, source_range)?.map(|t| RuntimeType::Array(t, ArrayLen::None))
944            }
945            Type::Object { properties } => properties
946                .into_iter()
947                .map(|p| {
948                    let pt = match p.type_ {
949                        Some(t) => t,
950                        None => return Ok(None),
951                    };
952                    Ok(RuntimeType::from_parsed(pt.inner, exec_state, source_range)?
953                        .map(|ty| (p.identifier.inner.name, ty)))
954                })
955                .collect::<Result<Option<Vec<_>>, CompilationError>>()?
956                .map(RuntimeType::Object),
957        })
958    }
959
960    pub fn human_friendly_type(&self) -> String {
961        match self {
962            RuntimeType::Primitive(ty) => ty.to_string(),
963            RuntimeType::Array(ty, ArrayLen::None) => format!("an array of {}", ty.display_multiple()),
964            RuntimeType::Array(ty, ArrayLen::NonEmpty) => format!("one or more {}", ty.display_multiple()),
965            RuntimeType::Array(ty, ArrayLen::Known(n)) => format!("an array of {n} {}", ty.display_multiple()),
966            RuntimeType::Union(tys) => tys
967                .iter()
968                .map(Self::human_friendly_type)
969                .collect::<Vec<_>>()
970                .join(" or "),
971            RuntimeType::Tuple(tys) => format!(
972                "an array with values of types ({})",
973                tys.iter().map(PrimitiveType::to_string).collect::<Vec<_>>().join(", ")
974            ),
975            RuntimeType::Object(_) => format!("an object with fields {}", self),
976        }
977    }
978
979    // Subtype with no coercion, including refining numeric types.
980    fn subtype(&self, sup: &RuntimeType) -> bool {
981        use RuntimeType::*;
982
983        match (self, sup) {
984            (Primitive(t1), Primitive(t2)) => t1 == t2,
985            // TODO arrays could be covariant
986            (Array(t1, l1), Array(t2, l2)) => t1 == t2 && l1.subtype(*l2),
987            (Tuple(t1), Tuple(t2)) => t1 == t2,
988            (Tuple(t1), Array(t2, l2)) => (l2.satisfied(t1.len())) && t1.iter().all(|t| t == t2),
989            (Union(ts1), Union(ts2)) => ts1.iter().all(|t| ts2.contains(t)),
990            (t1, Union(ts2)) => ts2.contains(t1),
991            // TODO record subtyping - subtype can be larger, fields can be covariant.
992            (Object(t1), Object(t2)) => t1 == t2,
993            _ => false,
994        }
995    }
996
997    fn primitive(self) -> Option<PrimitiveType> {
998        match self {
999            RuntimeType::Primitive(t) => Some(t),
1000            _ => None,
1001        }
1002    }
1003}
1004
1005impl fmt::Display for RuntimeType {
1006    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1007        match self {
1008            RuntimeType::Primitive(t) => t.fmt(f),
1009            RuntimeType::Array(t, l) => match l {
1010                ArrayLen::None => write!(f, "[{t}]"),
1011                ArrayLen::NonEmpty => write!(f, "[{t}; 1+]"),
1012                ArrayLen::Known(n) => write!(f, "[{t}; {n}]"),
1013            },
1014            RuntimeType::Tuple(ts) => write!(
1015                f,
1016                "[{}]",
1017                ts.iter().map(|t| t.to_string()).collect::<Vec<_>>().join(", ")
1018            ),
1019            RuntimeType::Union(ts) => write!(
1020                f,
1021                "{}",
1022                ts.iter().map(|t| t.to_string()).collect::<Vec<_>>().join(" | ")
1023            ),
1024            RuntimeType::Object(items) => write!(
1025                f,
1026                "{{ {} }}",
1027                items
1028                    .iter()
1029                    .map(|(n, t)| format!("{n}: {t}"))
1030                    .collect::<Vec<_>>()
1031                    .join(", ")
1032            ),
1033        }
1034    }
1035}
1036
1037#[derive(Debug, Clone, Copy, PartialEq)]
1038pub enum ArrayLen {
1039    None,
1040    NonEmpty,
1041    Known(usize),
1042}
1043
1044impl ArrayLen {
1045    pub fn subtype(self, other: ArrayLen) -> bool {
1046        match (self, other) {
1047            (_, ArrayLen::None) => true,
1048            (ArrayLen::NonEmpty, ArrayLen::NonEmpty) => true,
1049            (ArrayLen::Known(size), ArrayLen::NonEmpty) if size > 0 => true,
1050            (ArrayLen::Known(s1), ArrayLen::Known(s2)) if s1 == s2 => true,
1051            _ => false,
1052        }
1053    }
1054
1055    /// True if the length constraint is satisfied by the supplied length.
1056    fn satisfied(self, len: usize) -> bool {
1057        match self {
1058            ArrayLen::None => true,
1059            ArrayLen::NonEmpty => len > 0,
1060            ArrayLen::Known(s) => len == s,
1061        }
1062    }
1063}
1064
1065#[derive(Debug, Clone, PartialEq)]
1066pub enum PrimitiveType {
1067    Number(NumericType),
1068    String,
1069    Boolean,
1070    Sketch,
1071    Solid,
1072    Plane,
1073    ImportedGeometry,
1074}
1075
1076impl PrimitiveType {
1077    fn from_parsed(
1078        value: AstPrimitiveType,
1079        exec_state: &mut ExecState,
1080        source_range: SourceRange,
1081    ) -> Result<Option<Self>, CompilationError> {
1082        Ok(match value {
1083            AstPrimitiveType::String => Some(PrimitiveType::String),
1084            AstPrimitiveType::Boolean => Some(PrimitiveType::Boolean),
1085            AstPrimitiveType::Number(suffix) => Some(PrimitiveType::Number(NumericType::from_parsed(
1086                suffix,
1087                &exec_state.mod_local.settings,
1088            ))),
1089            AstPrimitiveType::Named(name) => {
1090                let ty_val = exec_state
1091                    .stack()
1092                    .get(&format!("{}{}", memory::TYPE_PREFIX, name.name), source_range)
1093                    .map_err(|_| CompilationError::err(source_range, format!("Unknown type: {}", name.name)))?;
1094
1095                let (ty, _) = match ty_val {
1096                    KclValue::Type { value: Some(ty), .. } => ty,
1097                    _ => unreachable!(),
1098                };
1099
1100                Some(ty.clone())
1101            }
1102            _ => None,
1103        })
1104    }
1105
1106    fn display_multiple(&self) -> String {
1107        match self {
1108            PrimitiveType::Number(NumericType::Known(unit)) => format!("numbers({unit})"),
1109            PrimitiveType::Number(_) => "numbers".to_owned(),
1110            PrimitiveType::String => "strings".to_owned(),
1111            PrimitiveType::Boolean => "bools".to_owned(),
1112            PrimitiveType::Sketch => "Sketches".to_owned(),
1113            PrimitiveType::Solid => "Solids".to_owned(),
1114            PrimitiveType::Plane => "Planes".to_owned(),
1115            PrimitiveType::ImportedGeometry => "imported geometries".to_owned(),
1116        }
1117    }
1118}
1119
1120impl fmt::Display for PrimitiveType {
1121    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1122        match self {
1123            PrimitiveType::Number(NumericType::Known(unit)) => write!(f, "number({unit})"),
1124            PrimitiveType::Number(_) => write!(f, "number"),
1125            PrimitiveType::String => write!(f, "string"),
1126            PrimitiveType::Boolean => write!(f, "bool"),
1127            PrimitiveType::Sketch => write!(f, "Sketch"),
1128            PrimitiveType::Solid => write!(f, "Solid"),
1129            PrimitiveType::Plane => write!(f, "Plane"),
1130            PrimitiveType::ImportedGeometry => write!(f, "imported geometry"),
1131        }
1132    }
1133}
1134
1135#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
1136#[ts(export)]
1137#[serde(tag = "type")]
1138pub enum NumericType {
1139    // Specified by the user (directly or indirectly)
1140    Known(UnitType),
1141    // Unspecified, using defaults
1142    Default { len: UnitLen, angle: UnitAngle },
1143    // Exceeded the ability of the type system to track.
1144    Unknown,
1145    // Type info has been explicitly cast away.
1146    Any,
1147}
1148
1149impl NumericType {
1150    pub fn count() -> Self {
1151        NumericType::Known(UnitType::Count)
1152    }
1153
1154    /// Combine two types when we expect them to be equal.
1155    pub fn combine_eq(self, other: &NumericType) -> NumericType {
1156        if &self == other {
1157            self
1158        } else {
1159            NumericType::Unknown
1160        }
1161    }
1162
1163    /// Combine n types when we expect them to be equal.
1164    ///
1165    /// Precondition: tys.len() > 0
1166    pub fn combine_n_eq(tys: &[NumericType]) -> NumericType {
1167        let ty0 = tys[0].clone();
1168        for t in &tys[1..] {
1169            if t != &ty0 {
1170                return NumericType::Unknown;
1171            }
1172        }
1173        ty0
1174    }
1175
1176    /// Combine two types in addition-like operations.
1177    pub fn combine_add(a: NumericType, b: NumericType) -> NumericType {
1178        if a == b {
1179            return a;
1180        }
1181        NumericType::Unknown
1182    }
1183
1184    /// Combine two types in multiplication-like operations.
1185    pub fn combine_mul(a: NumericType, b: NumericType) -> NumericType {
1186        if a == NumericType::count() {
1187            return b;
1188        }
1189        if b == NumericType::count() {
1190            return a;
1191        }
1192        NumericType::Unknown
1193    }
1194
1195    /// Combine two types in division-like operations.
1196    pub fn combine_div(a: NumericType, b: NumericType) -> NumericType {
1197        if b == NumericType::count() {
1198            return a;
1199        }
1200        NumericType::Unknown
1201    }
1202
1203    pub fn from_parsed(suffix: NumericSuffix, settings: &super::MetaSettings) -> Self {
1204        match suffix {
1205            NumericSuffix::None => NumericType::Default {
1206                len: settings.default_length_units,
1207                angle: settings.default_angle_units,
1208            },
1209            NumericSuffix::Count => NumericType::Known(UnitType::Count),
1210            NumericSuffix::Mm => NumericType::Known(UnitType::Length(UnitLen::Mm)),
1211            NumericSuffix::Cm => NumericType::Known(UnitType::Length(UnitLen::Cm)),
1212            NumericSuffix::M => NumericType::Known(UnitType::Length(UnitLen::M)),
1213            NumericSuffix::Inch => NumericType::Known(UnitType::Length(UnitLen::Inches)),
1214            NumericSuffix::Ft => NumericType::Known(UnitType::Length(UnitLen::Feet)),
1215            NumericSuffix::Yd => NumericType::Known(UnitType::Length(UnitLen::Yards)),
1216            NumericSuffix::Deg => NumericType::Known(UnitType::Angle(UnitAngle::Degrees)),
1217            NumericSuffix::Rad => NumericType::Known(UnitType::Angle(UnitAngle::Radians)),
1218        }
1219    }
1220}
1221
1222impl From<UnitLen> for NumericType {
1223    fn from(value: UnitLen) -> Self {
1224        NumericType::Known(UnitType::Length(value))
1225    }
1226}
1227
1228impl From<UnitAngle> for NumericType {
1229    fn from(value: UnitAngle) -> Self {
1230        NumericType::Known(UnitType::Angle(value))
1231    }
1232}
1233
1234#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, ts_rs::TS, JsonSchema)]
1235#[ts(export)]
1236#[serde(tag = "type")]
1237pub enum UnitType {
1238    Count,
1239    Length(UnitLen),
1240    Angle(UnitAngle),
1241}
1242
1243impl std::fmt::Display for UnitType {
1244    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1245        match self {
1246            UnitType::Count => write!(f, "_"),
1247            UnitType::Length(l) => l.fmt(f),
1248            UnitType::Angle(a) => a.fmt(f),
1249        }
1250    }
1251}
1252
1253// TODO called UnitLen so as not to clash with UnitLength in settings)
1254/// A unit of length.
1255#[derive(Debug, Default, Clone, Copy, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Eq)]
1256#[ts(export)]
1257#[serde(tag = "type")]
1258pub enum UnitLen {
1259    #[default]
1260    Mm,
1261    Cm,
1262    M,
1263    Inches,
1264    Feet,
1265    Yards,
1266}
1267
1268impl std::fmt::Display for UnitLen {
1269    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1270        match self {
1271            UnitLen::Mm => write!(f, "mm"),
1272            UnitLen::Cm => write!(f, "cm"),
1273            UnitLen::M => write!(f, "m"),
1274            UnitLen::Inches => write!(f, "in"),
1275            UnitLen::Feet => write!(f, "ft"),
1276            UnitLen::Yards => write!(f, "yd"),
1277        }
1278    }
1279}
1280
1281impl TryFrom<NumericSuffix> for UnitLen {
1282    type Error = ();
1283
1284    fn try_from(suffix: NumericSuffix) -> std::result::Result<Self, Self::Error> {
1285        match suffix {
1286            NumericSuffix::Mm => Ok(Self::Mm),
1287            NumericSuffix::Cm => Ok(Self::Cm),
1288            NumericSuffix::M => Ok(Self::M),
1289            NumericSuffix::Inch => Ok(Self::Inches),
1290            NumericSuffix::Ft => Ok(Self::Feet),
1291            NumericSuffix::Yd => Ok(Self::Yards),
1292            _ => Err(()),
1293        }
1294    }
1295}
1296
1297impl From<crate::UnitLength> for UnitLen {
1298    fn from(unit: crate::UnitLength) -> Self {
1299        match unit {
1300            crate::UnitLength::Cm => UnitLen::Cm,
1301            crate::UnitLength::Ft => UnitLen::Feet,
1302            crate::UnitLength::In => UnitLen::Inches,
1303            crate::UnitLength::M => UnitLen::M,
1304            crate::UnitLength::Mm => UnitLen::Mm,
1305            crate::UnitLength::Yd => UnitLen::Yards,
1306        }
1307    }
1308}
1309
1310impl From<UnitLen> for crate::UnitLength {
1311    fn from(unit: UnitLen) -> Self {
1312        match unit {
1313            UnitLen::Cm => crate::UnitLength::Cm,
1314            UnitLen::Feet => crate::UnitLength::Ft,
1315            UnitLen::Inches => crate::UnitLength::In,
1316            UnitLen::M => crate::UnitLength::M,
1317            UnitLen::Mm => crate::UnitLength::Mm,
1318            UnitLen::Yards => crate::UnitLength::Yd,
1319        }
1320    }
1321}
1322
1323impl From<UnitLen> for kittycad_modeling_cmds::units::UnitLength {
1324    fn from(unit: UnitLen) -> Self {
1325        match unit {
1326            UnitLen::Cm => kittycad_modeling_cmds::units::UnitLength::Centimeters,
1327            UnitLen::Feet => kittycad_modeling_cmds::units::UnitLength::Feet,
1328            UnitLen::Inches => kittycad_modeling_cmds::units::UnitLength::Inches,
1329            UnitLen::M => kittycad_modeling_cmds::units::UnitLength::Meters,
1330            UnitLen::Mm => kittycad_modeling_cmds::units::UnitLength::Millimeters,
1331            UnitLen::Yards => kittycad_modeling_cmds::units::UnitLength::Yards,
1332        }
1333    }
1334}
1335
1336/// A unit of angle.
1337#[derive(Debug, Default, Clone, Copy, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Eq)]
1338#[ts(export)]
1339#[serde(tag = "type")]
1340pub enum UnitAngle {
1341    #[default]
1342    Degrees,
1343    Radians,
1344}
1345
1346impl std::fmt::Display for UnitAngle {
1347    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1348        match self {
1349            UnitAngle::Degrees => write!(f, "deg"),
1350            UnitAngle::Radians => write!(f, "rad"),
1351        }
1352    }
1353}
1354
1355impl TryFrom<NumericSuffix> for UnitAngle {
1356    type Error = ();
1357
1358    fn try_from(suffix: NumericSuffix) -> std::result::Result<Self, Self::Error> {
1359        match suffix {
1360            NumericSuffix::Deg => Ok(Self::Degrees),
1361            NumericSuffix::Rad => Ok(Self::Radians),
1362            _ => Err(()),
1363        }
1364    }
1365}