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 #[cfg(feature = "artifact-graph")]
1116 artifact_id: id.into(),
1117 info: PlaneInfo {
1118 origin,
1119 x_axis: x_axis.normalize(),
1120 y_axis: y_axis.normalize(),
1121 },
1122 value: super::PlaneType::Uninit,
1123 meta: meta.clone(),
1124 };
1125
1126 Ok(KclValue::Plane { value: Box::new(plane) })
1127 }
1128 _ => Err(self.into()),
1129 },
1130 PrimitiveType::Face => match value {
1131 KclValue::Face { .. } => Ok(value.clone()),
1132 _ => Err(self.into()),
1133 },
1134 PrimitiveType::Helix => match value {
1135 KclValue::Helix { .. } => Ok(value.clone()),
1136 _ => Err(self.into()),
1137 },
1138 PrimitiveType::Edge => match value {
1139 KclValue::Uuid { .. } => Ok(value.clone()),
1140 KclValue::TagIdentifier { .. } => Ok(value.clone()),
1141 _ => Err(self.into()),
1142 },
1143 PrimitiveType::Axis2d => match value {
1144 KclValue::Object { value: values, meta } => {
1145 if values
1146 .get("origin")
1147 .ok_or(CoercionError::from(self))?
1148 .has_type(&RuntimeType::point2d())
1149 && values
1150 .get("direction")
1151 .ok_or(CoercionError::from(self))?
1152 .has_type(&RuntimeType::point2d())
1153 {
1154 return Ok(value.clone());
1155 }
1156
1157 let origin = values.get("origin").ok_or(self.into()).and_then(|p| {
1158 p.coerce_to_array_type(&RuntimeType::length(), ArrayLen::Known(2), exec_state, true)
1159 })?;
1160 let direction = values.get("direction").ok_or(self.into()).and_then(|p| {
1161 p.coerce_to_array_type(&RuntimeType::length(), ArrayLen::Known(2), exec_state, true)
1162 })?;
1163
1164 Ok(KclValue::Object {
1165 value: [("origin".to_owned(), origin), ("direction".to_owned(), direction)].into(),
1166 meta: meta.clone(),
1167 })
1168 }
1169 _ => Err(self.into()),
1170 },
1171 PrimitiveType::Axis3d => match value {
1172 KclValue::Object { value: values, meta } => {
1173 if values
1174 .get("origin")
1175 .ok_or(CoercionError::from(self))?
1176 .has_type(&RuntimeType::point3d())
1177 && values
1178 .get("direction")
1179 .ok_or(CoercionError::from(self))?
1180 .has_type(&RuntimeType::point3d())
1181 {
1182 return Ok(value.clone());
1183 }
1184
1185 let origin = values.get("origin").ok_or(self.into()).and_then(|p| {
1186 p.coerce_to_array_type(&RuntimeType::length(), ArrayLen::Known(3), exec_state, true)
1187 })?;
1188 let direction = values.get("direction").ok_or(self.into()).and_then(|p| {
1189 p.coerce_to_array_type(&RuntimeType::length(), ArrayLen::Known(3), exec_state, true)
1190 })?;
1191
1192 Ok(KclValue::Object {
1193 value: [("origin".to_owned(), origin), ("direction".to_owned(), direction)].into(),
1194 meta: meta.clone(),
1195 })
1196 }
1197 _ => Err(self.into()),
1198 },
1199 PrimitiveType::ImportedGeometry => match value {
1200 KclValue::ImportedGeometry { .. } => Ok(value.clone()),
1201 _ => Err(self.into()),
1202 },
1203 PrimitiveType::Function => match value {
1204 KclValue::Function { .. } => Ok(value.clone()),
1205 _ => Err(self.into()),
1206 },
1207 PrimitiveType::TagId => match value {
1208 KclValue::TagIdentifier { .. } => Ok(value.clone()),
1209 _ => Err(self.into()),
1210 },
1211 PrimitiveType::Tag => match value {
1212 KclValue::TagDeclarator { .. } | KclValue::TagIdentifier { .. } | KclValue::Uuid { .. } => {
1213 Ok(value.clone())
1214 }
1215 s @ KclValue::String { value, .. } if ["start", "end", "START", "END"].contains(&&**value) => {
1216 Ok(s.clone())
1217 }
1218 _ => Err(self.into()),
1219 },
1220 }
1221 }
1222
1223 fn coerce_to_array_type(
1224 &self,
1225 ty: &RuntimeType,
1226 len: ArrayLen,
1227 exec_state: &mut ExecState,
1228 allow_shrink: bool,
1229 ) -> Result<KclValue, CoercionError> {
1230 match self {
1231 KclValue::HomArray { value, ty: aty, .. } => {
1232 let satisfied_len = len.satisfied(value.len(), allow_shrink);
1233
1234 if aty.subtype(ty) {
1235 return satisfied_len
1242 .map(|len| KclValue::HomArray {
1243 value: value[..len].to_vec(),
1244 ty: aty.clone(),
1245 })
1246 .ok_or(self.into());
1247 }
1248
1249 if let Some(satisfied_len) = satisfied_len {
1251 let value_result = value
1252 .iter()
1253 .take(satisfied_len)
1254 .map(|v| v.coerce(ty, exec_state))
1255 .collect::<Result<Vec<_>, _>>();
1256
1257 if let Ok(value) = value_result {
1258 return Ok(KclValue::HomArray { value, ty: ty.clone() });
1260 }
1261 }
1262
1263 let mut values = Vec::new();
1265 for item in value {
1266 if let KclValue::HomArray { value: inner_value, .. } = item {
1267 for item in inner_value {
1269 values.push(item.coerce(ty, exec_state)?);
1270 }
1271 } else {
1272 values.push(item.coerce(ty, exec_state)?);
1273 }
1274 }
1275
1276 let len = len
1277 .satisfied(values.len(), allow_shrink)
1278 .ok_or(CoercionError::from(self))?;
1279
1280 if len > values.len() {
1281 let message = format!(
1282 "Internal: Expected coerced array length {len} to be less than or equal to original length {}",
1283 values.len()
1284 );
1285 exec_state.err(CompilationError::err(self.into(), message.clone()));
1286 #[cfg(debug_assertions)]
1287 panic!("{message}");
1288 }
1289 values.truncate(len);
1290
1291 Ok(KclValue::HomArray {
1292 value: values,
1293 ty: ty.clone(),
1294 })
1295 }
1296 KclValue::Tuple { value, .. } => {
1297 let len = len
1298 .satisfied(value.len(), allow_shrink)
1299 .ok_or(CoercionError::from(self))?;
1300 let value = value
1301 .iter()
1302 .map(|item| item.coerce(ty, exec_state))
1303 .take(len)
1304 .collect::<Result<Vec<_>, _>>()?;
1305
1306 Ok(KclValue::HomArray { value, ty: ty.clone() })
1307 }
1308 KclValue::KclNone { .. } if len.satisfied(0, false).is_some() => Ok(KclValue::HomArray {
1309 value: Vec::new(),
1310 ty: ty.clone(),
1311 }),
1312 _ if len.satisfied(1, false).is_some() => Ok(KclValue::HomArray {
1313 value: vec![self.coerce(ty, exec_state)?],
1314 ty: ty.clone(),
1315 }),
1316 _ => Err(self.into()),
1317 }
1318 }
1319
1320 fn coerce_to_tuple_type(&self, tys: &[RuntimeType], exec_state: &mut ExecState) -> Result<KclValue, CoercionError> {
1321 match self {
1322 KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } if value.len() == tys.len() => {
1323 let mut result = Vec::new();
1324 for (i, t) in tys.iter().enumerate() {
1325 result.push(value[i].coerce(t, exec_state)?);
1326 }
1327
1328 Ok(KclValue::Tuple {
1329 value: result,
1330 meta: Vec::new(),
1331 })
1332 }
1333 KclValue::KclNone { meta, .. } if tys.is_empty() => Ok(KclValue::Tuple {
1334 value: Vec::new(),
1335 meta: meta.clone(),
1336 }),
1337 value if tys.len() == 1 && value.has_type(&tys[0]) => Ok(KclValue::Tuple {
1338 value: vec![value.clone()],
1339 meta: Vec::new(),
1340 }),
1341 _ => Err(self.into()),
1342 }
1343 }
1344
1345 fn coerce_to_union_type(&self, tys: &[RuntimeType], exec_state: &mut ExecState) -> Result<KclValue, CoercionError> {
1346 for t in tys {
1347 if let Ok(v) = self.coerce(t, exec_state) {
1348 return Ok(v);
1349 }
1350 }
1351
1352 Err(self.into())
1353 }
1354
1355 fn coerce_to_object_type(
1356 &self,
1357 tys: &[(String, RuntimeType)],
1358 _exec_state: &mut ExecState,
1359 ) -> Result<KclValue, CoercionError> {
1360 match self {
1361 KclValue::Object { value, .. } => {
1362 for (s, t) in tys {
1363 if !value.get(s).ok_or(CoercionError::from(self))?.has_type(t) {
1365 return Err(self.into());
1366 }
1367 }
1368 Ok(self.clone())
1370 }
1371 KclValue::KclNone { meta, .. } if tys.is_empty() => Ok(KclValue::Object {
1372 value: HashMap::new(),
1373 meta: meta.clone(),
1374 }),
1375 _ => Err(self.into()),
1376 }
1377 }
1378
1379 pub fn principal_type(&self) -> Option<RuntimeType> {
1380 match self {
1381 KclValue::Bool { .. } => Some(RuntimeType::Primitive(PrimitiveType::Boolean)),
1382 KclValue::Number { ty, .. } => Some(RuntimeType::Primitive(PrimitiveType::Number(ty.clone()))),
1383 KclValue::String { .. } => Some(RuntimeType::Primitive(PrimitiveType::String)),
1384 KclValue::Object { value, .. } => {
1385 let properties = value
1386 .iter()
1387 .map(|(k, v)| v.principal_type().map(|t| (k.clone(), t)))
1388 .collect::<Option<Vec<_>>>()?;
1389 Some(RuntimeType::Object(properties))
1390 }
1391 KclValue::Plane { .. } => Some(RuntimeType::Primitive(PrimitiveType::Plane)),
1392 KclValue::Sketch { .. } => Some(RuntimeType::Primitive(PrimitiveType::Sketch)),
1393 KclValue::Solid { .. } => Some(RuntimeType::Primitive(PrimitiveType::Solid)),
1394 KclValue::Face { .. } => Some(RuntimeType::Primitive(PrimitiveType::Face)),
1395 KclValue::Helix { .. } => Some(RuntimeType::Primitive(PrimitiveType::Helix)),
1396 KclValue::ImportedGeometry(..) => Some(RuntimeType::Primitive(PrimitiveType::ImportedGeometry)),
1397 KclValue::Tuple { value, .. } => Some(RuntimeType::Tuple(
1398 value.iter().map(|v| v.principal_type()).collect::<Option<Vec<_>>>()?,
1399 )),
1400 KclValue::HomArray { ty, value, .. } => {
1401 Some(RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Known(value.len())))
1402 }
1403 KclValue::TagIdentifier(_) => Some(RuntimeType::Primitive(PrimitiveType::TagId)),
1404 KclValue::TagDeclarator(_) | KclValue::Uuid { .. } => Some(RuntimeType::Primitive(PrimitiveType::Tag)),
1405 KclValue::Function { .. } => Some(RuntimeType::Primitive(PrimitiveType::Function)),
1406 KclValue::Module { .. } | KclValue::KclNone { .. } | KclValue::Type { .. } => None,
1407 }
1408 }
1409}
1410
1411#[cfg(test)]
1412mod test {
1413 use super::*;
1414 use crate::execution::{parse_execute, ExecTestResults};
1415
1416 fn values(exec_state: &mut ExecState) -> Vec<KclValue> {
1417 vec![
1418 KclValue::Bool {
1419 value: true,
1420 meta: Vec::new(),
1421 },
1422 KclValue::Number {
1423 value: 1.0,
1424 ty: NumericType::count(),
1425 meta: Vec::new(),
1426 },
1427 KclValue::String {
1428 value: "hello".to_owned(),
1429 meta: Vec::new(),
1430 },
1431 KclValue::Tuple {
1432 value: Vec::new(),
1433 meta: Vec::new(),
1434 },
1435 KclValue::HomArray {
1436 value: Vec::new(),
1437 ty: RuntimeType::solid(),
1438 },
1439 KclValue::Object {
1440 value: crate::execution::KclObjectFields::new(),
1441 meta: Vec::new(),
1442 },
1443 KclValue::TagIdentifier(Box::new("foo".parse().unwrap())),
1444 KclValue::TagDeclarator(Box::new(crate::parsing::ast::types::TagDeclarator::new("foo"))),
1445 KclValue::Plane {
1446 value: Box::new(Plane::from_plane_data(crate::std::sketch::PlaneData::XY, exec_state).unwrap()),
1447 },
1448 KclValue::ImportedGeometry(crate::execution::ImportedGeometry::new(
1450 uuid::Uuid::nil(),
1451 Vec::new(),
1452 Vec::new(),
1453 )),
1454 ]
1456 }
1457
1458 #[track_caller]
1459 fn assert_coerce_results(
1460 value: &KclValue,
1461 super_type: &RuntimeType,
1462 expected_value: &KclValue,
1463 exec_state: &mut ExecState,
1464 ) {
1465 let is_subtype = value == expected_value;
1466 assert_eq!(&value.coerce(super_type, exec_state).unwrap(), expected_value);
1467 assert_eq!(
1468 is_subtype,
1469 value.principal_type().is_some() && value.principal_type().unwrap().subtype(super_type),
1470 "{:?} <: {super_type:?} should be {is_subtype}",
1471 value.principal_type().unwrap()
1472 );
1473 assert!(
1474 expected_value.principal_type().unwrap().subtype(super_type),
1475 "{} <: {super_type}",
1476 expected_value.principal_type().unwrap()
1477 )
1478 }
1479
1480 #[tokio::test(flavor = "multi_thread")]
1481 async fn coerce_idempotent() {
1482 let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock(None).await);
1483 let values = values(&mut exec_state);
1484 for v in &values {
1485 let ty = v.principal_type().unwrap();
1487 assert_coerce_results(v, &ty, v, &mut exec_state);
1488
1489 let uty1 = RuntimeType::Union(vec![ty.clone()]);
1491 let uty2 = RuntimeType::Union(vec![ty.clone(), RuntimeType::Primitive(PrimitiveType::Boolean)]);
1492 assert_coerce_results(v, &uty1, v, &mut exec_state);
1493 assert_coerce_results(v, &uty2, v, &mut exec_state);
1494
1495 let aty = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::None);
1497 let aty1 = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Known(1));
1498 let aty0 = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::NonEmpty);
1499
1500 match v {
1501 KclValue::Tuple { .. } | KclValue::HomArray { .. } => {
1502 assert_coerce_results(
1504 v,
1505 &aty,
1506 &KclValue::HomArray {
1507 value: vec![],
1508 ty: ty.clone(),
1509 },
1510 &mut exec_state,
1511 );
1512 v.coerce(&aty1, &mut exec_state).unwrap_err();
1515 v.coerce(&aty0, &mut exec_state).unwrap_err();
1518 }
1519 _ => {
1520 assert_coerce_results(
1521 v,
1522 &aty,
1523 &KclValue::HomArray {
1524 value: vec![v.clone()],
1525 ty: ty.clone(),
1526 },
1527 &mut exec_state,
1528 );
1529 assert_coerce_results(
1530 v,
1531 &aty1,
1532 &KclValue::HomArray {
1533 value: vec![v.clone()],
1534 ty: ty.clone(),
1535 },
1536 &mut exec_state,
1537 );
1538 assert_coerce_results(
1539 v,
1540 &aty0,
1541 &KclValue::HomArray {
1542 value: vec![v.clone()],
1543 ty: ty.clone(),
1544 },
1545 &mut exec_state,
1546 );
1547
1548 let tty = RuntimeType::Tuple(vec![ty.clone()]);
1550 assert_coerce_results(
1551 v,
1552 &tty,
1553 &KclValue::Tuple {
1554 value: vec![v.clone()],
1555 meta: Vec::new(),
1556 },
1557 &mut exec_state,
1558 );
1559 }
1560 }
1561 }
1562
1563 for v in &values[1..] {
1564 v.coerce(&RuntimeType::Primitive(PrimitiveType::Boolean), &mut exec_state)
1566 .unwrap_err();
1567 }
1568 }
1569
1570 #[tokio::test(flavor = "multi_thread")]
1571 async fn coerce_none() {
1572 let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock(None).await);
1573 let none = KclValue::KclNone {
1574 value: crate::parsing::ast::types::KclNone::new(),
1575 meta: Vec::new(),
1576 };
1577
1578 let aty = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::None);
1579 let aty0 = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::Known(0));
1580 let aty1 = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::Known(1));
1581 let aty1p = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::NonEmpty);
1582 assert_coerce_results(
1583 &none,
1584 &aty,
1585 &KclValue::HomArray {
1586 value: Vec::new(),
1587 ty: RuntimeType::solid(),
1588 },
1589 &mut exec_state,
1590 );
1591 assert_coerce_results(
1592 &none,
1593 &aty0,
1594 &KclValue::HomArray {
1595 value: Vec::new(),
1596 ty: RuntimeType::solid(),
1597 },
1598 &mut exec_state,
1599 );
1600 none.coerce(&aty1, &mut exec_state).unwrap_err();
1601 none.coerce(&aty1p, &mut exec_state).unwrap_err();
1602
1603 let tty = RuntimeType::Tuple(vec![]);
1604 let tty1 = RuntimeType::Tuple(vec![RuntimeType::solid()]);
1605 assert_coerce_results(
1606 &none,
1607 &tty,
1608 &KclValue::Tuple {
1609 value: Vec::new(),
1610 meta: Vec::new(),
1611 },
1612 &mut exec_state,
1613 );
1614 none.coerce(&tty1, &mut exec_state).unwrap_err();
1615
1616 let oty = RuntimeType::Object(vec![]);
1617 assert_coerce_results(
1618 &none,
1619 &oty,
1620 &KclValue::Object {
1621 value: HashMap::new(),
1622 meta: Vec::new(),
1623 },
1624 &mut exec_state,
1625 );
1626 }
1627
1628 #[tokio::test(flavor = "multi_thread")]
1629 async fn coerce_record() {
1630 let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock(None).await);
1631
1632 let obj0 = KclValue::Object {
1633 value: HashMap::new(),
1634 meta: Vec::new(),
1635 };
1636 let obj1 = KclValue::Object {
1637 value: [(
1638 "foo".to_owned(),
1639 KclValue::Bool {
1640 value: true,
1641 meta: Vec::new(),
1642 },
1643 )]
1644 .into(),
1645 meta: Vec::new(),
1646 };
1647 let obj2 = KclValue::Object {
1648 value: [
1649 (
1650 "foo".to_owned(),
1651 KclValue::Bool {
1652 value: true,
1653 meta: Vec::new(),
1654 },
1655 ),
1656 (
1657 "bar".to_owned(),
1658 KclValue::Number {
1659 value: 0.0,
1660 ty: NumericType::count(),
1661 meta: Vec::new(),
1662 },
1663 ),
1664 (
1665 "baz".to_owned(),
1666 KclValue::Number {
1667 value: 42.0,
1668 ty: NumericType::count(),
1669 meta: Vec::new(),
1670 },
1671 ),
1672 ]
1673 .into(),
1674 meta: Vec::new(),
1675 };
1676
1677 let ty0 = RuntimeType::Object(vec![]);
1678 assert_coerce_results(&obj0, &ty0, &obj0, &mut exec_state);
1679 assert_coerce_results(&obj1, &ty0, &obj1, &mut exec_state);
1680 assert_coerce_results(&obj2, &ty0, &obj2, &mut exec_state);
1681
1682 let ty1 = RuntimeType::Object(vec![("foo".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean))]);
1683 obj0.coerce(&ty1, &mut exec_state).unwrap_err();
1684 assert_coerce_results(&obj1, &ty1, &obj1, &mut exec_state);
1685 assert_coerce_results(&obj2, &ty1, &obj2, &mut exec_state);
1686
1687 let ty2 = RuntimeType::Object(vec![
1689 (
1690 "bar".to_owned(),
1691 RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1692 ),
1693 ("foo".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean)),
1694 ]);
1695 obj0.coerce(&ty2, &mut exec_state).unwrap_err();
1696 obj1.coerce(&ty2, &mut exec_state).unwrap_err();
1697 assert_coerce_results(&obj2, &ty2, &obj2, &mut exec_state);
1698
1699 let tyq = RuntimeType::Object(vec![("qux".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean))]);
1701 obj0.coerce(&tyq, &mut exec_state).unwrap_err();
1702 obj1.coerce(&tyq, &mut exec_state).unwrap_err();
1703 obj2.coerce(&tyq, &mut exec_state).unwrap_err();
1704
1705 let ty1 = RuntimeType::Object(vec![("bar".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean))]);
1707 obj2.coerce(&ty1, &mut exec_state).unwrap_err();
1708 }
1709
1710 #[tokio::test(flavor = "multi_thread")]
1711 async fn coerce_array() {
1712 let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock(None).await);
1713
1714 let hom_arr = KclValue::HomArray {
1715 value: vec![
1716 KclValue::Number {
1717 value: 0.0,
1718 ty: NumericType::count(),
1719 meta: Vec::new(),
1720 },
1721 KclValue::Number {
1722 value: 1.0,
1723 ty: NumericType::count(),
1724 meta: Vec::new(),
1725 },
1726 KclValue::Number {
1727 value: 2.0,
1728 ty: NumericType::count(),
1729 meta: Vec::new(),
1730 },
1731 KclValue::Number {
1732 value: 3.0,
1733 ty: NumericType::count(),
1734 meta: Vec::new(),
1735 },
1736 ],
1737 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1738 };
1739 let mixed1 = KclValue::Tuple {
1740 value: vec![
1741 KclValue::Number {
1742 value: 0.0,
1743 ty: NumericType::count(),
1744 meta: Vec::new(),
1745 },
1746 KclValue::Number {
1747 value: 1.0,
1748 ty: NumericType::count(),
1749 meta: Vec::new(),
1750 },
1751 ],
1752 meta: Vec::new(),
1753 };
1754 let mixed2 = KclValue::Tuple {
1755 value: vec![
1756 KclValue::Number {
1757 value: 0.0,
1758 ty: NumericType::count(),
1759 meta: Vec::new(),
1760 },
1761 KclValue::Bool {
1762 value: true,
1763 meta: Vec::new(),
1764 },
1765 ],
1766 meta: Vec::new(),
1767 };
1768
1769 let tyh = RuntimeType::Array(
1771 Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
1772 ArrayLen::Known(4),
1773 );
1774 let tym1 = RuntimeType::Tuple(vec![
1775 RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1776 RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1777 ]);
1778 let tym2 = RuntimeType::Tuple(vec![
1779 RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1780 RuntimeType::Primitive(PrimitiveType::Boolean),
1781 ]);
1782 assert_coerce_results(&hom_arr, &tyh, &hom_arr, &mut exec_state);
1783 assert_coerce_results(&mixed1, &tym1, &mixed1, &mut exec_state);
1784 assert_coerce_results(&mixed2, &tym2, &mixed2, &mut exec_state);
1785 mixed1.coerce(&tym2, &mut exec_state).unwrap_err();
1786 mixed2.coerce(&tym1, &mut exec_state).unwrap_err();
1787
1788 let tyhn = RuntimeType::Array(
1790 Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
1791 ArrayLen::None,
1792 );
1793 let tyh1 = RuntimeType::Array(
1794 Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
1795 ArrayLen::NonEmpty,
1796 );
1797 let tyh3 = RuntimeType::Array(
1798 Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
1799 ArrayLen::Known(3),
1800 );
1801 assert_coerce_results(&hom_arr, &tyhn, &hom_arr, &mut exec_state);
1802 assert_coerce_results(&hom_arr, &tyh1, &hom_arr, &mut exec_state);
1803 hom_arr.coerce(&tyh3, &mut exec_state).unwrap_err();
1804
1805 let hom_arr0 = KclValue::HomArray {
1806 value: vec![],
1807 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1808 };
1809 assert_coerce_results(&hom_arr0, &tyhn, &hom_arr0, &mut exec_state);
1810 hom_arr0.coerce(&tyh1, &mut exec_state).unwrap_err();
1811 hom_arr0.coerce(&tyh3, &mut exec_state).unwrap_err();
1812
1813 let tym1 = RuntimeType::Tuple(vec![
1816 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
1817 RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1818 ]);
1819 let tym2 = RuntimeType::Tuple(vec![
1820 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
1821 RuntimeType::Primitive(PrimitiveType::Boolean),
1822 ]);
1823 assert_coerce_results(&mixed1, &tym1, &mixed1, &mut exec_state);
1826 assert_coerce_results(&mixed2, &tym2, &mixed2, &mut exec_state);
1827
1828 let hom_arr_2 = KclValue::HomArray {
1830 value: vec![
1831 KclValue::Number {
1832 value: 0.0,
1833 ty: NumericType::count(),
1834 meta: Vec::new(),
1835 },
1836 KclValue::Number {
1837 value: 1.0,
1838 ty: NumericType::count(),
1839 meta: Vec::new(),
1840 },
1841 ],
1842 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1843 };
1844 let mixed0 = KclValue::Tuple {
1845 value: vec![],
1846 meta: Vec::new(),
1847 };
1848 assert_coerce_results(&mixed1, &tyhn, &hom_arr_2, &mut exec_state);
1849 assert_coerce_results(&mixed1, &tyh1, &hom_arr_2, &mut exec_state);
1850 assert_coerce_results(&mixed0, &tyhn, &hom_arr0, &mut exec_state);
1851 mixed0.coerce(&tyh, &mut exec_state).unwrap_err();
1852 mixed0.coerce(&tyh1, &mut exec_state).unwrap_err();
1853
1854 assert_coerce_results(&hom_arr_2, &tym1, &mixed1, &mut exec_state);
1856 hom_arr.coerce(&tym1, &mut exec_state).unwrap_err();
1857 hom_arr_2.coerce(&tym2, &mut exec_state).unwrap_err();
1858
1859 mixed0.coerce(&tym1, &mut exec_state).unwrap_err();
1860 mixed0.coerce(&tym2, &mut exec_state).unwrap_err();
1861 }
1862
1863 #[tokio::test(flavor = "multi_thread")]
1864 async fn coerce_union() {
1865 let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock(None).await);
1866
1867 assert!(RuntimeType::Union(vec![]).subtype(&RuntimeType::Union(vec![
1869 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
1870 RuntimeType::Primitive(PrimitiveType::Boolean)
1871 ])));
1872 assert!(
1873 RuntimeType::Union(vec![RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any))]).subtype(
1874 &RuntimeType::Union(vec![
1875 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
1876 RuntimeType::Primitive(PrimitiveType::Boolean)
1877 ])
1878 )
1879 );
1880 assert!(RuntimeType::Union(vec![
1881 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
1882 RuntimeType::Primitive(PrimitiveType::Boolean)
1883 ])
1884 .subtype(&RuntimeType::Union(vec![
1885 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
1886 RuntimeType::Primitive(PrimitiveType::Boolean)
1887 ])));
1888
1889 let count = KclValue::Number {
1891 value: 1.0,
1892 ty: NumericType::count(),
1893 meta: Vec::new(),
1894 };
1895
1896 let tya = RuntimeType::Union(vec![RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any))]);
1897 let tya2 = RuntimeType::Union(vec![
1898 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
1899 RuntimeType::Primitive(PrimitiveType::Boolean),
1900 ]);
1901 assert_coerce_results(&count, &tya, &count, &mut exec_state);
1902 assert_coerce_results(&count, &tya2, &count, &mut exec_state);
1903
1904 let tyb = RuntimeType::Union(vec![RuntimeType::Primitive(PrimitiveType::Boolean)]);
1906 let tyb2 = RuntimeType::Union(vec![
1907 RuntimeType::Primitive(PrimitiveType::Boolean),
1908 RuntimeType::Primitive(PrimitiveType::String),
1909 ]);
1910 count.coerce(&tyb, &mut exec_state).unwrap_err();
1911 count.coerce(&tyb2, &mut exec_state).unwrap_err();
1912 }
1913
1914 #[tokio::test(flavor = "multi_thread")]
1915 async fn coerce_axes() {
1916 let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock(None).await);
1917
1918 assert!(RuntimeType::Primitive(PrimitiveType::Axis2d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis2d)));
1920 assert!(RuntimeType::Primitive(PrimitiveType::Axis3d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis3d)));
1921 assert!(!RuntimeType::Primitive(PrimitiveType::Axis3d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis2d)));
1922 assert!(!RuntimeType::Primitive(PrimitiveType::Axis2d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis3d)));
1923
1924 let a2d = KclValue::Object {
1926 value: [
1927 (
1928 "origin".to_owned(),
1929 KclValue::HomArray {
1930 value: vec![
1931 KclValue::Number {
1932 value: 0.0,
1933 ty: NumericType::mm(),
1934 meta: Vec::new(),
1935 },
1936 KclValue::Number {
1937 value: 0.0,
1938 ty: NumericType::mm(),
1939 meta: Vec::new(),
1940 },
1941 ],
1942 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
1943 },
1944 ),
1945 (
1946 "direction".to_owned(),
1947 KclValue::HomArray {
1948 value: vec![
1949 KclValue::Number {
1950 value: 1.0,
1951 ty: NumericType::mm(),
1952 meta: Vec::new(),
1953 },
1954 KclValue::Number {
1955 value: 0.0,
1956 ty: NumericType::mm(),
1957 meta: Vec::new(),
1958 },
1959 ],
1960 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
1961 },
1962 ),
1963 ]
1964 .into(),
1965 meta: Vec::new(),
1966 };
1967 let a3d = KclValue::Object {
1968 value: [
1969 (
1970 "origin".to_owned(),
1971 KclValue::HomArray {
1972 value: vec![
1973 KclValue::Number {
1974 value: 0.0,
1975 ty: NumericType::mm(),
1976 meta: Vec::new(),
1977 },
1978 KclValue::Number {
1979 value: 0.0,
1980 ty: NumericType::mm(),
1981 meta: Vec::new(),
1982 },
1983 KclValue::Number {
1984 value: 0.0,
1985 ty: NumericType::mm(),
1986 meta: Vec::new(),
1987 },
1988 ],
1989 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
1990 },
1991 ),
1992 (
1993 "direction".to_owned(),
1994 KclValue::HomArray {
1995 value: vec![
1996 KclValue::Number {
1997 value: 1.0,
1998 ty: NumericType::mm(),
1999 meta: Vec::new(),
2000 },
2001 KclValue::Number {
2002 value: 0.0,
2003 ty: NumericType::mm(),
2004 meta: Vec::new(),
2005 },
2006 KclValue::Number {
2007 value: 1.0,
2008 ty: NumericType::mm(),
2009 meta: Vec::new(),
2010 },
2011 ],
2012 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
2013 },
2014 ),
2015 ]
2016 .into(),
2017 meta: Vec::new(),
2018 };
2019
2020 let ty2d = RuntimeType::Primitive(PrimitiveType::Axis2d);
2021 let ty3d = RuntimeType::Primitive(PrimitiveType::Axis3d);
2022
2023 assert_coerce_results(&a2d, &ty2d, &a2d, &mut exec_state);
2024 assert_coerce_results(&a3d, &ty3d, &a3d, &mut exec_state);
2025 assert_coerce_results(&a3d, &ty2d, &a2d, &mut exec_state);
2026 a2d.coerce(&ty3d, &mut exec_state).unwrap_err();
2027 }
2028
2029 #[tokio::test(flavor = "multi_thread")]
2030 async fn coerce_numeric() {
2031 let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock(None).await);
2032
2033 let count = KclValue::Number {
2034 value: 1.0,
2035 ty: NumericType::count(),
2036 meta: Vec::new(),
2037 };
2038 let mm = KclValue::Number {
2039 value: 1.0,
2040 ty: NumericType::mm(),
2041 meta: Vec::new(),
2042 };
2043 let inches = KclValue::Number {
2044 value: 1.0,
2045 ty: NumericType::Known(UnitType::Length(UnitLen::Inches)),
2046 meta: Vec::new(),
2047 };
2048 let rads = KclValue::Number {
2049 value: 1.0,
2050 ty: NumericType::Known(UnitType::Angle(UnitAngle::Radians)),
2051 meta: Vec::new(),
2052 };
2053 let default = KclValue::Number {
2054 value: 1.0,
2055 ty: NumericType::default(),
2056 meta: Vec::new(),
2057 };
2058 let any = KclValue::Number {
2059 value: 1.0,
2060 ty: NumericType::Any,
2061 meta: Vec::new(),
2062 };
2063 let unknown = KclValue::Number {
2064 value: 1.0,
2065 ty: NumericType::Unknown,
2066 meta: Vec::new(),
2067 };
2068
2069 assert_coerce_results(&count, &NumericType::count().into(), &count, &mut exec_state);
2071 assert_coerce_results(&mm, &NumericType::mm().into(), &mm, &mut exec_state);
2072 assert_coerce_results(&any, &NumericType::Any.into(), &any, &mut exec_state);
2073 assert_coerce_results(&unknown, &NumericType::Unknown.into(), &unknown, &mut exec_state);
2074 assert_coerce_results(&default, &NumericType::default().into(), &default, &mut exec_state);
2075
2076 assert_coerce_results(&count, &NumericType::Any.into(), &count, &mut exec_state);
2077 assert_coerce_results(&mm, &NumericType::Any.into(), &mm, &mut exec_state);
2078 assert_coerce_results(&unknown, &NumericType::Any.into(), &unknown, &mut exec_state);
2079 assert_coerce_results(&default, &NumericType::Any.into(), &default, &mut exec_state);
2080
2081 assert_eq!(
2082 default
2083 .coerce(
2084 &NumericType::Default {
2085 len: UnitLen::Yards,
2086 angle: UnitAngle::default()
2087 }
2088 .into(),
2089 &mut exec_state
2090 )
2091 .unwrap(),
2092 default
2093 );
2094
2095 count.coerce(&NumericType::mm().into(), &mut exec_state).unwrap_err();
2097 mm.coerce(&NumericType::count().into(), &mut exec_state).unwrap_err();
2098 unknown.coerce(&NumericType::mm().into(), &mut exec_state).unwrap_err();
2099 unknown
2100 .coerce(&NumericType::default().into(), &mut exec_state)
2101 .unwrap_err();
2102
2103 count.coerce(&NumericType::Unknown.into(), &mut exec_state).unwrap_err();
2104 mm.coerce(&NumericType::Unknown.into(), &mut exec_state).unwrap_err();
2105 default
2106 .coerce(&NumericType::Unknown.into(), &mut exec_state)
2107 .unwrap_err();
2108
2109 assert_eq!(
2110 inches
2111 .coerce(&NumericType::mm().into(), &mut exec_state)
2112 .unwrap()
2113 .as_f64()
2114 .unwrap()
2115 .round(),
2116 25.0
2117 );
2118 assert_eq!(
2119 rads.coerce(
2120 &NumericType::Known(UnitType::Angle(UnitAngle::Degrees)).into(),
2121 &mut exec_state
2122 )
2123 .unwrap()
2124 .as_f64()
2125 .unwrap()
2126 .round(),
2127 57.0
2128 );
2129 assert_eq!(
2130 inches
2131 .coerce(&NumericType::default().into(), &mut exec_state)
2132 .unwrap()
2133 .as_f64()
2134 .unwrap()
2135 .round(),
2136 1.0
2137 );
2138 assert_eq!(
2139 rads.coerce(&NumericType::default().into(), &mut exec_state)
2140 .unwrap()
2141 .as_f64()
2142 .unwrap()
2143 .round(),
2144 1.0
2145 );
2146 }
2147
2148 #[track_caller]
2149 fn assert_value_and_type(name: &str, result: &ExecTestResults, expected: f64, expected_ty: NumericType) {
2150 let mem = result.exec_state.stack();
2151 match mem
2152 .memory
2153 .get_from(name, result.mem_env, SourceRange::default(), 0)
2154 .unwrap()
2155 {
2156 KclValue::Number { value, ty, .. } => {
2157 assert_eq!(value.round(), expected);
2158 assert_eq!(*ty, expected_ty);
2159 }
2160 _ => unreachable!(),
2161 }
2162 }
2163
2164 #[tokio::test(flavor = "multi_thread")]
2165 async fn combine_numeric() {
2166 let program = r#"a = 5 + 4
2167b = 5 - 2
2168c = 5mm - 2mm + 10mm
2169d = 5mm - 2 + 10
2170e = 5 - 2mm + 10
2171f = 30mm - 1inch
2172
2173g = 2 * 10
2174h = 2 * 10mm
2175i = 2mm * 10mm
2176j = 2_ * 10
2177k = 2_ * 3mm * 3mm
2178
2179l = 1 / 10
2180m = 2mm / 1mm
2181n = 10inch / 2mm
2182o = 3mm / 3
2183p = 3_ / 4
2184q = 4inch / 2_
2185
2186r = min([0, 3, 42])
2187s = min([0, 3mm, -42])
2188t = min([100, 3in, 142mm])
2189u = min([3rad, 4in])
2190"#;
2191
2192 let result = parse_execute(program).await.unwrap();
2193 assert_eq!(
2194 result.exec_state.errors().len(),
2195 5,
2196 "errors: {:?}",
2197 result.exec_state.errors()
2198 );
2199
2200 assert_value_and_type("a", &result, 9.0, NumericType::default());
2201 assert_value_and_type("b", &result, 3.0, NumericType::default());
2202 assert_value_and_type("c", &result, 13.0, NumericType::mm());
2203 assert_value_and_type("d", &result, 13.0, NumericType::mm());
2204 assert_value_and_type("e", &result, 13.0, NumericType::mm());
2205 assert_value_and_type("f", &result, 5.0, NumericType::mm());
2206
2207 assert_value_and_type("g", &result, 20.0, NumericType::default());
2208 assert_value_and_type("h", &result, 20.0, NumericType::mm());
2209 assert_value_and_type("i", &result, 20.0, NumericType::Unknown);
2210 assert_value_and_type("j", &result, 20.0, NumericType::default());
2211 assert_value_and_type("k", &result, 18.0, NumericType::Unknown);
2212
2213 assert_value_and_type("l", &result, 0.0, NumericType::default());
2214 assert_value_and_type("m", &result, 2.0, NumericType::count());
2215 assert_value_and_type("n", &result, 5.0, NumericType::Unknown);
2216 assert_value_and_type("o", &result, 1.0, NumericType::mm());
2217 assert_value_and_type("p", &result, 1.0, NumericType::count());
2218 assert_value_and_type("q", &result, 2.0, NumericType::Known(UnitType::Length(UnitLen::Inches)));
2219
2220 assert_value_and_type("r", &result, 0.0, NumericType::default());
2221 assert_value_and_type("s", &result, -42.0, NumericType::mm());
2222 assert_value_and_type("t", &result, 3.0, NumericType::Unknown);
2223 assert_value_and_type("u", &result, 3.0, NumericType::Unknown);
2224 }
2225
2226 #[tokio::test(flavor = "multi_thread")]
2227 async fn bad_typed_arithmetic() {
2228 let program = r#"
2229a = 1rad
2230b = 180 / PI * a + 360
2231"#;
2232
2233 let result = parse_execute(program).await.unwrap();
2234
2235 assert_value_and_type("a", &result, 1.0, NumericType::radians());
2236 assert_value_and_type("b", &result, 417.0, NumericType::Unknown);
2237 }
2238
2239 #[tokio::test(flavor = "multi_thread")]
2240 async fn cos_coercions() {
2241 let program = r#"
2242a = cos(units::toRadians(30))
2243b = 3 / a
2244c = cos(30deg)
2245d = cos(30)
2246"#;
2247
2248 let result = parse_execute(program).await.unwrap();
2249 assert!(result.exec_state.errors().is_empty());
2250
2251 assert_value_and_type("a", &result, 1.0, NumericType::count());
2252 assert_value_and_type("b", &result, 3.0, NumericType::default());
2253 assert_value_and_type("c", &result, 1.0, NumericType::count());
2254 assert_value_and_type("d", &result, 1.0, NumericType::count());
2255 }
2256
2257 #[tokio::test(flavor = "multi_thread")]
2258 async fn coerce_nested_array() {
2259 let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock(None).await);
2260
2261 let mixed1 = KclValue::HomArray {
2262 value: vec![
2263 KclValue::Number {
2264 value: 0.0,
2265 ty: NumericType::count(),
2266 meta: Vec::new(),
2267 },
2268 KclValue::Number {
2269 value: 1.0,
2270 ty: NumericType::count(),
2271 meta: Vec::new(),
2272 },
2273 KclValue::HomArray {
2274 value: vec![
2275 KclValue::Number {
2276 value: 2.0,
2277 ty: NumericType::count(),
2278 meta: Vec::new(),
2279 },
2280 KclValue::Number {
2281 value: 3.0,
2282 ty: NumericType::count(),
2283 meta: Vec::new(),
2284 },
2285 ],
2286 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2287 },
2288 ],
2289 ty: RuntimeType::any(),
2290 };
2291
2292 let tym1 = RuntimeType::Array(
2294 Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2295 ArrayLen::NonEmpty,
2296 );
2297
2298 let result = KclValue::HomArray {
2299 value: vec![
2300 KclValue::Number {
2301 value: 0.0,
2302 ty: NumericType::count(),
2303 meta: Vec::new(),
2304 },
2305 KclValue::Number {
2306 value: 1.0,
2307 ty: NumericType::count(),
2308 meta: Vec::new(),
2309 },
2310 KclValue::Number {
2311 value: 2.0,
2312 ty: NumericType::count(),
2313 meta: Vec::new(),
2314 },
2315 KclValue::Number {
2316 value: 3.0,
2317 ty: NumericType::count(),
2318 meta: Vec::new(),
2319 },
2320 ],
2321 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2322 };
2323 assert_coerce_results(&mixed1, &tym1, &result, &mut exec_state);
2324 }
2325}