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::NamedViewValue;
28use crate::execution::Plane;
29use crate::execution::Segment;
30use crate::execution::SegmentRepr;
31use crate::execution::Sketch;
32use crate::execution::SketchConstraint;
33use crate::execution::SketchVar;
34use crate::execution::SketchVarId;
35use crate::execution::Solid;
36use crate::execution::TagIdentifier;
37use crate::execution::UnsolvedExpr;
38use crate::execution::annotations::FnAttrs;
39use crate::execution::annotations::SETTINGS;
40use crate::execution::annotations::SETTINGS_UNIT_LENGTH;
41use crate::execution::annotations::VersionConstraint;
42use crate::execution::annotations::{self};
43use crate::execution::types::NumericType;
44use crate::execution::types::NumericTypeExt;
45use crate::execution::types::PrimitiveType;
46use crate::execution::types::RuntimeType;
47use crate::parsing::ast::types::BoxNode;
48use crate::parsing::ast::types::DefaultParamVal;
49use crate::parsing::ast::types::FunctionExpression;
50use crate::parsing::ast::types::KclNone;
51use crate::parsing::ast::types::Literal;
52use crate::parsing::ast::types::LiteralValue;
53use crate::parsing::ast::types::Node;
54use crate::parsing::ast::types::NumericLiteral;
55use crate::parsing::ast::types::TagDeclarator;
56use crate::parsing::ast::types::TagNode;
57use crate::parsing::ast::types::Type;
58use crate::std::StdFnProps;
59use crate::std::args::TyF64;
60
61pub type KclObjectFields = HashMap<String, KclValue>;
62
63#[derive(Debug, Clone, Default, PartialEq, Serialize)]
64pub enum KclObjectKind {
65    #[default]
66    Default,
67    SketchTags {
68        #[serde(default, skip_serializing_if = "Vec::is_empty")]
69        deprecated_solid_tag_names: Vec<String>,
70    },
71}
72
73impl KclObjectKind {
74    pub(crate) fn is_default(&self) -> bool {
75        match self {
76            KclObjectKind::Default => true,
77            KclObjectKind::SketchTags { .. } => false,
78        }
79    }
80
81    pub(crate) fn deprecated_solid_tag_names(&self) -> &[String] {
82        match self {
83            Self::Default => &[],
84            Self::SketchTags {
85                deprecated_solid_tag_names,
86            } => deprecated_solid_tag_names,
87        }
88    }
89}
90
91/// Any KCL value.
92#[derive(Debug, Clone, Serialize, PartialEq)]
93#[serde(tag = "type")]
94pub enum KclValue {
95    Uuid {
96        value: ::uuid::Uuid,
97        #[serde(skip)]
98        meta: Vec<Metadata>,
99    },
100    Bool {
101        value: bool,
102        #[serde(skip)]
103        meta: Vec<Metadata>,
104    },
105    Number {
106        value: f64,
107        ty: NumericType,
108        #[serde(skip)]
109        meta: Vec<Metadata>,
110    },
111    String {
112        value: String,
113        #[serde(skip)]
114        meta: Vec<Metadata>,
115    },
116    Enum {
117        value: Box<EnumValue>,
118    },
119    SketchVar {
120        value: Box<SketchVar>,
121    },
122    SketchConstraint {
123        value: Box<SketchConstraint>,
124    },
125    Tuple {
126        value: Vec<KclValue>,
127        #[serde(skip)]
128        meta: Vec<Metadata>,
129    },
130    // An array where all values have a shared type (not necessarily the same principal type).
131    HomArray {
132        value: Vec<KclValue>,
133        // The type of values, not the array type.
134        #[serde(skip)]
135        ty: RuntimeType,
136    },
137    Object {
138        value: KclObjectFields,
139        constrainable: bool,
140        #[serde(default, skip_serializing_if = "KclObjectKind::is_default")]
141        object_kind: KclObjectKind,
142        #[serde(skip)]
143        meta: Vec<Metadata>,
144    },
145    TagIdentifier(Box<TagIdentifier>),
146    TagDeclarator(BoxNode<TagDeclarator>),
147    GdtAnnotation {
148        value: Box<GdtAnnotation>,
149    },
150    Plane {
151        value: Box<Plane>,
152    },
153    Face {
154        value: Box<Face>,
155    },
156    BoundedEdge {
157        value: BoundedEdge,
158        meta: Vec<Metadata>,
159    },
160    Segment {
161        value: Box<AbstractSegment>,
162    },
163    Sketch {
164        value: Box<Sketch>,
165    },
166    Solid {
167        value: Box<Solid>,
168    },
169    Helix {
170        value: Box<Helix>,
171    },
172    CameraView {
173        value: Box<CameraView>,
174    },
175    NamedView {
176        value: Box<NamedViewValue>,
177    },
178    ImportedGeometry(ImportedGeometry),
179    Function {
180        #[serde(serialize_with = "function_value_stub")]
181        value: Box<FunctionSource>,
182        #[serde(skip)]
183        meta: Vec<Metadata>,
184    },
185    Module {
186        value: ModuleId,
187        #[serde(skip)]
188        meta: Vec<Metadata>,
189    },
190    Type {
191        #[serde(skip)]
192        value: TypeDef,
193        experimental: bool,
194        #[serde(skip)]
195        meta: Vec<Metadata>,
196    },
197    KclNone {
198        value: KclNone,
199        #[serde(skip)]
200        meta: Vec<Metadata>,
201    },
202}
203
204fn function_value_stub<S>(_value: &FunctionSource, serializer: S) -> Result<S::Ok, S::Error>
205where
206    S: serde::Serializer,
207{
208    serializer.serialize_unit()
209}
210
211#[derive(Debug, Clone, PartialEq)]
212pub struct NamedParam {
213    pub experimental: bool,
214    /// If true, this parameter is deprecated regardless of the KCL version.
215    pub deprecated: bool,
216    /// Constraint marking the KCL version at or after which this parameter is deprecated.
217    pub deprecated_since: Option<VersionConstraint>,
218    pub default_value: Option<DefaultParamVal>,
219    pub ty: Option<Type>,
220    /// The `RuntimeType` that `ty` resolved to when the function declaration
221    /// executed, so the resolution happened in the scope where the signature
222    /// is written. `None` when `ty` is `None`. Populated by
223    /// [`FunctionSource::resolve_signature_types`].
224    pub resolved_ty: Option<RuntimeType>,
225}
226
227#[derive(Debug, Clone, PartialEq)]
228pub struct FunctionSource {
229    pub input_arg: Option<(String, Option<Type>)>,
230    /// The `RuntimeType` that the input (unlabeled) argument's type resolved
231    /// to when the function declaration executed. `None` when the input
232    /// argument has no type annotation. Populated by
233    /// [`FunctionSource::resolve_signature_types`].
234    pub resolved_input_ty: Option<RuntimeType>,
235    pub named_args: IndexMap<String, NamedParam>,
236    pub return_type: Option<Node<Type>>,
237    /// The `RuntimeType` that `return_type` resolved to when the function
238    /// declaration executed. `None` when `return_type` is `None`. Populated
239    /// by [`FunctionSource::resolve_signature_types`].
240    pub resolved_return_ty: Option<RuntimeType>,
241    pub deprecated: bool,
242    /// Constraint on the KCL version at which this function is deprecated, e.g.
243    /// "2.0". When the active `kclVersion` is at or after this, calls trigger a
244    /// deprecation warning.
245    pub deprecated_since: Option<VersionConstraint>,
246    pub experimental: bool,
247    pub include_in_feature_tree: bool,
248    pub std_props: Option<StdFnProps>,
249    pub body: FunctionBody,
250    pub ast: BoxNode<FunctionExpression>,
251}
252
253pub struct KclFunctionSourceParams {
254    pub std_props: Option<StdFnProps>,
255    pub experimental: bool,
256    pub include_in_feature_tree: bool,
257}
258
259impl FunctionSource {
260    pub fn rust(func: crate::std::StdFn, ast: BoxNode<FunctionExpression>, props: StdFnProps, attrs: FnAttrs) -> 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: BoxNode<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::NamedView { value } => to_vec_sr(value.meta()),
616            KclValue::ImportedGeometry(i) => to_vec_sr(&i.meta),
617            KclValue::Function { meta, .. } => to_vec_sr(&meta),
618            KclValue::Plane { value } => to_vec_sr(&value.meta),
619            KclValue::Face { value } => to_vec_sr(&value.meta),
620            KclValue::Segment { value } => to_vec_sr(&value.meta),
621            KclValue::Bool { meta, .. } => to_vec_sr(&meta),
622            KclValue::Number { meta, .. } => to_vec_sr(&meta),
623            KclValue::String { meta, .. } => to_vec_sr(&meta),
624            KclValue::Enum { value } => to_vec_sr(value.meta()),
625            KclValue::SketchVar { value, .. } => to_vec_sr(&value.meta),
626            KclValue::SketchConstraint { value, .. } => to_vec_sr(&value.meta),
627            KclValue::Tuple { meta, .. } => to_vec_sr(&meta),
628            KclValue::HomArray { value, .. } => value.iter().flat_map(Into::<Vec<SourceRange>>::into).collect(),
629            KclValue::Object { meta, .. } => to_vec_sr(&meta),
630            KclValue::Module { meta, .. } => to_vec_sr(&meta),
631            KclValue::Uuid { meta, .. } => to_vec_sr(&meta),
632            KclValue::Type { meta, .. } => to_vec_sr(&meta),
633            KclValue::KclNone { meta, .. } => to_vec_sr(&meta),
634            KclValue::BoundedEdge { meta, .. } => to_vec_sr(&meta),
635        }
636    }
637}
638
639fn to_vec_sr(meta: &[Metadata]) -> Vec<SourceRange> {
640    meta.iter().map(|m| m.source_range).collect()
641}
642
643impl From<&KclValue> for Vec<SourceRange> {
644    fn from(item: &KclValue) -> Self {
645        match item {
646            KclValue::TagDeclarator(t) => vec![SourceRange::new(t.start, t.end, t.module_id)],
647            KclValue::TagIdentifier(t) => to_vec_sr(&t.meta),
648            KclValue::GdtAnnotation { value } => to_vec_sr(&value.meta),
649            KclValue::Solid { value } => to_vec_sr(&value.meta),
650            KclValue::Sketch { value } => to_vec_sr(&value.meta),
651            KclValue::Helix { value } => to_vec_sr(&value.meta),
652            KclValue::CameraView { value } => to_vec_sr(value.meta()),
653            KclValue::NamedView { value } => to_vec_sr(value.meta()),
654            KclValue::ImportedGeometry(i) => to_vec_sr(&i.meta),
655            KclValue::Function { meta, .. } => to_vec_sr(meta),
656            KclValue::Plane { value } => to_vec_sr(&value.meta),
657            KclValue::Face { value } => to_vec_sr(&value.meta),
658            KclValue::Segment { value } => to_vec_sr(&value.meta),
659            KclValue::Bool { meta, .. } => to_vec_sr(meta),
660            KclValue::Number { meta, .. } => to_vec_sr(meta),
661            KclValue::String { meta, .. } => to_vec_sr(meta),
662            KclValue::Enum { value } => to_vec_sr(value.meta()),
663            KclValue::SketchVar { value, .. } => to_vec_sr(&value.meta),
664            KclValue::SketchConstraint { value, .. } => to_vec_sr(&value.meta),
665            KclValue::Uuid { meta, .. } => to_vec_sr(meta),
666            KclValue::Tuple { meta, .. } => to_vec_sr(meta),
667            KclValue::HomArray { value, .. } => value.iter().flat_map(Into::<Vec<SourceRange>>::into).collect(),
668            KclValue::Object { meta, .. } => to_vec_sr(meta),
669            KclValue::Module { meta, .. } => to_vec_sr(meta),
670            KclValue::KclNone { meta, .. } => to_vec_sr(meta),
671            KclValue::Type { meta, .. } => to_vec_sr(meta),
672            KclValue::BoundedEdge { meta, .. } => to_vec_sr(meta),
673        }
674    }
675}
676
677impl From<&KclValue> for SourceRange {
678    fn from(item: &KclValue) -> Self {
679        let v: Vec<_> = item.into();
680        v.into_iter().next().unwrap_or_default()
681    }
682}
683
684impl KclValue {
685    pub(crate) fn metadata(&self) -> Vec<Metadata> {
686        match self {
687            KclValue::Uuid { value: _, meta } => meta.clone(),
688            KclValue::Bool { value: _, meta } => meta.clone(),
689            KclValue::Number { meta, .. } => meta.clone(),
690            KclValue::String { value: _, meta } => meta.clone(),
691            KclValue::Enum { value } => value.meta().to_vec(),
692            KclValue::SketchVar { value, .. } => value.meta.clone(),
693            KclValue::SketchConstraint { value, .. } => value.meta.clone(),
694            KclValue::Tuple { value: _, meta } => meta.clone(),
695            KclValue::HomArray { value, .. } => value.iter().flat_map(|v| v.metadata()).collect(),
696            KclValue::Object { meta, .. } => meta.clone(),
697            KclValue::TagIdentifier(x) => x.meta.clone(),
698            KclValue::TagDeclarator(x) => vec![x.metadata()],
699            KclValue::GdtAnnotation { value } => value.meta.clone(),
700            KclValue::Plane { value } => value.meta.clone(),
701            KclValue::Face { value } => value.meta.clone(),
702            KclValue::Segment { value } => value.meta.clone(),
703            KclValue::Sketch { value } => value.meta.clone(),
704            KclValue::Solid { value } => value.meta.clone(),
705            KclValue::Helix { value } => value.meta.clone(),
706            KclValue::CameraView { value } => value.meta().to_vec(),
707            KclValue::NamedView { value } => value.meta().to_vec(),
708            KclValue::ImportedGeometry(x) => x.meta.clone(),
709            KclValue::Function { meta, .. } => meta.clone(),
710            KclValue::Module { meta, .. } => meta.clone(),
711            KclValue::KclNone { meta, .. } => meta.clone(),
712            KclValue::Type { meta, .. } => meta.clone(),
713            KclValue::BoundedEdge { meta, .. } => meta.clone(),
714        }
715    }
716
717    #[allow(unused)]
718    pub(crate) fn none() -> Self {
719        Self::KclNone {
720            value: Default::default(),
721            meta: Default::default(),
722        }
723    }
724
725    /// Returns true if we should generate an [`crate::execution::Operation`] to
726    /// display in the Feature Tree for variable declarations initialized with
727    /// this value.
728    pub(crate) fn show_variable_in_feature_tree(&self) -> bool {
729        match self {
730            KclValue::Uuid { .. } => false,
731            KclValue::Bool { .. } | KclValue::Number { .. } | KclValue::String { .. } | KclValue::Enum { .. } => true,
732            KclValue::SketchVar { .. }
733            | KclValue::SketchConstraint { .. }
734            | KclValue::Tuple { .. }
735            | KclValue::HomArray { .. }
736            | KclValue::Object { .. }
737            | KclValue::TagIdentifier(_)
738            | KclValue::TagDeclarator(_)
739            | KclValue::GdtAnnotation { .. }
740            | KclValue::Plane { .. }
741            | KclValue::Face { .. }
742            | KclValue::Segment { .. }
743            | KclValue::Sketch { .. }
744            | KclValue::Solid { .. }
745            | KclValue::Helix { .. }
746            | KclValue::CameraView { .. }
747            | KclValue::NamedView { .. }
748            | KclValue::ImportedGeometry(_)
749            | KclValue::Function { .. }
750            | KclValue::Module { .. }
751            | KclValue::Type { .. }
752            | KclValue::BoundedEdge { .. }
753            | KclValue::KclNone { .. } => false,
754        }
755    }
756
757    /// Human readable type name used in error messages.  Should not be relied
758    /// on for program logic.
759    pub(crate) fn human_friendly_type(&self) -> String {
760        match self {
761            KclValue::Uuid { .. } => "a unique ID (uuid)".to_owned(),
762            KclValue::TagDeclarator(_) => "a tag declarator".to_owned(),
763            KclValue::TagIdentifier(_) => "a tag identifier".to_owned(),
764            KclValue::GdtAnnotation { .. } => "an annotation".to_owned(),
765            KclValue::Solid { .. } => "a solid".to_owned(),
766            KclValue::Sketch { .. } => "a sketch".to_owned(),
767            KclValue::Helix { .. } => "a helix".to_owned(),
768            KclValue::CameraView { .. } => "a camera view".to_owned(),
769            KclValue::NamedView { .. } => "a named view".to_owned(),
770            KclValue::ImportedGeometry(_) => "an imported geometry".to_owned(),
771            KclValue::Function { .. } => "a function".to_owned(),
772            KclValue::Plane { .. } => "a plane".to_owned(),
773            KclValue::Face { .. } => "a face".to_owned(),
774            KclValue::Segment { .. } => "a segment".to_owned(),
775            KclValue::Bool { .. } => "a boolean (`true` or `false`)".to_owned(),
776            KclValue::Number {
777                ty: NumericType::Unknown,
778                ..
779            } => "a number with unknown units".to_owned(),
780            KclValue::Number {
781                ty: NumericType::Known(units),
782                ..
783            } => format!("a number ({units})"),
784            KclValue::Number { .. } => "a number".to_owned(),
785            KclValue::String { .. } => "a string".to_owned(),
786            KclValue::Enum { value } => format!("a value of enum `{}`", value.enum_id().declared_name()),
787            KclValue::SketchVar { .. } => "a sketch variable".to_owned(),
788            KclValue::SketchConstraint { .. } => "a sketch constraint".to_owned(),
789            KclValue::Object { .. } => "an object".to_owned(),
790            KclValue::Module { .. } => "a module".to_owned(),
791            KclValue::Type { .. } => "a type".to_owned(),
792            KclValue::KclNone { .. } => "none".to_owned(),
793            KclValue::BoundedEdge { .. } => "a bounded edge".to_owned(),
794            KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => {
795                if value.is_empty() {
796                    "an empty array".to_owned()
797                } else {
798                    // A max of 3 is good because it's common to use 3D points.
799                    const MAX: usize = 3;
800
801                    let len = value.len();
802                    let element_tys = value
803                        .iter()
804                        .take(MAX)
805                        .map(|elem| elem.principal_type_string())
806                        .collect::<Vec<_>>()
807                        .join(", ");
808                    let mut result = format!("an array of {element_tys}");
809                    if len > MAX {
810                        result.push_str(&format!(", ... with {len} values"));
811                    }
812                    if len == 1 {
813                        result.push_str(" with 1 value");
814                    }
815                    result
816                }
817            }
818        }
819    }
820
821    pub(crate) fn from_sketch_var_literal(
822        literal: &Node<NumericLiteral>,
823        id: SketchVarId,
824        node_path: Option<crate::NodePath>,
825        exec_state: &ExecState,
826    ) -> Self {
827        let meta = vec![literal.metadata()];
828        let ty = NumericType::from_parsed(literal.suffix, &exec_state.mod_local.settings);
829        KclValue::SketchVar {
830            value: Box::new(SketchVar {
831                id,
832                initial_value: literal.value,
833                node_path,
834                meta,
835                ty,
836            }),
837        }
838    }
839
840    pub(crate) fn from_literal(literal: Node<Literal>, exec_state: &mut ExecState) -> Self {
841        let meta = vec![literal.metadata()];
842        match literal.inner.value {
843            LiteralValue::Number { value, suffix } => {
844                let ty = NumericType::from_parsed(suffix, &exec_state.mod_local.settings);
845                if let NumericType::Default { len, .. } = &ty
846                    && !exec_state.mod_local.explicit_length_units
847                    && *len != UnitLength::Millimeters
848                {
849                    exec_state.warn(
850                        CompilationIssue::err(
851                            literal.as_source_range(),
852                            "Project-wide units are deprecated. Prefer to use per-file default units.",
853                        )
854                        .with_suggestion(
855                            "Fix by adding per-file settings",
856                            format!("@{SETTINGS}({SETTINGS_UNIT_LENGTH} = {len})\n"),
857                            // Insert at the start of the file.
858                            Some(SourceRange::new(0, 0, literal.module_id)),
859                            crate::errors::Tag::Deprecated,
860                        ),
861                        annotations::WARN_DEPRECATED,
862                    );
863                }
864                KclValue::Number { value, meta, ty }
865            }
866            LiteralValue::String(value) => KclValue::String { value, meta },
867            LiteralValue::Bool(value) => KclValue::Bool { value, meta },
868        }
869    }
870
871    pub(crate) fn from_default_param(param: DefaultParamVal, exec_state: &mut ExecState) -> Self {
872        match param {
873            DefaultParamVal::Literal(lit) => Self::from_literal(lit, exec_state),
874            DefaultParamVal::KclNone(value) => KclValue::KclNone {
875                value,
876                meta: Default::default(),
877            },
878        }
879    }
880
881    pub(crate) fn map_env_ref(&self, old_env: EnvironmentRef, new_env: EnvironmentRef) -> Self {
882        let mut result = self.clone();
883        if let KclValue::Function { ref mut value, .. } = result
884            && let FunctionSource {
885                body: FunctionBody::Kcl(memory),
886                ..
887            } = &mut **value
888        {
889            memory.replace_env(old_env, new_env);
890        }
891
892        result
893    }
894
895    pub(crate) fn map_env_ref_and_epoch(&self, old_env: EnvironmentRef, new_env: EnvironmentRef) -> Self {
896        let mut result = self.clone();
897        if let KclValue::Function { ref mut value, .. } = result
898            && let FunctionSource {
899                body: FunctionBody::Kcl(memory),
900                ..
901            } = &mut **value
902        {
903            memory.replace_env_and_epoch(old_env, new_env);
904        }
905
906        result
907    }
908
909    pub const fn from_number_with_type(f: f64, ty: NumericType, meta: Vec<Metadata>) -> Self {
910        Self::Number { value: f, meta, ty }
911    }
912
913    /// Put the point into a KCL value.
914    pub fn from_point2d(p: [f64; 2], ty: NumericType, meta: Vec<Metadata>) -> Self {
915        let [x, y] = p;
916        Self::Tuple {
917            value: vec![
918                Self::Number {
919                    value: x,
920                    meta: meta.clone(),
921                    ty,
922                },
923                Self::Number {
924                    value: y,
925                    meta: meta.clone(),
926                    ty,
927                },
928            ],
929            meta,
930        }
931    }
932
933    pub fn from_imported_geometries(geometries: Vec<ImportedGeometry>) -> Self {
934        geometries
935            .into_iter()
936            .map(|geometry| GeometryWithImportedGeometry::ImportedGeometry(Box::new(geometry)))
937            .collect::<Vec<_>>()
938            .into()
939    }
940
941    /// Put the point into a KCL value.
942    pub fn from_point3d(p: [f64; 3], ty: NumericType, meta: Vec<Metadata>) -> Self {
943        let [x, y, z] = p;
944        Self::Tuple {
945            value: vec![
946                Self::Number {
947                    value: x,
948                    meta: meta.clone(),
949                    ty,
950                },
951                Self::Number {
952                    value: y,
953                    meta: meta.clone(),
954                    ty,
955                },
956                Self::Number {
957                    value: z,
958                    meta: meta.clone(),
959                    ty,
960                },
961            ],
962            meta,
963        }
964    }
965
966    /// Put the point into a KCL point.
967    pub(crate) fn array_from_point2d(p: [f64; 2], ty: NumericType, meta: Vec<Metadata>) -> Self {
968        let [x, y] = p;
969        Self::HomArray {
970            value: vec![
971                Self::Number {
972                    value: x,
973                    meta: meta.clone(),
974                    ty,
975                },
976                Self::Number { value: y, meta, ty },
977            ],
978            ty: ty.into(),
979        }
980    }
981
982    /// Put the point into a KCL point.
983    pub fn array_from_point3d(p: [f64; 3], ty: NumericType, meta: Vec<Metadata>) -> Self {
984        let [x, y, z] = p;
985        Self::HomArray {
986            value: vec![
987                Self::Number {
988                    value: x,
989                    meta: meta.clone(),
990                    ty,
991                },
992                Self::Number {
993                    value: y,
994                    meta: meta.clone(),
995                    ty,
996                },
997                Self::Number { value: z, meta, ty },
998            ],
999            ty: ty.into(),
1000        }
1001    }
1002
1003    pub(crate) fn from_unsolved_expr(expr: UnsolvedExpr, meta: Vec<Metadata>) -> Self {
1004        match expr {
1005            UnsolvedExpr::Known(v) => crate::execution::KclValue::Number {
1006                value: v.n,
1007                ty: v.ty,
1008                meta,
1009            },
1010            // The original sketch var (if any) lives in `sketch_vars` and carries
1011            // its own node_path; this synthesized wrapper isn't pushed there, so
1012            // its node_path doesn't drive var-solution writeback.
1013            UnsolvedExpr::Unknown(var_id) => crate::execution::KclValue::SketchVar {
1014                value: Box::new(SketchVar {
1015                    id: var_id,
1016                    initial_value: Default::default(),
1017                    // TODO: Should this be the solver units?
1018                    ty: Default::default(),
1019                    node_path: None,
1020                    meta,
1021                }),
1022            },
1023        }
1024    }
1025
1026    pub(crate) fn as_usize(&self) -> Option<usize> {
1027        match self {
1028            KclValue::Number { value, .. } => crate::try_f64_to_usize(*value),
1029            _ => None,
1030        }
1031    }
1032
1033    pub fn as_int(&self) -> Option<i64> {
1034        match self {
1035            KclValue::Number { value, .. } => crate::try_f64_to_i64(*value),
1036            _ => None,
1037        }
1038    }
1039
1040    pub fn as_int_with_ty(&self) -> Option<(i64, NumericType)> {
1041        match self {
1042            KclValue::Number { value, ty, .. } => crate::try_f64_to_i64(*value).map(|i| (i, *ty)),
1043            _ => None,
1044        }
1045    }
1046
1047    pub fn as_object(&self) -> Option<&KclObjectFields> {
1048        match self {
1049            KclValue::Object { value, .. } => Some(value),
1050            _ => None,
1051        }
1052    }
1053
1054    pub fn into_object(self) -> Option<KclObjectFields> {
1055        match self {
1056            KclValue::Object { value, .. } => Some(value),
1057            _ => None,
1058        }
1059    }
1060
1061    pub fn as_unsolved_expr(&self) -> Option<UnsolvedExpr> {
1062        match self {
1063            KclValue::Number { value, ty, .. } => Some(UnsolvedExpr::Known(TyF64::new(*value, *ty))),
1064            KclValue::SketchVar { value, .. } => Some(UnsolvedExpr::Unknown(value.id)),
1065            _ => None,
1066        }
1067    }
1068
1069    pub fn to_sketch_expr(&self) -> Option<crate::front::Expr> {
1070        match self {
1071            KclValue::Number { value, ty, .. } => Some(crate::front::Expr::Number(crate::front::Number {
1072                value: *value,
1073                units: (*ty).try_into().ok()?,
1074            })),
1075            KclValue::SketchVar { value, .. } => Some(crate::front::Expr::Var(crate::front::Number {
1076                value: value.initial_value,
1077                units: value.ty.try_into().ok()?,
1078            })),
1079            _ => None,
1080        }
1081    }
1082
1083    pub fn as_str(&self) -> Option<&str> {
1084        match self {
1085            KclValue::String { value, .. } => Some(value),
1086            _ => None,
1087        }
1088    }
1089
1090    pub fn into_array(self) -> Vec<KclValue> {
1091        match self {
1092            KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => value,
1093            _ => vec![self],
1094        }
1095    }
1096
1097    pub fn as_slice(&self) -> Option<&[KclValue]> {
1098        match self {
1099            KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => Some(value),
1100            _ => None,
1101        }
1102    }
1103
1104    pub fn as_point2d(&self) -> Option<[TyF64; 2]> {
1105        let value = match self {
1106            KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => value,
1107            _ => return None,
1108        };
1109
1110        let [x, y] = value.as_slice() else {
1111            return None;
1112        };
1113        let x = x.as_ty_f64()?;
1114        let y = y.as_ty_f64()?;
1115        Some([x, y])
1116    }
1117
1118    pub fn as_point3d(&self) -> Option<[TyF64; 3]> {
1119        let value = match self {
1120            KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => value,
1121            _ => return None,
1122        };
1123
1124        let [x, y, z] = value.as_slice() else {
1125            return None;
1126        };
1127        let x = x.as_ty_f64()?;
1128        let y = y.as_ty_f64()?;
1129        let z = z.as_ty_f64()?;
1130        Some([x, y, z])
1131    }
1132
1133    pub fn as_uuid(&self) -> Option<uuid::Uuid> {
1134        match self {
1135            KclValue::Uuid { value, .. } => Some(*value),
1136            _ => None,
1137        }
1138    }
1139
1140    pub fn as_plane(&self) -> Option<&Plane> {
1141        match self {
1142            KclValue::Plane { value, .. } => Some(value),
1143            _ => None,
1144        }
1145    }
1146
1147    pub fn as_solid(&self) -> Option<&Solid> {
1148        match self {
1149            KclValue::Solid { value, .. } => Some(value),
1150            _ => None,
1151        }
1152    }
1153
1154    pub fn as_sketch(&self) -> Option<&Sketch> {
1155        match self {
1156            KclValue::Sketch { value, .. } => Some(value),
1157            _ => None,
1158        }
1159    }
1160
1161    pub fn as_mut_sketch(&mut self) -> Option<&mut Sketch> {
1162        match self {
1163            KclValue::Sketch { value } => Some(value),
1164            _ => None,
1165        }
1166    }
1167
1168    pub fn as_sketch_var(&self) -> Option<&SketchVar> {
1169        match self {
1170            KclValue::SketchVar { value, .. } => Some(value),
1171            _ => None,
1172        }
1173    }
1174
1175    /// A solved segment.
1176    pub fn as_segment(&self) -> Option<&Segment> {
1177        match self {
1178            KclValue::Segment { value, .. } => match &value.repr {
1179                SegmentRepr::Solved { segment } => Some(segment),
1180                _ => None,
1181            },
1182            _ => None,
1183        }
1184    }
1185
1186    /// A solved segment.
1187    pub fn into_segment(self) -> Option<Segment> {
1188        match self {
1189            KclValue::Segment { value, .. } => match value.repr {
1190                SegmentRepr::Solved { segment } => Some(*segment),
1191                _ => None,
1192            },
1193            _ => None,
1194        }
1195    }
1196
1197    pub fn as_mut_tag(&mut self) -> Option<&mut TagIdentifier> {
1198        match self {
1199            KclValue::TagIdentifier(value) => Some(value),
1200            _ => None,
1201        }
1202    }
1203
1204    #[cfg(test)]
1205    pub fn as_f64(&self) -> Option<f64> {
1206        match self {
1207            KclValue::Number { value, .. } => Some(*value),
1208            _ => None,
1209        }
1210    }
1211
1212    pub fn as_ty_f64(&self) -> Option<TyF64> {
1213        match self {
1214            KclValue::Number { value, ty, .. } => Some(TyF64::new(*value, *ty)),
1215            _ => None,
1216        }
1217    }
1218
1219    pub fn as_bool(&self) -> Option<bool> {
1220        match self {
1221            KclValue::Bool { value, .. } => Some(*value),
1222            _ => None,
1223        }
1224    }
1225
1226    /// If this value is of type function, return it.
1227    pub fn as_function(&self) -> Option<&FunctionSource> {
1228        match self {
1229            KclValue::Function { value, .. } => Some(value),
1230            _ => None,
1231        }
1232    }
1233
1234    /// Get a tag identifier from a memory item.
1235    pub fn get_tag_identifier(&self) -> Result<TagIdentifier, KclError> {
1236        match self {
1237            KclValue::TagIdentifier(t) => Ok(*t.clone()),
1238            _ => Err(KclError::new_semantic(KclErrorDetails::new(
1239                format!("Not a tag identifier: {self:?}"),
1240                self.clone().into(),
1241            ))),
1242        }
1243    }
1244
1245    /// Get a tag declarator from a memory item.
1246    pub fn get_tag_declarator(&self) -> Result<TagNode, KclError> {
1247        match self {
1248            KclValue::TagDeclarator(t) => Ok((**t).clone()),
1249            _ => Err(KclError::new_semantic(KclErrorDetails::new(
1250                format!("Not a tag declarator: {self:?}"),
1251                self.clone().into(),
1252            ))),
1253        }
1254    }
1255
1256    /// If this KCL value is a bool, retrieve it.
1257    pub fn get_bool(&self) -> Result<bool, KclError> {
1258        self.as_bool().ok_or_else(|| {
1259            KclError::new_type(KclErrorDetails::new(
1260                format!("Expected bool, found {}", self.human_friendly_type()),
1261                self.into(),
1262            ))
1263        })
1264    }
1265
1266    pub fn is_unknown_number(&self) -> bool {
1267        match self {
1268            KclValue::Number { ty, .. } => !ty.is_fully_specified(),
1269            _ => false,
1270        }
1271    }
1272
1273    pub fn value_str(&self) -> Option<String> {
1274        match self {
1275            KclValue::Bool { value, .. } => Some(format!("{value}")),
1276            // TODO: Show units.
1277            KclValue::Number { value, .. } => Some(format!("{value}")),
1278            KclValue::String { value, .. } => Some(format!("'{value}'")),
1279            KclValue::Enum { value } => Some(value.qualified_name()),
1280            // TODO: Show units.
1281            KclValue::SketchVar { value, .. } => Some(format!("var {}", value.initial_value)),
1282            KclValue::Uuid { value, .. } => Some(format!("{value}")),
1283            KclValue::TagDeclarator(tag) => Some(format!("${}", tag.name)),
1284            KclValue::TagIdentifier(tag) => Some(format!("${}", tag.value)),
1285            // TODO better Array and Object stringification
1286            KclValue::Tuple { .. } => Some("[...]".to_owned()),
1287            KclValue::HomArray { .. } => Some("[...]".to_owned()),
1288            KclValue::Object { .. } => Some("{ ... }".to_owned()),
1289            KclValue::Module { .. }
1290            | KclValue::GdtAnnotation { .. }
1291            | KclValue::SketchConstraint { .. }
1292            | KclValue::Solid { .. }
1293            | KclValue::Sketch { .. }
1294            | KclValue::Helix { .. }
1295            | KclValue::CameraView { .. }
1296            | KclValue::NamedView { .. }
1297            | KclValue::ImportedGeometry(_)
1298            | KclValue::Function { .. }
1299            | KclValue::Plane { .. }
1300            | KclValue::Face { .. }
1301            | KclValue::Segment { .. }
1302            | KclValue::KclNone { .. }
1303            | KclValue::BoundedEdge { .. }
1304            | KclValue::Type { .. } => None,
1305        }
1306    }
1307}
1308
1309impl From<Geometry> for KclValue {
1310    fn from(value: Geometry) -> Self {
1311        match value {
1312            Geometry::Sketch(x) => Self::Sketch { value: Box::new(x) },
1313            Geometry::Solid(x) => Self::Solid { value: Box::new(x) },
1314        }
1315    }
1316}
1317
1318impl From<GeometryWithImportedGeometry> for KclValue {
1319    fn from(value: GeometryWithImportedGeometry) -> Self {
1320        match value {
1321            GeometryWithImportedGeometry::Sketch(x) => Self::Sketch { value: Box::new(x) },
1322            GeometryWithImportedGeometry::Solid(x) => Self::Solid { value: Box::new(x) },
1323            GeometryWithImportedGeometry::ImportedGeometry(x) => Self::ImportedGeometry(*x),
1324        }
1325    }
1326}
1327
1328impl From<Vec<GeometryWithImportedGeometry>> for KclValue {
1329    fn from(mut values: Vec<GeometryWithImportedGeometry>) -> Self {
1330        if values.len() == 1
1331            && let Some(v) = values.pop()
1332        {
1333            KclValue::from(v)
1334        } else {
1335            KclValue::HomArray {
1336                value: values.into_iter().map(KclValue::from).collect(),
1337                ty: RuntimeType::Union(vec![
1338                    RuntimeType::Primitive(PrimitiveType::Sketch),
1339                    RuntimeType::Primitive(PrimitiveType::Solid),
1340                    RuntimeType::Primitive(PrimitiveType::ImportedGeometry),
1341                ]),
1342            }
1343        }
1344    }
1345}
1346
1347#[cfg(test)]
1348mod tests {
1349    use super::*;
1350    use crate::exec::UnitType;
1351
1352    #[test]
1353    fn test_human_friendly_type() {
1354        let len = KclValue::Number {
1355            value: 1.0,
1356            ty: NumericType::Known(UnitType::GenericLength),
1357            meta: vec![],
1358        };
1359        assert_eq!(len.human_friendly_type(), "a number (Length)".to_string());
1360
1361        let unknown = KclValue::Number {
1362            value: 1.0,
1363            ty: NumericType::Unknown,
1364            meta: vec![],
1365        };
1366        assert_eq!(unknown.human_friendly_type(), "a number with unknown units".to_string());
1367
1368        let mm = KclValue::Number {
1369            value: 1.0,
1370            ty: NumericType::Known(UnitType::Length(UnitLength::Millimeters)),
1371            meta: vec![],
1372        };
1373        assert_eq!(mm.human_friendly_type(), "a number (mm)".to_string());
1374
1375        let array1_mm = KclValue::HomArray {
1376            value: vec![mm.clone()],
1377            ty: RuntimeType::any(),
1378        };
1379        assert_eq!(
1380            array1_mm.human_friendly_type(),
1381            "an array of `number(mm)` with 1 value".to_string()
1382        );
1383
1384        let array2_mm = KclValue::HomArray {
1385            value: vec![mm.clone(), mm.clone()],
1386            ty: RuntimeType::any(),
1387        };
1388        assert_eq!(
1389            array2_mm.human_friendly_type(),
1390            "an array of `number(mm)`, `number(mm)`".to_string()
1391        );
1392
1393        let array3_mm = KclValue::HomArray {
1394            value: vec![mm.clone(), mm.clone(), mm.clone()],
1395            ty: RuntimeType::any(),
1396        };
1397        assert_eq!(
1398            array3_mm.human_friendly_type(),
1399            "an array of `number(mm)`, `number(mm)`, `number(mm)`".to_string()
1400        );
1401
1402        let inches = KclValue::Number {
1403            value: 1.0,
1404            ty: NumericType::Known(UnitType::Length(UnitLength::Inches)),
1405            meta: vec![],
1406        };
1407        let array4 = KclValue::HomArray {
1408            value: vec![mm.clone(), mm.clone(), inches, mm],
1409            ty: RuntimeType::any(),
1410        };
1411        assert_eq!(
1412            array4.human_friendly_type(),
1413            "an array of `number(mm)`, `number(mm)`, `number(in)`, ... with 4 values".to_string()
1414        );
1415
1416        let empty_array = KclValue::HomArray {
1417            value: vec![],
1418            ty: RuntimeType::any(),
1419        };
1420        assert_eq!(empty_array.human_friendly_type(), "an empty array".to_string());
1421
1422        let array_nested = KclValue::HomArray {
1423            value: vec![array2_mm],
1424            ty: RuntimeType::any(),
1425        };
1426        assert_eq!(
1427            array_nested.human_friendly_type(),
1428            "an array of `[any; 2]` with 1 value".to_string()
1429        );
1430    }
1431
1432    fn color_def() -> Arc<EnumTypeDef> {
1433        Arc::new(
1434            EnumTypeDef::new(
1435                EnumTypeId::new(ModuleId::default(), "Color"),
1436                vec!["Red".to_owned(), "Green".to_owned()],
1437            )
1438            .unwrap(),
1439        )
1440    }
1441
1442    fn color_red() -> KclValue {
1443        KclValue::Enum {
1444            value: Box::new(EnumValue::new(color_def(), "Red", vec![])),
1445        }
1446    }
1447
1448    #[test]
1449    fn enum_values_describe_themselves_by_name_and_variant() {
1450        let red = color_red();
1451
1452        assert_eq!(red.human_friendly_type(), "a value of enum `Color`");
1453        // Feature-tree and variable display use the qualified form.
1454        assert_eq!(red.value_str(), Some("Color::Red".to_owned()));
1455        assert!(red.show_variable_in_feature_tree());
1456    }
1457
1458    /// The externally visible form of an enum value is its nominal identity,
1459    /// never a representation of the variant. Pinning both view types keeps a
1460    /// future `@repr` from leaking out of these surfaces by accident.
1461    #[test]
1462    fn enum_values_are_exposed_by_nominal_identity() {
1463        let view = crate::execution::KclValueView::from(color_red());
1464        assert_eq!(
1465            view,
1466            crate::execution::KclValueView::Enum {
1467                enum_name: "Color".to_owned(),
1468                variant: "Red".to_owned(),
1469            }
1470        );
1471
1472        let op = crate::execution::cad_op::op_from_kcl_value(&color_red());
1473        assert_eq!(
1474            op,
1475            kcl_api::OpKclValue::Enum {
1476                enum_name: "Color".to_owned(),
1477                variant: "Red".to_owned(),
1478            }
1479        );
1480    }
1481
1482    /// Serialization is the third such surface, and the one that reaches
1483    /// `program_memory.snap`. A value holds its whole declaration, so this pins
1484    /// that only the identity and the variant are written out: the declaration
1485    /// will carry `@repr` values, and those must not appear here.
1486    #[test]
1487    fn enum_values_serialize_as_identity_and_variant() {
1488        assert_eq!(
1489            serde_json::to_value(color_red()).unwrap(),
1490            serde_json::json!({
1491                "type": "Enum",
1492                "value": {
1493                    "enum_id": { "module_id": 0, "declared_name": "Color" },
1494                    "variant": "Red",
1495                },
1496            })
1497        );
1498    }
1499
1500    #[test]
1501    fn enum_declarations_carry_their_variants() {
1502        let def = EnumTypeDef::new(
1503            EnumTypeId::new(ModuleId::default(), "Color"),
1504            vec!["Red".to_owned(), "Green".to_owned()],
1505        )
1506        .unwrap();
1507
1508        assert_eq!(def.variants(), ["Red", "Green"]);
1509        assert!(def.has_variant("Red"));
1510        assert!(!def.has_variant("Blue"));
1511        // Identity is the declaration, not the variant set: an enum declaring
1512        // the same variants elsewhere is a different type.
1513        assert_ne!(
1514            def.id(),
1515            EnumTypeDef::new(
1516                EnumTypeId::new(ModuleId::from_usize(1), "Color"),
1517                vec!["Red".to_owned(), "Green".to_owned()],
1518            )
1519            .unwrap()
1520            .id()
1521        );
1522    }
1523
1524    #[test]
1525    fn enum_rejects_duplicate_variant() {
1526        let err = EnumTypeDef::new(
1527            EnumTypeId::new(ModuleId::default(), "Color"),
1528            vec!["Red".to_owned(), "Green".to_owned(), "Red".to_owned()],
1529        )
1530        .unwrap_err();
1531
1532        assert_eq!(
1533            err,
1534            DuplicateVariant {
1535                name: "Red".to_owned(),
1536                first_index: 0,
1537                duplicate_index: 2,
1538            }
1539        );
1540    }
1541
1542    #[test]
1543    fn enum_reports_earliest_duplicate() {
1544        // `Green` repeats at index 3 and `Red` at index 4. The caller reports one
1545        // duplicate, so it must be the one the user reads first.
1546        let err = EnumTypeDef::new(
1547            EnumTypeId::new(ModuleId::default(), "Color"),
1548            vec![
1549                "Red".to_owned(),
1550                "Green".to_owned(),
1551                "Blue".to_owned(),
1552                "Green".to_owned(),
1553                "Red".to_owned(),
1554            ],
1555        )
1556        .unwrap_err();
1557
1558        assert_eq!(err.name, "Green");
1559        assert_eq!(err.first_index, 1);
1560        assert_eq!(err.duplicate_index, 3);
1561    }
1562}