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