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