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