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 pub fn is_fully_specified(&self) -> bool {
668 !matches!(
669 self,
670 NumericType::Unknown
671 | NumericType::Known(UnitType::Angle(UnitAngle::Unknown))
672 | NumericType::Known(UnitType::Length(UnitLen::Unknown))
673 | NumericType::Any
674 | NumericType::Default { .. }
675 )
676 }
677
678 fn example_ty(&self) -> Option<String> {
679 match self {
680 Self::Known(t) if !self.is_unknown() => Some(t.to_string()),
681 Self::Default { len, .. } => Some(len.to_string()),
682 _ => None,
683 }
684 }
685
686 fn coerce(&self, val: &KclValue) -> Result<KclValue, CoercionError> {
687 let KclValue::Number { value, ty, meta } = val else {
688 return Err(val.into());
689 };
690
691 if ty.subtype(self) {
692 return Ok(KclValue::Number {
693 value: *value,
694 ty: ty.clone(),
695 meta: meta.clone(),
696 });
697 }
698
699 use NumericType::*;
701 match (ty, self) {
702 (Unknown, _) => Err(CoercionError::from(val).with_explicit(self.example_ty().unwrap_or("mm".to_owned()))),
704 (_, Unknown) => Err(val.into()),
705
706 (Any, _) => Ok(KclValue::Number {
707 value: *value,
708 ty: self.clone(),
709 meta: meta.clone(),
710 }),
711
712 (_, Default { .. }) => Ok(KclValue::Number {
715 value: *value,
716 ty: ty.clone(),
717 meta: meta.clone(),
718 }),
719
720 (Known(UnitType::Length(l1)), Known(UnitType::Length(l2))) => {
722 let (value, ty) = l1.adjust_to(*value, *l2);
723 Ok(KclValue::Number {
724 value,
725 ty: Known(UnitType::Length(ty)),
726 meta: meta.clone(),
727 })
728 }
729 (Known(UnitType::Angle(a1)), Known(UnitType::Angle(a2))) => {
730 let (value, ty) = a1.adjust_to(*value, *a2);
731 Ok(KclValue::Number {
732 value,
733 ty: Known(UnitType::Angle(ty)),
734 meta: meta.clone(),
735 })
736 }
737
738 (Known(_), Known(_)) => Err(val.into()),
740
741 (Default { .. }, Known(UnitType::Count)) => Ok(KclValue::Number {
743 value: *value,
744 ty: Known(UnitType::Count),
745 meta: meta.clone(),
746 }),
747
748 (Default { len: l1, .. }, Known(UnitType::Length(l2))) => {
749 let (value, ty) = l1.adjust_to(*value, *l2);
750 Ok(KclValue::Number {
751 value,
752 ty: Known(UnitType::Length(ty)),
753 meta: meta.clone(),
754 })
755 }
756
757 (Default { angle: a1, .. }, Known(UnitType::Angle(a2))) => {
758 let (value, ty) = a1.adjust_to(*value, *a2);
759 Ok(KclValue::Number {
760 value,
761 ty: Known(UnitType::Angle(ty)),
762 meta: meta.clone(),
763 })
764 }
765
766 (_, _) => unreachable!(),
767 }
768 }
769
770 pub fn expect_length(&self) -> UnitLen {
771 match self {
772 Self::Known(UnitType::Length(len)) | Self::Default { len, .. } => *len,
773 _ => unreachable!("Found {self:?}"),
774 }
775 }
776
777 pub fn as_length(&self) -> Option<UnitLen> {
778 match self {
779 Self::Known(UnitType::Length(len)) | Self::Default { len, .. } => Some(*len),
780 _ => None,
781 }
782 }
783}
784
785impl From<NumericType> for RuntimeType {
786 fn from(t: NumericType) -> RuntimeType {
787 RuntimeType::Primitive(PrimitiveType::Number(t))
788 }
789}
790
791impl From<UnitLen> for NumericType {
792 fn from(value: UnitLen) -> Self {
793 NumericType::Known(UnitType::Length(value))
794 }
795}
796
797impl From<UnitAngle> for NumericType {
798 fn from(value: UnitAngle) -> Self {
799 NumericType::Known(UnitType::Angle(value))
800 }
801}
802
803#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, ts_rs::TS, JsonSchema)]
804#[ts(export)]
805#[serde(tag = "type")]
806pub enum UnitType {
807 Count,
808 Length(UnitLen),
809 Angle(UnitAngle),
810}
811
812impl std::fmt::Display for UnitType {
813 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
814 match self {
815 UnitType::Count => write!(f, "Count"),
816 UnitType::Length(l) => l.fmt(f),
817 UnitType::Angle(a) => a.fmt(f),
818 }
819 }
820}
821
822#[derive(Debug, Default, Clone, Copy, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Eq)]
825#[ts(export)]
826#[serde(tag = "type")]
827pub enum UnitLen {
828 #[default]
829 Mm,
830 Cm,
831 M,
832 Inches,
833 Feet,
834 Yards,
835 Unknown,
836}
837
838impl UnitLen {
839 pub fn adjust_to(self, value: f64, to: UnitLen) -> (f64, UnitLen) {
840 use UnitLen::*;
841
842 if self == to {
843 return (value, to);
844 }
845
846 if to == Unknown {
847 return (value, self);
848 }
849
850 let (base, base_unit) = match self {
851 Mm => (value, Mm),
852 Cm => (value * 10.0, Mm),
853 M => (value * 1000.0, Mm),
854 Inches => (value, Inches),
855 Feet => (value * 12.0, Inches),
856 Yards => (value * 36.0, Inches),
857 Unknown => unreachable!(),
858 };
859 let (base, base_unit) = match (base_unit, to) {
860 (Mm, Inches) | (Mm, Feet) | (Mm, Yards) => (base / 25.4, Inches),
861 (Inches, Mm) | (Inches, Cm) | (Inches, M) => (base * 25.4, Mm),
862 _ => (base, base_unit),
863 };
864
865 let value = match (base_unit, to) {
866 (Mm, Mm) => base,
867 (Mm, Cm) => base / 10.0,
868 (Mm, M) => base / 1000.0,
869 (Inches, Inches) => base,
870 (Inches, Feet) => base / 12.0,
871 (Inches, Yards) => base / 36.0,
872 _ => unreachable!(),
873 };
874
875 (value, to)
876 }
877}
878
879impl std::fmt::Display for UnitLen {
880 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
881 match self {
882 UnitLen::Mm => write!(f, "mm"),
883 UnitLen::Cm => write!(f, "cm"),
884 UnitLen::M => write!(f, "m"),
885 UnitLen::Inches => write!(f, "in"),
886 UnitLen::Feet => write!(f, "ft"),
887 UnitLen::Yards => write!(f, "yd"),
888 UnitLen::Unknown => write!(f, "Length"),
889 }
890 }
891}
892
893impl TryFrom<NumericSuffix> for UnitLen {
894 type Error = ();
895
896 fn try_from(suffix: NumericSuffix) -> std::result::Result<Self, Self::Error> {
897 match suffix {
898 NumericSuffix::Mm => Ok(Self::Mm),
899 NumericSuffix::Cm => Ok(Self::Cm),
900 NumericSuffix::M => Ok(Self::M),
901 NumericSuffix::Inch => Ok(Self::Inches),
902 NumericSuffix::Ft => Ok(Self::Feet),
903 NumericSuffix::Yd => Ok(Self::Yards),
904 _ => Err(()),
905 }
906 }
907}
908
909impl From<crate::UnitLength> for UnitLen {
910 fn from(unit: crate::UnitLength) -> Self {
911 match unit {
912 crate::UnitLength::Cm => UnitLen::Cm,
913 crate::UnitLength::Ft => UnitLen::Feet,
914 crate::UnitLength::In => UnitLen::Inches,
915 crate::UnitLength::M => UnitLen::M,
916 crate::UnitLength::Mm => UnitLen::Mm,
917 crate::UnitLength::Yd => UnitLen::Yards,
918 }
919 }
920}
921
922impl From<UnitLen> for crate::UnitLength {
923 fn from(unit: UnitLen) -> Self {
924 match unit {
925 UnitLen::Cm => crate::UnitLength::Cm,
926 UnitLen::Feet => crate::UnitLength::Ft,
927 UnitLen::Inches => crate::UnitLength::In,
928 UnitLen::M => crate::UnitLength::M,
929 UnitLen::Mm => crate::UnitLength::Mm,
930 UnitLen::Yards => crate::UnitLength::Yd,
931 UnitLen::Unknown => unreachable!(),
932 }
933 }
934}
935
936impl From<UnitLen> for kittycad_modeling_cmds::units::UnitLength {
937 fn from(unit: UnitLen) -> Self {
938 match unit {
939 UnitLen::Cm => kittycad_modeling_cmds::units::UnitLength::Centimeters,
940 UnitLen::Feet => kittycad_modeling_cmds::units::UnitLength::Feet,
941 UnitLen::Inches => kittycad_modeling_cmds::units::UnitLength::Inches,
942 UnitLen::M => kittycad_modeling_cmds::units::UnitLength::Meters,
943 UnitLen::Mm => kittycad_modeling_cmds::units::UnitLength::Millimeters,
944 UnitLen::Yards => kittycad_modeling_cmds::units::UnitLength::Yards,
945 UnitLen::Unknown => unreachable!(),
946 }
947 }
948}
949
950#[derive(Debug, Default, Clone, Copy, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Eq)]
952#[ts(export)]
953#[serde(tag = "type")]
954pub enum UnitAngle {
955 #[default]
956 Degrees,
957 Radians,
958 Unknown,
959}
960
961impl UnitAngle {
962 pub fn adjust_to(self, value: f64, to: UnitAngle) -> (f64, UnitAngle) {
963 use std::f64::consts::PI;
964
965 use UnitAngle::*;
966
967 if to == Unknown {
968 return (value, self);
969 }
970
971 let value = match (self, to) {
972 (Degrees, Degrees) => value,
973 (Degrees, Radians) => (value / 180.0) * PI,
974 (Radians, Degrees) => 180.0 * value / PI,
975 (Radians, Radians) => value,
976 (Unknown, _) | (_, Unknown) => unreachable!(),
977 };
978
979 (value, to)
980 }
981}
982
983impl std::fmt::Display for UnitAngle {
984 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
985 match self {
986 UnitAngle::Degrees => write!(f, "deg"),
987 UnitAngle::Radians => write!(f, "rad"),
988 UnitAngle::Unknown => write!(f, "Angle"),
989 }
990 }
991}
992
993impl TryFrom<NumericSuffix> for UnitAngle {
994 type Error = ();
995
996 fn try_from(suffix: NumericSuffix) -> std::result::Result<Self, Self::Error> {
997 match suffix {
998 NumericSuffix::Deg => Ok(Self::Degrees),
999 NumericSuffix::Rad => Ok(Self::Radians),
1000 _ => Err(()),
1001 }
1002 }
1003}
1004
1005#[derive(Debug, Clone)]
1006pub struct CoercionError {
1007 pub found: Option<RuntimeType>,
1008 pub explicit_coercion: Option<String>,
1009}
1010
1011impl CoercionError {
1012 fn with_explicit(mut self, c: String) -> Self {
1013 self.explicit_coercion = Some(c);
1014 self
1015 }
1016}
1017
1018impl From<&'_ KclValue> for CoercionError {
1019 fn from(value: &'_ KclValue) -> Self {
1020 CoercionError {
1021 found: value.principal_type(),
1022 explicit_coercion: None,
1023 }
1024 }
1025}
1026
1027impl KclValue {
1028 pub fn has_type(&self, ty: &RuntimeType) -> bool {
1030 let Some(self_ty) = self.principal_type() else {
1031 return false;
1032 };
1033
1034 self_ty.subtype(ty)
1035 }
1036
1037 pub fn coerce(&self, ty: &RuntimeType, exec_state: &mut ExecState) -> Result<KclValue, CoercionError> {
1044 match ty {
1045 RuntimeType::Primitive(ty) => self.coerce_to_primitive_type(ty, exec_state),
1046 RuntimeType::Array(ty, len) => self.coerce_to_array_type(ty, *len, exec_state, false),
1047 RuntimeType::Tuple(tys) => self.coerce_to_tuple_type(tys, exec_state),
1048 RuntimeType::Union(tys) => self.coerce_to_union_type(tys, exec_state),
1049 RuntimeType::Object(tys) => self.coerce_to_object_type(tys, exec_state),
1050 }
1051 }
1052
1053 fn coerce_to_primitive_type(
1054 &self,
1055 ty: &PrimitiveType,
1056 exec_state: &mut ExecState,
1057 ) -> Result<KclValue, CoercionError> {
1058 let value = match self {
1059 KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } if value.len() == 1 => &value[0],
1060 _ => self,
1061 };
1062 match ty {
1063 PrimitiveType::Any => Ok(value.clone()),
1064 PrimitiveType::Number(ty) => ty.coerce(value),
1065 PrimitiveType::String => match value {
1066 KclValue::String { .. } => Ok(value.clone()),
1067 _ => Err(self.into()),
1068 },
1069 PrimitiveType::Boolean => match value {
1070 KclValue::Bool { .. } => Ok(value.clone()),
1071 _ => Err(self.into()),
1072 },
1073 PrimitiveType::Sketch => match value {
1074 KclValue::Sketch { .. } => Ok(value.clone()),
1075 _ => Err(self.into()),
1076 },
1077 PrimitiveType::Solid => match value {
1078 KclValue::Solid { .. } => Ok(value.clone()),
1079 _ => Err(self.into()),
1080 },
1081 PrimitiveType::Plane => match value {
1082 KclValue::String { value: s, .. }
1083 if [
1084 "xy", "xz", "yz", "-xy", "-xz", "-yz", "XY", "XZ", "YZ", "-XY", "-XZ", "-YZ",
1085 ]
1086 .contains(&&**s) =>
1087 {
1088 Ok(value.clone())
1089 }
1090 KclValue::Plane { .. } => Ok(value.clone()),
1091 KclValue::Object { value, meta } => {
1092 let origin = value
1093 .get("origin")
1094 .and_then(Point3d::from_kcl_val)
1095 .ok_or(CoercionError::from(self))?;
1096 let x_axis = value
1097 .get("xAxis")
1098 .and_then(Point3d::from_kcl_val)
1099 .ok_or(CoercionError::from(self))?;
1100 let y_axis = value
1101 .get("yAxis")
1102 .and_then(Point3d::from_kcl_val)
1103 .ok_or(CoercionError::from(self))?;
1104
1105 if value.get("zAxis").is_some() {
1106 exec_state.warn(CompilationError::err(
1107 self.into(),
1108 "Object with a zAxis field is being coerced into a plane, but the zAxis is ignored.",
1109 ));
1110 }
1111
1112 let id = exec_state.mod_local.id_generator.next_uuid();
1113 let plane = Plane {
1114 id,
1115 artifact_id: id.into(),
1116 info: PlaneInfo {
1117 origin,
1118 x_axis: x_axis.normalize(),
1119 y_axis: y_axis.normalize(),
1120 },
1121 value: super::PlaneType::Uninit,
1122 meta: meta.clone(),
1123 };
1124
1125 Ok(KclValue::Plane { value: Box::new(plane) })
1126 }
1127 _ => Err(self.into()),
1128 },
1129 PrimitiveType::Face => match value {
1130 KclValue::Face { .. } => Ok(value.clone()),
1131 _ => Err(self.into()),
1132 },
1133 PrimitiveType::Helix => match value {
1134 KclValue::Helix { .. } => Ok(value.clone()),
1135 _ => Err(self.into()),
1136 },
1137 PrimitiveType::Edge => match value {
1138 KclValue::Uuid { .. } => Ok(value.clone()),
1139 KclValue::TagIdentifier { .. } => Ok(value.clone()),
1140 _ => Err(self.into()),
1141 },
1142 PrimitiveType::Axis2d => match value {
1143 KclValue::Object { value: values, meta } => {
1144 if values
1145 .get("origin")
1146 .ok_or(CoercionError::from(self))?
1147 .has_type(&RuntimeType::point2d())
1148 && values
1149 .get("direction")
1150 .ok_or(CoercionError::from(self))?
1151 .has_type(&RuntimeType::point2d())
1152 {
1153 return Ok(value.clone());
1154 }
1155
1156 let origin = values.get("origin").ok_or(self.into()).and_then(|p| {
1157 p.coerce_to_array_type(&RuntimeType::length(), ArrayLen::Known(2), exec_state, true)
1158 })?;
1159 let direction = values.get("direction").ok_or(self.into()).and_then(|p| {
1160 p.coerce_to_array_type(&RuntimeType::length(), ArrayLen::Known(2), exec_state, true)
1161 })?;
1162
1163 Ok(KclValue::Object {
1164 value: [("origin".to_owned(), origin), ("direction".to_owned(), direction)].into(),
1165 meta: meta.clone(),
1166 })
1167 }
1168 _ => Err(self.into()),
1169 },
1170 PrimitiveType::Axis3d => match value {
1171 KclValue::Object { value: values, meta } => {
1172 if values
1173 .get("origin")
1174 .ok_or(CoercionError::from(self))?
1175 .has_type(&RuntimeType::point3d())
1176 && values
1177 .get("direction")
1178 .ok_or(CoercionError::from(self))?
1179 .has_type(&RuntimeType::point3d())
1180 {
1181 return Ok(value.clone());
1182 }
1183
1184 let origin = values.get("origin").ok_or(self.into()).and_then(|p| {
1185 p.coerce_to_array_type(&RuntimeType::length(), ArrayLen::Known(3), exec_state, true)
1186 })?;
1187 let direction = values.get("direction").ok_or(self.into()).and_then(|p| {
1188 p.coerce_to_array_type(&RuntimeType::length(), ArrayLen::Known(3), exec_state, true)
1189 })?;
1190
1191 Ok(KclValue::Object {
1192 value: [("origin".to_owned(), origin), ("direction".to_owned(), direction)].into(),
1193 meta: meta.clone(),
1194 })
1195 }
1196 _ => Err(self.into()),
1197 },
1198 PrimitiveType::ImportedGeometry => match value {
1199 KclValue::ImportedGeometry { .. } => Ok(value.clone()),
1200 _ => Err(self.into()),
1201 },
1202 PrimitiveType::Function => match value {
1203 KclValue::Function { .. } => Ok(value.clone()),
1204 _ => Err(self.into()),
1205 },
1206 PrimitiveType::TagId => match value {
1207 KclValue::TagIdentifier { .. } => Ok(value.clone()),
1208 _ => Err(self.into()),
1209 },
1210 PrimitiveType::Tag => match value {
1211 KclValue::TagDeclarator { .. } | KclValue::TagIdentifier { .. } | KclValue::Uuid { .. } => {
1212 Ok(value.clone())
1213 }
1214 s @ KclValue::String { value, .. } if ["start", "end", "START", "END"].contains(&&**value) => {
1215 Ok(s.clone())
1216 }
1217 _ => Err(self.into()),
1218 },
1219 }
1220 }
1221
1222 fn coerce_to_array_type(
1223 &self,
1224 ty: &RuntimeType,
1225 len: ArrayLen,
1226 exec_state: &mut ExecState,
1227 allow_shrink: bool,
1228 ) -> Result<KclValue, CoercionError> {
1229 match self {
1230 KclValue::HomArray { value, ty: aty, .. } => {
1231 let satisfied_len = len.satisfied(value.len(), allow_shrink);
1232
1233 if aty.subtype(ty) {
1234 return satisfied_len
1241 .map(|len| KclValue::HomArray {
1242 value: value[..len].to_vec(),
1243 ty: aty.clone(),
1244 })
1245 .ok_or(self.into());
1246 }
1247
1248 if let Some(satisfied_len) = satisfied_len {
1250 let value_result = value
1251 .iter()
1252 .take(satisfied_len)
1253 .map(|v| v.coerce(ty, exec_state))
1254 .collect::<Result<Vec<_>, _>>();
1255
1256 if let Ok(value) = value_result {
1257 return Ok(KclValue::HomArray { value, ty: ty.clone() });
1259 }
1260 }
1261
1262 let mut values = Vec::new();
1264 for item in value {
1265 if let KclValue::HomArray { value: inner_value, .. } = item {
1266 for item in inner_value {
1268 values.push(item.coerce(ty, exec_state)?);
1269 }
1270 } else {
1271 values.push(item.coerce(ty, exec_state)?);
1272 }
1273 }
1274
1275 let len = len
1276 .satisfied(values.len(), allow_shrink)
1277 .ok_or(CoercionError::from(self))?;
1278
1279 if len > values.len() {
1280 let message = format!(
1281 "Internal: Expected coerced array length {len} to be less than or equal to original length {}",
1282 values.len()
1283 );
1284 exec_state.err(CompilationError::err(self.into(), message.clone()));
1285 #[cfg(debug_assertions)]
1286 panic!("{message}");
1287 }
1288 values.truncate(len);
1289
1290 Ok(KclValue::HomArray {
1291 value: values,
1292 ty: ty.clone(),
1293 })
1294 }
1295 KclValue::Tuple { value, .. } => {
1296 let len = len
1297 .satisfied(value.len(), allow_shrink)
1298 .ok_or(CoercionError::from(self))?;
1299 let value = value
1300 .iter()
1301 .map(|item| item.coerce(ty, exec_state))
1302 .take(len)
1303 .collect::<Result<Vec<_>, _>>()?;
1304
1305 Ok(KclValue::HomArray { value, ty: ty.clone() })
1306 }
1307 KclValue::KclNone { .. } if len.satisfied(0, false).is_some() => Ok(KclValue::HomArray {
1308 value: Vec::new(),
1309 ty: ty.clone(),
1310 }),
1311 _ if len.satisfied(1, false).is_some() => Ok(KclValue::HomArray {
1312 value: vec![self.coerce(ty, exec_state)?],
1313 ty: ty.clone(),
1314 }),
1315 _ => Err(self.into()),
1316 }
1317 }
1318
1319 fn coerce_to_tuple_type(&self, tys: &[RuntimeType], exec_state: &mut ExecState) -> Result<KclValue, CoercionError> {
1320 match self {
1321 KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } if value.len() == tys.len() => {
1322 let mut result = Vec::new();
1323 for (i, t) in tys.iter().enumerate() {
1324 result.push(value[i].coerce(t, exec_state)?);
1325 }
1326
1327 Ok(KclValue::Tuple {
1328 value: result,
1329 meta: Vec::new(),
1330 })
1331 }
1332 KclValue::KclNone { meta, .. } if tys.is_empty() => Ok(KclValue::Tuple {
1333 value: Vec::new(),
1334 meta: meta.clone(),
1335 }),
1336 value if tys.len() == 1 && value.has_type(&tys[0]) => Ok(KclValue::Tuple {
1337 value: vec![value.clone()],
1338 meta: Vec::new(),
1339 }),
1340 _ => Err(self.into()),
1341 }
1342 }
1343
1344 fn coerce_to_union_type(&self, tys: &[RuntimeType], exec_state: &mut ExecState) -> Result<KclValue, CoercionError> {
1345 for t in tys {
1346 if let Ok(v) = self.coerce(t, exec_state) {
1347 return Ok(v);
1348 }
1349 }
1350
1351 Err(self.into())
1352 }
1353
1354 fn coerce_to_object_type(
1355 &self,
1356 tys: &[(String, RuntimeType)],
1357 _exec_state: &mut ExecState,
1358 ) -> Result<KclValue, CoercionError> {
1359 match self {
1360 KclValue::Object { value, .. } => {
1361 for (s, t) in tys {
1362 if !value.get(s).ok_or(CoercionError::from(self))?.has_type(t) {
1364 return Err(self.into());
1365 }
1366 }
1367 Ok(self.clone())
1369 }
1370 KclValue::KclNone { meta, .. } if tys.is_empty() => Ok(KclValue::Object {
1371 value: HashMap::new(),
1372 meta: meta.clone(),
1373 }),
1374 _ => Err(self.into()),
1375 }
1376 }
1377
1378 pub fn principal_type(&self) -> Option<RuntimeType> {
1379 match self {
1380 KclValue::Bool { .. } => Some(RuntimeType::Primitive(PrimitiveType::Boolean)),
1381 KclValue::Number { ty, .. } => Some(RuntimeType::Primitive(PrimitiveType::Number(ty.clone()))),
1382 KclValue::String { .. } => Some(RuntimeType::Primitive(PrimitiveType::String)),
1383 KclValue::Object { value, .. } => {
1384 let properties = value
1385 .iter()
1386 .map(|(k, v)| v.principal_type().map(|t| (k.clone(), t)))
1387 .collect::<Option<Vec<_>>>()?;
1388 Some(RuntimeType::Object(properties))
1389 }
1390 KclValue::Plane { .. } => Some(RuntimeType::Primitive(PrimitiveType::Plane)),
1391 KclValue::Sketch { .. } => Some(RuntimeType::Primitive(PrimitiveType::Sketch)),
1392 KclValue::Solid { .. } => Some(RuntimeType::Primitive(PrimitiveType::Solid)),
1393 KclValue::Face { .. } => Some(RuntimeType::Primitive(PrimitiveType::Face)),
1394 KclValue::Helix { .. } => Some(RuntimeType::Primitive(PrimitiveType::Helix)),
1395 KclValue::ImportedGeometry(..) => Some(RuntimeType::Primitive(PrimitiveType::ImportedGeometry)),
1396 KclValue::Tuple { value, .. } => Some(RuntimeType::Tuple(
1397 value.iter().map(|v| v.principal_type()).collect::<Option<Vec<_>>>()?,
1398 )),
1399 KclValue::HomArray { ty, value, .. } => {
1400 Some(RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Known(value.len())))
1401 }
1402 KclValue::TagIdentifier(_) => Some(RuntimeType::Primitive(PrimitiveType::TagId)),
1403 KclValue::TagDeclarator(_) | KclValue::Uuid { .. } => Some(RuntimeType::Primitive(PrimitiveType::Tag)),
1404 KclValue::Function { .. } => Some(RuntimeType::Primitive(PrimitiveType::Function)),
1405 KclValue::Module { .. } | KclValue::KclNone { .. } | KclValue::Type { .. } => None,
1406 }
1407 }
1408}
1409
1410#[cfg(test)]
1411mod test {
1412 use super::*;
1413 use crate::execution::{parse_execute, ExecTestResults};
1414
1415 fn values(exec_state: &mut ExecState) -> Vec<KclValue> {
1416 vec![
1417 KclValue::Bool {
1418 value: true,
1419 meta: Vec::new(),
1420 },
1421 KclValue::Number {
1422 value: 1.0,
1423 ty: NumericType::count(),
1424 meta: Vec::new(),
1425 },
1426 KclValue::String {
1427 value: "hello".to_owned(),
1428 meta: Vec::new(),
1429 },
1430 KclValue::Tuple {
1431 value: Vec::new(),
1432 meta: Vec::new(),
1433 },
1434 KclValue::HomArray {
1435 value: Vec::new(),
1436 ty: RuntimeType::solid(),
1437 },
1438 KclValue::Object {
1439 value: crate::execution::KclObjectFields::new(),
1440 meta: Vec::new(),
1441 },
1442 KclValue::TagIdentifier(Box::new("foo".parse().unwrap())),
1443 KclValue::TagDeclarator(Box::new(crate::parsing::ast::types::TagDeclarator::new("foo"))),
1444 KclValue::Plane {
1445 value: Box::new(Plane::from_plane_data(crate::std::sketch::PlaneData::XY, exec_state).unwrap()),
1446 },
1447 KclValue::ImportedGeometry(crate::execution::ImportedGeometry::new(
1449 uuid::Uuid::nil(),
1450 Vec::new(),
1451 Vec::new(),
1452 )),
1453 ]
1455 }
1456
1457 #[track_caller]
1458 fn assert_coerce_results(
1459 value: &KclValue,
1460 super_type: &RuntimeType,
1461 expected_value: &KclValue,
1462 exec_state: &mut ExecState,
1463 ) {
1464 let is_subtype = value == expected_value;
1465 assert_eq!(&value.coerce(super_type, exec_state).unwrap(), expected_value);
1466 assert_eq!(
1467 is_subtype,
1468 value.principal_type().is_some() && value.principal_type().unwrap().subtype(super_type),
1469 "{:?} <: {super_type:?} should be {is_subtype}",
1470 value.principal_type().unwrap()
1471 );
1472 assert!(
1473 expected_value.principal_type().unwrap().subtype(super_type),
1474 "{} <: {super_type}",
1475 expected_value.principal_type().unwrap()
1476 )
1477 }
1478
1479 #[tokio::test(flavor = "multi_thread")]
1480 async fn coerce_idempotent() {
1481 let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock(None).await);
1482 let values = values(&mut exec_state);
1483 for v in &values {
1484 let ty = v.principal_type().unwrap();
1486 assert_coerce_results(v, &ty, v, &mut exec_state);
1487
1488 let uty1 = RuntimeType::Union(vec![ty.clone()]);
1490 let uty2 = RuntimeType::Union(vec![ty.clone(), RuntimeType::Primitive(PrimitiveType::Boolean)]);
1491 assert_coerce_results(v, &uty1, v, &mut exec_state);
1492 assert_coerce_results(v, &uty2, v, &mut exec_state);
1493
1494 let aty = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::None);
1496 let aty1 = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Known(1));
1497 let aty0 = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::NonEmpty);
1498
1499 match v {
1500 KclValue::Tuple { .. } | KclValue::HomArray { .. } => {
1501 assert_coerce_results(
1503 v,
1504 &aty,
1505 &KclValue::HomArray {
1506 value: vec![],
1507 ty: ty.clone(),
1508 },
1509 &mut exec_state,
1510 );
1511 v.coerce(&aty1, &mut exec_state).unwrap_err();
1514 v.coerce(&aty0, &mut exec_state).unwrap_err();
1517 }
1518 _ => {
1519 assert_coerce_results(
1520 v,
1521 &aty,
1522 &KclValue::HomArray {
1523 value: vec![v.clone()],
1524 ty: ty.clone(),
1525 },
1526 &mut exec_state,
1527 );
1528 assert_coerce_results(
1529 v,
1530 &aty1,
1531 &KclValue::HomArray {
1532 value: vec![v.clone()],
1533 ty: ty.clone(),
1534 },
1535 &mut exec_state,
1536 );
1537 assert_coerce_results(
1538 v,
1539 &aty0,
1540 &KclValue::HomArray {
1541 value: vec![v.clone()],
1542 ty: ty.clone(),
1543 },
1544 &mut exec_state,
1545 );
1546
1547 let tty = RuntimeType::Tuple(vec![ty.clone()]);
1549 assert_coerce_results(
1550 v,
1551 &tty,
1552 &KclValue::Tuple {
1553 value: vec![v.clone()],
1554 meta: Vec::new(),
1555 },
1556 &mut exec_state,
1557 );
1558 }
1559 }
1560 }
1561
1562 for v in &values[1..] {
1563 v.coerce(&RuntimeType::Primitive(PrimitiveType::Boolean), &mut exec_state)
1565 .unwrap_err();
1566 }
1567 }
1568
1569 #[tokio::test(flavor = "multi_thread")]
1570 async fn coerce_none() {
1571 let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock(None).await);
1572 let none = KclValue::KclNone {
1573 value: crate::parsing::ast::types::KclNone::new(),
1574 meta: Vec::new(),
1575 };
1576
1577 let aty = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::None);
1578 let aty0 = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::Known(0));
1579 let aty1 = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::Known(1));
1580 let aty1p = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::NonEmpty);
1581 assert_coerce_results(
1582 &none,
1583 &aty,
1584 &KclValue::HomArray {
1585 value: Vec::new(),
1586 ty: RuntimeType::solid(),
1587 },
1588 &mut exec_state,
1589 );
1590 assert_coerce_results(
1591 &none,
1592 &aty0,
1593 &KclValue::HomArray {
1594 value: Vec::new(),
1595 ty: RuntimeType::solid(),
1596 },
1597 &mut exec_state,
1598 );
1599 none.coerce(&aty1, &mut exec_state).unwrap_err();
1600 none.coerce(&aty1p, &mut exec_state).unwrap_err();
1601
1602 let tty = RuntimeType::Tuple(vec![]);
1603 let tty1 = RuntimeType::Tuple(vec![RuntimeType::solid()]);
1604 assert_coerce_results(
1605 &none,
1606 &tty,
1607 &KclValue::Tuple {
1608 value: Vec::new(),
1609 meta: Vec::new(),
1610 },
1611 &mut exec_state,
1612 );
1613 none.coerce(&tty1, &mut exec_state).unwrap_err();
1614
1615 let oty = RuntimeType::Object(vec![]);
1616 assert_coerce_results(
1617 &none,
1618 &oty,
1619 &KclValue::Object {
1620 value: HashMap::new(),
1621 meta: Vec::new(),
1622 },
1623 &mut exec_state,
1624 );
1625 }
1626
1627 #[tokio::test(flavor = "multi_thread")]
1628 async fn coerce_record() {
1629 let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock(None).await);
1630
1631 let obj0 = KclValue::Object {
1632 value: HashMap::new(),
1633 meta: Vec::new(),
1634 };
1635 let obj1 = KclValue::Object {
1636 value: [(
1637 "foo".to_owned(),
1638 KclValue::Bool {
1639 value: true,
1640 meta: Vec::new(),
1641 },
1642 )]
1643 .into(),
1644 meta: Vec::new(),
1645 };
1646 let obj2 = KclValue::Object {
1647 value: [
1648 (
1649 "foo".to_owned(),
1650 KclValue::Bool {
1651 value: true,
1652 meta: Vec::new(),
1653 },
1654 ),
1655 (
1656 "bar".to_owned(),
1657 KclValue::Number {
1658 value: 0.0,
1659 ty: NumericType::count(),
1660 meta: Vec::new(),
1661 },
1662 ),
1663 (
1664 "baz".to_owned(),
1665 KclValue::Number {
1666 value: 42.0,
1667 ty: NumericType::count(),
1668 meta: Vec::new(),
1669 },
1670 ),
1671 ]
1672 .into(),
1673 meta: Vec::new(),
1674 };
1675
1676 let ty0 = RuntimeType::Object(vec![]);
1677 assert_coerce_results(&obj0, &ty0, &obj0, &mut exec_state);
1678 assert_coerce_results(&obj1, &ty0, &obj1, &mut exec_state);
1679 assert_coerce_results(&obj2, &ty0, &obj2, &mut exec_state);
1680
1681 let ty1 = RuntimeType::Object(vec![("foo".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean))]);
1682 obj0.coerce(&ty1, &mut exec_state).unwrap_err();
1683 assert_coerce_results(&obj1, &ty1, &obj1, &mut exec_state);
1684 assert_coerce_results(&obj2, &ty1, &obj2, &mut exec_state);
1685
1686 let ty2 = RuntimeType::Object(vec![
1688 (
1689 "bar".to_owned(),
1690 RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1691 ),
1692 ("foo".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean)),
1693 ]);
1694 obj0.coerce(&ty2, &mut exec_state).unwrap_err();
1695 obj1.coerce(&ty2, &mut exec_state).unwrap_err();
1696 assert_coerce_results(&obj2, &ty2, &obj2, &mut exec_state);
1697
1698 let tyq = RuntimeType::Object(vec![("qux".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean))]);
1700 obj0.coerce(&tyq, &mut exec_state).unwrap_err();
1701 obj1.coerce(&tyq, &mut exec_state).unwrap_err();
1702 obj2.coerce(&tyq, &mut exec_state).unwrap_err();
1703
1704 let ty1 = RuntimeType::Object(vec![("bar".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean))]);
1706 obj2.coerce(&ty1, &mut exec_state).unwrap_err();
1707 }
1708
1709 #[tokio::test(flavor = "multi_thread")]
1710 async fn coerce_array() {
1711 let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock(None).await);
1712
1713 let hom_arr = KclValue::HomArray {
1714 value: vec![
1715 KclValue::Number {
1716 value: 0.0,
1717 ty: NumericType::count(),
1718 meta: Vec::new(),
1719 },
1720 KclValue::Number {
1721 value: 1.0,
1722 ty: NumericType::count(),
1723 meta: Vec::new(),
1724 },
1725 KclValue::Number {
1726 value: 2.0,
1727 ty: NumericType::count(),
1728 meta: Vec::new(),
1729 },
1730 KclValue::Number {
1731 value: 3.0,
1732 ty: NumericType::count(),
1733 meta: Vec::new(),
1734 },
1735 ],
1736 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1737 };
1738 let mixed1 = KclValue::Tuple {
1739 value: vec![
1740 KclValue::Number {
1741 value: 0.0,
1742 ty: NumericType::count(),
1743 meta: Vec::new(),
1744 },
1745 KclValue::Number {
1746 value: 1.0,
1747 ty: NumericType::count(),
1748 meta: Vec::new(),
1749 },
1750 ],
1751 meta: Vec::new(),
1752 };
1753 let mixed2 = KclValue::Tuple {
1754 value: vec![
1755 KclValue::Number {
1756 value: 0.0,
1757 ty: NumericType::count(),
1758 meta: Vec::new(),
1759 },
1760 KclValue::Bool {
1761 value: true,
1762 meta: Vec::new(),
1763 },
1764 ],
1765 meta: Vec::new(),
1766 };
1767
1768 let tyh = RuntimeType::Array(
1770 Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
1771 ArrayLen::Known(4),
1772 );
1773 let tym1 = RuntimeType::Tuple(vec![
1774 RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1775 RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1776 ]);
1777 let tym2 = RuntimeType::Tuple(vec![
1778 RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1779 RuntimeType::Primitive(PrimitiveType::Boolean),
1780 ]);
1781 assert_coerce_results(&hom_arr, &tyh, &hom_arr, &mut exec_state);
1782 assert_coerce_results(&mixed1, &tym1, &mixed1, &mut exec_state);
1783 assert_coerce_results(&mixed2, &tym2, &mixed2, &mut exec_state);
1784 mixed1.coerce(&tym2, &mut exec_state).unwrap_err();
1785 mixed2.coerce(&tym1, &mut exec_state).unwrap_err();
1786
1787 let tyhn = RuntimeType::Array(
1789 Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
1790 ArrayLen::None,
1791 );
1792 let tyh1 = RuntimeType::Array(
1793 Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
1794 ArrayLen::NonEmpty,
1795 );
1796 let tyh3 = RuntimeType::Array(
1797 Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
1798 ArrayLen::Known(3),
1799 );
1800 assert_coerce_results(&hom_arr, &tyhn, &hom_arr, &mut exec_state);
1801 assert_coerce_results(&hom_arr, &tyh1, &hom_arr, &mut exec_state);
1802 hom_arr.coerce(&tyh3, &mut exec_state).unwrap_err();
1803
1804 let hom_arr0 = KclValue::HomArray {
1805 value: vec![],
1806 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1807 };
1808 assert_coerce_results(&hom_arr0, &tyhn, &hom_arr0, &mut exec_state);
1809 hom_arr0.coerce(&tyh1, &mut exec_state).unwrap_err();
1810 hom_arr0.coerce(&tyh3, &mut exec_state).unwrap_err();
1811
1812 let tym1 = RuntimeType::Tuple(vec![
1815 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
1816 RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1817 ]);
1818 let tym2 = RuntimeType::Tuple(vec![
1819 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
1820 RuntimeType::Primitive(PrimitiveType::Boolean),
1821 ]);
1822 assert_coerce_results(&mixed1, &tym1, &mixed1, &mut exec_state);
1825 assert_coerce_results(&mixed2, &tym2, &mixed2, &mut exec_state);
1826
1827 let hom_arr_2 = KclValue::HomArray {
1829 value: vec![
1830 KclValue::Number {
1831 value: 0.0,
1832 ty: NumericType::count(),
1833 meta: Vec::new(),
1834 },
1835 KclValue::Number {
1836 value: 1.0,
1837 ty: NumericType::count(),
1838 meta: Vec::new(),
1839 },
1840 ],
1841 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1842 };
1843 let mixed0 = KclValue::Tuple {
1844 value: vec![],
1845 meta: Vec::new(),
1846 };
1847 assert_coerce_results(&mixed1, &tyhn, &hom_arr_2, &mut exec_state);
1848 assert_coerce_results(&mixed1, &tyh1, &hom_arr_2, &mut exec_state);
1849 assert_coerce_results(&mixed0, &tyhn, &hom_arr0, &mut exec_state);
1850 mixed0.coerce(&tyh, &mut exec_state).unwrap_err();
1851 mixed0.coerce(&tyh1, &mut exec_state).unwrap_err();
1852
1853 assert_coerce_results(&hom_arr_2, &tym1, &mixed1, &mut exec_state);
1855 hom_arr.coerce(&tym1, &mut exec_state).unwrap_err();
1856 hom_arr_2.coerce(&tym2, &mut exec_state).unwrap_err();
1857
1858 mixed0.coerce(&tym1, &mut exec_state).unwrap_err();
1859 mixed0.coerce(&tym2, &mut exec_state).unwrap_err();
1860 }
1861
1862 #[tokio::test(flavor = "multi_thread")]
1863 async fn coerce_union() {
1864 let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock(None).await);
1865
1866 assert!(RuntimeType::Union(vec![]).subtype(&RuntimeType::Union(vec![
1868 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
1869 RuntimeType::Primitive(PrimitiveType::Boolean)
1870 ])));
1871 assert!(
1872 RuntimeType::Union(vec![RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any))]).subtype(
1873 &RuntimeType::Union(vec![
1874 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
1875 RuntimeType::Primitive(PrimitiveType::Boolean)
1876 ])
1877 )
1878 );
1879 assert!(RuntimeType::Union(vec![
1880 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
1881 RuntimeType::Primitive(PrimitiveType::Boolean)
1882 ])
1883 .subtype(&RuntimeType::Union(vec![
1884 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
1885 RuntimeType::Primitive(PrimitiveType::Boolean)
1886 ])));
1887
1888 let count = KclValue::Number {
1890 value: 1.0,
1891 ty: NumericType::count(),
1892 meta: Vec::new(),
1893 };
1894
1895 let tya = RuntimeType::Union(vec![RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any))]);
1896 let tya2 = RuntimeType::Union(vec![
1897 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
1898 RuntimeType::Primitive(PrimitiveType::Boolean),
1899 ]);
1900 assert_coerce_results(&count, &tya, &count, &mut exec_state);
1901 assert_coerce_results(&count, &tya2, &count, &mut exec_state);
1902
1903 let tyb = RuntimeType::Union(vec![RuntimeType::Primitive(PrimitiveType::Boolean)]);
1905 let tyb2 = RuntimeType::Union(vec![
1906 RuntimeType::Primitive(PrimitiveType::Boolean),
1907 RuntimeType::Primitive(PrimitiveType::String),
1908 ]);
1909 count.coerce(&tyb, &mut exec_state).unwrap_err();
1910 count.coerce(&tyb2, &mut exec_state).unwrap_err();
1911 }
1912
1913 #[tokio::test(flavor = "multi_thread")]
1914 async fn coerce_axes() {
1915 let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock(None).await);
1916
1917 assert!(RuntimeType::Primitive(PrimitiveType::Axis2d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis2d)));
1919 assert!(RuntimeType::Primitive(PrimitiveType::Axis3d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis3d)));
1920 assert!(!RuntimeType::Primitive(PrimitiveType::Axis3d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis2d)));
1921 assert!(!RuntimeType::Primitive(PrimitiveType::Axis2d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis3d)));
1922
1923 let a2d = KclValue::Object {
1925 value: [
1926 (
1927 "origin".to_owned(),
1928 KclValue::HomArray {
1929 value: vec![
1930 KclValue::Number {
1931 value: 0.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 "direction".to_owned(),
1946 KclValue::HomArray {
1947 value: vec![
1948 KclValue::Number {
1949 value: 1.0,
1950 ty: NumericType::mm(),
1951 meta: Vec::new(),
1952 },
1953 KclValue::Number {
1954 value: 0.0,
1955 ty: NumericType::mm(),
1956 meta: Vec::new(),
1957 },
1958 ],
1959 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
1960 },
1961 ),
1962 ]
1963 .into(),
1964 meta: Vec::new(),
1965 };
1966 let a3d = KclValue::Object {
1967 value: [
1968 (
1969 "origin".to_owned(),
1970 KclValue::HomArray {
1971 value: vec![
1972 KclValue::Number {
1973 value: 0.0,
1974 ty: NumericType::mm(),
1975 meta: Vec::new(),
1976 },
1977 KclValue::Number {
1978 value: 0.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 ],
1988 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
1989 },
1990 ),
1991 (
1992 "direction".to_owned(),
1993 KclValue::HomArray {
1994 value: vec![
1995 KclValue::Number {
1996 value: 1.0,
1997 ty: NumericType::mm(),
1998 meta: Vec::new(),
1999 },
2000 KclValue::Number {
2001 value: 0.0,
2002 ty: NumericType::mm(),
2003 meta: Vec::new(),
2004 },
2005 KclValue::Number {
2006 value: 1.0,
2007 ty: NumericType::mm(),
2008 meta: Vec::new(),
2009 },
2010 ],
2011 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
2012 },
2013 ),
2014 ]
2015 .into(),
2016 meta: Vec::new(),
2017 };
2018
2019 let ty2d = RuntimeType::Primitive(PrimitiveType::Axis2d);
2020 let ty3d = RuntimeType::Primitive(PrimitiveType::Axis3d);
2021
2022 assert_coerce_results(&a2d, &ty2d, &a2d, &mut exec_state);
2023 assert_coerce_results(&a3d, &ty3d, &a3d, &mut exec_state);
2024 assert_coerce_results(&a3d, &ty2d, &a2d, &mut exec_state);
2025 a2d.coerce(&ty3d, &mut exec_state).unwrap_err();
2026 }
2027
2028 #[tokio::test(flavor = "multi_thread")]
2029 async fn coerce_numeric() {
2030 let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock(None).await);
2031
2032 let count = KclValue::Number {
2033 value: 1.0,
2034 ty: NumericType::count(),
2035 meta: Vec::new(),
2036 };
2037 let mm = KclValue::Number {
2038 value: 1.0,
2039 ty: NumericType::mm(),
2040 meta: Vec::new(),
2041 };
2042 let inches = KclValue::Number {
2043 value: 1.0,
2044 ty: NumericType::Known(UnitType::Length(UnitLen::Inches)),
2045 meta: Vec::new(),
2046 };
2047 let rads = KclValue::Number {
2048 value: 1.0,
2049 ty: NumericType::Known(UnitType::Angle(UnitAngle::Radians)),
2050 meta: Vec::new(),
2051 };
2052 let default = KclValue::Number {
2053 value: 1.0,
2054 ty: NumericType::default(),
2055 meta: Vec::new(),
2056 };
2057 let any = KclValue::Number {
2058 value: 1.0,
2059 ty: NumericType::Any,
2060 meta: Vec::new(),
2061 };
2062 let unknown = KclValue::Number {
2063 value: 1.0,
2064 ty: NumericType::Unknown,
2065 meta: Vec::new(),
2066 };
2067
2068 assert_coerce_results(&count, &NumericType::count().into(), &count, &mut exec_state);
2070 assert_coerce_results(&mm, &NumericType::mm().into(), &mm, &mut exec_state);
2071 assert_coerce_results(&any, &NumericType::Any.into(), &any, &mut exec_state);
2072 assert_coerce_results(&unknown, &NumericType::Unknown.into(), &unknown, &mut exec_state);
2073 assert_coerce_results(&default, &NumericType::default().into(), &default, &mut exec_state);
2074
2075 assert_coerce_results(&count, &NumericType::Any.into(), &count, &mut exec_state);
2076 assert_coerce_results(&mm, &NumericType::Any.into(), &mm, &mut exec_state);
2077 assert_coerce_results(&unknown, &NumericType::Any.into(), &unknown, &mut exec_state);
2078 assert_coerce_results(&default, &NumericType::Any.into(), &default, &mut exec_state);
2079
2080 assert_eq!(
2081 default
2082 .coerce(
2083 &NumericType::Default {
2084 len: UnitLen::Yards,
2085 angle: UnitAngle::default()
2086 }
2087 .into(),
2088 &mut exec_state
2089 )
2090 .unwrap(),
2091 default
2092 );
2093
2094 count.coerce(&NumericType::mm().into(), &mut exec_state).unwrap_err();
2096 mm.coerce(&NumericType::count().into(), &mut exec_state).unwrap_err();
2097 unknown.coerce(&NumericType::mm().into(), &mut exec_state).unwrap_err();
2098 unknown
2099 .coerce(&NumericType::default().into(), &mut exec_state)
2100 .unwrap_err();
2101
2102 count.coerce(&NumericType::Unknown.into(), &mut exec_state).unwrap_err();
2103 mm.coerce(&NumericType::Unknown.into(), &mut exec_state).unwrap_err();
2104 default
2105 .coerce(&NumericType::Unknown.into(), &mut exec_state)
2106 .unwrap_err();
2107
2108 assert_eq!(
2109 inches
2110 .coerce(&NumericType::mm().into(), &mut exec_state)
2111 .unwrap()
2112 .as_f64()
2113 .unwrap()
2114 .round(),
2115 25.0
2116 );
2117 assert_eq!(
2118 rads.coerce(
2119 &NumericType::Known(UnitType::Angle(UnitAngle::Degrees)).into(),
2120 &mut exec_state
2121 )
2122 .unwrap()
2123 .as_f64()
2124 .unwrap()
2125 .round(),
2126 57.0
2127 );
2128 assert_eq!(
2129 inches
2130 .coerce(&NumericType::default().into(), &mut exec_state)
2131 .unwrap()
2132 .as_f64()
2133 .unwrap()
2134 .round(),
2135 1.0
2136 );
2137 assert_eq!(
2138 rads.coerce(&NumericType::default().into(), &mut exec_state)
2139 .unwrap()
2140 .as_f64()
2141 .unwrap()
2142 .round(),
2143 1.0
2144 );
2145 }
2146
2147 #[track_caller]
2148 fn assert_value_and_type(name: &str, result: &ExecTestResults, expected: f64, expected_ty: NumericType) {
2149 let mem = result.exec_state.stack();
2150 match mem
2151 .memory
2152 .get_from(name, result.mem_env, SourceRange::default(), 0)
2153 .unwrap()
2154 {
2155 KclValue::Number { value, ty, .. } => {
2156 assert_eq!(value.round(), expected);
2157 assert_eq!(*ty, expected_ty);
2158 }
2159 _ => unreachable!(),
2160 }
2161 }
2162
2163 #[tokio::test(flavor = "multi_thread")]
2164 async fn combine_numeric() {
2165 let program = r#"a = 5 + 4
2166b = 5 - 2
2167c = 5mm - 2mm + 10mm
2168d = 5mm - 2 + 10
2169e = 5 - 2mm + 10
2170f = 30mm - 1inch
2171
2172g = 2 * 10
2173h = 2 * 10mm
2174i = 2mm * 10mm
2175j = 2_ * 10
2176k = 2_ * 3mm * 3mm
2177
2178l = 1 / 10
2179m = 2mm / 1mm
2180n = 10inch / 2mm
2181o = 3mm / 3
2182p = 3_ / 4
2183q = 4inch / 2_
2184
2185r = min([0, 3, 42])
2186s = min([0, 3mm, -42])
2187t = min([100, 3in, 142mm])
2188u = min([3rad, 4in])
2189"#;
2190
2191 let result = parse_execute(program).await.unwrap();
2192 assert_eq!(
2193 result.exec_state.errors().len(),
2194 5,
2195 "errors: {:?}",
2196 result.exec_state.errors()
2197 );
2198
2199 assert_value_and_type("a", &result, 9.0, NumericType::default());
2200 assert_value_and_type("b", &result, 3.0, NumericType::default());
2201 assert_value_and_type("c", &result, 13.0, NumericType::mm());
2202 assert_value_and_type("d", &result, 13.0, NumericType::mm());
2203 assert_value_and_type("e", &result, 13.0, NumericType::mm());
2204 assert_value_and_type("f", &result, 5.0, NumericType::mm());
2205
2206 assert_value_and_type("g", &result, 20.0, NumericType::default());
2207 assert_value_and_type("h", &result, 20.0, NumericType::mm());
2208 assert_value_and_type("i", &result, 20.0, NumericType::Unknown);
2209 assert_value_and_type("j", &result, 20.0, NumericType::default());
2210 assert_value_and_type("k", &result, 18.0, NumericType::Unknown);
2211
2212 assert_value_and_type("l", &result, 0.0, NumericType::default());
2213 assert_value_and_type("m", &result, 2.0, NumericType::count());
2214 assert_value_and_type("n", &result, 5.0, NumericType::Unknown);
2215 assert_value_and_type("o", &result, 1.0, NumericType::mm());
2216 assert_value_and_type("p", &result, 1.0, NumericType::count());
2217 assert_value_and_type("q", &result, 2.0, NumericType::Known(UnitType::Length(UnitLen::Inches)));
2218
2219 assert_value_and_type("r", &result, 0.0, NumericType::default());
2220 assert_value_and_type("s", &result, -42.0, NumericType::mm());
2221 assert_value_and_type("t", &result, 3.0, NumericType::Unknown);
2222 assert_value_and_type("u", &result, 3.0, NumericType::Unknown);
2223 }
2224
2225 #[tokio::test(flavor = "multi_thread")]
2226 async fn bad_typed_arithmetic() {
2227 let program = r#"
2228a = 1rad
2229b = 180 / PI * a + 360
2230"#;
2231
2232 let result = parse_execute(program).await.unwrap();
2233
2234 assert_value_and_type("a", &result, 1.0, NumericType::radians());
2235 assert_value_and_type("b", &result, 417.0, NumericType::Unknown);
2236 }
2237
2238 #[tokio::test(flavor = "multi_thread")]
2239 async fn cos_coercions() {
2240 let program = r#"
2241a = cos(units::toRadians(30))
2242b = 3 / a
2243c = cos(30deg)
2244d = cos(30)
2245"#;
2246
2247 let result = parse_execute(program).await.unwrap();
2248 assert!(result.exec_state.errors().is_empty());
2249
2250 assert_value_and_type("a", &result, 1.0, NumericType::count());
2251 assert_value_and_type("b", &result, 3.0, NumericType::default());
2252 assert_value_and_type("c", &result, 1.0, NumericType::count());
2253 assert_value_and_type("d", &result, 1.0, NumericType::count());
2254 }
2255
2256 #[tokio::test(flavor = "multi_thread")]
2257 async fn coerce_nested_array() {
2258 let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock(None).await);
2259
2260 let mixed1 = KclValue::HomArray {
2261 value: vec![
2262 KclValue::Number {
2263 value: 0.0,
2264 ty: NumericType::count(),
2265 meta: Vec::new(),
2266 },
2267 KclValue::Number {
2268 value: 1.0,
2269 ty: NumericType::count(),
2270 meta: Vec::new(),
2271 },
2272 KclValue::HomArray {
2273 value: vec![
2274 KclValue::Number {
2275 value: 2.0,
2276 ty: NumericType::count(),
2277 meta: Vec::new(),
2278 },
2279 KclValue::Number {
2280 value: 3.0,
2281 ty: NumericType::count(),
2282 meta: Vec::new(),
2283 },
2284 ],
2285 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2286 },
2287 ],
2288 ty: RuntimeType::any(),
2289 };
2290
2291 let tym1 = RuntimeType::Array(
2293 Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2294 ArrayLen::NonEmpty,
2295 );
2296
2297 let result = KclValue::HomArray {
2298 value: vec![
2299 KclValue::Number {
2300 value: 0.0,
2301 ty: NumericType::count(),
2302 meta: Vec::new(),
2303 },
2304 KclValue::Number {
2305 value: 1.0,
2306 ty: NumericType::count(),
2307 meta: Vec::new(),
2308 },
2309 KclValue::Number {
2310 value: 2.0,
2311 ty: NumericType::count(),
2312 meta: Vec::new(),
2313 },
2314 KclValue::Number {
2315 value: 3.0,
2316 ty: NumericType::count(),
2317 meta: Vec::new(),
2318 },
2319 ],
2320 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2321 };
2322 assert_coerce_results(&mixed1, &tym1, &result, &mut exec_state);
2323 }
2324}