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