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