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