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