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, PlaneInfo, Point3d,
12 },
13 parsing::{
14 ast::types::{PrimitiveType as AstPrimitiveType, Type},
15 token::NumericSuffix,
16 },
17 std::args::{FromKclValue, TyF64},
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 edge() -> Self {
32 RuntimeType::Primitive(PrimitiveType::Edge)
33 }
34
35 pub fn sketch() -> Self {
36 RuntimeType::Primitive(PrimitiveType::Sketch)
37 }
38
39 pub fn sketches() -> Self {
41 RuntimeType::Array(
42 Box::new(RuntimeType::Primitive(PrimitiveType::Sketch)),
43 ArrayLen::NonEmpty,
44 )
45 }
46
47 pub fn solids() -> Self {
49 RuntimeType::Array(
50 Box::new(RuntimeType::Primitive(PrimitiveType::Solid)),
51 ArrayLen::NonEmpty,
52 )
53 }
54
55 pub fn solid() -> Self {
56 RuntimeType::Primitive(PrimitiveType::Solid)
57 }
58
59 pub fn helix() -> Self {
60 RuntimeType::Primitive(PrimitiveType::Helix)
61 }
62
63 pub fn plane() -> Self {
64 RuntimeType::Primitive(PrimitiveType::Plane)
65 }
66
67 pub fn face() -> Self {
68 RuntimeType::Primitive(PrimitiveType::Face)
69 }
70
71 pub fn tag() -> Self {
72 RuntimeType::Primitive(PrimitiveType::Tag)
73 }
74
75 pub fn bool() -> Self {
76 RuntimeType::Primitive(PrimitiveType::Boolean)
77 }
78
79 pub fn string() -> Self {
80 RuntimeType::Primitive(PrimitiveType::String)
81 }
82
83 pub fn imported() -> Self {
84 RuntimeType::Primitive(PrimitiveType::ImportedGeometry)
85 }
86
87 pub fn point2d() -> Self {
89 RuntimeType::Array(Box::new(RuntimeType::length()), ArrayLen::Known(2))
90 }
91
92 pub fn point3d() -> Self {
94 RuntimeType::Array(Box::new(RuntimeType::length()), ArrayLen::Known(3))
95 }
96
97 pub fn length() -> Self {
98 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Known(UnitType::Length(
99 UnitLen::Unknown,
100 ))))
101 }
102
103 pub fn known_length(len: UnitLen) -> Self {
104 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Known(UnitType::Length(len))))
105 }
106
107 pub fn angle() -> Self {
108 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Known(UnitType::Angle(
109 UnitAngle::Unknown,
110 ))))
111 }
112
113 pub fn radians() -> Self {
114 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Known(UnitType::Angle(
115 UnitAngle::Radians,
116 ))))
117 }
118
119 pub fn degrees() -> Self {
120 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Known(UnitType::Angle(
121 UnitAngle::Degrees,
122 ))))
123 }
124
125 pub fn count() -> Self {
126 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Known(UnitType::Count)))
127 }
128
129 pub fn num_any() -> Self {
130 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any))
131 }
132
133 pub fn from_parsed(
134 value: Type,
135 exec_state: &mut ExecState,
136 source_range: SourceRange,
137 ) -> Result<Self, CompilationError> {
138 match value {
139 Type::Primitive(pt) => Self::from_parsed_primitive(pt, exec_state, source_range),
140 Type::Array { ty, len } => {
141 Self::from_parsed(*ty, exec_state, source_range).map(|t| RuntimeType::Array(Box::new(t), len))
142 }
143 Type::Union { tys } => tys
144 .into_iter()
145 .map(|t| Self::from_parsed_primitive(t.inner, exec_state, source_range))
146 .collect::<Result<Vec<_>, CompilationError>>()
147 .map(RuntimeType::Union),
148 Type::Object { properties } => properties
149 .into_iter()
150 .map(|p| {
151 RuntimeType::from_parsed(p.type_.unwrap().inner, exec_state, source_range)
152 .map(|ty| (p.identifier.inner.name, ty))
153 })
154 .collect::<Result<Vec<_>, CompilationError>>()
155 .map(RuntimeType::Object),
156 }
157 }
158
159 fn from_parsed_primitive(
160 value: AstPrimitiveType,
161 exec_state: &mut ExecState,
162 source_range: SourceRange,
163 ) -> Result<Self, CompilationError> {
164 Ok(match value {
165 AstPrimitiveType::String => RuntimeType::Primitive(PrimitiveType::String),
166 AstPrimitiveType::Boolean => RuntimeType::Primitive(PrimitiveType::Boolean),
167 AstPrimitiveType::Number(suffix) => RuntimeType::Primitive(PrimitiveType::Number(
168 NumericType::from_parsed(suffix, &exec_state.mod_local.settings),
169 )),
170 AstPrimitiveType::Named(name) => Self::from_alias(&name.name, exec_state, source_range)?,
171 AstPrimitiveType::Tag => RuntimeType::Primitive(PrimitiveType::Tag),
172 })
173 }
174
175 pub fn from_alias(
176 alias: &str,
177 exec_state: &mut ExecState,
178 source_range: SourceRange,
179 ) -> Result<Self, CompilationError> {
180 let ty_val = exec_state
181 .stack()
182 .get(&format!("{}{}", memory::TYPE_PREFIX, alias), source_range)
183 .map_err(|_| CompilationError::err(source_range, format!("Unknown type: {}", alias)))?;
184
185 Ok(match ty_val {
186 KclValue::Type { value, .. } => match value {
187 TypeDef::RustRepr(ty, _) => RuntimeType::Primitive(ty.clone()),
188 TypeDef::Alias(ty) => ty.clone(),
189 },
190 _ => unreachable!(),
191 })
192 }
193
194 pub fn human_friendly_type(&self) -> String {
195 match self {
196 RuntimeType::Primitive(ty) => ty.to_string(),
197 RuntimeType::Array(ty, ArrayLen::None) => format!("an array of {}", ty.display_multiple()),
198 RuntimeType::Array(ty, ArrayLen::NonEmpty) => format!("one or more {}", ty.display_multiple()),
199 RuntimeType::Array(ty, ArrayLen::Known(n)) => format!("an array of {n} {}", ty.display_multiple()),
200 RuntimeType::Union(tys) => tys
201 .iter()
202 .map(Self::human_friendly_type)
203 .collect::<Vec<_>>()
204 .join(" or "),
205 RuntimeType::Tuple(tys) => format!(
206 "an array with values of types ({})",
207 tys.iter().map(Self::human_friendly_type).collect::<Vec<_>>().join(", ")
208 ),
209 RuntimeType::Object(_) => format!("an object with fields {}", self),
210 }
211 }
212
213 fn subtype(&self, sup: &RuntimeType) -> bool {
215 use RuntimeType::*;
216
217 match (self, sup) {
218 (Primitive(t1), Primitive(t2)) => t1.subtype(t2),
219 (Array(t1, l1), Array(t2, l2)) => t1.subtype(t2) && l1.subtype(*l2),
220 (Tuple(t1), Tuple(t2)) => t1.len() == t2.len() && t1.iter().zip(t2).all(|(t1, t2)| t1.subtype(t2)),
221 (Union(ts1), Union(ts2)) => ts1.iter().all(|t| ts2.contains(t)),
222 (t1, Union(ts2)) => ts2.iter().any(|t| t1.subtype(t)),
223 (Object(t1), Object(t2)) => t2
224 .iter()
225 .all(|(f, t)| t1.iter().any(|(ff, tt)| f == ff && tt.subtype(t))),
226 (Object(t1), Primitive(PrimitiveType::Axis2d)) => {
228 t1.iter()
229 .any(|(n, t)| n == "origin" && t.subtype(&RuntimeType::point2d()))
230 && t1
231 .iter()
232 .any(|(n, t)| n == "direction" && t.subtype(&RuntimeType::point2d()))
233 }
234 (Object(t1), Primitive(PrimitiveType::Axis3d)) => {
235 t1.iter()
236 .any(|(n, t)| n == "origin" && t.subtype(&RuntimeType::point3d()))
237 && t1
238 .iter()
239 .any(|(n, t)| n == "direction" && t.subtype(&RuntimeType::point3d()))
240 }
241 (Primitive(PrimitiveType::Axis2d), Object(t2)) => {
242 t2.iter()
243 .any(|(n, t)| n == "origin" && t.subtype(&RuntimeType::point2d()))
244 && t2
245 .iter()
246 .any(|(n, t)| n == "direction" && t.subtype(&RuntimeType::point2d()))
247 }
248 (Primitive(PrimitiveType::Axis3d), Object(t2)) => {
249 t2.iter()
250 .any(|(n, t)| n == "origin" && t.subtype(&RuntimeType::point3d()))
251 && t2
252 .iter()
253 .any(|(n, t)| n == "direction" && t.subtype(&RuntimeType::point3d()))
254 }
255 _ => false,
256 }
257 }
258
259 fn display_multiple(&self) -> String {
260 match self {
261 RuntimeType::Primitive(ty) => ty.display_multiple(),
262 RuntimeType::Array(..) => "arrays".to_owned(),
263 RuntimeType::Union(tys) => tys
264 .iter()
265 .map(|t| t.display_multiple())
266 .collect::<Vec<_>>()
267 .join(" or "),
268 RuntimeType::Tuple(_) => "arrays".to_owned(),
269 RuntimeType::Object(_) => format!("objects with fields {self}"),
270 }
271 }
272}
273
274impl fmt::Display for RuntimeType {
275 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
276 match self {
277 RuntimeType::Primitive(t) => t.fmt(f),
278 RuntimeType::Array(t, l) => match l {
279 ArrayLen::None => write!(f, "[{t}]"),
280 ArrayLen::NonEmpty => write!(f, "[{t}; 1+]"),
281 ArrayLen::Known(n) => write!(f, "[{t}; {n}]"),
282 },
283 RuntimeType::Tuple(ts) => write!(
284 f,
285 "[{}]",
286 ts.iter().map(|t| t.to_string()).collect::<Vec<_>>().join(", ")
287 ),
288 RuntimeType::Union(ts) => write!(
289 f,
290 "{}",
291 ts.iter().map(|t| t.to_string()).collect::<Vec<_>>().join(" | ")
292 ),
293 RuntimeType::Object(items) => write!(
294 f,
295 "{{ {} }}",
296 items
297 .iter()
298 .map(|(n, t)| format!("{n}: {t}"))
299 .collect::<Vec<_>>()
300 .join(", ")
301 ),
302 }
303 }
304}
305
306#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, ts_rs::TS, JsonSchema)]
307pub enum ArrayLen {
308 None,
309 NonEmpty,
310 Known(usize),
311}
312
313impl ArrayLen {
314 pub fn subtype(self, other: ArrayLen) -> bool {
315 match (self, other) {
316 (_, ArrayLen::None) => true,
317 (ArrayLen::NonEmpty, ArrayLen::NonEmpty) => true,
318 (ArrayLen::Known(size), ArrayLen::NonEmpty) if size > 0 => true,
319 (ArrayLen::Known(s1), ArrayLen::Known(s2)) if s1 == s2 => true,
320 _ => false,
321 }
322 }
323
324 fn satisfied(self, len: usize, allow_shrink: bool) -> Option<usize> {
326 match self {
327 ArrayLen::None => Some(len),
328 ArrayLen::NonEmpty => (len > 0).then_some(len),
329 ArrayLen::Known(s) => (if allow_shrink { len >= s } else { len == s }).then_some(s),
330 }
331 }
332}
333
334#[derive(Debug, Clone, PartialEq)]
335pub enum PrimitiveType {
336 Number(NumericType),
337 String,
338 Boolean,
339 Tag,
340 Sketch,
341 Solid,
342 Plane,
343 Helix,
344 Face,
345 Edge,
346 Axis2d,
347 Axis3d,
348 ImportedGeometry,
349}
350
351impl PrimitiveType {
352 fn display_multiple(&self) -> String {
353 match self {
354 PrimitiveType::Number(NumericType::Known(unit)) => format!("numbers({unit})"),
355 PrimitiveType::Number(_) => "numbers".to_owned(),
356 PrimitiveType::String => "strings".to_owned(),
357 PrimitiveType::Boolean => "bools".to_owned(),
358 PrimitiveType::Sketch => "Sketches".to_owned(),
359 PrimitiveType::Solid => "Solids".to_owned(),
360 PrimitiveType::Plane => "Planes".to_owned(),
361 PrimitiveType::Helix => "Helices".to_owned(),
362 PrimitiveType::Face => "Faces".to_owned(),
363 PrimitiveType::Edge => "Edges".to_owned(),
364 PrimitiveType::Axis2d => "2d axes".to_owned(),
365 PrimitiveType::Axis3d => "3d axes".to_owned(),
366 PrimitiveType::ImportedGeometry => "imported geometries".to_owned(),
367 PrimitiveType::Tag => "tags".to_owned(),
368 }
369 }
370
371 fn subtype(&self, other: &PrimitiveType) -> bool {
372 match (self, other) {
373 (PrimitiveType::Number(n1), PrimitiveType::Number(n2)) => n1.subtype(n2),
374 (t1, t2) => t1 == t2,
375 }
376 }
377}
378
379impl fmt::Display for PrimitiveType {
380 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
381 match self {
382 PrimitiveType::Number(NumericType::Known(unit)) => write!(f, "number({unit})"),
383 PrimitiveType::Number(NumericType::Unknown) => write!(f, "number(unknown units)"),
384 PrimitiveType::Number(NumericType::Default { .. }) => write!(f, "number(default units)"),
385 PrimitiveType::Number(NumericType::Any) => write!(f, "number(any units)"),
386 PrimitiveType::String => write!(f, "string"),
387 PrimitiveType::Boolean => write!(f, "bool"),
388 PrimitiveType::Tag => write!(f, "tag"),
389 PrimitiveType::Sketch => write!(f, "Sketch"),
390 PrimitiveType::Solid => write!(f, "Solid"),
391 PrimitiveType::Plane => write!(f, "Plane"),
392 PrimitiveType::Face => write!(f, "Face"),
393 PrimitiveType::Edge => write!(f, "Edge"),
394 PrimitiveType::Axis2d => write!(f, "Axis2d"),
395 PrimitiveType::Axis3d => write!(f, "Axis3d"),
396 PrimitiveType::Helix => write!(f, "Helix"),
397 PrimitiveType::ImportedGeometry => write!(f, "imported geometry"),
398 }
399 }
400}
401
402#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, ts_rs::TS, JsonSchema)]
403#[ts(export)]
404#[serde(tag = "type")]
405pub enum NumericType {
406 Known(UnitType),
408 Default { len: UnitLen, angle: UnitAngle },
410 Unknown,
412 Any,
414}
415
416impl Default for NumericType {
417 fn default() -> Self {
418 NumericType::Default {
419 len: UnitLen::default(),
420 angle: UnitAngle::default(),
421 }
422 }
423}
424
425impl NumericType {
426 pub const fn count() -> Self {
427 NumericType::Known(UnitType::Count)
428 }
429
430 pub const fn mm() -> Self {
431 NumericType::Known(UnitType::Length(UnitLen::Mm))
432 }
433
434 pub const fn radians() -> Self {
435 NumericType::Known(UnitType::Angle(UnitAngle::Radians))
436 }
437
438 pub const fn degrees() -> Self {
439 NumericType::Known(UnitType::Angle(UnitAngle::Degrees))
440 }
441
442 pub fn expect_default_length(&self) -> Self {
443 match self {
444 NumericType::Default { len, .. } => NumericType::Known(UnitType::Length(*len)),
445 _ => unreachable!(),
446 }
447 }
448
449 pub fn expect_default_angle(&self) -> Self {
450 match self {
451 NumericType::Default { angle, .. } => NumericType::Known(UnitType::Angle(*angle)),
452 _ => unreachable!(),
453 }
454 }
455
456 pub fn combine_eq(a: TyF64, b: TyF64) -> (f64, f64, NumericType) {
462 use NumericType::*;
463 match (a.ty, b.ty) {
464 (at, bt) if at == bt => (a.n, b.n, at),
465 (at, Any) => (a.n, b.n, at),
466 (Any, bt) => (a.n, b.n, bt),
467
468 (t @ Known(UnitType::Length(l1)), Known(UnitType::Length(l2))) => (a.n, l2.adjust_to(b.n, l1).0, t),
469 (t @ Known(UnitType::Angle(a1)), Known(UnitType::Angle(a2))) => (a.n, a2.adjust_to(b.n, a1).0, t),
470
471 (Known(UnitType::Count), Default { .. }) | (Default { .. }, Known(UnitType::Count)) => {
472 (a.n, b.n, Known(UnitType::Count))
473 }
474 (t @ Known(UnitType::Length(l1)), Default { len: l2, .. }) if l1 == l2 => (a.n, b.n, t),
475 (Default { len: l1, .. }, t @ Known(UnitType::Length(l2))) if l1 == l2 => (a.n, b.n, t),
476 (t @ Known(UnitType::Angle(a1)), Default { angle: a2, .. }) if a1 == a2 => (a.n, b.n, t),
477 (Default { angle: a1, .. }, t @ Known(UnitType::Angle(a2))) if a1 == a2 => (a.n, b.n, t),
478
479 _ => (a.n, b.n, Unknown),
480 }
481 }
482
483 pub fn combine_eq_coerce(a: TyF64, b: TyF64) -> (f64, f64, NumericType) {
491 use NumericType::*;
492 match (a.ty, b.ty) {
493 (at, bt) if at == bt => (a.n, b.n, at),
494 (at, Any) => (a.n, b.n, at),
495 (Any, bt) => (a.n, b.n, bt),
496
497 (Default { .. }, Default { .. }) | (_, Unknown) | (Unknown, _) => (a.n, b.n, Unknown),
498
499 (t @ Known(UnitType::Length(l1)), Known(UnitType::Length(l2))) => (a.n, l2.adjust_to(b.n, l1).0, t),
501 (t @ Known(UnitType::Angle(a1)), Known(UnitType::Angle(a2))) => (a.n, a2.adjust_to(b.n, a1).0, t),
502
503 (Known(_), Known(_)) => (a.n, b.n, Unknown),
505
506 (Known(UnitType::Count), Default { .. }) | (Default { .. }, Known(UnitType::Count)) => {
508 (a.n, b.n, Known(UnitType::Count))
509 }
510
511 (t @ Known(UnitType::Length(l1)), Default { len: l2, .. }) => (a.n, l2.adjust_to(b.n, l1).0, t),
512 (Default { len: l1, .. }, t @ Known(UnitType::Length(l2))) => (l1.adjust_to(a.n, l2).0, b.n, t),
513
514 (t @ Known(UnitType::Angle(a1)), Default { angle: a2, .. }) => (a.n, a2.adjust_to(b.n, a1).0, t),
515 (Default { angle: a1, .. }, t @ Known(UnitType::Angle(a2))) => (a1.adjust_to(a.n, a2).0, b.n, t),
516 }
517 }
518
519 pub fn combine_eq_array(input: &[TyF64]) -> (Vec<f64>, NumericType) {
520 use NumericType::*;
521 let result = input.iter().map(|t| t.n).collect();
522
523 let mut ty = Any;
524 for i in input {
525 if i.ty == Any || ty == i.ty {
526 continue;
527 }
528
529 match (&ty, &i.ty) {
531 (Any, Default { .. }) if i.n == 0.0 => {}
532 (Any, t) => {
533 ty = t.clone();
534 }
535 (_, Unknown) | (Default { .. }, Default { .. }) => return (result, Unknown),
536
537 (Known(UnitType::Count), Default { .. }) | (Default { .. }, Known(UnitType::Count)) => {
538 ty = Known(UnitType::Count);
539 }
540
541 (Known(UnitType::Length(l1)), Default { len: l2, .. }) if l1 == l2 || i.n == 0.0 => {}
542 (Known(UnitType::Angle(a1)), Default { angle: a2, .. }) if a1 == a2 || i.n == 0.0 => {}
543
544 (Default { len: l1, .. }, Known(UnitType::Length(l2))) if l1 == l2 => {
545 ty = Known(UnitType::Length(*l2));
546 }
547 (Default { angle: a1, .. }, Known(UnitType::Angle(a2))) if a1 == a2 => {
548 ty = Known(UnitType::Angle(*a2));
549 }
550
551 _ => return (result, Unknown),
552 }
553 }
554
555 if ty == Any && !input.is_empty() {
556 ty = input[0].ty.clone();
557 }
558
559 (result, ty)
560 }
561
562 pub fn combine_mul(a: TyF64, b: TyF64) -> (f64, f64, NumericType) {
564 use NumericType::*;
565 match (a.ty, b.ty) {
566 (at @ Default { .. }, bt @ Default { .. }) if at == bt => (a.n, b.n, at),
567 (Default { .. }, Default { .. }) => (a.n, b.n, Unknown),
568 (Known(UnitType::Count), bt) => (a.n, b.n, bt),
569 (at, Known(UnitType::Count)) => (a.n, b.n, at),
570 (at @ Known(_), Default { .. }) | (Default { .. }, at @ Known(_)) => (a.n, b.n, at),
571 (Any, Any) => (a.n, b.n, Any),
572 _ => (a.n, b.n, Unknown),
573 }
574 }
575
576 pub fn combine_div(a: TyF64, b: TyF64) -> (f64, f64, NumericType) {
578 use NumericType::*;
579 match (a.ty, b.ty) {
580 (at @ Default { .. }, bt @ Default { .. }) if at == bt => (a.n, b.n, at),
581 (at, bt) if at == bt => (a.n, b.n, Known(UnitType::Count)),
582 (Default { .. }, Default { .. }) => (a.n, b.n, Unknown),
583 (at, Known(UnitType::Count) | Any) => (a.n, b.n, at),
584 (at @ Known(_), Default { .. }) => (a.n, b.n, at),
585 (Known(UnitType::Count), _) => (a.n, b.n, Known(UnitType::Count)),
586 _ => (a.n, b.n, Unknown),
587 }
588 }
589
590 pub fn from_parsed(suffix: NumericSuffix, settings: &super::MetaSettings) -> Self {
591 match suffix {
592 NumericSuffix::None => NumericType::Default {
593 len: settings.default_length_units,
594 angle: settings.default_angle_units,
595 },
596 NumericSuffix::Count => NumericType::Known(UnitType::Count),
597 NumericSuffix::Length => NumericType::Known(UnitType::Length(UnitLen::Unknown)),
598 NumericSuffix::Angle => NumericType::Known(UnitType::Angle(UnitAngle::Unknown)),
599 NumericSuffix::Mm => NumericType::Known(UnitType::Length(UnitLen::Mm)),
600 NumericSuffix::Cm => NumericType::Known(UnitType::Length(UnitLen::Cm)),
601 NumericSuffix::M => NumericType::Known(UnitType::Length(UnitLen::M)),
602 NumericSuffix::Inch => NumericType::Known(UnitType::Length(UnitLen::Inches)),
603 NumericSuffix::Ft => NumericType::Known(UnitType::Length(UnitLen::Feet)),
604 NumericSuffix::Yd => NumericType::Known(UnitType::Length(UnitLen::Yards)),
605 NumericSuffix::Deg => NumericType::Known(UnitType::Angle(UnitAngle::Degrees)),
606 NumericSuffix::Rad => NumericType::Known(UnitType::Angle(UnitAngle::Radians)),
607 NumericSuffix::Unknown => NumericType::Unknown,
608 }
609 }
610
611 fn subtype(&self, other: &NumericType) -> bool {
612 use NumericType::*;
613
614 match (self, other) {
615 (_, Any) => true,
616 (a, b) if a == b => true,
617 (
618 NumericType::Known(UnitType::Length(_)) | NumericType::Default { .. },
619 NumericType::Known(UnitType::Length(UnitLen::Unknown)),
620 )
621 | (
622 NumericType::Known(UnitType::Angle(_)) | NumericType::Default { .. },
623 NumericType::Known(UnitType::Angle(UnitAngle::Unknown)),
624 ) => true,
625 (Unknown, _) | (_, Unknown) => false,
626 (_, _) => false,
627 }
628 }
629
630 fn is_unknown(&self) -> bool {
631 matches!(
632 self,
633 NumericType::Unknown
634 | NumericType::Known(UnitType::Angle(UnitAngle::Unknown))
635 | NumericType::Known(UnitType::Length(UnitLen::Unknown))
636 )
637 }
638
639 fn example_ty(&self) -> Option<String> {
640 match self {
641 Self::Known(t) if !self.is_unknown() => Some(t.to_string()),
642 Self::Default { len, .. } => Some(len.to_string()),
643 _ => None,
644 }
645 }
646
647 fn coerce(&self, val: &KclValue) -> Result<KclValue, CoercionError> {
648 let KclValue::Number { value, ty, meta } = val else {
649 return Err(val.into());
650 };
651
652 if ty.subtype(self) {
653 return Ok(KclValue::Number {
654 value: *value,
655 ty: ty.clone(),
656 meta: meta.clone(),
657 });
658 }
659
660 use NumericType::*;
662 match (ty, self) {
663 (Unknown, _) => Err(CoercionError::from(val).with_explicit(self.example_ty().unwrap_or("mm".to_owned()))),
665 (_, Unknown) => Err(val.into()),
666 (Any, _) => Ok(KclValue::Number {
667 value: *value,
668 ty: self.clone(),
669 meta: meta.clone(),
670 }),
671
672 (_, Default { .. }) => Ok(KclValue::Number {
675 value: *value,
676 ty: ty.clone(),
677 meta: meta.clone(),
678 }),
679
680 (Known(UnitType::Length(l1)), Known(UnitType::Length(l2))) => {
682 let (value, ty) = l1.adjust_to(*value, *l2);
683 Ok(KclValue::Number {
684 value,
685 ty: Known(UnitType::Length(ty)),
686 meta: meta.clone(),
687 })
688 }
689 (Known(UnitType::Angle(a1)), Known(UnitType::Angle(a2))) => {
690 let (value, ty) = a1.adjust_to(*value, *a2);
691 Ok(KclValue::Number {
692 value,
693 ty: Known(UnitType::Angle(ty)),
694 meta: meta.clone(),
695 })
696 }
697
698 (Known(_), Known(_)) => Err(val.into()),
700
701 (Default { .. }, Known(UnitType::Count)) => Ok(KclValue::Number {
703 value: *value,
704 ty: Known(UnitType::Count),
705 meta: meta.clone(),
706 }),
707
708 (Default { len: l1, .. }, Known(UnitType::Length(l2))) => {
709 let (value, ty) = l1.adjust_to(*value, *l2);
710 Ok(KclValue::Number {
711 value,
712 ty: Known(UnitType::Length(ty)),
713 meta: meta.clone(),
714 })
715 }
716
717 (Default { angle: a1, .. }, Known(UnitType::Angle(a2))) => {
718 let (value, ty) = a1.adjust_to(*value, *a2);
719 Ok(KclValue::Number {
720 value,
721 ty: Known(UnitType::Angle(ty)),
722 meta: meta.clone(),
723 })
724 }
725
726 (_, _) => unreachable!(),
727 }
728 }
729
730 pub fn expect_length(&self) -> UnitLen {
731 match self {
732 Self::Known(UnitType::Length(len)) | Self::Default { len, .. } => *len,
733 _ => unreachable!("Found {self:?}"),
734 }
735 }
736
737 pub fn as_length(&self) -> Option<UnitLen> {
738 match self {
739 Self::Known(UnitType::Length(len)) | Self::Default { len, .. } => Some(*len),
740 _ => None,
741 }
742 }
743}
744
745impl From<NumericType> for RuntimeType {
746 fn from(t: NumericType) -> RuntimeType {
747 RuntimeType::Primitive(PrimitiveType::Number(t))
748 }
749}
750
751impl From<UnitLen> for NumericType {
752 fn from(value: UnitLen) -> Self {
753 NumericType::Known(UnitType::Length(value))
754 }
755}
756
757impl From<UnitAngle> for NumericType {
758 fn from(value: UnitAngle) -> Self {
759 NumericType::Known(UnitType::Angle(value))
760 }
761}
762
763#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, ts_rs::TS, JsonSchema)]
764#[ts(export)]
765#[serde(tag = "type")]
766pub enum UnitType {
767 Count,
768 Length(UnitLen),
769 Angle(UnitAngle),
770}
771
772impl std::fmt::Display for UnitType {
773 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
774 match self {
775 UnitType::Count => write!(f, "Count"),
776 UnitType::Length(l) => l.fmt(f),
777 UnitType::Angle(a) => a.fmt(f),
778 }
779 }
780}
781
782#[derive(Debug, Default, Clone, Copy, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Eq)]
785#[ts(export)]
786#[serde(tag = "type")]
787pub enum UnitLen {
788 #[default]
789 Mm,
790 Cm,
791 M,
792 Inches,
793 Feet,
794 Yards,
795 Unknown,
796}
797
798impl UnitLen {
799 pub fn adjust_to(self, value: f64, to: UnitLen) -> (f64, UnitLen) {
800 use UnitLen::*;
801
802 if self == to {
803 return (value, to);
804 }
805
806 if to == Unknown {
807 return (value, self);
808 }
809
810 let (base, base_unit) = match self {
811 Mm => (value, Mm),
812 Cm => (value * 10.0, Mm),
813 M => (value * 1000.0, Mm),
814 Inches => (value, Inches),
815 Feet => (value * 12.0, Inches),
816 Yards => (value * 36.0, Inches),
817 Unknown => unreachable!(),
818 };
819 let (base, base_unit) = match (base_unit, to) {
820 (Mm, Inches) | (Mm, Feet) | (Mm, Yards) => (base / 25.4, Inches),
821 (Inches, Mm) | (Inches, Cm) | (Inches, M) => (base * 25.4, Mm),
822 _ => (base, base_unit),
823 };
824
825 let value = match (base_unit, to) {
826 (Mm, Mm) => base,
827 (Mm, Cm) => base / 10.0,
828 (Mm, M) => base / 1000.0,
829 (Inches, Inches) => base,
830 (Inches, Feet) => base / 12.0,
831 (Inches, Yards) => base / 36.0,
832 _ => unreachable!(),
833 };
834
835 (value, to)
836 }
837}
838
839impl std::fmt::Display for UnitLen {
840 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
841 match self {
842 UnitLen::Mm => write!(f, "mm"),
843 UnitLen::Cm => write!(f, "cm"),
844 UnitLen::M => write!(f, "m"),
845 UnitLen::Inches => write!(f, "in"),
846 UnitLen::Feet => write!(f, "ft"),
847 UnitLen::Yards => write!(f, "yd"),
848 UnitLen::Unknown => write!(f, "Length"),
849 }
850 }
851}
852
853impl TryFrom<NumericSuffix> for UnitLen {
854 type Error = ();
855
856 fn try_from(suffix: NumericSuffix) -> std::result::Result<Self, Self::Error> {
857 match suffix {
858 NumericSuffix::Mm => Ok(Self::Mm),
859 NumericSuffix::Cm => Ok(Self::Cm),
860 NumericSuffix::M => Ok(Self::M),
861 NumericSuffix::Inch => Ok(Self::Inches),
862 NumericSuffix::Ft => Ok(Self::Feet),
863 NumericSuffix::Yd => Ok(Self::Yards),
864 _ => Err(()),
865 }
866 }
867}
868
869impl From<crate::UnitLength> for UnitLen {
870 fn from(unit: crate::UnitLength) -> Self {
871 match unit {
872 crate::UnitLength::Cm => UnitLen::Cm,
873 crate::UnitLength::Ft => UnitLen::Feet,
874 crate::UnitLength::In => UnitLen::Inches,
875 crate::UnitLength::M => UnitLen::M,
876 crate::UnitLength::Mm => UnitLen::Mm,
877 crate::UnitLength::Yd => UnitLen::Yards,
878 }
879 }
880}
881
882impl From<UnitLen> for crate::UnitLength {
883 fn from(unit: UnitLen) -> Self {
884 match unit {
885 UnitLen::Cm => crate::UnitLength::Cm,
886 UnitLen::Feet => crate::UnitLength::Ft,
887 UnitLen::Inches => crate::UnitLength::In,
888 UnitLen::M => crate::UnitLength::M,
889 UnitLen::Mm => crate::UnitLength::Mm,
890 UnitLen::Yards => crate::UnitLength::Yd,
891 UnitLen::Unknown => unreachable!(),
892 }
893 }
894}
895
896impl From<UnitLen> for kittycad_modeling_cmds::units::UnitLength {
897 fn from(unit: UnitLen) -> Self {
898 match unit {
899 UnitLen::Cm => kittycad_modeling_cmds::units::UnitLength::Centimeters,
900 UnitLen::Feet => kittycad_modeling_cmds::units::UnitLength::Feet,
901 UnitLen::Inches => kittycad_modeling_cmds::units::UnitLength::Inches,
902 UnitLen::M => kittycad_modeling_cmds::units::UnitLength::Meters,
903 UnitLen::Mm => kittycad_modeling_cmds::units::UnitLength::Millimeters,
904 UnitLen::Yards => kittycad_modeling_cmds::units::UnitLength::Yards,
905 UnitLen::Unknown => unreachable!(),
906 }
907 }
908}
909
910#[derive(Debug, Default, Clone, Copy, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Eq)]
912#[ts(export)]
913#[serde(tag = "type")]
914pub enum UnitAngle {
915 #[default]
916 Degrees,
917 Radians,
918 Unknown,
919}
920
921impl UnitAngle {
922 pub fn adjust_to(self, value: f64, to: UnitAngle) -> (f64, UnitAngle) {
923 use std::f64::consts::PI;
924
925 use UnitAngle::*;
926
927 if to == Unknown {
928 return (value, self);
929 }
930
931 let value = match (self, to) {
932 (Degrees, Degrees) => value,
933 (Degrees, Radians) => (value / 180.0) * PI,
934 (Radians, Degrees) => 180.0 * value / PI,
935 (Radians, Radians) => value,
936 (Unknown, _) | (_, Unknown) => unreachable!(),
937 };
938
939 (value, to)
940 }
941}
942
943impl std::fmt::Display for UnitAngle {
944 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
945 match self {
946 UnitAngle::Degrees => write!(f, "deg"),
947 UnitAngle::Radians => write!(f, "rad"),
948 UnitAngle::Unknown => write!(f, "Angle"),
949 }
950 }
951}
952
953impl TryFrom<NumericSuffix> for UnitAngle {
954 type Error = ();
955
956 fn try_from(suffix: NumericSuffix) -> std::result::Result<Self, Self::Error> {
957 match suffix {
958 NumericSuffix::Deg => Ok(Self::Degrees),
959 NumericSuffix::Rad => Ok(Self::Radians),
960 _ => Err(()),
961 }
962 }
963}
964
965#[derive(Debug, Clone)]
966pub struct CoercionError {
967 pub found: Option<RuntimeType>,
968 pub explicit_coercion: Option<String>,
969}
970
971impl CoercionError {
972 fn with_explicit(mut self, c: String) -> Self {
973 self.explicit_coercion = Some(c);
974 self
975 }
976}
977
978impl From<&'_ KclValue> for CoercionError {
979 fn from(value: &'_ KclValue) -> Self {
980 CoercionError {
981 found: value.principal_type(),
982 explicit_coercion: None,
983 }
984 }
985}
986
987impl KclValue {
988 pub fn has_type(&self, ty: &RuntimeType) -> bool {
990 let Some(self_ty) = self.principal_type() else {
991 return false;
992 };
993
994 self_ty.subtype(ty)
995 }
996
997 pub fn coerce(&self, ty: &RuntimeType, exec_state: &mut ExecState) -> Result<KclValue, CoercionError> {
1004 match ty {
1005 RuntimeType::Primitive(ty) => self.coerce_to_primitive_type(ty, exec_state),
1006 RuntimeType::Array(ty, len) => self.coerce_to_array_type(ty, *len, exec_state, false),
1007 RuntimeType::Tuple(tys) => self.coerce_to_tuple_type(tys, exec_state),
1008 RuntimeType::Union(tys) => self.coerce_to_union_type(tys, exec_state),
1009 RuntimeType::Object(tys) => self.coerce_to_object_type(tys, exec_state),
1010 }
1011 }
1012
1013 fn coerce_to_primitive_type(
1014 &self,
1015 ty: &PrimitiveType,
1016 exec_state: &mut ExecState,
1017 ) -> Result<KclValue, CoercionError> {
1018 let value = match self {
1019 KclValue::MixedArray { value, .. } | KclValue::HomArray { value, .. } if value.len() == 1 => &value[0],
1020 _ => self,
1021 };
1022 match ty {
1023 PrimitiveType::Number(ty) => ty.coerce(value),
1024 PrimitiveType::String => match value {
1025 KclValue::String { .. } => Ok(value.clone()),
1026 _ => Err(self.into()),
1027 },
1028 PrimitiveType::Boolean => match value {
1029 KclValue::Bool { .. } => Ok(value.clone()),
1030 _ => Err(self.into()),
1031 },
1032 PrimitiveType::Sketch => match value {
1033 KclValue::Sketch { .. } => Ok(value.clone()),
1034 _ => Err(self.into()),
1035 },
1036 PrimitiveType::Solid => match value {
1037 KclValue::Solid { .. } => Ok(value.clone()),
1038 _ => Err(self.into()),
1039 },
1040 PrimitiveType::Plane => match value {
1041 KclValue::String { value: s, .. }
1042 if [
1043 "xy", "xz", "yz", "-xy", "-xz", "-yz", "XY", "XZ", "YZ", "-XY", "-XZ", "-YZ",
1044 ]
1045 .contains(&&**s) =>
1046 {
1047 Ok(value.clone())
1048 }
1049 KclValue::Plane { .. } => Ok(value.clone()),
1050 KclValue::Object { value, meta } => {
1051 let origin = value
1052 .get("origin")
1053 .and_then(Point3d::from_kcl_val)
1054 .ok_or(CoercionError::from(self))?;
1055 let x_axis = value
1056 .get("xAxis")
1057 .and_then(Point3d::from_kcl_val)
1058 .ok_or(CoercionError::from(self))?;
1059 let y_axis = value
1060 .get("yAxis")
1061 .and_then(Point3d::from_kcl_val)
1062 .ok_or(CoercionError::from(self))?;
1063
1064 if value.get("zAxis").is_some() {
1065 exec_state.warn(CompilationError::err(
1066 self.into(),
1067 "Object with a zAxis field is being coerced into a plane, but the zAxis is ignored.",
1068 ));
1069 }
1070
1071 let id = exec_state.mod_local.id_generator.next_uuid();
1072 let plane = Plane {
1073 id,
1074 #[cfg(feature = "artifact-graph")]
1075 artifact_id: id.into(),
1076 info: PlaneInfo {
1077 origin,
1078 x_axis: x_axis.normalize(),
1079 y_axis: y_axis.normalize(),
1080 },
1081 value: super::PlaneType::Uninit,
1082 meta: meta.clone(),
1083 };
1084
1085 Ok(KclValue::Plane { value: Box::new(plane) })
1086 }
1087 _ => Err(self.into()),
1088 },
1089 PrimitiveType::Face => match value {
1090 KclValue::Face { .. } => Ok(value.clone()),
1091 _ => Err(self.into()),
1092 },
1093 PrimitiveType::Helix => match value {
1094 KclValue::Helix { .. } => Ok(value.clone()),
1095 _ => Err(self.into()),
1096 },
1097 PrimitiveType::Edge => match value {
1098 KclValue::Uuid { .. } => Ok(value.clone()),
1099 KclValue::TagIdentifier { .. } => Ok(value.clone()),
1100 _ => Err(self.into()),
1101 },
1102 PrimitiveType::Axis2d => match value {
1103 KclValue::Object { value: values, meta } => {
1104 if values
1105 .get("origin")
1106 .ok_or(CoercionError::from(self))?
1107 .has_type(&RuntimeType::point2d())
1108 && values
1109 .get("direction")
1110 .ok_or(CoercionError::from(self))?
1111 .has_type(&RuntimeType::point2d())
1112 {
1113 return Ok(value.clone());
1114 }
1115
1116 let origin = values.get("origin").ok_or(self.into()).and_then(|p| {
1117 p.coerce_to_array_type(&RuntimeType::length(), ArrayLen::Known(2), exec_state, true)
1118 })?;
1119 let direction = values.get("direction").ok_or(self.into()).and_then(|p| {
1120 p.coerce_to_array_type(&RuntimeType::length(), ArrayLen::Known(2), exec_state, true)
1121 })?;
1122
1123 Ok(KclValue::Object {
1124 value: [("origin".to_owned(), origin), ("direction".to_owned(), direction)].into(),
1125 meta: meta.clone(),
1126 })
1127 }
1128 _ => Err(self.into()),
1129 },
1130 PrimitiveType::Axis3d => match value {
1131 KclValue::Object { value: values, meta } => {
1132 if values
1133 .get("origin")
1134 .ok_or(CoercionError::from(self))?
1135 .has_type(&RuntimeType::point3d())
1136 && values
1137 .get("direction")
1138 .ok_or(CoercionError::from(self))?
1139 .has_type(&RuntimeType::point3d())
1140 {
1141 return Ok(value.clone());
1142 }
1143
1144 let origin = values.get("origin").ok_or(self.into()).and_then(|p| {
1145 p.coerce_to_array_type(&RuntimeType::length(), ArrayLen::Known(3), exec_state, true)
1146 })?;
1147 let direction = values.get("direction").ok_or(self.into()).and_then(|p| {
1148 p.coerce_to_array_type(&RuntimeType::length(), ArrayLen::Known(3), exec_state, true)
1149 })?;
1150
1151 Ok(KclValue::Object {
1152 value: [("origin".to_owned(), origin), ("direction".to_owned(), direction)].into(),
1153 meta: meta.clone(),
1154 })
1155 }
1156 _ => Err(self.into()),
1157 },
1158 PrimitiveType::ImportedGeometry => match value {
1159 KclValue::ImportedGeometry { .. } => Ok(value.clone()),
1160 _ => Err(self.into()),
1161 },
1162 PrimitiveType::Tag => match value {
1163 KclValue::TagDeclarator { .. } | KclValue::TagIdentifier { .. } | KclValue::Uuid { .. } => {
1164 Ok(value.clone())
1165 }
1166 s @ KclValue::String { value, .. } if ["start", "end", "START", "END"].contains(&&**value) => {
1167 Ok(s.clone())
1168 }
1169 _ => Err(self.into()),
1170 },
1171 }
1172 }
1173
1174 fn coerce_to_array_type(
1175 &self,
1176 ty: &RuntimeType,
1177 len: ArrayLen,
1178 exec_state: &mut ExecState,
1179 allow_shrink: bool,
1180 ) -> Result<KclValue, CoercionError> {
1181 if len.satisfied(1, false).is_some() && self.has_type(ty) {
1182 return Ok(KclValue::HomArray {
1183 value: vec![self.clone()],
1184 ty: ty.clone(),
1185 });
1186 }
1187 match self {
1188 KclValue::HomArray { value, ty: aty } => {
1189 if aty.subtype(ty) {
1190 len.satisfied(value.len(), allow_shrink)
1191 .map(|len| KclValue::HomArray {
1192 value: value[..len].to_vec(),
1193 ty: aty.clone(),
1194 })
1195 .ok_or(self.into())
1196 } else {
1197 Err(self.into())
1198 }
1199 }
1200 KclValue::MixedArray { value, .. } => {
1201 let mut values = Vec::new();
1203 for item in value {
1204 if let KclValue::HomArray {
1205 ty: inner_ty,
1206 value: inner_value,
1207 } = item
1208 {
1209 if inner_ty.subtype(ty) {
1210 values.extend(inner_value.iter().cloned());
1211 } else {
1212 values.push(item.clone());
1213 }
1214 } else {
1215 values.push(item.clone());
1216 }
1217 }
1218
1219 let len = len
1220 .satisfied(values.len(), allow_shrink)
1221 .ok_or(CoercionError::from(self))?;
1222
1223 let value = values[..len]
1224 .iter()
1225 .map(|v| v.coerce(ty, exec_state))
1226 .collect::<Result<Vec<_>, _>>()?;
1227
1228 Ok(KclValue::HomArray { value, ty: ty.clone() })
1229 }
1230 KclValue::KclNone { .. } if len.satisfied(0, false).is_some() => Ok(KclValue::HomArray {
1231 value: Vec::new(),
1232 ty: ty.clone(),
1233 }),
1234 _ => Err(self.into()),
1235 }
1236 }
1237
1238 fn coerce_to_tuple_type(&self, tys: &[RuntimeType], exec_state: &mut ExecState) -> Result<KclValue, CoercionError> {
1239 match self {
1240 KclValue::MixedArray { value, .. } | KclValue::HomArray { value, .. } if value.len() == tys.len() => {
1241 let mut result = Vec::new();
1242 for (i, t) in tys.iter().enumerate() {
1243 result.push(value[i].coerce(t, exec_state)?);
1244 }
1245
1246 Ok(KclValue::MixedArray {
1247 value: result,
1248 meta: Vec::new(),
1249 })
1250 }
1251 KclValue::KclNone { meta, .. } if tys.is_empty() => Ok(KclValue::MixedArray {
1252 value: Vec::new(),
1253 meta: meta.clone(),
1254 }),
1255 value if tys.len() == 1 && value.has_type(&tys[0]) => Ok(KclValue::MixedArray {
1256 value: vec![value.clone()],
1257 meta: Vec::new(),
1258 }),
1259 _ => Err(self.into()),
1260 }
1261 }
1262
1263 fn coerce_to_union_type(&self, tys: &[RuntimeType], exec_state: &mut ExecState) -> Result<KclValue, CoercionError> {
1264 for t in tys {
1265 if let Ok(v) = self.coerce(t, exec_state) {
1266 return Ok(v);
1267 }
1268 }
1269
1270 Err(self.into())
1271 }
1272
1273 fn coerce_to_object_type(
1274 &self,
1275 tys: &[(String, RuntimeType)],
1276 _exec_state: &mut ExecState,
1277 ) -> Result<KclValue, CoercionError> {
1278 match self {
1279 KclValue::Object { value, .. } => {
1280 for (s, t) in tys {
1281 if !value.get(s).ok_or(CoercionError::from(self))?.has_type(t) {
1283 return Err(self.into());
1284 }
1285 }
1286 Ok(self.clone())
1288 }
1289 KclValue::KclNone { meta, .. } if tys.is_empty() => Ok(KclValue::Object {
1290 value: HashMap::new(),
1291 meta: meta.clone(),
1292 }),
1293 _ => Err(self.into()),
1294 }
1295 }
1296
1297 pub fn principal_type(&self) -> Option<RuntimeType> {
1298 match self {
1299 KclValue::Bool { .. } => Some(RuntimeType::Primitive(PrimitiveType::Boolean)),
1300 KclValue::Number { ty, .. } => Some(RuntimeType::Primitive(PrimitiveType::Number(ty.clone()))),
1301 KclValue::String { .. } => Some(RuntimeType::Primitive(PrimitiveType::String)),
1302 KclValue::Object { value, .. } => {
1303 let properties = value
1304 .iter()
1305 .map(|(k, v)| v.principal_type().map(|t| (k.clone(), t)))
1306 .collect::<Option<Vec<_>>>()?;
1307 Some(RuntimeType::Object(properties))
1308 }
1309 KclValue::Plane { .. } => Some(RuntimeType::Primitive(PrimitiveType::Plane)),
1310 KclValue::Sketch { .. } => Some(RuntimeType::Primitive(PrimitiveType::Sketch)),
1311 KclValue::Solid { .. } => Some(RuntimeType::Primitive(PrimitiveType::Solid)),
1312 KclValue::Face { .. } => Some(RuntimeType::Primitive(PrimitiveType::Face)),
1313 KclValue::Helix { .. } => Some(RuntimeType::Primitive(PrimitiveType::Helix)),
1314 KclValue::ImportedGeometry(..) => Some(RuntimeType::Primitive(PrimitiveType::ImportedGeometry)),
1315 KclValue::MixedArray { value, .. } => Some(RuntimeType::Tuple(
1316 value.iter().map(|v| v.principal_type()).collect::<Option<Vec<_>>>()?,
1317 )),
1318 KclValue::HomArray { ty, value, .. } => {
1319 Some(RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Known(value.len())))
1320 }
1321 KclValue::TagIdentifier(_) | KclValue::TagDeclarator(_) | KclValue::Uuid { .. } => {
1322 Some(RuntimeType::Primitive(PrimitiveType::Tag))
1323 }
1324 KclValue::Function { .. } | KclValue::Module { .. } | KclValue::KclNone { .. } | KclValue::Type { .. } => {
1325 None
1326 }
1327 }
1328 }
1329}
1330
1331#[cfg(test)]
1332mod test {
1333 use super::*;
1334 use crate::execution::{parse_execute, ExecTestResults};
1335
1336 fn values(exec_state: &mut ExecState) -> Vec<KclValue> {
1337 vec![
1338 KclValue::Bool {
1339 value: true,
1340 meta: Vec::new(),
1341 },
1342 KclValue::Number {
1343 value: 1.0,
1344 ty: NumericType::count(),
1345 meta: Vec::new(),
1346 },
1347 KclValue::String {
1348 value: "hello".to_owned(),
1349 meta: Vec::new(),
1350 },
1351 KclValue::MixedArray {
1352 value: Vec::new(),
1353 meta: Vec::new(),
1354 },
1355 KclValue::HomArray {
1356 value: Vec::new(),
1357 ty: RuntimeType::solid(),
1358 },
1359 KclValue::Object {
1360 value: crate::execution::KclObjectFields::new(),
1361 meta: Vec::new(),
1362 },
1363 KclValue::TagIdentifier(Box::new("foo".parse().unwrap())),
1364 KclValue::TagDeclarator(Box::new(crate::parsing::ast::types::TagDeclarator::new("foo"))),
1365 KclValue::Plane {
1366 value: Box::new(Plane::from_plane_data(crate::std::sketch::PlaneData::XY, exec_state).unwrap()),
1367 },
1368 KclValue::ImportedGeometry(crate::execution::ImportedGeometry::new(
1370 uuid::Uuid::nil(),
1371 Vec::new(),
1372 Vec::new(),
1373 )),
1374 ]
1376 }
1377
1378 #[track_caller]
1379 fn assert_coerce_results(
1380 value: &KclValue,
1381 super_type: &RuntimeType,
1382 expected_value: &KclValue,
1383 exec_state: &mut ExecState,
1384 ) {
1385 let is_subtype = value == expected_value;
1386 assert_eq!(&value.coerce(super_type, exec_state).unwrap(), expected_value);
1387 assert_eq!(
1388 is_subtype,
1389 value.principal_type().is_some() && value.principal_type().unwrap().subtype(super_type),
1390 "{:?} <: {super_type:?} should be {is_subtype}",
1391 value.principal_type().unwrap()
1392 );
1393 assert!(
1394 expected_value.principal_type().unwrap().subtype(super_type),
1395 "{} <: {super_type}",
1396 expected_value.principal_type().unwrap()
1397 )
1398 }
1399
1400 #[tokio::test(flavor = "multi_thread")]
1401 async fn coerce_idempotent() {
1402 let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock().await);
1403 let values = values(&mut exec_state);
1404 for v in &values {
1405 let ty = v.principal_type().unwrap();
1407 assert_coerce_results(v, &ty, v, &mut exec_state);
1408
1409 let uty1 = RuntimeType::Union(vec![ty.clone()]);
1411 let uty2 = RuntimeType::Union(vec![ty.clone(), RuntimeType::Primitive(PrimitiveType::Boolean)]);
1412 assert_coerce_results(v, &uty1, v, &mut exec_state);
1413 assert_coerce_results(v, &uty2, v, &mut exec_state);
1414
1415 let aty = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::None);
1417 let aty1 = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Known(1));
1418 let aty0 = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::NonEmpty);
1419
1420 assert_coerce_results(
1421 v,
1422 &aty,
1423 &KclValue::HomArray {
1424 value: vec![v.clone()],
1425 ty: ty.clone(),
1426 },
1427 &mut exec_state,
1428 );
1429 assert_coerce_results(
1430 v,
1431 &aty1,
1432 &KclValue::HomArray {
1433 value: vec![v.clone()],
1434 ty: ty.clone(),
1435 },
1436 &mut exec_state,
1437 );
1438 assert_coerce_results(
1439 v,
1440 &aty0,
1441 &KclValue::HomArray {
1442 value: vec![v.clone()],
1443 ty: ty.clone(),
1444 },
1445 &mut exec_state,
1446 );
1447
1448 let tty = RuntimeType::Tuple(vec![ty.clone()]);
1450 assert_coerce_results(
1451 v,
1452 &tty,
1453 &KclValue::MixedArray {
1454 value: vec![v.clone()],
1455 meta: Vec::new(),
1456 },
1457 &mut exec_state,
1458 );
1459 }
1460
1461 for v in &values[1..] {
1462 v.coerce(&RuntimeType::Primitive(PrimitiveType::Boolean), &mut exec_state)
1464 .unwrap_err();
1465 }
1466 }
1467
1468 #[tokio::test(flavor = "multi_thread")]
1469 async fn coerce_none() {
1470 let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock().await);
1471 let none = KclValue::KclNone {
1472 value: crate::parsing::ast::types::KclNone::new(),
1473 meta: Vec::new(),
1474 };
1475
1476 let aty = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::None);
1477 let aty0 = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::Known(0));
1478 let aty1 = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::Known(1));
1479 let aty1p = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::NonEmpty);
1480 assert_coerce_results(
1481 &none,
1482 &aty,
1483 &KclValue::HomArray {
1484 value: Vec::new(),
1485 ty: RuntimeType::solid(),
1486 },
1487 &mut exec_state,
1488 );
1489 assert_coerce_results(
1490 &none,
1491 &aty0,
1492 &KclValue::HomArray {
1493 value: Vec::new(),
1494 ty: RuntimeType::solid(),
1495 },
1496 &mut exec_state,
1497 );
1498 none.coerce(&aty1, &mut exec_state).unwrap_err();
1499 none.coerce(&aty1p, &mut exec_state).unwrap_err();
1500
1501 let tty = RuntimeType::Tuple(vec![]);
1502 let tty1 = RuntimeType::Tuple(vec![RuntimeType::solid()]);
1503 assert_coerce_results(
1504 &none,
1505 &tty,
1506 &KclValue::MixedArray {
1507 value: Vec::new(),
1508 meta: Vec::new(),
1509 },
1510 &mut exec_state,
1511 );
1512 none.coerce(&tty1, &mut exec_state).unwrap_err();
1513
1514 let oty = RuntimeType::Object(vec![]);
1515 assert_coerce_results(
1516 &none,
1517 &oty,
1518 &KclValue::Object {
1519 value: HashMap::new(),
1520 meta: Vec::new(),
1521 },
1522 &mut exec_state,
1523 );
1524 }
1525
1526 #[tokio::test(flavor = "multi_thread")]
1527 async fn coerce_record() {
1528 let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock().await);
1529
1530 let obj0 = KclValue::Object {
1531 value: HashMap::new(),
1532 meta: Vec::new(),
1533 };
1534 let obj1 = KclValue::Object {
1535 value: [(
1536 "foo".to_owned(),
1537 KclValue::Bool {
1538 value: true,
1539 meta: Vec::new(),
1540 },
1541 )]
1542 .into(),
1543 meta: Vec::new(),
1544 };
1545 let obj2 = KclValue::Object {
1546 value: [
1547 (
1548 "foo".to_owned(),
1549 KclValue::Bool {
1550 value: true,
1551 meta: Vec::new(),
1552 },
1553 ),
1554 (
1555 "bar".to_owned(),
1556 KclValue::Number {
1557 value: 0.0,
1558 ty: NumericType::count(),
1559 meta: Vec::new(),
1560 },
1561 ),
1562 (
1563 "baz".to_owned(),
1564 KclValue::Number {
1565 value: 42.0,
1566 ty: NumericType::count(),
1567 meta: Vec::new(),
1568 },
1569 ),
1570 ]
1571 .into(),
1572 meta: Vec::new(),
1573 };
1574
1575 let ty0 = RuntimeType::Object(vec![]);
1576 assert_coerce_results(&obj0, &ty0, &obj0, &mut exec_state);
1577 assert_coerce_results(&obj1, &ty0, &obj1, &mut exec_state);
1578 assert_coerce_results(&obj2, &ty0, &obj2, &mut exec_state);
1579
1580 let ty1 = RuntimeType::Object(vec![("foo".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean))]);
1581 obj0.coerce(&ty1, &mut exec_state).unwrap_err();
1582 assert_coerce_results(&obj1, &ty1, &obj1, &mut exec_state);
1583 assert_coerce_results(&obj2, &ty1, &obj2, &mut exec_state);
1584
1585 let ty2 = RuntimeType::Object(vec![
1587 (
1588 "bar".to_owned(),
1589 RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1590 ),
1591 ("foo".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean)),
1592 ]);
1593 obj0.coerce(&ty2, &mut exec_state).unwrap_err();
1594 obj1.coerce(&ty2, &mut exec_state).unwrap_err();
1595 assert_coerce_results(&obj2, &ty2, &obj2, &mut exec_state);
1596
1597 let tyq = RuntimeType::Object(vec![("qux".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean))]);
1599 obj0.coerce(&tyq, &mut exec_state).unwrap_err();
1600 obj1.coerce(&tyq, &mut exec_state).unwrap_err();
1601 obj2.coerce(&tyq, &mut exec_state).unwrap_err();
1602
1603 let ty1 = RuntimeType::Object(vec![("bar".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean))]);
1605 obj2.coerce(&ty1, &mut exec_state).unwrap_err();
1606 }
1607
1608 #[tokio::test(flavor = "multi_thread")]
1609 async fn coerce_array() {
1610 let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock().await);
1611
1612 let hom_arr = KclValue::HomArray {
1613 value: vec![
1614 KclValue::Number {
1615 value: 0.0,
1616 ty: NumericType::count(),
1617 meta: Vec::new(),
1618 },
1619 KclValue::Number {
1620 value: 1.0,
1621 ty: NumericType::count(),
1622 meta: Vec::new(),
1623 },
1624 KclValue::Number {
1625 value: 2.0,
1626 ty: NumericType::count(),
1627 meta: Vec::new(),
1628 },
1629 KclValue::Number {
1630 value: 3.0,
1631 ty: NumericType::count(),
1632 meta: Vec::new(),
1633 },
1634 ],
1635 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1636 };
1637 let mixed1 = KclValue::MixedArray {
1638 value: vec![
1639 KclValue::Number {
1640 value: 0.0,
1641 ty: NumericType::count(),
1642 meta: Vec::new(),
1643 },
1644 KclValue::Number {
1645 value: 1.0,
1646 ty: NumericType::count(),
1647 meta: Vec::new(),
1648 },
1649 ],
1650 meta: Vec::new(),
1651 };
1652 let mixed2 = KclValue::MixedArray {
1653 value: vec![
1654 KclValue::Number {
1655 value: 0.0,
1656 ty: NumericType::count(),
1657 meta: Vec::new(),
1658 },
1659 KclValue::Bool {
1660 value: true,
1661 meta: Vec::new(),
1662 },
1663 ],
1664 meta: Vec::new(),
1665 };
1666
1667 let tyh = RuntimeType::Array(
1669 Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
1670 ArrayLen::Known(4),
1671 );
1672 let tym1 = RuntimeType::Tuple(vec![
1673 RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1674 RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1675 ]);
1676 let tym2 = RuntimeType::Tuple(vec![
1677 RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1678 RuntimeType::Primitive(PrimitiveType::Boolean),
1679 ]);
1680 assert_coerce_results(&hom_arr, &tyh, &hom_arr, &mut exec_state);
1681 assert_coerce_results(&mixed1, &tym1, &mixed1, &mut exec_state);
1682 assert_coerce_results(&mixed2, &tym2, &mixed2, &mut exec_state);
1683 mixed1.coerce(&tym2, &mut exec_state).unwrap_err();
1684 mixed2.coerce(&tym1, &mut exec_state).unwrap_err();
1685
1686 let tyhn = RuntimeType::Array(
1688 Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
1689 ArrayLen::None,
1690 );
1691 let tyh1 = RuntimeType::Array(
1692 Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
1693 ArrayLen::NonEmpty,
1694 );
1695 let tyh3 = RuntimeType::Array(
1696 Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
1697 ArrayLen::Known(3),
1698 );
1699 assert_coerce_results(&hom_arr, &tyhn, &hom_arr, &mut exec_state);
1700 assert_coerce_results(&hom_arr, &tyh1, &hom_arr, &mut exec_state);
1701 hom_arr.coerce(&tyh3, &mut exec_state).unwrap_err();
1702
1703 let hom_arr0 = KclValue::HomArray {
1704 value: vec![],
1705 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1706 };
1707 assert_coerce_results(&hom_arr0, &tyhn, &hom_arr0, &mut exec_state);
1708 hom_arr0.coerce(&tyh1, &mut exec_state).unwrap_err();
1709 hom_arr0.coerce(&tyh3, &mut exec_state).unwrap_err();
1710
1711 let tym1 = RuntimeType::Tuple(vec![
1714 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
1715 RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1716 ]);
1717 let tym2 = RuntimeType::Tuple(vec![
1718 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
1719 RuntimeType::Primitive(PrimitiveType::Boolean),
1720 ]);
1721 assert_coerce_results(&mixed1, &tym1, &mixed1, &mut exec_state);
1724 assert_coerce_results(&mixed2, &tym2, &mixed2, &mut exec_state);
1725
1726 let hom_arr_2 = KclValue::HomArray {
1728 value: vec![
1729 KclValue::Number {
1730 value: 0.0,
1731 ty: NumericType::count(),
1732 meta: Vec::new(),
1733 },
1734 KclValue::Number {
1735 value: 1.0,
1736 ty: NumericType::count(),
1737 meta: Vec::new(),
1738 },
1739 ],
1740 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1741 };
1742 let mixed0 = KclValue::MixedArray {
1743 value: vec![],
1744 meta: Vec::new(),
1745 };
1746 assert_coerce_results(&mixed1, &tyhn, &hom_arr_2, &mut exec_state);
1747 assert_coerce_results(&mixed1, &tyh1, &hom_arr_2, &mut exec_state);
1748 assert_coerce_results(&mixed0, &tyhn, &hom_arr0, &mut exec_state);
1749 mixed0.coerce(&tyh, &mut exec_state).unwrap_err();
1750 mixed0.coerce(&tyh1, &mut exec_state).unwrap_err();
1751
1752 assert_coerce_results(&hom_arr_2, &tym1, &mixed1, &mut exec_state);
1754 hom_arr.coerce(&tym1, &mut exec_state).unwrap_err();
1755 hom_arr_2.coerce(&tym2, &mut exec_state).unwrap_err();
1756
1757 mixed0.coerce(&tym1, &mut exec_state).unwrap_err();
1758 mixed0.coerce(&tym2, &mut exec_state).unwrap_err();
1759 }
1760
1761 #[tokio::test(flavor = "multi_thread")]
1762 async fn coerce_union() {
1763 let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock().await);
1764
1765 assert!(RuntimeType::Union(vec![]).subtype(&RuntimeType::Union(vec![
1767 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
1768 RuntimeType::Primitive(PrimitiveType::Boolean)
1769 ])));
1770 assert!(
1771 RuntimeType::Union(vec![RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any))]).subtype(
1772 &RuntimeType::Union(vec![
1773 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
1774 RuntimeType::Primitive(PrimitiveType::Boolean)
1775 ])
1776 )
1777 );
1778 assert!(RuntimeType::Union(vec![
1779 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
1780 RuntimeType::Primitive(PrimitiveType::Boolean)
1781 ])
1782 .subtype(&RuntimeType::Union(vec![
1783 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
1784 RuntimeType::Primitive(PrimitiveType::Boolean)
1785 ])));
1786
1787 let count = KclValue::Number {
1789 value: 1.0,
1790 ty: NumericType::count(),
1791 meta: Vec::new(),
1792 };
1793
1794 let tya = RuntimeType::Union(vec![RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any))]);
1795 let tya2 = RuntimeType::Union(vec![
1796 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
1797 RuntimeType::Primitive(PrimitiveType::Boolean),
1798 ]);
1799 assert_coerce_results(&count, &tya, &count, &mut exec_state);
1800 assert_coerce_results(&count, &tya2, &count, &mut exec_state);
1801
1802 let tyb = RuntimeType::Union(vec![RuntimeType::Primitive(PrimitiveType::Boolean)]);
1804 let tyb2 = RuntimeType::Union(vec![
1805 RuntimeType::Primitive(PrimitiveType::Boolean),
1806 RuntimeType::Primitive(PrimitiveType::String),
1807 ]);
1808 count.coerce(&tyb, &mut exec_state).unwrap_err();
1809 count.coerce(&tyb2, &mut exec_state).unwrap_err();
1810 }
1811
1812 #[tokio::test(flavor = "multi_thread")]
1813 async fn coerce_axes() {
1814 let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock().await);
1815
1816 assert!(RuntimeType::Primitive(PrimitiveType::Axis2d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis2d)));
1818 assert!(RuntimeType::Primitive(PrimitiveType::Axis3d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis3d)));
1819 assert!(!RuntimeType::Primitive(PrimitiveType::Axis3d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis2d)));
1820 assert!(!RuntimeType::Primitive(PrimitiveType::Axis2d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis3d)));
1821
1822 let a2d = KclValue::Object {
1824 value: [
1825 (
1826 "origin".to_owned(),
1827 KclValue::HomArray {
1828 value: vec![
1829 KclValue::Number {
1830 value: 0.0,
1831 ty: NumericType::mm(),
1832 meta: Vec::new(),
1833 },
1834 KclValue::Number {
1835 value: 0.0,
1836 ty: NumericType::mm(),
1837 meta: Vec::new(),
1838 },
1839 ],
1840 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
1841 },
1842 ),
1843 (
1844 "direction".to_owned(),
1845 KclValue::HomArray {
1846 value: vec![
1847 KclValue::Number {
1848 value: 1.0,
1849 ty: NumericType::mm(),
1850 meta: Vec::new(),
1851 },
1852 KclValue::Number {
1853 value: 0.0,
1854 ty: NumericType::mm(),
1855 meta: Vec::new(),
1856 },
1857 ],
1858 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
1859 },
1860 ),
1861 ]
1862 .into(),
1863 meta: Vec::new(),
1864 };
1865 let a3d = KclValue::Object {
1866 value: [
1867 (
1868 "origin".to_owned(),
1869 KclValue::HomArray {
1870 value: vec![
1871 KclValue::Number {
1872 value: 0.0,
1873 ty: NumericType::mm(),
1874 meta: Vec::new(),
1875 },
1876 KclValue::Number {
1877 value: 0.0,
1878 ty: NumericType::mm(),
1879 meta: Vec::new(),
1880 },
1881 KclValue::Number {
1882 value: 0.0,
1883 ty: NumericType::mm(),
1884 meta: Vec::new(),
1885 },
1886 ],
1887 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
1888 },
1889 ),
1890 (
1891 "direction".to_owned(),
1892 KclValue::HomArray {
1893 value: vec![
1894 KclValue::Number {
1895 value: 1.0,
1896 ty: NumericType::mm(),
1897 meta: Vec::new(),
1898 },
1899 KclValue::Number {
1900 value: 0.0,
1901 ty: NumericType::mm(),
1902 meta: Vec::new(),
1903 },
1904 KclValue::Number {
1905 value: 1.0,
1906 ty: NumericType::mm(),
1907 meta: Vec::new(),
1908 },
1909 ],
1910 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
1911 },
1912 ),
1913 ]
1914 .into(),
1915 meta: Vec::new(),
1916 };
1917
1918 let ty2d = RuntimeType::Primitive(PrimitiveType::Axis2d);
1919 let ty3d = RuntimeType::Primitive(PrimitiveType::Axis3d);
1920
1921 assert_coerce_results(&a2d, &ty2d, &a2d, &mut exec_state);
1922 assert_coerce_results(&a3d, &ty3d, &a3d, &mut exec_state);
1923 assert_coerce_results(&a3d, &ty2d, &a2d, &mut exec_state);
1924 a2d.coerce(&ty3d, &mut exec_state).unwrap_err();
1925 }
1926
1927 #[tokio::test(flavor = "multi_thread")]
1928 async fn coerce_numeric() {
1929 let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock().await);
1930
1931 let count = KclValue::Number {
1932 value: 1.0,
1933 ty: NumericType::count(),
1934 meta: Vec::new(),
1935 };
1936 let mm = KclValue::Number {
1937 value: 1.0,
1938 ty: NumericType::mm(),
1939 meta: Vec::new(),
1940 };
1941 let inches = KclValue::Number {
1942 value: 1.0,
1943 ty: NumericType::Known(UnitType::Length(UnitLen::Inches)),
1944 meta: Vec::new(),
1945 };
1946 let rads = KclValue::Number {
1947 value: 1.0,
1948 ty: NumericType::Known(UnitType::Angle(UnitAngle::Radians)),
1949 meta: Vec::new(),
1950 };
1951 let default = KclValue::Number {
1952 value: 1.0,
1953 ty: NumericType::default(),
1954 meta: Vec::new(),
1955 };
1956 let any = KclValue::Number {
1957 value: 1.0,
1958 ty: NumericType::Any,
1959 meta: Vec::new(),
1960 };
1961 let unknown = KclValue::Number {
1962 value: 1.0,
1963 ty: NumericType::Unknown,
1964 meta: Vec::new(),
1965 };
1966
1967 assert_coerce_results(&count, &NumericType::count().into(), &count, &mut exec_state);
1969 assert_coerce_results(&mm, &NumericType::mm().into(), &mm, &mut exec_state);
1970 assert_coerce_results(&any, &NumericType::Any.into(), &any, &mut exec_state);
1971 assert_coerce_results(&unknown, &NumericType::Unknown.into(), &unknown, &mut exec_state);
1972 assert_coerce_results(&default, &NumericType::default().into(), &default, &mut exec_state);
1973
1974 assert_coerce_results(&count, &NumericType::Any.into(), &count, &mut exec_state);
1975 assert_coerce_results(&mm, &NumericType::Any.into(), &mm, &mut exec_state);
1976 assert_coerce_results(&unknown, &NumericType::Any.into(), &unknown, &mut exec_state);
1977 assert_coerce_results(&default, &NumericType::Any.into(), &default, &mut exec_state);
1978
1979 assert_eq!(
1980 default
1981 .coerce(
1982 &NumericType::Default {
1983 len: UnitLen::Yards,
1984 angle: UnitAngle::default()
1985 }
1986 .into(),
1987 &mut exec_state
1988 )
1989 .unwrap(),
1990 default
1991 );
1992
1993 count.coerce(&NumericType::mm().into(), &mut exec_state).unwrap_err();
1995 mm.coerce(&NumericType::count().into(), &mut exec_state).unwrap_err();
1996 unknown.coerce(&NumericType::mm().into(), &mut exec_state).unwrap_err();
1997 unknown
1998 .coerce(&NumericType::default().into(), &mut exec_state)
1999 .unwrap_err();
2000
2001 count.coerce(&NumericType::Unknown.into(), &mut exec_state).unwrap_err();
2002 mm.coerce(&NumericType::Unknown.into(), &mut exec_state).unwrap_err();
2003 default
2004 .coerce(&NumericType::Unknown.into(), &mut exec_state)
2005 .unwrap_err();
2006
2007 assert_eq!(
2008 inches
2009 .coerce(&NumericType::mm().into(), &mut exec_state)
2010 .unwrap()
2011 .as_f64()
2012 .unwrap()
2013 .round(),
2014 25.0
2015 );
2016 assert_eq!(
2017 rads.coerce(
2018 &NumericType::Known(UnitType::Angle(UnitAngle::Degrees)).into(),
2019 &mut exec_state
2020 )
2021 .unwrap()
2022 .as_f64()
2023 .unwrap()
2024 .round(),
2025 57.0
2026 );
2027 assert_eq!(
2028 inches
2029 .coerce(&NumericType::default().into(), &mut exec_state)
2030 .unwrap()
2031 .as_f64()
2032 .unwrap()
2033 .round(),
2034 1.0
2035 );
2036 assert_eq!(
2037 rads.coerce(&NumericType::default().into(), &mut exec_state)
2038 .unwrap()
2039 .as_f64()
2040 .unwrap()
2041 .round(),
2042 1.0
2043 );
2044 }
2045
2046 #[track_caller]
2047 fn assert_value_and_type(name: &str, result: &ExecTestResults, expected: f64, expected_ty: NumericType) {
2048 let mem = result.exec_state.stack();
2049 match mem
2050 .memory
2051 .get_from(name, result.mem_env, SourceRange::default(), 0)
2052 .unwrap()
2053 {
2054 KclValue::Number { value, ty, .. } => {
2055 assert_eq!(value.round(), expected);
2056 assert_eq!(*ty, expected_ty);
2057 }
2058 _ => unreachable!(),
2059 }
2060 }
2061
2062 #[tokio::test(flavor = "multi_thread")]
2063 async fn combine_numeric() {
2064 let program = r#"a = 5 + 4
2065b = 5 - 2
2066c = 5mm - 2mm + 10mm
2067d = 5mm - 2 + 10
2068e = 5 - 2mm + 10
2069f = 30mm - 1inch
2070
2071g = 2 * 10
2072h = 2 * 10mm
2073i = 2mm * 10mm
2074j = 2_ * 10
2075k = 2_ * 3mm * 3mm
2076
2077l = 1 / 10
2078m = 2mm / 1mm
2079n = 10inch / 2mm
2080o = 3mm / 3
2081p = 3_ / 4
2082q = 4inch / 2_
2083
2084r = min([0, 3, 42])
2085s = min([0, 3mm, -42])
2086t = min([100, 3in, 142mm])
2087u = min([3rad, 4in])
2088"#;
2089
2090 let result = parse_execute(program).await.unwrap();
2091 assert_eq!(
2092 result.exec_state.errors().len(),
2093 5,
2094 "errors: {:?}",
2095 result.exec_state.errors()
2096 );
2097
2098 assert_value_and_type("a", &result, 9.0, NumericType::default());
2099 assert_value_and_type("b", &result, 3.0, NumericType::default());
2100 assert_value_and_type("c", &result, 13.0, NumericType::mm());
2101 assert_value_and_type("d", &result, 13.0, NumericType::mm());
2102 assert_value_and_type("e", &result, 13.0, NumericType::mm());
2103 assert_value_and_type("f", &result, 5.0, NumericType::mm());
2104
2105 assert_value_and_type("g", &result, 20.0, NumericType::default());
2106 assert_value_and_type("h", &result, 20.0, NumericType::mm());
2107 assert_value_and_type("i", &result, 20.0, NumericType::Unknown);
2108 assert_value_and_type("j", &result, 20.0, NumericType::default());
2109 assert_value_and_type("k", &result, 18.0, NumericType::Unknown);
2110
2111 assert_value_and_type("l", &result, 0.0, NumericType::default());
2112 assert_value_and_type("m", &result, 2.0, NumericType::count());
2113 assert_value_and_type("n", &result, 5.0, NumericType::Unknown);
2114 assert_value_and_type("o", &result, 1.0, NumericType::mm());
2115 assert_value_and_type("p", &result, 1.0, NumericType::count());
2116 assert_value_and_type("q", &result, 2.0, NumericType::Known(UnitType::Length(UnitLen::Inches)));
2117
2118 assert_value_and_type("r", &result, 0.0, NumericType::default());
2119 assert_value_and_type("s", &result, -42.0, NumericType::mm());
2120 assert_value_and_type("t", &result, 3.0, NumericType::Unknown);
2121 assert_value_and_type("u", &result, 3.0, NumericType::Unknown);
2122 }
2123
2124 #[tokio::test(flavor = "multi_thread")]
2125 async fn bad_typed_arithmetic() {
2126 let program = r#"
2127a = 1rad
2128b = 180 / PI * a + 360
2129"#;
2130
2131 let result = parse_execute(program).await.unwrap();
2132
2133 assert_value_and_type("a", &result, 1.0, NumericType::radians());
2134 assert_value_and_type("b", &result, 417.0, NumericType::Unknown);
2135 }
2136
2137 #[tokio::test(flavor = "multi_thread")]
2138 async fn cos_coercions() {
2139 let program = r#"
2140a = cos(units::toRadians(30))
2141b = 3 / a
2142c = cos(30deg)
2143d = cos(30)
2144"#;
2145
2146 let result = parse_execute(program).await.unwrap();
2147 assert!(result.exec_state.errors().is_empty());
2148
2149 assert_value_and_type("a", &result, 1.0, NumericType::count());
2150 assert_value_and_type("b", &result, 3.0, NumericType::default());
2151 assert_value_and_type("c", &result, 1.0, NumericType::count());
2152 assert_value_and_type("d", &result, 1.0, NumericType::count());
2153 }
2154
2155 #[tokio::test(flavor = "multi_thread")]
2156 async fn coerce_nested_array() {
2157 let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock().await);
2158
2159 let mixed1 = KclValue::MixedArray {
2160 value: vec![
2161 KclValue::Number {
2162 value: 0.0,
2163 ty: NumericType::count(),
2164 meta: Vec::new(),
2165 },
2166 KclValue::Number {
2167 value: 1.0,
2168 ty: NumericType::count(),
2169 meta: Vec::new(),
2170 },
2171 KclValue::HomArray {
2172 value: vec![
2173 KclValue::Number {
2174 value: 2.0,
2175 ty: NumericType::count(),
2176 meta: Vec::new(),
2177 },
2178 KclValue::Number {
2179 value: 3.0,
2180 ty: NumericType::count(),
2181 meta: Vec::new(),
2182 },
2183 ],
2184 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2185 },
2186 ],
2187 meta: Vec::new(),
2188 };
2189
2190 let tym1 = RuntimeType::Array(
2192 Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2193 ArrayLen::NonEmpty,
2194 );
2195
2196 let result = KclValue::HomArray {
2197 value: vec![
2198 KclValue::Number {
2199 value: 0.0,
2200 ty: NumericType::count(),
2201 meta: Vec::new(),
2202 },
2203 KclValue::Number {
2204 value: 1.0,
2205 ty: NumericType::count(),
2206 meta: Vec::new(),
2207 },
2208 KclValue::Number {
2209 value: 2.0,
2210 ty: NumericType::count(),
2211 meta: Vec::new(),
2212 },
2213 KclValue::Number {
2214 value: 3.0,
2215 ty: NumericType::count(),
2216 meta: Vec::new(),
2217 },
2218 ],
2219 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2220 };
2221 assert_coerce_results(&mixed1, &tym1, &result, &mut exec_state);
2222 }
2223}