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