kcl_lib/execution/
kcl_value.rs

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