1use std::collections::HashMap;
2
3use anyhow::Result;
4use indexmap::IndexMap;
5use kittycad_modeling_cmds::units::UnitLength;
6use serde::Serialize;
7
8use crate::{
9 CompilationError, KclError, ModuleId, SourceRange,
10 errors::KclErrorDetails,
11 execution::{
12 EnvironmentRef, ExecState, Face, Geometry, GeometryWithImportedGeometry, Helix, ImportedGeometry, Metadata,
13 Plane, Sketch, Solid, TagIdentifier,
14 annotations::{self, FnAttrs, SETTINGS, SETTINGS_UNIT_LENGTH},
15 types::{NumericType, PrimitiveType, RuntimeType},
16 },
17 parsing::ast::types::{
18 DefaultParamVal, FunctionExpression, KclNone, Literal, LiteralValue, Node, NumericLiteral, TagDeclarator,
19 TagNode, Type,
20 },
21 std::{StdFnProps, args::TyF64},
22};
23
24pub type KclObjectFields = HashMap<String, KclValue>;
25
26#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS)]
28#[ts(export)]
29#[serde(tag = "type")]
30pub enum KclValue {
31 Uuid {
32 value: ::uuid::Uuid,
33 #[serde(skip)]
34 meta: Vec<Metadata>,
35 },
36 Bool {
37 value: bool,
38 #[serde(skip)]
39 meta: Vec<Metadata>,
40 },
41 Number {
42 value: f64,
43 ty: NumericType,
44 #[serde(skip)]
45 meta: Vec<Metadata>,
46 },
47 String {
48 value: String,
49 #[serde(skip)]
50 meta: Vec<Metadata>,
51 },
52 Tuple {
53 value: Vec<KclValue>,
54 #[serde(skip)]
55 meta: Vec<Metadata>,
56 },
57 HomArray {
59 value: Vec<KclValue>,
60 #[serde(skip)]
62 ty: RuntimeType,
63 },
64 Object {
65 value: KclObjectFields,
66 constrainable: bool,
67 #[serde(skip)]
68 meta: Vec<Metadata>,
69 },
70 TagIdentifier(Box<TagIdentifier>),
71 TagDeclarator(crate::parsing::ast::types::BoxNode<TagDeclarator>),
72 Plane {
73 value: Box<Plane>,
74 },
75 Face {
76 value: Box<Face>,
77 },
78 Sketch {
79 value: Box<Sketch>,
80 },
81 Solid {
82 value: Box<Solid>,
83 },
84 Helix {
85 value: Box<Helix>,
86 },
87 ImportedGeometry(ImportedGeometry),
88 Function {
89 #[serde(serialize_with = "function_value_stub")]
90 #[ts(type = "null")]
91 value: Box<FunctionSource>,
92 #[serde(skip)]
93 meta: Vec<Metadata>,
94 },
95 Module {
96 value: ModuleId,
97 #[serde(skip)]
98 meta: Vec<Metadata>,
99 },
100 #[ts(skip)]
101 Type {
102 #[serde(skip)]
103 value: TypeDef,
104 experimental: bool,
105 #[serde(skip)]
106 meta: Vec<Metadata>,
107 },
108 KclNone {
109 value: KclNone,
110 #[serde(skip)]
111 meta: Vec<Metadata>,
112 },
113}
114
115fn function_value_stub<S>(_value: &FunctionSource, serializer: S) -> Result<S::Ok, S::Error>
116where
117 S: serde::Serializer,
118{
119 serializer.serialize_unit()
120}
121
122#[derive(Debug, Clone, PartialEq)]
123pub struct FunctionSource {
124 pub input_arg: Option<(String, Option<Type>)>,
125 pub named_args: IndexMap<String, (Option<DefaultParamVal>, Option<Type>)>,
126 pub return_type: Option<Node<Type>>,
127 pub deprecated: bool,
128 pub experimental: bool,
129 pub include_in_feature_tree: bool,
130 pub is_std: bool,
131 pub body: FunctionBody,
132 pub ast: crate::parsing::ast::types::BoxNode<FunctionExpression>,
133}
134
135impl FunctionSource {
136 pub fn rust(
137 func: crate::std::StdFn,
138 ast: Box<Node<FunctionExpression>>,
139 props: StdFnProps,
140 attrs: FnAttrs,
141 ) -> Self {
142 let (input_arg, named_args) = Self::args_from_ast(&ast);
143
144 FunctionSource {
145 input_arg,
146 named_args,
147 return_type: ast.return_type.clone(),
148 deprecated: attrs.deprecated,
149 experimental: attrs.experimental,
150 include_in_feature_tree: props.include_in_feature_tree,
151 is_std: true,
152 body: FunctionBody::Rust(func),
153 ast,
154 }
155 }
156
157 pub fn kcl(ast: Box<Node<FunctionExpression>>, memory: EnvironmentRef, is_std: bool) -> Self {
158 let (input_arg, named_args) = Self::args_from_ast(&ast);
159 FunctionSource {
160 input_arg,
161 named_args,
162 return_type: ast.return_type.clone(),
163 deprecated: false,
164 experimental: false,
165 include_in_feature_tree: true,
166 is_std,
167 body: FunctionBody::Kcl(memory),
168 ast,
169 }
170 }
171
172 #[allow(clippy::type_complexity)]
173 fn args_from_ast(
174 ast: &FunctionExpression,
175 ) -> (
176 Option<(String, Option<Type>)>,
177 IndexMap<String, (Option<DefaultParamVal>, Option<Type>)>,
178 ) {
179 let mut input_arg = None;
180 let mut named_args = IndexMap::new();
181 for p in &ast.params {
182 if !p.labeled {
183 input_arg = Some((
184 p.identifier.name.clone(),
185 p.param_type.as_ref().map(|t| t.inner.clone()),
186 ));
187 continue;
188 }
189
190 named_args.insert(
191 p.identifier.name.clone(),
192 (p.default_value.clone(), p.param_type.as_ref().map(|t| t.inner.clone())),
193 );
194 }
195
196 (input_arg, named_args)
197 }
198}
199
200#[derive(Debug, Clone, PartialEq)]
201#[allow(unpredictable_function_pointer_comparisons)]
204pub enum FunctionBody {
205 Rust(crate::std::StdFn),
206 Kcl(EnvironmentRef),
207}
208
209#[derive(Debug, Clone, PartialEq)]
210pub enum TypeDef {
211 RustRepr(PrimitiveType, StdFnProps),
212 Alias(RuntimeType),
213}
214
215impl From<Vec<Sketch>> for KclValue {
216 fn from(mut eg: Vec<Sketch>) -> Self {
217 if eg.len() == 1 {
218 KclValue::Sketch {
219 value: Box::new(eg.pop().unwrap()),
220 }
221 } else {
222 KclValue::HomArray {
223 value: eg
224 .into_iter()
225 .map(|s| KclValue::Sketch { value: Box::new(s) })
226 .collect(),
227 ty: RuntimeType::Primitive(PrimitiveType::Sketch),
228 }
229 }
230 }
231}
232
233impl From<Vec<Solid>> for KclValue {
234 fn from(mut eg: Vec<Solid>) -> Self {
235 if eg.len() == 1 {
236 KclValue::Solid {
237 value: Box::new(eg.pop().unwrap()),
238 }
239 } else {
240 KclValue::HomArray {
241 value: eg.into_iter().map(|s| KclValue::Solid { value: Box::new(s) }).collect(),
242 ty: RuntimeType::Primitive(PrimitiveType::Solid),
243 }
244 }
245 }
246}
247
248impl From<KclValue> for Vec<SourceRange> {
249 fn from(item: KclValue) -> Self {
250 match item {
251 KclValue::TagDeclarator(t) => vec![SourceRange::new(t.start, t.end, t.module_id)],
252 KclValue::TagIdentifier(t) => to_vec_sr(&t.meta),
253 KclValue::Solid { value } => to_vec_sr(&value.meta),
254 KclValue::Sketch { value } => to_vec_sr(&value.meta),
255 KclValue::Helix { value } => to_vec_sr(&value.meta),
256 KclValue::ImportedGeometry(i) => to_vec_sr(&i.meta),
257 KclValue::Function { meta, .. } => to_vec_sr(&meta),
258 KclValue::Plane { value } => to_vec_sr(&value.meta),
259 KclValue::Face { value } => to_vec_sr(&value.meta),
260 KclValue::Bool { meta, .. } => to_vec_sr(&meta),
261 KclValue::Number { meta, .. } => to_vec_sr(&meta),
262 KclValue::String { meta, .. } => to_vec_sr(&meta),
263 KclValue::Tuple { meta, .. } => to_vec_sr(&meta),
264 KclValue::HomArray { value, .. } => value.iter().flat_map(Into::<Vec<SourceRange>>::into).collect(),
265 KclValue::Object { meta, .. } => to_vec_sr(&meta),
266 KclValue::Module { meta, .. } => to_vec_sr(&meta),
267 KclValue::Uuid { meta, .. } => to_vec_sr(&meta),
268 KclValue::Type { meta, .. } => to_vec_sr(&meta),
269 KclValue::KclNone { meta, .. } => to_vec_sr(&meta),
270 }
271 }
272}
273
274fn to_vec_sr(meta: &[Metadata]) -> Vec<SourceRange> {
275 meta.iter().map(|m| m.source_range).collect()
276}
277
278impl From<&KclValue> for Vec<SourceRange> {
279 fn from(item: &KclValue) -> Self {
280 match item {
281 KclValue::TagDeclarator(t) => vec![SourceRange::new(t.start, t.end, t.module_id)],
282 KclValue::TagIdentifier(t) => to_vec_sr(&t.meta),
283 KclValue::Solid { value } => to_vec_sr(&value.meta),
284 KclValue::Sketch { value } => to_vec_sr(&value.meta),
285 KclValue::Helix { value } => to_vec_sr(&value.meta),
286 KclValue::ImportedGeometry(i) => to_vec_sr(&i.meta),
287 KclValue::Function { meta, .. } => to_vec_sr(meta),
288 KclValue::Plane { value } => to_vec_sr(&value.meta),
289 KclValue::Face { value } => to_vec_sr(&value.meta),
290 KclValue::Bool { meta, .. } => to_vec_sr(meta),
291 KclValue::Number { meta, .. } => to_vec_sr(meta),
292 KclValue::String { meta, .. } => to_vec_sr(meta),
293 KclValue::Uuid { meta, .. } => to_vec_sr(meta),
294 KclValue::Tuple { meta, .. } => to_vec_sr(meta),
295 KclValue::HomArray { value, .. } => value.iter().flat_map(Into::<Vec<SourceRange>>::into).collect(),
296 KclValue::Object { meta, .. } => to_vec_sr(meta),
297 KclValue::Module { meta, .. } => to_vec_sr(meta),
298 KclValue::KclNone { meta, .. } => to_vec_sr(meta),
299 KclValue::Type { meta, .. } => to_vec_sr(meta),
300 }
301 }
302}
303
304impl From<&KclValue> for SourceRange {
305 fn from(item: &KclValue) -> Self {
306 let v: Vec<_> = item.into();
307 v.into_iter().next().unwrap_or_default()
308 }
309}
310
311impl KclValue {
312 pub(crate) fn metadata(&self) -> Vec<Metadata> {
313 match self {
314 KclValue::Uuid { value: _, meta } => meta.clone(),
315 KclValue::Bool { value: _, meta } => meta.clone(),
316 KclValue::Number { meta, .. } => meta.clone(),
317 KclValue::String { value: _, meta } => meta.clone(),
318 KclValue::Tuple { value: _, meta } => meta.clone(),
319 KclValue::HomArray { value, .. } => value.iter().flat_map(|v| v.metadata()).collect(),
320 KclValue::Object { meta, .. } => meta.clone(),
321 KclValue::TagIdentifier(x) => x.meta.clone(),
322 KclValue::TagDeclarator(x) => vec![x.metadata()],
323 KclValue::Plane { value } => value.meta.clone(),
324 KclValue::Face { value } => value.meta.clone(),
325 KclValue::Sketch { value } => value.meta.clone(),
326 KclValue::Solid { value } => value.meta.clone(),
327 KclValue::Helix { value } => value.meta.clone(),
328 KclValue::ImportedGeometry(x) => x.meta.clone(),
329 KclValue::Function { meta, .. } => meta.clone(),
330 KclValue::Module { meta, .. } => meta.clone(),
331 KclValue::KclNone { meta, .. } => meta.clone(),
332 KclValue::Type { meta, .. } => meta.clone(),
333 }
334 }
335
336 #[allow(unused)]
337 pub(crate) fn none() -> Self {
338 Self::KclNone {
339 value: Default::default(),
340 meta: Default::default(),
341 }
342 }
343
344 pub(crate) fn show_variable_in_feature_tree(&self) -> bool {
348 match self {
349 KclValue::Uuid { .. } => false,
350 KclValue::Bool { .. } | KclValue::Number { .. } | KclValue::String { .. } => true,
351 KclValue::Tuple { .. }
352 | KclValue::HomArray { .. }
353 | KclValue::Object { .. }
354 | KclValue::TagIdentifier(_)
355 | KclValue::TagDeclarator(_)
356 | KclValue::Plane { .. }
357 | KclValue::Face { .. }
358 | KclValue::Sketch { .. }
359 | KclValue::Solid { .. }
360 | KclValue::Helix { .. }
361 | KclValue::ImportedGeometry(_)
362 | KclValue::Function { .. }
363 | KclValue::Module { .. }
364 | KclValue::Type { .. }
365 | KclValue::KclNone { .. } => false,
366 }
367 }
368
369 pub(crate) fn human_friendly_type(&self) -> String {
372 match self {
373 KclValue::Uuid { .. } => "a unique ID (uuid)".to_owned(),
374 KclValue::TagDeclarator(_) => "a tag declarator".to_owned(),
375 KclValue::TagIdentifier(_) => "a tag identifier".to_owned(),
376 KclValue::Solid { .. } => "a solid".to_owned(),
377 KclValue::Sketch { .. } => "a sketch".to_owned(),
378 KclValue::Helix { .. } => "a helix".to_owned(),
379 KclValue::ImportedGeometry(_) => "an imported geometry".to_owned(),
380 KclValue::Function { .. } => "a function".to_owned(),
381 KclValue::Plane { .. } => "a plane".to_owned(),
382 KclValue::Face { .. } => "a face".to_owned(),
383 KclValue::Bool { .. } => "a boolean (`true` or `false`)".to_owned(),
384 KclValue::Number {
385 ty: NumericType::Unknown,
386 ..
387 } => "a number with unknown units".to_owned(),
388 KclValue::Number {
389 ty: NumericType::Known(units),
390 ..
391 } => format!("a number ({units})"),
392 KclValue::Number { .. } => "a number".to_owned(),
393 KclValue::String { .. } => "a string".to_owned(),
394 KclValue::Object { .. } => "an object".to_owned(),
395 KclValue::Module { .. } => "a module".to_owned(),
396 KclValue::Type { .. } => "a type".to_owned(),
397 KclValue::KclNone { .. } => "none".to_owned(),
398 KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => {
399 if value.is_empty() {
400 "an empty array".to_owned()
401 } else {
402 const MAX: usize = 3;
404
405 let len = value.len();
406 let element_tys = value
407 .iter()
408 .take(MAX)
409 .map(|elem| elem.principal_type_string())
410 .collect::<Vec<_>>()
411 .join(", ");
412 let mut result = format!("an array of {element_tys}");
413 if len > MAX {
414 result.push_str(&format!(", ... with {len} values"));
415 }
416 if len == 1 {
417 result.push_str(" with 1 value");
418 }
419 result
420 }
421 }
422 }
423 }
424
425 pub(crate) fn from_numeric_literal(literal: &Node<NumericLiteral>, exec_state: &mut ExecState) -> Self {
426 let meta = vec![literal.metadata()];
427 let ty = NumericType::from_parsed(literal.suffix, &exec_state.mod_local.settings);
428 KclValue::Number {
429 value: literal.value,
430 meta,
431 ty,
432 }
433 }
434
435 pub(crate) fn from_literal(literal: Node<Literal>, exec_state: &mut ExecState) -> Self {
436 let meta = vec![literal.metadata()];
437 match literal.inner.value {
438 LiteralValue::Number { value, suffix } => {
439 let ty = NumericType::from_parsed(suffix, &exec_state.mod_local.settings);
440 if let NumericType::Default { len, .. } = &ty
441 && !exec_state.mod_local.explicit_length_units
442 && *len != UnitLength::Millimeters
443 {
444 exec_state.warn(
445 CompilationError::err(
446 literal.as_source_range(),
447 "Project-wide units are deprecated. Prefer to use per-file default units.",
448 )
449 .with_suggestion(
450 "Fix by adding per-file settings",
451 format!("@{SETTINGS}({SETTINGS_UNIT_LENGTH} = {len})\n"),
452 Some(SourceRange::new(0, 0, literal.module_id)),
454 crate::errors::Tag::Deprecated,
455 ),
456 annotations::WARN_DEPRECATED,
457 );
458 }
459 KclValue::Number { value, meta, ty }
460 }
461 LiteralValue::String(value) => KclValue::String { value, meta },
462 LiteralValue::Bool(value) => KclValue::Bool { value, meta },
463 }
464 }
465
466 pub(crate) fn from_default_param(param: DefaultParamVal, exec_state: &mut ExecState) -> Self {
467 match param {
468 DefaultParamVal::Literal(lit) => Self::from_literal(lit, exec_state),
469 DefaultParamVal::KclNone(value) => KclValue::KclNone {
470 value,
471 meta: Default::default(),
472 },
473 }
474 }
475
476 pub(crate) fn map_env_ref(&self, old_env: usize, new_env: usize) -> Self {
477 let mut result = self.clone();
478 if let KclValue::Function { ref mut value, .. } = result
479 && let FunctionSource {
480 body: FunctionBody::Kcl(memory),
481 ..
482 } = &mut **value
483 {
484 memory.replace_env(old_env, new_env);
485 }
486
487 result
488 }
489
490 pub const fn from_number_with_type(f: f64, ty: NumericType, meta: Vec<Metadata>) -> Self {
491 Self::Number { value: f, meta, ty }
492 }
493
494 pub fn from_point2d(p: [f64; 2], ty: NumericType, meta: Vec<Metadata>) -> Self {
496 let [x, y] = p;
497 Self::Tuple {
498 value: vec![
499 Self::Number {
500 value: x,
501 meta: meta.clone(),
502 ty,
503 },
504 Self::Number {
505 value: y,
506 meta: meta.clone(),
507 ty,
508 },
509 ],
510 meta,
511 }
512 }
513
514 pub fn from_point3d(p: [f64; 3], ty: NumericType, meta: Vec<Metadata>) -> Self {
516 let [x, y, z] = p;
517 Self::Tuple {
518 value: vec![
519 Self::Number {
520 value: x,
521 meta: meta.clone(),
522 ty,
523 },
524 Self::Number {
525 value: y,
526 meta: meta.clone(),
527 ty,
528 },
529 Self::Number {
530 value: z,
531 meta: meta.clone(),
532 ty,
533 },
534 ],
535 meta,
536 }
537 }
538
539 pub fn array_from_point3d(p: [f64; 3], ty: NumericType, meta: Vec<Metadata>) -> Self {
541 let [x, y, z] = p;
542 Self::HomArray {
543 value: vec![
544 Self::Number {
545 value: x,
546 meta: meta.clone(),
547 ty,
548 },
549 Self::Number {
550 value: y,
551 meta: meta.clone(),
552 ty,
553 },
554 Self::Number {
555 value: z,
556 meta: meta.clone(),
557 ty,
558 },
559 ],
560 ty: ty.into(),
561 }
562 }
563
564 pub(crate) fn as_usize(&self) -> Option<usize> {
565 match self {
566 KclValue::Number { value, .. } => crate::try_f64_to_usize(*value),
567 _ => None,
568 }
569 }
570
571 pub fn as_int(&self) -> Option<i64> {
572 match self {
573 KclValue::Number { value, .. } => crate::try_f64_to_i64(*value),
574 _ => None,
575 }
576 }
577
578 pub fn as_int_with_ty(&self) -> Option<(i64, NumericType)> {
579 match self {
580 KclValue::Number { value, ty, .. } => crate::try_f64_to_i64(*value).map(|i| (i, *ty)),
581 _ => None,
582 }
583 }
584
585 pub fn as_object(&self) -> Option<&KclObjectFields> {
586 match self {
587 KclValue::Object { value, .. } => Some(value),
588 _ => None,
589 }
590 }
591
592 pub fn into_object(self) -> Option<KclObjectFields> {
593 match self {
594 KclValue::Object { value, .. } => Some(value),
595 _ => None,
596 }
597 }
598
599 pub fn as_str(&self) -> Option<&str> {
600 match self {
601 KclValue::String { value, .. } => Some(value),
602 _ => None,
603 }
604 }
605
606 pub fn into_array(self) -> Vec<KclValue> {
607 match self {
608 KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => value,
609 _ => vec![self],
610 }
611 }
612
613 pub fn as_point2d(&self) -> Option<[TyF64; 2]> {
614 let value = match self {
615 KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => value,
616 _ => return None,
617 };
618
619 if value.len() != 2 {
620 return None;
621 }
622 let x = value[0].as_ty_f64()?;
623 let y = value[1].as_ty_f64()?;
624 Some([x, y])
625 }
626
627 pub fn as_point3d(&self) -> Option<[TyF64; 3]> {
628 let value = match self {
629 KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => value,
630 _ => return None,
631 };
632
633 if value.len() != 3 {
634 return None;
635 }
636 let x = value[0].as_ty_f64()?;
637 let y = value[1].as_ty_f64()?;
638 let z = value[2].as_ty_f64()?;
639 Some([x, y, z])
640 }
641
642 pub fn as_uuid(&self) -> Option<uuid::Uuid> {
643 match self {
644 KclValue::Uuid { value, .. } => Some(*value),
645 _ => None,
646 }
647 }
648
649 pub fn as_plane(&self) -> Option<&Plane> {
650 match self {
651 KclValue::Plane { value, .. } => Some(value),
652 _ => None,
653 }
654 }
655
656 pub fn as_solid(&self) -> Option<&Solid> {
657 match self {
658 KclValue::Solid { value, .. } => Some(value),
659 _ => None,
660 }
661 }
662
663 pub fn as_sketch(&self) -> Option<&Sketch> {
664 match self {
665 KclValue::Sketch { value, .. } => Some(value),
666 _ => None,
667 }
668 }
669
670 pub fn as_mut_sketch(&mut self) -> Option<&mut Sketch> {
671 match self {
672 KclValue::Sketch { value } => Some(value),
673 _ => None,
674 }
675 }
676
677 pub fn as_mut_tag(&mut self) -> Option<&mut TagIdentifier> {
678 match self {
679 KclValue::TagIdentifier(value) => Some(value),
680 _ => None,
681 }
682 }
683
684 #[cfg(test)]
685 pub fn as_f64(&self) -> Option<f64> {
686 match self {
687 KclValue::Number { value, .. } => Some(*value),
688 _ => None,
689 }
690 }
691
692 pub fn as_ty_f64(&self) -> Option<TyF64> {
693 match self {
694 KclValue::Number { value, ty, .. } => Some(TyF64::new(*value, *ty)),
695 _ => None,
696 }
697 }
698
699 pub fn as_bool(&self) -> Option<bool> {
700 match self {
701 KclValue::Bool { value, .. } => Some(*value),
702 _ => None,
703 }
704 }
705
706 pub fn as_function(&self) -> Option<&FunctionSource> {
708 match self {
709 KclValue::Function { value, .. } => Some(value),
710 _ => None,
711 }
712 }
713
714 pub fn get_tag_identifier(&self) -> Result<TagIdentifier, KclError> {
716 match self {
717 KclValue::TagIdentifier(t) => Ok(*t.clone()),
718 _ => Err(KclError::new_semantic(KclErrorDetails::new(
719 format!("Not a tag identifier: {self:?}"),
720 self.clone().into(),
721 ))),
722 }
723 }
724
725 pub fn get_tag_declarator(&self) -> Result<TagNode, KclError> {
727 match self {
728 KclValue::TagDeclarator(t) => Ok((**t).clone()),
729 _ => Err(KclError::new_semantic(KclErrorDetails::new(
730 format!("Not a tag declarator: {self:?}"),
731 self.clone().into(),
732 ))),
733 }
734 }
735
736 pub fn get_bool(&self) -> Result<bool, KclError> {
738 self.as_bool().ok_or_else(|| {
739 KclError::new_type(KclErrorDetails::new(
740 format!("Expected bool, found {}", self.human_friendly_type()),
741 self.into(),
742 ))
743 })
744 }
745
746 pub fn is_unknown_number(&self) -> bool {
747 match self {
748 KclValue::Number { ty, .. } => !ty.is_fully_specified(),
749 _ => false,
750 }
751 }
752
753 pub fn value_str(&self) -> Option<String> {
754 match self {
755 KclValue::Bool { value, .. } => Some(format!("{value}")),
756 KclValue::Number { value, .. } => Some(format!("{value}")),
757 KclValue::String { value, .. } => Some(format!("'{value}'")),
758 KclValue::Uuid { value, .. } => Some(format!("{value}")),
759 KclValue::TagDeclarator(tag) => Some(format!("${}", tag.name)),
760 KclValue::TagIdentifier(tag) => Some(format!("${}", tag.value)),
761 KclValue::Tuple { .. } => Some("[...]".to_owned()),
763 KclValue::HomArray { .. } => Some("[...]".to_owned()),
764 KclValue::Object { .. } => Some("{ ... }".to_owned()),
765 KclValue::Module { .. }
766 | KclValue::Solid { .. }
767 | KclValue::Sketch { .. }
768 | KclValue::Helix { .. }
769 | KclValue::ImportedGeometry(_)
770 | KclValue::Function { .. }
771 | KclValue::Plane { .. }
772 | KclValue::Face { .. }
773 | KclValue::KclNone { .. }
774 | KclValue::Type { .. } => None,
775 }
776 }
777}
778
779impl From<Geometry> for KclValue {
780 fn from(value: Geometry) -> Self {
781 match value {
782 Geometry::Sketch(x) => Self::Sketch { value: Box::new(x) },
783 Geometry::Solid(x) => Self::Solid { value: Box::new(x) },
784 }
785 }
786}
787
788impl From<GeometryWithImportedGeometry> for KclValue {
789 fn from(value: GeometryWithImportedGeometry) -> Self {
790 match value {
791 GeometryWithImportedGeometry::Sketch(x) => Self::Sketch { value: Box::new(x) },
792 GeometryWithImportedGeometry::Solid(x) => Self::Solid { value: Box::new(x) },
793 GeometryWithImportedGeometry::ImportedGeometry(x) => Self::ImportedGeometry(*x),
794 }
795 }
796}
797
798#[cfg(test)]
799mod tests {
800 use super::*;
801 use crate::exec::UnitType;
802
803 #[test]
804 fn test_human_friendly_type() {
805 let len = KclValue::Number {
806 value: 1.0,
807 ty: NumericType::Known(UnitType::GenericLength),
808 meta: vec![],
809 };
810 assert_eq!(len.human_friendly_type(), "a number (Length)".to_string());
811
812 let unknown = KclValue::Number {
813 value: 1.0,
814 ty: NumericType::Unknown,
815 meta: vec![],
816 };
817 assert_eq!(unknown.human_friendly_type(), "a number with unknown units".to_string());
818
819 let mm = KclValue::Number {
820 value: 1.0,
821 ty: NumericType::Known(UnitType::Length(UnitLength::Millimeters)),
822 meta: vec![],
823 };
824 assert_eq!(mm.human_friendly_type(), "a number (mm)".to_string());
825
826 let array1_mm = KclValue::HomArray {
827 value: vec![mm.clone()],
828 ty: RuntimeType::any(),
829 };
830 assert_eq!(
831 array1_mm.human_friendly_type(),
832 "an array of `number(mm)` with 1 value".to_string()
833 );
834
835 let array2_mm = KclValue::HomArray {
836 value: vec![mm.clone(), mm.clone()],
837 ty: RuntimeType::any(),
838 };
839 assert_eq!(
840 array2_mm.human_friendly_type(),
841 "an array of `number(mm)`, `number(mm)`".to_string()
842 );
843
844 let array3_mm = KclValue::HomArray {
845 value: vec![mm.clone(), mm.clone(), mm.clone()],
846 ty: RuntimeType::any(),
847 };
848 assert_eq!(
849 array3_mm.human_friendly_type(),
850 "an array of `number(mm)`, `number(mm)`, `number(mm)`".to_string()
851 );
852
853 let inches = KclValue::Number {
854 value: 1.0,
855 ty: NumericType::Known(UnitType::Length(UnitLength::Inches)),
856 meta: vec![],
857 };
858 let array4 = KclValue::HomArray {
859 value: vec![mm.clone(), mm.clone(), inches.clone(), mm.clone()],
860 ty: RuntimeType::any(),
861 };
862 assert_eq!(
863 array4.human_friendly_type(),
864 "an array of `number(mm)`, `number(mm)`, `number(in)`, ... with 4 values".to_string()
865 );
866
867 let empty_array = KclValue::HomArray {
868 value: vec![],
869 ty: RuntimeType::any(),
870 };
871 assert_eq!(empty_array.human_friendly_type(), "an empty array".to_string());
872
873 let array_nested = KclValue::HomArray {
874 value: vec![array2_mm.clone()],
875 ty: RuntimeType::any(),
876 };
877 assert_eq!(
878 array_nested.human_friendly_type(),
879 "an array of `[any; 2]` with 1 value".to_string()
880 );
881 }
882}