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 KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } = self {
294 if !value.is_empty() {
296 let max = 3;
298 let len = value.len();
299 let ellipsis = if len > max { ", ..." } else { "" };
300 let element_label = if len == 1 { "value" } else { "values" };
301 let element_tys = value
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 match self {
446 KclValue::Object { value, .. } => Some(value),
447 _ => None,
448 }
449 }
450
451 pub fn into_object(self) -> Option<KclObjectFields> {
452 match self {
453 KclValue::Object { value, .. } => Some(value),
454 _ => None,
455 }
456 }
457
458 pub fn as_str(&self) -> Option<&str> {
459 match self {
460 KclValue::String { value, .. } => Some(value),
461 _ => None,
462 }
463 }
464
465 pub fn into_array(self) -> Vec<KclValue> {
466 match self {
467 KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => value,
468 _ => vec![self],
469 }
470 }
471
472 pub fn as_point2d(&self) -> Option<[TyF64; 2]> {
473 let value = match self {
474 KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => value,
475 _ => return None,
476 };
477
478 if value.len() != 2 {
479 return None;
480 }
481 let x = value[0].as_ty_f64()?;
482 let y = value[1].as_ty_f64()?;
483 Some([x, y])
484 }
485
486 pub fn as_point3d(&self) -> Option<[TyF64; 3]> {
487 let value = match self {
488 KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => value,
489 _ => return None,
490 };
491
492 if value.len() != 3 {
493 return None;
494 }
495 let x = value[0].as_ty_f64()?;
496 let y = value[1].as_ty_f64()?;
497 let z = value[2].as_ty_f64()?;
498 Some([x, y, z])
499 }
500
501 pub fn as_uuid(&self) -> Option<uuid::Uuid> {
502 match self {
503 KclValue::Uuid { value, .. } => Some(*value),
504 _ => None,
505 }
506 }
507
508 pub fn as_plane(&self) -> Option<&Plane> {
509 match self {
510 KclValue::Plane { value, .. } => Some(value),
511 _ => None,
512 }
513 }
514
515 pub fn as_solid(&self) -> Option<&Solid> {
516 match self {
517 KclValue::Solid { value, .. } => Some(value),
518 _ => None,
519 }
520 }
521
522 pub fn as_sketch(&self) -> Option<&Sketch> {
523 match self {
524 KclValue::Sketch { value, .. } => Some(value),
525 _ => None,
526 }
527 }
528
529 pub fn as_mut_sketch(&mut self) -> Option<&mut Sketch> {
530 match self {
531 KclValue::Sketch { value } => Some(value),
532 _ => None,
533 }
534 }
535
536 pub fn as_mut_tag(&mut self) -> Option<&mut TagIdentifier> {
537 match self {
538 KclValue::TagIdentifier(value) => Some(value),
539 _ => None,
540 }
541 }
542
543 #[cfg(test)]
544 pub fn as_f64(&self) -> Option<f64> {
545 match self {
546 KclValue::Number { value, .. } => Some(*value),
547 _ => None,
548 }
549 }
550
551 pub fn as_ty_f64(&self) -> Option<TyF64> {
552 match self {
553 KclValue::Number { value, ty, .. } => Some(TyF64::new(*value, ty.clone())),
554 _ => None,
555 }
556 }
557
558 pub fn as_bool(&self) -> Option<bool> {
559 match self {
560 KclValue::Bool { value, .. } => Some(*value),
561 _ => None,
562 }
563 }
564
565 pub fn as_function(&self) -> Option<&FunctionSource> {
567 match self {
568 KclValue::Function { value, .. } => Some(value),
569 _ => None,
570 }
571 }
572
573 pub fn get_tag_identifier(&self) -> Result<TagIdentifier, KclError> {
575 match self {
576 KclValue::TagIdentifier(t) => Ok(*t.clone()),
577 _ => Err(KclError::new_semantic(KclErrorDetails::new(
578 format!("Not a tag identifier: {:?}", self),
579 self.clone().into(),
580 ))),
581 }
582 }
583
584 pub fn get_tag_declarator(&self) -> Result<TagNode, KclError> {
586 match self {
587 KclValue::TagDeclarator(t) => Ok((**t).clone()),
588 _ => Err(KclError::new_semantic(KclErrorDetails::new(
589 format!("Not a tag declarator: {:?}", self),
590 self.clone().into(),
591 ))),
592 }
593 }
594
595 pub fn get_bool(&self) -> Result<bool, KclError> {
597 self.as_bool().ok_or_else(|| {
598 KclError::new_type(KclErrorDetails::new(
599 format!("Expected bool, found {}", self.human_friendly_type()),
600 self.into(),
601 ))
602 })
603 }
604
605 pub fn value_str(&self) -> Option<String> {
606 match self {
607 KclValue::Bool { value, .. } => Some(format!("{value}")),
608 KclValue::Number { value, .. } => Some(format!("{value}")),
609 KclValue::String { value, .. } => Some(format!("'{value}'")),
610 KclValue::Uuid { value, .. } => Some(format!("{value}")),
611 KclValue::TagDeclarator(tag) => Some(format!("${}", tag.name)),
612 KclValue::TagIdentifier(tag) => Some(format!("${}", tag.value)),
613 KclValue::Tuple { .. } => Some("[...]".to_owned()),
615 KclValue::HomArray { .. } => Some("[...]".to_owned()),
616 KclValue::Object { .. } => Some("{ ... }".to_owned()),
617 KclValue::Module { .. }
618 | KclValue::Solid { .. }
619 | KclValue::Sketch { .. }
620 | KclValue::Helix { .. }
621 | KclValue::ImportedGeometry(_)
622 | KclValue::Function { .. }
623 | KclValue::Plane { .. }
624 | KclValue::Face { .. }
625 | KclValue::KclNone { .. }
626 | KclValue::Type { .. } => None,
627 }
628 }
629}
630
631impl From<Geometry> for KclValue {
632 fn from(value: Geometry) -> Self {
633 match value {
634 Geometry::Sketch(x) => Self::Sketch { value: Box::new(x) },
635 Geometry::Solid(x) => Self::Solid { value: Box::new(x) },
636 }
637 }
638}
639
640impl From<GeometryWithImportedGeometry> for KclValue {
641 fn from(value: GeometryWithImportedGeometry) -> Self {
642 match value {
643 GeometryWithImportedGeometry::Sketch(x) => Self::Sketch { value: Box::new(x) },
644 GeometryWithImportedGeometry::Solid(x) => Self::Solid { value: Box::new(x) },
645 GeometryWithImportedGeometry::ImportedGeometry(x) => Self::ImportedGeometry(*x),
646 }
647 }
648}
649
650#[cfg(test)]
651mod tests {
652 use super::*;
653
654 #[test]
655 fn test_human_friendly_type() {
656 let len = KclValue::Number {
657 value: 1.0,
658 ty: NumericType::Known(UnitType::Length(UnitLen::Unknown)),
659 meta: vec![],
660 };
661 assert_eq!(len.human_friendly_type(), "number(Length)".to_string());
662
663 let unknown = KclValue::Number {
664 value: 1.0,
665 ty: NumericType::Unknown,
666 meta: vec![],
667 };
668 assert_eq!(unknown.human_friendly_type(), "number(unknown units)".to_string());
669
670 let mm = KclValue::Number {
671 value: 1.0,
672 ty: NumericType::Known(UnitType::Length(UnitLen::Mm)),
673 meta: vec![],
674 };
675 assert_eq!(mm.human_friendly_type(), "number(mm)".to_string());
676
677 let array1_mm = KclValue::HomArray {
678 value: vec![mm.clone()],
679 ty: RuntimeType::any(),
680 };
681 assert_eq!(
682 array1_mm.human_friendly_type(),
683 "array of number(mm) with 1 value".to_string()
684 );
685
686 let array2_mm = KclValue::HomArray {
687 value: vec![mm.clone(), mm.clone()],
688 ty: RuntimeType::any(),
689 };
690 assert_eq!(
691 array2_mm.human_friendly_type(),
692 "array of number(mm), number(mm) with 2 values".to_string()
693 );
694
695 let array3_mm = KclValue::HomArray {
696 value: vec![mm.clone(), mm.clone(), mm.clone()],
697 ty: RuntimeType::any(),
698 };
699 assert_eq!(
700 array3_mm.human_friendly_type(),
701 "array of number(mm), number(mm), number(mm) with 3 values".to_string()
702 );
703
704 let inches = KclValue::Number {
705 value: 1.0,
706 ty: NumericType::Known(UnitType::Length(UnitLen::Inches)),
707 meta: vec![],
708 };
709 let array4 = KclValue::HomArray {
710 value: vec![mm.clone(), mm.clone(), inches.clone(), mm.clone()],
711 ty: RuntimeType::any(),
712 };
713 assert_eq!(
714 array4.human_friendly_type(),
715 "array of number(mm), number(mm), number(in), ... with 4 values".to_string()
716 );
717
718 let empty_array = KclValue::HomArray {
719 value: vec![],
720 ty: RuntimeType::any(),
721 };
722 assert_eq!(empty_array.human_friendly_type(), "[any; 0]".to_string());
723
724 let array_nested = KclValue::HomArray {
725 value: vec![array2_mm.clone()],
726 ty: RuntimeType::any(),
727 };
728 assert_eq!(
729 array_nested.human_friendly_type(),
730 "array of [any; 2] with 1 value".to_string()
731 );
732 }
733}