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::{self, 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                            annotations::WARN_DEPRECATED,
381                        );
382                    }
383                }
384                KclValue::Number { value, meta, ty }
385            }
386            LiteralValue::String(value) => KclValue::String { value, meta },
387            LiteralValue::Bool(value) => KclValue::Bool { value, meta },
388        }
389    }
390
391    pub(crate) fn from_default_param(param: DefaultParamVal, exec_state: &mut ExecState) -> Self {
392        match param {
393            DefaultParamVal::Literal(lit) => Self::from_literal(lit, exec_state),
394            DefaultParamVal::KclNone(value) => KclValue::KclNone {
395                value,
396                meta: Default::default(),
397            },
398        }
399    }
400
401    pub(crate) fn map_env_ref(&self, old_env: usize, new_env: usize) -> Self {
402        let mut result = self.clone();
403        if let KclValue::Function {
404            value: FunctionSource::User { ref mut memory, .. },
405            ..
406        } = result
407        {
408            memory.replace_env(old_env, new_env);
409        }
410        result
411    }
412
413    pub const fn from_number_with_type(f: f64, ty: NumericType, meta: Vec<Metadata>) -> Self {
414        Self::Number { value: f, meta, ty }
415    }
416
417    /// Put the point into a KCL value.
418    pub fn from_point2d(p: [f64; 2], ty: NumericType, meta: Vec<Metadata>) -> Self {
419        let [x, y] = p;
420        Self::Tuple {
421            value: vec![
422                Self::Number {
423                    value: x,
424                    meta: meta.clone(),
425                    ty,
426                },
427                Self::Number {
428                    value: y,
429                    meta: meta.clone(),
430                    ty,
431                },
432            ],
433            meta,
434        }
435    }
436
437    /// Put the point into a KCL value.
438    pub fn from_point3d(p: [f64; 3], ty: NumericType, meta: Vec<Metadata>) -> Self {
439        let [x, y, z] = p;
440        Self::Tuple {
441            value: vec![
442                Self::Number {
443                    value: x,
444                    meta: meta.clone(),
445                    ty,
446                },
447                Self::Number {
448                    value: y,
449                    meta: meta.clone(),
450                    ty,
451                },
452                Self::Number {
453                    value: z,
454                    meta: meta.clone(),
455                    ty,
456                },
457            ],
458            meta,
459        }
460    }
461
462    /// Put the point into a KCL point.
463    pub fn array_from_point3d(p: [f64; 3], ty: NumericType, meta: Vec<Metadata>) -> Self {
464        let [x, y, z] = p;
465        Self::HomArray {
466            value: vec![
467                Self::Number {
468                    value: x,
469                    meta: meta.clone(),
470                    ty,
471                },
472                Self::Number {
473                    value: y,
474                    meta: meta.clone(),
475                    ty,
476                },
477                Self::Number {
478                    value: z,
479                    meta: meta.clone(),
480                    ty,
481                },
482            ],
483            ty: ty.into(),
484        }
485    }
486
487    pub(crate) fn as_usize(&self) -> Option<usize> {
488        match self {
489            KclValue::Number { value, .. } => crate::try_f64_to_usize(*value),
490            _ => None,
491        }
492    }
493
494    pub fn as_int(&self) -> Option<i64> {
495        match self {
496            KclValue::Number { value, .. } => crate::try_f64_to_i64(*value),
497            _ => None,
498        }
499    }
500
501    pub fn as_int_with_ty(&self) -> Option<(i64, NumericType)> {
502        match self {
503            KclValue::Number { value, ty, .. } => crate::try_f64_to_i64(*value).map(|i| (i, *ty)),
504            _ => None,
505        }
506    }
507
508    pub fn as_object(&self) -> Option<&KclObjectFields> {
509        match self {
510            KclValue::Object { value, .. } => Some(value),
511            _ => None,
512        }
513    }
514
515    pub fn into_object(self) -> Option<KclObjectFields> {
516        match self {
517            KclValue::Object { value, .. } => Some(value),
518            _ => None,
519        }
520    }
521
522    pub fn as_str(&self) -> Option<&str> {
523        match self {
524            KclValue::String { value, .. } => Some(value),
525            _ => None,
526        }
527    }
528
529    pub fn into_array(self) -> Vec<KclValue> {
530        match self {
531            KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => value,
532            _ => vec![self],
533        }
534    }
535
536    pub fn as_point2d(&self) -> Option<[TyF64; 2]> {
537        let value = match self {
538            KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => value,
539            _ => return None,
540        };
541
542        if value.len() != 2 {
543            return None;
544        }
545        let x = value[0].as_ty_f64()?;
546        let y = value[1].as_ty_f64()?;
547        Some([x, y])
548    }
549
550    pub fn as_point3d(&self) -> Option<[TyF64; 3]> {
551        let value = match self {
552            KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => value,
553            _ => return None,
554        };
555
556        if value.len() != 3 {
557            return None;
558        }
559        let x = value[0].as_ty_f64()?;
560        let y = value[1].as_ty_f64()?;
561        let z = value[2].as_ty_f64()?;
562        Some([x, y, z])
563    }
564
565    pub fn as_uuid(&self) -> Option<uuid::Uuid> {
566        match self {
567            KclValue::Uuid { value, .. } => Some(*value),
568            _ => None,
569        }
570    }
571
572    pub fn as_plane(&self) -> Option<&Plane> {
573        match self {
574            KclValue::Plane { value, .. } => Some(value),
575            _ => None,
576        }
577    }
578
579    pub fn as_solid(&self) -> Option<&Solid> {
580        match self {
581            KclValue::Solid { value, .. } => Some(value),
582            _ => None,
583        }
584    }
585
586    pub fn as_sketch(&self) -> Option<&Sketch> {
587        match self {
588            KclValue::Sketch { value, .. } => Some(value),
589            _ => None,
590        }
591    }
592
593    pub fn as_mut_sketch(&mut self) -> Option<&mut Sketch> {
594        match self {
595            KclValue::Sketch { value } => Some(value),
596            _ => None,
597        }
598    }
599
600    pub fn as_mut_tag(&mut self) -> Option<&mut TagIdentifier> {
601        match self {
602            KclValue::TagIdentifier(value) => Some(value),
603            _ => None,
604        }
605    }
606
607    #[cfg(test)]
608    pub fn as_f64(&self) -> Option<f64> {
609        match self {
610            KclValue::Number { value, .. } => Some(*value),
611            _ => None,
612        }
613    }
614
615    pub fn as_ty_f64(&self) -> Option<TyF64> {
616        match self {
617            KclValue::Number { value, ty, .. } => Some(TyF64::new(*value, *ty)),
618            _ => None,
619        }
620    }
621
622    pub fn as_bool(&self) -> Option<bool> {
623        match self {
624            KclValue::Bool { value, .. } => Some(*value),
625            _ => None,
626        }
627    }
628
629    /// If this value is of type function, return it.
630    pub fn as_function(&self) -> Option<&FunctionSource> {
631        match self {
632            KclValue::Function { value, .. } => Some(value),
633            _ => None,
634        }
635    }
636
637    /// Get a tag identifier from a memory item.
638    pub fn get_tag_identifier(&self) -> Result<TagIdentifier, KclError> {
639        match self {
640            KclValue::TagIdentifier(t) => Ok(*t.clone()),
641            _ => Err(KclError::new_semantic(KclErrorDetails::new(
642                format!("Not a tag identifier: {self:?}"),
643                self.clone().into(),
644            ))),
645        }
646    }
647
648    /// Get a tag declarator from a memory item.
649    pub fn get_tag_declarator(&self) -> Result<TagNode, KclError> {
650        match self {
651            KclValue::TagDeclarator(t) => Ok((**t).clone()),
652            _ => Err(KclError::new_semantic(KclErrorDetails::new(
653                format!("Not a tag declarator: {self:?}"),
654                self.clone().into(),
655            ))),
656        }
657    }
658
659    /// If this KCL value is a bool, retrieve it.
660    pub fn get_bool(&self) -> Result<bool, KclError> {
661        self.as_bool().ok_or_else(|| {
662            KclError::new_type(KclErrorDetails::new(
663                format!("Expected bool, found {}", self.human_friendly_type()),
664                self.into(),
665            ))
666        })
667    }
668
669    pub fn is_unknown_number(&self) -> bool {
670        match self {
671            KclValue::Number { ty, .. } => !ty.is_fully_specified(),
672            _ => false,
673        }
674    }
675
676    pub fn value_str(&self) -> Option<String> {
677        match self {
678            KclValue::Bool { value, .. } => Some(format!("{value}")),
679            KclValue::Number { value, .. } => Some(format!("{value}")),
680            KclValue::String { value, .. } => Some(format!("'{value}'")),
681            KclValue::Uuid { value, .. } => Some(format!("{value}")),
682            KclValue::TagDeclarator(tag) => Some(format!("${}", tag.name)),
683            KclValue::TagIdentifier(tag) => Some(format!("${}", tag.value)),
684            // TODO better Array and Object stringification
685            KclValue::Tuple { .. } => Some("[...]".to_owned()),
686            KclValue::HomArray { .. } => Some("[...]".to_owned()),
687            KclValue::Object { .. } => Some("{ ... }".to_owned()),
688            KclValue::Module { .. }
689            | KclValue::Solid { .. }
690            | KclValue::Sketch { .. }
691            | KclValue::Helix { .. }
692            | KclValue::ImportedGeometry(_)
693            | KclValue::Function { .. }
694            | KclValue::Plane { .. }
695            | KclValue::Face { .. }
696            | KclValue::KclNone { .. }
697            | KclValue::Type { .. } => None,
698        }
699    }
700}
701
702impl From<Geometry> for KclValue {
703    fn from(value: Geometry) -> Self {
704        match value {
705            Geometry::Sketch(x) => Self::Sketch { value: Box::new(x) },
706            Geometry::Solid(x) => Self::Solid { value: Box::new(x) },
707        }
708    }
709}
710
711impl From<GeometryWithImportedGeometry> for KclValue {
712    fn from(value: GeometryWithImportedGeometry) -> Self {
713        match value {
714            GeometryWithImportedGeometry::Sketch(x) => Self::Sketch { value: Box::new(x) },
715            GeometryWithImportedGeometry::Solid(x) => Self::Solid { value: Box::new(x) },
716            GeometryWithImportedGeometry::ImportedGeometry(x) => Self::ImportedGeometry(*x),
717        }
718    }
719}
720
721#[cfg(test)]
722mod tests {
723    use super::*;
724    use crate::exec::UnitType;
725
726    #[test]
727    fn test_human_friendly_type() {
728        let len = KclValue::Number {
729            value: 1.0,
730            ty: NumericType::Known(UnitType::Length(UnitLen::Unknown)),
731            meta: vec![],
732        };
733        assert_eq!(len.human_friendly_type(), "a number (Length)".to_string());
734
735        let unknown = KclValue::Number {
736            value: 1.0,
737            ty: NumericType::Unknown,
738            meta: vec![],
739        };
740        assert_eq!(unknown.human_friendly_type(), "a number with unknown units".to_string());
741
742        let mm = KclValue::Number {
743            value: 1.0,
744            ty: NumericType::Known(UnitType::Length(UnitLen::Mm)),
745            meta: vec![],
746        };
747        assert_eq!(mm.human_friendly_type(), "a number (mm)".to_string());
748
749        let array1_mm = KclValue::HomArray {
750            value: vec![mm.clone()],
751            ty: RuntimeType::any(),
752        };
753        assert_eq!(
754            array1_mm.human_friendly_type(),
755            "an array of `number(mm)` with 1 value".to_string()
756        );
757
758        let array2_mm = KclValue::HomArray {
759            value: vec![mm.clone(), mm.clone()],
760            ty: RuntimeType::any(),
761        };
762        assert_eq!(
763            array2_mm.human_friendly_type(),
764            "an array of `number(mm)`, `number(mm)`".to_string()
765        );
766
767        let array3_mm = KclValue::HomArray {
768            value: vec![mm.clone(), mm.clone(), mm.clone()],
769            ty: RuntimeType::any(),
770        };
771        assert_eq!(
772            array3_mm.human_friendly_type(),
773            "an array of `number(mm)`, `number(mm)`, `number(mm)`".to_string()
774        );
775
776        let inches = KclValue::Number {
777            value: 1.0,
778            ty: NumericType::Known(UnitType::Length(UnitLen::Inches)),
779            meta: vec![],
780        };
781        let array4 = KclValue::HomArray {
782            value: vec![mm.clone(), mm.clone(), inches.clone(), mm.clone()],
783            ty: RuntimeType::any(),
784        };
785        assert_eq!(
786            array4.human_friendly_type(),
787            "an array of `number(mm)`, `number(mm)`, `number(in)`, ... with 4 values".to_string()
788        );
789
790        let empty_array = KclValue::HomArray {
791            value: vec![],
792            ty: RuntimeType::any(),
793        };
794        assert_eq!(empty_array.human_friendly_type(), "an empty array".to_string());
795
796        let array_nested = KclValue::HomArray {
797            value: vec![array2_mm.clone()],
798            ty: RuntimeType::any(),
799        };
800        assert_eq!(
801            array_nested.human_friendly_type(),
802            "an array of `[any; 2]` with 1 value".to_string()
803        );
804    }
805}