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