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