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