kcl_lib/execution/
kcl_value.rs

1use std::collections::HashMap;
2
3use anyhow::Result;
4use schemars::JsonSchema;
5use serde::Serialize;
6
7use crate::{
8    CompilationError, KclError, ModuleId, SourceRange,
9    errors::KclErrorDetails,
10    execution::{
11        EnvironmentRef, ExecState, Face, Geometry, GeometryWithImportedGeometry, Helix, ImportedGeometry, MetaSettings,
12        Metadata, Plane, Sketch, Solid, TagIdentifier,
13        annotations::{SETTINGS, SETTINGS_UNIT_LENGTH},
14        types::{NumericType, PrimitiveType, RuntimeType, UnitLen},
15    },
16    parsing::ast::types::{
17        DefaultParamVal, FunctionExpression, KclNone, Literal, LiteralValue, Node, TagDeclarator, TagNode,
18    },
19    std::{StdFnProps, args::TyF64},
20};
21
22pub type KclObjectFields = HashMap<String, KclValue>;
23
24/// Any KCL value.
25#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
26#[ts(export)]
27#[serde(tag = "type")]
28pub enum KclValue {
29    Uuid {
30        value: ::uuid::Uuid,
31        #[serde(skip)]
32        meta: Vec<Metadata>,
33    },
34    Bool {
35        value: bool,
36        #[serde(skip)]
37        meta: Vec<Metadata>,
38    },
39    Number {
40        value: f64,
41        ty: NumericType,
42        #[serde(skip)]
43        meta: Vec<Metadata>,
44    },
45    String {
46        value: String,
47        #[serde(skip)]
48        meta: Vec<Metadata>,
49    },
50    Tuple {
51        value: Vec<KclValue>,
52        #[serde(skip)]
53        meta: Vec<Metadata>,
54    },
55    // An array where all values have a shared type (not necessarily the same principal type).
56    HomArray {
57        value: Vec<KclValue>,
58        // The type of values, not the array type.
59        #[serde(skip)]
60        ty: RuntimeType,
61    },
62    Object {
63        value: KclObjectFields,
64        #[serde(skip)]
65        meta: Vec<Metadata>,
66    },
67    TagIdentifier(Box<TagIdentifier>),
68    TagDeclarator(crate::parsing::ast::types::BoxNode<TagDeclarator>),
69    Plane {
70        value: Box<Plane>,
71    },
72    Face {
73        value: Box<Face>,
74    },
75    Sketch {
76        value: Box<Sketch>,
77    },
78    Solid {
79        value: Box<Solid>,
80    },
81    Helix {
82        value: Box<Helix>,
83    },
84    ImportedGeometry(ImportedGeometry),
85    Function {
86        #[serde(serialize_with = "function_value_stub")]
87        #[ts(type = "null")]
88        value: FunctionSource,
89        #[serde(skip)]
90        meta: Vec<Metadata>,
91    },
92    Module {
93        value: ModuleId,
94        #[serde(skip)]
95        meta: Vec<Metadata>,
96    },
97    #[ts(skip)]
98    Type {
99        #[serde(skip)]
100        value: TypeDef,
101        #[serde(skip)]
102        meta: Vec<Metadata>,
103    },
104    KclNone {
105        value: KclNone,
106        #[serde(skip)]
107        meta: Vec<Metadata>,
108    },
109}
110
111fn function_value_stub<S>(_value: &FunctionSource, serializer: S) -> Result<S::Ok, S::Error>
112where
113    S: serde::Serializer,
114{
115    serializer.serialize_unit()
116}
117
118#[derive(Debug, Clone, PartialEq, Default)]
119pub enum FunctionSource {
120    #[default]
121    None,
122    Std {
123        func: crate::std::StdFn,
124        ast: crate::parsing::ast::types::BoxNode<FunctionExpression>,
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(r#gen: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema {
140        // TODO: Actually generate a reasonable schema.
141        r#gen.subschema_for::<()>()
142    }
143}
144
145#[derive(Debug, Clone, PartialEq)]
146pub enum TypeDef {
147    RustRepr(PrimitiveType, StdFnProps),
148    Alias(RuntimeType),
149}
150
151impl From<Vec<Sketch>> for KclValue {
152    fn from(mut eg: Vec<Sketch>) -> Self {
153        if eg.len() == 1 {
154            KclValue::Sketch {
155                value: Box::new(eg.pop().unwrap()),
156            }
157        } else {
158            KclValue::HomArray {
159                value: eg
160                    .into_iter()
161                    .map(|s| KclValue::Sketch { value: Box::new(s) })
162                    .collect(),
163                ty: RuntimeType::Primitive(PrimitiveType::Sketch),
164            }
165        }
166    }
167}
168
169impl From<Vec<Solid>> for KclValue {
170    fn from(mut eg: Vec<Solid>) -> Self {
171        if eg.len() == 1 {
172            KclValue::Solid {
173                value: Box::new(eg.pop().unwrap()),
174            }
175        } else {
176            KclValue::HomArray {
177                value: eg.into_iter().map(|s| KclValue::Solid { value: Box::new(s) }).collect(),
178                ty: RuntimeType::Primitive(PrimitiveType::Solid),
179            }
180        }
181    }
182}
183
184impl From<KclValue> for Vec<SourceRange> {
185    fn from(item: KclValue) -> Self {
186        match item {
187            KclValue::TagDeclarator(t) => vec![SourceRange::new(t.start, t.end, t.module_id)],
188            KclValue::TagIdentifier(t) => to_vec_sr(&t.meta),
189            KclValue::Solid { value } => to_vec_sr(&value.meta),
190            KclValue::Sketch { value } => to_vec_sr(&value.meta),
191            KclValue::Helix { value } => to_vec_sr(&value.meta),
192            KclValue::ImportedGeometry(i) => to_vec_sr(&i.meta),
193            KclValue::Function { meta, .. } => to_vec_sr(&meta),
194            KclValue::Plane { value } => to_vec_sr(&value.meta),
195            KclValue::Face { value } => to_vec_sr(&value.meta),
196            KclValue::Bool { meta, .. } => to_vec_sr(&meta),
197            KclValue::Number { meta, .. } => to_vec_sr(&meta),
198            KclValue::String { meta, .. } => to_vec_sr(&meta),
199            KclValue::Tuple { meta, .. } => to_vec_sr(&meta),
200            KclValue::HomArray { value, .. } => value.iter().flat_map(Into::<Vec<SourceRange>>::into).collect(),
201            KclValue::Object { meta, .. } => to_vec_sr(&meta),
202            KclValue::Module { meta, .. } => to_vec_sr(&meta),
203            KclValue::Uuid { meta, .. } => to_vec_sr(&meta),
204            KclValue::Type { meta, .. } => to_vec_sr(&meta),
205            KclValue::KclNone { meta, .. } => to_vec_sr(&meta),
206        }
207    }
208}
209
210fn to_vec_sr(meta: &[Metadata]) -> Vec<SourceRange> {
211    meta.iter().map(|m| m.source_range).collect()
212}
213
214impl From<&KclValue> for Vec<SourceRange> {
215    fn from(item: &KclValue) -> Self {
216        match item {
217            KclValue::TagDeclarator(t) => vec![SourceRange::new(t.start, t.end, t.module_id)],
218            KclValue::TagIdentifier(t) => to_vec_sr(&t.meta),
219            KclValue::Solid { value } => to_vec_sr(&value.meta),
220            KclValue::Sketch { value } => to_vec_sr(&value.meta),
221            KclValue::Helix { value } => to_vec_sr(&value.meta),
222            KclValue::ImportedGeometry(i) => to_vec_sr(&i.meta),
223            KclValue::Function { meta, .. } => to_vec_sr(meta),
224            KclValue::Plane { value } => to_vec_sr(&value.meta),
225            KclValue::Face { value } => to_vec_sr(&value.meta),
226            KclValue::Bool { meta, .. } => to_vec_sr(meta),
227            KclValue::Number { meta, .. } => to_vec_sr(meta),
228            KclValue::String { meta, .. } => to_vec_sr(meta),
229            KclValue::Uuid { meta, .. } => to_vec_sr(meta),
230            KclValue::Tuple { meta, .. } => to_vec_sr(meta),
231            KclValue::HomArray { value, .. } => value.iter().flat_map(Into::<Vec<SourceRange>>::into).collect(),
232            KclValue::Object { meta, .. } => to_vec_sr(meta),
233            KclValue::Module { meta, .. } => to_vec_sr(meta),
234            KclValue::KclNone { meta, .. } => to_vec_sr(meta),
235            KclValue::Type { meta, .. } => to_vec_sr(meta),
236        }
237    }
238}
239
240impl From<&KclValue> for SourceRange {
241    fn from(item: &KclValue) -> Self {
242        let v: Vec<_> = item.into();
243        v.into_iter().next().unwrap_or_default()
244    }
245}
246
247impl KclValue {
248    pub(crate) fn metadata(&self) -> Vec<Metadata> {
249        match self {
250            KclValue::Uuid { value: _, meta } => meta.clone(),
251            KclValue::Bool { value: _, meta } => meta.clone(),
252            KclValue::Number { meta, .. } => meta.clone(),
253            KclValue::String { value: _, meta } => meta.clone(),
254            KclValue::Tuple { value: _, meta } => meta.clone(),
255            KclValue::HomArray { value, .. } => value.iter().flat_map(|v| v.metadata()).collect(),
256            KclValue::Object { value: _, meta } => meta.clone(),
257            KclValue::TagIdentifier(x) => x.meta.clone(),
258            KclValue::TagDeclarator(x) => vec![x.metadata()],
259            KclValue::Plane { value } => value.meta.clone(),
260            KclValue::Face { value } => value.meta.clone(),
261            KclValue::Sketch { value } => value.meta.clone(),
262            KclValue::Solid { value } => value.meta.clone(),
263            KclValue::Helix { value } => value.meta.clone(),
264            KclValue::ImportedGeometry(x) => x.meta.clone(),
265            KclValue::Function { meta, .. } => meta.clone(),
266            KclValue::Module { meta, .. } => meta.clone(),
267            KclValue::KclNone { meta, .. } => meta.clone(),
268            KclValue::Type { meta, .. } => meta.clone(),
269        }
270    }
271
272    #[allow(unused)]
273    pub(crate) fn none() -> Self {
274        Self::KclNone {
275            value: Default::default(),
276            meta: Default::default(),
277        }
278    }
279
280    /// Returns true if we should generate an [`crate::execution::Operation`] to
281    /// display in the Feature Tree for variable declarations initialized with
282    /// this value.
283    pub(crate) fn show_variable_in_feature_tree(&self) -> bool {
284        match self {
285            KclValue::Uuid { .. } => false,
286            KclValue::Bool { .. } | KclValue::Number { .. } | KclValue::String { .. } => true,
287            KclValue::Tuple { .. }
288            | KclValue::HomArray { .. }
289            | KclValue::Object { .. }
290            | KclValue::TagIdentifier(_)
291            | KclValue::TagDeclarator(_)
292            | KclValue::Plane { .. }
293            | KclValue::Face { .. }
294            | KclValue::Sketch { .. }
295            | KclValue::Solid { .. }
296            | KclValue::Helix { .. }
297            | KclValue::ImportedGeometry(_)
298            | KclValue::Function { .. }
299            | KclValue::Module { .. }
300            | KclValue::Type { .. }
301            | KclValue::KclNone { .. } => false,
302        }
303    }
304
305    /// Human readable type name used in error messages.  Should not be relied
306    /// on for program logic.
307    pub(crate) fn human_friendly_type(&self) -> String {
308        match self {
309            KclValue::Uuid { .. } => "a unique ID (uuid)".to_owned(),
310            KclValue::TagDeclarator(_) => "a tag declarator".to_owned(),
311            KclValue::TagIdentifier(_) => "a tag identifier".to_owned(),
312            KclValue::Solid { .. } => "a solid".to_owned(),
313            KclValue::Sketch { .. } => "a sketch".to_owned(),
314            KclValue::Helix { .. } => "a helix".to_owned(),
315            KclValue::ImportedGeometry(_) => "an imported geometry".to_owned(),
316            KclValue::Function { .. } => "a function".to_owned(),
317            KclValue::Plane { .. } => "a plane".to_owned(),
318            KclValue::Face { .. } => "a face".to_owned(),
319            KclValue::Bool { .. } => "a boolean (`true` or `false`)".to_owned(),
320            KclValue::Number {
321                ty: NumericType::Unknown,
322                ..
323            } => "a number with unknown units".to_owned(),
324            KclValue::Number {
325                ty: NumericType::Known(units),
326                ..
327            } => format!("a number ({units})"),
328            KclValue::Number { .. } => "a number".to_owned(),
329            KclValue::String { .. } => "a string".to_owned(),
330            KclValue::Object { .. } => "an object".to_owned(),
331            KclValue::Module { .. } => "a module".to_owned(),
332            KclValue::Type { .. } => "a type".to_owned(),
333            KclValue::KclNone { .. } => "none".to_owned(),
334            KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => {
335                if value.is_empty() {
336                    "an empty array".to_owned()
337                } else {
338                    // A max of 3 is good because it's common to use 3D points.
339                    const MAX: usize = 3;
340
341                    let len = value.len();
342                    let element_tys = value
343                        .iter()
344                        .take(MAX)
345                        .map(|elem| elem.principal_type_string())
346                        .collect::<Vec<_>>()
347                        .join(", ");
348                    let mut result = format!("an array of {element_tys}");
349                    if len > MAX {
350                        result.push_str(&format!(", ... with {len} values"));
351                    }
352                    if len == 1 {
353                        result.push_str(" with 1 value");
354                    }
355                    result
356                }
357            }
358        }
359    }
360
361    pub(crate) fn from_literal(literal: Node<Literal>, exec_state: &mut ExecState) -> Self {
362        let meta = vec![literal.metadata()];
363        match literal.inner.value {
364            LiteralValue::Number { value, suffix } => {
365                let ty = NumericType::from_parsed(suffix, &exec_state.mod_local.settings);
366                if let NumericType::Default { len, .. } = &ty {
367                    if !exec_state.mod_local.explicit_length_units && *len != UnitLen::Mm {
368                        exec_state.warn(
369                            CompilationError::err(
370                                literal.as_source_range(),
371                                "Project-wide units are deprecated. Prefer to use per-file default units.",
372                            )
373                            .with_suggestion(
374                                "Fix by adding per-file settings",
375                                format!("@{SETTINGS}({SETTINGS_UNIT_LENGTH} = {len})\n"),
376                                // Insert at the start of the file.
377                                Some(SourceRange::new(0, 0, literal.module_id)),
378                                crate::errors::Tag::Deprecated,
379                            ),
380                        );
381                    }
382                }
383                KclValue::Number { value, meta, ty }
384            }
385            LiteralValue::String(value) => KclValue::String { value, meta },
386            LiteralValue::Bool(value) => KclValue::Bool { value, meta },
387        }
388    }
389
390    pub(crate) fn from_default_param(param: DefaultParamVal, exec_state: &mut ExecState) -> Self {
391        match param {
392            DefaultParamVal::Literal(lit) => Self::from_literal(lit, exec_state),
393            DefaultParamVal::KclNone(value) => KclValue::KclNone {
394                value,
395                meta: Default::default(),
396            },
397        }
398    }
399
400    pub(crate) fn map_env_ref(&self, old_env: usize, new_env: usize) -> Self {
401        let mut result = self.clone();
402        if let KclValue::Function {
403            value: FunctionSource::User { ref mut memory, .. },
404            ..
405        } = result
406        {
407            memory.replace_env(old_env, new_env);
408        }
409        result
410    }
411
412    pub const fn from_number_with_type(f: f64, ty: NumericType, meta: Vec<Metadata>) -> Self {
413        Self::Number { value: f, meta, ty }
414    }
415
416    /// Put the point into a KCL value.
417    pub fn from_point2d(p: [f64; 2], ty: NumericType, meta: Vec<Metadata>) -> Self {
418        let [x, y] = p;
419        Self::Tuple {
420            value: vec![
421                Self::Number {
422                    value: x,
423                    meta: meta.clone(),
424                    ty,
425                },
426                Self::Number {
427                    value: y,
428                    meta: meta.clone(),
429                    ty,
430                },
431            ],
432            meta,
433        }
434    }
435
436    /// Put the point into a KCL value.
437    pub fn from_point3d(p: [f64; 3], ty: NumericType, meta: Vec<Metadata>) -> Self {
438        let [x, y, z] = p;
439        Self::Tuple {
440            value: vec![
441                Self::Number {
442                    value: x,
443                    meta: meta.clone(),
444                    ty,
445                },
446                Self::Number {
447                    value: y,
448                    meta: meta.clone(),
449                    ty,
450                },
451                Self::Number {
452                    value: z,
453                    meta: meta.clone(),
454                    ty,
455                },
456            ],
457            meta,
458        }
459    }
460
461    pub(crate) fn as_usize(&self) -> Option<usize> {
462        match self {
463            KclValue::Number { value, .. } => crate::try_f64_to_usize(*value),
464            _ => None,
465        }
466    }
467
468    pub fn as_int(&self) -> Option<i64> {
469        match self {
470            KclValue::Number { value, .. } => crate::try_f64_to_i64(*value),
471            _ => None,
472        }
473    }
474
475    pub fn as_int_with_ty(&self) -> Option<(i64, NumericType)> {
476        match self {
477            KclValue::Number { value, ty, .. } => crate::try_f64_to_i64(*value).map(|i| (i, *ty)),
478            _ => None,
479        }
480    }
481
482    pub fn as_object(&self) -> Option<&KclObjectFields> {
483        match self {
484            KclValue::Object { value, .. } => Some(value),
485            _ => None,
486        }
487    }
488
489    pub fn into_object(self) -> Option<KclObjectFields> {
490        match self {
491            KclValue::Object { value, .. } => Some(value),
492            _ => None,
493        }
494    }
495
496    pub fn as_str(&self) -> Option<&str> {
497        match self {
498            KclValue::String { value, .. } => Some(value),
499            _ => None,
500        }
501    }
502
503    pub fn into_array(self) -> Vec<KclValue> {
504        match self {
505            KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => value,
506            _ => vec![self],
507        }
508    }
509
510    pub fn as_point2d(&self) -> Option<[TyF64; 2]> {
511        let value = match self {
512            KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => value,
513            _ => return None,
514        };
515
516        if value.len() != 2 {
517            return None;
518        }
519        let x = value[0].as_ty_f64()?;
520        let y = value[1].as_ty_f64()?;
521        Some([x, y])
522    }
523
524    pub fn as_point3d(&self) -> Option<[TyF64; 3]> {
525        let value = match self {
526            KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => value,
527            _ => return None,
528        };
529
530        if value.len() != 3 {
531            return None;
532        }
533        let x = value[0].as_ty_f64()?;
534        let y = value[1].as_ty_f64()?;
535        let z = value[2].as_ty_f64()?;
536        Some([x, y, z])
537    }
538
539    pub fn as_uuid(&self) -> Option<uuid::Uuid> {
540        match self {
541            KclValue::Uuid { value, .. } => Some(*value),
542            _ => None,
543        }
544    }
545
546    pub fn as_plane(&self) -> Option<&Plane> {
547        match self {
548            KclValue::Plane { value, .. } => Some(value),
549            _ => None,
550        }
551    }
552
553    pub fn as_solid(&self) -> Option<&Solid> {
554        match self {
555            KclValue::Solid { value, .. } => Some(value),
556            _ => None,
557        }
558    }
559
560    pub fn as_sketch(&self) -> Option<&Sketch> {
561        match self {
562            KclValue::Sketch { value, .. } => Some(value),
563            _ => None,
564        }
565    }
566
567    pub fn as_mut_sketch(&mut self) -> Option<&mut Sketch> {
568        match self {
569            KclValue::Sketch { value } => Some(value),
570            _ => None,
571        }
572    }
573
574    pub fn as_mut_tag(&mut self) -> Option<&mut TagIdentifier> {
575        match self {
576            KclValue::TagIdentifier(value) => Some(value),
577            _ => None,
578        }
579    }
580
581    #[cfg(test)]
582    pub fn as_f64(&self) -> Option<f64> {
583        match self {
584            KclValue::Number { value, .. } => Some(*value),
585            _ => None,
586        }
587    }
588
589    pub fn as_ty_f64(&self) -> Option<TyF64> {
590        match self {
591            KclValue::Number { value, ty, .. } => Some(TyF64::new(*value, *ty)),
592            _ => None,
593        }
594    }
595
596    pub fn as_bool(&self) -> Option<bool> {
597        match self {
598            KclValue::Bool { value, .. } => Some(*value),
599            _ => None,
600        }
601    }
602
603    /// If this value is of type function, return it.
604    pub fn as_function(&self) -> Option<&FunctionSource> {
605        match self {
606            KclValue::Function { value, .. } => Some(value),
607            _ => None,
608        }
609    }
610
611    /// Get a tag identifier from a memory item.
612    pub fn get_tag_identifier(&self) -> Result<TagIdentifier, KclError> {
613        match self {
614            KclValue::TagIdentifier(t) => Ok(*t.clone()),
615            _ => Err(KclError::new_semantic(KclErrorDetails::new(
616                format!("Not a tag identifier: {self:?}"),
617                self.clone().into(),
618            ))),
619        }
620    }
621
622    /// Get a tag declarator from a memory item.
623    pub fn get_tag_declarator(&self) -> Result<TagNode, KclError> {
624        match self {
625            KclValue::TagDeclarator(t) => Ok((**t).clone()),
626            _ => Err(KclError::new_semantic(KclErrorDetails::new(
627                format!("Not a tag declarator: {self:?}"),
628                self.clone().into(),
629            ))),
630        }
631    }
632
633    /// If this KCL value is a bool, retrieve it.
634    pub fn get_bool(&self) -> Result<bool, KclError> {
635        self.as_bool().ok_or_else(|| {
636            KclError::new_type(KclErrorDetails::new(
637                format!("Expected bool, found {}", self.human_friendly_type()),
638                self.into(),
639            ))
640        })
641    }
642
643    pub fn is_unknown_number(&self) -> bool {
644        match self {
645            KclValue::Number { ty, .. } => !ty.is_fully_specified(),
646            _ => false,
647        }
648    }
649
650    pub fn value_str(&self) -> Option<String> {
651        match self {
652            KclValue::Bool { value, .. } => Some(format!("{value}")),
653            KclValue::Number { value, .. } => Some(format!("{value}")),
654            KclValue::String { value, .. } => Some(format!("'{value}'")),
655            KclValue::Uuid { value, .. } => Some(format!("{value}")),
656            KclValue::TagDeclarator(tag) => Some(format!("${}", tag.name)),
657            KclValue::TagIdentifier(tag) => Some(format!("${}", tag.value)),
658            // TODO better Array and Object stringification
659            KclValue::Tuple { .. } => Some("[...]".to_owned()),
660            KclValue::HomArray { .. } => Some("[...]".to_owned()),
661            KclValue::Object { .. } => Some("{ ... }".to_owned()),
662            KclValue::Module { .. }
663            | KclValue::Solid { .. }
664            | KclValue::Sketch { .. }
665            | KclValue::Helix { .. }
666            | KclValue::ImportedGeometry(_)
667            | KclValue::Function { .. }
668            | KclValue::Plane { .. }
669            | KclValue::Face { .. }
670            | KclValue::KclNone { .. }
671            | KclValue::Type { .. } => None,
672        }
673    }
674}
675
676impl From<Geometry> for KclValue {
677    fn from(value: Geometry) -> Self {
678        match value {
679            Geometry::Sketch(x) => Self::Sketch { value: Box::new(x) },
680            Geometry::Solid(x) => Self::Solid { value: Box::new(x) },
681        }
682    }
683}
684
685impl From<GeometryWithImportedGeometry> for KclValue {
686    fn from(value: GeometryWithImportedGeometry) -> Self {
687        match value {
688            GeometryWithImportedGeometry::Sketch(x) => Self::Sketch { value: Box::new(x) },
689            GeometryWithImportedGeometry::Solid(x) => Self::Solid { value: Box::new(x) },
690            GeometryWithImportedGeometry::ImportedGeometry(x) => Self::ImportedGeometry(*x),
691        }
692    }
693}
694
695#[cfg(test)]
696mod tests {
697    use super::*;
698    use crate::exec::UnitType;
699
700    #[test]
701    fn test_human_friendly_type() {
702        let len = KclValue::Number {
703            value: 1.0,
704            ty: NumericType::Known(UnitType::Length(UnitLen::Unknown)),
705            meta: vec![],
706        };
707        assert_eq!(len.human_friendly_type(), "a number (Length)".to_string());
708
709        let unknown = KclValue::Number {
710            value: 1.0,
711            ty: NumericType::Unknown,
712            meta: vec![],
713        };
714        assert_eq!(unknown.human_friendly_type(), "a number with unknown units".to_string());
715
716        let mm = KclValue::Number {
717            value: 1.0,
718            ty: NumericType::Known(UnitType::Length(UnitLen::Mm)),
719            meta: vec![],
720        };
721        assert_eq!(mm.human_friendly_type(), "a number (mm)".to_string());
722
723        let array1_mm = KclValue::HomArray {
724            value: vec![mm.clone()],
725            ty: RuntimeType::any(),
726        };
727        assert_eq!(
728            array1_mm.human_friendly_type(),
729            "an array of `number(mm)` with 1 value".to_string()
730        );
731
732        let array2_mm = KclValue::HomArray {
733            value: vec![mm.clone(), mm.clone()],
734            ty: RuntimeType::any(),
735        };
736        assert_eq!(
737            array2_mm.human_friendly_type(),
738            "an array of `number(mm)`, `number(mm)`".to_string()
739        );
740
741        let array3_mm = KclValue::HomArray {
742            value: vec![mm.clone(), mm.clone(), mm.clone()],
743            ty: RuntimeType::any(),
744        };
745        assert_eq!(
746            array3_mm.human_friendly_type(),
747            "an array of `number(mm)`, `number(mm)`, `number(mm)`".to_string()
748        );
749
750        let inches = KclValue::Number {
751            value: 1.0,
752            ty: NumericType::Known(UnitType::Length(UnitLen::Inches)),
753            meta: vec![],
754        };
755        let array4 = KclValue::HomArray {
756            value: vec![mm.clone(), mm.clone(), inches.clone(), mm.clone()],
757            ty: RuntimeType::any(),
758        };
759        assert_eq!(
760            array4.human_friendly_type(),
761            "an array of `number(mm)`, `number(mm)`, `number(in)`, ... with 4 values".to_string()
762        );
763
764        let empty_array = KclValue::HomArray {
765            value: vec![],
766            ty: RuntimeType::any(),
767        };
768        assert_eq!(empty_array.human_friendly_type(), "an empty array".to_string());
769
770        let array_nested = KclValue::HomArray {
771            value: vec![array2_mm.clone()],
772            ty: RuntimeType::any(),
773        };
774        assert_eq!(
775            array_nested.human_friendly_type(),
776            "an array of `[any; 2]` with 1 value".to_string()
777        );
778    }
779}