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)]
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 annotations::WARN_DEPRECATED,
381 );
382 }
383 }
384 KclValue::Number { value, meta, ty }
385 }
386 LiteralValue::String(value) => KclValue::String { value, meta },
387 LiteralValue::Bool(value) => KclValue::Bool { value, meta },
388 }
389 }
390
391 pub(crate) fn from_default_param(param: DefaultParamVal, exec_state: &mut ExecState) -> Self {
392 match param {
393 DefaultParamVal::Literal(lit) => Self::from_literal(lit, exec_state),
394 DefaultParamVal::KclNone(value) => KclValue::KclNone {
395 value,
396 meta: Default::default(),
397 },
398 }
399 }
400
401 pub(crate) fn map_env_ref(&self, old_env: usize, new_env: usize) -> Self {
402 let mut result = self.clone();
403 if let KclValue::Function {
404 value: FunctionSource::User { ref mut memory, .. },
405 ..
406 } = result
407 {
408 memory.replace_env(old_env, new_env);
409 }
410 result
411 }
412
413 pub const fn from_number_with_type(f: f64, ty: NumericType, meta: Vec<Metadata>) -> Self {
414 Self::Number { value: f, meta, ty }
415 }
416
417 pub fn from_point2d(p: [f64; 2], ty: NumericType, meta: Vec<Metadata>) -> Self {
419 let [x, y] = p;
420 Self::Tuple {
421 value: vec![
422 Self::Number {
423 value: x,
424 meta: meta.clone(),
425 ty,
426 },
427 Self::Number {
428 value: y,
429 meta: meta.clone(),
430 ty,
431 },
432 ],
433 meta,
434 }
435 }
436
437 pub fn from_point3d(p: [f64; 3], ty: NumericType, meta: Vec<Metadata>) -> Self {
439 let [x, y, z] = p;
440 Self::Tuple {
441 value: vec![
442 Self::Number {
443 value: x,
444 meta: meta.clone(),
445 ty,
446 },
447 Self::Number {
448 value: y,
449 meta: meta.clone(),
450 ty,
451 },
452 Self::Number {
453 value: z,
454 meta: meta.clone(),
455 ty,
456 },
457 ],
458 meta,
459 }
460 }
461
462 pub fn array_from_point3d(p: [f64; 3], ty: NumericType, meta: Vec<Metadata>) -> Self {
464 let [x, y, z] = p;
465 Self::HomArray {
466 value: vec![
467 Self::Number {
468 value: x,
469 meta: meta.clone(),
470 ty,
471 },
472 Self::Number {
473 value: y,
474 meta: meta.clone(),
475 ty,
476 },
477 Self::Number {
478 value: z,
479 meta: meta.clone(),
480 ty,
481 },
482 ],
483 ty: ty.into(),
484 }
485 }
486
487 pub(crate) fn as_usize(&self) -> Option<usize> {
488 match self {
489 KclValue::Number { value, .. } => crate::try_f64_to_usize(*value),
490 _ => None,
491 }
492 }
493
494 pub fn as_int(&self) -> Option<i64> {
495 match self {
496 KclValue::Number { value, .. } => crate::try_f64_to_i64(*value),
497 _ => None,
498 }
499 }
500
501 pub fn as_int_with_ty(&self) -> Option<(i64, NumericType)> {
502 match self {
503 KclValue::Number { value, ty, .. } => crate::try_f64_to_i64(*value).map(|i| (i, *ty)),
504 _ => None,
505 }
506 }
507
508 pub fn as_object(&self) -> Option<&KclObjectFields> {
509 match self {
510 KclValue::Object { value, .. } => Some(value),
511 _ => None,
512 }
513 }
514
515 pub fn into_object(self) -> Option<KclObjectFields> {
516 match self {
517 KclValue::Object { value, .. } => Some(value),
518 _ => None,
519 }
520 }
521
522 pub fn as_str(&self) -> Option<&str> {
523 match self {
524 KclValue::String { value, .. } => Some(value),
525 _ => None,
526 }
527 }
528
529 pub fn into_array(self) -> Vec<KclValue> {
530 match self {
531 KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => value,
532 _ => vec![self],
533 }
534 }
535
536 pub fn as_point2d(&self) -> Option<[TyF64; 2]> {
537 let value = match self {
538 KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => value,
539 _ => return None,
540 };
541
542 if value.len() != 2 {
543 return None;
544 }
545 let x = value[0].as_ty_f64()?;
546 let y = value[1].as_ty_f64()?;
547 Some([x, y])
548 }
549
550 pub fn as_point3d(&self) -> Option<[TyF64; 3]> {
551 let value = match self {
552 KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } => value,
553 _ => return None,
554 };
555
556 if value.len() != 3 {
557 return None;
558 }
559 let x = value[0].as_ty_f64()?;
560 let y = value[1].as_ty_f64()?;
561 let z = value[2].as_ty_f64()?;
562 Some([x, y, z])
563 }
564
565 pub fn as_uuid(&self) -> Option<uuid::Uuid> {
566 match self {
567 KclValue::Uuid { value, .. } => Some(*value),
568 _ => None,
569 }
570 }
571
572 pub fn as_plane(&self) -> Option<&Plane> {
573 match self {
574 KclValue::Plane { value, .. } => Some(value),
575 _ => None,
576 }
577 }
578
579 pub fn as_solid(&self) -> Option<&Solid> {
580 match self {
581 KclValue::Solid { value, .. } => Some(value),
582 _ => None,
583 }
584 }
585
586 pub fn as_sketch(&self) -> Option<&Sketch> {
587 match self {
588 KclValue::Sketch { value, .. } => Some(value),
589 _ => None,
590 }
591 }
592
593 pub fn as_mut_sketch(&mut self) -> Option<&mut Sketch> {
594 match self {
595 KclValue::Sketch { value } => Some(value),
596 _ => None,
597 }
598 }
599
600 pub fn as_mut_tag(&mut self) -> Option<&mut TagIdentifier> {
601 match self {
602 KclValue::TagIdentifier(value) => Some(value),
603 _ => None,
604 }
605 }
606
607 #[cfg(test)]
608 pub fn as_f64(&self) -> Option<f64> {
609 match self {
610 KclValue::Number { value, .. } => Some(*value),
611 _ => None,
612 }
613 }
614
615 pub fn as_ty_f64(&self) -> Option<TyF64> {
616 match self {
617 KclValue::Number { value, ty, .. } => Some(TyF64::new(*value, *ty)),
618 _ => None,
619 }
620 }
621
622 pub fn as_bool(&self) -> Option<bool> {
623 match self {
624 KclValue::Bool { value, .. } => Some(*value),
625 _ => None,
626 }
627 }
628
629 pub fn as_function(&self) -> Option<&FunctionSource> {
631 match self {
632 KclValue::Function { value, .. } => Some(value),
633 _ => None,
634 }
635 }
636
637 pub fn get_tag_identifier(&self) -> Result<TagIdentifier, KclError> {
639 match self {
640 KclValue::TagIdentifier(t) => Ok(*t.clone()),
641 _ => Err(KclError::new_semantic(KclErrorDetails::new(
642 format!("Not a tag identifier: {self:?}"),
643 self.clone().into(),
644 ))),
645 }
646 }
647
648 pub fn get_tag_declarator(&self) -> Result<TagNode, KclError> {
650 match self {
651 KclValue::TagDeclarator(t) => Ok((**t).clone()),
652 _ => Err(KclError::new_semantic(KclErrorDetails::new(
653 format!("Not a tag declarator: {self:?}"),
654 self.clone().into(),
655 ))),
656 }
657 }
658
659 pub fn get_bool(&self) -> Result<bool, KclError> {
661 self.as_bool().ok_or_else(|| {
662 KclError::new_type(KclErrorDetails::new(
663 format!("Expected bool, found {}", self.human_friendly_type()),
664 self.into(),
665 ))
666 })
667 }
668
669 pub fn is_unknown_number(&self) -> bool {
670 match self {
671 KclValue::Number { ty, .. } => !ty.is_fully_specified(),
672 _ => false,
673 }
674 }
675
676 pub fn value_str(&self) -> Option<String> {
677 match self {
678 KclValue::Bool { value, .. } => Some(format!("{value}")),
679 KclValue::Number { value, .. } => Some(format!("{value}")),
680 KclValue::String { value, .. } => Some(format!("'{value}'")),
681 KclValue::Uuid { value, .. } => Some(format!("{value}")),
682 KclValue::TagDeclarator(tag) => Some(format!("${}", tag.name)),
683 KclValue::TagIdentifier(tag) => Some(format!("${}", tag.value)),
684 KclValue::Tuple { .. } => Some("[...]".to_owned()),
686 KclValue::HomArray { .. } => Some("[...]".to_owned()),
687 KclValue::Object { .. } => Some("{ ... }".to_owned()),
688 KclValue::Module { .. }
689 | KclValue::Solid { .. }
690 | KclValue::Sketch { .. }
691 | KclValue::Helix { .. }
692 | KclValue::ImportedGeometry(_)
693 | KclValue::Function { .. }
694 | KclValue::Plane { .. }
695 | KclValue::Face { .. }
696 | KclValue::KclNone { .. }
697 | KclValue::Type { .. } => None,
698 }
699 }
700}
701
702impl From<Geometry> for KclValue {
703 fn from(value: Geometry) -> Self {
704 match value {
705 Geometry::Sketch(x) => Self::Sketch { value: Box::new(x) },
706 Geometry::Solid(x) => Self::Solid { value: Box::new(x) },
707 }
708 }
709}
710
711impl From<GeometryWithImportedGeometry> for KclValue {
712 fn from(value: GeometryWithImportedGeometry) -> Self {
713 match value {
714 GeometryWithImportedGeometry::Sketch(x) => Self::Sketch { value: Box::new(x) },
715 GeometryWithImportedGeometry::Solid(x) => Self::Solid { value: Box::new(x) },
716 GeometryWithImportedGeometry::ImportedGeometry(x) => Self::ImportedGeometry(*x),
717 }
718 }
719}
720
721#[cfg(test)]
722mod tests {
723 use super::*;
724 use crate::exec::UnitType;
725
726 #[test]
727 fn test_human_friendly_type() {
728 let len = KclValue::Number {
729 value: 1.0,
730 ty: NumericType::Known(UnitType::Length(UnitLen::Unknown)),
731 meta: vec![],
732 };
733 assert_eq!(len.human_friendly_type(), "a number (Length)".to_string());
734
735 let unknown = KclValue::Number {
736 value: 1.0,
737 ty: NumericType::Unknown,
738 meta: vec![],
739 };
740 assert_eq!(unknown.human_friendly_type(), "a number with unknown units".to_string());
741
742 let mm = KclValue::Number {
743 value: 1.0,
744 ty: NumericType::Known(UnitType::Length(UnitLen::Mm)),
745 meta: vec![],
746 };
747 assert_eq!(mm.human_friendly_type(), "a number (mm)".to_string());
748
749 let array1_mm = KclValue::HomArray {
750 value: vec![mm.clone()],
751 ty: RuntimeType::any(),
752 };
753 assert_eq!(
754 array1_mm.human_friendly_type(),
755 "an array of `number(mm)` with 1 value".to_string()
756 );
757
758 let array2_mm = KclValue::HomArray {
759 value: vec![mm.clone(), mm.clone()],
760 ty: RuntimeType::any(),
761 };
762 assert_eq!(
763 array2_mm.human_friendly_type(),
764 "an array of `number(mm)`, `number(mm)`".to_string()
765 );
766
767 let array3_mm = KclValue::HomArray {
768 value: vec![mm.clone(), mm.clone(), mm.clone()],
769 ty: RuntimeType::any(),
770 };
771 assert_eq!(
772 array3_mm.human_friendly_type(),
773 "an array of `number(mm)`, `number(mm)`, `number(mm)`".to_string()
774 );
775
776 let inches = KclValue::Number {
777 value: 1.0,
778 ty: NumericType::Known(UnitType::Length(UnitLen::Inches)),
779 meta: vec![],
780 };
781 let array4 = KclValue::HomArray {
782 value: vec![mm.clone(), mm.clone(), inches.clone(), mm.clone()],
783 ty: RuntimeType::any(),
784 };
785 assert_eq!(
786 array4.human_friendly_type(),
787 "an array of `number(mm)`, `number(mm)`, `number(in)`, ... with 4 values".to_string()
788 );
789
790 let empty_array = KclValue::HomArray {
791 value: vec![],
792 ty: RuntimeType::any(),
793 };
794 assert_eq!(empty_array.human_friendly_type(), "an empty array".to_string());
795
796 let array_nested = KclValue::HomArray {
797 value: vec![array2_mm.clone()],
798 ty: RuntimeType::any(),
799 };
800 assert_eq!(
801 array_nested.human_friendly_type(),
802 "an array of `[any; 2]` with 1 value".to_string()
803 );
804 }
805}