Skip to main content

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