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