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