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