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