kcl_lib/execution/
kcl_value.rs

1use std::{collections::HashMap, fmt};
2
3use anyhow::Result;
4use schemars::JsonSchema;
5use serde::{Deserialize, Serialize};
6
7use super::{
8    memory::{self, EnvironmentRef},
9    MetaSettings,
10};
11use crate::{
12    errors::KclErrorDetails,
13    execution::{
14        ExecState, ExecutorContext, Face, Helix, ImportedGeometry, Metadata, Plane, Sketch, SketchSet, Solid, SolidSet,
15        TagIdentifier,
16    },
17    parsing::{
18        ast::types::{
19            DefaultParamVal, FunctionExpression, KclNone, Literal, LiteralValue, Node,
20            PrimitiveType as AstPrimitiveType, TagDeclarator, TagNode, Type,
21        },
22        token::NumericSuffix,
23    },
24    std::{args::Arg, StdFnProps},
25    CompilationError, KclError, ModuleId, SourceRange,
26};
27
28pub type KclObjectFields = HashMap<String, KclValue>;
29
30/// Any KCL value.
31#[derive(Debug, Clone, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
32#[ts(export)]
33#[serde(tag = "type")]
34pub enum KclValue {
35    Uuid {
36        value: ::uuid::Uuid,
37        #[serde(rename = "__meta")]
38        meta: Vec<Metadata>,
39    },
40    Bool {
41        value: bool,
42        #[serde(rename = "__meta")]
43        meta: Vec<Metadata>,
44    },
45    Number {
46        value: f64,
47        ty: NumericType,
48        #[serde(rename = "__meta")]
49        meta: Vec<Metadata>,
50    },
51    String {
52        value: String,
53        #[serde(rename = "__meta")]
54        meta: Vec<Metadata>,
55    },
56    MixedArray {
57        value: Vec<KclValue>,
58        #[serde(rename = "__meta")]
59        meta: Vec<Metadata>,
60    },
61    Object {
62        value: KclObjectFields,
63        #[serde(rename = "__meta")]
64        meta: Vec<Metadata>,
65    },
66    TagIdentifier(Box<TagIdentifier>),
67    TagDeclarator(crate::parsing::ast::types::BoxNode<TagDeclarator>),
68    Plane {
69        value: Box<Plane>,
70    },
71    Face {
72        value: Box<Face>,
73    },
74    Sketch {
75        value: Box<Sketch>,
76    },
77    Sketches {
78        value: Vec<Box<Sketch>>,
79    },
80    Solid {
81        value: Box<Solid>,
82    },
83    Solids {
84        value: Vec<Box<Solid>>,
85    },
86    Helix {
87        value: Box<Helix>,
88    },
89    ImportedGeometry(ImportedGeometry),
90    #[ts(skip)]
91    Function {
92        #[serde(skip)]
93        value: FunctionSource,
94        #[serde(rename = "__meta")]
95        meta: Vec<Metadata>,
96    },
97    Module {
98        value: ModuleId,
99        #[serde(rename = "__meta")]
100        meta: Vec<Metadata>,
101    },
102    #[ts(skip)]
103    Type {
104        #[serde(skip)]
105        value: Option<(PrimitiveType, StdFnProps)>,
106        #[serde(rename = "__meta")]
107        meta: Vec<Metadata>,
108    },
109    KclNone {
110        value: KclNone,
111        #[serde(rename = "__meta")]
112        meta: Vec<Metadata>,
113    },
114    // Only used for memory management. Should never be visible outside of the memory module.
115    Tombstone {
116        value: (),
117        #[serde(rename = "__meta")]
118        meta: Vec<Metadata>,
119    },
120}
121
122#[derive(Debug, Clone, PartialEq, Default)]
123pub enum FunctionSource {
124    #[default]
125    None,
126    Std {
127        func: crate::std::StdFn,
128        props: StdFnProps,
129    },
130    User {
131        ast: crate::parsing::ast::types::BoxNode<FunctionExpression>,
132        settings: MetaSettings,
133        memory: EnvironmentRef,
134    },
135}
136
137impl JsonSchema for FunctionSource {
138    fn schema_name() -> String {
139        "FunctionSource".to_owned()
140    }
141
142    fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema {
143        // TODO: Actually generate a reasonable schema.
144        gen.subschema_for::<()>()
145    }
146}
147
148impl From<SketchSet> for KclValue {
149    fn from(sg: SketchSet) -> Self {
150        match sg {
151            SketchSet::Sketch(value) => KclValue::Sketch { value },
152            SketchSet::Sketches(value) => KclValue::Sketches { value },
153        }
154    }
155}
156
157impl From<Vec<Box<Sketch>>> for KclValue {
158    fn from(sg: Vec<Box<Sketch>>) -> Self {
159        KclValue::Sketches { value: sg }
160    }
161}
162
163impl From<SolidSet> for KclValue {
164    fn from(eg: SolidSet) -> Self {
165        match eg {
166            SolidSet::Solid(eg) => KclValue::Solid { value: eg },
167            SolidSet::Solids(egs) => KclValue::Solids { value: egs },
168        }
169    }
170}
171
172impl From<Vec<Box<Solid>>> for KclValue {
173    fn from(eg: Vec<Box<Solid>>) -> Self {
174        if eg.len() == 1 {
175            KclValue::Solid { value: eg[0].clone() }
176        } else {
177            KclValue::Solids { value: eg }
178        }
179    }
180}
181impl From<KclValue> for Vec<SourceRange> {
182    fn from(item: KclValue) -> Self {
183        match item {
184            KclValue::TagDeclarator(t) => vec![SourceRange::new(t.start, t.end, t.module_id)],
185            KclValue::TagIdentifier(t) => to_vec_sr(&t.meta),
186            KclValue::Solid { value } => to_vec_sr(&value.meta),
187            KclValue::Solids { value } => value.iter().flat_map(|eg| to_vec_sr(&eg.meta)).collect(),
188            KclValue::Sketch { value } => to_vec_sr(&value.meta),
189            KclValue::Sketches { value } => value.iter().flat_map(|eg| to_vec_sr(&eg.meta)).collect(),
190            KclValue::Helix { value } => to_vec_sr(&value.meta),
191            KclValue::ImportedGeometry(i) => to_vec_sr(&i.meta),
192            KclValue::Function { meta, .. } => to_vec_sr(&meta),
193            KclValue::Plane { value } => to_vec_sr(&value.meta),
194            KclValue::Face { value } => to_vec_sr(&value.meta),
195            KclValue::Bool { meta, .. } => to_vec_sr(&meta),
196            KclValue::Number { meta, .. } => to_vec_sr(&meta),
197            KclValue::String { meta, .. } => to_vec_sr(&meta),
198            KclValue::MixedArray { meta, .. } => to_vec_sr(&meta),
199            KclValue::Object { meta, .. } => to_vec_sr(&meta),
200            KclValue::Module { meta, .. } => to_vec_sr(&meta),
201            KclValue::Uuid { meta, .. } => to_vec_sr(&meta),
202            KclValue::Type { meta, .. } => to_vec_sr(&meta),
203            KclValue::KclNone { meta, .. } => to_vec_sr(&meta),
204            KclValue::Tombstone { .. } => unreachable!("Tombstone SourceRange"),
205        }
206    }
207}
208
209fn to_vec_sr(meta: &[Metadata]) -> Vec<SourceRange> {
210    meta.iter().map(|m| m.source_range).collect()
211}
212
213impl From<&KclValue> for Vec<SourceRange> {
214    fn from(item: &KclValue) -> Self {
215        match item {
216            KclValue::TagDeclarator(t) => vec![SourceRange::new(t.start, t.end, t.module_id)],
217            KclValue::TagIdentifier(t) => to_vec_sr(&t.meta),
218            KclValue::Solid { value } => to_vec_sr(&value.meta),
219            KclValue::Solids { value } => value.iter().flat_map(|eg| to_vec_sr(&eg.meta)).collect(),
220            KclValue::Sketch { value } => to_vec_sr(&value.meta),
221            KclValue::Sketches { value } => value.iter().flat_map(|eg| to_vec_sr(&eg.meta)).collect(),
222            KclValue::Helix { value } => to_vec_sr(&value.meta),
223            KclValue::ImportedGeometry(i) => to_vec_sr(&i.meta),
224            KclValue::Function { meta, .. } => to_vec_sr(meta),
225            KclValue::Plane { value } => to_vec_sr(&value.meta),
226            KclValue::Face { value } => to_vec_sr(&value.meta),
227            KclValue::Bool { meta, .. } => to_vec_sr(meta),
228            KclValue::Number { meta, .. } => to_vec_sr(meta),
229            KclValue::String { meta, .. } => to_vec_sr(meta),
230            KclValue::Uuid { meta, .. } => to_vec_sr(meta),
231            KclValue::MixedArray { meta, .. } => to_vec_sr(meta),
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            KclValue::Tombstone { .. } => unreachable!("Tombstone &SourceRange"),
237        }
238    }
239}
240
241impl From<&KclValue> for SourceRange {
242    fn from(item: &KclValue) -> Self {
243        let v: Vec<_> = item.into();
244        v.into_iter().next().unwrap_or_default()
245    }
246}
247
248impl KclValue {
249    pub(crate) fn metadata(&self) -> Vec<Metadata> {
250        match self {
251            KclValue::Uuid { value: _, meta } => meta.clone(),
252            KclValue::Bool { value: _, meta } => meta.clone(),
253            KclValue::Number { meta, .. } => meta.clone(),
254            KclValue::String { value: _, meta } => meta.clone(),
255            KclValue::MixedArray { value: _, meta } => meta.clone(),
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::Sketches { value } => value.iter().flat_map(|sketch| &sketch.meta).copied().collect(),
263            KclValue::Solid { value } => value.meta.clone(),
264            KclValue::Solids { value } => value.iter().flat_map(|sketch| &sketch.meta).copied().collect(),
265            KclValue::Helix { value } => value.meta.clone(),
266            KclValue::ImportedGeometry(x) => x.meta.clone(),
267            KclValue::Function { meta, .. } => meta.clone(),
268            KclValue::Module { meta, .. } => meta.clone(),
269            KclValue::KclNone { meta, .. } => meta.clone(),
270            KclValue::Type { meta, .. } => meta.clone(),
271            KclValue::Tombstone { .. } => unreachable!("Tombstone Metadata"),
272        }
273    }
274
275    pub(crate) fn function_def_source_range(&self) -> Option<SourceRange> {
276        let KclValue::Function {
277            value: FunctionSource::User { ast, .. },
278            ..
279        } = self
280        else {
281            return None;
282        };
283        // TODO: It would be nice if we could extract the source range starting
284        // at the fn, but that's the variable declaration.
285        Some(ast.as_source_range())
286    }
287
288    pub(crate) fn get_solid_set(&self) -> Result<SolidSet> {
289        match self {
290            KclValue::Solid { value } => Ok(SolidSet::Solid(value.clone())),
291            KclValue::Solids { value } => Ok(SolidSet::Solids(value.clone())),
292            KclValue::MixedArray { value, .. } => {
293                let solids: Vec<_> = value
294                    .iter()
295                    .enumerate()
296                    .map(|(i, v)| {
297                        v.as_solid().map(|v| v.to_owned()).map(Box::new).ok_or_else(|| {
298                            anyhow::anyhow!(
299                                "expected this array to only contain solids, but element {i} was actually {}",
300                                v.human_friendly_type()
301                            )
302                        })
303                    })
304                    .collect::<Result<_, _>>()?;
305                Ok(SolidSet::Solids(solids))
306            }
307            _ => anyhow::bail!("Not a solid or solids: {:?}", self),
308        }
309    }
310
311    #[allow(unused)]
312    pub(crate) fn none() -> Self {
313        Self::KclNone {
314            value: Default::default(),
315            meta: Default::default(),
316        }
317    }
318
319    /// Human readable type name used in error messages.  Should not be relied
320    /// on for program logic.
321    pub(crate) fn human_friendly_type(&self) -> &'static str {
322        match self {
323            KclValue::Uuid { .. } => "Unique ID (uuid)",
324            KclValue::TagDeclarator(_) => "TagDeclarator",
325            KclValue::TagIdentifier(_) => "TagIdentifier",
326            KclValue::Solid { .. } => "Solid",
327            KclValue::Solids { .. } => "Solids",
328            KclValue::Sketch { .. } => "Sketch",
329            KclValue::Sketches { .. } => "Sketches",
330            KclValue::Helix { .. } => "Helix",
331            KclValue::ImportedGeometry(_) => "ImportedGeometry",
332            KclValue::Function { .. } => "Function",
333            KclValue::Plane { .. } => "Plane",
334            KclValue::Face { .. } => "Face",
335            KclValue::Bool { .. } => "boolean (true/false value)",
336            KclValue::Number { .. } => "number",
337            KclValue::String { .. } => "string (text)",
338            KclValue::MixedArray { .. } => "array (list)",
339            KclValue::Object { .. } => "object",
340            KclValue::Module { .. } => "module",
341            KclValue::Type { .. } => "type",
342            KclValue::KclNone { .. } => "None",
343            KclValue::Tombstone { .. } => "TOMBSTONE",
344        }
345    }
346
347    pub(crate) fn from_literal(literal: Node<Literal>, settings: &MetaSettings) -> Self {
348        let meta = vec![literal.metadata()];
349        match literal.inner.value {
350            LiteralValue::Number { value, suffix } => KclValue::Number {
351                value,
352                meta,
353                ty: NumericType::from_parsed(suffix, settings),
354            },
355            LiteralValue::String(value) => KclValue::String { value, meta },
356            LiteralValue::Bool(value) => KclValue::Bool { value, meta },
357        }
358    }
359
360    pub(crate) fn from_default_param(param: DefaultParamVal, settings: &MetaSettings) -> Self {
361        match param {
362            DefaultParamVal::Literal(lit) => Self::from_literal(lit, settings),
363            DefaultParamVal::KclNone(none) => KclValue::KclNone {
364                value: none,
365                meta: Default::default(),
366            },
367        }
368    }
369
370    pub(crate) fn map_env_ref(&self, env_map: &HashMap<EnvironmentRef, EnvironmentRef>) -> Self {
371        let mut result = self.clone();
372        if let KclValue::Function {
373            value: FunctionSource::User { ref mut memory, .. },
374            ..
375        } = result
376        {
377            if let Some(new) = env_map.get(memory) {
378                *memory = *new;
379            }
380        }
381        result
382    }
383
384    /// Put the number into a KCL value.
385    pub const fn from_number(f: f64, meta: Vec<Metadata>) -> Self {
386        Self::Number {
387            value: f,
388            meta,
389            ty: NumericType::Unknown,
390        }
391    }
392
393    pub const fn from_number_with_type(f: f64, ty: NumericType, meta: Vec<Metadata>) -> Self {
394        Self::Number { value: f, meta, ty }
395    }
396
397    /// Put the point into a KCL value.
398    pub fn from_point2d(p: [f64; 2], ty: NumericType, meta: Vec<Metadata>) -> Self {
399        Self::MixedArray {
400            value: vec![
401                Self::Number {
402                    value: p[0],
403                    meta: meta.clone(),
404                    ty: ty.clone(),
405                },
406                Self::Number {
407                    value: p[1],
408                    meta: meta.clone(),
409                    ty,
410                },
411            ],
412            meta,
413        }
414    }
415
416    pub(crate) fn as_usize(&self) -> Option<usize> {
417        match self {
418            KclValue::Number { value, .. } => crate::try_f64_to_usize(*value),
419            _ => None,
420        }
421    }
422
423    pub fn as_int(&self) -> Option<i64> {
424        match self {
425            KclValue::Number { value, .. } => crate::try_f64_to_i64(*value),
426            _ => None,
427        }
428    }
429
430    pub fn as_object(&self) -> Option<&KclObjectFields> {
431        if let KclValue::Object { value, meta: _ } = &self {
432            Some(value)
433        } else {
434            None
435        }
436    }
437
438    pub fn into_object(self) -> Option<KclObjectFields> {
439        if let KclValue::Object { value, meta: _ } = self {
440            Some(value)
441        } else {
442            None
443        }
444    }
445
446    pub fn as_str(&self) -> Option<&str> {
447        if let KclValue::String { value, meta: _ } = &self {
448            Some(value)
449        } else {
450            None
451        }
452    }
453
454    pub fn as_array(&self) -> Option<&[KclValue]> {
455        if let KclValue::MixedArray { value, meta: _ } = &self {
456            Some(value)
457        } else {
458            None
459        }
460    }
461
462    pub fn as_point2d(&self) -> Option<[f64; 2]> {
463        let arr = self.as_array()?;
464        if arr.len() != 2 {
465            return None;
466        }
467        let x = arr[0].as_f64()?;
468        let y = arr[1].as_f64()?;
469        Some([x, y])
470    }
471
472    pub fn as_uuid(&self) -> Option<uuid::Uuid> {
473        if let KclValue::Uuid { value, meta: _ } = &self {
474            Some(*value)
475        } else {
476            None
477        }
478    }
479
480    pub fn as_plane(&self) -> Option<&Plane> {
481        if let KclValue::Plane { value } = &self {
482            Some(value)
483        } else {
484            None
485        }
486    }
487
488    pub fn as_solid(&self) -> Option<&Solid> {
489        if let KclValue::Solid { value } = &self {
490            Some(value)
491        } else {
492            None
493        }
494    }
495
496    pub fn as_sketch(&self) -> Option<&Sketch> {
497        if let KclValue::Sketch { value } = self {
498            Some(value)
499        } else {
500            None
501        }
502    }
503
504    pub fn as_f64(&self) -> Option<f64> {
505        if let KclValue::Number { value, .. } = &self {
506            Some(*value)
507        } else {
508            None
509        }
510    }
511
512    pub fn as_bool(&self) -> Option<bool> {
513        if let KclValue::Bool { value, meta: _ } = &self {
514            Some(*value)
515        } else {
516            None
517        }
518    }
519
520    /// If this value fits in a u32, return it.
521    pub fn get_u32(&self, source_ranges: Vec<SourceRange>) -> Result<u32, KclError> {
522        let u = self.as_int().and_then(|n| u64::try_from(n).ok()).ok_or_else(|| {
523            KclError::Semantic(KclErrorDetails {
524                message: "Expected an integer >= 0".to_owned(),
525                source_ranges: source_ranges.clone(),
526            })
527        })?;
528        u32::try_from(u).map_err(|_| {
529            KclError::Semantic(KclErrorDetails {
530                message: "Number was too big".to_owned(),
531                source_ranges,
532            })
533        })
534    }
535
536    /// If this value is of type function, return it.
537    pub fn get_function(&self) -> Option<&FunctionSource> {
538        match self {
539            KclValue::Function { value, .. } => Some(value),
540            _ => None,
541        }
542    }
543
544    /// Get a tag identifier from a memory item.
545    pub fn get_tag_identifier(&self) -> Result<TagIdentifier, KclError> {
546        match self {
547            KclValue::TagIdentifier(t) => Ok(*t.clone()),
548            _ => Err(KclError::Semantic(KclErrorDetails {
549                message: format!("Not a tag identifier: {:?}", self),
550                source_ranges: self.clone().into(),
551            })),
552        }
553    }
554
555    /// Get a tag declarator from a memory item.
556    pub fn get_tag_declarator(&self) -> Result<TagNode, KclError> {
557        match self {
558            KclValue::TagDeclarator(t) => Ok((**t).clone()),
559            _ => Err(KclError::Semantic(KclErrorDetails {
560                message: format!("Not a tag declarator: {:?}", self),
561                source_ranges: self.clone().into(),
562            })),
563        }
564    }
565
566    /// Get an optional tag from a memory item.
567    pub fn get_tag_declarator_opt(&self) -> Result<Option<TagNode>, KclError> {
568        match self {
569            KclValue::TagDeclarator(t) => Ok(Some((**t).clone())),
570            _ => Err(KclError::Semantic(KclErrorDetails {
571                message: format!("Not a tag declarator: {:?}", self),
572                source_ranges: self.clone().into(),
573            })),
574        }
575    }
576
577    /// If this KCL value is a bool, retrieve it.
578    pub fn get_bool(&self) -> Result<bool, KclError> {
579        let Self::Bool { value: b, .. } = self else {
580            return Err(KclError::Type(KclErrorDetails {
581                source_ranges: self.into(),
582                message: format!("Expected bool, found {}", self.human_friendly_type()),
583            }));
584        };
585        Ok(*b)
586    }
587
588    /// True if `self` has a type which is a subtype of `ty` without coercion.
589    pub fn has_type(&self, ty: &RuntimeType) -> bool {
590        let Some(self_ty) = self.principal_type() else {
591            return false;
592        };
593
594        self_ty.subtype(ty)
595    }
596
597    pub fn principal_type(&self) -> Option<RuntimeType> {
598        match self {
599            KclValue::Bool { .. } => Some(RuntimeType::Primitive(PrimitiveType::Boolean)),
600            KclValue::Number { ty, .. } => Some(RuntimeType::Primitive(PrimitiveType::Number(ty.clone()))),
601            KclValue::String { .. } => Some(RuntimeType::Primitive(PrimitiveType::String)),
602            KclValue::Object { value, .. } => {
603                let properties = value
604                    .iter()
605                    .map(|(k, v)| v.principal_type().map(|t| (k.clone(), t)))
606                    .collect::<Option<Vec<_>>>()?;
607                Some(RuntimeType::Object(properties))
608            }
609            KclValue::Plane { .. } => Some(RuntimeType::Primitive(PrimitiveType::Plane)),
610            KclValue::Sketch { .. } => Some(RuntimeType::Primitive(PrimitiveType::Sketch)),
611            KclValue::Sketches { .. } => Some(RuntimeType::Array(PrimitiveType::Sketch)),
612            KclValue::Solid { .. } => Some(RuntimeType::Primitive(PrimitiveType::Solid)),
613            KclValue::Solids { .. } => Some(RuntimeType::Array(PrimitiveType::Solid)),
614            KclValue::MixedArray { value, .. } => Some(RuntimeType::Tuple(
615                value
616                    .iter()
617                    .map(|v| v.principal_type().and_then(RuntimeType::primitive))
618                    .collect::<Option<Vec<_>>>()?,
619            )),
620            KclValue::Face { .. } => None,
621            KclValue::Helix { .. }
622            | KclValue::ImportedGeometry(..)
623            | KclValue::Function { .. }
624            | KclValue::Module { .. }
625            | KclValue::TagIdentifier(_)
626            | KclValue::TagDeclarator(_)
627            | KclValue::KclNone { .. }
628            | KclValue::Type { .. }
629            | KclValue::Uuid { .. }
630            | KclValue::Tombstone { .. } => None,
631        }
632    }
633
634    /// If this memory item is a function, call it with the given arguments, return its val as Ok.
635    /// If it's not a function, return Err.
636    pub async fn call_fn(
637        &self,
638        args: Vec<Arg>,
639        exec_state: &mut ExecState,
640        ctx: ExecutorContext,
641        source_range: SourceRange,
642    ) -> Result<Option<KclValue>, KclError> {
643        match self {
644            KclValue::Function {
645                value: FunctionSource::Std { func, props },
646                ..
647            } => {
648                if props.deprecated {
649                    exec_state.warn(CompilationError::err(
650                        source_range,
651                        format!(
652                            "`{}` is deprecated, see the docs for a recommended replacement",
653                            props.name
654                        ),
655                    ));
656                }
657                exec_state.mut_stack().push_new_env_for_rust_call();
658                let args = crate::std::Args::new(
659                    args,
660                    source_range,
661                    ctx.clone(),
662                    exec_state.mod_local.pipe_value.clone().map(Arg::synthetic),
663                );
664                let result = func(exec_state, args).await.map(Some);
665                exec_state.mut_stack().pop_env();
666                result
667            }
668            KclValue::Function {
669                value: FunctionSource::User { ast, memory, .. },
670                ..
671            } => crate::execution::exec_ast::call_user_defined_function(args, *memory, ast, exec_state, &ctx).await,
672            _ => Err(KclError::Semantic(KclErrorDetails {
673                message: "cannot call this because it isn't a function".to_string(),
674                source_ranges: vec![source_range],
675            })),
676        }
677    }
678
679    /// If this is a function, call it by applying keyword arguments.
680    /// If it's not a function, returns an error.
681    pub async fn call_fn_kw(
682        &self,
683        args: crate::std::Args,
684        exec_state: &mut ExecState,
685        ctx: ExecutorContext,
686        callsite: SourceRange,
687    ) -> Result<Option<KclValue>, KclError> {
688        match self {
689            KclValue::Function {
690                value: FunctionSource::Std { func: _, props },
691                ..
692            } => {
693                if props.deprecated {
694                    exec_state.warn(CompilationError::err(
695                        callsite,
696                        format!(
697                            "`{}` is deprecated, see the docs for a recommended replacement",
698                            props.name
699                        ),
700                    ));
701                }
702                todo!("Implement KCL stdlib fns with keyword args");
703            }
704            KclValue::Function {
705                value: FunctionSource::User { ast, memory, .. },
706                ..
707            } => {
708                crate::execution::exec_ast::call_user_defined_function_kw(args.kw_args, *memory, ast, exec_state, &ctx)
709                    .await
710            }
711            _ => Err(KclError::Semantic(KclErrorDetails {
712                message: "cannot call this because it isn't a function".to_string(),
713                source_ranges: vec![callsite],
714            })),
715        }
716    }
717
718    pub fn value_str(&self) -> Option<String> {
719        match self {
720            KclValue::Bool { value, .. } => Some(format!("{value}")),
721            KclValue::Number { value, .. } => Some(format!("{value}")),
722            KclValue::String { value, .. } => Some(format!("'{value}'")),
723            KclValue::Uuid { value, .. } => Some(format!("{value}")),
724            KclValue::TagDeclarator(tag) => Some(format!("${}", tag.name)),
725            KclValue::TagIdentifier(tag) => Some(format!("${}", tag.value)),
726            // TODO better Array and Object stringification
727            KclValue::MixedArray { .. } => Some("[...]".to_owned()),
728            KclValue::Object { .. } => Some("{ ... }".to_owned()),
729            KclValue::Module { .. }
730            | KclValue::Solid { .. }
731            | KclValue::Solids { .. }
732            | KclValue::Sketch { .. }
733            | KclValue::Sketches { .. }
734            | KclValue::Helix { .. }
735            | KclValue::ImportedGeometry(_)
736            | KclValue::Function { .. }
737            | KclValue::Plane { .. }
738            | KclValue::Face { .. }
739            | KclValue::KclNone { .. }
740            | KclValue::Type { .. }
741            | KclValue::Tombstone { .. } => None,
742        }
743    }
744}
745
746#[derive(Debug, Clone, PartialEq)]
747pub enum RuntimeType {
748    Primitive(PrimitiveType),
749    Array(PrimitiveType),
750    Tuple(Vec<PrimitiveType>),
751    Object(Vec<(String, RuntimeType)>),
752}
753
754impl RuntimeType {
755    pub fn from_parsed(
756        value: Type,
757        exec_state: &mut ExecState,
758        source_range: SourceRange,
759    ) -> Result<Option<Self>, CompilationError> {
760        Ok(match value {
761            Type::Primitive(pt) => {
762                PrimitiveType::from_parsed(pt, exec_state, source_range)?.map(RuntimeType::Primitive)
763            }
764            Type::Array(pt) => PrimitiveType::from_parsed(pt, exec_state, source_range)?.map(RuntimeType::Array),
765            Type::Object { properties } => properties
766                .into_iter()
767                .map(|p| {
768                    let pt = match p.type_ {
769                        Some(t) => t,
770                        None => return Ok(None),
771                    };
772                    Ok(RuntimeType::from_parsed(pt.inner, exec_state, source_range)?
773                        .map(|ty| (p.identifier.inner.name, ty)))
774                })
775                .collect::<Result<Option<Vec<_>>, CompilationError>>()?
776                .map(RuntimeType::Object),
777        })
778    }
779
780    // Subtype with no coercion, including refining numeric types.
781    fn subtype(&self, sup: &RuntimeType) -> bool {
782        use RuntimeType::*;
783
784        match (self, sup) {
785            // TODO arrays could be covariant
786            (Primitive(t1), Primitive(t2)) | (Array(t1), Array(t2)) => t1 == t2,
787            (Tuple(t1), Tuple(t2)) => t1 == t2,
788            (Tuple(t1), Array(t2)) => t1.iter().all(|t| t == t2),
789            // TODO record subtyping - subtype can be larger, fields can be covariant.
790            (Object(t1), Object(t2)) => t1 == t2,
791            _ => false,
792        }
793    }
794
795    fn primitive(self) -> Option<PrimitiveType> {
796        match self {
797            RuntimeType::Primitive(t) => Some(t),
798            _ => None,
799        }
800    }
801}
802
803impl fmt::Display for RuntimeType {
804    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
805        match self {
806            RuntimeType::Primitive(t) => t.fmt(f),
807            RuntimeType::Array(t) => write!(f, "[{t}]"),
808            RuntimeType::Tuple(ts) => write!(
809                f,
810                "[{}]",
811                ts.iter().map(|t| t.to_string()).collect::<Vec<_>>().join(", ")
812            ),
813            RuntimeType::Object(items) => write!(
814                f,
815                "{{ {} }}",
816                items
817                    .iter()
818                    .map(|(n, t)| format!("{n}: {t}"))
819                    .collect::<Vec<_>>()
820                    .join(", ")
821            ),
822        }
823    }
824}
825
826#[derive(Debug, Clone, PartialEq)]
827pub enum PrimitiveType {
828    Number(NumericType),
829    String,
830    Boolean,
831    Sketch,
832    Solid,
833    Plane,
834}
835
836impl PrimitiveType {
837    fn from_parsed(
838        value: AstPrimitiveType,
839        exec_state: &mut ExecState,
840        source_range: SourceRange,
841    ) -> Result<Option<Self>, CompilationError> {
842        Ok(match value {
843            AstPrimitiveType::String => Some(PrimitiveType::String),
844            AstPrimitiveType::Boolean => Some(PrimitiveType::Boolean),
845            AstPrimitiveType::Number(suffix) => Some(PrimitiveType::Number(NumericType::from_parsed(
846                suffix,
847                &exec_state.mod_local.settings,
848            ))),
849            AstPrimitiveType::Named(name) => {
850                let ty_val = exec_state
851                    .stack()
852                    .get(&format!("{}{}", memory::TYPE_PREFIX, name.name), source_range)
853                    .map_err(|_| CompilationError::err(source_range, format!("Unknown type: {}", name.name)))?;
854
855                let (ty, _) = match ty_val {
856                    KclValue::Type { value: Some(ty), .. } => ty,
857                    _ => unreachable!(),
858                };
859
860                Some(ty.clone())
861            }
862            _ => None,
863        })
864    }
865}
866
867impl fmt::Display for PrimitiveType {
868    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
869        match self {
870            PrimitiveType::Number(NumericType::Known(unit)) => write!(f, "number({unit})"),
871            PrimitiveType::Number(_) => write!(f, "number"),
872            PrimitiveType::String => write!(f, "string"),
873            PrimitiveType::Boolean => write!(f, "bool"),
874            PrimitiveType::Sketch => write!(f, "Sketch"),
875            PrimitiveType::Solid => write!(f, "Solid"),
876            PrimitiveType::Plane => write!(f, "Plane"),
877        }
878    }
879}
880
881#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
882#[ts(export)]
883#[serde(tag = "type")]
884pub enum NumericType {
885    // Specified by the user (directly or indirectly)
886    Known(UnitType),
887    // Unspecified, using defaults
888    Default { len: UnitLen, angle: UnitAngle },
889    // Exceeded the ability of the type system to track.
890    Unknown,
891    // Type info has been explicitly cast away.
892    Any,
893}
894
895impl NumericType {
896    pub fn count() -> Self {
897        NumericType::Known(UnitType::Count)
898    }
899
900    /// Combine two types when we expect them to be equal.
901    pub fn combine_eq(self, other: &NumericType) -> NumericType {
902        if &self == other {
903            self
904        } else {
905            NumericType::Unknown
906        }
907    }
908
909    /// Combine n types when we expect them to be equal.
910    ///
911    /// Precondition: tys.len() > 0
912    pub fn combine_n_eq(tys: &[NumericType]) -> NumericType {
913        let ty0 = tys[0].clone();
914        for t in &tys[1..] {
915            if t != &ty0 {
916                return NumericType::Unknown;
917            }
918        }
919        ty0
920    }
921
922    /// Combine two types in addition-like operations.
923    pub fn combine_add(a: NumericType, b: NumericType) -> NumericType {
924        if a == b {
925            return a;
926        }
927        NumericType::Unknown
928    }
929
930    /// Combine two types in multiplication-like operations.
931    pub fn combine_mul(a: NumericType, b: NumericType) -> NumericType {
932        if a == NumericType::count() {
933            return b;
934        }
935        if b == NumericType::count() {
936            return a;
937        }
938        NumericType::Unknown
939    }
940
941    /// Combine two types in division-like operations.
942    pub fn combine_div(a: NumericType, b: NumericType) -> NumericType {
943        if b == NumericType::count() {
944            return a;
945        }
946        NumericType::Unknown
947    }
948
949    pub fn from_parsed(suffix: NumericSuffix, settings: &super::MetaSettings) -> Self {
950        match suffix {
951            NumericSuffix::None => NumericType::Default {
952                len: settings.default_length_units,
953                angle: settings.default_angle_units,
954            },
955            NumericSuffix::Count => NumericType::Known(UnitType::Count),
956            NumericSuffix::Mm => NumericType::Known(UnitType::Length(UnitLen::Mm)),
957            NumericSuffix::Cm => NumericType::Known(UnitType::Length(UnitLen::Cm)),
958            NumericSuffix::M => NumericType::Known(UnitType::Length(UnitLen::M)),
959            NumericSuffix::Inch => NumericType::Known(UnitType::Length(UnitLen::Inches)),
960            NumericSuffix::Ft => NumericType::Known(UnitType::Length(UnitLen::Feet)),
961            NumericSuffix::Yd => NumericType::Known(UnitType::Length(UnitLen::Yards)),
962            NumericSuffix::Deg => NumericType::Known(UnitType::Angle(UnitAngle::Degrees)),
963            NumericSuffix::Rad => NumericType::Known(UnitType::Angle(UnitAngle::Radians)),
964        }
965    }
966}
967
968impl From<UnitLen> for NumericType {
969    fn from(value: UnitLen) -> Self {
970        NumericType::Known(UnitType::Length(value))
971    }
972}
973
974impl From<UnitAngle> for NumericType {
975    fn from(value: UnitAngle) -> Self {
976        NumericType::Known(UnitType::Angle(value))
977    }
978}
979
980#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, ts_rs::TS, JsonSchema)]
981#[ts(export)]
982#[serde(tag = "type")]
983pub enum UnitType {
984    Count,
985    Length(UnitLen),
986    Angle(UnitAngle),
987}
988
989impl std::fmt::Display for UnitType {
990    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
991        match self {
992            UnitType::Count => write!(f, "_"),
993            UnitType::Length(l) => l.fmt(f),
994            UnitType::Angle(a) => a.fmt(f),
995        }
996    }
997}
998
999// TODO called UnitLen so as not to clash with UnitLength in settings)
1000/// A unit of length.
1001#[derive(Debug, Default, Clone, Copy, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Eq)]
1002#[ts(export)]
1003#[serde(tag = "type")]
1004pub enum UnitLen {
1005    #[default]
1006    Mm,
1007    Cm,
1008    M,
1009    Inches,
1010    Feet,
1011    Yards,
1012}
1013
1014impl std::fmt::Display for UnitLen {
1015    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1016        match self {
1017            UnitLen::Mm => write!(f, "mm"),
1018            UnitLen::Cm => write!(f, "cm"),
1019            UnitLen::M => write!(f, "m"),
1020            UnitLen::Inches => write!(f, "in"),
1021            UnitLen::Feet => write!(f, "ft"),
1022            UnitLen::Yards => write!(f, "yd"),
1023        }
1024    }
1025}
1026
1027impl TryFrom<NumericSuffix> for UnitLen {
1028    type Error = ();
1029
1030    fn try_from(suffix: NumericSuffix) -> std::result::Result<Self, Self::Error> {
1031        match suffix {
1032            NumericSuffix::Mm => Ok(Self::Mm),
1033            NumericSuffix::Cm => Ok(Self::Cm),
1034            NumericSuffix::M => Ok(Self::M),
1035            NumericSuffix::Inch => Ok(Self::Inches),
1036            NumericSuffix::Ft => Ok(Self::Feet),
1037            NumericSuffix::Yd => Ok(Self::Yards),
1038            _ => Err(()),
1039        }
1040    }
1041}
1042
1043impl From<crate::UnitLength> for UnitLen {
1044    fn from(unit: crate::UnitLength) -> Self {
1045        match unit {
1046            crate::UnitLength::Cm => UnitLen::Cm,
1047            crate::UnitLength::Ft => UnitLen::Feet,
1048            crate::UnitLength::In => UnitLen::Inches,
1049            crate::UnitLength::M => UnitLen::M,
1050            crate::UnitLength::Mm => UnitLen::Mm,
1051            crate::UnitLength::Yd => UnitLen::Yards,
1052        }
1053    }
1054}
1055
1056impl From<UnitLen> for crate::UnitLength {
1057    fn from(unit: UnitLen) -> Self {
1058        match unit {
1059            UnitLen::Cm => crate::UnitLength::Cm,
1060            UnitLen::Feet => crate::UnitLength::Ft,
1061            UnitLen::Inches => crate::UnitLength::In,
1062            UnitLen::M => crate::UnitLength::M,
1063            UnitLen::Mm => crate::UnitLength::Mm,
1064            UnitLen::Yards => crate::UnitLength::Yd,
1065        }
1066    }
1067}
1068
1069impl From<UnitLen> for kittycad_modeling_cmds::units::UnitLength {
1070    fn from(unit: UnitLen) -> Self {
1071        match unit {
1072            UnitLen::Cm => kittycad_modeling_cmds::units::UnitLength::Centimeters,
1073            UnitLen::Feet => kittycad_modeling_cmds::units::UnitLength::Feet,
1074            UnitLen::Inches => kittycad_modeling_cmds::units::UnitLength::Inches,
1075            UnitLen::M => kittycad_modeling_cmds::units::UnitLength::Meters,
1076            UnitLen::Mm => kittycad_modeling_cmds::units::UnitLength::Millimeters,
1077            UnitLen::Yards => kittycad_modeling_cmds::units::UnitLength::Yards,
1078        }
1079    }
1080}
1081
1082/// A unit of angle.
1083#[derive(Debug, Default, Clone, Copy, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Eq)]
1084#[ts(export)]
1085#[serde(tag = "type")]
1086pub enum UnitAngle {
1087    #[default]
1088    Degrees,
1089    Radians,
1090}
1091
1092impl std::fmt::Display for UnitAngle {
1093    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1094        match self {
1095            UnitAngle::Degrees => write!(f, "deg"),
1096            UnitAngle::Radians => write!(f, "rad"),
1097        }
1098    }
1099}
1100
1101impl TryFrom<NumericSuffix> for UnitAngle {
1102    type Error = ();
1103
1104    fn try_from(suffix: NumericSuffix) -> std::result::Result<Self, Self::Error> {
1105        match suffix {
1106            NumericSuffix::Deg => Ok(Self::Degrees),
1107            NumericSuffix::Rad => Ok(Self::Radians),
1108            _ => Err(()),
1109        }
1110    }
1111}