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