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