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