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