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