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