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