Skip to main content

kcl_lib/execution/
kcl_value.rs

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