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