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