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