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#[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 HomArray {
134 value: Vec<KclValue>,
135 #[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 pub added_in: Option<VersionConstraint>,
219 pub deprecated: bool,
221 pub deprecated_since: Option<VersionConstraint>,
223 pub removed_in: Option<VersionConstraint>,
226 pub default_value: Option<DefaultParamVal>,
227 pub ty: Option<Type>,
228 pub resolved_ty: Option<RuntimeType>,
233}
234
235#[derive(Debug, Clone, Copy, PartialEq, Eq)]
241pub(crate) enum ParamUnavailable<'a> {
242 NotYetAdded(&'a VersionConstraint),
245 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
269pub 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 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 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 pub resolved_input_ty: Option<RuntimeType>,
310 pub named_args: IndexMap<String, NamedParam>,
311 pub return_type: Option<Node<Type>>,
312 pub resolved_return_ty: Option<RuntimeType>,
316 pub deprecated: bool,
317 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 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 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 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) = ¶m.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(¶m.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#[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 Enum(Arc<EnumTypeDef>),
500}
501
502impl TypeDef {
503 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#[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 pub fn declared_name(&self) -> &str {
543 &self.declared_name
544 }
545}
546
547#[derive(Debug, Clone, PartialEq)]
549pub struct EnumTypeDef {
550 id: EnumTypeId,
551 variants: Vec<String>,
552 experimental: bool,
554}
555
556#[derive(Debug, Clone, PartialEq)]
564pub struct DuplicateVariant {
565 pub name: String,
567 pub first_index: usize,
569 pub duplicate_index: usize,
571}
572
573impl EnumTypeDef {
574 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#[derive(Debug, Clone, Serialize)]
629pub struct EnumValue {
630 #[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
644impl 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 pub fn declared_string_repr(&self) -> String {
683 self.variant.clone()
684 }
685
686 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 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 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 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 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 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 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 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 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 UnsolvedExpr::Unknown(var_id) => crate::execution::KclValue::SketchVar {
1151 value: Box::new(SketchVar {
1152 id: var_id,
1153 initial_value: Default::default(),
1154 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 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 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 pub fn as_function(&self) -> Option<&FunctionSource> {
1365 match self {
1366 KclValue::Function { value, .. } => Some(value),
1367 _ => None,
1368 }
1369 }
1370
1371 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 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 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 KclValue::Number { value, .. } => Some(format!("{value}")),
1415 KclValue::String { value, .. } => Some(format!("'{value}'")),
1416 KclValue::Enum { value } => Some(value.qualified_name()),
1417 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 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 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 assert_eq!(red.value_str(), Some("Color::Red".to_owned()));
1626 assert!(red.show_variable_in_feature_tree());
1627 }
1628
1629 #[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 #[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 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 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}