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