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