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