1use std::{collections::HashMap, fmt};
2
3use anyhow::Result;
4use schemars::JsonSchema;
5use serde::{Deserialize, Serialize};
6
7use crate::{
8 execution::{
9 kcl_value::{KclValue, TypeDef},
10 memory::{self},
11 ExecState, Plane, Point3d,
12 },
13 parsing::{
14 ast::types::{PrimitiveType as AstPrimitiveType, Type},
15 token::NumericSuffix,
16 },
17 std::args::FromKclValue,
18 CompilationError, SourceRange,
19};
20
21#[derive(Debug, Clone, PartialEq)]
22pub enum RuntimeType {
23 Primitive(PrimitiveType),
24 Array(Box<RuntimeType>, ArrayLen),
25 Union(Vec<RuntimeType>),
26 Tuple(Vec<RuntimeType>),
27 Object(Vec<(String, RuntimeType)>),
28}
29
30impl RuntimeType {
31 pub fn sketch() -> Self {
32 RuntimeType::Primitive(PrimitiveType::Sketch)
33 }
34
35 pub fn sketches() -> Self {
37 RuntimeType::Array(
38 Box::new(RuntimeType::Primitive(PrimitiveType::Sketch)),
39 ArrayLen::NonEmpty,
40 )
41 }
42
43 pub fn solids() -> Self {
45 RuntimeType::Array(
46 Box::new(RuntimeType::Primitive(PrimitiveType::Solid)),
47 ArrayLen::NonEmpty,
48 )
49 }
50
51 pub fn solid() -> Self {
52 RuntimeType::Primitive(PrimitiveType::Solid)
53 }
54
55 pub fn imported() -> Self {
56 RuntimeType::Primitive(PrimitiveType::ImportedGeometry)
57 }
58
59 pub fn from_parsed(
60 value: Type,
61 exec_state: &mut ExecState,
62 source_range: SourceRange,
63 ) -> Result<Self, CompilationError> {
64 match value {
65 Type::Primitive(pt) => Self::from_parsed_primitive(pt, exec_state, source_range),
66 Type::Array { ty, len } => {
67 Self::from_parsed_primitive(ty, exec_state, source_range).map(|t| RuntimeType::Array(Box::new(t), len))
68 }
69 Type::Union { tys } => tys
70 .into_iter()
71 .map(|t| Self::from_parsed_primitive(t.inner, exec_state, source_range))
72 .collect::<Result<Vec<_>, CompilationError>>()
73 .map(RuntimeType::Union),
74 Type::Object { properties } => properties
75 .into_iter()
76 .map(|p| {
77 RuntimeType::from_parsed(p.type_.unwrap().inner, exec_state, source_range)
78 .map(|ty| (p.identifier.inner.name, ty))
79 })
80 .collect::<Result<Vec<_>, CompilationError>>()
81 .map(RuntimeType::Object),
82 }
83 }
84
85 fn from_parsed_primitive(
86 value: AstPrimitiveType,
87 exec_state: &mut ExecState,
88 source_range: SourceRange,
89 ) -> Result<Self, CompilationError> {
90 Ok(match value {
91 AstPrimitiveType::String => RuntimeType::Primitive(PrimitiveType::String),
92 AstPrimitiveType::Boolean => RuntimeType::Primitive(PrimitiveType::Boolean),
93 AstPrimitiveType::Number(suffix) => RuntimeType::Primitive(PrimitiveType::Number(
94 NumericType::from_parsed(suffix, &exec_state.mod_local.settings),
95 )),
96 AstPrimitiveType::Named(name) => {
97 let ty_val = exec_state
98 .stack()
99 .get(&format!("{}{}", memory::TYPE_PREFIX, name.name), source_range)
100 .map_err(|_| CompilationError::err(source_range, format!("Unknown type: {}", name.name)))?;
101
102 match ty_val {
103 KclValue::Type { value, .. } => match value {
104 TypeDef::RustRepr(ty, _) => RuntimeType::Primitive(ty.clone()),
105 TypeDef::Alias(ty) => ty.clone(),
106 },
107 _ => unreachable!(),
108 }
109 }
110 AstPrimitiveType::Tag => RuntimeType::Primitive(PrimitiveType::Tag),
111 })
112 }
113
114 pub fn human_friendly_type(&self) -> String {
115 match self {
116 RuntimeType::Primitive(ty) => ty.to_string(),
117 RuntimeType::Array(ty, ArrayLen::None) => format!("an array of {}", ty.display_multiple()),
118 RuntimeType::Array(ty, ArrayLen::NonEmpty) => format!("one or more {}", ty.display_multiple()),
119 RuntimeType::Array(ty, ArrayLen::Known(n)) => format!("an array of {n} {}", ty.display_multiple()),
120 RuntimeType::Union(tys) => tys
121 .iter()
122 .map(Self::human_friendly_type)
123 .collect::<Vec<_>>()
124 .join(" or "),
125 RuntimeType::Tuple(tys) => format!(
126 "an array with values of types ({})",
127 tys.iter().map(Self::human_friendly_type).collect::<Vec<_>>().join(", ")
128 ),
129 RuntimeType::Object(_) => format!("an object with fields {}", self),
130 }
131 }
132
133 fn subtype(&self, sup: &RuntimeType) -> bool {
135 use RuntimeType::*;
136
137 match (self, sup) {
138 (Primitive(t1), Primitive(t2)) => t1.subtype(t2),
139 (Array(t1, l1), Array(t2, l2)) => t1.subtype(t2) && l1.subtype(*l2),
140 (Tuple(t1), Tuple(t2)) => t1.len() == t2.len() && t1.iter().zip(t2).all(|(t1, t2)| t1.subtype(t2)),
141 (Union(ts1), Union(ts2)) => ts1.iter().all(|t| ts2.contains(t)),
142 (t1, Union(ts2)) => ts2.iter().any(|t| t1.subtype(t)),
143 (Object(t1), Object(t2)) => t2
144 .iter()
145 .all(|(f, t)| t1.iter().any(|(ff, tt)| f == ff && tt.subtype(t))),
146 _ => false,
147 }
148 }
149
150 fn display_multiple(&self) -> String {
151 match self {
152 RuntimeType::Primitive(ty) => ty.display_multiple(),
153 RuntimeType::Array(..) => "arrays".to_owned(),
154 RuntimeType::Union(tys) => tys
155 .iter()
156 .map(|t| t.display_multiple())
157 .collect::<Vec<_>>()
158 .join(" or "),
159 RuntimeType::Tuple(_) => "arrays".to_owned(),
160 RuntimeType::Object(_) => format!("objects with fields {self}"),
161 }
162 }
163}
164
165impl fmt::Display for RuntimeType {
166 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
167 match self {
168 RuntimeType::Primitive(t) => t.fmt(f),
169 RuntimeType::Array(t, l) => match l {
170 ArrayLen::None => write!(f, "[{t}]"),
171 ArrayLen::NonEmpty => write!(f, "[{t}; 1+]"),
172 ArrayLen::Known(n) => write!(f, "[{t}; {n}]"),
173 },
174 RuntimeType::Tuple(ts) => write!(
175 f,
176 "[{}]",
177 ts.iter().map(|t| t.to_string()).collect::<Vec<_>>().join(", ")
178 ),
179 RuntimeType::Union(ts) => write!(
180 f,
181 "{}",
182 ts.iter().map(|t| t.to_string()).collect::<Vec<_>>().join(" | ")
183 ),
184 RuntimeType::Object(items) => write!(
185 f,
186 "{{ {} }}",
187 items
188 .iter()
189 .map(|(n, t)| format!("{n}: {t}"))
190 .collect::<Vec<_>>()
191 .join(", ")
192 ),
193 }
194 }
195}
196
197#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, ts_rs::TS, JsonSchema)]
198pub enum ArrayLen {
199 None,
200 NonEmpty,
201 Known(usize),
202}
203
204impl ArrayLen {
205 pub fn subtype(self, other: ArrayLen) -> bool {
206 match (self, other) {
207 (_, ArrayLen::None) => true,
208 (ArrayLen::NonEmpty, ArrayLen::NonEmpty) => true,
209 (ArrayLen::Known(size), ArrayLen::NonEmpty) if size > 0 => true,
210 (ArrayLen::Known(s1), ArrayLen::Known(s2)) if s1 == s2 => true,
211 _ => false,
212 }
213 }
214
215 fn satisfied(self, len: usize) -> bool {
217 match self {
218 ArrayLen::None => true,
219 ArrayLen::NonEmpty => len > 0,
220 ArrayLen::Known(s) => len == s,
221 }
222 }
223}
224
225#[derive(Debug, Clone, PartialEq)]
226pub enum PrimitiveType {
227 Number(NumericType),
228 String,
229 Boolean,
230 Tag,
231 Sketch,
232 Solid,
233 Plane,
234 Helix,
235 Face,
236 ImportedGeometry,
237}
238
239impl PrimitiveType {
240 fn display_multiple(&self) -> String {
241 match self {
242 PrimitiveType::Number(NumericType::Known(unit)) => format!("numbers({unit})"),
243 PrimitiveType::Number(_) => "numbers".to_owned(),
244 PrimitiveType::String => "strings".to_owned(),
245 PrimitiveType::Boolean => "bools".to_owned(),
246 PrimitiveType::Sketch => "Sketches".to_owned(),
247 PrimitiveType::Solid => "Solids".to_owned(),
248 PrimitiveType::Plane => "Planes".to_owned(),
249 PrimitiveType::Helix => "Helices".to_owned(),
250 PrimitiveType::Face => "Faces".to_owned(),
251 PrimitiveType::ImportedGeometry => "imported geometries".to_owned(),
252 PrimitiveType::Tag => "tags".to_owned(),
253 }
254 }
255
256 fn subtype(&self, other: &PrimitiveType) -> bool {
257 match (self, other) {
258 (PrimitiveType::Number(n1), PrimitiveType::Number(n2)) => n1.subtype(n2),
259 (t1, t2) => t1 == t2,
260 }
261 }
262}
263
264impl fmt::Display for PrimitiveType {
265 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
266 match self {
267 PrimitiveType::Number(NumericType::Known(unit)) => write!(f, "number({unit})"),
268 PrimitiveType::Number(_) => write!(f, "number"),
269 PrimitiveType::String => write!(f, "string"),
270 PrimitiveType::Boolean => write!(f, "bool"),
271 PrimitiveType::Tag => write!(f, "tag"),
272 PrimitiveType::Sketch => write!(f, "Sketch"),
273 PrimitiveType::Solid => write!(f, "Solid"),
274 PrimitiveType::Plane => write!(f, "Plane"),
275 PrimitiveType::Face => write!(f, "Face"),
276 PrimitiveType::Helix => write!(f, "Helix"),
277 PrimitiveType::ImportedGeometry => write!(f, "imported geometry"),
278 }
279 }
280}
281
282#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema)]
283#[ts(export)]
284#[serde(tag = "type")]
285pub enum NumericType {
286 Known(UnitType),
288 Default { len: UnitLen, angle: UnitAngle },
290 Unknown,
292 Any,
294}
295
296impl NumericType {
297 pub fn count() -> Self {
298 NumericType::Known(UnitType::Count)
299 }
300
301 pub fn combine_eq(self, other: &NumericType) -> NumericType {
303 if &self == other {
304 self
305 } else {
306 NumericType::Unknown
307 }
308 }
309
310 pub fn combine_n_eq(tys: &[NumericType]) -> NumericType {
314 let ty0 = tys[0].clone();
315 for t in &tys[1..] {
316 if t != &ty0 {
317 return NumericType::Unknown;
318 }
319 }
320 ty0
321 }
322
323 pub fn combine_add(a: NumericType, b: NumericType) -> NumericType {
325 if a == b {
326 return a;
327 }
328 NumericType::Unknown
329 }
330
331 pub fn combine_mul(a: NumericType, b: NumericType) -> NumericType {
333 if a == NumericType::count() {
334 return b;
335 }
336 if b == NumericType::count() {
337 return a;
338 }
339 NumericType::Unknown
340 }
341
342 pub fn combine_div(a: NumericType, b: NumericType) -> NumericType {
344 if b == NumericType::count() {
345 return a;
346 }
347 NumericType::Unknown
348 }
349
350 pub fn from_parsed(suffix: NumericSuffix, settings: &super::MetaSettings) -> Self {
351 match suffix {
352 NumericSuffix::None => NumericType::Default {
353 len: settings.default_length_units,
354 angle: settings.default_angle_units,
355 },
356 NumericSuffix::Count => NumericType::Known(UnitType::Count),
357 NumericSuffix::Mm => NumericType::Known(UnitType::Length(UnitLen::Mm)),
358 NumericSuffix::Cm => NumericType::Known(UnitType::Length(UnitLen::Cm)),
359 NumericSuffix::M => NumericType::Known(UnitType::Length(UnitLen::M)),
360 NumericSuffix::Inch => NumericType::Known(UnitType::Length(UnitLen::Inches)),
361 NumericSuffix::Ft => NumericType::Known(UnitType::Length(UnitLen::Feet)),
362 NumericSuffix::Yd => NumericType::Known(UnitType::Length(UnitLen::Yards)),
363 NumericSuffix::Deg => NumericType::Known(UnitType::Angle(UnitAngle::Degrees)),
364 NumericSuffix::Rad => NumericType::Known(UnitType::Angle(UnitAngle::Radians)),
365 }
366 }
367
368 fn subtype(&self, other: &NumericType) -> bool {
369 use NumericType::*;
370
371 match (self, other) {
372 (Unknown, _) | (_, Unknown) => false,
373 (a, b) if a == b => true,
374 (_, Any) => true,
375 (_, _) => false,
376 }
377 }
378}
379
380impl From<UnitLen> for NumericType {
381 fn from(value: UnitLen) -> Self {
382 NumericType::Known(UnitType::Length(value))
383 }
384}
385
386impl From<UnitAngle> for NumericType {
387 fn from(value: UnitAngle) -> Self {
388 NumericType::Known(UnitType::Angle(value))
389 }
390}
391
392#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, ts_rs::TS, JsonSchema)]
393#[ts(export)]
394#[serde(tag = "type")]
395pub enum UnitType {
396 Count,
397 Length(UnitLen),
398 Angle(UnitAngle),
399}
400
401impl std::fmt::Display for UnitType {
402 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
403 match self {
404 UnitType::Count => write!(f, "_"),
405 UnitType::Length(l) => l.fmt(f),
406 UnitType::Angle(a) => a.fmt(f),
407 }
408 }
409}
410
411#[derive(Debug, Default, Clone, Copy, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Eq)]
414#[ts(export)]
415#[serde(tag = "type")]
416pub enum UnitLen {
417 #[default]
418 Mm,
419 Cm,
420 M,
421 Inches,
422 Feet,
423 Yards,
424}
425
426impl std::fmt::Display for UnitLen {
427 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
428 match self {
429 UnitLen::Mm => write!(f, "mm"),
430 UnitLen::Cm => write!(f, "cm"),
431 UnitLen::M => write!(f, "m"),
432 UnitLen::Inches => write!(f, "in"),
433 UnitLen::Feet => write!(f, "ft"),
434 UnitLen::Yards => write!(f, "yd"),
435 }
436 }
437}
438
439impl TryFrom<NumericSuffix> for UnitLen {
440 type Error = ();
441
442 fn try_from(suffix: NumericSuffix) -> std::result::Result<Self, Self::Error> {
443 match suffix {
444 NumericSuffix::Mm => Ok(Self::Mm),
445 NumericSuffix::Cm => Ok(Self::Cm),
446 NumericSuffix::M => Ok(Self::M),
447 NumericSuffix::Inch => Ok(Self::Inches),
448 NumericSuffix::Ft => Ok(Self::Feet),
449 NumericSuffix::Yd => Ok(Self::Yards),
450 _ => Err(()),
451 }
452 }
453}
454
455impl From<crate::UnitLength> for UnitLen {
456 fn from(unit: crate::UnitLength) -> Self {
457 match unit {
458 crate::UnitLength::Cm => UnitLen::Cm,
459 crate::UnitLength::Ft => UnitLen::Feet,
460 crate::UnitLength::In => UnitLen::Inches,
461 crate::UnitLength::M => UnitLen::M,
462 crate::UnitLength::Mm => UnitLen::Mm,
463 crate::UnitLength::Yd => UnitLen::Yards,
464 }
465 }
466}
467
468impl From<UnitLen> for crate::UnitLength {
469 fn from(unit: UnitLen) -> Self {
470 match unit {
471 UnitLen::Cm => crate::UnitLength::Cm,
472 UnitLen::Feet => crate::UnitLength::Ft,
473 UnitLen::Inches => crate::UnitLength::In,
474 UnitLen::M => crate::UnitLength::M,
475 UnitLen::Mm => crate::UnitLength::Mm,
476 UnitLen::Yards => crate::UnitLength::Yd,
477 }
478 }
479}
480
481impl From<UnitLen> for kittycad_modeling_cmds::units::UnitLength {
482 fn from(unit: UnitLen) -> Self {
483 match unit {
484 UnitLen::Cm => kittycad_modeling_cmds::units::UnitLength::Centimeters,
485 UnitLen::Feet => kittycad_modeling_cmds::units::UnitLength::Feet,
486 UnitLen::Inches => kittycad_modeling_cmds::units::UnitLength::Inches,
487 UnitLen::M => kittycad_modeling_cmds::units::UnitLength::Meters,
488 UnitLen::Mm => kittycad_modeling_cmds::units::UnitLength::Millimeters,
489 UnitLen::Yards => kittycad_modeling_cmds::units::UnitLength::Yards,
490 }
491 }
492}
493
494#[derive(Debug, Default, Clone, Copy, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Eq)]
496#[ts(export)]
497#[serde(tag = "type")]
498pub enum UnitAngle {
499 #[default]
500 Degrees,
501 Radians,
502}
503
504impl std::fmt::Display for UnitAngle {
505 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
506 match self {
507 UnitAngle::Degrees => write!(f, "deg"),
508 UnitAngle::Radians => write!(f, "rad"),
509 }
510 }
511}
512
513impl TryFrom<NumericSuffix> for UnitAngle {
514 type Error = ();
515
516 fn try_from(suffix: NumericSuffix) -> std::result::Result<Self, Self::Error> {
517 match suffix {
518 NumericSuffix::Deg => Ok(Self::Degrees),
519 NumericSuffix::Rad => Ok(Self::Radians),
520 _ => Err(()),
521 }
522 }
523}
524
525impl KclValue {
526 pub fn has_type(&self, ty: &RuntimeType) -> bool {
528 let Some(self_ty) = self.principal_type() else {
529 return false;
530 };
531
532 self_ty.subtype(ty)
533 }
534
535 pub fn coerce(&self, ty: &RuntimeType, exec_state: &mut ExecState) -> Option<KclValue> {
542 match ty {
543 RuntimeType::Primitive(ty) => self.coerce_to_primitive_type(ty, exec_state),
544 RuntimeType::Array(ty, len) => self.coerce_to_array_type(ty, *len, exec_state),
545 RuntimeType::Tuple(tys) => self.coerce_to_tuple_type(tys, exec_state),
546 RuntimeType::Union(tys) => self.coerce_to_union_type(tys, exec_state),
547 RuntimeType::Object(tys) => self.coerce_to_object_type(tys, exec_state),
548 }
549 }
550
551 fn coerce_to_primitive_type(&self, ty: &PrimitiveType, exec_state: &mut ExecState) -> Option<KclValue> {
552 let value = match self {
553 KclValue::MixedArray { value, .. } | KclValue::HomArray { value, .. } if value.len() == 1 => &value[0],
554 _ => self,
555 };
556 match ty {
557 PrimitiveType::Number(_ty) => match value {
559 KclValue::Number { .. } => Some(value.clone()),
560 _ => None,
561 },
562 PrimitiveType::String => match value {
563 KclValue::String { .. } => Some(value.clone()),
564 _ => None,
565 },
566 PrimitiveType::Boolean => match value {
567 KclValue::Bool { .. } => Some(value.clone()),
568 _ => None,
569 },
570 PrimitiveType::Sketch => match value {
571 KclValue::Sketch { .. } => Some(value.clone()),
572 _ => None,
573 },
574 PrimitiveType::Solid => match value {
575 KclValue::Solid { .. } => Some(value.clone()),
576 _ => None,
577 },
578 PrimitiveType::Plane => match value {
579 KclValue::Plane { .. } => Some(value.clone()),
580 KclValue::Object { value, meta } => {
581 let origin = value.get("origin").and_then(Point3d::from_kcl_val)?;
582 let x_axis = value.get("xAxis").and_then(Point3d::from_kcl_val)?;
583 let y_axis = value.get("yAxis").and_then(Point3d::from_kcl_val)?;
584 let z_axis = value.get("zAxis").and_then(Point3d::from_kcl_val)?;
585
586 let id = exec_state.mod_local.id_generator.next_uuid();
587 let plane = Plane {
588 id,
589 artifact_id: id.into(),
590 origin,
591 x_axis,
592 y_axis,
593 z_axis,
594 value: super::PlaneType::Uninit,
595 units: exec_state.length_unit(),
597 meta: meta.clone(),
598 };
599
600 Some(KclValue::Plane { value: Box::new(plane) })
601 }
602 _ => None,
603 },
604 PrimitiveType::Face => match value {
605 KclValue::Face { .. } => Some(value.clone()),
606 _ => None,
607 },
608 PrimitiveType::Helix => match value {
609 KclValue::Helix { .. } => Some(value.clone()),
610 _ => None,
611 },
612 PrimitiveType::ImportedGeometry => match value {
613 KclValue::ImportedGeometry { .. } => Some(value.clone()),
614 _ => None,
615 },
616 PrimitiveType::Tag => match value {
617 KclValue::TagDeclarator { .. } => Some(value.clone()),
618 KclValue::TagIdentifier { .. } => Some(value.clone()),
619 _ => None,
620 },
621 }
622 }
623
624 fn coerce_to_array_type(&self, ty: &RuntimeType, len: ArrayLen, exec_state: &mut ExecState) -> Option<KclValue> {
625 match self {
626 KclValue::HomArray { value, ty: aty } if aty == ty => {
627 let value = match len {
628 ArrayLen::None => value.clone(),
629 ArrayLen::NonEmpty => {
630 if value.is_empty() {
631 return None;
632 }
633
634 value.clone()
635 }
636 ArrayLen::Known(n) => {
637 if n != value.len() {
638 return None;
639 }
640
641 value[..n].to_vec()
642 }
643 };
644
645 Some(KclValue::HomArray { value, ty: ty.clone() })
646 }
647 value if len.satisfied(1) && value.has_type(ty) => Some(KclValue::HomArray {
648 value: vec![value.clone()],
649 ty: ty.clone(),
650 }),
651 KclValue::MixedArray { value, .. } => {
652 let value = match len {
653 ArrayLen::None => value.clone(),
654 ArrayLen::NonEmpty => {
655 if value.is_empty() {
656 return None;
657 }
658
659 value.clone()
660 }
661 ArrayLen::Known(n) => {
662 if n != value.len() {
663 return None;
664 }
665
666 value[..n].to_vec()
667 }
668 };
669
670 let value = value
671 .iter()
672 .map(|v| v.coerce(ty, exec_state))
673 .collect::<Option<Vec<_>>>()?;
674
675 Some(KclValue::HomArray { value, ty: ty.clone() })
676 }
677 KclValue::KclNone { .. } if len.satisfied(0) => Some(KclValue::HomArray {
678 value: Vec::new(),
679 ty: ty.clone(),
680 }),
681 _ => None,
682 }
683 }
684
685 fn coerce_to_tuple_type(&self, tys: &[RuntimeType], exec_state: &mut ExecState) -> Option<KclValue> {
686 match self {
687 KclValue::MixedArray { value, .. } | KclValue::HomArray { value, .. } if value.len() == tys.len() => {
688 let mut result = Vec::new();
689 for (i, t) in tys.iter().enumerate() {
690 result.push(value[i].coerce(t, exec_state)?);
691 }
692
693 Some(KclValue::MixedArray {
694 value: result,
695 meta: Vec::new(),
696 })
697 }
698 KclValue::KclNone { meta, .. } if tys.is_empty() => Some(KclValue::MixedArray {
699 value: Vec::new(),
700 meta: meta.clone(),
701 }),
702 value if tys.len() == 1 && value.has_type(&tys[0]) => Some(KclValue::MixedArray {
703 value: vec![value.clone()],
704 meta: Vec::new(),
705 }),
706 _ => None,
707 }
708 }
709
710 fn coerce_to_union_type(&self, tys: &[RuntimeType], exec_state: &mut ExecState) -> Option<KclValue> {
711 for t in tys {
712 if let Some(v) = self.coerce(t, exec_state) {
713 return Some(v);
714 }
715 }
716
717 None
718 }
719
720 fn coerce_to_object_type(&self, tys: &[(String, RuntimeType)], _exec_state: &mut ExecState) -> Option<KclValue> {
721 match self {
722 KclValue::Object { value, .. } => {
723 for (s, t) in tys {
724 if !value.get(s)?.has_type(t) {
726 return None;
727 }
728 }
729 Some(self.clone())
731 }
732 KclValue::KclNone { meta, .. } if tys.is_empty() => Some(KclValue::Object {
733 value: HashMap::new(),
734 meta: meta.clone(),
735 }),
736 _ => None,
737 }
738 }
739
740 pub fn principal_type(&self) -> Option<RuntimeType> {
741 match self {
742 KclValue::Bool { .. } => Some(RuntimeType::Primitive(PrimitiveType::Boolean)),
743 KclValue::Number { ty, .. } => Some(RuntimeType::Primitive(PrimitiveType::Number(ty.clone()))),
744 KclValue::String { .. } => Some(RuntimeType::Primitive(PrimitiveType::String)),
745 KclValue::Object { value, .. } => {
746 let properties = value
747 .iter()
748 .map(|(k, v)| v.principal_type().map(|t| (k.clone(), t)))
749 .collect::<Option<Vec<_>>>()?;
750 Some(RuntimeType::Object(properties))
751 }
752 KclValue::Plane { .. } => Some(RuntimeType::Primitive(PrimitiveType::Plane)),
753 KclValue::Sketch { .. } => Some(RuntimeType::Primitive(PrimitiveType::Sketch)),
754 KclValue::Solid { .. } => Some(RuntimeType::Primitive(PrimitiveType::Solid)),
755 KclValue::Face { .. } => Some(RuntimeType::Primitive(PrimitiveType::Face)),
756 KclValue::Helix { .. } => Some(RuntimeType::Primitive(PrimitiveType::Helix)),
757 KclValue::ImportedGeometry(..) => Some(RuntimeType::Primitive(PrimitiveType::ImportedGeometry)),
758 KclValue::MixedArray { value, .. } => Some(RuntimeType::Tuple(
759 value.iter().map(|v| v.principal_type()).collect::<Option<Vec<_>>>()?,
760 )),
761 KclValue::HomArray { ty, value, .. } => {
762 Some(RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Known(value.len())))
763 }
764 KclValue::TagIdentifier(_) | KclValue::TagDeclarator(_) => Some(RuntimeType::Primitive(PrimitiveType::Tag)),
765 KclValue::Function { .. }
766 | KclValue::Module { .. }
767 | KclValue::KclNone { .. }
768 | KclValue::Type { .. }
769 | KclValue::Uuid { .. } => None,
770 }
771 }
772}
773
774#[cfg(test)]
775mod test {
776 use super::*;
777
778 fn values(exec_state: &mut ExecState) -> Vec<KclValue> {
779 vec![
780 KclValue::Bool {
781 value: true,
782 meta: Vec::new(),
783 },
784 KclValue::Number {
785 value: 1.0,
786 ty: NumericType::count(),
787 meta: Vec::new(),
788 },
789 KclValue::String {
790 value: "hello".to_owned(),
791 meta: Vec::new(),
792 },
793 KclValue::MixedArray {
794 value: Vec::new(),
795 meta: Vec::new(),
796 },
797 KclValue::HomArray {
798 value: Vec::new(),
799 ty: RuntimeType::solid(),
800 },
801 KclValue::Object {
802 value: crate::execution::KclObjectFields::new(),
803 meta: Vec::new(),
804 },
805 KclValue::TagIdentifier(Box::new("foo".parse().unwrap())),
806 KclValue::TagDeclarator(Box::new(crate::parsing::ast::types::TagDeclarator::new("foo"))),
807 KclValue::Plane {
808 value: Box::new(Plane::from_plane_data(crate::std::sketch::PlaneData::XY, exec_state)),
809 },
810 KclValue::ImportedGeometry(crate::execution::ImportedGeometry {
812 id: uuid::Uuid::nil(),
813 value: Vec::new(),
814 meta: Vec::new(),
815 }),
816 ]
818 }
819
820 #[track_caller]
821 fn assert_coerce_results(
822 value: &KclValue,
823 super_type: &RuntimeType,
824 expected_value: &KclValue,
825 exec_state: &mut ExecState,
826 ) {
827 let is_subtype = value == expected_value;
828 assert_eq!(&value.coerce(super_type, exec_state).unwrap(), expected_value);
829 assert_eq!(
830 is_subtype,
831 value.principal_type().is_some() && value.principal_type().unwrap().subtype(super_type),
832 "{:?} <: {super_type:?} should be {is_subtype}",
833 value.principal_type().unwrap()
834 );
835 assert!(
836 expected_value.principal_type().unwrap().subtype(super_type),
837 "{} <: {super_type}",
838 expected_value.principal_type().unwrap()
839 )
840 }
841
842 #[tokio::test(flavor = "multi_thread")]
843 async fn coerce_idempotent() {
844 let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock().await);
845 let values = values(&mut exec_state);
846 for v in &values {
847 let ty = v.principal_type().unwrap();
849 assert_coerce_results(v, &ty, v, &mut exec_state);
850
851 let uty1 = RuntimeType::Union(vec![ty.clone()]);
853 let uty2 = RuntimeType::Union(vec![ty.clone(), RuntimeType::Primitive(PrimitiveType::Boolean)]);
854 assert_coerce_results(v, &uty1, v, &mut exec_state);
855 assert_coerce_results(v, &uty2, v, &mut exec_state);
856
857 let aty = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::None);
859 let aty1 = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Known(1));
860 let aty0 = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::NonEmpty);
861
862 assert_coerce_results(
863 v,
864 &aty,
865 &KclValue::HomArray {
866 value: vec![v.clone()],
867 ty: ty.clone(),
868 },
869 &mut exec_state,
870 );
871 assert_coerce_results(
872 v,
873 &aty1,
874 &KclValue::HomArray {
875 value: vec![v.clone()],
876 ty: ty.clone(),
877 },
878 &mut exec_state,
879 );
880 assert_coerce_results(
881 v,
882 &aty0,
883 &KclValue::HomArray {
884 value: vec![v.clone()],
885 ty: ty.clone(),
886 },
887 &mut exec_state,
888 );
889
890 let tty = RuntimeType::Tuple(vec![ty.clone()]);
892 assert_coerce_results(
893 v,
894 &tty,
895 &KclValue::MixedArray {
896 value: vec![v.clone()],
897 meta: Vec::new(),
898 },
899 &mut exec_state,
900 );
901 }
902
903 for v in &values[1..] {
904 assert!(v
906 .coerce(&RuntimeType::Primitive(PrimitiveType::Boolean), &mut exec_state)
907 .is_none());
908 }
909 }
910
911 #[tokio::test(flavor = "multi_thread")]
912 async fn coerce_none() {
913 let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock().await);
914 let none = KclValue::KclNone {
915 value: crate::parsing::ast::types::KclNone::new(),
916 meta: Vec::new(),
917 };
918
919 let aty = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::None);
920 let aty0 = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::Known(0));
921 let aty1 = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::Known(1));
922 let aty1p = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::NonEmpty);
923 assert_coerce_results(
924 &none,
925 &aty,
926 &KclValue::HomArray {
927 value: Vec::new(),
928 ty: RuntimeType::solid(),
929 },
930 &mut exec_state,
931 );
932 assert_coerce_results(
933 &none,
934 &aty0,
935 &KclValue::HomArray {
936 value: Vec::new(),
937 ty: RuntimeType::solid(),
938 },
939 &mut exec_state,
940 );
941 assert!(none.coerce(&aty1, &mut exec_state).is_none());
942 assert!(none.coerce(&aty1p, &mut exec_state).is_none());
943
944 let tty = RuntimeType::Tuple(vec![]);
945 let tty1 = RuntimeType::Tuple(vec![RuntimeType::solid()]);
946 assert_coerce_results(
947 &none,
948 &tty,
949 &KclValue::MixedArray {
950 value: Vec::new(),
951 meta: Vec::new(),
952 },
953 &mut exec_state,
954 );
955 assert!(none.coerce(&tty1, &mut exec_state).is_none());
956
957 let oty = RuntimeType::Object(vec![]);
958 assert_coerce_results(
959 &none,
960 &oty,
961 &KclValue::Object {
962 value: HashMap::new(),
963 meta: Vec::new(),
964 },
965 &mut exec_state,
966 );
967 }
968
969 #[tokio::test(flavor = "multi_thread")]
970 async fn coerce_record() {
971 let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock().await);
972
973 let obj0 = KclValue::Object {
974 value: HashMap::new(),
975 meta: Vec::new(),
976 };
977 let obj1 = KclValue::Object {
978 value: [(
979 "foo".to_owned(),
980 KclValue::Bool {
981 value: true,
982 meta: Vec::new(),
983 },
984 )]
985 .into(),
986 meta: Vec::new(),
987 };
988 let obj2 = KclValue::Object {
989 value: [
990 (
991 "foo".to_owned(),
992 KclValue::Bool {
993 value: true,
994 meta: Vec::new(),
995 },
996 ),
997 (
998 "bar".to_owned(),
999 KclValue::Number {
1000 value: 0.0,
1001 ty: NumericType::count(),
1002 meta: Vec::new(),
1003 },
1004 ),
1005 (
1006 "baz".to_owned(),
1007 KclValue::Number {
1008 value: 42.0,
1009 ty: NumericType::count(),
1010 meta: Vec::new(),
1011 },
1012 ),
1013 ]
1014 .into(),
1015 meta: Vec::new(),
1016 };
1017
1018 let ty0 = RuntimeType::Object(vec![]);
1019 assert_coerce_results(&obj0, &ty0, &obj0, &mut exec_state);
1020 assert_coerce_results(&obj1, &ty0, &obj1, &mut exec_state);
1021 assert_coerce_results(&obj2, &ty0, &obj2, &mut exec_state);
1022
1023 let ty1 = RuntimeType::Object(vec![("foo".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean))]);
1024 assert!(&obj0.coerce(&ty1, &mut exec_state).is_none());
1025 assert_coerce_results(&obj1, &ty1, &obj1, &mut exec_state);
1026 assert_coerce_results(&obj2, &ty1, &obj2, &mut exec_state);
1027
1028 let ty2 = RuntimeType::Object(vec![
1030 (
1031 "bar".to_owned(),
1032 RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1033 ),
1034 ("foo".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean)),
1035 ]);
1036 assert!(&obj0.coerce(&ty2, &mut exec_state).is_none());
1037 assert!(&obj1.coerce(&ty2, &mut exec_state).is_none());
1038 assert_coerce_results(&obj2, &ty2, &obj2, &mut exec_state);
1039
1040 let tyq = RuntimeType::Object(vec![("qux".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean))]);
1042 assert!(&obj0.coerce(&tyq, &mut exec_state).is_none());
1043 assert!(&obj1.coerce(&tyq, &mut exec_state).is_none());
1044 assert!(&obj2.coerce(&tyq, &mut exec_state).is_none());
1045
1046 let ty1 = RuntimeType::Object(vec![("bar".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean))]);
1048 assert!(&obj2.coerce(&ty1, &mut exec_state).is_none());
1049 }
1050
1051 #[tokio::test(flavor = "multi_thread")]
1052 async fn coerce_array() {
1053 let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock().await);
1054
1055 let hom_arr = KclValue::HomArray {
1056 value: vec![
1057 KclValue::Number {
1058 value: 0.0,
1059 ty: NumericType::count(),
1060 meta: Vec::new(),
1061 },
1062 KclValue::Number {
1063 value: 1.0,
1064 ty: NumericType::count(),
1065 meta: Vec::new(),
1066 },
1067 KclValue::Number {
1068 value: 2.0,
1069 ty: NumericType::count(),
1070 meta: Vec::new(),
1071 },
1072 KclValue::Number {
1073 value: 3.0,
1074 ty: NumericType::count(),
1075 meta: Vec::new(),
1076 },
1077 ],
1078 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1079 };
1080 let mixed1 = KclValue::MixedArray {
1081 value: vec![
1082 KclValue::Number {
1083 value: 0.0,
1084 ty: NumericType::count(),
1085 meta: Vec::new(),
1086 },
1087 KclValue::Number {
1088 value: 1.0,
1089 ty: NumericType::count(),
1090 meta: Vec::new(),
1091 },
1092 ],
1093 meta: Vec::new(),
1094 };
1095 let mixed2 = KclValue::MixedArray {
1096 value: vec![
1097 KclValue::Number {
1098 value: 0.0,
1099 ty: NumericType::count(),
1100 meta: Vec::new(),
1101 },
1102 KclValue::Bool {
1103 value: true,
1104 meta: Vec::new(),
1105 },
1106 ],
1107 meta: Vec::new(),
1108 };
1109
1110 let tyh = RuntimeType::Array(
1112 Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
1113 ArrayLen::Known(4),
1114 );
1115 let tym1 = RuntimeType::Tuple(vec![
1116 RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1117 RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1118 ]);
1119 let tym2 = RuntimeType::Tuple(vec![
1120 RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1121 RuntimeType::Primitive(PrimitiveType::Boolean),
1122 ]);
1123 assert_coerce_results(&hom_arr, &tyh, &hom_arr, &mut exec_state);
1124 assert_coerce_results(&mixed1, &tym1, &mixed1, &mut exec_state);
1125 assert_coerce_results(&mixed2, &tym2, &mixed2, &mut exec_state);
1126 assert!(&mixed1.coerce(&tym2, &mut exec_state).is_none());
1127 assert!(&mixed2.coerce(&tym1, &mut exec_state).is_none());
1128
1129 let tyhn = RuntimeType::Array(
1131 Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
1132 ArrayLen::None,
1133 );
1134 let tyh1 = RuntimeType::Array(
1135 Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
1136 ArrayLen::NonEmpty,
1137 );
1138 let tyh3 = RuntimeType::Array(
1139 Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
1140 ArrayLen::Known(3),
1141 );
1142 assert_coerce_results(&hom_arr, &tyhn, &hom_arr, &mut exec_state);
1143 assert_coerce_results(&hom_arr, &tyh1, &hom_arr, &mut exec_state);
1144 assert!(&hom_arr.coerce(&tyh3, &mut exec_state).is_none());
1145
1146 let hom_arr0 = KclValue::HomArray {
1147 value: vec![],
1148 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1149 };
1150 assert_coerce_results(&hom_arr0, &tyhn, &hom_arr0, &mut exec_state);
1151 assert!(&hom_arr0.coerce(&tyh1, &mut exec_state).is_none());
1152 assert!(&hom_arr0.coerce(&tyh3, &mut exec_state).is_none());
1153
1154 let tym1 = RuntimeType::Tuple(vec![
1157 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
1158 RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1159 ]);
1160 let tym2 = RuntimeType::Tuple(vec![
1161 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
1162 RuntimeType::Primitive(PrimitiveType::Boolean),
1163 ]);
1164 assert_coerce_results(&mixed1, &tym1, &mixed1, &mut exec_state);
1167 assert_coerce_results(&mixed2, &tym2, &mixed2, &mut exec_state);
1168
1169 let hom_arr_2 = KclValue::HomArray {
1171 value: vec![
1172 KclValue::Number {
1173 value: 0.0,
1174 ty: NumericType::count(),
1175 meta: Vec::new(),
1176 },
1177 KclValue::Number {
1178 value: 1.0,
1179 ty: NumericType::count(),
1180 meta: Vec::new(),
1181 },
1182 ],
1183 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1184 };
1185 let mixed0 = KclValue::MixedArray {
1186 value: vec![],
1187 meta: Vec::new(),
1188 };
1189 assert_coerce_results(&mixed1, &tyhn, &hom_arr_2, &mut exec_state);
1190 assert_coerce_results(&mixed1, &tyh1, &hom_arr_2, &mut exec_state);
1191 assert_coerce_results(&mixed0, &tyhn, &hom_arr0, &mut exec_state);
1192 assert!(&mixed0.coerce(&tyh, &mut exec_state).is_none());
1193 assert!(&mixed0.coerce(&tyh1, &mut exec_state).is_none());
1194
1195 assert_coerce_results(&hom_arr_2, &tym1, &mixed1, &mut exec_state);
1197 assert!(&hom_arr.coerce(&tym1, &mut exec_state).is_none());
1198 assert!(&hom_arr_2.coerce(&tym2, &mut exec_state).is_none());
1199
1200 assert!(&mixed0.coerce(&tym1, &mut exec_state).is_none());
1201 assert!(&mixed0.coerce(&tym2, &mut exec_state).is_none());
1202 }
1203
1204 #[tokio::test(flavor = "multi_thread")]
1205 async fn coerce_union() {
1206 let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock().await);
1207
1208 assert!(RuntimeType::Union(vec![]).subtype(&RuntimeType::Union(vec![
1210 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
1211 RuntimeType::Primitive(PrimitiveType::Boolean)
1212 ])));
1213 assert!(
1214 RuntimeType::Union(vec![RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any))]).subtype(
1215 &RuntimeType::Union(vec![
1216 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
1217 RuntimeType::Primitive(PrimitiveType::Boolean)
1218 ])
1219 )
1220 );
1221 assert!(RuntimeType::Union(vec![
1222 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
1223 RuntimeType::Primitive(PrimitiveType::Boolean)
1224 ])
1225 .subtype(&RuntimeType::Union(vec![
1226 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
1227 RuntimeType::Primitive(PrimitiveType::Boolean)
1228 ])));
1229
1230 let count = KclValue::Number {
1232 value: 1.0,
1233 ty: NumericType::count(),
1234 meta: Vec::new(),
1235 };
1236
1237 let tya = RuntimeType::Union(vec![RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any))]);
1238 let tya2 = RuntimeType::Union(vec![
1239 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
1240 RuntimeType::Primitive(PrimitiveType::Boolean),
1241 ]);
1242 assert_coerce_results(&count, &tya, &count, &mut exec_state);
1243 assert_coerce_results(&count, &tya2, &count, &mut exec_state);
1244
1245 let tyb = RuntimeType::Union(vec![RuntimeType::Primitive(PrimitiveType::Boolean)]);
1247 let tyb2 = RuntimeType::Union(vec![
1248 RuntimeType::Primitive(PrimitiveType::Boolean),
1249 RuntimeType::Primitive(PrimitiveType::String),
1250 ]);
1251 assert!(count.coerce(&tyb, &mut exec_state).is_none());
1252 assert!(count.coerce(&tyb2, &mut exec_state).is_none());
1253 }
1254}