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