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