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