Skip to main content

kcl_lib/execution/
kcl_value.rs

1use std::collections::HashMap;
2use std::sync::Arc;
3
4use anyhow::Result;
5use indexmap::IndexMap;
6use kcl_api::UnitLength;
7use serde::Serialize;
8use serde::Serializer;
9
10use crate::CompilationIssue;
11use crate::KclError;
12use crate::ModuleId;
13use crate::SourceRange;
14use crate::errors::KclErrorDetails;
15use crate::execution::AbstractSegment;
16use crate::execution::BoundedEdge;
17use crate::execution::EnvironmentRef;
18use crate::execution::ExecState;
19use crate::execution::Face;
20use crate::execution::GdtAnnotation;
21use crate::execution::Geometry;
22use crate::execution::GeometryWithImportedGeometry;
23use crate::execution::Helix;
24use crate::execution::ImportedGeometry;
25use crate::execution::Metadata;
26use crate::execution::Plane;
27use crate::execution::Segment;
28use crate::execution::SegmentRepr;
29use crate::execution::Sketch;
30use crate::execution::SketchConstraint;
31use crate::execution::SketchVar;
32use crate::execution::SketchVarId;
33use crate::execution::Solid;
34use crate::execution::TagIdentifier;
35use crate::execution::UnsolvedExpr;
36use crate::execution::annotations::FnAttrs;
37use crate::execution::annotations::SETTINGS;
38use crate::execution::annotations::SETTINGS_UNIT_LENGTH;
39use crate::execution::annotations::VersionConstraint;
40use crate::execution::annotations::{self};
41use crate::execution::types::NumericType;
42use crate::execution::types::NumericTypeExt;
43use crate::execution::types::PrimitiveType;
44use crate::execution::types::RuntimeType;
45use crate::parsing::ast::types::DefaultParamVal;
46use crate::parsing::ast::types::FunctionExpression;
47use crate::parsing::ast::types::KclNone;
48use crate::parsing::ast::types::Literal;
49use crate::parsing::ast::types::LiteralValue;
50use crate::parsing::ast::types::Node;
51use crate::parsing::ast::types::NumericLiteral;
52use crate::parsing::ast::types::TagDeclarator;
53use crate::parsing::ast::types::TagNode;
54use crate::parsing::ast::types::Type;
55use crate::std::StdFnProps;
56use crate::std::args::TyF64;
57
58pub type KclObjectFields = HashMap<String, KclValue>;
59
60#[derive(Debug, Clone, Default, PartialEq, Serialize)]
61pub enum KclObjectKind {
62    #[default]
63    Default,
64    SketchTags {
65        #[serde(default, skip_serializing_if = "Vec::is_empty")]
66        deprecated_solid_tag_names: Vec<String>,
67    },
68}
69
70impl KclObjectKind {
71    pub(crate) fn is_default(&self) -> bool {
72        match self {
73            KclObjectKind::Default => true,
74            KclObjectKind::SketchTags { .. } => false,
75        }
76    }
77
78    pub(crate) fn deprecated_solid_tag_names(&self) -> &[String] {
79        match self {
80            Self::Default => &[],
81            Self::SketchTags {
82                deprecated_solid_tag_names,
83            } => deprecated_solid_tag_names,
84        }
85    }
86}
87
88/// Any KCL value.
89#[derive(Debug, Clone, Serialize, PartialEq)]
90#[serde(tag = "type")]
91pub enum KclValue {
92    Uuid {
93        value: ::uuid::Uuid,
94        #[serde(skip)]
95        meta: Vec<Metadata>,
96    },
97    Bool {
98        value: bool,
99        #[serde(skip)]
100        meta: Vec<Metadata>,
101    },
102    Number {
103        value: f64,
104        ty: NumericType,
105        #[serde(skip)]
106        meta: Vec<Metadata>,
107    },
108    String {
109        value: String,
110        #[serde(skip)]
111        meta: Vec<Metadata>,
112    },
113    Enum {
114        value: Box<EnumValue>,
115    },
116    SketchVar {
117        value: Box<SketchVar>,
118    },
119    SketchConstraint {
120        value: Box<SketchConstraint>,
121    },
122    Tuple {
123        value: Vec<KclValue>,
124        #[serde(skip)]
125        meta: Vec<Metadata>,
126    },
127    // An array where all values have a shared type (not necessarily the same principal type).
128    HomArray {
129        value: Vec<KclValue>,
130        // The type of values, not the array type.
131        #[serde(skip)]
132        ty: RuntimeType,
133    },
134    Object {
135        value: KclObjectFields,
136        constrainable: bool,
137        #[serde(default, skip_serializing_if = "KclObjectKind::is_default")]
138        object_kind: KclObjectKind,
139        #[serde(skip)]
140        meta: Vec<Metadata>,
141    },
142    TagIdentifier(Box<TagIdentifier>),
143    TagDeclarator(crate::parsing::ast::types::BoxNode<TagDeclarator>),
144    GdtAnnotation {
145        value: Box<GdtAnnotation>,
146    },
147    Plane {
148        value: Box<Plane>,
149    },
150    Face {
151        value: Box<Face>,
152    },
153    BoundedEdge {
154        value: BoundedEdge,
155        meta: Vec<Metadata>,
156    },
157    Segment {
158        value: Box<AbstractSegment>,
159    },
160    Sketch {
161        value: Box<Sketch>,
162    },
163    Solid {
164        value: Box<Solid>,
165    },
166    Helix {
167        value: Box<Helix>,
168    },
169    ImportedGeometry(ImportedGeometry),
170    Function {
171        #[serde(serialize_with = "function_value_stub")]
172        value: Box<FunctionSource>,
173        #[serde(skip)]
174        meta: Vec<Metadata>,
175    },
176    Module {
177        value: ModuleId,
178        #[serde(skip)]
179        meta: Vec<Metadata>,
180    },
181    Type {
182        #[serde(skip)]
183        value: TypeDef,
184        experimental: bool,
185        #[serde(skip)]
186        meta: Vec<Metadata>,
187    },
188    KclNone {
189        value: KclNone,
190        #[serde(skip)]
191        meta: Vec<Metadata>,
192    },
193}
194
195fn function_value_stub<S>(_value: &FunctionSource, serializer: S) -> Result<S::Ok, S::Error>
196where
197    S: serde::Serializer,
198{
199    serializer.serialize_unit()
200}
201
202#[derive(Debug, Clone, PartialEq)]
203pub struct NamedParam {
204    pub experimental: bool,
205    /// If true, this parameter is deprecated regardless of the KCL version.
206    pub deprecated: bool,
207    /// Constraint marking the KCL version at or after which this parameter is deprecated.
208    pub deprecated_since: Option<VersionConstraint>,
209    pub default_value: Option<DefaultParamVal>,
210    pub ty: Option<Type>,
211}
212
213#[derive(Debug, Clone, PartialEq)]
214pub struct FunctionSource {
215    pub input_arg: Option<(String, Option<Type>)>,
216    pub named_args: IndexMap<String, NamedParam>,
217    pub return_type: Option<Node<Type>>,
218    pub deprecated: bool,
219    /// Constraint on the KCL version at which this function is deprecated, e.g.
220    /// "2.0". When the active `kclVersion` is at or after this, calls trigger a
221    /// deprecation warning.
222    pub deprecated_since: Option<VersionConstraint>,
223    pub experimental: bool,
224    pub include_in_feature_tree: bool,
225    pub std_props: Option<StdFnProps>,
226    pub body: FunctionBody,
227    pub ast: crate::parsing::ast::types::BoxNode<FunctionExpression>,
228}
229
230pub struct KclFunctionSourceParams {
231    pub std_props: Option<StdFnProps>,
232    pub experimental: bool,
233    pub include_in_feature_tree: bool,
234}
235
236impl FunctionSource {
237    pub fn rust(
238        func: crate::std::StdFn,
239        ast: Box<Node<FunctionExpression>>,
240        props: StdFnProps,
241        attrs: FnAttrs,
242    ) -> Self {
243        let (input_arg, named_args) = Self::args_from_ast(&ast);
244
245        FunctionSource {
246            input_arg,
247            named_args,
248            return_type: ast.return_type.clone(),
249            deprecated: attrs.deprecated,
250            deprecated_since: attrs.deprecated_since,
251            experimental: attrs.experimental,
252            include_in_feature_tree: attrs.include_in_feature_tree,
253            std_props: Some(props),
254            body: FunctionBody::Rust(func),
255            ast,
256        }
257    }
258
259    pub fn kcl(ast: Box<Node<FunctionExpression>>, memory: EnvironmentRef, params: KclFunctionSourceParams) -> Self {
260        let KclFunctionSourceParams {
261            std_props,
262            experimental,
263            include_in_feature_tree,
264        } = params;
265        let (input_arg, named_args) = Self::args_from_ast(&ast);
266        FunctionSource {
267            input_arg,
268            named_args,
269            return_type: ast.return_type.clone(),
270            deprecated: false,
271            deprecated_since: None,
272            experimental,
273            include_in_feature_tree,
274            std_props,
275            body: FunctionBody::Kcl(memory),
276            ast,
277        }
278    }
279
280    #[expect(clippy::type_complexity)]
281    fn args_from_ast(ast: &FunctionExpression) -> (Option<(String, Option<Type>)>, IndexMap<String, NamedParam>) {
282        let mut input_arg = None;
283        let mut named_args = IndexMap::new();
284        for p in &ast.params {
285            if !p.labeled {
286                input_arg = Some((
287                    p.identifier.name.clone(),
288                    p.param_type.as_ref().map(|t| t.inner.clone()),
289                ));
290                continue;
291            }
292
293            named_args.insert(
294                p.identifier.name.clone(),
295                NamedParam {
296                    experimental: p.experimental,
297                    deprecated: p.deprecated,
298                    deprecated_since: p.deprecated_since.clone(),
299                    default_value: p.default_value.clone(),
300                    ty: p.param_type.as_ref().map(|t| t.inner.clone()),
301                },
302            );
303        }
304
305        (input_arg, named_args)
306    }
307
308    pub(crate) fn is_std(&self) -> bool {
309        self.std_props.is_some()
310    }
311}
312
313#[derive(Debug, Clone, PartialEq)]
314// If you try to compare two `crate::std::StdFn` the results will be meaningless and arbitrary,
315// because they're just function pointers.
316#[allow(unpredictable_function_pointer_comparisons)]
317pub enum FunctionBody {
318    Rust(crate::std::StdFn),
319    Kcl(EnvironmentRef),
320}
321
322#[derive(Debug, Clone, PartialEq)]
323pub enum TypeDef {
324    RustRepr(PrimitiveType, StdFnProps),
325    Alias(RuntimeType),
326    /// Shared rather than owned so that every value of the enum points at the
327    /// one declaration object, and so that reading the type out of memory,
328    /// which clones the `KclValue`, does not copy the variant list.
329    Enum(Arc<EnumTypeDef>),
330}
331
332/// The nominal identity of an enum.
333///
334/// Two enums are the same type only if they come from the same `type`
335/// declaration, so identity is the declaring module plus the name written at
336/// the declaration site. Importing under an alias renames the binding, not the
337/// type, so it leaves identity untouched. Two enums declaring identical variant
338/// names are still distinct types.
339#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
340pub struct EnumTypeId {
341    module_id: ModuleId,
342    declared_name: String,
343}
344
345impl EnumTypeId {
346    pub fn new(module_id: ModuleId, declared_name: impl Into<String>) -> Self {
347        Self {
348            module_id,
349            declared_name: declared_name.into(),
350        }
351    }
352
353    pub fn module_id(&self) -> ModuleId {
354        self.module_id
355    }
356
357    /// The name at the declaration site, which is what users see in
358    /// diagnostics even when the enum was imported under another name.
359    pub fn declared_name(&self) -> &str {
360        &self.declared_name
361    }
362}
363
364/// A declared enum: its identity plus its variants in declaration order.
365#[derive(Debug, Clone, PartialEq)]
366pub struct EnumTypeDef {
367    id: EnumTypeId,
368    variants: Vec<String>,
369}
370
371/// Two variants of one enum declared under the same name, e.g.
372/// `type Color { | Red | Red }`.
373///
374/// Carries indices into the variant list rather than source ranges so that
375/// `EnumTypeDef` stays independent of the AST and of diagnostic types. The
376/// caller holds the declaration, so it can turn an index back into the range it
377/// needs for the error it reports.
378#[derive(Debug, Clone, PartialEq)]
379pub struct DuplicateVariant {
380    /// The name declared twice.
381    pub name: String,
382    /// Where the name was first declared.
383    pub first_index: usize,
384    /// Where it was declared again. Always greater than `first_index`.
385    pub duplicate_index: usize,
386}
387
388impl EnumTypeDef {
389    /// Variant names must be unique, so this is the only way to build an
390    /// `EnumTypeDef` and it rejects a repeat rather than dropping it. Silently
391    /// collapsing duplicates would deny the user a diagnostic naming the variant
392    /// they typed twice.
393    ///
394    /// Reports the earliest repeat when a declaration contains several.
395    pub fn new(id: EnumTypeId, variants: Vec<String>) -> Result<Self, DuplicateVariant> {
396        for (duplicate_index, variant) in variants.iter().enumerate() {
397            if let Some(first_index) = variants[..duplicate_index].iter().position(|v| v == variant) {
398                return Err(DuplicateVariant {
399                    name: variant.clone(),
400                    first_index,
401                    duplicate_index,
402                });
403            }
404        }
405
406        Ok(Self { id, variants })
407    }
408
409    pub fn id(&self) -> &EnumTypeId {
410        &self.id
411    }
412
413    pub fn variants(&self) -> &[String] {
414        &self.variants
415    }
416
417    pub fn has_variant(&self, name: &str) -> bool {
418        self.variants.iter().any(|v| v == name)
419    }
420}
421
422/// A value of an enum type, i.e. one of its variants.
423///
424/// V1 variants are nullary, so the variant name is the entire value. The value
425/// holds its declaration rather than only the declaration's identity, which is
426/// what lets a variant be projected to its declared representation: that
427/// representation is per-variant declaration data, and a value cannot find its
428/// declaration by name, because an import alias renames the binding and a value
429/// can reach a module that never imported the type at all. The declaration is
430/// reachable, not part of the value: identity and equality read the declaration's
431/// id and the variant name, never a representation.
432#[derive(Debug, Clone, Serialize)]
433pub struct EnumValue {
434    /// Serialized as `enum_id` so that the exposed shape stays the nominal
435    /// identity plus the variant, and no declaration data leaks into snapshots
436    /// or the memory pane.
437    #[serde(rename = "enum_id", serialize_with = "serialize_enum_def_id")]
438    def: Arc<EnumTypeDef>,
439    variant: String,
440    #[serde(skip)]
441    meta: Vec<Metadata>,
442}
443
444fn serialize_enum_def_id<S: Serializer>(def: &Arc<EnumTypeDef>, serializer: S) -> Result<S::Ok, S::Error> {
445    def.id().serialize(serializer)
446}
447
448/// Two values are equal when they name the same variant of the same declaration.
449/// Written out rather than derived because the declaration handle is a route to
450/// the declaration and not part of the value: comparing it would, once variants
451/// carry representations, let a representation decide equality.
452impl PartialEq for EnumValue {
453    fn eq(&self, other: &Self) -> bool {
454        self.def.id() == other.def.id() && self.variant == other.variant
455    }
456}
457
458impl EnumValue {
459    pub fn new(def: Arc<EnumTypeDef>, variant: impl Into<String>, meta: Vec<Metadata>) -> Self {
460        Self {
461            def,
462            variant: variant.into(),
463            meta,
464        }
465    }
466
467    pub fn enum_id(&self) -> &EnumTypeId {
468        self.def.id()
469    }
470
471    pub fn variant(&self) -> &str {
472        &self.variant
473    }
474
475    pub fn meta(&self) -> &[Metadata] {
476        &self.meta
477    }
478
479    /// The string this variant projects to under `enumValue: string`.
480    ///
481    /// The declared representation of the variant, which in V1 is always the
482    /// variant name because no variant can declare a `@repr` yet. This is the
483    /// single place that answers the question, so when `@repr` lands it reads the
484    /// declaration here rather than adding a second notion of representation at
485    /// the projection site.
486    pub fn declared_string_repr(&self) -> String {
487        self.variant.clone()
488    }
489
490    /// How the value is written in KCL and shown to users, e.g. `Color::Red`.
491    pub fn qualified_name(&self) -> String {
492        format!("{}::{}", self.def.id().declared_name(), self.variant)
493    }
494}
495
496impl From<Vec<GdtAnnotation>> for KclValue {
497    fn from(mut values: Vec<GdtAnnotation>) -> Self {
498        if values.len() == 1 {
499            let value = values.pop().expect("Just checked len == 1");
500            KclValue::GdtAnnotation { value: Box::new(value) }
501        } else {
502            KclValue::HomArray {
503                value: values
504                    .into_iter()
505                    .map(|s| KclValue::GdtAnnotation { value: Box::new(s) })
506                    .collect(),
507                ty: RuntimeType::Primitive(PrimitiveType::GdtAnnotation),
508            }
509        }
510    }
511}
512
513impl From<Vec<Sketch>> for KclValue {
514    fn from(mut eg: Vec<Sketch>) -> Self {
515        if eg.len() == 1
516            && let Some(s) = eg.pop()
517        {
518            KclValue::Sketch { value: Box::new(s) }
519        } else {
520            KclValue::HomArray {
521                value: eg
522                    .into_iter()
523                    .map(|s| KclValue::Sketch { value: Box::new(s) })
524                    .collect(),
525                ty: RuntimeType::Primitive(PrimitiveType::Sketch),
526            }
527        }
528    }
529}
530
531impl From<Vec<Solid>> for KclValue {
532    fn from(mut eg: Vec<Solid>) -> Self {
533        if eg.len() == 1
534            && let Some(s) = eg.pop()
535        {
536            KclValue::Solid { value: Box::new(s) }
537        } else {
538            KclValue::HomArray {
539                value: eg.into_iter().map(|s| KclValue::Solid { value: Box::new(s) }).collect(),
540                ty: RuntimeType::Primitive(PrimitiveType::Solid),
541            }
542        }
543    }
544}
545
546impl From<KclValue> for Vec<SourceRange> {
547    fn from(item: KclValue) -> Self {
548        match item {
549            KclValue::TagDeclarator(t) => vec![SourceRange::new(t.start, t.end, t.module_id)],
550            KclValue::TagIdentifier(t) => to_vec_sr(&t.meta),
551            KclValue::GdtAnnotation { value } => to_vec_sr(&value.meta),
552            KclValue::Solid { value } => to_vec_sr(&value.meta),
553            KclValue::Sketch { value } => to_vec_sr(&value.meta),
554            KclValue::Helix { value } => to_vec_sr(&value.meta),
555            KclValue::ImportedGeometry(i) => to_vec_sr(&i.meta),
556            KclValue::Function { meta, .. } => to_vec_sr(&meta),
557            KclValue::Plane { value } => to_vec_sr(&value.meta),
558            KclValue::Face { value } => to_vec_sr(&value.meta),
559            KclValue::Segment { value } => to_vec_sr(&value.meta),
560            KclValue::Bool { meta, .. } => to_vec_sr(&meta),
561            KclValue::Number { meta, .. } => to_vec_sr(&meta),
562            KclValue::String { meta, .. } => to_vec_sr(&meta),
563            KclValue::Enum { value } => to_vec_sr(value.meta()),
564            KclValue::SketchVar { value, .. } => to_vec_sr(&value.meta),
565            KclValue::SketchConstraint { value, .. } => to_vec_sr(&value.meta),
566            KclValue::Tuple { meta, .. } => to_vec_sr(&meta),
567            KclValue::HomArray { value, .. } => value.iter().flat_map(Into::<Vec<SourceRange>>::into).collect(),
568            KclValue::Object { meta, .. } => to_vec_sr(&meta),
569            KclValue::Module { meta, .. } => to_vec_sr(&meta),
570            KclValue::Uuid { meta, .. } => to_vec_sr(&meta),
571            KclValue::Type { meta, .. } => to_vec_sr(&meta),
572            KclValue::KclNone { meta, .. } => to_vec_sr(&meta),
573            KclValue::BoundedEdge { meta, .. } => to_vec_sr(&meta),
574        }
575    }
576}
577
578fn to_vec_sr(meta: &[Metadata]) -> Vec<SourceRange> {
579    meta.iter().map(|m| m.source_range).collect()
580}
581
582impl From<&KclValue> for Vec<SourceRange> {
583    fn from(item: &KclValue) -> Self {
584        match item {
585            KclValue::TagDeclarator(t) => vec![SourceRange::new(t.start, t.end, t.module_id)],
586            KclValue::TagIdentifier(t) => to_vec_sr(&t.meta),
587            KclValue::GdtAnnotation { value } => to_vec_sr(&value.meta),
588            KclValue::Solid { value } => to_vec_sr(&value.meta),
589            KclValue::Sketch { value } => to_vec_sr(&value.meta),
590            KclValue::Helix { value } => to_vec_sr(&value.meta),
591            KclValue::ImportedGeometry(i) => to_vec_sr(&i.meta),
592            KclValue::Function { meta, .. } => to_vec_sr(meta),
593            KclValue::Plane { value } => to_vec_sr(&value.meta),
594            KclValue::Face { value } => to_vec_sr(&value.meta),
595            KclValue::Segment { value } => to_vec_sr(&value.meta),
596            KclValue::Bool { meta, .. } => to_vec_sr(meta),
597            KclValue::Number { meta, .. } => to_vec_sr(meta),
598            KclValue::String { meta, .. } => to_vec_sr(meta),
599            KclValue::Enum { value } => to_vec_sr(value.meta()),
600            KclValue::SketchVar { value, .. } => to_vec_sr(&value.meta),
601            KclValue::SketchConstraint { value, .. } => to_vec_sr(&value.meta),
602            KclValue::Uuid { meta, .. } => to_vec_sr(meta),
603            KclValue::Tuple { meta, .. } => to_vec_sr(meta),
604            KclValue::HomArray { value, .. } => value.iter().flat_map(Into::<Vec<SourceRange>>::into).collect(),
605            KclValue::Object { meta, .. } => to_vec_sr(meta),
606            KclValue::Module { meta, .. } => to_vec_sr(meta),
607            KclValue::KclNone { meta, .. } => to_vec_sr(meta),
608            KclValue::Type { meta, .. } => to_vec_sr(meta),
609            KclValue::BoundedEdge { meta, .. } => to_vec_sr(meta),
610        }
611    }
612}
613
614impl From<&KclValue> for SourceRange {
615    fn from(item: &KclValue) -> Self {
616        let v: Vec<_> = item.into();
617        v.into_iter().next().unwrap_or_default()
618    }
619}
620
621impl KclValue {
622    pub(crate) fn metadata(&self) -> Vec<Metadata> {
623        match self {
624            KclValue::Uuid { value: _, meta } => meta.clone(),
625            KclValue::Bool { value: _, meta } => meta.clone(),
626            KclValue::Number { meta, .. } => meta.clone(),
627            KclValue::String { value: _, meta } => meta.clone(),
628            KclValue::Enum { value } => value.meta().to_vec(),
629            KclValue::SketchVar { value, .. } => value.meta.clone(),
630            KclValue::SketchConstraint { value, .. } => value.meta.clone(),
631            KclValue::Tuple { value: _, meta } => meta.clone(),
632            KclValue::HomArray { value, .. } => value.iter().flat_map(|v| v.metadata()).collect(),
633            KclValue::Object { meta, .. } => meta.clone(),
634            KclValue::TagIdentifier(x) => x.meta.clone(),
635            KclValue::TagDeclarator(x) => vec![x.metadata()],
636            KclValue::GdtAnnotation { value } => value.meta.clone(),
637            KclValue::Plane { value } => value.meta.clone(),
638            KclValue::Face { value } => value.meta.clone(),
639            KclValue::Segment { value } => value.meta.clone(),
640            KclValue::Sketch { value } => value.meta.clone(),
641            KclValue::Solid { value } => value.meta.clone(),
642            KclValue::Helix { value } => value.meta.clone(),
643            KclValue::ImportedGeometry(x) => x.meta.clone(),
644            KclValue::Function { meta, .. } => meta.clone(),
645            KclValue::Module { meta, .. } => meta.clone(),
646            KclValue::KclNone { meta, .. } => meta.clone(),
647            KclValue::Type { meta, .. } => meta.clone(),
648            KclValue::BoundedEdge { meta, .. } => meta.clone(),
649        }
650    }
651
652    #[allow(unused)]
653    pub(crate) fn none() -> Self {
654        Self::KclNone {
655            value: Default::default(),
656            meta: Default::default(),
657        }
658    }
659
660    /// Returns true if we should generate an [`crate::execution::Operation`] to
661    /// display in the Feature Tree for variable declarations initialized with
662    /// this value.
663    pub(crate) fn show_variable_in_feature_tree(&self) -> bool {
664        match self {
665            KclValue::Uuid { .. } => false,
666            KclValue::Bool { .. } | KclValue::Number { .. } | KclValue::String { .. } | KclValue::Enum { .. } => true,
667            KclValue::SketchVar { .. }
668            | KclValue::SketchConstraint { .. }
669            | KclValue::Tuple { .. }
670            | KclValue::HomArray { .. }
671            | KclValue::Object { .. }
672            | KclValue::TagIdentifier(_)
673            | KclValue::TagDeclarator(_)
674            | KclValue::GdtAnnotation { .. }
675            | KclValue::Plane { .. }
676            | KclValue::Face { .. }
677            | KclValue::Segment { .. }
678            | KclValue::Sketch { .. }
679            | KclValue::Solid { .. }
680            | KclValue::Helix { .. }
681            | KclValue::ImportedGeometry(_)
682            | KclValue::Function { .. }
683            | KclValue::Module { .. }
684            | KclValue::Type { .. }
685            | KclValue::BoundedEdge { .. }
686            | KclValue::KclNone { .. } => false,
687        }
688    }
689
690    /// Human readable type name used in error messages.  Should not be relied
691    /// on for program logic.
692    pub(crate) fn human_friendly_type(&self) -> String {
693        match self {
694            KclValue::Uuid { .. } => "a unique ID (uuid)".to_owned(),
695            KclValue::TagDeclarator(_) => "a tag declarator".to_owned(),
696            KclValue::TagIdentifier(_) => "a tag identifier".to_owned(),
697            KclValue::GdtAnnotation { .. } => "an annotation".to_owned(),
698            KclValue::Solid { .. } => "a solid".to_owned(),
699            KclValue::Sketch { .. } => "a sketch".to_owned(),
700            KclValue::Helix { .. } => "a helix".to_owned(),
701            KclValue::ImportedGeometry(_) => "an imported geometry".to_owned(),
702            KclValue::Function { .. } => "a function".to_owned(),
703            KclValue::Plane { .. } => "a plane".to_owned(),
704            KclValue::Face { .. } => "a face".to_owned(),
705            KclValue::Segment { .. } => "a segment".to_owned(),
706            KclValue::Bool { .. } => "a boolean (`true` or `false`)".to_owned(),
707            KclValue::Number {
708                ty: NumericType::Unknown,
709                ..
710            } => "a number with unknown units".to_owned(),
711            KclValue::Number {
712                ty: NumericType::Known(units),
713                ..
714            } => format!("a number ({units})"),
715            KclValue::Number { .. } => "a number".to_owned(),
716            KclValue::String { .. } => "a string".to_owned(),
717            KclValue::Enum { value } => format!("a value of enum `{}`", value.enum_id().declared_name()),
718            KclValue::SketchVar { .. } => "a sketch variable".to_owned(),
719            KclValue::SketchConstraint { .. } => "a sketch constraint".to_owned(),
720            KclValue::Object { .. } => "an object".to_owned(),
721            KclValue::Module { .. } => "a module".to_owned(),
722            KclValue::Type { .. } => "a type".to_owned(),
723            KclValue::KclNone { .. } => "none".to_owned(),
724            KclValue::BoundedEdge { .. } => "a bounded edge".to_owned(),
725            KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => {
726                if value.is_empty() {
727                    "an empty array".to_owned()
728                } else {
729                    // A max of 3 is good because it's common to use 3D points.
730                    const MAX: usize = 3;
731
732                    let len = value.len();
733                    let element_tys = value
734                        .iter()
735                        .take(MAX)
736                        .map(|elem| elem.principal_type_string())
737                        .collect::<Vec<_>>()
738                        .join(", ");
739                    let mut result = format!("an array of {element_tys}");
740                    if len > MAX {
741                        result.push_str(&format!(", ... with {len} values"));
742                    }
743                    if len == 1 {
744                        result.push_str(" with 1 value");
745                    }
746                    result
747                }
748            }
749        }
750    }
751
752    pub(crate) fn from_sketch_var_literal(
753        literal: &Node<NumericLiteral>,
754        id: SketchVarId,
755        node_path: Option<crate::NodePath>,
756        exec_state: &ExecState,
757    ) -> Self {
758        let meta = vec![literal.metadata()];
759        let ty = NumericType::from_parsed(literal.suffix, &exec_state.mod_local.settings);
760        KclValue::SketchVar {
761            value: Box::new(SketchVar {
762                id,
763                initial_value: literal.value,
764                node_path,
765                meta,
766                ty,
767            }),
768        }
769    }
770
771    pub(crate) fn from_literal(literal: Node<Literal>, exec_state: &mut ExecState) -> Self {
772        let meta = vec![literal.metadata()];
773        match literal.inner.value {
774            LiteralValue::Number { value, suffix } => {
775                let ty = NumericType::from_parsed(suffix, &exec_state.mod_local.settings);
776                if let NumericType::Default { len, .. } = &ty
777                    && !exec_state.mod_local.explicit_length_units
778                    && *len != UnitLength::Millimeters
779                {
780                    exec_state.warn(
781                        CompilationIssue::err(
782                            literal.as_source_range(),
783                            "Project-wide units are deprecated. Prefer to use per-file default units.",
784                        )
785                        .with_suggestion(
786                            "Fix by adding per-file settings",
787                            format!("@{SETTINGS}({SETTINGS_UNIT_LENGTH} = {len})\n"),
788                            // Insert at the start of the file.
789                            Some(SourceRange::new(0, 0, literal.module_id)),
790                            crate::errors::Tag::Deprecated,
791                        ),
792                        annotations::WARN_DEPRECATED,
793                    );
794                }
795                KclValue::Number { value, meta, ty }
796            }
797            LiteralValue::String(value) => KclValue::String { value, meta },
798            LiteralValue::Bool(value) => KclValue::Bool { value, meta },
799        }
800    }
801
802    pub(crate) fn from_default_param(param: DefaultParamVal, exec_state: &mut ExecState) -> Self {
803        match param {
804            DefaultParamVal::Literal(lit) => Self::from_literal(lit, exec_state),
805            DefaultParamVal::KclNone(value) => KclValue::KclNone {
806                value,
807                meta: Default::default(),
808            },
809        }
810    }
811
812    pub(crate) fn map_env_ref(&self, old_env: EnvironmentRef, new_env: EnvironmentRef) -> Self {
813        let mut result = self.clone();
814        if let KclValue::Function { ref mut value, .. } = result
815            && let FunctionSource {
816                body: FunctionBody::Kcl(memory),
817                ..
818            } = &mut **value
819        {
820            memory.replace_env(old_env, new_env);
821        }
822
823        result
824    }
825
826    pub(crate) fn map_env_ref_and_epoch(&self, old_env: EnvironmentRef, new_env: EnvironmentRef) -> Self {
827        let mut result = self.clone();
828        if let KclValue::Function { ref mut value, .. } = result
829            && let FunctionSource {
830                body: FunctionBody::Kcl(memory),
831                ..
832            } = &mut **value
833        {
834            memory.replace_env_and_epoch(old_env, new_env);
835        }
836
837        result
838    }
839
840    pub const fn from_number_with_type(f: f64, ty: NumericType, meta: Vec<Metadata>) -> Self {
841        Self::Number { value: f, meta, ty }
842    }
843
844    /// Put the point into a KCL value.
845    pub fn from_point2d(p: [f64; 2], ty: NumericType, meta: Vec<Metadata>) -> Self {
846        let [x, y] = p;
847        Self::Tuple {
848            value: vec![
849                Self::Number {
850                    value: x,
851                    meta: meta.clone(),
852                    ty,
853                },
854                Self::Number {
855                    value: y,
856                    meta: meta.clone(),
857                    ty,
858                },
859            ],
860            meta,
861        }
862    }
863
864    /// Put the point into a KCL value.
865    pub fn from_point3d(p: [f64; 3], ty: NumericType, meta: Vec<Metadata>) -> Self {
866        let [x, y, z] = p;
867        Self::Tuple {
868            value: vec![
869                Self::Number {
870                    value: x,
871                    meta: meta.clone(),
872                    ty,
873                },
874                Self::Number {
875                    value: y,
876                    meta: meta.clone(),
877                    ty,
878                },
879                Self::Number {
880                    value: z,
881                    meta: meta.clone(),
882                    ty,
883                },
884            ],
885            meta,
886        }
887    }
888
889    /// Put the point into a KCL point.
890    pub(crate) fn array_from_point2d(p: [f64; 2], ty: NumericType, meta: Vec<Metadata>) -> Self {
891        let [x, y] = p;
892        Self::HomArray {
893            value: vec![
894                Self::Number {
895                    value: x,
896                    meta: meta.clone(),
897                    ty,
898                },
899                Self::Number { value: y, meta, ty },
900            ],
901            ty: ty.into(),
902        }
903    }
904
905    /// Put the point into a KCL point.
906    pub fn array_from_point3d(p: [f64; 3], ty: NumericType, meta: Vec<Metadata>) -> Self {
907        let [x, y, z] = p;
908        Self::HomArray {
909            value: vec![
910                Self::Number {
911                    value: x,
912                    meta: meta.clone(),
913                    ty,
914                },
915                Self::Number {
916                    value: y,
917                    meta: meta.clone(),
918                    ty,
919                },
920                Self::Number { value: z, meta, ty },
921            ],
922            ty: ty.into(),
923        }
924    }
925
926    pub(crate) fn from_unsolved_expr(expr: UnsolvedExpr, meta: Vec<Metadata>) -> Self {
927        match expr {
928            UnsolvedExpr::Known(v) => crate::execution::KclValue::Number {
929                value: v.n,
930                ty: v.ty,
931                meta,
932            },
933            // The original sketch var (if any) lives in `sketch_vars` and carries
934            // its own node_path; this synthesized wrapper isn't pushed there, so
935            // its node_path doesn't drive var-solution writeback.
936            UnsolvedExpr::Unknown(var_id) => crate::execution::KclValue::SketchVar {
937                value: Box::new(SketchVar {
938                    id: var_id,
939                    initial_value: Default::default(),
940                    // TODO: Should this be the solver units?
941                    ty: Default::default(),
942                    node_path: None,
943                    meta,
944                }),
945            },
946        }
947    }
948
949    pub(crate) fn as_usize(&self) -> Option<usize> {
950        match self {
951            KclValue::Number { value, .. } => crate::try_f64_to_usize(*value),
952            _ => None,
953        }
954    }
955
956    pub fn as_int(&self) -> Option<i64> {
957        match self {
958            KclValue::Number { value, .. } => crate::try_f64_to_i64(*value),
959            _ => None,
960        }
961    }
962
963    pub fn as_int_with_ty(&self) -> Option<(i64, NumericType)> {
964        match self {
965            KclValue::Number { value, ty, .. } => crate::try_f64_to_i64(*value).map(|i| (i, *ty)),
966            _ => None,
967        }
968    }
969
970    pub fn as_object(&self) -> Option<&KclObjectFields> {
971        match self {
972            KclValue::Object { value, .. } => Some(value),
973            _ => None,
974        }
975    }
976
977    pub fn into_object(self) -> Option<KclObjectFields> {
978        match self {
979            KclValue::Object { value, .. } => Some(value),
980            _ => None,
981        }
982    }
983
984    pub fn as_unsolved_expr(&self) -> Option<UnsolvedExpr> {
985        match self {
986            KclValue::Number { value, ty, .. } => Some(UnsolvedExpr::Known(TyF64::new(*value, *ty))),
987            KclValue::SketchVar { value, .. } => Some(UnsolvedExpr::Unknown(value.id)),
988            _ => None,
989        }
990    }
991
992    pub fn to_sketch_expr(&self) -> Option<crate::front::Expr> {
993        match self {
994            KclValue::Number { value, ty, .. } => Some(crate::front::Expr::Number(crate::front::Number {
995                value: *value,
996                units: (*ty).try_into().ok()?,
997            })),
998            KclValue::SketchVar { value, .. } => Some(crate::front::Expr::Var(crate::front::Number {
999                value: value.initial_value,
1000                units: value.ty.try_into().ok()?,
1001            })),
1002            _ => None,
1003        }
1004    }
1005
1006    pub fn as_str(&self) -> Option<&str> {
1007        match self {
1008            KclValue::String { value, .. } => Some(value),
1009            _ => None,
1010        }
1011    }
1012
1013    pub fn into_array(self) -> Vec<KclValue> {
1014        match self {
1015            KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => value,
1016            _ => vec![self],
1017        }
1018    }
1019
1020    pub fn as_slice(&self) -> Option<&[KclValue]> {
1021        match self {
1022            KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => Some(value),
1023            _ => None,
1024        }
1025    }
1026
1027    pub fn as_point2d(&self) -> Option<[TyF64; 2]> {
1028        let value = match self {
1029            KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => value,
1030            _ => return None,
1031        };
1032
1033        let [x, y] = value.as_slice() else {
1034            return None;
1035        };
1036        let x = x.as_ty_f64()?;
1037        let y = y.as_ty_f64()?;
1038        Some([x, y])
1039    }
1040
1041    pub fn as_point3d(&self) -> Option<[TyF64; 3]> {
1042        let value = match self {
1043            KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => value,
1044            _ => return None,
1045        };
1046
1047        let [x, y, z] = value.as_slice() else {
1048            return None;
1049        };
1050        let x = x.as_ty_f64()?;
1051        let y = y.as_ty_f64()?;
1052        let z = z.as_ty_f64()?;
1053        Some([x, y, z])
1054    }
1055
1056    pub fn as_uuid(&self) -> Option<uuid::Uuid> {
1057        match self {
1058            KclValue::Uuid { value, .. } => Some(*value),
1059            _ => None,
1060        }
1061    }
1062
1063    pub fn as_plane(&self) -> Option<&Plane> {
1064        match self {
1065            KclValue::Plane { value, .. } => Some(value),
1066            _ => None,
1067        }
1068    }
1069
1070    pub fn as_solid(&self) -> Option<&Solid> {
1071        match self {
1072            KclValue::Solid { value, .. } => Some(value),
1073            _ => None,
1074        }
1075    }
1076
1077    pub fn as_sketch(&self) -> Option<&Sketch> {
1078        match self {
1079            KclValue::Sketch { value, .. } => Some(value),
1080            _ => None,
1081        }
1082    }
1083
1084    pub fn as_mut_sketch(&mut self) -> Option<&mut Sketch> {
1085        match self {
1086            KclValue::Sketch { value } => Some(value),
1087            _ => None,
1088        }
1089    }
1090
1091    pub fn as_sketch_var(&self) -> Option<&SketchVar> {
1092        match self {
1093            KclValue::SketchVar { value, .. } => Some(value),
1094            _ => None,
1095        }
1096    }
1097
1098    /// A solved segment.
1099    pub fn as_segment(&self) -> Option<&Segment> {
1100        match self {
1101            KclValue::Segment { value, .. } => match &value.repr {
1102                SegmentRepr::Solved { segment } => Some(segment),
1103                _ => None,
1104            },
1105            _ => None,
1106        }
1107    }
1108
1109    /// A solved segment.
1110    pub fn into_segment(self) -> Option<Segment> {
1111        match self {
1112            KclValue::Segment { value, .. } => match value.repr {
1113                SegmentRepr::Solved { segment } => Some(*segment),
1114                _ => None,
1115            },
1116            _ => None,
1117        }
1118    }
1119
1120    pub fn as_mut_tag(&mut self) -> Option<&mut TagIdentifier> {
1121        match self {
1122            KclValue::TagIdentifier(value) => Some(value),
1123            _ => None,
1124        }
1125    }
1126
1127    #[cfg(test)]
1128    pub fn as_f64(&self) -> Option<f64> {
1129        match self {
1130            KclValue::Number { value, .. } => Some(*value),
1131            _ => None,
1132        }
1133    }
1134
1135    pub fn as_ty_f64(&self) -> Option<TyF64> {
1136        match self {
1137            KclValue::Number { value, ty, .. } => Some(TyF64::new(*value, *ty)),
1138            _ => None,
1139        }
1140    }
1141
1142    pub fn as_bool(&self) -> Option<bool> {
1143        match self {
1144            KclValue::Bool { value, .. } => Some(*value),
1145            _ => None,
1146        }
1147    }
1148
1149    /// If this value is of type function, return it.
1150    pub fn as_function(&self) -> Option<&FunctionSource> {
1151        match self {
1152            KclValue::Function { value, .. } => Some(value),
1153            _ => None,
1154        }
1155    }
1156
1157    /// Get a tag identifier from a memory item.
1158    pub fn get_tag_identifier(&self) -> Result<TagIdentifier, KclError> {
1159        match self {
1160            KclValue::TagIdentifier(t) => Ok(*t.clone()),
1161            _ => Err(KclError::new_semantic(KclErrorDetails::new(
1162                format!("Not a tag identifier: {self:?}"),
1163                self.clone().into(),
1164            ))),
1165        }
1166    }
1167
1168    /// Get a tag declarator from a memory item.
1169    pub fn get_tag_declarator(&self) -> Result<TagNode, KclError> {
1170        match self {
1171            KclValue::TagDeclarator(t) => Ok((**t).clone()),
1172            _ => Err(KclError::new_semantic(KclErrorDetails::new(
1173                format!("Not a tag declarator: {self:?}"),
1174                self.clone().into(),
1175            ))),
1176        }
1177    }
1178
1179    /// If this KCL value is a bool, retrieve it.
1180    pub fn get_bool(&self) -> Result<bool, KclError> {
1181        self.as_bool().ok_or_else(|| {
1182            KclError::new_type(KclErrorDetails::new(
1183                format!("Expected bool, found {}", self.human_friendly_type()),
1184                self.into(),
1185            ))
1186        })
1187    }
1188
1189    pub fn is_unknown_number(&self) -> bool {
1190        match self {
1191            KclValue::Number { ty, .. } => !ty.is_fully_specified(),
1192            _ => false,
1193        }
1194    }
1195
1196    pub fn value_str(&self) -> Option<String> {
1197        match self {
1198            KclValue::Bool { value, .. } => Some(format!("{value}")),
1199            // TODO: Show units.
1200            KclValue::Number { value, .. } => Some(format!("{value}")),
1201            KclValue::String { value, .. } => Some(format!("'{value}'")),
1202            KclValue::Enum { value } => Some(value.qualified_name()),
1203            // TODO: Show units.
1204            KclValue::SketchVar { value, .. } => Some(format!("var {}", value.initial_value)),
1205            KclValue::Uuid { value, .. } => Some(format!("{value}")),
1206            KclValue::TagDeclarator(tag) => Some(format!("${}", tag.name)),
1207            KclValue::TagIdentifier(tag) => Some(format!("${}", tag.value)),
1208            // TODO better Array and Object stringification
1209            KclValue::Tuple { .. } => Some("[...]".to_owned()),
1210            KclValue::HomArray { .. } => Some("[...]".to_owned()),
1211            KclValue::Object { .. } => Some("{ ... }".to_owned()),
1212            KclValue::Module { .. }
1213            | KclValue::GdtAnnotation { .. }
1214            | KclValue::SketchConstraint { .. }
1215            | KclValue::Solid { .. }
1216            | KclValue::Sketch { .. }
1217            | KclValue::Helix { .. }
1218            | KclValue::ImportedGeometry(_)
1219            | KclValue::Function { .. }
1220            | KclValue::Plane { .. }
1221            | KclValue::Face { .. }
1222            | KclValue::Segment { .. }
1223            | KclValue::KclNone { .. }
1224            | KclValue::BoundedEdge { .. }
1225            | KclValue::Type { .. } => None,
1226        }
1227    }
1228}
1229
1230impl From<Geometry> for KclValue {
1231    fn from(value: Geometry) -> Self {
1232        match value {
1233            Geometry::Sketch(x) => Self::Sketch { value: Box::new(x) },
1234            Geometry::Solid(x) => Self::Solid { value: Box::new(x) },
1235        }
1236    }
1237}
1238
1239impl From<GeometryWithImportedGeometry> for KclValue {
1240    fn from(value: GeometryWithImportedGeometry) -> Self {
1241        match value {
1242            GeometryWithImportedGeometry::Sketch(x) => Self::Sketch { value: Box::new(x) },
1243            GeometryWithImportedGeometry::Solid(x) => Self::Solid { value: Box::new(x) },
1244            GeometryWithImportedGeometry::ImportedGeometry(x) => Self::ImportedGeometry(*x),
1245        }
1246    }
1247}
1248
1249impl From<Vec<GeometryWithImportedGeometry>> for KclValue {
1250    fn from(mut values: Vec<GeometryWithImportedGeometry>) -> Self {
1251        if values.len() == 1
1252            && let Some(v) = values.pop()
1253        {
1254            KclValue::from(v)
1255        } else {
1256            KclValue::HomArray {
1257                value: values.into_iter().map(KclValue::from).collect(),
1258                ty: RuntimeType::Union(vec![
1259                    RuntimeType::Primitive(PrimitiveType::Sketch),
1260                    RuntimeType::Primitive(PrimitiveType::Solid),
1261                    RuntimeType::Primitive(PrimitiveType::ImportedGeometry),
1262                ]),
1263            }
1264        }
1265    }
1266}
1267
1268#[cfg(test)]
1269mod tests {
1270    use super::*;
1271    use crate::exec::UnitType;
1272
1273    #[test]
1274    fn test_human_friendly_type() {
1275        let len = KclValue::Number {
1276            value: 1.0,
1277            ty: NumericType::Known(UnitType::GenericLength),
1278            meta: vec![],
1279        };
1280        assert_eq!(len.human_friendly_type(), "a number (Length)".to_string());
1281
1282        let unknown = KclValue::Number {
1283            value: 1.0,
1284            ty: NumericType::Unknown,
1285            meta: vec![],
1286        };
1287        assert_eq!(unknown.human_friendly_type(), "a number with unknown units".to_string());
1288
1289        let mm = KclValue::Number {
1290            value: 1.0,
1291            ty: NumericType::Known(UnitType::Length(UnitLength::Millimeters)),
1292            meta: vec![],
1293        };
1294        assert_eq!(mm.human_friendly_type(), "a number (mm)".to_string());
1295
1296        let array1_mm = KclValue::HomArray {
1297            value: vec![mm.clone()],
1298            ty: RuntimeType::any(),
1299        };
1300        assert_eq!(
1301            array1_mm.human_friendly_type(),
1302            "an array of `number(mm)` with 1 value".to_string()
1303        );
1304
1305        let array2_mm = KclValue::HomArray {
1306            value: vec![mm.clone(), mm.clone()],
1307            ty: RuntimeType::any(),
1308        };
1309        assert_eq!(
1310            array2_mm.human_friendly_type(),
1311            "an array of `number(mm)`, `number(mm)`".to_string()
1312        );
1313
1314        let array3_mm = KclValue::HomArray {
1315            value: vec![mm.clone(), mm.clone(), mm.clone()],
1316            ty: RuntimeType::any(),
1317        };
1318        assert_eq!(
1319            array3_mm.human_friendly_type(),
1320            "an array of `number(mm)`, `number(mm)`, `number(mm)`".to_string()
1321        );
1322
1323        let inches = KclValue::Number {
1324            value: 1.0,
1325            ty: NumericType::Known(UnitType::Length(UnitLength::Inches)),
1326            meta: vec![],
1327        };
1328        let array4 = KclValue::HomArray {
1329            value: vec![mm.clone(), mm.clone(), inches, mm],
1330            ty: RuntimeType::any(),
1331        };
1332        assert_eq!(
1333            array4.human_friendly_type(),
1334            "an array of `number(mm)`, `number(mm)`, `number(in)`, ... with 4 values".to_string()
1335        );
1336
1337        let empty_array = KclValue::HomArray {
1338            value: vec![],
1339            ty: RuntimeType::any(),
1340        };
1341        assert_eq!(empty_array.human_friendly_type(), "an empty array".to_string());
1342
1343        let array_nested = KclValue::HomArray {
1344            value: vec![array2_mm],
1345            ty: RuntimeType::any(),
1346        };
1347        assert_eq!(
1348            array_nested.human_friendly_type(),
1349            "an array of `[any; 2]` with 1 value".to_string()
1350        );
1351    }
1352
1353    fn color_def() -> Arc<EnumTypeDef> {
1354        Arc::new(
1355            EnumTypeDef::new(
1356                EnumTypeId::new(ModuleId::default(), "Color"),
1357                vec!["Red".to_owned(), "Green".to_owned()],
1358            )
1359            .unwrap(),
1360        )
1361    }
1362
1363    fn color_red() -> KclValue {
1364        KclValue::Enum {
1365            value: Box::new(EnumValue::new(color_def(), "Red", vec![])),
1366        }
1367    }
1368
1369    #[test]
1370    fn enum_values_describe_themselves_by_name_and_variant() {
1371        let red = color_red();
1372
1373        assert_eq!(red.human_friendly_type(), "a value of enum `Color`");
1374        // Feature-tree and variable display use the qualified form.
1375        assert_eq!(red.value_str(), Some("Color::Red".to_owned()));
1376        assert!(red.show_variable_in_feature_tree());
1377    }
1378
1379    /// The externally visible form of an enum value is its nominal identity,
1380    /// never a representation of the variant. Pinning both view types keeps a
1381    /// future `@repr` from leaking out of these surfaces by accident.
1382    #[test]
1383    fn enum_values_are_exposed_by_nominal_identity() {
1384        let view = crate::execution::KclValueView::from(color_red());
1385        assert_eq!(
1386            view,
1387            crate::execution::KclValueView::Enum {
1388                enum_name: "Color".to_owned(),
1389                variant: "Red".to_owned(),
1390            }
1391        );
1392
1393        let op = crate::execution::cad_op::op_from_kcl_value(&color_red());
1394        assert_eq!(
1395            op,
1396            kcl_api::OpKclValue::Enum {
1397                enum_name: "Color".to_owned(),
1398                variant: "Red".to_owned(),
1399            }
1400        );
1401    }
1402
1403    /// Serialization is the third such surface, and the one that reaches
1404    /// `program_memory.snap`. A value holds its whole declaration, so this pins
1405    /// that only the identity and the variant are written out: the declaration
1406    /// will carry `@repr` values, and those must not appear here.
1407    #[test]
1408    fn enum_values_serialize_as_identity_and_variant() {
1409        assert_eq!(
1410            serde_json::to_value(color_red()).unwrap(),
1411            serde_json::json!({
1412                "type": "Enum",
1413                "value": {
1414                    "enum_id": { "module_id": 0, "declared_name": "Color" },
1415                    "variant": "Red",
1416                },
1417            })
1418        );
1419    }
1420
1421    #[test]
1422    fn enum_declarations_carry_their_variants() {
1423        let def = EnumTypeDef::new(
1424            EnumTypeId::new(ModuleId::default(), "Color"),
1425            vec!["Red".to_owned(), "Green".to_owned()],
1426        )
1427        .unwrap();
1428
1429        assert_eq!(def.variants(), ["Red", "Green"]);
1430        assert!(def.has_variant("Red"));
1431        assert!(!def.has_variant("Blue"));
1432        // Identity is the declaration, not the variant set: an enum declaring
1433        // the same variants elsewhere is a different type.
1434        assert_ne!(
1435            def.id(),
1436            EnumTypeDef::new(
1437                EnumTypeId::new(ModuleId::from_usize(1), "Color"),
1438                vec!["Red".to_owned(), "Green".to_owned()],
1439            )
1440            .unwrap()
1441            .id()
1442        );
1443    }
1444
1445    #[test]
1446    fn enum_rejects_duplicate_variant() {
1447        let err = EnumTypeDef::new(
1448            EnumTypeId::new(ModuleId::default(), "Color"),
1449            vec!["Red".to_owned(), "Green".to_owned(), "Red".to_owned()],
1450        )
1451        .unwrap_err();
1452
1453        assert_eq!(
1454            err,
1455            DuplicateVariant {
1456                name: "Red".to_owned(),
1457                first_index: 0,
1458                duplicate_index: 2,
1459            }
1460        );
1461    }
1462
1463    #[test]
1464    fn enum_reports_earliest_duplicate() {
1465        // `Green` repeats at index 3 and `Red` at index 4. The caller reports one
1466        // duplicate, so it must be the one the user reads first.
1467        let err = EnumTypeDef::new(
1468            EnumTypeId::new(ModuleId::default(), "Color"),
1469            vec![
1470                "Red".to_owned(),
1471                "Green".to_owned(),
1472                "Blue".to_owned(),
1473                "Green".to_owned(),
1474                "Red".to_owned(),
1475            ],
1476        )
1477        .unwrap_err();
1478
1479        assert_eq!(err.name, "Green");
1480        assert_eq!(err.first_index, 1);
1481        assert_eq!(err.duplicate_index, 3);
1482    }
1483}