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: usize, new_env: usize) -> 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 const fn from_number_with_type(f: f64, ty: NumericType, meta: Vec<Metadata>) -> Self {
559        Self::Number { value: f, meta, ty }
560    }
561
562    /// Put the point into a KCL value.
563    pub fn from_point2d(p: [f64; 2], ty: NumericType, meta: Vec<Metadata>) -> Self {
564        let [x, y] = p;
565        Self::Tuple {
566            value: vec![
567                Self::Number {
568                    value: x,
569                    meta: meta.clone(),
570                    ty,
571                },
572                Self::Number {
573                    value: y,
574                    meta: meta.clone(),
575                    ty,
576                },
577            ],
578            meta,
579        }
580    }
581
582    /// Put the point into a KCL value.
583    pub fn from_point3d(p: [f64; 3], ty: NumericType, meta: Vec<Metadata>) -> Self {
584        let [x, y, z] = 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                Self::Number {
598                    value: z,
599                    meta: meta.clone(),
600                    ty,
601                },
602            ],
603            meta,
604        }
605    }
606
607    /// Put the point into a KCL point.
608    pub(crate) fn array_from_point2d(p: [f64; 2], ty: NumericType, meta: Vec<Metadata>) -> Self {
609        let [x, y] = p;
610        Self::HomArray {
611            value: vec![
612                Self::Number {
613                    value: x,
614                    meta: meta.clone(),
615                    ty,
616                },
617                Self::Number { value: y, meta, ty },
618            ],
619            ty: ty.into(),
620        }
621    }
622
623    /// Put the point into a KCL point.
624    pub fn array_from_point3d(p: [f64; 3], ty: NumericType, meta: Vec<Metadata>) -> Self {
625        let [x, y, z] = p;
626        Self::HomArray {
627            value: vec![
628                Self::Number {
629                    value: x,
630                    meta: meta.clone(),
631                    ty,
632                },
633                Self::Number {
634                    value: y,
635                    meta: meta.clone(),
636                    ty,
637                },
638                Self::Number { value: z, meta, ty },
639            ],
640            ty: ty.into(),
641        }
642    }
643
644    pub(crate) fn from_unsolved_expr(expr: UnsolvedExpr, meta: Vec<Metadata>) -> Self {
645        match expr {
646            UnsolvedExpr::Known(v) => crate::execution::KclValue::Number {
647                value: v.n,
648                ty: v.ty,
649                meta,
650            },
651            UnsolvedExpr::Unknown(var_id) => crate::execution::KclValue::SketchVar {
652                value: Box::new(SketchVar {
653                    id: var_id,
654                    initial_value: Default::default(),
655                    // TODO: Should this be the solver units?
656                    ty: Default::default(),
657                    meta,
658                }),
659            },
660        }
661    }
662
663    pub(crate) fn as_usize(&self) -> Option<usize> {
664        match self {
665            KclValue::Number { value, .. } => crate::try_f64_to_usize(*value),
666            _ => None,
667        }
668    }
669
670    pub fn as_int(&self) -> Option<i64> {
671        match self {
672            KclValue::Number { value, .. } => crate::try_f64_to_i64(*value),
673            _ => None,
674        }
675    }
676
677    pub fn as_int_with_ty(&self) -> Option<(i64, NumericType)> {
678        match self {
679            KclValue::Number { value, ty, .. } => crate::try_f64_to_i64(*value).map(|i| (i, *ty)),
680            _ => None,
681        }
682    }
683
684    pub fn as_object(&self) -> Option<&KclObjectFields> {
685        match self {
686            KclValue::Object { value, .. } => Some(value),
687            _ => None,
688        }
689    }
690
691    pub fn into_object(self) -> Option<KclObjectFields> {
692        match self {
693            KclValue::Object { value, .. } => Some(value),
694            _ => None,
695        }
696    }
697
698    pub fn as_unsolved_expr(&self) -> Option<UnsolvedExpr> {
699        match self {
700            KclValue::Number { value, ty, .. } => Some(UnsolvedExpr::Known(TyF64::new(*value, *ty))),
701            KclValue::SketchVar { value, .. } => Some(UnsolvedExpr::Unknown(value.id)),
702            _ => None,
703        }
704    }
705
706    pub fn to_sketch_expr(&self) -> Option<crate::front::Expr> {
707        match self {
708            KclValue::Number { value, ty, .. } => Some(crate::front::Expr::Number(crate::front::Number {
709                value: *value,
710                units: (*ty).try_into().ok()?,
711            })),
712            KclValue::SketchVar { value, .. } => Some(crate::front::Expr::Var(crate::front::Number {
713                value: value.initial_value,
714                units: value.ty.try_into().ok()?,
715            })),
716            _ => None,
717        }
718    }
719
720    pub fn as_str(&self) -> Option<&str> {
721        match self {
722            KclValue::String { value, .. } => Some(value),
723            _ => None,
724        }
725    }
726
727    pub fn into_array(self) -> Vec<KclValue> {
728        match self {
729            KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => value,
730            _ => vec![self],
731        }
732    }
733
734    pub fn as_point2d(&self) -> Option<[TyF64; 2]> {
735        let value = match self {
736            KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => value,
737            _ => return None,
738        };
739
740        if value.len() != 2 {
741            return None;
742        }
743        let x = value[0].as_ty_f64()?;
744        let y = value[1].as_ty_f64()?;
745        Some([x, y])
746    }
747
748    pub fn as_point3d(&self) -> Option<[TyF64; 3]> {
749        let value = match self {
750            KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => value,
751            _ => return None,
752        };
753
754        if value.len() != 3 {
755            return None;
756        }
757        let x = value[0].as_ty_f64()?;
758        let y = value[1].as_ty_f64()?;
759        let z = value[2].as_ty_f64()?;
760        Some([x, y, z])
761    }
762
763    pub fn as_uuid(&self) -> Option<uuid::Uuid> {
764        match self {
765            KclValue::Uuid { value, .. } => Some(*value),
766            _ => None,
767        }
768    }
769
770    pub fn as_plane(&self) -> Option<&Plane> {
771        match self {
772            KclValue::Plane { value, .. } => Some(value),
773            _ => None,
774        }
775    }
776
777    pub fn as_solid(&self) -> Option<&Solid> {
778        match self {
779            KclValue::Solid { value, .. } => Some(value),
780            _ => None,
781        }
782    }
783
784    pub fn as_sketch(&self) -> Option<&Sketch> {
785        match self {
786            KclValue::Sketch { value, .. } => Some(value),
787            _ => None,
788        }
789    }
790
791    pub fn as_mut_sketch(&mut self) -> Option<&mut Sketch> {
792        match self {
793            KclValue::Sketch { value } => Some(value),
794            _ => None,
795        }
796    }
797
798    pub fn as_sketch_var(&self) -> Option<&SketchVar> {
799        match self {
800            KclValue::SketchVar { value, .. } => Some(value),
801            _ => None,
802        }
803    }
804
805    pub fn as_mut_tag(&mut self) -> Option<&mut TagIdentifier> {
806        match self {
807            KclValue::TagIdentifier(value) => Some(value),
808            _ => None,
809        }
810    }
811
812    #[cfg(test)]
813    pub fn as_f64(&self) -> Option<f64> {
814        match self {
815            KclValue::Number { value, .. } => Some(*value),
816            _ => None,
817        }
818    }
819
820    pub fn as_ty_f64(&self) -> Option<TyF64> {
821        match self {
822            KclValue::Number { value, ty, .. } => Some(TyF64::new(*value, *ty)),
823            _ => None,
824        }
825    }
826
827    pub fn as_bool(&self) -> Option<bool> {
828        match self {
829            KclValue::Bool { value, .. } => Some(*value),
830            _ => None,
831        }
832    }
833
834    /// If this value is of type function, return it.
835    pub fn as_function(&self) -> Option<&FunctionSource> {
836        match self {
837            KclValue::Function { value, .. } => Some(value),
838            _ => None,
839        }
840    }
841
842    /// Get a tag identifier from a memory item.
843    pub fn get_tag_identifier(&self) -> Result<TagIdentifier, KclError> {
844        match self {
845            KclValue::TagIdentifier(t) => Ok(*t.clone()),
846            _ => Err(KclError::new_semantic(KclErrorDetails::new(
847                format!("Not a tag identifier: {self:?}"),
848                self.clone().into(),
849            ))),
850        }
851    }
852
853    /// Get a tag declarator from a memory item.
854    pub fn get_tag_declarator(&self) -> Result<TagNode, KclError> {
855        match self {
856            KclValue::TagDeclarator(t) => Ok((**t).clone()),
857            _ => Err(KclError::new_semantic(KclErrorDetails::new(
858                format!("Not a tag declarator: {self:?}"),
859                self.clone().into(),
860            ))),
861        }
862    }
863
864    /// If this KCL value is a bool, retrieve it.
865    pub fn get_bool(&self) -> Result<bool, KclError> {
866        self.as_bool().ok_or_else(|| {
867            KclError::new_type(KclErrorDetails::new(
868                format!("Expected bool, found {}", self.human_friendly_type()),
869                self.into(),
870            ))
871        })
872    }
873
874    pub fn is_unknown_number(&self) -> bool {
875        match self {
876            KclValue::Number { ty, .. } => !ty.is_fully_specified(),
877            _ => false,
878        }
879    }
880
881    pub fn value_str(&self) -> Option<String> {
882        match self {
883            KclValue::Bool { value, .. } => Some(format!("{value}")),
884            // TODO: Show units.
885            KclValue::Number { value, .. } => Some(format!("{value}")),
886            KclValue::String { value, .. } => Some(format!("'{value}'")),
887            // TODO: Show units.
888            KclValue::SketchVar { value, .. } => Some(format!("var {}", value.initial_value)),
889            KclValue::Uuid { value, .. } => Some(format!("{value}")),
890            KclValue::TagDeclarator(tag) => Some(format!("${}", tag.name)),
891            KclValue::TagIdentifier(tag) => Some(format!("${}", tag.value)),
892            // TODO better Array and Object stringification
893            KclValue::Tuple { .. } => Some("[...]".to_owned()),
894            KclValue::HomArray { .. } => Some("[...]".to_owned()),
895            KclValue::Object { .. } => Some("{ ... }".to_owned()),
896            KclValue::Module { .. }
897            | KclValue::GdtAnnotation { .. }
898            | KclValue::SketchConstraint { .. }
899            | KclValue::Solid { .. }
900            | KclValue::Sketch { .. }
901            | KclValue::Helix { .. }
902            | KclValue::ImportedGeometry(_)
903            | KclValue::Function { .. }
904            | KclValue::Plane { .. }
905            | KclValue::Face { .. }
906            | KclValue::Segment { .. }
907            | KclValue::KclNone { .. }
908            | KclValue::Type { .. } => None,
909        }
910    }
911}
912
913impl From<Geometry> for KclValue {
914    fn from(value: Geometry) -> Self {
915        match value {
916            Geometry::Sketch(x) => Self::Sketch { value: Box::new(x) },
917            Geometry::Solid(x) => Self::Solid { value: Box::new(x) },
918        }
919    }
920}
921
922impl From<GeometryWithImportedGeometry> for KclValue {
923    fn from(value: GeometryWithImportedGeometry) -> Self {
924        match value {
925            GeometryWithImportedGeometry::Sketch(x) => Self::Sketch { value: Box::new(x) },
926            GeometryWithImportedGeometry::Solid(x) => Self::Solid { value: Box::new(x) },
927            GeometryWithImportedGeometry::ImportedGeometry(x) => Self::ImportedGeometry(*x),
928        }
929    }
930}
931
932#[cfg(test)]
933mod tests {
934    use super::*;
935    use crate::exec::UnitType;
936
937    #[test]
938    fn test_human_friendly_type() {
939        let len = KclValue::Number {
940            value: 1.0,
941            ty: NumericType::Known(UnitType::GenericLength),
942            meta: vec![],
943        };
944        assert_eq!(len.human_friendly_type(), "a number (Length)".to_string());
945
946        let unknown = KclValue::Number {
947            value: 1.0,
948            ty: NumericType::Unknown,
949            meta: vec![],
950        };
951        assert_eq!(unknown.human_friendly_type(), "a number with unknown units".to_string());
952
953        let mm = KclValue::Number {
954            value: 1.0,
955            ty: NumericType::Known(UnitType::Length(UnitLength::Millimeters)),
956            meta: vec![],
957        };
958        assert_eq!(mm.human_friendly_type(), "a number (mm)".to_string());
959
960        let array1_mm = KclValue::HomArray {
961            value: vec![mm.clone()],
962            ty: RuntimeType::any(),
963        };
964        assert_eq!(
965            array1_mm.human_friendly_type(),
966            "an array of `number(mm)` with 1 value".to_string()
967        );
968
969        let array2_mm = KclValue::HomArray {
970            value: vec![mm.clone(), mm.clone()],
971            ty: RuntimeType::any(),
972        };
973        assert_eq!(
974            array2_mm.human_friendly_type(),
975            "an array of `number(mm)`, `number(mm)`".to_string()
976        );
977
978        let array3_mm = KclValue::HomArray {
979            value: vec![mm.clone(), mm.clone(), mm.clone()],
980            ty: RuntimeType::any(),
981        };
982        assert_eq!(
983            array3_mm.human_friendly_type(),
984            "an array of `number(mm)`, `number(mm)`, `number(mm)`".to_string()
985        );
986
987        let inches = KclValue::Number {
988            value: 1.0,
989            ty: NumericType::Known(UnitType::Length(UnitLength::Inches)),
990            meta: vec![],
991        };
992        let array4 = KclValue::HomArray {
993            value: vec![mm.clone(), mm.clone(), inches, mm],
994            ty: RuntimeType::any(),
995        };
996        assert_eq!(
997            array4.human_friendly_type(),
998            "an array of `number(mm)`, `number(mm)`, `number(in)`, ... with 4 values".to_string()
999        );
1000
1001        let empty_array = KclValue::HomArray {
1002            value: vec![],
1003            ty: RuntimeType::any(),
1004        };
1005        assert_eq!(empty_array.human_friendly_type(), "an empty array".to_string());
1006
1007        let array_nested = KclValue::HomArray {
1008            value: vec![array2_mm],
1009            ty: RuntimeType::any(),
1010        };
1011        assert_eq!(
1012            array_nested.human_friendly_type(),
1013            "an array of `[any; 2]` with 1 value".to_string()
1014        );
1015    }
1016}