kcl_lib/execution/
kcl_value.rs

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