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