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