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 object_kind: Default::default(),
1521 })
1522 }
1523 _ => Err(self.into()),
1524 },
1525 PrimitiveType::Axis3d => match self {
1526 KclValue::Object {
1527 value: values, meta, ..
1528 } => {
1529 if values
1530 .get("origin")
1531 .ok_or(CoercionError::from(self))?
1532 .has_type(&RuntimeType::point3d())
1533 && values
1534 .get("direction")
1535 .ok_or(CoercionError::from(self))?
1536 .has_type(&RuntimeType::point3d())
1537 {
1538 return Ok(self.clone());
1539 }
1540
1541 let origin = values.get("origin").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 let direction = values.get("direction").ok_or(self.into()).and_then(|p| {
1551 p.coerce_to_array_type(
1552 &RuntimeType::length(),
1553 convert_units,
1554 ArrayLen::Known(3),
1555 exec_state,
1556 true,
1557 )
1558 })?;
1559
1560 Ok(KclValue::Object {
1561 value: [("origin".to_owned(), origin), ("direction".to_owned(), direction)].into(),
1562 meta: meta.clone(),
1563 constrainable: false,
1564 object_kind: Default::default(),
1565 })
1566 }
1567 _ => Err(self.into()),
1568 },
1569 PrimitiveType::ImportedGeometry => match self {
1570 KclValue::ImportedGeometry { .. } => Ok(self.clone()),
1571 _ => Err(self.into()),
1572 },
1573 PrimitiveType::Function => match self {
1574 KclValue::Function { .. } => Ok(self.clone()),
1575 _ => Err(self.into()),
1576 },
1577 PrimitiveType::TagDecl => match self {
1578 KclValue::TagDeclarator { .. } => Ok(self.clone()),
1579 _ => Err(self.into()),
1580 },
1581 }
1582 }
1583
1584 fn coerce_to_array_type(
1585 &self,
1586 ty: &RuntimeType,
1587 convert_units: bool,
1588 len: ArrayLen,
1589 exec_state: &mut ExecState,
1590 allow_shrink: bool,
1591 ) -> Result<KclValue, CoercionError> {
1592 match self {
1593 KclValue::HomArray { value, ty: aty, .. } => {
1594 let satisfied_len = len.satisfied(value.len(), allow_shrink);
1595
1596 if aty.subtype(ty) {
1597 return satisfied_len
1604 .map(|len| KclValue::HomArray {
1605 value: value[..len].to_vec(),
1606 ty: aty.clone(),
1607 })
1608 .ok_or(self.into());
1609 }
1610
1611 if let Some(satisfied_len) = satisfied_len {
1613 let value_result = value
1614 .iter()
1615 .take(satisfied_len)
1616 .map(|v| v.coerce(ty, convert_units, exec_state))
1617 .collect::<Result<Vec<_>, _>>();
1618
1619 if let Ok(value) = value_result {
1620 return Ok(KclValue::HomArray { value, ty: ty.clone() });
1622 }
1623 }
1624
1625 let mut values = Vec::new();
1627 for item in value {
1628 if let KclValue::HomArray { value: inner_value, .. } = item {
1629 for item in inner_value {
1631 values.push(item.coerce(ty, convert_units, exec_state)?);
1632 }
1633 } else {
1634 values.push(item.coerce(ty, convert_units, exec_state)?);
1635 }
1636 }
1637
1638 let len = len
1639 .satisfied(values.len(), allow_shrink)
1640 .ok_or(CoercionError::from(self))?;
1641
1642 if len > values.len() {
1643 let message = format!(
1644 "Internal: Expected coerced array length {len} to be less than or equal to original length {}",
1645 values.len()
1646 );
1647 exec_state.err(CompilationIssue::err(self.into(), message.clone()));
1648 #[cfg(debug_assertions)]
1649 panic!("{message}");
1650 }
1651 values.truncate(len);
1652
1653 Ok(KclValue::HomArray {
1654 value: values,
1655 ty: ty.clone(),
1656 })
1657 }
1658 KclValue::Tuple { value, .. } => {
1659 let len = len
1660 .satisfied(value.len(), allow_shrink)
1661 .ok_or(CoercionError::from(self))?;
1662 let value = value
1663 .iter()
1664 .map(|item| item.coerce(ty, convert_units, exec_state))
1665 .take(len)
1666 .collect::<Result<Vec<_>, _>>()?;
1667
1668 Ok(KclValue::HomArray { value, ty: ty.clone() })
1669 }
1670 KclValue::KclNone { .. } if len.satisfied(0, false).is_some() => Ok(KclValue::HomArray {
1671 value: Vec::new(),
1672 ty: ty.clone(),
1673 }),
1674 _ if len.satisfied(1, false).is_some() => self.coerce(ty, convert_units, exec_state),
1675 _ => Err(self.into()),
1676 }
1677 }
1678
1679 fn coerce_to_tuple_type(
1680 &self,
1681 tys: &[RuntimeType],
1682 convert_units: bool,
1683 exec_state: &mut ExecState,
1684 ) -> Result<KclValue, CoercionError> {
1685 match self {
1686 KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } if value.len() == tys.len() => {
1687 let mut result = Vec::new();
1688 for (i, t) in tys.iter().enumerate() {
1689 result.push(value[i].coerce(t, convert_units, exec_state)?);
1690 }
1691
1692 Ok(KclValue::Tuple {
1693 value: result,
1694 meta: Vec::new(),
1695 })
1696 }
1697 KclValue::KclNone { meta, .. } if tys.is_empty() => Ok(KclValue::Tuple {
1698 value: Vec::new(),
1699 meta: meta.clone(),
1700 }),
1701 _ if tys.len() == 1 => self.coerce(&tys[0], convert_units, exec_state),
1702 _ => Err(self.into()),
1703 }
1704 }
1705
1706 fn coerce_to_union_type(
1707 &self,
1708 tys: &[RuntimeType],
1709 convert_units: bool,
1710 exec_state: &mut ExecState,
1711 ) -> Result<KclValue, CoercionError> {
1712 for t in tys {
1713 if let Ok(v) = self.coerce(t, convert_units, exec_state) {
1714 return Ok(v);
1715 }
1716 }
1717
1718 Err(self.into())
1719 }
1720
1721 fn coerce_to_object_type(
1722 &self,
1723 tys: &[(String, RuntimeType)],
1724 constrainable: bool,
1725 _convert_units: bool,
1726 _exec_state: &mut ExecState,
1727 ) -> Result<KclValue, CoercionError> {
1728 match self {
1729 KclValue::Object { value, meta, .. } => {
1730 for (s, t) in tys {
1731 if !value.get(s).ok_or(CoercionError::from(self))?.has_type(t) {
1733 return Err(self.into());
1734 }
1735 }
1736 Ok(KclValue::Object {
1738 value: value.clone(),
1739 meta: meta.clone(),
1740 constrainable,
1743 object_kind: Default::default(),
1744 })
1745 }
1746 KclValue::KclNone { meta, .. } if tys.is_empty() => Ok(KclValue::Object {
1747 value: HashMap::new(),
1748 meta: meta.clone(),
1749 constrainable,
1750 object_kind: Default::default(),
1751 }),
1752 _ => Err(self.into()),
1753 }
1754 }
1755
1756 pub fn principal_type(&self) -> Option<RuntimeType> {
1757 match self {
1758 KclValue::Bool { .. } => Some(RuntimeType::Primitive(PrimitiveType::Boolean)),
1759 KclValue::Number { ty, .. } => Some(RuntimeType::Primitive(PrimitiveType::Number(*ty))),
1760 KclValue::String { .. } => Some(RuntimeType::Primitive(PrimitiveType::String)),
1761 KclValue::SketchVar { value, .. } => Some(RuntimeType::Primitive(PrimitiveType::Number(value.ty))),
1762 KclValue::SketchConstraint { .. } => Some(RuntimeType::Primitive(PrimitiveType::Constraint)),
1763 KclValue::Object {
1764 value, constrainable, ..
1765 } => {
1766 let properties = value
1767 .iter()
1768 .map(|(k, v)| v.principal_type().map(|t| (k.clone(), t)))
1769 .collect::<Option<Vec<_>>>()?;
1770 Some(RuntimeType::Object(properties, *constrainable))
1771 }
1772 KclValue::GdtAnnotation { .. } => Some(RuntimeType::Primitive(PrimitiveType::GdtAnnotation)),
1773 KclValue::Plane { .. } => Some(RuntimeType::Primitive(PrimitiveType::Plane)),
1774 KclValue::Sketch { .. } => Some(RuntimeType::Primitive(PrimitiveType::Sketch)),
1775 KclValue::Solid { .. } => Some(RuntimeType::Primitive(PrimitiveType::Solid)),
1776 KclValue::Face { .. } => Some(RuntimeType::Primitive(PrimitiveType::Face)),
1777 KclValue::Segment { .. } => Some(RuntimeType::Primitive(PrimitiveType::Segment)),
1778 KclValue::Helix { .. } => Some(RuntimeType::Primitive(PrimitiveType::Helix)),
1779 KclValue::ImportedGeometry(..) => Some(RuntimeType::Primitive(PrimitiveType::ImportedGeometry)),
1780 KclValue::Tuple { value, .. } => Some(RuntimeType::Tuple(
1781 value.iter().map(|v| v.principal_type()).collect::<Option<Vec<_>>>()?,
1782 )),
1783 KclValue::HomArray { ty, value, .. } => {
1784 Some(RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Known(value.len())))
1785 }
1786 KclValue::TagIdentifier(_) => Some(RuntimeType::Primitive(PrimitiveType::TaggedEdge)),
1787 KclValue::TagDeclarator(_) => Some(RuntimeType::Primitive(PrimitiveType::TagDecl)),
1788 KclValue::Uuid { .. } => Some(RuntimeType::Primitive(PrimitiveType::Edge)),
1789 KclValue::Function { .. } => Some(RuntimeType::Primitive(PrimitiveType::Function)),
1790 KclValue::KclNone { .. } => Some(RuntimeType::Primitive(PrimitiveType::None)),
1791 KclValue::Module { .. } | KclValue::Type { .. } => None,
1792 KclValue::BoundedEdge { .. } => Some(RuntimeType::Primitive(PrimitiveType::BoundedEdge)),
1793 }
1794 }
1795
1796 pub fn principal_type_string(&self) -> String {
1797 if let Some(ty) = self.principal_type() {
1798 return format!("`{ty}`");
1799 }
1800
1801 match self {
1802 KclValue::Module { .. } => "module",
1803 KclValue::KclNone { .. } => "none",
1804 KclValue::Type { .. } => "type",
1805 _ => {
1806 debug_assert!(false);
1807 "<unexpected type>"
1808 }
1809 }
1810 .to_owned()
1811 }
1812}
1813
1814#[cfg(test)]
1815mod test {
1816 use super::*;
1817 use crate::execution::ExecTestResults;
1818 use crate::execution::parse_execute;
1819
1820 async fn new_exec_state() -> (crate::ExecutorContext, ExecState) {
1821 let ctx = crate::ExecutorContext::new_mock(None).await;
1822 let exec_state = ExecState::new(&ctx);
1823 (ctx, exec_state)
1824 }
1825
1826 fn values(exec_state: &mut ExecState) -> Vec<KclValue> {
1827 vec![
1828 KclValue::Bool {
1829 value: true,
1830 meta: Vec::new(),
1831 },
1832 KclValue::Number {
1833 value: 1.0,
1834 ty: NumericType::count(),
1835 meta: Vec::new(),
1836 },
1837 KclValue::String {
1838 value: "hello".to_owned(),
1839 meta: Vec::new(),
1840 },
1841 KclValue::Tuple {
1842 value: Vec::new(),
1843 meta: Vec::new(),
1844 },
1845 KclValue::HomArray {
1846 value: Vec::new(),
1847 ty: RuntimeType::solid(),
1848 },
1849 KclValue::Object {
1850 value: crate::execution::KclObjectFields::new(),
1851 meta: Vec::new(),
1852 constrainable: false,
1853 object_kind: Default::default(),
1854 },
1855 KclValue::TagIdentifier(Box::new("foo".parse().unwrap())),
1856 KclValue::TagDeclarator(Box::new(crate::parsing::ast::types::TagDeclarator::new("foo"))),
1857 KclValue::Plane {
1858 value: Box::new(
1859 Plane::from_plane_data_skipping_engine(crate::std::sketch::PlaneData::XY, exec_state).unwrap(),
1860 ),
1861 },
1862 KclValue::ImportedGeometry(crate::execution::ImportedGeometry::new(
1864 uuid::Uuid::nil(),
1865 Vec::new(),
1866 Vec::new(),
1867 )),
1868 ]
1870 }
1871
1872 #[track_caller]
1873 fn assert_coerce_results(
1874 value: &KclValue,
1875 super_type: &RuntimeType,
1876 expected_value: &KclValue,
1877 exec_state: &mut ExecState,
1878 ) {
1879 let is_subtype = value == expected_value;
1880 let actual = value.coerce(super_type, true, exec_state).unwrap();
1881 assert_eq!(&actual, expected_value);
1882 assert_eq!(
1883 is_subtype,
1884 value.principal_type().is_some() && value.principal_type().unwrap().subtype(super_type),
1885 "{:?} <: {super_type:?} should be {is_subtype}",
1886 value.principal_type().unwrap()
1887 );
1888 assert!(
1889 expected_value.principal_type().unwrap().subtype(super_type),
1890 "{} <: {super_type}",
1891 expected_value.principal_type().unwrap()
1892 )
1893 }
1894
1895 #[tokio::test(flavor = "multi_thread")]
1896 async fn coerce_idempotent() {
1897 let (ctx, mut exec_state) = new_exec_state().await;
1898 let values = values(&mut exec_state);
1899 for v in &values {
1900 let ty = v.principal_type().unwrap();
1902 assert_coerce_results(v, &ty, v, &mut exec_state);
1903
1904 let uty1 = RuntimeType::Union(vec![ty.clone()]);
1906 let uty2 = RuntimeType::Union(vec![ty.clone(), RuntimeType::Primitive(PrimitiveType::Boolean)]);
1907 assert_coerce_results(v, &uty1, v, &mut exec_state);
1908 assert_coerce_results(v, &uty2, v, &mut exec_state);
1909
1910 let aty = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::None);
1912 let aty1 = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Known(1));
1913 let aty0 = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Minimum(1));
1914
1915 match v {
1916 KclValue::HomArray { .. } => {
1917 assert_coerce_results(
1919 v,
1920 &aty,
1921 &KclValue::HomArray {
1922 value: vec![],
1923 ty: ty.clone(),
1924 },
1925 &mut exec_state,
1926 );
1927 v.coerce(&aty1, true, &mut exec_state).unwrap_err();
1930 v.coerce(&aty0, true, &mut exec_state).unwrap_err();
1933 }
1934 KclValue::Tuple { .. } => {}
1935 _ => {
1936 assert_coerce_results(v, &aty, v, &mut exec_state);
1937 assert_coerce_results(v, &aty1, v, &mut exec_state);
1938 assert_coerce_results(v, &aty0, v, &mut exec_state);
1939
1940 let tty = RuntimeType::Tuple(vec![ty.clone()]);
1942 assert_coerce_results(v, &tty, v, &mut exec_state);
1943 }
1944 }
1945 }
1946
1947 for v in &values[1..] {
1948 v.coerce(&RuntimeType::Primitive(PrimitiveType::Boolean), true, &mut exec_state)
1950 .unwrap_err();
1951 }
1952 ctx.close().await;
1953 }
1954
1955 #[tokio::test(flavor = "multi_thread")]
1956 async fn coerce_none() {
1957 let (ctx, mut exec_state) = new_exec_state().await;
1958 let none = KclValue::KclNone {
1959 value: crate::parsing::ast::types::KclNone::new(),
1960 meta: Vec::new(),
1961 };
1962
1963 let aty = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::None);
1964 let aty0 = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::Known(0));
1965 let aty1 = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::Known(1));
1966 let aty1p = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::Minimum(1));
1967 assert_coerce_results(
1968 &none,
1969 &aty,
1970 &KclValue::HomArray {
1971 value: Vec::new(),
1972 ty: RuntimeType::solid(),
1973 },
1974 &mut exec_state,
1975 );
1976 assert_coerce_results(
1977 &none,
1978 &aty0,
1979 &KclValue::HomArray {
1980 value: Vec::new(),
1981 ty: RuntimeType::solid(),
1982 },
1983 &mut exec_state,
1984 );
1985 none.coerce(&aty1, true, &mut exec_state).unwrap_err();
1986 none.coerce(&aty1p, true, &mut exec_state).unwrap_err();
1987
1988 let tty = RuntimeType::Tuple(vec![]);
1989 let tty1 = RuntimeType::Tuple(vec![RuntimeType::solid()]);
1990 assert_coerce_results(
1991 &none,
1992 &tty,
1993 &KclValue::Tuple {
1994 value: Vec::new(),
1995 meta: Vec::new(),
1996 },
1997 &mut exec_state,
1998 );
1999 none.coerce(&tty1, true, &mut exec_state).unwrap_err();
2000
2001 let oty = RuntimeType::Object(vec![], false);
2002 assert_coerce_results(
2003 &none,
2004 &oty,
2005 &KclValue::Object {
2006 value: HashMap::new(),
2007 meta: Vec::new(),
2008 constrainable: false,
2009 object_kind: Default::default(),
2010 },
2011 &mut exec_state,
2012 );
2013 ctx.close().await;
2014 }
2015
2016 #[tokio::test(flavor = "multi_thread")]
2017 async fn coerce_record() {
2018 let (ctx, mut exec_state) = new_exec_state().await;
2019
2020 let obj0 = KclValue::Object {
2021 value: HashMap::new(),
2022 meta: Vec::new(),
2023 constrainable: false,
2024 object_kind: Default::default(),
2025 };
2026 let obj1 = KclValue::Object {
2027 value: [(
2028 "foo".to_owned(),
2029 KclValue::Bool {
2030 value: true,
2031 meta: Vec::new(),
2032 },
2033 )]
2034 .into(),
2035 meta: Vec::new(),
2036 constrainable: false,
2037 object_kind: Default::default(),
2038 };
2039 let obj2 = KclValue::Object {
2040 value: [
2041 (
2042 "foo".to_owned(),
2043 KclValue::Bool {
2044 value: true,
2045 meta: Vec::new(),
2046 },
2047 ),
2048 (
2049 "bar".to_owned(),
2050 KclValue::Number {
2051 value: 0.0,
2052 ty: NumericType::count(),
2053 meta: Vec::new(),
2054 },
2055 ),
2056 (
2057 "baz".to_owned(),
2058 KclValue::Number {
2059 value: 42.0,
2060 ty: NumericType::count(),
2061 meta: Vec::new(),
2062 },
2063 ),
2064 ]
2065 .into(),
2066 meta: Vec::new(),
2067 constrainable: false,
2068 object_kind: Default::default(),
2069 };
2070
2071 let ty0 = RuntimeType::Object(vec![], false);
2072 assert_coerce_results(&obj0, &ty0, &obj0, &mut exec_state);
2073 assert_coerce_results(&obj1, &ty0, &obj1, &mut exec_state);
2074 assert_coerce_results(&obj2, &ty0, &obj2, &mut exec_state);
2075
2076 let ty1 = RuntimeType::Object(
2077 vec![("foo".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean))],
2078 false,
2079 );
2080 obj0.coerce(&ty1, true, &mut exec_state).unwrap_err();
2081 assert_coerce_results(&obj1, &ty1, &obj1, &mut exec_state);
2082 assert_coerce_results(&obj2, &ty1, &obj2, &mut exec_state);
2083
2084 let ty2 = RuntimeType::Object(
2086 vec![
2087 (
2088 "bar".to_owned(),
2089 RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2090 ),
2091 ("foo".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean)),
2092 ],
2093 false,
2094 );
2095 obj0.coerce(&ty2, true, &mut exec_state).unwrap_err();
2096 obj1.coerce(&ty2, true, &mut exec_state).unwrap_err();
2097 assert_coerce_results(&obj2, &ty2, &obj2, &mut exec_state);
2098
2099 let tyq = RuntimeType::Object(
2101 vec![("qux".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean))],
2102 false,
2103 );
2104 obj0.coerce(&tyq, true, &mut exec_state).unwrap_err();
2105 obj1.coerce(&tyq, true, &mut exec_state).unwrap_err();
2106 obj2.coerce(&tyq, true, &mut exec_state).unwrap_err();
2107
2108 let ty1 = RuntimeType::Object(
2110 vec![("bar".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean))],
2111 false,
2112 );
2113 obj2.coerce(&ty1, true, &mut exec_state).unwrap_err();
2114 ctx.close().await;
2115 }
2116
2117 #[tokio::test(flavor = "multi_thread")]
2118 async fn coerce_array() {
2119 let (ctx, mut exec_state) = new_exec_state().await;
2120
2121 let hom_arr = KclValue::HomArray {
2122 value: vec![
2123 KclValue::Number {
2124 value: 0.0,
2125 ty: NumericType::count(),
2126 meta: Vec::new(),
2127 },
2128 KclValue::Number {
2129 value: 1.0,
2130 ty: NumericType::count(),
2131 meta: Vec::new(),
2132 },
2133 KclValue::Number {
2134 value: 2.0,
2135 ty: NumericType::count(),
2136 meta: Vec::new(),
2137 },
2138 KclValue::Number {
2139 value: 3.0,
2140 ty: NumericType::count(),
2141 meta: Vec::new(),
2142 },
2143 ],
2144 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2145 };
2146 let mixed1 = KclValue::Tuple {
2147 value: vec![
2148 KclValue::Number {
2149 value: 0.0,
2150 ty: NumericType::count(),
2151 meta: Vec::new(),
2152 },
2153 KclValue::Number {
2154 value: 1.0,
2155 ty: NumericType::count(),
2156 meta: Vec::new(),
2157 },
2158 ],
2159 meta: Vec::new(),
2160 };
2161 let mixed2 = KclValue::Tuple {
2162 value: vec![
2163 KclValue::Number {
2164 value: 0.0,
2165 ty: NumericType::count(),
2166 meta: Vec::new(),
2167 },
2168 KclValue::Bool {
2169 value: true,
2170 meta: Vec::new(),
2171 },
2172 ],
2173 meta: Vec::new(),
2174 };
2175
2176 let tyh = RuntimeType::Array(
2178 Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2179 ArrayLen::Known(4),
2180 );
2181 let tym1 = RuntimeType::Tuple(vec![
2182 RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2183 RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2184 ]);
2185 let tym2 = RuntimeType::Tuple(vec![
2186 RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2187 RuntimeType::Primitive(PrimitiveType::Boolean),
2188 ]);
2189 assert_coerce_results(&hom_arr, &tyh, &hom_arr, &mut exec_state);
2190 assert_coerce_results(&mixed1, &tym1, &mixed1, &mut exec_state);
2191 assert_coerce_results(&mixed2, &tym2, &mixed2, &mut exec_state);
2192 mixed1.coerce(&tym2, true, &mut exec_state).unwrap_err();
2193 mixed2.coerce(&tym1, true, &mut exec_state).unwrap_err();
2194
2195 let tyhn = RuntimeType::Array(
2197 Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2198 ArrayLen::None,
2199 );
2200 let tyh1 = RuntimeType::Array(
2201 Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2202 ArrayLen::Minimum(1),
2203 );
2204 let tyh3 = RuntimeType::Array(
2205 Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2206 ArrayLen::Known(3),
2207 );
2208 let tyhm3 = RuntimeType::Array(
2209 Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2210 ArrayLen::Minimum(3),
2211 );
2212 let tyhm5 = RuntimeType::Array(
2213 Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2214 ArrayLen::Minimum(5),
2215 );
2216 assert_coerce_results(&hom_arr, &tyhn, &hom_arr, &mut exec_state);
2217 assert_coerce_results(&hom_arr, &tyh1, &hom_arr, &mut exec_state);
2218 hom_arr.coerce(&tyh3, true, &mut exec_state).unwrap_err();
2219 assert_coerce_results(&hom_arr, &tyhm3, &hom_arr, &mut exec_state);
2220 hom_arr.coerce(&tyhm5, true, &mut exec_state).unwrap_err();
2221
2222 let hom_arr0 = KclValue::HomArray {
2223 value: vec![],
2224 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2225 };
2226 assert_coerce_results(&hom_arr0, &tyhn, &hom_arr0, &mut exec_state);
2227 hom_arr0.coerce(&tyh1, true, &mut exec_state).unwrap_err();
2228 hom_arr0.coerce(&tyh3, true, &mut exec_state).unwrap_err();
2229
2230 let tym1 = RuntimeType::Tuple(vec![
2233 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2234 RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2235 ]);
2236 let tym2 = RuntimeType::Tuple(vec![
2237 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2238 RuntimeType::Primitive(PrimitiveType::Boolean),
2239 ]);
2240 assert_coerce_results(&mixed1, &tym1, &mixed1, &mut exec_state);
2243 assert_coerce_results(&mixed2, &tym2, &mixed2, &mut exec_state);
2244
2245 let hom_arr_2 = KclValue::HomArray {
2247 value: vec![
2248 KclValue::Number {
2249 value: 0.0,
2250 ty: NumericType::count(),
2251 meta: Vec::new(),
2252 },
2253 KclValue::Number {
2254 value: 1.0,
2255 ty: NumericType::count(),
2256 meta: Vec::new(),
2257 },
2258 ],
2259 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2260 };
2261 let mixed0 = KclValue::Tuple {
2262 value: vec![],
2263 meta: Vec::new(),
2264 };
2265 assert_coerce_results(&mixed1, &tyhn, &hom_arr_2, &mut exec_state);
2266 assert_coerce_results(&mixed1, &tyh1, &hom_arr_2, &mut exec_state);
2267 assert_coerce_results(&mixed0, &tyhn, &hom_arr0, &mut exec_state);
2268 mixed0.coerce(&tyh, true, &mut exec_state).unwrap_err();
2269 mixed0.coerce(&tyh1, true, &mut exec_state).unwrap_err();
2270
2271 assert_coerce_results(&hom_arr_2, &tym1, &mixed1, &mut exec_state);
2273 hom_arr.coerce(&tym1, true, &mut exec_state).unwrap_err();
2274 hom_arr_2.coerce(&tym2, true, &mut exec_state).unwrap_err();
2275
2276 mixed0.coerce(&tym1, true, &mut exec_state).unwrap_err();
2277 mixed0.coerce(&tym2, true, &mut exec_state).unwrap_err();
2278 ctx.close().await;
2279 }
2280
2281 #[tokio::test(flavor = "multi_thread")]
2282 async fn coerce_union() {
2283 let (ctx, mut exec_state) = new_exec_state().await;
2284
2285 assert!(RuntimeType::Union(vec![]).subtype(&RuntimeType::Union(vec![
2287 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2288 RuntimeType::Primitive(PrimitiveType::Boolean)
2289 ])));
2290 assert!(
2291 RuntimeType::Union(vec![RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any))]).subtype(
2292 &RuntimeType::Union(vec![
2293 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2294 RuntimeType::Primitive(PrimitiveType::Boolean)
2295 ])
2296 )
2297 );
2298 assert!(
2299 RuntimeType::Union(vec![
2300 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2301 RuntimeType::Primitive(PrimitiveType::Boolean)
2302 ])
2303 .subtype(&RuntimeType::Union(vec![
2304 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2305 RuntimeType::Primitive(PrimitiveType::Boolean)
2306 ]))
2307 );
2308
2309 let count = KclValue::Number {
2311 value: 1.0,
2312 ty: NumericType::count(),
2313 meta: Vec::new(),
2314 };
2315
2316 let tya = RuntimeType::Union(vec![RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any))]);
2317 let tya2 = RuntimeType::Union(vec![
2318 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2319 RuntimeType::Primitive(PrimitiveType::Boolean),
2320 ]);
2321 assert_coerce_results(&count, &tya, &count, &mut exec_state);
2322 assert_coerce_results(&count, &tya2, &count, &mut exec_state);
2323
2324 let tyb = RuntimeType::Union(vec![RuntimeType::Primitive(PrimitiveType::Boolean)]);
2326 let tyb2 = RuntimeType::Union(vec![
2327 RuntimeType::Primitive(PrimitiveType::Boolean),
2328 RuntimeType::Primitive(PrimitiveType::String),
2329 ]);
2330 count.coerce(&tyb, true, &mut exec_state).unwrap_err();
2331 count.coerce(&tyb2, true, &mut exec_state).unwrap_err();
2332 ctx.close().await;
2333 }
2334
2335 #[tokio::test(flavor = "multi_thread")]
2336 async fn coerce_axes() {
2337 let (ctx, mut exec_state) = new_exec_state().await;
2338
2339 assert!(RuntimeType::Primitive(PrimitiveType::Axis2d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis2d)));
2341 assert!(RuntimeType::Primitive(PrimitiveType::Axis3d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis3d)));
2342 assert!(!RuntimeType::Primitive(PrimitiveType::Axis3d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis2d)));
2343 assert!(!RuntimeType::Primitive(PrimitiveType::Axis2d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis3d)));
2344
2345 let a2d = KclValue::Object {
2347 value: [
2348 (
2349 "origin".to_owned(),
2350 KclValue::HomArray {
2351 value: vec![
2352 KclValue::Number {
2353 value: 0.0,
2354 ty: NumericType::mm(),
2355 meta: Vec::new(),
2356 },
2357 KclValue::Number {
2358 value: 0.0,
2359 ty: NumericType::mm(),
2360 meta: Vec::new(),
2361 },
2362 ],
2363 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
2364 },
2365 ),
2366 (
2367 "direction".to_owned(),
2368 KclValue::HomArray {
2369 value: vec![
2370 KclValue::Number {
2371 value: 1.0,
2372 ty: NumericType::mm(),
2373 meta: Vec::new(),
2374 },
2375 KclValue::Number {
2376 value: 0.0,
2377 ty: NumericType::mm(),
2378 meta: Vec::new(),
2379 },
2380 ],
2381 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
2382 },
2383 ),
2384 ]
2385 .into(),
2386 meta: Vec::new(),
2387 constrainable: false,
2388 object_kind: Default::default(),
2389 };
2390 let a3d = KclValue::Object {
2391 value: [
2392 (
2393 "origin".to_owned(),
2394 KclValue::HomArray {
2395 value: vec![
2396 KclValue::Number {
2397 value: 0.0,
2398 ty: NumericType::mm(),
2399 meta: Vec::new(),
2400 },
2401 KclValue::Number {
2402 value: 0.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 ],
2412 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
2413 },
2414 ),
2415 (
2416 "direction".to_owned(),
2417 KclValue::HomArray {
2418 value: vec![
2419 KclValue::Number {
2420 value: 1.0,
2421 ty: NumericType::mm(),
2422 meta: Vec::new(),
2423 },
2424 KclValue::Number {
2425 value: 0.0,
2426 ty: NumericType::mm(),
2427 meta: Vec::new(),
2428 },
2429 KclValue::Number {
2430 value: 1.0,
2431 ty: NumericType::mm(),
2432 meta: Vec::new(),
2433 },
2434 ],
2435 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
2436 },
2437 ),
2438 ]
2439 .into(),
2440 meta: Vec::new(),
2441 constrainable: false,
2442 object_kind: Default::default(),
2443 };
2444
2445 let ty2d = RuntimeType::Primitive(PrimitiveType::Axis2d);
2446 let ty3d = RuntimeType::Primitive(PrimitiveType::Axis3d);
2447
2448 assert_coerce_results(&a2d, &ty2d, &a2d, &mut exec_state);
2449 assert_coerce_results(&a3d, &ty3d, &a3d, &mut exec_state);
2450 assert_coerce_results(&a3d, &ty2d, &a2d, &mut exec_state);
2451 a2d.coerce(&ty3d, true, &mut exec_state).unwrap_err();
2452 ctx.close().await;
2453 }
2454
2455 #[tokio::test(flavor = "multi_thread")]
2456 async fn coerce_numeric() {
2457 let (ctx, mut exec_state) = new_exec_state().await;
2458
2459 let count = KclValue::Number {
2460 value: 1.0,
2461 ty: NumericType::count(),
2462 meta: Vec::new(),
2463 };
2464 let mm = KclValue::Number {
2465 value: 1.0,
2466 ty: NumericType::mm(),
2467 meta: Vec::new(),
2468 };
2469 let inches = KclValue::Number {
2470 value: 1.0,
2471 ty: NumericType::Known(UnitType::Length(UnitLength::Inches)),
2472 meta: Vec::new(),
2473 };
2474 let rads = KclValue::Number {
2475 value: 1.0,
2476 ty: NumericType::Known(UnitType::Angle(UnitAngle::Radians)),
2477 meta: Vec::new(),
2478 };
2479 let default = KclValue::Number {
2480 value: 1.0,
2481 ty: NumericType::default(),
2482 meta: Vec::new(),
2483 };
2484 let any = KclValue::Number {
2485 value: 1.0,
2486 ty: NumericType::Any,
2487 meta: Vec::new(),
2488 };
2489 let unknown = KclValue::Number {
2490 value: 1.0,
2491 ty: NumericType::Unknown,
2492 meta: Vec::new(),
2493 };
2494
2495 assert_coerce_results(&count, &NumericType::count().into(), &count, &mut exec_state);
2497 assert_coerce_results(&mm, &NumericType::mm().into(), &mm, &mut exec_state);
2498 assert_coerce_results(&any, &NumericType::Any.into(), &any, &mut exec_state);
2499 assert_coerce_results(&unknown, &NumericType::Unknown.into(), &unknown, &mut exec_state);
2500 assert_coerce_results(&default, &NumericType::default().into(), &default, &mut exec_state);
2501
2502 assert_coerce_results(&count, &NumericType::Any.into(), &count, &mut exec_state);
2503 assert_coerce_results(&mm, &NumericType::Any.into(), &mm, &mut exec_state);
2504 assert_coerce_results(&unknown, &NumericType::Any.into(), &unknown, &mut exec_state);
2505 assert_coerce_results(&default, &NumericType::Any.into(), &default, &mut exec_state);
2506
2507 assert_eq!(
2508 default
2509 .coerce(
2510 &NumericType::Default {
2511 len: UnitLength::Yards,
2512 angle: UnitAngle::Degrees,
2513 }
2514 .into(),
2515 true,
2516 &mut exec_state
2517 )
2518 .unwrap(),
2519 default
2520 );
2521
2522 count
2524 .coerce(&NumericType::mm().into(), true, &mut exec_state)
2525 .unwrap_err();
2526 mm.coerce(&NumericType::count().into(), true, &mut exec_state)
2527 .unwrap_err();
2528 unknown
2529 .coerce(&NumericType::mm().into(), true, &mut exec_state)
2530 .unwrap_err();
2531 unknown
2532 .coerce(&NumericType::default().into(), true, &mut exec_state)
2533 .unwrap_err();
2534
2535 count
2536 .coerce(&NumericType::Unknown.into(), true, &mut exec_state)
2537 .unwrap_err();
2538 mm.coerce(&NumericType::Unknown.into(), true, &mut exec_state)
2539 .unwrap_err();
2540 default
2541 .coerce(&NumericType::Unknown.into(), true, &mut exec_state)
2542 .unwrap_err();
2543
2544 assert_eq!(
2545 inches
2546 .coerce(&NumericType::mm().into(), true, &mut exec_state)
2547 .unwrap()
2548 .as_f64()
2549 .unwrap()
2550 .round(),
2551 25.0
2552 );
2553 assert_eq!(
2554 rads.coerce(
2555 &NumericType::Known(UnitType::Angle(UnitAngle::Degrees)).into(),
2556 true,
2557 &mut exec_state
2558 )
2559 .unwrap()
2560 .as_f64()
2561 .unwrap()
2562 .round(),
2563 57.0
2564 );
2565 assert_eq!(
2566 inches
2567 .coerce(&NumericType::default().into(), true, &mut exec_state)
2568 .unwrap()
2569 .as_f64()
2570 .unwrap()
2571 .round(),
2572 1.0
2573 );
2574 assert_eq!(
2575 rads.coerce(&NumericType::default().into(), true, &mut exec_state)
2576 .unwrap()
2577 .as_f64()
2578 .unwrap()
2579 .round(),
2580 1.0
2581 );
2582 ctx.close().await;
2583 }
2584
2585 #[track_caller]
2586 fn assert_value_and_type(name: &str, result: &ExecTestResults, expected: f64, expected_ty: NumericType) {
2587 let mem = result.exec_state.stack();
2588 match mem
2589 .memory
2590 .get_from_owned(name, result.mem_env, SourceRange::default(), 0)
2591 .unwrap()
2592 {
2593 KclValue::Number { value, ty, .. } => {
2594 assert_eq!(value.round(), expected);
2595 assert_eq!(ty, expected_ty);
2596 }
2597 _ => unreachable!(),
2598 }
2599 }
2600
2601 #[tokio::test(flavor = "multi_thread")]
2602 async fn combine_numeric() {
2603 let program = r#"a = 5 + 4
2604b = 5 - 2
2605c = 5mm - 2mm + 10mm
2606d = 5mm - 2 + 10
2607e = 5 - 2mm + 10
2608f = 30mm - 1inch
2609
2610g = 2 * 10
2611h = 2 * 10mm
2612i = 2mm * 10mm
2613j = 2_ * 10
2614k = 2_ * 3mm * 3mm
2615
2616l = 1 / 10
2617m = 2mm / 1mm
2618n = 10inch / 2mm
2619o = 3mm / 3
2620p = 3_ / 4
2621q = 4inch / 2_
2622
2623r = min([0, 3, 42])
2624s = min([0, 3mm, -42])
2625t = min([100, 3in, 142mm])
2626u = min([3rad, 4in])
2627"#;
2628
2629 let result = parse_execute(program).await.unwrap();
2630 assert_eq!(
2631 result.exec_state.issues().len(),
2632 5,
2633 "errors: {:?}",
2634 result.exec_state.issues()
2635 );
2636
2637 assert_value_and_type("a", &result, 9.0, NumericType::default());
2638 assert_value_and_type("b", &result, 3.0, NumericType::default());
2639 assert_value_and_type("c", &result, 13.0, NumericType::mm());
2640 assert_value_and_type("d", &result, 13.0, NumericType::mm());
2641 assert_value_and_type("e", &result, 13.0, NumericType::mm());
2642 assert_value_and_type("f", &result, 5.0, NumericType::mm());
2643
2644 assert_value_and_type("g", &result, 20.0, NumericType::default());
2645 assert_value_and_type("h", &result, 20.0, NumericType::mm());
2646 assert_value_and_type("i", &result, 20.0, NumericType::Unknown);
2647 assert_value_and_type("j", &result, 20.0, NumericType::default());
2648 assert_value_and_type("k", &result, 18.0, NumericType::Unknown);
2649
2650 assert_value_and_type("l", &result, 0.0, NumericType::default());
2651 assert_value_and_type("m", &result, 2.0, NumericType::count());
2652 assert_value_and_type("n", &result, 5.0, NumericType::Unknown);
2653 assert_value_and_type("o", &result, 1.0, NumericType::mm());
2654 assert_value_and_type("p", &result, 1.0, NumericType::count());
2655 assert_value_and_type(
2656 "q",
2657 &result,
2658 2.0,
2659 NumericType::Known(UnitType::Length(UnitLength::Inches)),
2660 );
2661
2662 assert_value_and_type("r", &result, 0.0, NumericType::default());
2663 assert_value_and_type("s", &result, -42.0, NumericType::mm());
2664 assert_value_and_type("t", &result, 3.0, NumericType::Unknown);
2665 assert_value_and_type("u", &result, 3.0, NumericType::Unknown);
2666 }
2667
2668 #[tokio::test(flavor = "multi_thread")]
2669 async fn bad_typed_arithmetic() {
2670 let program = r#"
2671a = 1rad
2672b = 180 / PI * a + 360
2673"#;
2674
2675 let result = parse_execute(program).await.unwrap();
2676
2677 assert_value_and_type("a", &result, 1.0, NumericType::radians());
2678 assert_value_and_type("b", &result, 417.0, NumericType::Unknown);
2679 }
2680
2681 #[tokio::test(flavor = "multi_thread")]
2682 async fn cos_coercions() {
2683 let program = r#"
2684a = cos(units::toRadians(30deg))
2685b = 3 / a
2686c = cos(30deg)
2687d = cos(1rad)
2688"#;
2689
2690 let result = parse_execute(program).await.unwrap();
2691 assert!(
2692 result.exec_state.issues().is_empty(),
2693 "{:?}",
2694 result.exec_state.issues()
2695 );
2696
2697 assert_value_and_type("a", &result, 1.0, NumericType::default());
2698 assert_value_and_type("b", &result, 3.0, NumericType::default());
2699 assert_value_and_type("c", &result, 1.0, NumericType::default());
2700 assert_value_and_type("d", &result, 1.0, NumericType::default());
2701 }
2702
2703 #[tokio::test(flavor = "multi_thread")]
2704 async fn coerce_nested_array() {
2705 let (ctx, mut exec_state) = new_exec_state().await;
2706
2707 let mixed1 = KclValue::HomArray {
2708 value: vec![
2709 KclValue::Number {
2710 value: 0.0,
2711 ty: NumericType::count(),
2712 meta: Vec::new(),
2713 },
2714 KclValue::Number {
2715 value: 1.0,
2716 ty: NumericType::count(),
2717 meta: Vec::new(),
2718 },
2719 KclValue::HomArray {
2720 value: vec![
2721 KclValue::Number {
2722 value: 2.0,
2723 ty: NumericType::count(),
2724 meta: Vec::new(),
2725 },
2726 KclValue::Number {
2727 value: 3.0,
2728 ty: NumericType::count(),
2729 meta: Vec::new(),
2730 },
2731 ],
2732 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2733 },
2734 ],
2735 ty: RuntimeType::any(),
2736 };
2737
2738 let tym1 = RuntimeType::Array(
2740 Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2741 ArrayLen::Minimum(1),
2742 );
2743
2744 let result = KclValue::HomArray {
2745 value: vec![
2746 KclValue::Number {
2747 value: 0.0,
2748 ty: NumericType::count(),
2749 meta: Vec::new(),
2750 },
2751 KclValue::Number {
2752 value: 1.0,
2753 ty: NumericType::count(),
2754 meta: Vec::new(),
2755 },
2756 KclValue::Number {
2757 value: 2.0,
2758 ty: NumericType::count(),
2759 meta: Vec::new(),
2760 },
2761 KclValue::Number {
2762 value: 3.0,
2763 ty: NumericType::count(),
2764 meta: Vec::new(),
2765 },
2766 ],
2767 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2768 };
2769 assert_coerce_results(&mixed1, &tym1, &result, &mut exec_state);
2770 ctx.close().await;
2771 }
2772}