kcl_lib/execution/
kcl_value.rs

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