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