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