1use std::{collections::HashMap, fmt};
2
3use anyhow::Result;
4use schemars::JsonSchema;
5use serde::{Deserialize, Serialize};
6
7use crate::{
8 CompilationError, SourceRange,
9 execution::{
10 ExecState, Plane, PlaneInfo, Point3d,
11 kcl_value::{KclValue, TypeDef},
12 memory::{self},
13 },
14 parsing::{
15 ast::types::{PrimitiveType as AstPrimitiveType, Type},
16 token::NumericSuffix,
17 },
18 std::args::{FromKclValue, TyF64},
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 function() -> Self {
44 RuntimeType::Primitive(PrimitiveType::Function)
45 }
46
47 pub fn sketch() -> Self {
48 RuntimeType::Primitive(PrimitiveType::Sketch)
49 }
50
51 pub fn sketch_or_surface() -> Self {
52 RuntimeType::Union(vec![Self::sketch(), Self::plane(), Self::face()])
53 }
54
55 pub fn sketches() -> Self {
57 RuntimeType::Array(
58 Box::new(RuntimeType::Primitive(PrimitiveType::Sketch)),
59 ArrayLen::Minimum(1),
60 )
61 }
62
63 pub fn solids() -> Self {
65 RuntimeType::Array(
66 Box::new(RuntimeType::Primitive(PrimitiveType::Solid)),
67 ArrayLen::Minimum(1),
68 )
69 }
70
71 pub fn solid() -> Self {
72 RuntimeType::Primitive(PrimitiveType::Solid)
73 }
74
75 pub fn helix() -> Self {
76 RuntimeType::Primitive(PrimitiveType::Helix)
77 }
78
79 pub fn plane() -> Self {
80 RuntimeType::Primitive(PrimitiveType::Plane)
81 }
82
83 pub fn face() -> Self {
84 RuntimeType::Primitive(PrimitiveType::Face)
85 }
86
87 pub fn tag_decl() -> Self {
88 RuntimeType::Primitive(PrimitiveType::TagDecl)
89 }
90
91 pub fn tagged_face() -> Self {
92 RuntimeType::Primitive(PrimitiveType::TaggedFace)
93 }
94
95 pub fn tagged_edge() -> Self {
96 RuntimeType::Primitive(PrimitiveType::TaggedEdge)
97 }
98
99 pub fn bool() -> Self {
100 RuntimeType::Primitive(PrimitiveType::Boolean)
101 }
102
103 pub fn string() -> Self {
104 RuntimeType::Primitive(PrimitiveType::String)
105 }
106
107 pub fn imported() -> Self {
108 RuntimeType::Primitive(PrimitiveType::ImportedGeometry)
109 }
110
111 pub fn point2d() -> Self {
113 RuntimeType::Array(Box::new(RuntimeType::length()), ArrayLen::Known(2))
114 }
115
116 pub fn point3d() -> Self {
118 RuntimeType::Array(Box::new(RuntimeType::length()), ArrayLen::Known(3))
119 }
120
121 pub fn length() -> Self {
122 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Known(UnitType::Length(
123 UnitLen::Unknown,
124 ))))
125 }
126
127 pub fn known_length(len: UnitLen) -> Self {
128 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Known(UnitType::Length(len))))
129 }
130
131 pub fn angle() -> Self {
132 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Known(UnitType::Angle(
133 UnitAngle::Unknown,
134 ))))
135 }
136
137 pub fn radians() -> Self {
138 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Known(UnitType::Angle(
139 UnitAngle::Radians,
140 ))))
141 }
142
143 pub fn degrees() -> Self {
144 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Known(UnitType::Angle(
145 UnitAngle::Degrees,
146 ))))
147 }
148
149 pub fn count() -> Self {
150 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Known(UnitType::Count)))
151 }
152
153 pub fn num_any() -> Self {
154 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any))
155 }
156
157 pub fn from_parsed(
158 value: Type,
159 exec_state: &mut ExecState,
160 source_range: SourceRange,
161 ) -> Result<Self, CompilationError> {
162 match value {
163 Type::Primitive(pt) => Self::from_parsed_primitive(pt, exec_state, source_range),
164 Type::Array { ty, len } => {
165 Self::from_parsed(*ty, exec_state, source_range).map(|t| RuntimeType::Array(Box::new(t), len))
166 }
167 Type::Union { tys } => tys
168 .into_iter()
169 .map(|t| Self::from_parsed(t.inner, exec_state, source_range))
170 .collect::<Result<Vec<_>, CompilationError>>()
171 .map(RuntimeType::Union),
172 Type::Object { properties } => properties
173 .into_iter()
174 .map(|(id, ty)| {
175 RuntimeType::from_parsed(ty.inner, exec_state, source_range).map(|ty| (id.name.clone(), ty))
176 })
177 .collect::<Result<Vec<_>, CompilationError>>()
178 .map(RuntimeType::Object),
179 }
180 }
181
182 fn from_parsed_primitive(
183 value: AstPrimitiveType,
184 exec_state: &mut ExecState,
185 source_range: SourceRange,
186 ) -> Result<Self, CompilationError> {
187 Ok(match value {
188 AstPrimitiveType::Any => RuntimeType::Primitive(PrimitiveType::Any),
189 AstPrimitiveType::String => RuntimeType::Primitive(PrimitiveType::String),
190 AstPrimitiveType::Boolean => RuntimeType::Primitive(PrimitiveType::Boolean),
191 AstPrimitiveType::Number(suffix) => {
192 let ty = match suffix {
193 NumericSuffix::None => NumericType::Any,
194 _ => NumericType::from_parsed(suffix, &exec_state.mod_local.settings),
195 };
196 RuntimeType::Primitive(PrimitiveType::Number(ty))
197 }
198 AstPrimitiveType::Named { id } => Self::from_alias(&id.name, exec_state, source_range)?,
199 AstPrimitiveType::TagDecl => RuntimeType::Primitive(PrimitiveType::TagDecl),
200 AstPrimitiveType::ImportedGeometry => RuntimeType::Primitive(PrimitiveType::ImportedGeometry),
201 AstPrimitiveType::Function(_) => RuntimeType::Primitive(PrimitiveType::Function),
202 })
203 }
204
205 pub fn from_alias(
206 alias: &str,
207 exec_state: &mut ExecState,
208 source_range: SourceRange,
209 ) -> Result<Self, CompilationError> {
210 let ty_val = exec_state
211 .stack()
212 .get(&format!("{}{}", memory::TYPE_PREFIX, alias), source_range)
213 .map_err(|_| CompilationError::err(source_range, format!("Unknown type: {alias}")))?;
214
215 Ok(match ty_val {
216 KclValue::Type { value, .. } => match value {
217 TypeDef::RustRepr(ty, _) => RuntimeType::Primitive(ty.clone()),
218 TypeDef::Alias(ty) => ty.clone(),
219 },
220 _ => unreachable!(),
221 })
222 }
223
224 pub fn human_friendly_type(&self) -> String {
225 match self {
226 RuntimeType::Primitive(ty) => ty.to_string(),
227 RuntimeType::Array(ty, ArrayLen::None | ArrayLen::Minimum(0)) => {
228 format!("an array of {}", ty.display_multiple())
229 }
230 RuntimeType::Array(ty, ArrayLen::Minimum(1)) => format!("one or more {}", ty.display_multiple()),
231 RuntimeType::Array(ty, ArrayLen::Minimum(n)) => {
232 format!("an array of {n} or more {}", ty.display_multiple())
233 }
234 RuntimeType::Array(ty, ArrayLen::Known(n)) => format!("an array of {n} {}", ty.display_multiple()),
235 RuntimeType::Union(tys) => tys
236 .iter()
237 .map(Self::human_friendly_type)
238 .collect::<Vec<_>>()
239 .join(" or "),
240 RuntimeType::Tuple(tys) => format!(
241 "a tuple with values of types ({})",
242 tys.iter().map(Self::human_friendly_type).collect::<Vec<_>>().join(", ")
243 ),
244 RuntimeType::Object(_) => format!("an object with fields {self}"),
245 }
246 }
247
248 pub(crate) fn subtype(&self, sup: &RuntimeType) -> bool {
250 use RuntimeType::*;
251
252 match (self, sup) {
253 (_, Primitive(PrimitiveType::Any)) => true,
254 (Primitive(t1), Primitive(t2)) => t1.subtype(t2),
255 (Array(t1, l1), Array(t2, l2)) => t1.subtype(t2) && l1.subtype(*l2),
256 (Tuple(t1), Tuple(t2)) => t1.len() == t2.len() && t1.iter().zip(t2).all(|(t1, t2)| t1.subtype(t2)),
257
258 (Union(ts1), Union(ts2)) => ts1.iter().all(|t| ts2.contains(t)),
259 (t1, Union(ts2)) => ts2.iter().any(|t| t1.subtype(t)),
260
261 (Object(t1), Object(t2)) => t2
262 .iter()
263 .all(|(f, t)| t1.iter().any(|(ff, tt)| f == ff && tt.subtype(t))),
264
265 (t1, RuntimeType::Array(t2, l)) if t1.subtype(t2) && ArrayLen::Known(1).subtype(*l) => true,
267 (RuntimeType::Array(t1, ArrayLen::Known(1)), t2) if t1.subtype(t2) => true,
268 (t1, RuntimeType::Tuple(t2)) if !t2.is_empty() && t1.subtype(&t2[0]) => true,
269 (RuntimeType::Tuple(t1), t2) if t1.len() == 1 && t1[0].subtype(t2) => true,
270
271 (Object(t1), Primitive(PrimitiveType::Axis2d)) => {
273 t1.iter()
274 .any(|(n, t)| n == "origin" && t.subtype(&RuntimeType::point2d()))
275 && t1
276 .iter()
277 .any(|(n, t)| n == "direction" && t.subtype(&RuntimeType::point2d()))
278 }
279 (Object(t1), Primitive(PrimitiveType::Axis3d)) => {
280 t1.iter()
281 .any(|(n, t)| n == "origin" && t.subtype(&RuntimeType::point3d()))
282 && t1
283 .iter()
284 .any(|(n, t)| n == "direction" && t.subtype(&RuntimeType::point3d()))
285 }
286 (Primitive(PrimitiveType::Axis2d), Object(t2)) => {
287 t2.iter()
288 .any(|(n, t)| n == "origin" && t.subtype(&RuntimeType::point2d()))
289 && t2
290 .iter()
291 .any(|(n, t)| n == "direction" && t.subtype(&RuntimeType::point2d()))
292 }
293 (Primitive(PrimitiveType::Axis3d), Object(t2)) => {
294 t2.iter()
295 .any(|(n, t)| n == "origin" && t.subtype(&RuntimeType::point3d()))
296 && t2
297 .iter()
298 .any(|(n, t)| n == "direction" && t.subtype(&RuntimeType::point3d()))
299 }
300 _ => false,
301 }
302 }
303
304 fn display_multiple(&self) -> String {
305 match self {
306 RuntimeType::Primitive(ty) => ty.display_multiple(),
307 RuntimeType::Array(..) => "arrays".to_owned(),
308 RuntimeType::Union(tys) => tys
309 .iter()
310 .map(|t| t.display_multiple())
311 .collect::<Vec<_>>()
312 .join(" or "),
313 RuntimeType::Tuple(_) => "tuples".to_owned(),
314 RuntimeType::Object(_) => format!("objects with fields {self}"),
315 }
316 }
317}
318
319impl fmt::Display for RuntimeType {
320 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
321 match self {
322 RuntimeType::Primitive(t) => t.fmt(f),
323 RuntimeType::Array(t, l) => match l {
324 ArrayLen::None => write!(f, "[{t}]"),
325 ArrayLen::Minimum(n) => write!(f, "[{t}; {n}+]"),
326 ArrayLen::Known(n) => write!(f, "[{t}; {n}]"),
327 },
328 RuntimeType::Tuple(ts) => write!(
329 f,
330 "({})",
331 ts.iter().map(|t| t.to_string()).collect::<Vec<_>>().join(", ")
332 ),
333 RuntimeType::Union(ts) => write!(
334 f,
335 "{}",
336 ts.iter().map(|t| t.to_string()).collect::<Vec<_>>().join(" | ")
337 ),
338 RuntimeType::Object(items) => write!(
339 f,
340 "{{ {} }}",
341 items
342 .iter()
343 .map(|(n, t)| format!("{n}: {t}"))
344 .collect::<Vec<_>>()
345 .join(", ")
346 ),
347 }
348 }
349}
350
351#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, ts_rs::TS, JsonSchema)]
352pub enum ArrayLen {
353 None,
354 Minimum(usize),
355 Known(usize),
356}
357
358impl ArrayLen {
359 pub fn subtype(self, other: ArrayLen) -> bool {
360 match (self, other) {
361 (_, ArrayLen::None) => true,
362 (ArrayLen::Minimum(s1), ArrayLen::Minimum(s2)) if s1 >= s2 => true,
363 (ArrayLen::Known(s1), ArrayLen::Minimum(s2)) if s1 >= s2 => true,
364 (ArrayLen::None, ArrayLen::Minimum(0)) => true,
365 (ArrayLen::Known(s1), ArrayLen::Known(s2)) if s1 == s2 => true,
366 _ => false,
367 }
368 }
369
370 fn satisfied(self, len: usize, allow_shrink: bool) -> Option<usize> {
372 match self {
373 ArrayLen::None => Some(len),
374 ArrayLen::Minimum(s) => (len >= s).then_some(len),
375 ArrayLen::Known(s) => (if allow_shrink { len >= s } else { len == s }).then_some(s),
376 }
377 }
378}
379
380#[derive(Debug, Clone, PartialEq)]
381pub enum PrimitiveType {
382 Any,
383 Number(NumericType),
384 String,
385 Boolean,
386 TaggedEdge,
387 TaggedFace,
388 TagDecl,
389 Sketch,
390 Solid,
391 Plane,
392 Helix,
393 Face,
394 Edge,
395 Axis2d,
396 Axis3d,
397 ImportedGeometry,
398 Function,
399}
400
401impl PrimitiveType {
402 fn display_multiple(&self) -> String {
403 match self {
404 PrimitiveType::Any => "any values".to_owned(),
405 PrimitiveType::Number(NumericType::Known(unit)) => format!("numbers({unit})"),
406 PrimitiveType::Number(_) => "numbers".to_owned(),
407 PrimitiveType::String => "strings".to_owned(),
408 PrimitiveType::Boolean => "bools".to_owned(),
409 PrimitiveType::Sketch => "Sketches".to_owned(),
410 PrimitiveType::Solid => "Solids".to_owned(),
411 PrimitiveType::Plane => "Planes".to_owned(),
412 PrimitiveType::Helix => "Helices".to_owned(),
413 PrimitiveType::Face => "Faces".to_owned(),
414 PrimitiveType::Edge => "Edges".to_owned(),
415 PrimitiveType::Axis2d => "2d axes".to_owned(),
416 PrimitiveType::Axis3d => "3d axes".to_owned(),
417 PrimitiveType::ImportedGeometry => "imported geometries".to_owned(),
418 PrimitiveType::Function => "functions".to_owned(),
419 PrimitiveType::TagDecl => "tag declarators".to_owned(),
420 PrimitiveType::TaggedEdge => "tagged edges".to_owned(),
421 PrimitiveType::TaggedFace => "tagged faces".to_owned(),
422 }
423 }
424
425 fn subtype(&self, other: &PrimitiveType) -> bool {
426 match (self, other) {
427 (_, PrimitiveType::Any) => true,
428 (PrimitiveType::Number(n1), PrimitiveType::Number(n2)) => n1.subtype(n2),
429 (PrimitiveType::TaggedEdge, PrimitiveType::TaggedFace)
430 | (PrimitiveType::TaggedEdge, PrimitiveType::Edge) => true,
431 (t1, t2) => t1 == t2,
432 }
433 }
434}
435
436impl fmt::Display for PrimitiveType {
437 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
438 match self {
439 PrimitiveType::Any => write!(f, "any"),
440 PrimitiveType::Number(NumericType::Known(unit)) => write!(f, "number({unit})"),
441 PrimitiveType::Number(NumericType::Unknown) => write!(f, "number(unknown units)"),
442 PrimitiveType::Number(NumericType::Default { .. }) => write!(f, "number"),
443 PrimitiveType::Number(NumericType::Any) => write!(f, "number(any units)"),
444 PrimitiveType::String => write!(f, "string"),
445 PrimitiveType::Boolean => write!(f, "bool"),
446 PrimitiveType::TagDecl => write!(f, "tag declarator"),
447 PrimitiveType::TaggedEdge => write!(f, "tagged edge"),
448 PrimitiveType::TaggedFace => write!(f, "tagged face"),
449 PrimitiveType::Sketch => write!(f, "Sketch"),
450 PrimitiveType::Solid => write!(f, "Solid"),
451 PrimitiveType::Plane => write!(f, "Plane"),
452 PrimitiveType::Face => write!(f, "Face"),
453 PrimitiveType::Edge => write!(f, "Edge"),
454 PrimitiveType::Axis2d => write!(f, "Axis2d"),
455 PrimitiveType::Axis3d => write!(f, "Axis3d"),
456 PrimitiveType::Helix => write!(f, "Helix"),
457 PrimitiveType::ImportedGeometry => write!(f, "ImportedGeometry"),
458 PrimitiveType::Function => write!(f, "fn"),
459 }
460 }
461}
462
463#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, ts_rs::TS, JsonSchema)]
464#[ts(export)]
465#[serde(tag = "type")]
466pub enum NumericType {
467 Known(UnitType),
469 Default { len: UnitLen, angle: UnitAngle },
471 Unknown,
473 Any,
475}
476
477impl Default for NumericType {
478 fn default() -> Self {
479 NumericType::Default {
480 len: UnitLen::default(),
481 angle: UnitAngle::default(),
482 }
483 }
484}
485
486impl NumericType {
487 pub const fn count() -> Self {
488 NumericType::Known(UnitType::Count)
489 }
490
491 pub const fn mm() -> Self {
492 NumericType::Known(UnitType::Length(UnitLen::Mm))
493 }
494
495 pub const fn radians() -> Self {
496 NumericType::Known(UnitType::Angle(UnitAngle::Radians))
497 }
498
499 pub const fn degrees() -> Self {
500 NumericType::Known(UnitType::Angle(UnitAngle::Degrees))
501 }
502
503 pub fn combine_eq(a: TyF64, b: TyF64) -> (f64, f64, NumericType) {
509 use NumericType::*;
510 match (a.ty, b.ty) {
511 (at, bt) if at == bt => (a.n, b.n, at),
512 (at, Any) => (a.n, b.n, at),
513 (Any, bt) => (a.n, b.n, bt),
514
515 (t @ Known(UnitType::Length(l1)), Known(UnitType::Length(l2))) => (a.n, l2.adjust_to(b.n, l1).0, t),
516 (t @ Known(UnitType::Angle(a1)), Known(UnitType::Angle(a2))) => (a.n, a2.adjust_to(b.n, a1).0, t),
517
518 (Known(UnitType::Count), Default { .. }) | (Default { .. }, Known(UnitType::Count)) => {
519 (a.n, b.n, Known(UnitType::Count))
520 }
521 (t @ Known(UnitType::Length(l1)), Default { len: l2, .. }) if l1 == l2 => (a.n, b.n, t),
522 (Default { len: l1, .. }, t @ Known(UnitType::Length(l2))) if l1 == l2 => (a.n, b.n, t),
523 (t @ Known(UnitType::Angle(a1)), Default { angle: a2, .. }) if a1 == a2 => (a.n, b.n, t),
524 (Default { angle: a1, .. }, t @ Known(UnitType::Angle(a2))) if a1 == a2 => (a.n, b.n, t),
525
526 _ => (a.n, b.n, Unknown),
527 }
528 }
529
530 pub fn combine_eq_coerce(a: TyF64, b: TyF64) -> (f64, f64, NumericType) {
538 use NumericType::*;
539 match (a.ty, b.ty) {
540 (at, bt) if at == bt => (a.n, b.n, at),
541 (at, Any) => (a.n, b.n, at),
542 (Any, bt) => (a.n, b.n, bt),
543
544 (t @ Known(UnitType::Length(l1)), Known(UnitType::Length(l2))) => (a.n, l2.adjust_to(b.n, l1).0, t),
546 (t @ Known(UnitType::Angle(a1)), Known(UnitType::Angle(a2))) => (a.n, a2.adjust_to(b.n, a1).0, t),
547
548 (Known(UnitType::Count), Default { .. }) | (Default { .. }, Known(UnitType::Count)) => {
550 (a.n, b.n, Known(UnitType::Count))
551 }
552
553 (t @ Known(UnitType::Length(l1)), Default { len: l2, .. }) => (a.n, l2.adjust_to(b.n, l1).0, t),
554 (Default { len: l1, .. }, t @ Known(UnitType::Length(l2))) => (l1.adjust_to(a.n, l2).0, b.n, t),
555 (t @ Known(UnitType::Angle(a1)), Default { angle: a2, .. }) => (a.n, a2.adjust_to(b.n, a1).0, t),
556 (Default { angle: a1, .. }, t @ Known(UnitType::Angle(a2))) => (a1.adjust_to(a.n, a2).0, b.n, t),
557
558 (Known(_), Known(_)) | (Default { .. }, Default { .. }) | (_, Unknown) | (Unknown, _) => {
559 (a.n, b.n, Unknown)
560 }
561 }
562 }
563
564 pub fn combine_eq_array(input: &[TyF64]) -> (Vec<f64>, NumericType) {
565 use NumericType::*;
566 let result = input.iter().map(|t| t.n).collect();
567
568 let mut ty = Any;
569 for i in input {
570 if i.ty == Any || ty == i.ty {
571 continue;
572 }
573
574 match (&ty, &i.ty) {
576 (Any, Default { .. }) if i.n == 0.0 => {}
577 (Any, t) => {
578 ty = *t;
579 }
580 (_, Unknown) | (Default { .. }, Default { .. }) => return (result, Unknown),
581
582 (Known(UnitType::Count), Default { .. }) | (Default { .. }, Known(UnitType::Count)) => {
583 ty = Known(UnitType::Count);
584 }
585
586 (Known(UnitType::Length(l1)), Default { len: l2, .. }) if l1 == l2 || i.n == 0.0 => {}
587 (Known(UnitType::Angle(a1)), Default { angle: a2, .. }) if a1 == a2 || i.n == 0.0 => {}
588
589 (Default { len: l1, .. }, Known(UnitType::Length(l2))) if l1 == l2 => {
590 ty = Known(UnitType::Length(*l2));
591 }
592 (Default { angle: a1, .. }, Known(UnitType::Angle(a2))) if a1 == a2 => {
593 ty = Known(UnitType::Angle(*a2));
594 }
595
596 _ => return (result, Unknown),
597 }
598 }
599
600 if ty == Any && !input.is_empty() {
601 ty = input[0].ty;
602 }
603
604 (result, ty)
605 }
606
607 pub fn combine_mul(a: TyF64, b: TyF64) -> (f64, f64, NumericType) {
609 use NumericType::*;
610 match (a.ty, b.ty) {
611 (at @ Default { .. }, bt @ Default { .. }) if at == bt => (a.n, b.n, at),
612 (Default { .. }, Default { .. }) => (a.n, b.n, Unknown),
613 (Known(UnitType::Count), bt) => (a.n, b.n, bt),
614 (at, Known(UnitType::Count)) => (a.n, b.n, at),
615 (at @ Known(_), Default { .. }) | (Default { .. }, at @ Known(_)) => (a.n, b.n, at),
616 (Any, Any) => (a.n, b.n, Any),
617 _ => (a.n, b.n, Unknown),
618 }
619 }
620
621 pub fn combine_div(a: TyF64, b: TyF64) -> (f64, f64, NumericType) {
623 use NumericType::*;
624 match (a.ty, b.ty) {
625 (at @ Default { .. }, bt @ Default { .. }) if at == bt => (a.n, b.n, at),
626 (at, bt) if at == bt => (a.n, b.n, Known(UnitType::Count)),
627 (Default { .. }, Default { .. }) => (a.n, b.n, Unknown),
628 (at, Known(UnitType::Count) | Any) => (a.n, b.n, at),
629 (at @ Known(_), Default { .. }) => (a.n, b.n, at),
630 (Known(UnitType::Count), _) => (a.n, b.n, Known(UnitType::Count)),
631 _ => (a.n, b.n, Unknown),
632 }
633 }
634
635 pub fn combine_mod(a: TyF64, b: TyF64) -> (f64, f64, NumericType) {
637 use NumericType::*;
638 match (a.ty, b.ty) {
639 (at @ Default { .. }, bt @ Default { .. }) if at == bt => (a.n, b.n, at),
640 (at, bt) if at == bt => (a.n, b.n, at),
641 (Default { .. }, Default { .. }) => (a.n, b.n, Unknown),
642 (at, Known(UnitType::Count) | Any) => (a.n, b.n, at),
643 (at @ Known(_), Default { .. }) => (a.n, b.n, at),
644 (Known(UnitType::Count), _) => (a.n, b.n, Known(UnitType::Count)),
645 _ => (a.n, b.n, Unknown),
646 }
647 }
648
649 pub fn from_parsed(suffix: NumericSuffix, settings: &super::MetaSettings) -> Self {
650 match suffix {
651 NumericSuffix::None => NumericType::Default {
652 len: settings.default_length_units,
653 angle: settings.default_angle_units,
654 },
655 NumericSuffix::Count => NumericType::Known(UnitType::Count),
656 NumericSuffix::Length => NumericType::Known(UnitType::Length(UnitLen::Unknown)),
657 NumericSuffix::Angle => NumericType::Known(UnitType::Angle(UnitAngle::Unknown)),
658 NumericSuffix::Mm => NumericType::Known(UnitType::Length(UnitLen::Mm)),
659 NumericSuffix::Cm => NumericType::Known(UnitType::Length(UnitLen::Cm)),
660 NumericSuffix::M => NumericType::Known(UnitType::Length(UnitLen::M)),
661 NumericSuffix::Inch => NumericType::Known(UnitType::Length(UnitLen::Inches)),
662 NumericSuffix::Ft => NumericType::Known(UnitType::Length(UnitLen::Feet)),
663 NumericSuffix::Yd => NumericType::Known(UnitType::Length(UnitLen::Yards)),
664 NumericSuffix::Deg => NumericType::Known(UnitType::Angle(UnitAngle::Degrees)),
665 NumericSuffix::Rad => NumericType::Known(UnitType::Angle(UnitAngle::Radians)),
666 NumericSuffix::Unknown => NumericType::Unknown,
667 }
668 }
669
670 fn subtype(&self, other: &NumericType) -> bool {
671 use NumericType::*;
672
673 match (self, other) {
674 (_, Any) => true,
675 (a, b) if a == b => true,
676 (
677 NumericType::Known(UnitType::Length(_)) | NumericType::Default { .. },
678 NumericType::Known(UnitType::Length(UnitLen::Unknown)),
679 )
680 | (
681 NumericType::Known(UnitType::Angle(_)) | NumericType::Default { .. },
682 NumericType::Known(UnitType::Angle(UnitAngle::Unknown)),
683 ) => true,
684 (Unknown, _) | (_, Unknown) => false,
685 (_, _) => false,
686 }
687 }
688
689 fn is_unknown(&self) -> bool {
690 matches!(
691 self,
692 NumericType::Unknown
693 | NumericType::Known(UnitType::Angle(UnitAngle::Unknown))
694 | NumericType::Known(UnitType::Length(UnitLen::Unknown))
695 )
696 }
697
698 pub fn is_fully_specified(&self) -> bool {
699 !matches!(
700 self,
701 NumericType::Unknown
702 | NumericType::Known(UnitType::Angle(UnitAngle::Unknown))
703 | NumericType::Known(UnitType::Length(UnitLen::Unknown))
704 | NumericType::Any
705 | NumericType::Default { .. }
706 )
707 }
708
709 fn example_ty(&self) -> Option<String> {
710 match self {
711 Self::Known(t) if !self.is_unknown() => Some(t.to_string()),
712 Self::Default { len, .. } => Some(len.to_string()),
713 _ => None,
714 }
715 }
716
717 fn coerce(&self, val: &KclValue) -> Result<KclValue, CoercionError> {
718 let KclValue::Number { value, ty, meta } = val else {
719 return Err(val.into());
720 };
721
722 if ty.subtype(self) {
723 return Ok(KclValue::Number {
724 value: *value,
725 ty: *ty,
726 meta: meta.clone(),
727 });
728 }
729
730 use NumericType::*;
732 match (ty, self) {
733 (Unknown, _) => Err(CoercionError::from(val).with_explicit(self.example_ty().unwrap_or("mm".to_owned()))),
735 (_, Unknown) => Err(val.into()),
736
737 (Any, _) => Ok(KclValue::Number {
738 value: *value,
739 ty: *self,
740 meta: meta.clone(),
741 }),
742
743 (_, Default { .. }) => Ok(KclValue::Number {
746 value: *value,
747 ty: *ty,
748 meta: meta.clone(),
749 }),
750
751 (Known(UnitType::Length(l1)), Known(UnitType::Length(l2))) => {
753 let (value, ty) = l1.adjust_to(*value, *l2);
754 Ok(KclValue::Number {
755 value,
756 ty: Known(UnitType::Length(ty)),
757 meta: meta.clone(),
758 })
759 }
760 (Known(UnitType::Angle(a1)), Known(UnitType::Angle(a2))) => {
761 let (value, ty) = a1.adjust_to(*value, *a2);
762 Ok(KclValue::Number {
763 value,
764 ty: Known(UnitType::Angle(ty)),
765 meta: meta.clone(),
766 })
767 }
768
769 (Known(_), Known(_)) => Err(val.into()),
771
772 (Default { .. }, Known(UnitType::Count)) => Ok(KclValue::Number {
774 value: *value,
775 ty: Known(UnitType::Count),
776 meta: meta.clone(),
777 }),
778
779 (Default { len: l1, .. }, Known(UnitType::Length(l2))) => {
780 let (value, ty) = l1.adjust_to(*value, *l2);
781 Ok(KclValue::Number {
782 value,
783 ty: Known(UnitType::Length(ty)),
784 meta: meta.clone(),
785 })
786 }
787
788 (Default { angle: a1, .. }, Known(UnitType::Angle(a2))) => {
789 let (value, ty) = a1.adjust_to(*value, *a2);
790 Ok(KclValue::Number {
791 value,
792 ty: Known(UnitType::Angle(ty)),
793 meta: meta.clone(),
794 })
795 }
796
797 (_, _) => unreachable!(),
798 }
799 }
800
801 pub fn expect_length(&self) -> UnitLen {
802 match self {
803 Self::Known(UnitType::Length(len)) | Self::Default { len, .. } => *len,
804 _ => unreachable!("Found {self:?}"),
805 }
806 }
807
808 pub fn as_length(&self) -> Option<UnitLen> {
809 match self {
810 Self::Known(UnitType::Length(len)) | Self::Default { len, .. } => Some(*len),
811 _ => None,
812 }
813 }
814}
815
816impl From<NumericType> for RuntimeType {
817 fn from(t: NumericType) -> RuntimeType {
818 RuntimeType::Primitive(PrimitiveType::Number(t))
819 }
820}
821
822impl From<UnitLen> for NumericType {
823 fn from(value: UnitLen) -> Self {
824 NumericType::Known(UnitType::Length(value))
825 }
826}
827
828impl From<UnitAngle> for NumericType {
829 fn from(value: UnitAngle) -> Self {
830 NumericType::Known(UnitType::Angle(value))
831 }
832}
833
834#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, ts_rs::TS, JsonSchema)]
835#[ts(export)]
836#[serde(tag = "type")]
837pub enum UnitType {
838 Count,
839 Length(UnitLen),
840 Angle(UnitAngle),
841}
842
843impl UnitType {
844 pub(crate) fn to_suffix(self) -> Option<String> {
845 match self {
846 UnitType::Count => Some("_".to_owned()),
847 UnitType::Length(UnitLen::Unknown) => None,
848 UnitType::Angle(UnitAngle::Unknown) => None,
849 UnitType::Length(l) => Some(l.to_string()),
850 UnitType::Angle(a) => Some(a.to_string()),
851 }
852 }
853}
854
855impl std::fmt::Display for UnitType {
856 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
857 match self {
858 UnitType::Count => write!(f, "Count"),
859 UnitType::Length(l) => l.fmt(f),
860 UnitType::Angle(a) => a.fmt(f),
861 }
862 }
863}
864
865#[derive(Debug, Default, Clone, Copy, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Eq)]
868#[ts(export)]
869#[serde(tag = "type")]
870pub enum UnitLen {
871 #[default]
872 Mm,
873 Cm,
874 M,
875 Inches,
876 Feet,
877 Yards,
878 Unknown,
879}
880
881impl UnitLen {
882 pub fn adjust_to(self, value: f64, to: UnitLen) -> (f64, UnitLen) {
883 use UnitLen::*;
884
885 if self == to {
886 return (value, to);
887 }
888
889 if to == Unknown {
890 return (value, self);
891 }
892
893 let (base, base_unit) = match self {
894 Mm => (value, Mm),
895 Cm => (value * 10.0, Mm),
896 M => (value * 1000.0, Mm),
897 Inches => (value, Inches),
898 Feet => (value * 12.0, Inches),
899 Yards => (value * 36.0, Inches),
900 Unknown => unreachable!(),
901 };
902 let (base, base_unit) = match (base_unit, to) {
903 (Mm, Inches) | (Mm, Feet) | (Mm, Yards) => (base / 25.4, Inches),
904 (Inches, Mm) | (Inches, Cm) | (Inches, M) => (base * 25.4, Mm),
905 _ => (base, base_unit),
906 };
907
908 let value = match (base_unit, to) {
909 (Mm, Mm) => base,
910 (Mm, Cm) => base / 10.0,
911 (Mm, M) => base / 1000.0,
912 (Inches, Inches) => base,
913 (Inches, Feet) => base / 12.0,
914 (Inches, Yards) => base / 36.0,
915 _ => unreachable!(),
916 };
917
918 (value, to)
919 }
920}
921
922impl std::fmt::Display for UnitLen {
923 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
924 match self {
925 UnitLen::Mm => write!(f, "mm"),
926 UnitLen::Cm => write!(f, "cm"),
927 UnitLen::M => write!(f, "m"),
928 UnitLen::Inches => write!(f, "in"),
929 UnitLen::Feet => write!(f, "ft"),
930 UnitLen::Yards => write!(f, "yd"),
931 UnitLen::Unknown => write!(f, "Length"),
932 }
933 }
934}
935
936impl TryFrom<NumericSuffix> for UnitLen {
937 type Error = ();
938
939 fn try_from(suffix: NumericSuffix) -> std::result::Result<Self, Self::Error> {
940 match suffix {
941 NumericSuffix::Mm => Ok(Self::Mm),
942 NumericSuffix::Cm => Ok(Self::Cm),
943 NumericSuffix::M => Ok(Self::M),
944 NumericSuffix::Inch => Ok(Self::Inches),
945 NumericSuffix::Ft => Ok(Self::Feet),
946 NumericSuffix::Yd => Ok(Self::Yards),
947 _ => Err(()),
948 }
949 }
950}
951
952impl From<crate::UnitLength> for UnitLen {
953 fn from(unit: crate::UnitLength) -> Self {
954 match unit {
955 crate::UnitLength::Cm => UnitLen::Cm,
956 crate::UnitLength::Ft => UnitLen::Feet,
957 crate::UnitLength::In => UnitLen::Inches,
958 crate::UnitLength::M => UnitLen::M,
959 crate::UnitLength::Mm => UnitLen::Mm,
960 crate::UnitLength::Yd => UnitLen::Yards,
961 }
962 }
963}
964
965impl From<UnitLen> for crate::UnitLength {
966 fn from(unit: UnitLen) -> Self {
967 match unit {
968 UnitLen::Cm => crate::UnitLength::Cm,
969 UnitLen::Feet => crate::UnitLength::Ft,
970 UnitLen::Inches => crate::UnitLength::In,
971 UnitLen::M => crate::UnitLength::M,
972 UnitLen::Mm => crate::UnitLength::Mm,
973 UnitLen::Yards => crate::UnitLength::Yd,
974 UnitLen::Unknown => unreachable!(),
975 }
976 }
977}
978
979impl From<UnitLen> for kittycad_modeling_cmds::units::UnitLength {
980 fn from(unit: UnitLen) -> Self {
981 match unit {
982 UnitLen::Cm => kittycad_modeling_cmds::units::UnitLength::Centimeters,
983 UnitLen::Feet => kittycad_modeling_cmds::units::UnitLength::Feet,
984 UnitLen::Inches => kittycad_modeling_cmds::units::UnitLength::Inches,
985 UnitLen::M => kittycad_modeling_cmds::units::UnitLength::Meters,
986 UnitLen::Mm => kittycad_modeling_cmds::units::UnitLength::Millimeters,
987 UnitLen::Yards => kittycad_modeling_cmds::units::UnitLength::Yards,
988 UnitLen::Unknown => unreachable!(),
989 }
990 }
991}
992
993#[derive(Debug, Default, Clone, Copy, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Eq)]
995#[ts(export)]
996#[serde(tag = "type")]
997pub enum UnitAngle {
998 #[default]
999 Degrees,
1000 Radians,
1001 Unknown,
1002}
1003
1004impl UnitAngle {
1005 pub fn adjust_to(self, value: f64, to: UnitAngle) -> (f64, UnitAngle) {
1006 use std::f64::consts::PI;
1007
1008 use UnitAngle::*;
1009
1010 if to == Unknown {
1011 return (value, self);
1012 }
1013
1014 let value = match (self, to) {
1015 (Degrees, Degrees) => value,
1016 (Degrees, Radians) => (value / 180.0) * PI,
1017 (Radians, Degrees) => 180.0 * value / PI,
1018 (Radians, Radians) => value,
1019 (Unknown, _) | (_, Unknown) => unreachable!(),
1020 };
1021
1022 (value, to)
1023 }
1024}
1025
1026impl std::fmt::Display for UnitAngle {
1027 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1028 match self {
1029 UnitAngle::Degrees => write!(f, "deg"),
1030 UnitAngle::Radians => write!(f, "rad"),
1031 UnitAngle::Unknown => write!(f, "Angle"),
1032 }
1033 }
1034}
1035
1036impl TryFrom<NumericSuffix> for UnitAngle {
1037 type Error = ();
1038
1039 fn try_from(suffix: NumericSuffix) -> std::result::Result<Self, Self::Error> {
1040 match suffix {
1041 NumericSuffix::Deg => Ok(Self::Degrees),
1042 NumericSuffix::Rad => Ok(Self::Radians),
1043 _ => Err(()),
1044 }
1045 }
1046}
1047
1048#[derive(Debug, Clone)]
1049pub struct CoercionError {
1050 pub found: Option<RuntimeType>,
1051 pub explicit_coercion: Option<String>,
1052}
1053
1054impl CoercionError {
1055 fn with_explicit(mut self, c: String) -> Self {
1056 self.explicit_coercion = Some(c);
1057 self
1058 }
1059}
1060
1061impl From<&'_ KclValue> for CoercionError {
1062 fn from(value: &'_ KclValue) -> Self {
1063 CoercionError {
1064 found: value.principal_type(),
1065 explicit_coercion: None,
1066 }
1067 }
1068}
1069
1070impl KclValue {
1071 pub fn has_type(&self, ty: &RuntimeType) -> bool {
1073 let Some(self_ty) = self.principal_type() else {
1074 return false;
1075 };
1076
1077 self_ty.subtype(ty)
1078 }
1079
1080 pub fn coerce(
1087 &self,
1088 ty: &RuntimeType,
1089 convert_units: bool,
1090 exec_state: &mut ExecState,
1091 ) -> Result<KclValue, CoercionError> {
1092 match self {
1093 KclValue::Tuple { value, .. }
1094 if value.len() == 1
1095 && !matches!(ty, RuntimeType::Primitive(PrimitiveType::Any) | RuntimeType::Tuple(..)) =>
1096 {
1097 if let Ok(coerced) = value[0].coerce(ty, convert_units, exec_state) {
1098 return Ok(coerced);
1099 }
1100 }
1101 KclValue::HomArray { value, .. }
1102 if value.len() == 1
1103 && !matches!(ty, RuntimeType::Primitive(PrimitiveType::Any) | RuntimeType::Array(..)) =>
1104 {
1105 if let Ok(coerced) = value[0].coerce(ty, convert_units, exec_state) {
1106 return Ok(coerced);
1107 }
1108 }
1109 _ => {}
1110 }
1111
1112 match ty {
1113 RuntimeType::Primitive(ty) => self.coerce_to_primitive_type(ty, convert_units, exec_state),
1114 RuntimeType::Array(ty, len) => self.coerce_to_array_type(ty, convert_units, *len, exec_state, false),
1115 RuntimeType::Tuple(tys) => self.coerce_to_tuple_type(tys, convert_units, exec_state),
1116 RuntimeType::Union(tys) => self.coerce_to_union_type(tys, convert_units, exec_state),
1117 RuntimeType::Object(tys) => self.coerce_to_object_type(tys, convert_units, exec_state),
1118 }
1119 }
1120
1121 fn coerce_to_primitive_type(
1122 &self,
1123 ty: &PrimitiveType,
1124 convert_units: bool,
1125 exec_state: &mut ExecState,
1126 ) -> Result<KclValue, CoercionError> {
1127 match ty {
1128 PrimitiveType::Any => Ok(self.clone()),
1129 PrimitiveType::Number(ty) => {
1130 if convert_units {
1131 return ty.coerce(self);
1132 }
1133
1134 if let KclValue::Number { value: n, meta, .. } = &self {
1141 if ty.is_fully_specified() {
1142 let value = KclValue::Number {
1143 ty: NumericType::Any,
1144 value: *n,
1145 meta: meta.clone(),
1146 };
1147 return ty.coerce(&value);
1148 }
1149 }
1150 ty.coerce(self)
1151 }
1152 PrimitiveType::String => match self {
1153 KclValue::String { .. } => Ok(self.clone()),
1154 _ => Err(self.into()),
1155 },
1156 PrimitiveType::Boolean => match self {
1157 KclValue::Bool { .. } => Ok(self.clone()),
1158 _ => Err(self.into()),
1159 },
1160 PrimitiveType::Sketch => match self {
1161 KclValue::Sketch { .. } => Ok(self.clone()),
1162 _ => Err(self.into()),
1163 },
1164 PrimitiveType::Solid => match self {
1165 KclValue::Solid { .. } => Ok(self.clone()),
1166 _ => Err(self.into()),
1167 },
1168 PrimitiveType::Plane => match self {
1169 KclValue::String { value: s, .. }
1170 if [
1171 "xy", "xz", "yz", "-xy", "-xz", "-yz", "XY", "XZ", "YZ", "-XY", "-XZ", "-YZ",
1172 ]
1173 .contains(&&**s) =>
1174 {
1175 Ok(self.clone())
1176 }
1177 KclValue::Plane { .. } => Ok(self.clone()),
1178 KclValue::Object { value, meta } => {
1179 let origin = value
1180 .get("origin")
1181 .and_then(Point3d::from_kcl_val)
1182 .ok_or(CoercionError::from(self))?;
1183 let x_axis = value
1184 .get("xAxis")
1185 .and_then(Point3d::from_kcl_val)
1186 .ok_or(CoercionError::from(self))?;
1187 let y_axis = value
1188 .get("yAxis")
1189 .and_then(Point3d::from_kcl_val)
1190 .ok_or(CoercionError::from(self))?;
1191 let z_axis = x_axis.axes_cross_product(&y_axis);
1192
1193 if value.get("zAxis").is_some() {
1194 exec_state.warn(CompilationError::err(
1195 self.into(),
1196 "Object with a zAxis field is being coerced into a plane, but the zAxis is ignored.",
1197 ));
1198 }
1199
1200 let id = exec_state.mod_local.id_generator.next_uuid();
1201 let plane = Plane {
1202 id,
1203 artifact_id: id.into(),
1204 info: PlaneInfo {
1205 origin,
1206 x_axis: x_axis.normalize(),
1207 y_axis: y_axis.normalize(),
1208 z_axis: z_axis.normalize(),
1209 },
1210 value: super::PlaneType::Uninit,
1211 meta: meta.clone(),
1212 };
1213
1214 Ok(KclValue::Plane { value: Box::new(plane) })
1215 }
1216 _ => Err(self.into()),
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}