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