1use std::collections::HashMap;
2use std::sync::Arc;
3
4use anyhow::Result;
5use indexmap::IndexMap;
6use kcl_api::UnitLength;
7use serde::Serialize;
8use serde::Serializer;
9
10use crate::CompilationIssue;
11use crate::KclError;
12use crate::ModuleId;
13use crate::SourceRange;
14use crate::errors::KclErrorDetails;
15use crate::execution::AbstractSegment;
16use crate::execution::BoundedEdge;
17use crate::execution::CameraView;
18use crate::execution::EnvironmentRef;
19use crate::execution::ExecState;
20use crate::execution::Face;
21use crate::execution::GdtAnnotation;
22use crate::execution::Geometry;
23use crate::execution::GeometryWithImportedGeometry;
24use crate::execution::Helix;
25use crate::execution::ImportedGeometry;
26use crate::execution::Metadata;
27use crate::execution::Plane;
28use crate::execution::Segment;
29use crate::execution::SegmentRepr;
30use crate::execution::Sketch;
31use crate::execution::SketchConstraint;
32use crate::execution::SketchVar;
33use crate::execution::SketchVarId;
34use crate::execution::Solid;
35use crate::execution::TagIdentifier;
36use crate::execution::UnsolvedExpr;
37use crate::execution::annotations::FnAttrs;
38use crate::execution::annotations::SETTINGS;
39use crate::execution::annotations::SETTINGS_UNIT_LENGTH;
40use crate::execution::annotations::VersionConstraint;
41use crate::execution::annotations::{self};
42use crate::execution::types::NumericType;
43use crate::execution::types::NumericTypeExt;
44use crate::execution::types::PrimitiveType;
45use crate::execution::types::RuntimeType;
46use crate::parsing::ast::types::DefaultParamVal;
47use crate::parsing::ast::types::FunctionExpression;
48use crate::parsing::ast::types::KclNone;
49use crate::parsing::ast::types::Literal;
50use crate::parsing::ast::types::LiteralValue;
51use crate::parsing::ast::types::Node;
52use crate::parsing::ast::types::NumericLiteral;
53use crate::parsing::ast::types::TagDeclarator;
54use crate::parsing::ast::types::TagNode;
55use crate::parsing::ast::types::Type;
56use crate::std::StdFnProps;
57use crate::std::args::TyF64;
58
59pub type KclObjectFields = HashMap<String, KclValue>;
60
61#[derive(Debug, Clone, Default, PartialEq, Serialize)]
62pub enum KclObjectKind {
63 #[default]
64 Default,
65 SketchTags {
66 #[serde(default, skip_serializing_if = "Vec::is_empty")]
67 deprecated_solid_tag_names: Vec<String>,
68 },
69}
70
71impl KclObjectKind {
72 pub(crate) fn is_default(&self) -> bool {
73 match self {
74 KclObjectKind::Default => true,
75 KclObjectKind::SketchTags { .. } => false,
76 }
77 }
78
79 pub(crate) fn deprecated_solid_tag_names(&self) -> &[String] {
80 match self {
81 Self::Default => &[],
82 Self::SketchTags {
83 deprecated_solid_tag_names,
84 } => deprecated_solid_tag_names,
85 }
86 }
87}
88
89#[derive(Debug, Clone, Serialize, PartialEq)]
91#[serde(tag = "type")]
92pub enum KclValue {
93 Uuid {
94 value: ::uuid::Uuid,
95 #[serde(skip)]
96 meta: Vec<Metadata>,
97 },
98 Bool {
99 value: bool,
100 #[serde(skip)]
101 meta: Vec<Metadata>,
102 },
103 Number {
104 value: f64,
105 ty: NumericType,
106 #[serde(skip)]
107 meta: Vec<Metadata>,
108 },
109 String {
110 value: String,
111 #[serde(skip)]
112 meta: Vec<Metadata>,
113 },
114 Enum {
115 value: Box<EnumValue>,
116 },
117 SketchVar {
118 value: Box<SketchVar>,
119 },
120 SketchConstraint {
121 value: Box<SketchConstraint>,
122 },
123 Tuple {
124 value: Vec<KclValue>,
125 #[serde(skip)]
126 meta: Vec<Metadata>,
127 },
128 HomArray {
130 value: Vec<KclValue>,
131 #[serde(skip)]
133 ty: RuntimeType,
134 },
135 Object {
136 value: KclObjectFields,
137 constrainable: bool,
138 #[serde(default, skip_serializing_if = "KclObjectKind::is_default")]
139 object_kind: KclObjectKind,
140 #[serde(skip)]
141 meta: Vec<Metadata>,
142 },
143 TagIdentifier(Box<TagIdentifier>),
144 TagDeclarator(crate::parsing::ast::types::BoxNode<TagDeclarator>),
145 GdtAnnotation {
146 value: Box<GdtAnnotation>,
147 },
148 Plane {
149 value: Box<Plane>,
150 },
151 Face {
152 value: Box<Face>,
153 },
154 BoundedEdge {
155 value: BoundedEdge,
156 meta: Vec<Metadata>,
157 },
158 Segment {
159 value: Box<AbstractSegment>,
160 },
161 Sketch {
162 value: Box<Sketch>,
163 },
164 Solid {
165 value: Box<Solid>,
166 },
167 Helix {
168 value: Box<Helix>,
169 },
170 CameraView {
171 value: Box<CameraView>,
172 },
173 ImportedGeometry(ImportedGeometry),
174 Function {
175 #[serde(serialize_with = "function_value_stub")]
176 value: Box<FunctionSource>,
177 #[serde(skip)]
178 meta: Vec<Metadata>,
179 },
180 Module {
181 value: ModuleId,
182 #[serde(skip)]
183 meta: Vec<Metadata>,
184 },
185 Type {
186 #[serde(skip)]
187 value: TypeDef,
188 experimental: bool,
189 #[serde(skip)]
190 meta: Vec<Metadata>,
191 },
192 KclNone {
193 value: KclNone,
194 #[serde(skip)]
195 meta: Vec<Metadata>,
196 },
197}
198
199fn function_value_stub<S>(_value: &FunctionSource, serializer: S) -> Result<S::Ok, S::Error>
200where
201 S: serde::Serializer,
202{
203 serializer.serialize_unit()
204}
205
206#[derive(Debug, Clone, PartialEq)]
207pub struct NamedParam {
208 pub experimental: bool,
209 pub deprecated: bool,
211 pub deprecated_since: Option<VersionConstraint>,
213 pub default_value: Option<DefaultParamVal>,
214 pub ty: Option<Type>,
215 pub resolved_ty: Option<RuntimeType>,
220}
221
222#[derive(Debug, Clone, PartialEq)]
223pub struct FunctionSource {
224 pub input_arg: Option<(String, Option<Type>)>,
225 pub resolved_input_ty: Option<RuntimeType>,
230 pub named_args: IndexMap<String, NamedParam>,
231 pub return_type: Option<Node<Type>>,
232 pub resolved_return_ty: Option<RuntimeType>,
236 pub deprecated: bool,
237 pub deprecated_since: Option<VersionConstraint>,
241 pub experimental: bool,
242 pub include_in_feature_tree: bool,
243 pub std_props: Option<StdFnProps>,
244 pub body: FunctionBody,
245 pub ast: crate::parsing::ast::types::BoxNode<FunctionExpression>,
246}
247
248pub struct KclFunctionSourceParams {
249 pub std_props: Option<StdFnProps>,
250 pub experimental: bool,
251 pub include_in_feature_tree: bool,
252}
253
254impl FunctionSource {
255 pub fn rust(
256 func: crate::std::StdFn,
257 ast: Box<Node<FunctionExpression>>,
258 props: StdFnProps,
259 attrs: FnAttrs,
260 ) -> Self {
261 let (input_arg, named_args) = Self::args_from_ast(&ast);
262
263 FunctionSource {
264 input_arg,
265 resolved_input_ty: None,
266 named_args,
267 return_type: ast.return_type.clone(),
268 resolved_return_ty: None,
269 deprecated: attrs.deprecated,
270 deprecated_since: attrs.deprecated_since,
271 experimental: attrs.experimental,
272 include_in_feature_tree: attrs.include_in_feature_tree,
273 std_props: Some(props),
274 body: FunctionBody::Rust(func),
275 ast,
276 }
277 }
278
279 pub fn kcl(ast: Box<Node<FunctionExpression>>, memory: EnvironmentRef, params: KclFunctionSourceParams) -> Self {
280 let KclFunctionSourceParams {
281 std_props,
282 experimental,
283 include_in_feature_tree,
284 } = params;
285 let (input_arg, named_args) = Self::args_from_ast(&ast);
286 FunctionSource {
287 input_arg,
288 resolved_input_ty: None,
289 named_args,
290 return_type: ast.return_type.clone(),
291 resolved_return_ty: None,
292 deprecated: false,
293 deprecated_since: None,
294 experimental,
295 include_in_feature_tree,
296 std_props,
297 body: FunctionBody::Kcl(memory),
298 ast,
299 }
300 }
301
302 #[expect(clippy::type_complexity)]
303 fn args_from_ast(ast: &FunctionExpression) -> (Option<(String, Option<Type>)>, IndexMap<String, NamedParam>) {
304 let mut input_arg = None;
305 let mut named_args = IndexMap::new();
306 for p in &ast.params {
307 if !p.labeled {
308 input_arg = Some((
309 p.identifier.name.clone(),
310 p.param_type.as_ref().map(|t| t.inner.clone()),
311 ));
312 continue;
313 }
314
315 named_args.insert(
316 p.identifier.name.clone(),
317 NamedParam {
318 experimental: p.experimental,
319 deprecated: p.deprecated,
320 deprecated_since: p.deprecated_since.clone(),
321 default_value: p.default_value.clone(),
322 ty: p.param_type.as_ref().map(|t| t.inner.clone()),
323 resolved_ty: None,
324 },
325 );
326 }
327
328 (input_arg, named_args)
329 }
330
331 pub(crate) fn is_std(&self) -> bool {
332 self.std_props.is_some()
333 }
334
335 pub(crate) fn resolve_signature_types(&mut self, exec_state: &mut ExecState) -> Result<(), KclError> {
346 for param in &self.ast.params {
347 let Some(ty) = ¶m.param_type else {
348 continue;
349 };
350 let resolved = RuntimeType::from_parsed(ty.inner.clone(), exec_state, ty.as_source_range(), false, false)
351 .map_err(|e| KclError::new_semantic(e.into()))?;
352 if param.labeled {
353 if let Some(named) = self.named_args.get_mut(¶m.identifier.name) {
354 named.resolved_ty = Some(resolved);
355 }
356 } else {
357 self.resolved_input_ty = Some(resolved);
358 }
359 }
360
361 if let Some(ret_ty) = &self.return_type {
362 self.resolved_return_ty = Some(
363 RuntimeType::from_parsed(ret_ty.inner.clone(), exec_state, ret_ty.as_source_range(), false, false)
364 .map_err(|e| KclError::new_semantic(e.into()))?,
365 );
366 }
367
368 Ok(())
369 }
370}
371
372#[derive(Debug, Clone, PartialEq)]
373#[allow(unpredictable_function_pointer_comparisons)]
376pub enum FunctionBody {
377 Rust(crate::std::StdFn),
378 Kcl(EnvironmentRef),
379}
380
381#[derive(Debug, Clone, PartialEq)]
382pub enum TypeDef {
383 RustRepr(PrimitiveType, StdFnProps),
384 Alias(RuntimeType),
385 Enum(Arc<EnumTypeDef>),
389}
390
391#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize)]
399pub struct EnumTypeId {
400 module_id: ModuleId,
401 declared_name: String,
402}
403
404impl EnumTypeId {
405 pub fn new(module_id: ModuleId, declared_name: impl Into<String>) -> Self {
406 Self {
407 module_id,
408 declared_name: declared_name.into(),
409 }
410 }
411
412 pub fn module_id(&self) -> ModuleId {
413 self.module_id
414 }
415
416 pub fn declared_name(&self) -> &str {
419 &self.declared_name
420 }
421}
422
423#[derive(Debug, Clone, PartialEq)]
425pub struct EnumTypeDef {
426 id: EnumTypeId,
427 variants: Vec<String>,
428}
429
430#[derive(Debug, Clone, PartialEq)]
438pub struct DuplicateVariant {
439 pub name: String,
441 pub first_index: usize,
443 pub duplicate_index: usize,
445}
446
447impl EnumTypeDef {
448 pub fn new(id: EnumTypeId, variants: Vec<String>) -> Result<Self, DuplicateVariant> {
455 for (duplicate_index, variant) in variants.iter().enumerate() {
456 if let Some(first_index) = variants[..duplicate_index].iter().position(|v| v == variant) {
457 return Err(DuplicateVariant {
458 name: variant.clone(),
459 first_index,
460 duplicate_index,
461 });
462 }
463 }
464
465 Ok(Self { id, variants })
466 }
467
468 pub fn id(&self) -> &EnumTypeId {
469 &self.id
470 }
471
472 pub fn variants(&self) -> &[String] {
473 &self.variants
474 }
475
476 pub fn has_variant(&self, name: &str) -> bool {
477 self.variants.iter().any(|v| v == name)
478 }
479}
480
481#[derive(Debug, Clone, Serialize)]
492pub struct EnumValue {
493 #[serde(rename = "enum_id", serialize_with = "serialize_enum_def_id")]
497 def: Arc<EnumTypeDef>,
498 variant: String,
499 #[serde(skip)]
500 meta: Vec<Metadata>,
501}
502
503fn serialize_enum_def_id<S: Serializer>(def: &Arc<EnumTypeDef>, serializer: S) -> Result<S::Ok, S::Error> {
504 def.id().serialize(serializer)
505}
506
507impl PartialEq for EnumValue {
512 fn eq(&self, other: &Self) -> bool {
513 self.def.id() == other.def.id() && self.variant == other.variant
514 }
515}
516
517impl EnumValue {
518 pub fn new(def: Arc<EnumTypeDef>, variant: impl Into<String>, meta: Vec<Metadata>) -> Self {
519 Self {
520 def,
521 variant: variant.into(),
522 meta,
523 }
524 }
525
526 pub fn enum_id(&self) -> &EnumTypeId {
527 self.def.id()
528 }
529
530 pub fn variant(&self) -> &str {
531 &self.variant
532 }
533
534 pub fn meta(&self) -> &[Metadata] {
535 &self.meta
536 }
537
538 pub fn declared_string_repr(&self) -> String {
546 self.variant.clone()
547 }
548
549 pub fn qualified_name(&self) -> String {
551 format!("{}::{}", self.def.id().declared_name(), self.variant)
552 }
553}
554
555impl From<Vec<GdtAnnotation>> for KclValue {
556 fn from(mut values: Vec<GdtAnnotation>) -> Self {
557 if values.len() == 1 {
558 let value = values.pop().expect("Just checked len == 1");
559 KclValue::GdtAnnotation { value: Box::new(value) }
560 } else {
561 KclValue::HomArray {
562 value: values
563 .into_iter()
564 .map(|s| KclValue::GdtAnnotation { value: Box::new(s) })
565 .collect(),
566 ty: RuntimeType::Primitive(PrimitiveType::GdtAnnotation),
567 }
568 }
569 }
570}
571
572impl From<Vec<Sketch>> for KclValue {
573 fn from(mut eg: Vec<Sketch>) -> Self {
574 if eg.len() == 1
575 && let Some(s) = eg.pop()
576 {
577 KclValue::Sketch { value: Box::new(s) }
578 } else {
579 KclValue::HomArray {
580 value: eg
581 .into_iter()
582 .map(|s| KclValue::Sketch { value: Box::new(s) })
583 .collect(),
584 ty: RuntimeType::Primitive(PrimitiveType::Sketch),
585 }
586 }
587 }
588}
589
590impl From<Vec<Solid>> for KclValue {
591 fn from(mut eg: Vec<Solid>) -> Self {
592 if eg.len() == 1
593 && let Some(s) = eg.pop()
594 {
595 KclValue::Solid { value: Box::new(s) }
596 } else {
597 KclValue::HomArray {
598 value: eg.into_iter().map(|s| KclValue::Solid { value: Box::new(s) }).collect(),
599 ty: RuntimeType::Primitive(PrimitiveType::Solid),
600 }
601 }
602 }
603}
604
605impl From<KclValue> for Vec<SourceRange> {
606 fn from(item: KclValue) -> Self {
607 match item {
608 KclValue::TagDeclarator(t) => vec![SourceRange::new(t.start, t.end, t.module_id)],
609 KclValue::TagIdentifier(t) => to_vec_sr(&t.meta),
610 KclValue::GdtAnnotation { value } => to_vec_sr(&value.meta),
611 KclValue::Solid { value } => to_vec_sr(&value.meta),
612 KclValue::Sketch { value } => to_vec_sr(&value.meta),
613 KclValue::Helix { value } => to_vec_sr(&value.meta),
614 KclValue::CameraView { value } => to_vec_sr(value.meta()),
615 KclValue::ImportedGeometry(i) => to_vec_sr(&i.meta),
616 KclValue::Function { meta, .. } => to_vec_sr(&meta),
617 KclValue::Plane { value } => to_vec_sr(&value.meta),
618 KclValue::Face { value } => to_vec_sr(&value.meta),
619 KclValue::Segment { value } => to_vec_sr(&value.meta),
620 KclValue::Bool { meta, .. } => to_vec_sr(&meta),
621 KclValue::Number { meta, .. } => to_vec_sr(&meta),
622 KclValue::String { meta, .. } => to_vec_sr(&meta),
623 KclValue::Enum { value } => to_vec_sr(value.meta()),
624 KclValue::SketchVar { value, .. } => to_vec_sr(&value.meta),
625 KclValue::SketchConstraint { value, .. } => to_vec_sr(&value.meta),
626 KclValue::Tuple { meta, .. } => to_vec_sr(&meta),
627 KclValue::HomArray { value, .. } => value.iter().flat_map(Into::<Vec<SourceRange>>::into).collect(),
628 KclValue::Object { meta, .. } => to_vec_sr(&meta),
629 KclValue::Module { meta, .. } => to_vec_sr(&meta),
630 KclValue::Uuid { meta, .. } => to_vec_sr(&meta),
631 KclValue::Type { meta, .. } => to_vec_sr(&meta),
632 KclValue::KclNone { meta, .. } => to_vec_sr(&meta),
633 KclValue::BoundedEdge { meta, .. } => to_vec_sr(&meta),
634 }
635 }
636}
637
638fn to_vec_sr(meta: &[Metadata]) -> Vec<SourceRange> {
639 meta.iter().map(|m| m.source_range).collect()
640}
641
642impl From<&KclValue> for Vec<SourceRange> {
643 fn from(item: &KclValue) -> Self {
644 match item {
645 KclValue::TagDeclarator(t) => vec![SourceRange::new(t.start, t.end, t.module_id)],
646 KclValue::TagIdentifier(t) => to_vec_sr(&t.meta),
647 KclValue::GdtAnnotation { value } => to_vec_sr(&value.meta),
648 KclValue::Solid { value } => to_vec_sr(&value.meta),
649 KclValue::Sketch { value } => to_vec_sr(&value.meta),
650 KclValue::Helix { value } => to_vec_sr(&value.meta),
651 KclValue::CameraView { value } => to_vec_sr(value.meta()),
652 KclValue::ImportedGeometry(i) => to_vec_sr(&i.meta),
653 KclValue::Function { meta, .. } => to_vec_sr(meta),
654 KclValue::Plane { value } => to_vec_sr(&value.meta),
655 KclValue::Face { value } => to_vec_sr(&value.meta),
656 KclValue::Segment { value } => to_vec_sr(&value.meta),
657 KclValue::Bool { meta, .. } => to_vec_sr(meta),
658 KclValue::Number { meta, .. } => to_vec_sr(meta),
659 KclValue::String { meta, .. } => to_vec_sr(meta),
660 KclValue::Enum { value } => to_vec_sr(value.meta()),
661 KclValue::SketchVar { value, .. } => to_vec_sr(&value.meta),
662 KclValue::SketchConstraint { value, .. } => to_vec_sr(&value.meta),
663 KclValue::Uuid { meta, .. } => to_vec_sr(meta),
664 KclValue::Tuple { meta, .. } => to_vec_sr(meta),
665 KclValue::HomArray { value, .. } => value.iter().flat_map(Into::<Vec<SourceRange>>::into).collect(),
666 KclValue::Object { meta, .. } => to_vec_sr(meta),
667 KclValue::Module { meta, .. } => to_vec_sr(meta),
668 KclValue::KclNone { meta, .. } => to_vec_sr(meta),
669 KclValue::Type { meta, .. } => to_vec_sr(meta),
670 KclValue::BoundedEdge { meta, .. } => to_vec_sr(meta),
671 }
672 }
673}
674
675impl From<&KclValue> for SourceRange {
676 fn from(item: &KclValue) -> Self {
677 let v: Vec<_> = item.into();
678 v.into_iter().next().unwrap_or_default()
679 }
680}
681
682impl KclValue {
683 pub(crate) fn metadata(&self) -> Vec<Metadata> {
684 match self {
685 KclValue::Uuid { value: _, meta } => meta.clone(),
686 KclValue::Bool { value: _, meta } => meta.clone(),
687 KclValue::Number { meta, .. } => meta.clone(),
688 KclValue::String { value: _, meta } => meta.clone(),
689 KclValue::Enum { value } => value.meta().to_vec(),
690 KclValue::SketchVar { value, .. } => value.meta.clone(),
691 KclValue::SketchConstraint { value, .. } => value.meta.clone(),
692 KclValue::Tuple { value: _, meta } => meta.clone(),
693 KclValue::HomArray { value, .. } => value.iter().flat_map(|v| v.metadata()).collect(),
694 KclValue::Object { meta, .. } => meta.clone(),
695 KclValue::TagIdentifier(x) => x.meta.clone(),
696 KclValue::TagDeclarator(x) => vec![x.metadata()],
697 KclValue::GdtAnnotation { value } => value.meta.clone(),
698 KclValue::Plane { value } => value.meta.clone(),
699 KclValue::Face { value } => value.meta.clone(),
700 KclValue::Segment { value } => value.meta.clone(),
701 KclValue::Sketch { value } => value.meta.clone(),
702 KclValue::Solid { value } => value.meta.clone(),
703 KclValue::Helix { value } => value.meta.clone(),
704 KclValue::CameraView { value } => value.meta().to_vec(),
705 KclValue::ImportedGeometry(x) => x.meta.clone(),
706 KclValue::Function { meta, .. } => meta.clone(),
707 KclValue::Module { meta, .. } => meta.clone(),
708 KclValue::KclNone { meta, .. } => meta.clone(),
709 KclValue::Type { meta, .. } => meta.clone(),
710 KclValue::BoundedEdge { meta, .. } => meta.clone(),
711 }
712 }
713
714 #[allow(unused)]
715 pub(crate) fn none() -> Self {
716 Self::KclNone {
717 value: Default::default(),
718 meta: Default::default(),
719 }
720 }
721
722 pub(crate) fn show_variable_in_feature_tree(&self) -> bool {
726 match self {
727 KclValue::Uuid { .. } => false,
728 KclValue::Bool { .. } | KclValue::Number { .. } | KclValue::String { .. } | KclValue::Enum { .. } => true,
729 KclValue::SketchVar { .. }
730 | KclValue::SketchConstraint { .. }
731 | KclValue::Tuple { .. }
732 | KclValue::HomArray { .. }
733 | KclValue::Object { .. }
734 | KclValue::TagIdentifier(_)
735 | KclValue::TagDeclarator(_)
736 | KclValue::GdtAnnotation { .. }
737 | KclValue::Plane { .. }
738 | KclValue::Face { .. }
739 | KclValue::Segment { .. }
740 | KclValue::Sketch { .. }
741 | KclValue::Solid { .. }
742 | KclValue::Helix { .. }
743 | KclValue::CameraView { .. }
744 | KclValue::ImportedGeometry(_)
745 | KclValue::Function { .. }
746 | KclValue::Module { .. }
747 | KclValue::Type { .. }
748 | KclValue::BoundedEdge { .. }
749 | KclValue::KclNone { .. } => false,
750 }
751 }
752
753 pub(crate) fn human_friendly_type(&self) -> String {
756 match self {
757 KclValue::Uuid { .. } => "a unique ID (uuid)".to_owned(),
758 KclValue::TagDeclarator(_) => "a tag declarator".to_owned(),
759 KclValue::TagIdentifier(_) => "a tag identifier".to_owned(),
760 KclValue::GdtAnnotation { .. } => "an annotation".to_owned(),
761 KclValue::Solid { .. } => "a solid".to_owned(),
762 KclValue::Sketch { .. } => "a sketch".to_owned(),
763 KclValue::Helix { .. } => "a helix".to_owned(),
764 KclValue::CameraView { .. } => "a camera view".to_owned(),
765 KclValue::ImportedGeometry(_) => "an imported geometry".to_owned(),
766 KclValue::Function { .. } => "a function".to_owned(),
767 KclValue::Plane { .. } => "a plane".to_owned(),
768 KclValue::Face { .. } => "a face".to_owned(),
769 KclValue::Segment { .. } => "a segment".to_owned(),
770 KclValue::Bool { .. } => "a boolean (`true` or `false`)".to_owned(),
771 KclValue::Number {
772 ty: NumericType::Unknown,
773 ..
774 } => "a number with unknown units".to_owned(),
775 KclValue::Number {
776 ty: NumericType::Known(units),
777 ..
778 } => format!("a number ({units})"),
779 KclValue::Number { .. } => "a number".to_owned(),
780 KclValue::String { .. } => "a string".to_owned(),
781 KclValue::Enum { value } => format!("a value of enum `{}`", value.enum_id().declared_name()),
782 KclValue::SketchVar { .. } => "a sketch variable".to_owned(),
783 KclValue::SketchConstraint { .. } => "a sketch constraint".to_owned(),
784 KclValue::Object { .. } => "an object".to_owned(),
785 KclValue::Module { .. } => "a module".to_owned(),
786 KclValue::Type { .. } => "a type".to_owned(),
787 KclValue::KclNone { .. } => "none".to_owned(),
788 KclValue::BoundedEdge { .. } => "a bounded edge".to_owned(),
789 KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => {
790 if value.is_empty() {
791 "an empty array".to_owned()
792 } else {
793 const MAX: usize = 3;
795
796 let len = value.len();
797 let element_tys = value
798 .iter()
799 .take(MAX)
800 .map(|elem| elem.principal_type_string())
801 .collect::<Vec<_>>()
802 .join(", ");
803 let mut result = format!("an array of {element_tys}");
804 if len > MAX {
805 result.push_str(&format!(", ... with {len} values"));
806 }
807 if len == 1 {
808 result.push_str(" with 1 value");
809 }
810 result
811 }
812 }
813 }
814 }
815
816 pub(crate) fn from_sketch_var_literal(
817 literal: &Node<NumericLiteral>,
818 id: SketchVarId,
819 node_path: Option<crate::NodePath>,
820 exec_state: &ExecState,
821 ) -> Self {
822 let meta = vec![literal.metadata()];
823 let ty = NumericType::from_parsed(literal.suffix, &exec_state.mod_local.settings);
824 KclValue::SketchVar {
825 value: Box::new(SketchVar {
826 id,
827 initial_value: literal.value,
828 node_path,
829 meta,
830 ty,
831 }),
832 }
833 }
834
835 pub(crate) fn from_literal(literal: Node<Literal>, exec_state: &mut ExecState) -> Self {
836 let meta = vec![literal.metadata()];
837 match literal.inner.value {
838 LiteralValue::Number { value, suffix } => {
839 let ty = NumericType::from_parsed(suffix, &exec_state.mod_local.settings);
840 if let NumericType::Default { len, .. } = &ty
841 && !exec_state.mod_local.explicit_length_units
842 && *len != UnitLength::Millimeters
843 {
844 exec_state.warn(
845 CompilationIssue::err(
846 literal.as_source_range(),
847 "Project-wide units are deprecated. Prefer to use per-file default units.",
848 )
849 .with_suggestion(
850 "Fix by adding per-file settings",
851 format!("@{SETTINGS}({SETTINGS_UNIT_LENGTH} = {len})\n"),
852 Some(SourceRange::new(0, 0, literal.module_id)),
854 crate::errors::Tag::Deprecated,
855 ),
856 annotations::WARN_DEPRECATED,
857 );
858 }
859 KclValue::Number { value, meta, ty }
860 }
861 LiteralValue::String(value) => KclValue::String { value, meta },
862 LiteralValue::Bool(value) => KclValue::Bool { value, meta },
863 }
864 }
865
866 pub(crate) fn from_default_param(param: DefaultParamVal, exec_state: &mut ExecState) -> Self {
867 match param {
868 DefaultParamVal::Literal(lit) => Self::from_literal(lit, exec_state),
869 DefaultParamVal::KclNone(value) => KclValue::KclNone {
870 value,
871 meta: Default::default(),
872 },
873 }
874 }
875
876 pub(crate) fn map_env_ref(&self, old_env: EnvironmentRef, new_env: EnvironmentRef) -> Self {
877 let mut result = self.clone();
878 if let KclValue::Function { ref mut value, .. } = result
879 && let FunctionSource {
880 body: FunctionBody::Kcl(memory),
881 ..
882 } = &mut **value
883 {
884 memory.replace_env(old_env, new_env);
885 }
886
887 result
888 }
889
890 pub(crate) fn map_env_ref_and_epoch(&self, old_env: EnvironmentRef, new_env: EnvironmentRef) -> Self {
891 let mut result = self.clone();
892 if let KclValue::Function { ref mut value, .. } = result
893 && let FunctionSource {
894 body: FunctionBody::Kcl(memory),
895 ..
896 } = &mut **value
897 {
898 memory.replace_env_and_epoch(old_env, new_env);
899 }
900
901 result
902 }
903
904 pub const fn from_number_with_type(f: f64, ty: NumericType, meta: Vec<Metadata>) -> Self {
905 Self::Number { value: f, meta, ty }
906 }
907
908 pub fn from_point2d(p: [f64; 2], ty: NumericType, meta: Vec<Metadata>) -> Self {
910 let [x, y] = p;
911 Self::Tuple {
912 value: vec![
913 Self::Number {
914 value: x,
915 meta: meta.clone(),
916 ty,
917 },
918 Self::Number {
919 value: y,
920 meta: meta.clone(),
921 ty,
922 },
923 ],
924 meta,
925 }
926 }
927
928 pub fn from_point3d(p: [f64; 3], ty: NumericType, meta: Vec<Metadata>) -> Self {
930 let [x, y, z] = p;
931 Self::Tuple {
932 value: vec![
933 Self::Number {
934 value: x,
935 meta: meta.clone(),
936 ty,
937 },
938 Self::Number {
939 value: y,
940 meta: meta.clone(),
941 ty,
942 },
943 Self::Number {
944 value: z,
945 meta: meta.clone(),
946 ty,
947 },
948 ],
949 meta,
950 }
951 }
952
953 pub(crate) fn array_from_point2d(p: [f64; 2], ty: NumericType, meta: Vec<Metadata>) -> Self {
955 let [x, y] = p;
956 Self::HomArray {
957 value: vec![
958 Self::Number {
959 value: x,
960 meta: meta.clone(),
961 ty,
962 },
963 Self::Number { value: y, meta, ty },
964 ],
965 ty: ty.into(),
966 }
967 }
968
969 pub fn array_from_point3d(p: [f64; 3], ty: NumericType, meta: Vec<Metadata>) -> Self {
971 let [x, y, z] = p;
972 Self::HomArray {
973 value: vec![
974 Self::Number {
975 value: x,
976 meta: meta.clone(),
977 ty,
978 },
979 Self::Number {
980 value: y,
981 meta: meta.clone(),
982 ty,
983 },
984 Self::Number { value: z, meta, ty },
985 ],
986 ty: ty.into(),
987 }
988 }
989
990 pub(crate) fn from_unsolved_expr(expr: UnsolvedExpr, meta: Vec<Metadata>) -> Self {
991 match expr {
992 UnsolvedExpr::Known(v) => crate::execution::KclValue::Number {
993 value: v.n,
994 ty: v.ty,
995 meta,
996 },
997 UnsolvedExpr::Unknown(var_id) => crate::execution::KclValue::SketchVar {
1001 value: Box::new(SketchVar {
1002 id: var_id,
1003 initial_value: Default::default(),
1004 ty: Default::default(),
1006 node_path: None,
1007 meta,
1008 }),
1009 },
1010 }
1011 }
1012
1013 pub(crate) fn as_usize(&self) -> Option<usize> {
1014 match self {
1015 KclValue::Number { value, .. } => crate::try_f64_to_usize(*value),
1016 _ => None,
1017 }
1018 }
1019
1020 pub fn as_int(&self) -> Option<i64> {
1021 match self {
1022 KclValue::Number { value, .. } => crate::try_f64_to_i64(*value),
1023 _ => None,
1024 }
1025 }
1026
1027 pub fn as_int_with_ty(&self) -> Option<(i64, NumericType)> {
1028 match self {
1029 KclValue::Number { value, ty, .. } => crate::try_f64_to_i64(*value).map(|i| (i, *ty)),
1030 _ => None,
1031 }
1032 }
1033
1034 pub fn as_object(&self) -> Option<&KclObjectFields> {
1035 match self {
1036 KclValue::Object { value, .. } => Some(value),
1037 _ => None,
1038 }
1039 }
1040
1041 pub fn into_object(self) -> Option<KclObjectFields> {
1042 match self {
1043 KclValue::Object { value, .. } => Some(value),
1044 _ => None,
1045 }
1046 }
1047
1048 pub fn as_unsolved_expr(&self) -> Option<UnsolvedExpr> {
1049 match self {
1050 KclValue::Number { value, ty, .. } => Some(UnsolvedExpr::Known(TyF64::new(*value, *ty))),
1051 KclValue::SketchVar { value, .. } => Some(UnsolvedExpr::Unknown(value.id)),
1052 _ => None,
1053 }
1054 }
1055
1056 pub fn to_sketch_expr(&self) -> Option<crate::front::Expr> {
1057 match self {
1058 KclValue::Number { value, ty, .. } => Some(crate::front::Expr::Number(crate::front::Number {
1059 value: *value,
1060 units: (*ty).try_into().ok()?,
1061 })),
1062 KclValue::SketchVar { value, .. } => Some(crate::front::Expr::Var(crate::front::Number {
1063 value: value.initial_value,
1064 units: value.ty.try_into().ok()?,
1065 })),
1066 _ => None,
1067 }
1068 }
1069
1070 pub fn as_str(&self) -> Option<&str> {
1071 match self {
1072 KclValue::String { value, .. } => Some(value),
1073 _ => None,
1074 }
1075 }
1076
1077 pub fn into_array(self) -> Vec<KclValue> {
1078 match self {
1079 KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => value,
1080 _ => vec![self],
1081 }
1082 }
1083
1084 pub fn as_slice(&self) -> Option<&[KclValue]> {
1085 match self {
1086 KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => Some(value),
1087 _ => None,
1088 }
1089 }
1090
1091 pub fn as_point2d(&self) -> Option<[TyF64; 2]> {
1092 let value = match self {
1093 KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => value,
1094 _ => return None,
1095 };
1096
1097 let [x, y] = value.as_slice() else {
1098 return None;
1099 };
1100 let x = x.as_ty_f64()?;
1101 let y = y.as_ty_f64()?;
1102 Some([x, y])
1103 }
1104
1105 pub fn as_point3d(&self) -> Option<[TyF64; 3]> {
1106 let value = match self {
1107 KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => value,
1108 _ => return None,
1109 };
1110
1111 let [x, y, z] = value.as_slice() else {
1112 return None;
1113 };
1114 let x = x.as_ty_f64()?;
1115 let y = y.as_ty_f64()?;
1116 let z = z.as_ty_f64()?;
1117 Some([x, y, z])
1118 }
1119
1120 pub fn as_uuid(&self) -> Option<uuid::Uuid> {
1121 match self {
1122 KclValue::Uuid { value, .. } => Some(*value),
1123 _ => None,
1124 }
1125 }
1126
1127 pub fn as_plane(&self) -> Option<&Plane> {
1128 match self {
1129 KclValue::Plane { value, .. } => Some(value),
1130 _ => None,
1131 }
1132 }
1133
1134 pub fn as_solid(&self) -> Option<&Solid> {
1135 match self {
1136 KclValue::Solid { value, .. } => Some(value),
1137 _ => None,
1138 }
1139 }
1140
1141 pub fn as_sketch(&self) -> Option<&Sketch> {
1142 match self {
1143 KclValue::Sketch { value, .. } => Some(value),
1144 _ => None,
1145 }
1146 }
1147
1148 pub fn as_mut_sketch(&mut self) -> Option<&mut Sketch> {
1149 match self {
1150 KclValue::Sketch { value } => Some(value),
1151 _ => None,
1152 }
1153 }
1154
1155 pub fn as_sketch_var(&self) -> Option<&SketchVar> {
1156 match self {
1157 KclValue::SketchVar { value, .. } => Some(value),
1158 _ => None,
1159 }
1160 }
1161
1162 pub fn as_segment(&self) -> Option<&Segment> {
1164 match self {
1165 KclValue::Segment { value, .. } => match &value.repr {
1166 SegmentRepr::Solved { segment } => Some(segment),
1167 _ => None,
1168 },
1169 _ => None,
1170 }
1171 }
1172
1173 pub fn into_segment(self) -> Option<Segment> {
1175 match self {
1176 KclValue::Segment { value, .. } => match value.repr {
1177 SegmentRepr::Solved { segment } => Some(*segment),
1178 _ => None,
1179 },
1180 _ => None,
1181 }
1182 }
1183
1184 pub fn as_mut_tag(&mut self) -> Option<&mut TagIdentifier> {
1185 match self {
1186 KclValue::TagIdentifier(value) => Some(value),
1187 _ => None,
1188 }
1189 }
1190
1191 #[cfg(test)]
1192 pub fn as_f64(&self) -> Option<f64> {
1193 match self {
1194 KclValue::Number { value, .. } => Some(*value),
1195 _ => None,
1196 }
1197 }
1198
1199 pub fn as_ty_f64(&self) -> Option<TyF64> {
1200 match self {
1201 KclValue::Number { value, ty, .. } => Some(TyF64::new(*value, *ty)),
1202 _ => None,
1203 }
1204 }
1205
1206 pub fn as_bool(&self) -> Option<bool> {
1207 match self {
1208 KclValue::Bool { value, .. } => Some(*value),
1209 _ => None,
1210 }
1211 }
1212
1213 pub fn as_function(&self) -> Option<&FunctionSource> {
1215 match self {
1216 KclValue::Function { value, .. } => Some(value),
1217 _ => None,
1218 }
1219 }
1220
1221 pub fn get_tag_identifier(&self) -> Result<TagIdentifier, KclError> {
1223 match self {
1224 KclValue::TagIdentifier(t) => Ok(*t.clone()),
1225 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1226 format!("Not a tag identifier: {self:?}"),
1227 self.clone().into(),
1228 ))),
1229 }
1230 }
1231
1232 pub fn get_tag_declarator(&self) -> Result<TagNode, KclError> {
1234 match self {
1235 KclValue::TagDeclarator(t) => Ok((**t).clone()),
1236 _ => Err(KclError::new_semantic(KclErrorDetails::new(
1237 format!("Not a tag declarator: {self:?}"),
1238 self.clone().into(),
1239 ))),
1240 }
1241 }
1242
1243 pub fn get_bool(&self) -> Result<bool, KclError> {
1245 self.as_bool().ok_or_else(|| {
1246 KclError::new_type(KclErrorDetails::new(
1247 format!("Expected bool, found {}", self.human_friendly_type()),
1248 self.into(),
1249 ))
1250 })
1251 }
1252
1253 pub fn is_unknown_number(&self) -> bool {
1254 match self {
1255 KclValue::Number { ty, .. } => !ty.is_fully_specified(),
1256 _ => false,
1257 }
1258 }
1259
1260 pub fn value_str(&self) -> Option<String> {
1261 match self {
1262 KclValue::Bool { value, .. } => Some(format!("{value}")),
1263 KclValue::Number { value, .. } => Some(format!("{value}")),
1265 KclValue::String { value, .. } => Some(format!("'{value}'")),
1266 KclValue::Enum { value } => Some(value.qualified_name()),
1267 KclValue::SketchVar { value, .. } => Some(format!("var {}", value.initial_value)),
1269 KclValue::Uuid { value, .. } => Some(format!("{value}")),
1270 KclValue::TagDeclarator(tag) => Some(format!("${}", tag.name)),
1271 KclValue::TagIdentifier(tag) => Some(format!("${}", tag.value)),
1272 KclValue::Tuple { .. } => Some("[...]".to_owned()),
1274 KclValue::HomArray { .. } => Some("[...]".to_owned()),
1275 KclValue::Object { .. } => Some("{ ... }".to_owned()),
1276 KclValue::Module { .. }
1277 | KclValue::GdtAnnotation { .. }
1278 | KclValue::SketchConstraint { .. }
1279 | KclValue::Solid { .. }
1280 | KclValue::Sketch { .. }
1281 | KclValue::Helix { .. }
1282 | KclValue::CameraView { .. }
1283 | KclValue::ImportedGeometry(_)
1284 | KclValue::Function { .. }
1285 | KclValue::Plane { .. }
1286 | KclValue::Face { .. }
1287 | KclValue::Segment { .. }
1288 | KclValue::KclNone { .. }
1289 | KclValue::BoundedEdge { .. }
1290 | KclValue::Type { .. } => None,
1291 }
1292 }
1293}
1294
1295impl From<Geometry> for KclValue {
1296 fn from(value: Geometry) -> Self {
1297 match value {
1298 Geometry::Sketch(x) => Self::Sketch { value: Box::new(x) },
1299 Geometry::Solid(x) => Self::Solid { value: Box::new(x) },
1300 }
1301 }
1302}
1303
1304impl From<GeometryWithImportedGeometry> for KclValue {
1305 fn from(value: GeometryWithImportedGeometry) -> Self {
1306 match value {
1307 GeometryWithImportedGeometry::Sketch(x) => Self::Sketch { value: Box::new(x) },
1308 GeometryWithImportedGeometry::Solid(x) => Self::Solid { value: Box::new(x) },
1309 GeometryWithImportedGeometry::ImportedGeometry(x) => Self::ImportedGeometry(*x),
1310 }
1311 }
1312}
1313
1314impl From<Vec<GeometryWithImportedGeometry>> for KclValue {
1315 fn from(mut values: Vec<GeometryWithImportedGeometry>) -> Self {
1316 if values.len() == 1
1317 && let Some(v) = values.pop()
1318 {
1319 KclValue::from(v)
1320 } else {
1321 KclValue::HomArray {
1322 value: values.into_iter().map(KclValue::from).collect(),
1323 ty: RuntimeType::Union(vec![
1324 RuntimeType::Primitive(PrimitiveType::Sketch),
1325 RuntimeType::Primitive(PrimitiveType::Solid),
1326 RuntimeType::Primitive(PrimitiveType::ImportedGeometry),
1327 ]),
1328 }
1329 }
1330 }
1331}
1332
1333#[cfg(test)]
1334mod tests {
1335 use super::*;
1336 use crate::exec::UnitType;
1337
1338 #[test]
1339 fn test_human_friendly_type() {
1340 let len = KclValue::Number {
1341 value: 1.0,
1342 ty: NumericType::Known(UnitType::GenericLength),
1343 meta: vec![],
1344 };
1345 assert_eq!(len.human_friendly_type(), "a number (Length)".to_string());
1346
1347 let unknown = KclValue::Number {
1348 value: 1.0,
1349 ty: NumericType::Unknown,
1350 meta: vec![],
1351 };
1352 assert_eq!(unknown.human_friendly_type(), "a number with unknown units".to_string());
1353
1354 let mm = KclValue::Number {
1355 value: 1.0,
1356 ty: NumericType::Known(UnitType::Length(UnitLength::Millimeters)),
1357 meta: vec![],
1358 };
1359 assert_eq!(mm.human_friendly_type(), "a number (mm)".to_string());
1360
1361 let array1_mm = KclValue::HomArray {
1362 value: vec![mm.clone()],
1363 ty: RuntimeType::any(),
1364 };
1365 assert_eq!(
1366 array1_mm.human_friendly_type(),
1367 "an array of `number(mm)` with 1 value".to_string()
1368 );
1369
1370 let array2_mm = KclValue::HomArray {
1371 value: vec![mm.clone(), mm.clone()],
1372 ty: RuntimeType::any(),
1373 };
1374 assert_eq!(
1375 array2_mm.human_friendly_type(),
1376 "an array of `number(mm)`, `number(mm)`".to_string()
1377 );
1378
1379 let array3_mm = KclValue::HomArray {
1380 value: vec![mm.clone(), mm.clone(), mm.clone()],
1381 ty: RuntimeType::any(),
1382 };
1383 assert_eq!(
1384 array3_mm.human_friendly_type(),
1385 "an array of `number(mm)`, `number(mm)`, `number(mm)`".to_string()
1386 );
1387
1388 let inches = KclValue::Number {
1389 value: 1.0,
1390 ty: NumericType::Known(UnitType::Length(UnitLength::Inches)),
1391 meta: vec![],
1392 };
1393 let array4 = KclValue::HomArray {
1394 value: vec![mm.clone(), mm.clone(), inches, mm],
1395 ty: RuntimeType::any(),
1396 };
1397 assert_eq!(
1398 array4.human_friendly_type(),
1399 "an array of `number(mm)`, `number(mm)`, `number(in)`, ... with 4 values".to_string()
1400 );
1401
1402 let empty_array = KclValue::HomArray {
1403 value: vec![],
1404 ty: RuntimeType::any(),
1405 };
1406 assert_eq!(empty_array.human_friendly_type(), "an empty array".to_string());
1407
1408 let array_nested = KclValue::HomArray {
1409 value: vec![array2_mm],
1410 ty: RuntimeType::any(),
1411 };
1412 assert_eq!(
1413 array_nested.human_friendly_type(),
1414 "an array of `[any; 2]` with 1 value".to_string()
1415 );
1416 }
1417
1418 fn color_def() -> Arc<EnumTypeDef> {
1419 Arc::new(
1420 EnumTypeDef::new(
1421 EnumTypeId::new(ModuleId::default(), "Color"),
1422 vec!["Red".to_owned(), "Green".to_owned()],
1423 )
1424 .unwrap(),
1425 )
1426 }
1427
1428 fn color_red() -> KclValue {
1429 KclValue::Enum {
1430 value: Box::new(EnumValue::new(color_def(), "Red", vec![])),
1431 }
1432 }
1433
1434 #[test]
1435 fn enum_values_describe_themselves_by_name_and_variant() {
1436 let red = color_red();
1437
1438 assert_eq!(red.human_friendly_type(), "a value of enum `Color`");
1439 assert_eq!(red.value_str(), Some("Color::Red".to_owned()));
1441 assert!(red.show_variable_in_feature_tree());
1442 }
1443
1444 #[test]
1448 fn enum_values_are_exposed_by_nominal_identity() {
1449 let view = crate::execution::KclValueView::from(color_red());
1450 assert_eq!(
1451 view,
1452 crate::execution::KclValueView::Enum {
1453 enum_name: "Color".to_owned(),
1454 variant: "Red".to_owned(),
1455 }
1456 );
1457
1458 let op = crate::execution::cad_op::op_from_kcl_value(&color_red());
1459 assert_eq!(
1460 op,
1461 kcl_api::OpKclValue::Enum {
1462 enum_name: "Color".to_owned(),
1463 variant: "Red".to_owned(),
1464 }
1465 );
1466 }
1467
1468 #[test]
1473 fn enum_values_serialize_as_identity_and_variant() {
1474 assert_eq!(
1475 serde_json::to_value(color_red()).unwrap(),
1476 serde_json::json!({
1477 "type": "Enum",
1478 "value": {
1479 "enum_id": { "module_id": 0, "declared_name": "Color" },
1480 "variant": "Red",
1481 },
1482 })
1483 );
1484 }
1485
1486 #[test]
1487 fn enum_declarations_carry_their_variants() {
1488 let def = EnumTypeDef::new(
1489 EnumTypeId::new(ModuleId::default(), "Color"),
1490 vec!["Red".to_owned(), "Green".to_owned()],
1491 )
1492 .unwrap();
1493
1494 assert_eq!(def.variants(), ["Red", "Green"]);
1495 assert!(def.has_variant("Red"));
1496 assert!(!def.has_variant("Blue"));
1497 assert_ne!(
1500 def.id(),
1501 EnumTypeDef::new(
1502 EnumTypeId::new(ModuleId::from_usize(1), "Color"),
1503 vec!["Red".to_owned(), "Green".to_owned()],
1504 )
1505 .unwrap()
1506 .id()
1507 );
1508 }
1509
1510 #[test]
1511 fn enum_rejects_duplicate_variant() {
1512 let err = EnumTypeDef::new(
1513 EnumTypeId::new(ModuleId::default(), "Color"),
1514 vec!["Red".to_owned(), "Green".to_owned(), "Red".to_owned()],
1515 )
1516 .unwrap_err();
1517
1518 assert_eq!(
1519 err,
1520 DuplicateVariant {
1521 name: "Red".to_owned(),
1522 first_index: 0,
1523 duplicate_index: 2,
1524 }
1525 );
1526 }
1527
1528 #[test]
1529 fn enum_reports_earliest_duplicate() {
1530 let err = EnumTypeDef::new(
1533 EnumTypeId::new(ModuleId::default(), "Color"),
1534 vec![
1535 "Red".to_owned(),
1536 "Green".to_owned(),
1537 "Blue".to_owned(),
1538 "Green".to_owned(),
1539 "Red".to_owned(),
1540 ],
1541 )
1542 .unwrap_err();
1543
1544 assert_eq!(err.name, "Green");
1545 assert_eq!(err.first_index, 1);
1546 assert_eq!(err.duplicate_index, 3);
1547 }
1548}