1use std::collections::HashMap;
2use std::str::FromStr;
3
4use anyhow::Result;
5pub use kcl_api::NumericType;
6use kcl_api::UnitAngle;
7use kcl_api::UnitLength;
8pub use kcl_api::UnitType;
9use serde::Deserialize;
10use serde::Serialize;
11
12use crate::CompilationIssue;
13use crate::KclError;
14use crate::SourceRange;
15use crate::errors::KclErrorDetails;
16use crate::exec::PlaneKind;
17use crate::execution::ExecState;
18use crate::execution::Plane;
19use crate::execution::PlaneInfo;
20use crate::execution::Point3d;
21use crate::execution::SKETCH_OBJECT_META;
22use crate::execution::SKETCH_OBJECT_META_SKETCH;
23use crate::execution::annotations;
24use crate::execution::kcl_value::EnumTypeId;
25use crate::execution::kcl_value::KclValue;
26use crate::execution::kcl_value::TypeDef;
27use crate::execution::memory::{self};
28use crate::fmt;
29use crate::parsing::ast::types::PrimitiveType as AstPrimitiveType;
30use crate::parsing::ast::types::Type;
31use crate::parsing::token::NumericSuffix;
32use crate::std::args::FromKclValue;
33use crate::std::args::TyF64;
34
35#[derive(Debug, Clone, PartialEq)]
36pub enum RuntimeType {
37 Primitive(PrimitiveType),
38 Array(Box<RuntimeType>, ArrayLen),
39 Union(Vec<RuntimeType>),
40 Tuple(Vec<RuntimeType>),
41 Object(Vec<(String, RuntimeType)>, bool),
42 Enum(EnumTypeId),
46}
47
48impl RuntimeType {
49 pub fn any() -> Self {
50 RuntimeType::Primitive(PrimitiveType::Any)
51 }
52
53 pub fn never() -> Self {
54 RuntimeType::Primitive(PrimitiveType::Never)
55 }
56
57 pub fn any_array() -> Self {
58 RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::None)
59 }
60
61 pub fn edge() -> Self {
62 RuntimeType::Primitive(PrimitiveType::Edge)
63 }
64
65 pub fn function() -> Self {
66 RuntimeType::Primitive(PrimitiveType::Function)
67 }
68
69 pub fn segment() -> Self {
70 RuntimeType::Primitive(PrimitiveType::Segment)
71 }
72
73 pub fn segments() -> Self {
75 RuntimeType::Array(Box::new(Self::segment()), ArrayLen::Minimum(1))
76 }
77
78 pub fn sketch() -> Self {
79 RuntimeType::Primitive(PrimitiveType::Sketch)
80 }
81
82 pub fn sketch_or_surface() -> Self {
83 RuntimeType::Union(vec![Self::sketch(), Self::plane(), Self::face()])
84 }
85
86 pub fn sketches() -> Self {
88 RuntimeType::Array(
89 Box::new(RuntimeType::Primitive(PrimitiveType::Sketch)),
90 ArrayLen::Minimum(1),
91 )
92 }
93
94 pub fn faces() -> Self {
96 RuntimeType::Array(
97 Box::new(RuntimeType::Primitive(PrimitiveType::Face)),
98 ArrayLen::Minimum(1),
99 )
100 }
101
102 pub fn tagged_faces() -> Self {
104 RuntimeType::Array(
105 Box::new(RuntimeType::Primitive(PrimitiveType::TaggedFace)),
106 ArrayLen::Minimum(1),
107 )
108 }
109
110 pub fn solids() -> Self {
112 RuntimeType::Array(
113 Box::new(RuntimeType::Primitive(PrimitiveType::Solid)),
114 ArrayLen::Minimum(1),
115 )
116 }
117
118 pub fn solid() -> Self {
119 RuntimeType::Primitive(PrimitiveType::Solid)
120 }
121
122 pub fn gdt() -> Self {
123 RuntimeType::Primitive(PrimitiveType::GdtAnnotation)
124 }
125
126 pub fn gdts() -> Self {
128 RuntimeType::Array(
129 Box::new(RuntimeType::Primitive(PrimitiveType::GdtAnnotation)),
130 ArrayLen::Minimum(1),
131 )
132 }
133
134 pub fn helices() -> Self {
136 RuntimeType::Array(
137 Box::new(RuntimeType::Primitive(PrimitiveType::Helix)),
138 ArrayLen::Minimum(1),
139 )
140 }
141 pub fn helix() -> Self {
142 RuntimeType::Primitive(PrimitiveType::Helix)
143 }
144
145 pub fn plane() -> Self {
146 RuntimeType::Primitive(PrimitiveType::Plane)
147 }
148
149 pub fn planes() -> Self {
151 RuntimeType::Array(
152 Box::new(RuntimeType::Primitive(PrimitiveType::Plane)),
153 ArrayLen::Minimum(1),
154 )
155 }
156
157 pub fn face() -> Self {
158 RuntimeType::Primitive(PrimitiveType::Face)
159 }
160
161 pub fn tag_decl() -> Self {
162 RuntimeType::Primitive(PrimitiveType::TagDecl)
163 }
164
165 pub fn tagged_face() -> Self {
166 RuntimeType::Primitive(PrimitiveType::TaggedFace)
167 }
168
169 pub fn tagged_face_or_segment() -> Self {
170 RuntimeType::Union(vec![
171 RuntimeType::Primitive(PrimitiveType::TaggedFace),
172 RuntimeType::Primitive(PrimitiveType::Segment),
173 ])
174 }
175
176 pub fn tagged_edge() -> Self {
177 RuntimeType::Primitive(PrimitiveType::TaggedEdge)
178 }
179
180 pub fn bool() -> Self {
181 RuntimeType::Primitive(PrimitiveType::Boolean)
182 }
183
184 pub fn string() -> Self {
185 RuntimeType::Primitive(PrimitiveType::String)
186 }
187
188 pub fn imported() -> Self {
189 RuntimeType::Primitive(PrimitiveType::ImportedGeometry)
190 }
191
192 pub fn point2d() -> Self {
194 RuntimeType::Array(Box::new(RuntimeType::length()), ArrayLen::Known(2))
195 }
196
197 pub fn point3d() -> Self {
199 RuntimeType::Array(Box::new(RuntimeType::length()), ArrayLen::Known(3))
200 }
201
202 pub fn length() -> Self {
203 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Known(UnitType::GenericLength)))
204 }
205
206 pub fn known_length(len: UnitLength) -> Self {
207 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Known(UnitType::Length(len))))
208 }
209
210 pub fn angle() -> Self {
211 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Known(UnitType::GenericAngle)))
212 }
213
214 pub fn radians() -> Self {
215 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Known(UnitType::Angle(
216 UnitAngle::Radians,
217 ))))
218 }
219
220 pub fn degrees() -> Self {
221 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Known(UnitType::Angle(
222 UnitAngle::Degrees,
223 ))))
224 }
225
226 pub fn count() -> Self {
227 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Known(UnitType::Count)))
228 }
229
230 pub fn num_any() -> Self {
231 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any))
232 }
233
234 pub fn from_parsed(
235 value: Type,
236 exec_state: &mut ExecState,
237 source_range: SourceRange,
238 constrainable: bool,
239 suppress_warnings: bool,
240 ) -> Result<Self, CompilationIssue> {
241 match value {
242 Type::Primitive(pt) => Self::from_parsed_primitive(pt, exec_state, source_range, suppress_warnings),
243 Type::Array { ty, len } => {
244 Self::from_parsed(*ty, exec_state, source_range, constrainable, suppress_warnings)
245 .map(|t| RuntimeType::Array(Box::new(t), len))
246 }
247 Type::Union { tys } => tys
248 .into_iter()
249 .map(|t| Self::from_parsed(t.inner, exec_state, source_range, constrainable, suppress_warnings))
250 .collect::<Result<Vec<_>, CompilationIssue>>()
251 .map(RuntimeType::Union),
252 Type::Object { properties } => properties
253 .into_iter()
254 .map(|(id, ty)| {
255 RuntimeType::from_parsed(ty.inner, exec_state, source_range, constrainable, suppress_warnings)
256 .map(|ty| (id.name.clone(), ty))
257 })
258 .collect::<Result<Vec<_>, CompilationIssue>>()
259 .map(|values| RuntimeType::Object(values, constrainable)),
260 }
261 }
262
263 fn from_parsed_primitive(
264 value: AstPrimitiveType,
265 exec_state: &mut ExecState,
266 source_range: SourceRange,
267 suppress_warnings: bool,
268 ) -> Result<Self, CompilationIssue> {
269 Ok(match value {
270 AstPrimitiveType::Any => RuntimeType::Primitive(PrimitiveType::Any),
271 AstPrimitiveType::Never => RuntimeType::never(),
272 AstPrimitiveType::None => RuntimeType::Primitive(PrimitiveType::None),
273 AstPrimitiveType::String => RuntimeType::Primitive(PrimitiveType::String),
274 AstPrimitiveType::Boolean => RuntimeType::Primitive(PrimitiveType::Boolean),
275 AstPrimitiveType::Number(suffix) => {
276 let ty = match suffix {
277 NumericSuffix::None => NumericType::Any,
278 _ => NumericType::from_parsed(suffix, &exec_state.mod_local.settings),
279 };
280 RuntimeType::Primitive(PrimitiveType::Number(ty))
281 }
282 AstPrimitiveType::Named { id } => Self::from_alias(&id.name, exec_state, source_range, suppress_warnings)?,
283 AstPrimitiveType::TagDecl => RuntimeType::Primitive(PrimitiveType::TagDecl),
284 AstPrimitiveType::ImportedGeometry => RuntimeType::Primitive(PrimitiveType::ImportedGeometry),
285 AstPrimitiveType::Function(_) => RuntimeType::Primitive(PrimitiveType::Function),
286 })
287 }
288
289 pub fn from_alias(
290 alias: &str,
291 exec_state: &mut ExecState,
292 source_range: SourceRange,
293 suppress_warnings: bool,
294 ) -> Result<Self, CompilationIssue> {
295 let ty_val = exec_state
296 .stack()
297 .get(&format!("{}{}", memory::TYPE_PREFIX, alias), source_range)
298 .map_err(|_| CompilationIssue::err(source_range, format!("Unknown type: {alias}")))?;
299
300 Ok(match ty_val {
301 KclValue::Type {
302 value, experimental, ..
303 } => {
304 let result = match value {
305 TypeDef::RustRepr(ty, _) => RuntimeType::Primitive(ty),
306 TypeDef::Alias(ty) => ty,
307 TypeDef::Enum(def) => RuntimeType::Enum(def.id().clone()),
308 };
309 if experimental && !suppress_warnings {
310 exec_state.warn_experimental(&format!("the type `{alias}`"), source_range);
311 }
312 result
313 }
314 _ => unreachable!(),
315 })
316 }
317
318 pub fn human_friendly_type(&self) -> String {
319 match self {
320 RuntimeType::Primitive(ty) => ty.to_string(),
321 RuntimeType::Array(ty, ArrayLen::None | ArrayLen::Minimum(0)) => {
322 format!("an array of {}", ty.display_multiple())
323 }
324 RuntimeType::Array(ty, ArrayLen::Minimum(1)) => format!("one or more {}", ty.display_multiple()),
325 RuntimeType::Array(ty, ArrayLen::Minimum(n)) => {
326 format!("an array of {n} or more {}", ty.display_multiple())
327 }
328 RuntimeType::Array(ty, ArrayLen::Known(n)) => format!("an array of {n} {}", ty.display_multiple()),
329 RuntimeType::Union(tys) => tys
330 .iter()
331 .map(Self::human_friendly_type)
332 .collect::<Vec<_>>()
333 .join(" or "),
334 RuntimeType::Tuple(tys) => format!(
335 "a tuple with values of types ({})",
336 tys.iter().map(Self::human_friendly_type).collect::<Vec<_>>().join(", ")
337 ),
338 RuntimeType::Object(..) => format!("an object with fields {self}"),
339 RuntimeType::Enum(id) => id.declared_name().to_owned(),
340 }
341 }
342
343 pub(crate) fn subtype(&self, sup: &RuntimeType) -> bool {
345 use RuntimeType::*;
346
347 match (self, sup) {
348 (Primitive(PrimitiveType::Never), _) => true,
349 (_, Primitive(PrimitiveType::Any)) => true,
350 (Primitive(t1), Primitive(t2)) => t1.subtype(t2),
351 (Array(t1, l1), Array(t2, l2)) => t1.subtype(t2) && l1.subtype(*l2),
352 (Tuple(t1), Tuple(t2)) => t1.len() == t2.len() && t1.iter().zip(t2).all(|(t1, t2)| t1.subtype(t2)),
353
354 (Union(ts1), t2) => ts1.iter().all(|t| t.subtype(t2)),
355 (t1, Union(ts2)) => ts2.iter().any(|t| t1.subtype(t)),
356
357 (Object(t1, _), Object(t2, _)) => t2
358 .iter()
359 .all(|(f, t)| t1.iter().any(|(ff, tt)| f == ff && tt.subtype(t))),
360
361 (Enum(id1), Enum(id2)) => id1 == id2,
365
366 (t1, RuntimeType::Array(t2, l)) if t1.subtype(t2) && ArrayLen::Known(1).subtype(*l) => true,
368 (RuntimeType::Array(t1, ArrayLen::Known(1)), t2) if t1.subtype(t2) => true,
369 (t1, RuntimeType::Tuple(t2)) if !t2.is_empty() && t1.subtype(&t2[0]) => true,
370 (RuntimeType::Tuple(t1), t2) if t1.len() == 1 && t1[0].subtype(t2) => true,
371
372 (Object(t1, _), Primitive(PrimitiveType::Axis2d)) => {
374 t1.iter()
375 .any(|(n, t)| n == "origin" && t.subtype(&RuntimeType::point2d()))
376 && t1
377 .iter()
378 .any(|(n, t)| n == "direction" && t.subtype(&RuntimeType::point2d()))
379 }
380 (Object(t1, _), Primitive(PrimitiveType::Axis3d)) => {
381 t1.iter()
382 .any(|(n, t)| n == "origin" && t.subtype(&RuntimeType::point3d()))
383 && t1
384 .iter()
385 .any(|(n, t)| n == "direction" && t.subtype(&RuntimeType::point3d()))
386 }
387 (Primitive(PrimitiveType::Axis2d), Object(t2, _)) => {
388 t2.iter()
389 .any(|(n, t)| n == "origin" && t.subtype(&RuntimeType::point2d()))
390 && t2
391 .iter()
392 .any(|(n, t)| n == "direction" && t.subtype(&RuntimeType::point2d()))
393 }
394 (Primitive(PrimitiveType::Axis3d), Object(t2, _)) => {
395 t2.iter()
396 .any(|(n, t)| n == "origin" && t.subtype(&RuntimeType::point3d()))
397 && t2
398 .iter()
399 .any(|(n, t)| n == "direction" && t.subtype(&RuntimeType::point3d()))
400 }
401 _ => false,
402 }
403 }
404
405 fn display_multiple(&self) -> String {
406 match self {
407 RuntimeType::Primitive(ty) => ty.display_multiple(),
408 RuntimeType::Array(..) => "arrays".to_owned(),
409 RuntimeType::Union(tys) => tys
410 .iter()
411 .map(|t| t.display_multiple())
412 .collect::<Vec<_>>()
413 .join(" or "),
414 RuntimeType::Tuple(_) => "tuples".to_owned(),
415 RuntimeType::Object(..) => format!("objects with fields {self}"),
416 RuntimeType::Enum(id) => format!("`{}` values", id.declared_name()),
417 }
418 }
419}
420
421impl std::fmt::Display for RuntimeType {
422 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
423 match self {
424 RuntimeType::Primitive(t) => t.fmt(f),
425 RuntimeType::Array(t, l) => match l {
426 ArrayLen::None => write!(f, "[{t}]"),
427 ArrayLen::Minimum(n) => write!(f, "[{t}; {n}+]"),
428 ArrayLen::Known(n) => write!(f, "[{t}; {n}]"),
429 },
430 RuntimeType::Tuple(ts) => write!(
431 f,
432 "({})",
433 ts.iter().map(|t| t.to_string()).collect::<Vec<_>>().join(", ")
434 ),
435 RuntimeType::Union(ts) => write!(
436 f,
437 "{}",
438 ts.iter().map(|t| t.to_string()).collect::<Vec<_>>().join(" | ")
439 ),
440 RuntimeType::Object(items, _) => write!(
441 f,
442 "{{ {} }}",
443 items
444 .iter()
445 .map(|(n, t)| format!("{n}: {t}"))
446 .collect::<Vec<_>>()
447 .join(", ")
448 ),
449 RuntimeType::Enum(id) => write!(f, "{}", id.declared_name()),
450 }
451 }
452}
453
454#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, ts_rs::TS)]
455pub enum ArrayLen {
456 None,
457 Minimum(usize),
458 Known(usize),
459}
460
461impl ArrayLen {
462 pub fn subtype(self, other: ArrayLen) -> bool {
463 match (self, other) {
464 (_, ArrayLen::None) => true,
465 (ArrayLen::Minimum(s1), ArrayLen::Minimum(s2)) if s1 >= s2 => true,
466 (ArrayLen::Known(s1), ArrayLen::Minimum(s2)) if s1 >= s2 => true,
467 (ArrayLen::None, ArrayLen::Minimum(0)) => true,
468 (ArrayLen::Known(s1), ArrayLen::Known(s2)) if s1 == s2 => true,
469 _ => false,
470 }
471 }
472
473 pub fn satisfied(self, len: usize, allow_shrink: bool) -> Option<usize> {
475 match self {
476 ArrayLen::None => Some(len),
477 ArrayLen::Minimum(s) => (len >= s).then_some(len),
478 ArrayLen::Known(s) => (if allow_shrink { len >= s } else { len == s }).then_some(s),
479 }
480 }
481
482 pub fn human_friendly_type(self) -> String {
483 match self {
484 ArrayLen::None | ArrayLen::Minimum(0) => "any number of elements".to_owned(),
485 ArrayLen::Minimum(1) => "at least 1 element".to_owned(),
486 ArrayLen::Minimum(n) => format!("at least {n} elements"),
487 ArrayLen::Known(0) => "no elements".to_owned(),
488 ArrayLen::Known(1) => "exactly 1 element".to_owned(),
489 ArrayLen::Known(n) => format!("exactly {n} elements"),
490 }
491 }
492}
493
494#[derive(Debug, Clone, PartialEq)]
495pub enum PrimitiveType {
496 Any,
497 Never,
498 None,
499 Number(NumericType),
500 String,
501 Boolean,
502 TaggedEdge,
503 TaggedFace,
504 TagDecl,
505 GdtAnnotation,
506 Segment,
507 Sketch,
508 Constraint,
509 Solid,
510 Plane,
511 Helix,
512 Face,
513 Edge,
514 BoundedEdge,
515 Axis2d,
516 Axis3d,
517 ImportedGeometry,
518 Function,
519 CameraView,
520 NamedView,
521}
522
523impl PrimitiveType {
524 fn display_multiple(&self) -> String {
525 match self {
526 PrimitiveType::Any => "any values".to_owned(),
527 PrimitiveType::Never => "values of type `never`".to_owned(),
528 PrimitiveType::None => "none values".to_owned(),
529 PrimitiveType::Number(NumericType::Known(unit)) => format!("numbers({unit})"),
530 PrimitiveType::Number(_) => "numbers".to_owned(),
531 PrimitiveType::String => "strings".to_owned(),
532 PrimitiveType::Boolean => "bools".to_owned(),
533 PrimitiveType::GdtAnnotation => "GD&T Annotations".to_owned(),
534 PrimitiveType::Segment => "Segments".to_owned(),
535 PrimitiveType::Sketch => "Sketches".to_owned(),
536 PrimitiveType::Constraint => "Constraints".to_owned(),
537 PrimitiveType::Solid => "Solids".to_owned(),
538 PrimitiveType::Plane => "Planes".to_owned(),
539 PrimitiveType::Helix => "Helices".to_owned(),
540 PrimitiveType::Face => "Faces".to_owned(),
541 PrimitiveType::Edge => "Edges".to_owned(),
542 PrimitiveType::BoundedEdge => "BoundedEdges".to_owned(),
543 PrimitiveType::Axis2d => "2d axes".to_owned(),
544 PrimitiveType::Axis3d => "3d axes".to_owned(),
545 PrimitiveType::ImportedGeometry => "imported geometries".to_owned(),
546 PrimitiveType::Function => "functions".to_owned(),
547 PrimitiveType::TagDecl => "tag declarators".to_owned(),
548 PrimitiveType::TaggedEdge => "tagged edges".to_owned(),
549 PrimitiveType::TaggedFace => "tagged faces".to_owned(),
550 PrimitiveType::CameraView => "camera views".to_owned(),
551 PrimitiveType::NamedView => "named views".to_owned(),
552 }
553 }
554
555 fn subtype(&self, other: &PrimitiveType) -> bool {
556 match (self, other) {
557 (PrimitiveType::Never, _) => true,
558 (_, PrimitiveType::Any) => true,
559 (PrimitiveType::Number(n1), PrimitiveType::Number(n2)) => n1.subtype(n2),
560 (PrimitiveType::TaggedEdge, PrimitiveType::TaggedFace)
561 | (PrimitiveType::TaggedEdge, PrimitiveType::Edge) => true,
562 (t1, t2) => t1 == t2,
563 }
564 }
565}
566
567impl std::fmt::Display for PrimitiveType {
568 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
569 match self {
570 PrimitiveType::Any => write!(f, "any"),
571 PrimitiveType::Never => write!(f, "never"),
572 PrimitiveType::None => write!(f, "none"),
573 PrimitiveType::Number(NumericType::Known(unit)) => write!(f, "number({unit})"),
574 PrimitiveType::Number(NumericType::Unknown) => write!(f, "number(unknown units)"),
575 PrimitiveType::Number(NumericType::Default { .. }) => write!(f, "number"),
576 PrimitiveType::Number(NumericType::Any) => write!(f, "number(any units)"),
577 PrimitiveType::String => write!(f, "string"),
578 PrimitiveType::Boolean => write!(f, "bool"),
579 PrimitiveType::TagDecl => write!(f, "tag declarator"),
580 PrimitiveType::TaggedEdge => write!(f, "tagged edge"),
581 PrimitiveType::TaggedFace => write!(f, "tagged face"),
582 PrimitiveType::GdtAnnotation => write!(f, "GD&T Annotation"),
583 PrimitiveType::Segment => write!(f, "Segment"),
584 PrimitiveType::Sketch => write!(f, "Sketch"),
585 PrimitiveType::Constraint => write!(f, "Constraint"),
586 PrimitiveType::Solid => write!(f, "Solid"),
587 PrimitiveType::Plane => write!(f, "Plane"),
588 PrimitiveType::Face => write!(f, "Face"),
589 PrimitiveType::Edge => write!(f, "Edge"),
590 PrimitiveType::BoundedEdge => write!(f, "BoundedEdge"),
591 PrimitiveType::Axis2d => write!(f, "Axis2d"),
592 PrimitiveType::Axis3d => write!(f, "Axis3d"),
593 PrimitiveType::Helix => write!(f, "Helix"),
594 PrimitiveType::ImportedGeometry => write!(f, "ImportedGeometry"),
595 PrimitiveType::Function => write!(f, "fn"),
596 PrimitiveType::CameraView => write!(f, "CameraView"),
597 PrimitiveType::NamedView => write!(f, "NamedView"),
598 }
599 }
600}
601
602pub trait NumericTypeExt {
603 fn count() -> Self;
604
605 fn mm() -> Self;
606
607 fn radians() -> Self;
608
609 fn degrees() -> Self;
610
611 fn length(unit: UnitLength) -> Self;
612
613 fn optional_length(unit: Option<UnitLength>) -> Self;
614
615 fn angle(unit: UnitAngle) -> Self;
616
617 fn combine_eq(a: TyF64, b: TyF64, exec_state: &mut ExecState, source_range: SourceRange)
623 -> (f64, f64, NumericType);
624
625 fn combine_eq_coerce(
633 a: TyF64,
634 b: TyF64,
635 for_errs: Option<(&mut ExecState, SourceRange)>,
636 ) -> (f64, f64, NumericType);
637
638 fn combine_eq_array(input: &[TyF64]) -> (Vec<f64>, NumericType);
639
640 fn combine_mul(a: TyF64, b: TyF64) -> (f64, f64, NumericType);
642
643 fn combine_div(a: TyF64, b: TyF64) -> (f64, f64, NumericType);
645
646 fn combine_mod(a: TyF64, b: TyF64) -> (f64, f64, NumericType);
648
649 fn combine_range(
655 a: TyF64,
656 b: TyF64,
657 exec_state: &mut ExecState,
658 source_range: SourceRange,
659 ) -> Result<(f64, f64, NumericType), KclError>;
660
661 fn from_parsed(suffix: NumericSuffix, settings: &super::MetaSettings) -> Self;
662
663 fn subtype(&self, other: &NumericType) -> bool;
664
665 fn is_unknown(&self) -> bool;
666
667 fn is_fully_specified(&self) -> bool;
668
669 fn example_ty(&self) -> Option<String>;
670
671 fn coerce(&self, val: &KclValue) -> Result<KclValue, CoercionError>;
672
673 fn as_length(&self) -> Option<UnitLength>;
674}
675
676impl NumericTypeExt for NumericType {
677 fn count() -> Self {
678 NumericType::Known(UnitType::Count)
679 }
680
681 fn mm() -> Self {
682 NumericType::Known(UnitType::Length(UnitLength::Millimeters))
683 }
684
685 fn radians() -> Self {
686 NumericType::Known(UnitType::Angle(UnitAngle::Radians))
687 }
688
689 fn degrees() -> Self {
690 NumericType::Known(UnitType::Angle(UnitAngle::Degrees))
691 }
692
693 fn length(unit: UnitLength) -> Self {
694 NumericType::Known(UnitType::Length(unit))
695 }
696
697 fn optional_length(unit: Option<UnitLength>) -> Self {
698 match unit {
699 Some(unit) => Self::length(unit),
700 None => NumericType::Unknown,
701 }
702 }
703
704 fn angle(unit: UnitAngle) -> Self {
705 NumericType::Known(UnitType::Angle(unit))
706 }
707
708 fn combine_eq(
714 a: TyF64,
715 b: TyF64,
716 exec_state: &mut ExecState,
717 source_range: SourceRange,
718 ) -> (f64, f64, NumericType) {
719 use NumericType::*;
720 match (a.ty, b.ty) {
721 (at, bt) if at == bt => (a.n, b.n, at),
722 (at, Any) => (a.n, b.n, at),
723 (Any, bt) => (a.n, b.n, bt),
724
725 (t @ Known(UnitType::Length(l1)), Known(UnitType::Length(l2))) => (a.n, adjust_length(l2, b.n, l1).0, t),
726 (t @ Known(UnitType::Angle(a1)), Known(UnitType::Angle(a2))) => (a.n, adjust_angle(a2, b.n, a1).0, t),
727
728 (t @ Known(UnitType::Length(_)), Known(UnitType::GenericLength)) => (a.n, b.n, t),
729 (Known(UnitType::GenericLength), t @ Known(UnitType::Length(_))) => (a.n, b.n, t),
730 (t @ Known(UnitType::Angle(_)), Known(UnitType::GenericAngle)) => (a.n, b.n, t),
731 (Known(UnitType::GenericAngle), t @ Known(UnitType::Angle(_))) => (a.n, b.n, t),
732
733 (Known(UnitType::Count), Default { .. }) | (Default { .. }, Known(UnitType::Count)) => {
734 (a.n, b.n, Known(UnitType::Count))
735 }
736 (t @ Known(UnitType::Length(l1)), Default { len: l2, .. }) if l1 == l2 => (a.n, b.n, t),
737 (Default { len: l1, .. }, t @ Known(UnitType::Length(l2))) if l1 == l2 => (a.n, b.n, t),
738 (t @ Known(UnitType::Angle(a1)), Default { angle: a2, .. }) if a1 == a2 => {
739 if b.n != 0.0 {
740 exec_state.warn(
741 CompilationIssue::err(source_range, "Prefer to use explicit units for angles"),
742 annotations::WARN_ANGLE_UNITS,
743 );
744 }
745 (a.n, b.n, t)
746 }
747 (Default { angle: a1, .. }, t @ Known(UnitType::Angle(a2))) if a1 == a2 => {
748 if a.n != 0.0 {
749 exec_state.warn(
750 CompilationIssue::err(source_range, "Prefer to use explicit units for angles"),
751 annotations::WARN_ANGLE_UNITS,
752 );
753 }
754 (a.n, b.n, t)
755 }
756
757 _ => (a.n, b.n, Unknown),
758 }
759 }
760
761 fn combine_eq_coerce(
769 a: TyF64,
770 b: TyF64,
771 for_errs: Option<(&mut ExecState, SourceRange)>,
772 ) -> (f64, f64, NumericType) {
773 use NumericType::*;
774 match (a.ty, b.ty) {
775 (at, bt) if at == bt => (a.n, b.n, at),
776 (at, Any) => (a.n, b.n, at),
777 (Any, bt) => (a.n, b.n, bt),
778
779 (t @ Known(UnitType::Length(l1)), Known(UnitType::Length(l2))) => (a.n, adjust_length(l2, b.n, l1).0, t),
781 (t @ Known(UnitType::Angle(a1)), Known(UnitType::Angle(a2))) => (a.n, adjust_angle(a2, b.n, a1).0, t),
782
783 (t @ Known(UnitType::Length(_)), Known(UnitType::GenericLength)) => (a.n, b.n, t),
784 (Known(UnitType::GenericLength), t @ Known(UnitType::Length(_))) => (a.n, b.n, t),
785 (t @ Known(UnitType::Angle(_)), Known(UnitType::GenericAngle)) => (a.n, b.n, t),
786 (Known(UnitType::GenericAngle), t @ Known(UnitType::Angle(_))) => (a.n, b.n, t),
787
788 (Known(UnitType::Count), Default { .. }) | (Default { .. }, Known(UnitType::Count)) => {
790 (a.n, b.n, Known(UnitType::Count))
791 }
792
793 (t @ Known(UnitType::Length(l1)), Default { len: l2, .. }) => (a.n, adjust_length(l2, b.n, l1).0, t),
794 (Default { len: l1, .. }, t @ Known(UnitType::Length(l2))) => (adjust_length(l1, a.n, l2).0, b.n, t),
795 (t @ Known(UnitType::Angle(a1)), Default { angle: a2, .. }) => {
796 if let Some((exec_state, source_range)) = for_errs
797 && b.n != 0.0
798 {
799 exec_state.warn(
800 CompilationIssue::err(source_range, "Prefer to use explicit units for angles"),
801 annotations::WARN_ANGLE_UNITS,
802 );
803 }
804 (a.n, adjust_angle(a2, b.n, a1).0, t)
805 }
806 (Default { angle: a1, .. }, t @ Known(UnitType::Angle(a2))) => {
807 if let Some((exec_state, source_range)) = for_errs
808 && a.n != 0.0
809 {
810 exec_state.warn(
811 CompilationIssue::err(source_range, "Prefer to use explicit units for angles"),
812 annotations::WARN_ANGLE_UNITS,
813 );
814 }
815 (adjust_angle(a1, a.n, a2).0, b.n, t)
816 }
817
818 (Default { len: l1, .. }, Known(UnitType::GenericLength)) => (a.n, b.n, Self::length(l1)),
819 (Known(UnitType::GenericLength), Default { len: l2, .. }) => (a.n, b.n, Self::length(l2)),
820 (Default { angle: a1, .. }, Known(UnitType::GenericAngle)) => {
821 if let Some((exec_state, source_range)) = for_errs
822 && b.n != 0.0
823 {
824 exec_state.warn(
825 CompilationIssue::err(source_range, "Prefer to use explicit units for angles"),
826 annotations::WARN_ANGLE_UNITS,
827 );
828 }
829 (a.n, b.n, Self::angle(a1))
830 }
831 (Known(UnitType::GenericAngle), Default { angle: a2, .. }) => {
832 if let Some((exec_state, source_range)) = for_errs
833 && a.n != 0.0
834 {
835 exec_state.warn(
836 CompilationIssue::err(source_range, "Prefer to use explicit units for angles"),
837 annotations::WARN_ANGLE_UNITS,
838 );
839 }
840 (a.n, b.n, Self::angle(a2))
841 }
842
843 (Known(_), Known(_)) | (Default { .. }, Default { .. }) | (_, Unknown) | (Unknown, _) => {
844 (a.n, b.n, Unknown)
845 }
846 }
847 }
848
849 fn combine_eq_array(input: &[TyF64]) -> (Vec<f64>, NumericType) {
850 use NumericType::*;
851 let result = input.iter().map(|t| t.n).collect();
852
853 let mut ty = Any;
854 for i in input {
855 if i.ty == Any || ty == i.ty {
856 continue;
857 }
858
859 match (&ty, &i.ty) {
861 (Any, Default { .. }) if i.n == 0.0 => {}
862 (Any, t) => {
863 ty = *t;
864 }
865 (_, Unknown) | (Default { .. }, Default { .. }) => return (result, Unknown),
866
867 (Known(UnitType::Count), Default { .. }) | (Default { .. }, Known(UnitType::Count)) => {
868 ty = Known(UnitType::Count);
869 }
870
871 (Known(UnitType::Length(l1)), Default { len: l2, .. }) if l1 == l2 || i.n == 0.0 => {}
872 (Known(UnitType::Angle(a1)), Default { angle: a2, .. }) if a1 == a2 || i.n == 0.0 => {}
873
874 (Default { len: l1, .. }, Known(UnitType::Length(l2))) if l1 == l2 => {
875 ty = Known(UnitType::Length(*l2));
876 }
877 (Default { angle: a1, .. }, Known(UnitType::Angle(a2))) if a1 == a2 => {
878 ty = Known(UnitType::Angle(*a2));
879 }
880
881 _ => return (result, Unknown),
882 }
883 }
884
885 if ty == Any && !input.is_empty() {
886 ty = input[0].ty;
887 }
888
889 (result, ty)
890 }
891
892 fn combine_mul(a: TyF64, b: TyF64) -> (f64, f64, NumericType) {
894 use NumericType::*;
895 match (a.ty, b.ty) {
896 (at @ Default { .. }, bt @ Default { .. }) if at == bt => (a.n, b.n, at),
897 (Default { .. }, Default { .. }) => (a.n, b.n, Unknown),
898 (Known(UnitType::Count), bt) => (a.n, b.n, bt),
899 (at, Known(UnitType::Count)) => (a.n, b.n, at),
900 (at @ Known(_), Default { .. }) | (Default { .. }, at @ Known(_)) => (a.n, b.n, at),
901 (Any, Any) => (a.n, b.n, Any),
902 _ => (a.n, b.n, Unknown),
903 }
904 }
905
906 fn combine_div(a: TyF64, b: TyF64) -> (f64, f64, NumericType) {
908 use NumericType::*;
909 match (a.ty, b.ty) {
910 (at @ Default { .. }, bt @ Default { .. }) if at == bt => (a.n, b.n, at),
911 (at, bt) if at == bt => (a.n, b.n, Known(UnitType::Count)),
912 (Default { .. }, Default { .. }) => (a.n, b.n, Unknown),
913 (at, Known(UnitType::Count) | Any) => (a.n, b.n, at),
914 (at @ Known(_), Default { .. }) => (a.n, b.n, at),
915 (Known(UnitType::Count), _) => (a.n, b.n, Known(UnitType::Count)),
916 _ => (a.n, b.n, Unknown),
917 }
918 }
919
920 fn combine_mod(a: TyF64, b: TyF64) -> (f64, f64, NumericType) {
922 use NumericType::*;
923 match (a.ty, b.ty) {
924 (at @ Default { .. }, bt @ Default { .. }) if at == bt => (a.n, b.n, at),
925 (at, bt) if at == bt => (a.n, b.n, at),
926 (Default { .. }, Default { .. }) => (a.n, b.n, Unknown),
927 (at, Known(UnitType::Count) | Any) => (a.n, b.n, at),
928 (at @ Known(_), Default { .. }) => (a.n, b.n, at),
929 (Known(UnitType::Count), _) => (a.n, b.n, Known(UnitType::Count)),
930 _ => (a.n, b.n, Unknown),
931 }
932 }
933
934 fn combine_range(
940 a: TyF64,
941 b: TyF64,
942 exec_state: &mut ExecState,
943 source_range: SourceRange,
944 ) -> Result<(f64, f64, NumericType), KclError> {
945 use NumericType::*;
946 match (a.ty, b.ty) {
947 (at, bt) if at == bt => Ok((a.n, b.n, at)),
948 (at, Any) => Ok((a.n, b.n, at)),
949 (Any, bt) => Ok((a.n, b.n, bt)),
950
951 (Known(UnitType::Length(l1)), Known(UnitType::Length(l2))) => {
952 Err(KclError::new_semantic(KclErrorDetails::new(
953 format!("Range start and range end have incompatible units: {l1} and {l2}"),
954 vec![source_range],
955 )))
956 }
957 (Known(UnitType::Angle(a1)), Known(UnitType::Angle(a2))) => {
958 Err(KclError::new_semantic(KclErrorDetails::new(
959 format!("Range start and range end have incompatible units: {a1} and {a2}"),
960 vec![source_range],
961 )))
962 }
963
964 (t @ Known(UnitType::Length(_)), Known(UnitType::GenericLength)) => Ok((a.n, b.n, t)),
965 (Known(UnitType::GenericLength), t @ Known(UnitType::Length(_))) => Ok((a.n, b.n, t)),
966 (t @ Known(UnitType::Angle(_)), Known(UnitType::GenericAngle)) => Ok((a.n, b.n, t)),
967 (Known(UnitType::GenericAngle), t @ Known(UnitType::Angle(_))) => Ok((a.n, b.n, t)),
968
969 (Known(UnitType::Count), Default { .. }) | (Default { .. }, Known(UnitType::Count)) => {
970 Ok((a.n, b.n, Known(UnitType::Count)))
971 }
972 (t @ Known(UnitType::Length(l1)), Default { len: l2, .. }) if l1 == l2 => Ok((a.n, b.n, t)),
973 (Default { len: l1, .. }, t @ Known(UnitType::Length(l2))) if l1 == l2 => Ok((a.n, b.n, t)),
974 (t @ Known(UnitType::Angle(a1)), Default { angle: a2, .. }) if a1 == a2 => {
975 if b.n != 0.0 {
976 exec_state.warn(
977 CompilationIssue::err(source_range, "Prefer to use explicit units for angles"),
978 annotations::WARN_ANGLE_UNITS,
979 );
980 }
981 Ok((a.n, b.n, t))
982 }
983 (Default { angle: a1, .. }, t @ Known(UnitType::Angle(a2))) if a1 == a2 => {
984 if a.n != 0.0 {
985 exec_state.warn(
986 CompilationIssue::err(source_range, "Prefer to use explicit units for angles"),
987 annotations::WARN_ANGLE_UNITS,
988 );
989 }
990 Ok((a.n, b.n, t))
991 }
992
993 _ => {
994 let a = fmt::human_display_number(a.n, a.ty);
995 let b = fmt::human_display_number(b.n, b.ty);
996 Err(KclError::new_semantic(KclErrorDetails::new(
997 format!(
998 "Range start and range end must be of the same type and have compatible units, but found {a} and {b}",
999 ),
1000 vec![source_range],
1001 )))
1002 }
1003 }
1004 }
1005
1006 fn from_parsed(suffix: NumericSuffix, settings: &super::MetaSettings) -> Self {
1007 match suffix {
1008 NumericSuffix::None => NumericType::Default {
1009 len: settings.default_length_units,
1010 angle: settings.default_angle_units,
1011 },
1012 NumericSuffix::Count => NumericType::Known(UnitType::Count),
1013 NumericSuffix::Length => NumericType::Known(UnitType::GenericLength),
1014 NumericSuffix::Angle => NumericType::Known(UnitType::GenericAngle),
1015 NumericSuffix::Mm => NumericType::Known(UnitType::Length(UnitLength::Millimeters)),
1016 NumericSuffix::Cm => NumericType::Known(UnitType::Length(UnitLength::Centimeters)),
1017 NumericSuffix::M => NumericType::Known(UnitType::Length(UnitLength::Meters)),
1018 NumericSuffix::Inch => NumericType::Known(UnitType::Length(UnitLength::Inches)),
1019 NumericSuffix::Ft => NumericType::Known(UnitType::Length(UnitLength::Feet)),
1020 NumericSuffix::Yd => NumericType::Known(UnitType::Length(UnitLength::Yards)),
1021 NumericSuffix::Deg => NumericType::Known(UnitType::Angle(UnitAngle::Degrees)),
1022 NumericSuffix::Rad => NumericType::Known(UnitType::Angle(UnitAngle::Radians)),
1023 NumericSuffix::Unknown => NumericType::Unknown,
1024 }
1025 }
1026
1027 fn subtype(&self, other: &NumericType) -> bool {
1028 use NumericType::*;
1029
1030 match (self, other) {
1031 (_, Any) => true,
1032 (a, b) if a == b => true,
1033 (
1034 NumericType::Known(UnitType::Length(_))
1035 | NumericType::Known(UnitType::GenericLength)
1036 | NumericType::Default { .. },
1037 NumericType::Known(UnitType::GenericLength),
1038 )
1039 | (
1040 NumericType::Known(UnitType::Angle(_))
1041 | NumericType::Known(UnitType::GenericAngle)
1042 | NumericType::Default { .. },
1043 NumericType::Known(UnitType::GenericAngle),
1044 ) => true,
1045 (Unknown, _) | (_, Unknown) => false,
1046 (_, _) => false,
1047 }
1048 }
1049
1050 fn is_unknown(&self) -> bool {
1051 matches!(
1052 self,
1053 NumericType::Unknown
1054 | NumericType::Known(UnitType::GenericAngle)
1055 | NumericType::Known(UnitType::GenericLength)
1056 )
1057 }
1058
1059 fn is_fully_specified(&self) -> bool {
1060 !matches!(
1061 self,
1062 NumericType::Unknown
1063 | NumericType::Known(UnitType::GenericAngle)
1064 | NumericType::Known(UnitType::GenericLength)
1065 | NumericType::Any
1066 | NumericType::Default { .. }
1067 )
1068 }
1069
1070 fn example_ty(&self) -> Option<String> {
1071 match self {
1072 Self::Known(t) if !self.is_unknown() => Some(t.to_string()),
1073 Self::Default { len, .. } => Some(len.to_string()),
1074 _ => None,
1075 }
1076 }
1077
1078 fn coerce(&self, val: &KclValue) -> Result<KclValue, CoercionError> {
1079 let (value, ty, meta) = match val {
1080 KclValue::Number { value, ty, meta } => (value, ty, meta),
1081 KclValue::SketchVar { .. } => return Ok(val.clone()),
1085 _ => return Err(val.into()),
1086 };
1087
1088 if ty.subtype(self) {
1089 return Ok(KclValue::Number {
1090 value: *value,
1091 ty: *ty,
1092 meta: meta.clone(),
1093 });
1094 }
1095
1096 use NumericType::*;
1098 match (ty, self) {
1099 (Unknown, _) => Err(CoercionError::from(val).with_explicit(self.example_ty().unwrap_or("mm".to_owned()))),
1101 (_, Unknown) => Err(val.into()),
1102
1103 (Any, _) => Ok(KclValue::Number {
1104 value: *value,
1105 ty: *self,
1106 meta: meta.clone(),
1107 }),
1108
1109 (_, Default { .. }) => Ok(KclValue::Number {
1112 value: *value,
1113 ty: *ty,
1114 meta: meta.clone(),
1115 }),
1116
1117 (Known(UnitType::Length(l1)), Known(UnitType::Length(l2))) => {
1119 let (value, ty) = adjust_length(*l1, *value, *l2);
1120 Ok(KclValue::Number {
1121 value,
1122 ty: Known(UnitType::Length(ty)),
1123 meta: meta.clone(),
1124 })
1125 }
1126 (Known(UnitType::Angle(a1)), Known(UnitType::Angle(a2))) => {
1127 let (value, ty) = adjust_angle(*a1, *value, *a2);
1128 Ok(KclValue::Number {
1129 value,
1130 ty: Known(UnitType::Angle(ty)),
1131 meta: meta.clone(),
1132 })
1133 }
1134
1135 (Known(_), Known(_)) => Err(val.into()),
1137
1138 (Default { .. }, Known(UnitType::Count)) => Ok(KclValue::Number {
1140 value: *value,
1141 ty: Known(UnitType::Count),
1142 meta: meta.clone(),
1143 }),
1144
1145 (Default { len: l1, .. }, Known(UnitType::Length(l2))) => {
1146 let (value, ty) = adjust_length(*l1, *value, *l2);
1147 Ok(KclValue::Number {
1148 value,
1149 ty: Known(UnitType::Length(ty)),
1150 meta: meta.clone(),
1151 })
1152 }
1153
1154 (Default { angle: a1, .. }, Known(UnitType::Angle(a2))) => {
1155 let (value, ty) = adjust_angle(*a1, *value, *a2);
1156 Ok(KclValue::Number {
1157 value,
1158 ty: Known(UnitType::Angle(ty)),
1159 meta: meta.clone(),
1160 })
1161 }
1162
1163 (_, _) => unreachable!(),
1164 }
1165 }
1166
1167 fn as_length(&self) -> Option<UnitLength> {
1168 match self {
1169 Self::Known(UnitType::Length(len)) | Self::Default { len, .. } => Some(*len),
1170 _ => None,
1171 }
1172 }
1173}
1174
1175impl From<NumericType> for RuntimeType {
1176 fn from(t: NumericType) -> RuntimeType {
1177 RuntimeType::Primitive(PrimitiveType::Number(t))
1178 }
1179}
1180
1181impl From<UnitLength> for NumericSuffix {
1182 fn from(value: UnitLength) -> Self {
1183 match value {
1184 UnitLength::Millimeters => NumericSuffix::Mm,
1185 UnitLength::Centimeters => NumericSuffix::Cm,
1186 UnitLength::Meters => NumericSuffix::M,
1187 UnitLength::Inches => NumericSuffix::Inch,
1188 UnitLength::Feet => NumericSuffix::Ft,
1189 UnitLength::Yards => NumericSuffix::Yd,
1190 }
1191 }
1192}
1193
1194#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, ts_rs::TS)]
1195pub struct NumericSuffixTypeConvertError;
1196
1197impl TryFrom<NumericType> for NumericSuffix {
1198 type Error = NumericSuffixTypeConvertError;
1199
1200 fn try_from(value: NumericType) -> Result<Self, Self::Error> {
1201 match value {
1202 NumericType::Known(UnitType::Count) => Ok(NumericSuffix::Count),
1203 NumericType::Known(UnitType::Length(unit_length)) => Ok(NumericSuffix::from(unit_length)),
1204 NumericType::Known(UnitType::GenericLength) => Ok(NumericSuffix::Length),
1205 NumericType::Known(UnitType::Angle(UnitAngle::Degrees)) => Ok(NumericSuffix::Deg),
1206 NumericType::Known(UnitType::Angle(UnitAngle::Radians)) => Ok(NumericSuffix::Rad),
1207 NumericType::Known(UnitType::GenericAngle) => Ok(NumericSuffix::Angle),
1208 NumericType::Default { .. } => Ok(NumericSuffix::None),
1209 NumericType::Unknown => Ok(NumericSuffix::Unknown),
1210 NumericType::Any => Err(NumericSuffixTypeConvertError),
1211 }
1212 }
1213}
1214
1215pub fn adjust_length(from: UnitLength, value: f64, to: UnitLength) -> (f64, UnitLength) {
1216 use UnitLength::*;
1217
1218 if from == to {
1219 return (value, to);
1220 }
1221
1222 let (base, base_unit) = match from {
1223 Millimeters => (value, Millimeters),
1224 Centimeters => (value * 10.0, Millimeters),
1225 Meters => (value * 1000.0, Millimeters),
1226 Inches => (value, Inches),
1227 Feet => (value * 12.0, Inches),
1228 Yards => (value * 36.0, Inches),
1229 };
1230 let (base, base_unit) = match (base_unit, to) {
1231 (Millimeters, Inches) | (Millimeters, Feet) | (Millimeters, Yards) => (base / 25.4, Inches),
1232 (Inches, Millimeters) | (Inches, Centimeters) | (Inches, Meters) => (base * 25.4, Millimeters),
1233 _ => (base, base_unit),
1234 };
1235
1236 let value = match (base_unit, to) {
1237 (Millimeters, Millimeters) => base,
1238 (Millimeters, Centimeters) => base / 10.0,
1239 (Millimeters, Meters) => base / 1000.0,
1240 (Inches, Inches) => base,
1241 (Inches, Feet) => base / 12.0,
1242 (Inches, Yards) => base / 36.0,
1243 _ => unreachable!(),
1244 };
1245
1246 (value, to)
1247}
1248
1249pub fn adjust_angle(from: UnitAngle, value: f64, to: UnitAngle) -> (f64, UnitAngle) {
1250 use std::f64::consts::PI;
1251
1252 use UnitAngle::*;
1253
1254 let value = match (from, to) {
1255 (Degrees, Degrees) => value,
1256 (Degrees, Radians) => (value / 180.0) * PI,
1257 (Radians, Degrees) => 180.0 * value / PI,
1258 (Radians, Radians) => value,
1259 };
1260
1261 (value, to)
1262}
1263
1264pub(super) fn length_from_str(s: &str, source_range: SourceRange) -> Result<UnitLength, KclError> {
1265 match s {
1267 "mm" => Ok(UnitLength::Millimeters),
1268 "cm" => Ok(UnitLength::Centimeters),
1269 "m" => Ok(UnitLength::Meters),
1270 "inch" | "in" => Ok(UnitLength::Inches),
1271 "ft" => Ok(UnitLength::Feet),
1272 "yd" => Ok(UnitLength::Yards),
1273 value => Err(KclError::new_semantic(KclErrorDetails::new(
1274 format!("Unexpected value for length units: `{value}`; expected one of `mm`, `cm`, `m`, `in`, `ft`, `yd`"),
1275 vec![source_range],
1276 ))),
1277 }
1278}
1279
1280pub(super) fn angle_from_str(s: &str, source_range: SourceRange) -> Result<UnitAngle, KclError> {
1281 UnitAngle::from_str(s).map_err(|_| {
1282 KclError::new_semantic(KclErrorDetails::new(
1283 format!("Unexpected value for angle units: `{s}`; expected one of `deg`, `rad`"),
1284 vec![source_range],
1285 ))
1286 })
1287}
1288
1289#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1295pub struct CoercionMode {
1296 convert_units: bool,
1297 project_enums: bool,
1298}
1299
1300impl CoercionMode {
1301 pub fn implicit() -> Self {
1306 CoercionMode {
1307 convert_units: true,
1308 project_enums: false,
1309 }
1310 }
1311
1312 pub fn explicit() -> Self {
1316 CoercionMode {
1317 convert_units: false,
1318 project_enums: true,
1319 }
1320 }
1321
1322 pub(crate) fn convert_units(self) -> bool {
1323 self.convert_units
1324 }
1325
1326 pub(crate) fn project_enums(self) -> bool {
1327 self.project_enums
1328 }
1329
1330 pub(crate) fn without_projection(self) -> Self {
1333 CoercionMode {
1334 project_enums: false,
1335 ..self
1336 }
1337 }
1338}
1339
1340#[derive(Debug, Clone)]
1341pub struct CoercionError {
1342 pub found: Option<RuntimeType>,
1343 pub explicit_coercion: Option<String>,
1344 pub message: Option<String>,
1347}
1348
1349impl CoercionError {
1350 fn with_explicit(mut self, c: String) -> Self {
1351 self.explicit_coercion = Some(c);
1352 self
1353 }
1354
1355 fn with_message(mut self, message: String) -> Self {
1356 self.message = Some(message);
1357 self
1358 }
1359}
1360
1361impl From<&'_ KclValue> for CoercionError {
1362 fn from(value: &'_ KclValue) -> Self {
1363 CoercionError {
1364 found: value.principal_type(),
1365 explicit_coercion: None,
1366 message: None,
1367 }
1368 }
1369}
1370
1371impl KclValue {
1372 pub fn has_type(&self, ty: &RuntimeType) -> bool {
1374 let Some(self_ty) = self.principal_type() else {
1375 return false;
1376 };
1377
1378 self_ty.subtype(ty)
1379 }
1380
1381 pub fn coerce(
1388 &self,
1389 ty: &RuntimeType,
1390 mode: CoercionMode,
1391 exec_state: &mut ExecState,
1392 ) -> Result<KclValue, CoercionError> {
1393 match self {
1394 KclValue::Tuple { value, .. }
1395 if value.len() == 1
1396 && !matches!(ty, RuntimeType::Primitive(PrimitiveType::Any) | RuntimeType::Tuple(..)) =>
1397 {
1398 if let Ok(coerced) = value[0].coerce(ty, mode, exec_state) {
1399 return Ok(coerced);
1400 }
1401 }
1402 KclValue::HomArray { value, .. }
1403 if value.len() == 1
1404 && !matches!(ty, RuntimeType::Primitive(PrimitiveType::Any) | RuntimeType::Array(..)) =>
1405 {
1406 if let Ok(coerced) = value[0].coerce(ty, mode, exec_state) {
1407 return Ok(coerced);
1408 }
1409 }
1410 _ => {}
1411 }
1412
1413 match ty {
1414 RuntimeType::Primitive(ty) => self.coerce_to_primitive_type(ty, mode, exec_state),
1415 RuntimeType::Array(ty, len) => self.coerce_to_array_type(ty, mode, *len, exec_state, false),
1416 RuntimeType::Tuple(tys) => self.coerce_to_tuple_type(tys, mode, exec_state),
1417 RuntimeType::Union(tys) => self.coerce_to_union_type(tys, mode, exec_state),
1418 RuntimeType::Object(tys, constrainable) => {
1419 self.coerce_to_object_type(tys, *constrainable, mode, exec_state)
1420 }
1421 RuntimeType::Enum(id) => self.coerce_to_enum_type(id),
1422 }
1423 }
1424
1425 fn coerce_to_enum_type(&self, id: &EnumTypeId) -> Result<KclValue, CoercionError> {
1430 match self {
1431 KclValue::Enum { value } if value.enum_id() == id => Ok(self.clone()),
1432 _ => Err(self.into()),
1433 }
1434 }
1435
1436 fn coerce_to_primitive_type(
1437 &self,
1438 ty: &PrimitiveType,
1439 mode: CoercionMode,
1440 exec_state: &mut ExecState,
1441 ) -> Result<KclValue, CoercionError> {
1442 match ty {
1443 PrimitiveType::Any => Ok(self.clone()),
1444 PrimitiveType::Never => Err(self.into()),
1445 PrimitiveType::None => match self {
1446 KclValue::KclNone { .. } => Ok(self.clone()),
1447 _ => Err(self.into()),
1448 },
1449 PrimitiveType::Number(ty) => {
1450 if let KclValue::Enum { value } = self
1455 && mode.project_enums()
1456 {
1457 return Err(CoercionError::from(self).with_message(format!(
1458 "Cannot project enum `{}` to a number. An enum projects to `string`; projecting to a number is not supported yet.",
1459 value.enum_id().declared_name()
1460 )));
1461 }
1462
1463 if mode.convert_units() {
1464 return ty.coerce(self);
1465 }
1466
1467 if let KclValue::Number { value: n, meta, .. } = &self
1474 && ty.is_fully_specified()
1475 {
1476 let value = KclValue::Number {
1477 ty: NumericType::Any,
1478 value: *n,
1479 meta: meta.clone(),
1480 };
1481 return ty.coerce(&value);
1482 }
1483 ty.coerce(self)
1484 }
1485 PrimitiveType::String => match self {
1486 KclValue::String { .. } => Ok(self.clone()),
1487 KclValue::Enum { value } if mode.project_enums() => Ok(KclValue::String {
1490 value: value.declared_string_repr(),
1491 meta: value.meta().to_vec(),
1492 }),
1493 _ => Err(self.into()),
1494 },
1495 PrimitiveType::Boolean => match self {
1496 KclValue::Bool { .. } => Ok(self.clone()),
1497 _ => Err(self.into()),
1498 },
1499 PrimitiveType::GdtAnnotation => match self {
1500 KclValue::GdtAnnotation { .. } => Ok(self.clone()),
1501 _ => Err(self.into()),
1502 },
1503 PrimitiveType::CameraView => match self {
1504 KclValue::CameraView { .. } => Ok(self.clone()),
1505 _ => Err(self.into()),
1506 },
1507 PrimitiveType::NamedView => match self {
1508 KclValue::NamedView { .. } => Ok(self.clone()),
1509 _ => Err(self.into()),
1510 },
1511 PrimitiveType::Segment => match self {
1512 KclValue::Segment { .. } => Ok(self.clone()),
1513 _ => Err(self.into()),
1514 },
1515 PrimitiveType::Sketch => match self {
1516 KclValue::Sketch { .. } => Ok(self.clone()),
1517 KclValue::Object { value, .. } => {
1518 let Some(meta) = value.get(SKETCH_OBJECT_META) else {
1519 return Err(self.into());
1520 };
1521 let KclValue::Object { value: meta_map, .. } = meta else {
1522 return Err(self.into());
1523 };
1524 let Some(sketch) = meta_map.get(SKETCH_OBJECT_META_SKETCH).and_then(KclValue::as_sketch) else {
1525 return Err(self.into());
1526 };
1527
1528 Ok(KclValue::Sketch {
1529 value: Box::new(sketch.clone()),
1530 })
1531 }
1532 _ => Err(self.into()),
1533 },
1534 PrimitiveType::Constraint => match self {
1535 KclValue::SketchConstraint { .. } => Ok(self.clone()),
1536 _ => Err(self.into()),
1537 },
1538 PrimitiveType::Solid => match self {
1539 KclValue::Solid { .. } => Ok(self.clone()),
1540 _ => Err(self.into()),
1541 },
1542 PrimitiveType::Plane => {
1543 match self {
1544 KclValue::String { value: s, .. }
1545 if [
1546 "xy", "xz", "yz", "-xy", "-xz", "-yz", "XY", "XZ", "YZ", "-XY", "-XZ", "-YZ",
1547 ]
1548 .contains(&&**s) =>
1549 {
1550 Ok(self.clone())
1551 }
1552 KclValue::Plane { .. } => Ok(self.clone()),
1553 KclValue::Object { value, meta, .. } => {
1554 let origin = value
1555 .get("origin")
1556 .and_then(Point3d::from_kcl_val)
1557 .ok_or(CoercionError::from(self))?;
1558 let x_axis = value
1559 .get("xAxis")
1560 .and_then(Point3d::from_kcl_val)
1561 .ok_or(CoercionError::from(self))?;
1562 let y_axis = value
1563 .get("yAxis")
1564 .and_then(Point3d::from_kcl_val)
1565 .ok_or(CoercionError::from(self))?;
1566 let z_axis = x_axis.axes_cross_product(&y_axis);
1567
1568 if value.get("zAxis").is_some() {
1569 exec_state.warn(CompilationIssue::err(
1570 self.into(),
1571 "Object with a zAxis field is being coerced into a plane, but the zAxis is ignored.",
1572 ), annotations::WARN_IGNORED_Z_AXIS);
1573 }
1574
1575 let id = exec_state.mod_local.id_generator.next_uuid();
1576 let info = PlaneInfo {
1577 origin,
1578 x_axis: x_axis.normalize(),
1579 y_axis: y_axis.normalize(),
1580 z_axis: z_axis.normalize(),
1581 };
1582 let plane = Plane {
1583 id,
1584 artifact_id: id.into(),
1585 object_id: None,
1586 kind: PlaneKind::from(&info),
1587 info,
1588 meta: meta.clone(),
1589 };
1590
1591 Ok(KclValue::Plane { value: Box::new(plane) })
1592 }
1593 _ => Err(self.into()),
1594 }
1595 }
1596 PrimitiveType::Face => match self {
1597 KclValue::Face { .. } => Ok(self.clone()),
1598 _ => Err(self.into()),
1599 },
1600 PrimitiveType::Helix => match self {
1601 KclValue::Helix { .. } => Ok(self.clone()),
1602 _ => Err(self.into()),
1603 },
1604 PrimitiveType::Edge => match self {
1605 KclValue::Uuid { .. } => Ok(self.clone()),
1606 KclValue::TagIdentifier { .. } => Ok(self.clone()),
1607 _ => Err(self.into()),
1608 },
1609 PrimitiveType::BoundedEdge => match self {
1610 KclValue::BoundedEdge { .. } => Ok(self.clone()),
1611 _ => Err(self.into()),
1612 },
1613 PrimitiveType::TaggedEdge => match self {
1614 KclValue::TagIdentifier { .. } => Ok(self.clone()),
1615 _ => Err(self.into()),
1616 },
1617 PrimitiveType::TaggedFace => match self {
1618 KclValue::TagIdentifier { .. } => Ok(self.clone()),
1619 s @ KclValue::String { value, .. } if ["start", "end", "START", "END"].contains(&&**value) => {
1620 Ok(s.clone())
1621 }
1622 _ => Err(self.into()),
1623 },
1624 PrimitiveType::Axis2d => match self {
1625 KclValue::Object {
1626 value: values, meta, ..
1627 } => {
1628 if values
1629 .get("origin")
1630 .ok_or(CoercionError::from(self))?
1631 .has_type(&RuntimeType::point2d())
1632 && values
1633 .get("direction")
1634 .ok_or(CoercionError::from(self))?
1635 .has_type(&RuntimeType::point2d())
1636 {
1637 return Ok(self.clone());
1638 }
1639
1640 let origin = values.get("origin").ok_or(self.into()).and_then(|p| {
1641 p.coerce_to_array_type(&RuntimeType::length(), mode, ArrayLen::Known(2), exec_state, true)
1642 })?;
1643 let direction = values.get("direction").ok_or(self.into()).and_then(|p| {
1644 p.coerce_to_array_type(&RuntimeType::length(), mode, ArrayLen::Known(2), exec_state, true)
1645 })?;
1646
1647 Ok(KclValue::Object {
1648 value: [("origin".to_owned(), origin), ("direction".to_owned(), direction)].into(),
1649 meta: meta.clone(),
1650 constrainable: false,
1651 object_kind: Default::default(),
1652 })
1653 }
1654 _ => Err(self.into()),
1655 },
1656 PrimitiveType::Axis3d => match self {
1657 KclValue::Object {
1658 value: values, meta, ..
1659 } => {
1660 if values
1661 .get("origin")
1662 .ok_or(CoercionError::from(self))?
1663 .has_type(&RuntimeType::point3d())
1664 && values
1665 .get("direction")
1666 .ok_or(CoercionError::from(self))?
1667 .has_type(&RuntimeType::point3d())
1668 {
1669 return Ok(self.clone());
1670 }
1671
1672 let origin = values.get("origin").ok_or(self.into()).and_then(|p| {
1673 p.coerce_to_array_type(&RuntimeType::length(), mode, ArrayLen::Known(3), exec_state, true)
1674 })?;
1675 let direction = values.get("direction").ok_or(self.into()).and_then(|p| {
1676 p.coerce_to_array_type(&RuntimeType::length(), mode, ArrayLen::Known(3), exec_state, true)
1677 })?;
1678
1679 Ok(KclValue::Object {
1680 value: [("origin".to_owned(), origin), ("direction".to_owned(), direction)].into(),
1681 meta: meta.clone(),
1682 constrainable: false,
1683 object_kind: Default::default(),
1684 })
1685 }
1686 _ => Err(self.into()),
1687 },
1688 PrimitiveType::ImportedGeometry => match self {
1689 KclValue::ImportedGeometry { .. } => Ok(self.clone()),
1690 _ => Err(self.into()),
1691 },
1692 PrimitiveType::Function => match self {
1693 KclValue::Function { .. } => Ok(self.clone()),
1694 _ => Err(self.into()),
1695 },
1696 PrimitiveType::TagDecl => match self {
1697 KclValue::TagDeclarator { .. } => Ok(self.clone()),
1698 _ => Err(self.into()),
1699 },
1700 }
1701 }
1702
1703 fn coerce_to_array_type(
1704 &self,
1705 ty: &RuntimeType,
1706 mode: CoercionMode,
1707 len: ArrayLen,
1708 exec_state: &mut ExecState,
1709 allow_shrink: bool,
1710 ) -> Result<KclValue, CoercionError> {
1711 match self {
1712 KclValue::HomArray { value, ty: aty, .. } => {
1713 let satisfied_len = len.satisfied(value.len(), allow_shrink);
1714
1715 if aty.subtype(ty) {
1716 return satisfied_len
1723 .map(|len| KclValue::HomArray {
1724 value: value[..len].to_vec(),
1725 ty: aty.clone(),
1726 })
1727 .ok_or(self.into());
1728 }
1729
1730 if let Some(satisfied_len) = satisfied_len {
1732 let value_result = value
1733 .iter()
1734 .take(satisfied_len)
1735 .map(|v| v.coerce(ty, mode, exec_state))
1736 .collect::<Result<Vec<_>, _>>();
1737
1738 if let Ok(value) = value_result {
1739 return Ok(KclValue::HomArray { value, ty: ty.clone() });
1741 }
1742 }
1743
1744 let mut values = Vec::new();
1746 for item in value {
1747 if let KclValue::HomArray { value: inner_value, .. } = item {
1748 for item in inner_value {
1750 values.push(item.coerce(ty, mode, exec_state)?);
1751 }
1752 } else {
1753 values.push(item.coerce(ty, mode, exec_state)?);
1754 }
1755 }
1756
1757 let len = len
1758 .satisfied(values.len(), allow_shrink)
1759 .ok_or(CoercionError::from(self))?;
1760
1761 if len > values.len() {
1762 let message = format!(
1763 "Internal: Expected coerced array length {len} to be less than or equal to original length {}",
1764 values.len()
1765 );
1766 exec_state.err(CompilationIssue::err(self.into(), message.clone()));
1767 #[cfg(debug_assertions)]
1768 panic!("{message}");
1769 }
1770 values.truncate(len);
1771
1772 Ok(KclValue::HomArray {
1773 value: values,
1774 ty: ty.clone(),
1775 })
1776 }
1777 KclValue::Tuple { value, .. } => {
1778 let len = len
1779 .satisfied(value.len(), allow_shrink)
1780 .ok_or(CoercionError::from(self))?;
1781 let value = value
1782 .iter()
1783 .map(|item| item.coerce(ty, mode, exec_state))
1784 .take(len)
1785 .collect::<Result<Vec<_>, _>>()?;
1786
1787 Ok(KclValue::HomArray { value, ty: ty.clone() })
1788 }
1789 KclValue::KclNone { .. } if len.satisfied(0, false).is_some() => Ok(KclValue::HomArray {
1790 value: Vec::new(),
1791 ty: ty.clone(),
1792 }),
1793 _ if len.satisfied(1, false).is_some() => self.coerce(ty, mode, exec_state),
1794 _ => Err(self.into()),
1795 }
1796 }
1797
1798 fn coerce_to_tuple_type(
1799 &self,
1800 tys: &[RuntimeType],
1801 mode: CoercionMode,
1802 exec_state: &mut ExecState,
1803 ) -> Result<KclValue, CoercionError> {
1804 match self {
1805 KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } if value.len() == tys.len() => {
1806 let mut result = Vec::new();
1807 for (i, t) in tys.iter().enumerate() {
1808 result.push(value[i].coerce(t, mode, exec_state)?);
1809 }
1810
1811 Ok(KclValue::Tuple {
1812 value: result,
1813 meta: Vec::new(),
1814 })
1815 }
1816 KclValue::KclNone { meta, .. } if tys.is_empty() => Ok(KclValue::Tuple {
1817 value: Vec::new(),
1818 meta: meta.clone(),
1819 }),
1820 _ if tys.len() == 1 => self.coerce(&tys[0], mode, exec_state),
1821 _ => Err(self.into()),
1822 }
1823 }
1824
1825 fn coerce_to_union_type(
1826 &self,
1827 tys: &[RuntimeType],
1828 mode: CoercionMode,
1829 exec_state: &mut ExecState,
1830 ) -> Result<KclValue, CoercionError> {
1831 if mode.project_enums() {
1837 let exact = mode.without_projection();
1838 for t in tys {
1839 if let Ok(v) = self.coerce(t, exact, exec_state) {
1840 return Ok(v);
1841 }
1842 }
1843 }
1844
1845 for t in tys {
1846 if let Ok(v) = self.coerce(t, mode, exec_state) {
1847 return Ok(v);
1848 }
1849 }
1850
1851 Err(self.into())
1852 }
1853
1854 fn coerce_to_object_type(
1855 &self,
1856 tys: &[(String, RuntimeType)],
1857 constrainable: bool,
1858 _mode: CoercionMode,
1859 _exec_state: &mut ExecState,
1860 ) -> Result<KclValue, CoercionError> {
1861 match self {
1862 KclValue::Object { value, meta, .. } => {
1863 for (s, t) in tys {
1864 if !value.get(s).ok_or(CoercionError::from(self))?.has_type(t) {
1866 return Err(self.into());
1867 }
1868 }
1869 Ok(KclValue::Object {
1871 value: value.clone(),
1872 meta: meta.clone(),
1873 constrainable,
1876 object_kind: Default::default(),
1877 })
1878 }
1879 KclValue::KclNone { meta, .. } if tys.is_empty() => Ok(KclValue::Object {
1880 value: HashMap::new(),
1881 meta: meta.clone(),
1882 constrainable,
1883 object_kind: Default::default(),
1884 }),
1885 _ => Err(self.into()),
1886 }
1887 }
1888
1889 pub fn principal_type(&self) -> Option<RuntimeType> {
1890 match self {
1891 KclValue::Bool { .. } => Some(RuntimeType::Primitive(PrimitiveType::Boolean)),
1892 KclValue::Number { ty, .. } => Some(RuntimeType::Primitive(PrimitiveType::Number(*ty))),
1893 KclValue::String { .. } => Some(RuntimeType::Primitive(PrimitiveType::String)),
1894 KclValue::Enum { value } => Some(RuntimeType::Enum(value.enum_id().clone())),
1895 KclValue::SketchVar { value, .. } => Some(RuntimeType::Primitive(PrimitiveType::Number(value.ty))),
1896 KclValue::SketchConstraint { .. } => Some(RuntimeType::Primitive(PrimitiveType::Constraint)),
1897 KclValue::Object {
1898 value, constrainable, ..
1899 } => {
1900 let properties = value
1901 .iter()
1902 .map(|(k, v)| v.principal_type().map(|t| (k.clone(), t)))
1903 .collect::<Option<Vec<_>>>()?;
1904 Some(RuntimeType::Object(properties, *constrainable))
1905 }
1906 KclValue::GdtAnnotation { .. } => Some(RuntimeType::Primitive(PrimitiveType::GdtAnnotation)),
1907 KclValue::CameraView { .. } => Some(RuntimeType::Primitive(PrimitiveType::CameraView)),
1908 KclValue::NamedView { .. } => Some(RuntimeType::Primitive(PrimitiveType::NamedView)),
1909 KclValue::Plane { .. } => Some(RuntimeType::Primitive(PrimitiveType::Plane)),
1910 KclValue::Sketch { .. } => Some(RuntimeType::Primitive(PrimitiveType::Sketch)),
1911 KclValue::Solid { .. } => Some(RuntimeType::Primitive(PrimitiveType::Solid)),
1912 KclValue::Face { .. } => Some(RuntimeType::Primitive(PrimitiveType::Face)),
1913 KclValue::Segment { .. } => Some(RuntimeType::Primitive(PrimitiveType::Segment)),
1914 KclValue::Helix { .. } => Some(RuntimeType::Primitive(PrimitiveType::Helix)),
1915 KclValue::ImportedGeometry(..) => Some(RuntimeType::Primitive(PrimitiveType::ImportedGeometry)),
1916 KclValue::Tuple { value, .. } => Some(RuntimeType::Tuple(
1917 value.iter().map(|v| v.principal_type()).collect::<Option<Vec<_>>>()?,
1918 )),
1919 KclValue::HomArray { ty, value, .. } => {
1920 Some(RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Known(value.len())))
1921 }
1922 KclValue::TagIdentifier(_) => Some(RuntimeType::Primitive(PrimitiveType::TaggedEdge)),
1923 KclValue::TagDeclarator(_) => Some(RuntimeType::Primitive(PrimitiveType::TagDecl)),
1924 KclValue::Uuid { .. } => Some(RuntimeType::Primitive(PrimitiveType::Edge)),
1925 KclValue::Function { .. } => Some(RuntimeType::Primitive(PrimitiveType::Function)),
1926 KclValue::KclNone { .. } => Some(RuntimeType::Primitive(PrimitiveType::None)),
1927 KclValue::Module { .. } | KclValue::Type { .. } => None,
1928 KclValue::BoundedEdge { .. } => Some(RuntimeType::Primitive(PrimitiveType::BoundedEdge)),
1929 }
1930 }
1931
1932 pub fn principal_type_string(&self) -> String {
1933 if let Some(ty) = self.principal_type() {
1934 return format!("`{ty}`");
1935 }
1936
1937 match self {
1938 KclValue::Module { .. } => "module",
1939 KclValue::KclNone { .. } => "none",
1940 KclValue::Type { .. } => "type",
1941 _ => {
1942 debug_assert!(false);
1943 "<unexpected type>"
1944 }
1945 }
1946 .to_owned()
1947 }
1948}
1949
1950#[cfg(test)]
1951mod test {
1952 use std::sync::Arc;
1953
1954 use super::*;
1955 use crate::ModuleId;
1956 use crate::execution::ExecTestResults;
1957 use crate::execution::kcl_value::EnumTypeDef;
1958 use crate::execution::kcl_value::EnumValue;
1959 use crate::execution::parse_execute;
1960
1961 async fn new_exec_state() -> (crate::ExecutorContext, ExecState) {
1962 let ctx = crate::ExecutorContext::new_mock(None).await;
1963 let exec_state = ExecState::new(&ctx);
1964 (ctx, exec_state)
1965 }
1966
1967 fn values(exec_state: &mut ExecState) -> Vec<KclValue> {
1968 vec![
1969 KclValue::Bool {
1970 value: true,
1971 meta: Vec::new(),
1972 },
1973 KclValue::Number {
1974 value: 1.0,
1975 ty: NumericType::count(),
1976 meta: Vec::new(),
1977 },
1978 KclValue::String {
1979 value: "hello".to_owned(),
1980 meta: Vec::new(),
1981 },
1982 KclValue::Tuple {
1983 value: Vec::new(),
1984 meta: Vec::new(),
1985 },
1986 KclValue::HomArray {
1987 value: Vec::new(),
1988 ty: RuntimeType::solid(),
1989 },
1990 KclValue::Object {
1991 value: crate::execution::KclObjectFields::new(),
1992 meta: Vec::new(),
1993 constrainable: false,
1994 object_kind: Default::default(),
1995 },
1996 KclValue::TagIdentifier(Box::new("foo".parse().unwrap())),
1997 KclValue::TagDeclarator(crate::parsing::ast::types::BoxNode::new(
1998 crate::parsing::ast::types::TagDeclarator::new("foo"),
1999 )),
2000 KclValue::Plane {
2001 value: Box::new(
2002 Plane::from_plane_data_skipping_engine(crate::std::sketch::PlaneData::XY, exec_state).unwrap(),
2003 ),
2004 },
2005 KclValue::ImportedGeometry(crate::execution::ImportedGeometry::new(
2007 uuid::Uuid::nil(),
2008 Vec::new(),
2009 Vec::new(),
2010 )),
2011 ]
2013 }
2014
2015 #[track_caller]
2016 fn assert_coerce_results(
2017 value: &KclValue,
2018 super_type: &RuntimeType,
2019 expected_value: &KclValue,
2020 exec_state: &mut ExecState,
2021 ) {
2022 let is_subtype = value == expected_value;
2023 let actual = value.coerce(super_type, CoercionMode::implicit(), exec_state).unwrap();
2024 assert_eq!(&actual, expected_value);
2025 assert_eq!(
2026 is_subtype,
2027 value.principal_type().is_some() && value.principal_type().unwrap().subtype(super_type),
2028 "{:?} <: {super_type:?} should be {is_subtype}",
2029 value.principal_type().unwrap()
2030 );
2031 assert!(
2032 expected_value.principal_type().unwrap().subtype(super_type),
2033 "{} <: {super_type}",
2034 expected_value.principal_type().unwrap()
2035 )
2036 }
2037
2038 #[tokio::test(flavor = "multi_thread")]
2039 async fn coerce_idempotent() {
2040 let (ctx, mut exec_state) = new_exec_state().await;
2041 let values = values(&mut exec_state);
2042 for v in &values {
2043 let ty = v.principal_type().unwrap();
2045 assert_coerce_results(v, &ty, v, &mut exec_state);
2046
2047 let uty1 = RuntimeType::Union(vec![ty.clone()]);
2049 let uty2 = RuntimeType::Union(vec![ty.clone(), RuntimeType::Primitive(PrimitiveType::Boolean)]);
2050 assert_coerce_results(v, &uty1, v, &mut exec_state);
2051 assert_coerce_results(v, &uty2, v, &mut exec_state);
2052
2053 let aty = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::None);
2055 let aty1 = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Known(1));
2056 let aty0 = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Minimum(1));
2057
2058 match v {
2059 KclValue::HomArray { .. } => {
2060 assert_coerce_results(
2062 v,
2063 &aty,
2064 &KclValue::HomArray {
2065 value: vec![],
2066 ty: ty.clone(),
2067 },
2068 &mut exec_state,
2069 );
2070 v.coerce(&aty1, CoercionMode::implicit(), &mut exec_state).unwrap_err();
2073 v.coerce(&aty0, CoercionMode::implicit(), &mut exec_state).unwrap_err();
2076 }
2077 KclValue::Tuple { .. } => {}
2078 _ => {
2079 assert_coerce_results(v, &aty, v, &mut exec_state);
2080 assert_coerce_results(v, &aty1, v, &mut exec_state);
2081 assert_coerce_results(v, &aty0, v, &mut exec_state);
2082
2083 let tty = RuntimeType::Tuple(vec![ty.clone()]);
2085 assert_coerce_results(v, &tty, v, &mut exec_state);
2086 }
2087 }
2088 }
2089
2090 for v in &values[1..] {
2091 v.coerce(
2093 &RuntimeType::Primitive(PrimitiveType::Boolean),
2094 CoercionMode::implicit(),
2095 &mut exec_state,
2096 )
2097 .unwrap_err();
2098 }
2099 ctx.close().await;
2100 }
2101
2102 #[tokio::test(flavor = "multi_thread")]
2103 async fn coerce_none() {
2104 let (ctx, mut exec_state) = new_exec_state().await;
2105 let none = KclValue::KclNone {
2106 value: crate::parsing::ast::types::KclNone::new(),
2107 meta: Vec::new(),
2108 };
2109
2110 let aty = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::None);
2111 let aty0 = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::Known(0));
2112 let aty1 = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::Known(1));
2113 let aty1p = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::Minimum(1));
2114 assert_coerce_results(
2115 &none,
2116 &aty,
2117 &KclValue::HomArray {
2118 value: Vec::new(),
2119 ty: RuntimeType::solid(),
2120 },
2121 &mut exec_state,
2122 );
2123 assert_coerce_results(
2124 &none,
2125 &aty0,
2126 &KclValue::HomArray {
2127 value: Vec::new(),
2128 ty: RuntimeType::solid(),
2129 },
2130 &mut exec_state,
2131 );
2132 none.coerce(&aty1, CoercionMode::implicit(), &mut exec_state)
2133 .unwrap_err();
2134 none.coerce(&aty1p, CoercionMode::implicit(), &mut exec_state)
2135 .unwrap_err();
2136
2137 let tty = RuntimeType::Tuple(vec![]);
2138 let tty1 = RuntimeType::Tuple(vec![RuntimeType::solid()]);
2139 assert_coerce_results(
2140 &none,
2141 &tty,
2142 &KclValue::Tuple {
2143 value: Vec::new(),
2144 meta: Vec::new(),
2145 },
2146 &mut exec_state,
2147 );
2148 none.coerce(&tty1, CoercionMode::implicit(), &mut exec_state)
2149 .unwrap_err();
2150
2151 let oty = RuntimeType::Object(vec![], false);
2152 assert_coerce_results(
2153 &none,
2154 &oty,
2155 &KclValue::Object {
2156 value: HashMap::new(),
2157 meta: Vec::new(),
2158 constrainable: false,
2159 object_kind: Default::default(),
2160 },
2161 &mut exec_state,
2162 );
2163 ctx.close().await;
2164 }
2165
2166 #[tokio::test(flavor = "multi_thread")]
2167 async fn coerce_record() {
2168 let (ctx, mut exec_state) = new_exec_state().await;
2169
2170 let obj0 = KclValue::Object {
2171 value: HashMap::new(),
2172 meta: Vec::new(),
2173 constrainable: false,
2174 object_kind: Default::default(),
2175 };
2176 let obj1 = KclValue::Object {
2177 value: [(
2178 "foo".to_owned(),
2179 KclValue::Bool {
2180 value: true,
2181 meta: Vec::new(),
2182 },
2183 )]
2184 .into(),
2185 meta: Vec::new(),
2186 constrainable: false,
2187 object_kind: Default::default(),
2188 };
2189 let obj2 = KclValue::Object {
2190 value: [
2191 (
2192 "foo".to_owned(),
2193 KclValue::Bool {
2194 value: true,
2195 meta: Vec::new(),
2196 },
2197 ),
2198 (
2199 "bar".to_owned(),
2200 KclValue::Number {
2201 value: 0.0,
2202 ty: NumericType::count(),
2203 meta: Vec::new(),
2204 },
2205 ),
2206 (
2207 "baz".to_owned(),
2208 KclValue::Number {
2209 value: 42.0,
2210 ty: NumericType::count(),
2211 meta: Vec::new(),
2212 },
2213 ),
2214 ]
2215 .into(),
2216 meta: Vec::new(),
2217 constrainable: false,
2218 object_kind: Default::default(),
2219 };
2220
2221 let ty0 = RuntimeType::Object(vec![], false);
2222 assert_coerce_results(&obj0, &ty0, &obj0, &mut exec_state);
2223 assert_coerce_results(&obj1, &ty0, &obj1, &mut exec_state);
2224 assert_coerce_results(&obj2, &ty0, &obj2, &mut exec_state);
2225
2226 let ty1 = RuntimeType::Object(
2227 vec![("foo".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean))],
2228 false,
2229 );
2230 obj0.coerce(&ty1, CoercionMode::implicit(), &mut exec_state)
2231 .unwrap_err();
2232 assert_coerce_results(&obj1, &ty1, &obj1, &mut exec_state);
2233 assert_coerce_results(&obj2, &ty1, &obj2, &mut exec_state);
2234
2235 let ty2 = RuntimeType::Object(
2237 vec![
2238 (
2239 "bar".to_owned(),
2240 RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2241 ),
2242 ("foo".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean)),
2243 ],
2244 false,
2245 );
2246 obj0.coerce(&ty2, CoercionMode::implicit(), &mut exec_state)
2247 .unwrap_err();
2248 obj1.coerce(&ty2, CoercionMode::implicit(), &mut exec_state)
2249 .unwrap_err();
2250 assert_coerce_results(&obj2, &ty2, &obj2, &mut exec_state);
2251
2252 let tyq = RuntimeType::Object(
2254 vec![("qux".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean))],
2255 false,
2256 );
2257 obj0.coerce(&tyq, CoercionMode::implicit(), &mut exec_state)
2258 .unwrap_err();
2259 obj1.coerce(&tyq, CoercionMode::implicit(), &mut exec_state)
2260 .unwrap_err();
2261 obj2.coerce(&tyq, CoercionMode::implicit(), &mut exec_state)
2262 .unwrap_err();
2263
2264 let ty1 = RuntimeType::Object(
2266 vec![("bar".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean))],
2267 false,
2268 );
2269 obj2.coerce(&ty1, CoercionMode::implicit(), &mut exec_state)
2270 .unwrap_err();
2271 ctx.close().await;
2272 }
2273
2274 #[tokio::test(flavor = "multi_thread")]
2275 async fn coerce_array() {
2276 let (ctx, mut exec_state) = new_exec_state().await;
2277
2278 let hom_arr = KclValue::HomArray {
2279 value: vec![
2280 KclValue::Number {
2281 value: 0.0,
2282 ty: NumericType::count(),
2283 meta: Vec::new(),
2284 },
2285 KclValue::Number {
2286 value: 1.0,
2287 ty: NumericType::count(),
2288 meta: Vec::new(),
2289 },
2290 KclValue::Number {
2291 value: 2.0,
2292 ty: NumericType::count(),
2293 meta: Vec::new(),
2294 },
2295 KclValue::Number {
2296 value: 3.0,
2297 ty: NumericType::count(),
2298 meta: Vec::new(),
2299 },
2300 ],
2301 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2302 };
2303 let mixed1 = KclValue::Tuple {
2304 value: vec![
2305 KclValue::Number {
2306 value: 0.0,
2307 ty: NumericType::count(),
2308 meta: Vec::new(),
2309 },
2310 KclValue::Number {
2311 value: 1.0,
2312 ty: NumericType::count(),
2313 meta: Vec::new(),
2314 },
2315 ],
2316 meta: Vec::new(),
2317 };
2318 let mixed2 = KclValue::Tuple {
2319 value: vec![
2320 KclValue::Number {
2321 value: 0.0,
2322 ty: NumericType::count(),
2323 meta: Vec::new(),
2324 },
2325 KclValue::Bool {
2326 value: true,
2327 meta: Vec::new(),
2328 },
2329 ],
2330 meta: Vec::new(),
2331 };
2332
2333 let tyh = RuntimeType::Array(
2335 Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2336 ArrayLen::Known(4),
2337 );
2338 let tym1 = RuntimeType::Tuple(vec![
2339 RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2340 RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2341 ]);
2342 let tym2 = RuntimeType::Tuple(vec![
2343 RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2344 RuntimeType::Primitive(PrimitiveType::Boolean),
2345 ]);
2346 assert_coerce_results(&hom_arr, &tyh, &hom_arr, &mut exec_state);
2347 assert_coerce_results(&mixed1, &tym1, &mixed1, &mut exec_state);
2348 assert_coerce_results(&mixed2, &tym2, &mixed2, &mut exec_state);
2349 mixed1
2350 .coerce(&tym2, CoercionMode::implicit(), &mut exec_state)
2351 .unwrap_err();
2352 mixed2
2353 .coerce(&tym1, CoercionMode::implicit(), &mut exec_state)
2354 .unwrap_err();
2355
2356 let tyhn = RuntimeType::Array(
2358 Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2359 ArrayLen::None,
2360 );
2361 let tyh1 = RuntimeType::Array(
2362 Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2363 ArrayLen::Minimum(1),
2364 );
2365 let tyh3 = RuntimeType::Array(
2366 Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2367 ArrayLen::Known(3),
2368 );
2369 let tyhm3 = RuntimeType::Array(
2370 Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2371 ArrayLen::Minimum(3),
2372 );
2373 let tyhm5 = RuntimeType::Array(
2374 Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2375 ArrayLen::Minimum(5),
2376 );
2377 assert_coerce_results(&hom_arr, &tyhn, &hom_arr, &mut exec_state);
2378 assert_coerce_results(&hom_arr, &tyh1, &hom_arr, &mut exec_state);
2379 hom_arr
2380 .coerce(&tyh3, CoercionMode::implicit(), &mut exec_state)
2381 .unwrap_err();
2382 assert_coerce_results(&hom_arr, &tyhm3, &hom_arr, &mut exec_state);
2383 hom_arr
2384 .coerce(&tyhm5, CoercionMode::implicit(), &mut exec_state)
2385 .unwrap_err();
2386
2387 let hom_arr0 = KclValue::HomArray {
2388 value: vec![],
2389 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2390 };
2391 assert_coerce_results(&hom_arr0, &tyhn, &hom_arr0, &mut exec_state);
2392 hom_arr0
2393 .coerce(&tyh1, CoercionMode::implicit(), &mut exec_state)
2394 .unwrap_err();
2395 hom_arr0
2396 .coerce(&tyh3, CoercionMode::implicit(), &mut exec_state)
2397 .unwrap_err();
2398
2399 let tym1 = RuntimeType::Tuple(vec![
2402 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2403 RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2404 ]);
2405 let tym2 = RuntimeType::Tuple(vec![
2406 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2407 RuntimeType::Primitive(PrimitiveType::Boolean),
2408 ]);
2409 assert_coerce_results(&mixed1, &tym1, &mixed1, &mut exec_state);
2412 assert_coerce_results(&mixed2, &tym2, &mixed2, &mut exec_state);
2413
2414 let hom_arr_2 = KclValue::HomArray {
2416 value: vec![
2417 KclValue::Number {
2418 value: 0.0,
2419 ty: NumericType::count(),
2420 meta: Vec::new(),
2421 },
2422 KclValue::Number {
2423 value: 1.0,
2424 ty: NumericType::count(),
2425 meta: Vec::new(),
2426 },
2427 ],
2428 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2429 };
2430 let mixed0 = KclValue::Tuple {
2431 value: vec![],
2432 meta: Vec::new(),
2433 };
2434 assert_coerce_results(&mixed1, &tyhn, &hom_arr_2, &mut exec_state);
2435 assert_coerce_results(&mixed1, &tyh1, &hom_arr_2, &mut exec_state);
2436 assert_coerce_results(&mixed0, &tyhn, &hom_arr0, &mut exec_state);
2437 mixed0
2438 .coerce(&tyh, CoercionMode::implicit(), &mut exec_state)
2439 .unwrap_err();
2440 mixed0
2441 .coerce(&tyh1, CoercionMode::implicit(), &mut exec_state)
2442 .unwrap_err();
2443
2444 assert_coerce_results(&hom_arr_2, &tym1, &mixed1, &mut exec_state);
2446 hom_arr
2447 .coerce(&tym1, CoercionMode::implicit(), &mut exec_state)
2448 .unwrap_err();
2449 hom_arr_2
2450 .coerce(&tym2, CoercionMode::implicit(), &mut exec_state)
2451 .unwrap_err();
2452
2453 mixed0
2454 .coerce(&tym1, CoercionMode::implicit(), &mut exec_state)
2455 .unwrap_err();
2456 mixed0
2457 .coerce(&tym2, CoercionMode::implicit(), &mut exec_state)
2458 .unwrap_err();
2459 ctx.close().await;
2460 }
2461
2462 #[tokio::test(flavor = "multi_thread")]
2463 async fn coerce_union() {
2464 let (ctx, mut exec_state) = new_exec_state().await;
2465
2466 assert!(RuntimeType::Union(vec![]).subtype(&RuntimeType::Union(vec![
2468 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2469 RuntimeType::Primitive(PrimitiveType::Boolean)
2470 ])));
2471 assert!(
2472 RuntimeType::Union(vec![RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any))]).subtype(
2473 &RuntimeType::Union(vec![
2474 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2475 RuntimeType::Primitive(PrimitiveType::Boolean)
2476 ])
2477 )
2478 );
2479 assert!(
2480 RuntimeType::Union(vec![
2481 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2482 RuntimeType::Primitive(PrimitiveType::Boolean)
2483 ])
2484 .subtype(&RuntimeType::Union(vec![
2485 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2486 RuntimeType::Primitive(PrimitiveType::Boolean)
2487 ]))
2488 );
2489
2490 let count = KclValue::Number {
2492 value: 1.0,
2493 ty: NumericType::count(),
2494 meta: Vec::new(),
2495 };
2496
2497 let tya = RuntimeType::Union(vec![RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any))]);
2498 let tya2 = RuntimeType::Union(vec![
2499 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2500 RuntimeType::Primitive(PrimitiveType::Boolean),
2501 ]);
2502 assert_coerce_results(&count, &tya, &count, &mut exec_state);
2503 assert_coerce_results(&count, &tya2, &count, &mut exec_state);
2504
2505 let tyb = RuntimeType::Union(vec![RuntimeType::Primitive(PrimitiveType::Boolean)]);
2507 let tyb2 = RuntimeType::Union(vec![
2508 RuntimeType::Primitive(PrimitiveType::Boolean),
2509 RuntimeType::Primitive(PrimitiveType::String),
2510 ]);
2511 count
2512 .coerce(&tyb, CoercionMode::implicit(), &mut exec_state)
2513 .unwrap_err();
2514 count
2515 .coerce(&tyb2, CoercionMode::implicit(), &mut exec_state)
2516 .unwrap_err();
2517 ctx.close().await;
2518 }
2519
2520 #[test]
2521 fn union_subtyping_uses_member_subtyping() {
2522 let tagged_edge = RuntimeType::Primitive(PrimitiveType::TaggedEdge);
2523 let edge = RuntimeType::Primitive(PrimitiveType::Edge);
2524 let string = RuntimeType::string();
2525 let boolean = RuntimeType::bool();
2526
2527 let tagged_edge_or_string = RuntimeType::Union(vec![tagged_edge.clone(), string.clone()]);
2528 let edge_or_string = RuntimeType::Union(vec![edge.clone(), string.clone()]);
2529
2530 assert!(tagged_edge_or_string.subtype(&edge_or_string));
2532 assert!(!edge_or_string.subtype(&tagged_edge_or_string));
2534
2535 assert!(RuntimeType::Union(vec![tagged_edge.clone(), edge.clone()]).subtype(&edge));
2537 assert!(!RuntimeType::Union(vec![tagged_edge, boolean]).subtype(&edge));
2539
2540 assert!(RuntimeType::Union(vec![]).subtype(&string));
2542 }
2543
2544 #[test]
2545 fn nested_union_subtyping_is_associative_and_recursive() {
2546 let tagged_edge = RuntimeType::Primitive(PrimitiveType::TaggedEdge);
2547 let edge = RuntimeType::Primitive(PrimitiveType::Edge);
2548 let string = RuntimeType::string();
2549 let boolean = RuntimeType::bool();
2550
2551 let left_associative = RuntimeType::Union(vec![
2552 RuntimeType::Union(vec![string.clone(), boolean.clone()]),
2553 edge.clone(),
2554 ]);
2555 let right_associative =
2556 RuntimeType::Union(vec![string, RuntimeType::Union(vec![boolean.clone(), edge.clone()])]);
2557
2558 assert!(left_associative.subtype(&right_associative));
2560 assert!(right_associative.subtype(&left_associative));
2562
2563 let nested_edges = RuntimeType::Union(vec![
2564 RuntimeType::Union(vec![tagged_edge.clone(), edge.clone()]),
2565 tagged_edge.clone(),
2566 ]);
2567 assert!(nested_edges.subtype(&edge));
2569
2570 let nested_with_bool = RuntimeType::Union(vec![RuntimeType::Union(vec![tagged_edge, boolean]), edge.clone()]);
2571 assert!(!nested_with_bool.subtype(&edge));
2573 }
2574
2575 fn enum_ty(module_id: u32, name: &str) -> RuntimeType {
2576 RuntimeType::Enum(EnumTypeId::new(ModuleId::from_usize(module_id as usize), name))
2577 }
2578
2579 fn enum_def(module_id: u32, name: &str, variants: &[&str]) -> Arc<EnumTypeDef> {
2582 Arc::new(
2583 EnumTypeDef::new(
2584 EnumTypeId::new(ModuleId::from_usize(module_id as usize), name),
2585 variants.iter().map(|v| (*v).to_owned()).collect(),
2586 )
2587 .unwrap(),
2588 )
2589 }
2590
2591 #[test]
2592 fn enum_subtyping_is_nominal() {
2593 let color = enum_ty(0, "Color");
2594 let shape = enum_ty(0, "Shape");
2595
2596 assert!(color.subtype(&color));
2599 assert!(!color.subtype(&shape));
2601 assert!(!shape.subtype(&color));
2602 }
2603
2604 #[test]
2605 fn enum_identity_is_module_plus_declared_name() {
2606 assert!(!enum_ty(0, "Color").subtype(&enum_ty(1, "Color")));
2608 assert!(enum_ty(1, "Color").subtype(&enum_ty(1, "Color")));
2611 }
2612
2613 #[test]
2614 fn enum_participates_in_the_general_type_rules() {
2615 let color = enum_ty(0, "Color");
2616
2617 assert!(color.subtype(&RuntimeType::any()));
2619 assert!(RuntimeType::never().subtype(&color));
2620 assert!(!color.subtype(&RuntimeType::never()));
2621
2622 assert!(color.subtype(&RuntimeType::Union(vec![color.clone(), RuntimeType::string()])));
2625 assert!(!color.subtype(&RuntimeType::Union(vec![RuntimeType::string(), enum_ty(0, "Shape")])));
2626 assert!(color.subtype(&RuntimeType::Array(Box::new(color.clone()), ArrayLen::Known(1))));
2627 assert!(RuntimeType::Array(Box::new(color.clone()), ArrayLen::Known(1)).subtype(&color));
2628
2629 assert!(!color.subtype(&RuntimeType::string()));
2631 assert!(!RuntimeType::string().subtype(&color));
2632 }
2633
2634 #[test]
2635 fn enum_values_report_their_own_type() {
2636 let red = KclValue::Enum {
2637 value: Box::new(EnumValue::new(enum_def(0, "Color", &["Red"]), "Red", Vec::new())),
2638 };
2639
2640 assert_eq!(red.principal_type(), Some(enum_ty(0, "Color")));
2641 assert!(red.has_type(&enum_ty(0, "Color")));
2642 assert!(!red.has_type(&enum_ty(0, "Shape")));
2644 assert!(!red.has_type(&RuntimeType::string()));
2645 }
2646
2647 #[test]
2648 fn enum_types_display_by_declared_name() {
2649 let color = enum_ty(0, "Color");
2650
2651 assert_eq!(color.to_string(), "Color");
2652 assert_eq!(color.human_friendly_type(), "Color");
2653 assert_eq!(
2654 RuntimeType::Array(Box::new(color), ArrayLen::Minimum(1)).human_friendly_type(),
2655 "one or more `Color` values"
2656 );
2657 }
2658
2659 #[tokio::test(flavor = "multi_thread")]
2662 async fn from_alias_resolves_a_declared_enum_to_its_nominal_type() {
2663 let mut exec_state = parse_execute("x = 1").await.unwrap().exec_state;
2666 let id = EnumTypeId::new(ModuleId::default(), "Color");
2667 let source_range = SourceRange::default();
2668
2669 exec_state.mut_stack().push_new_root_env(true).unwrap();
2671 exec_state
2672 .mut_stack()
2673 .add(
2674 format!("{}Color", memory::TYPE_PREFIX),
2675 KclValue::Type {
2676 value: TypeDef::Enum(Arc::new(EnumTypeDef::new(id.clone(), vec!["Red".to_owned()]).unwrap())),
2677 experimental: false,
2678 meta: vec![],
2679 },
2680 source_range,
2681 )
2682 .unwrap();
2683
2684 assert_eq!(
2685 RuntimeType::from_alias("Color", &mut exec_state, source_range, false).unwrap(),
2686 RuntimeType::Enum(id)
2687 );
2688 RuntimeType::from_alias("Shape", &mut exec_state, source_range, false).unwrap_err();
2690 }
2691
2692 #[tokio::test(flavor = "multi_thread")]
2693 async fn enum_coercion_requires_the_same_declaration() {
2694 let (ctx, mut exec_state) = new_exec_state().await;
2695 let red = KclValue::Enum {
2696 value: Box::new(EnumValue::new(enum_def(0, "Color", &["Red"]), "Red", Vec::new())),
2697 };
2698
2699 assert_eq!(
2701 red.coerce(&enum_ty(0, "Color"), CoercionMode::implicit(), &mut exec_state)
2702 .unwrap(),
2703 red
2704 );
2705 red.coerce(&enum_ty(0, "Shape"), CoercionMode::implicit(), &mut exec_state)
2708 .unwrap_err();
2709 red.coerce(&enum_ty(1, "Color"), CoercionMode::implicit(), &mut exec_state)
2710 .unwrap_err();
2711 red.coerce(&RuntimeType::string(), CoercionMode::implicit(), &mut exec_state)
2712 .unwrap_err();
2713 let string = KclValue::String {
2715 value: "Red".to_owned(),
2716 meta: Vec::new(),
2717 };
2718 string
2719 .coerce(&enum_ty(0, "Color"), CoercionMode::implicit(), &mut exec_state)
2720 .unwrap_err();
2721
2722 ctx.close().await;
2723 }
2724
2725 fn enum_value(module_id: u32, name: &str, variants: &[&str], variant: &str) -> KclValue {
2726 KclValue::Enum {
2727 value: Box::new(EnumValue::new(enum_def(module_id, name, variants), variant, Vec::new())),
2728 }
2729 }
2730
2731 fn string_value(value: &str) -> KclValue {
2732 KclValue::String {
2733 value: value.to_owned(),
2734 meta: Vec::new(),
2735 }
2736 }
2737
2738 #[tokio::test(flavor = "multi_thread")]
2745 async fn enum_projects_by_target_shape() {
2746 let (ctx, mut exec_state) = new_exec_state().await;
2747 let variants = &["Red", "Green"];
2748 let red = enum_value(0, "Color", variants, "Red");
2749 let green = enum_value(0, "Color", variants, "Green");
2750 let color = enum_ty(0, "Color");
2751 let string = RuntimeType::string();
2752 let strings = RuntimeType::Array(Box::new(string.clone()), ArrayLen::None);
2753 let array = |value: Vec<KclValue>, ty: RuntimeType| KclValue::HomArray { value, ty };
2754 let tuple = |value: Vec<KclValue>| KclValue::Tuple {
2755 value,
2756 meta: Vec::new(),
2757 };
2758
2759 #[allow(clippy::type_complexity)]
2760 let rows: Vec<(&str, KclValue, RuntimeType, Option<KclValue>, Option<KclValue>)> = vec![
2761 (
2762 "a bare enum",
2763 red.clone(),
2764 string.clone(),
2765 Some(string_value("Red")),
2766 None,
2767 ),
2768 (
2769 "an array, element by element",
2770 array(vec![red.clone(), green.clone()], color.clone()),
2771 strings.clone(),
2772 Some(array(vec![string_value("Red"), string_value("Green")], string.clone())),
2773 None,
2774 ),
2775 (
2776 "an array of arrays, so more than one level down",
2777 array(vec![array(vec![green.clone()], RuntimeType::any())], RuntimeType::any()),
2778 RuntimeType::Array(Box::new(strings.clone()), ArrayLen::None),
2779 Some(array(
2780 vec![array(vec![string_value("Green")], string.clone())],
2781 strings.clone(),
2782 )),
2783 None,
2784 ),
2785 (
2786 "a tuple, positionally, beside a value that needs nothing done",
2788 tuple(vec![red.clone(), string_value("plain")]),
2789 RuntimeType::Tuple(vec![string.clone(), string.clone()]),
2790 Some(tuple(vec![string_value("Red"), string_value("plain")])),
2791 None,
2792 ),
2793 (
2794 "a one-element array against a bare string",
2797 array(vec![red.clone()], RuntimeType::any()),
2798 string.clone(),
2799 Some(string_value("Red")),
2800 None,
2801 ),
2802 (
2803 "an object field, which projects nothing",
2809 KclValue::Object {
2810 value: HashMap::from([("c".to_owned(), red.clone())]),
2811 constrainable: false,
2812 object_kind: Default::default(),
2813 meta: Vec::new(),
2814 },
2815 RuntimeType::Object(vec![("c".to_owned(), string.clone())], false),
2816 None,
2817 None,
2818 ),
2819 (
2820 "its own type, which is a check rather than a conversion",
2821 red.clone(),
2822 color.clone(),
2823 Some(red.clone()),
2824 Some(red.clone()),
2825 ),
2826 (
2827 "another declaration, which projection is not a way around",
2828 red.clone(),
2829 enum_ty(0, "Shade"),
2830 None,
2831 None,
2832 ),
2833 ];
2834
2835 for (case, value, target, explicit, implicit) in rows {
2836 assert_eq!(
2837 value.coerce(&target, CoercionMode::explicit(), &mut exec_state).ok(),
2838 explicit,
2839 "explicit mode, case: {case}"
2840 );
2841 assert_eq!(
2842 value.coerce(&target, CoercionMode::implicit(), &mut exec_state).ok(),
2843 implicit,
2844 "implicit mode, case: {case}"
2845 );
2846 }
2847
2848 ctx.close().await;
2849 }
2850
2851 #[tokio::test(flavor = "multi_thread")]
2856 async fn enum_projection_ignores_the_order_a_union_was_written_in() {
2857 let (ctx, mut exec_state) = new_exec_state().await;
2858 let red = enum_value(0, "Color", &["Red"], "Red");
2859 let string = RuntimeType::string();
2860 let color = enum_ty(0, "Color");
2861 let shade = enum_ty(0, "Shade");
2862
2863 let rows: Vec<(&str, Vec<RuntimeType>, Option<KclValue>)> = vec![
2864 ("the enum first", vec![color.clone(), string.clone()], Some(red.clone())),
2865 ("the enum last", vec![string.clone(), color.clone()], Some(red.clone())),
2866 (
2867 "no member accepts an enum, so projection is what satisfies it",
2868 vec![RuntimeType::bool(), string.clone()],
2869 Some(string_value("Red")),
2870 ),
2871 (
2872 "a different enum is not a match, so this projects too",
2873 vec![shade.clone(), string.clone()],
2874 Some(string_value("Red")),
2875 ),
2876 (
2877 "a different enum with no string member is unsatisfiable",
2878 vec![shade, RuntimeType::bool()],
2879 None,
2880 ),
2881 ];
2882
2883 for (case, tys, expected) in rows {
2884 let union = RuntimeType::Union(tys);
2885 assert_eq!(
2886 red.coerce(&union, CoercionMode::explicit(), &mut exec_state).ok(),
2887 expected,
2888 "case: {case} ({union})"
2889 );
2890 }
2891
2892 ctx.close().await;
2893 }
2894
2895 #[tokio::test(flavor = "multi_thread")]
2899 async fn enum_projection_to_a_number_explains_itself() {
2900 let (ctx, mut exec_state) = new_exec_state().await;
2901 let red = enum_value(0, "Color", &["Red"], "Red");
2902 let message = "Cannot project enum `Color` to a number. An enum projects to `string`; projecting to a number is not supported yet.";
2903
2904 for (case, mode, expected) in [
2905 ("explicit", CoercionMode::explicit(), Some(message)),
2906 ("implicit", CoercionMode::implicit(), None),
2907 ] {
2908 let err = red.coerce(&RuntimeType::count(), mode, &mut exec_state).unwrap_err();
2909 assert_eq!(err.message.as_deref(), expected, "case: {case}");
2910 }
2911
2912 ctx.close().await;
2913 }
2914
2915 #[tokio::test(flavor = "multi_thread")]
2916 async fn never_is_bottom_and_uninhabited() {
2917 let (ctx, mut exec_state) = new_exec_state().await;
2918 let never = RuntimeType::never();
2919 let string = RuntimeType::string();
2920
2921 for ty in [
2922 RuntimeType::any(),
2923 string.clone(),
2924 RuntimeType::Array(Box::new(string.clone()), ArrayLen::None),
2925 RuntimeType::Tuple(vec![string.clone()]),
2926 RuntimeType::Object(vec![("value".to_owned(), string.clone())], false),
2927 RuntimeType::Union(vec![string.clone(), RuntimeType::bool()]),
2928 ] {
2929 assert!(never.subtype(&ty), "`never` should be a subtype of {ty}");
2930 }
2931
2932 assert!(!string.subtype(&never));
2933 assert!(RuntimeType::Union(vec![never.clone(), string.clone()]).subtype(&string));
2934
2935 for value in values(&mut exec_state) {
2936 value
2937 .coerce(&never, CoercionMode::implicit(), &mut exec_state)
2938 .unwrap_err();
2939 }
2940 ctx.close().await;
2941 }
2942
2943 #[tokio::test(flavor = "multi_thread")]
2944 async fn coerce_axes() {
2945 let (ctx, mut exec_state) = new_exec_state().await;
2946
2947 assert!(RuntimeType::Primitive(PrimitiveType::Axis2d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis2d)));
2949 assert!(RuntimeType::Primitive(PrimitiveType::Axis3d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis3d)));
2950 assert!(!RuntimeType::Primitive(PrimitiveType::Axis3d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis2d)));
2951 assert!(!RuntimeType::Primitive(PrimitiveType::Axis2d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis3d)));
2952
2953 let a2d = KclValue::Object {
2955 value: [
2956 (
2957 "origin".to_owned(),
2958 KclValue::HomArray {
2959 value: vec![
2960 KclValue::Number {
2961 value: 0.0,
2962 ty: NumericType::mm(),
2963 meta: Vec::new(),
2964 },
2965 KclValue::Number {
2966 value: 0.0,
2967 ty: NumericType::mm(),
2968 meta: Vec::new(),
2969 },
2970 ],
2971 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
2972 },
2973 ),
2974 (
2975 "direction".to_owned(),
2976 KclValue::HomArray {
2977 value: vec![
2978 KclValue::Number {
2979 value: 1.0,
2980 ty: NumericType::mm(),
2981 meta: Vec::new(),
2982 },
2983 KclValue::Number {
2984 value: 0.0,
2985 ty: NumericType::mm(),
2986 meta: Vec::new(),
2987 },
2988 ],
2989 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
2990 },
2991 ),
2992 ]
2993 .into(),
2994 meta: Vec::new(),
2995 constrainable: false,
2996 object_kind: Default::default(),
2997 };
2998 let a3d = KclValue::Object {
2999 value: [
3000 (
3001 "origin".to_owned(),
3002 KclValue::HomArray {
3003 value: vec![
3004 KclValue::Number {
3005 value: 0.0,
3006 ty: NumericType::mm(),
3007 meta: Vec::new(),
3008 },
3009 KclValue::Number {
3010 value: 0.0,
3011 ty: NumericType::mm(),
3012 meta: Vec::new(),
3013 },
3014 KclValue::Number {
3015 value: 0.0,
3016 ty: NumericType::mm(),
3017 meta: Vec::new(),
3018 },
3019 ],
3020 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
3021 },
3022 ),
3023 (
3024 "direction".to_owned(),
3025 KclValue::HomArray {
3026 value: vec![
3027 KclValue::Number {
3028 value: 1.0,
3029 ty: NumericType::mm(),
3030 meta: Vec::new(),
3031 },
3032 KclValue::Number {
3033 value: 0.0,
3034 ty: NumericType::mm(),
3035 meta: Vec::new(),
3036 },
3037 KclValue::Number {
3038 value: 1.0,
3039 ty: NumericType::mm(),
3040 meta: Vec::new(),
3041 },
3042 ],
3043 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
3044 },
3045 ),
3046 ]
3047 .into(),
3048 meta: Vec::new(),
3049 constrainable: false,
3050 object_kind: Default::default(),
3051 };
3052
3053 let ty2d = RuntimeType::Primitive(PrimitiveType::Axis2d);
3054 let ty3d = RuntimeType::Primitive(PrimitiveType::Axis3d);
3055
3056 assert_coerce_results(&a2d, &ty2d, &a2d, &mut exec_state);
3057 assert_coerce_results(&a3d, &ty3d, &a3d, &mut exec_state);
3058 assert_coerce_results(&a3d, &ty2d, &a2d, &mut exec_state);
3059 a2d.coerce(&ty3d, CoercionMode::implicit(), &mut exec_state)
3060 .unwrap_err();
3061 ctx.close().await;
3062 }
3063
3064 #[tokio::test(flavor = "multi_thread")]
3065 async fn coerce_numeric() {
3066 let (ctx, mut exec_state) = new_exec_state().await;
3067
3068 let count = KclValue::Number {
3069 value: 1.0,
3070 ty: NumericType::count(),
3071 meta: Vec::new(),
3072 };
3073 let mm = KclValue::Number {
3074 value: 1.0,
3075 ty: NumericType::mm(),
3076 meta: Vec::new(),
3077 };
3078 let inches = KclValue::Number {
3079 value: 1.0,
3080 ty: NumericType::Known(UnitType::Length(UnitLength::Inches)),
3081 meta: Vec::new(),
3082 };
3083 let rads = KclValue::Number {
3084 value: 1.0,
3085 ty: NumericType::Known(UnitType::Angle(UnitAngle::Radians)),
3086 meta: Vec::new(),
3087 };
3088 let default = KclValue::Number {
3089 value: 1.0,
3090 ty: NumericType::default(),
3091 meta: Vec::new(),
3092 };
3093 let any = KclValue::Number {
3094 value: 1.0,
3095 ty: NumericType::Any,
3096 meta: Vec::new(),
3097 };
3098 let unknown = KclValue::Number {
3099 value: 1.0,
3100 ty: NumericType::Unknown,
3101 meta: Vec::new(),
3102 };
3103
3104 assert_coerce_results(&count, &NumericType::count().into(), &count, &mut exec_state);
3106 assert_coerce_results(&mm, &NumericType::mm().into(), &mm, &mut exec_state);
3107 assert_coerce_results(&any, &NumericType::Any.into(), &any, &mut exec_state);
3108 assert_coerce_results(&unknown, &NumericType::Unknown.into(), &unknown, &mut exec_state);
3109 assert_coerce_results(&default, &NumericType::default().into(), &default, &mut exec_state);
3110
3111 assert_coerce_results(&count, &NumericType::Any.into(), &count, &mut exec_state);
3112 assert_coerce_results(&mm, &NumericType::Any.into(), &mm, &mut exec_state);
3113 assert_coerce_results(&unknown, &NumericType::Any.into(), &unknown, &mut exec_state);
3114 assert_coerce_results(&default, &NumericType::Any.into(), &default, &mut exec_state);
3115
3116 assert_eq!(
3117 default
3118 .coerce(
3119 &NumericType::Default {
3120 len: UnitLength::Yards,
3121 angle: UnitAngle::Degrees,
3122 }
3123 .into(),
3124 CoercionMode::implicit(),
3125 &mut exec_state
3126 )
3127 .unwrap(),
3128 default
3129 );
3130
3131 count
3133 .coerce(&NumericType::mm().into(), CoercionMode::implicit(), &mut exec_state)
3134 .unwrap_err();
3135 mm.coerce(&NumericType::count().into(), CoercionMode::implicit(), &mut exec_state)
3136 .unwrap_err();
3137 unknown
3138 .coerce(&NumericType::mm().into(), CoercionMode::implicit(), &mut exec_state)
3139 .unwrap_err();
3140 unknown
3141 .coerce(
3142 &NumericType::default().into(),
3143 CoercionMode::implicit(),
3144 &mut exec_state,
3145 )
3146 .unwrap_err();
3147
3148 count
3149 .coerce(&NumericType::Unknown.into(), CoercionMode::implicit(), &mut exec_state)
3150 .unwrap_err();
3151 mm.coerce(&NumericType::Unknown.into(), CoercionMode::implicit(), &mut exec_state)
3152 .unwrap_err();
3153 default
3154 .coerce(&NumericType::Unknown.into(), CoercionMode::implicit(), &mut exec_state)
3155 .unwrap_err();
3156
3157 assert_eq!(
3158 inches
3159 .coerce(&NumericType::mm().into(), CoercionMode::implicit(), &mut exec_state)
3160 .unwrap()
3161 .as_f64()
3162 .unwrap()
3163 .round(),
3164 25.0
3165 );
3166 assert_eq!(
3167 rads.coerce(
3168 &NumericType::Known(UnitType::Angle(UnitAngle::Degrees)).into(),
3169 CoercionMode::implicit(),
3170 &mut exec_state
3171 )
3172 .unwrap()
3173 .as_f64()
3174 .unwrap()
3175 .round(),
3176 57.0
3177 );
3178 assert_eq!(
3179 inches
3180 .coerce(
3181 &NumericType::default().into(),
3182 CoercionMode::implicit(),
3183 &mut exec_state
3184 )
3185 .unwrap()
3186 .as_f64()
3187 .unwrap()
3188 .round(),
3189 1.0
3190 );
3191 assert_eq!(
3192 rads.coerce(
3193 &NumericType::default().into(),
3194 CoercionMode::implicit(),
3195 &mut exec_state
3196 )
3197 .unwrap()
3198 .as_f64()
3199 .unwrap()
3200 .round(),
3201 1.0
3202 );
3203 ctx.close().await;
3204 }
3205
3206 #[track_caller]
3207 fn assert_value_and_type(name: &str, result: &ExecTestResults, expected: f64, expected_ty: NumericType) {
3208 let mem = result.exec_state.stack();
3209 match mem
3210 .memory
3211 .get_from_owned(name, result.mem_env, SourceRange::default(), 0)
3212 .unwrap()
3213 {
3214 KclValue::Number { value, ty, .. } => {
3215 assert_eq!(value.round(), expected);
3216 assert_eq!(ty, expected_ty);
3217 }
3218 _ => unreachable!(),
3219 }
3220 }
3221
3222 #[tokio::test(flavor = "multi_thread")]
3223 async fn combine_numeric() {
3224 let program = r#"a = 5 + 4
3225b = 5 - 2
3226c = 5mm - 2mm + 10mm
3227d = 5mm - 2 + 10
3228e = 5 - 2mm + 10
3229f = 30mm - 1inch
3230
3231g = 2 * 10
3232h = 2 * 10mm
3233i = 2mm * 10mm
3234j = 2_ * 10
3235k = 2_ * 3mm * 3mm
3236
3237l = 1 / 10
3238m = 2mm / 1mm
3239n = 10inch / 2mm
3240o = 3mm / 3
3241p = 3_ / 4
3242q = 4inch / 2_
3243
3244r = min([0, 3, 42])
3245s = min([0, 3mm, -42])
3246t = min([100, 3in, 142mm])
3247u = min([3rad, 4in])
3248"#;
3249
3250 let result = parse_execute(program).await.unwrap();
3251 assert_eq!(
3252 result.exec_state.issues().len(),
3253 5,
3254 "errors: {:?}",
3255 result.exec_state.issues()
3256 );
3257
3258 assert_value_and_type("a", &result, 9.0, NumericType::default());
3259 assert_value_and_type("b", &result, 3.0, NumericType::default());
3260 assert_value_and_type("c", &result, 13.0, NumericType::mm());
3261 assert_value_and_type("d", &result, 13.0, NumericType::mm());
3262 assert_value_and_type("e", &result, 13.0, NumericType::mm());
3263 assert_value_and_type("f", &result, 5.0, NumericType::mm());
3264
3265 assert_value_and_type("g", &result, 20.0, NumericType::default());
3266 assert_value_and_type("h", &result, 20.0, NumericType::mm());
3267 assert_value_and_type("i", &result, 20.0, NumericType::Unknown);
3268 assert_value_and_type("j", &result, 20.0, NumericType::default());
3269 assert_value_and_type("k", &result, 18.0, NumericType::Unknown);
3270
3271 assert_value_and_type("l", &result, 0.0, NumericType::default());
3272 assert_value_and_type("m", &result, 2.0, NumericType::count());
3273 assert_value_and_type("n", &result, 5.0, NumericType::Unknown);
3274 assert_value_and_type("o", &result, 1.0, NumericType::mm());
3275 assert_value_and_type("p", &result, 1.0, NumericType::count());
3276 assert_value_and_type(
3277 "q",
3278 &result,
3279 2.0,
3280 NumericType::Known(UnitType::Length(UnitLength::Inches)),
3281 );
3282
3283 assert_value_and_type("r", &result, 0.0, NumericType::default());
3284 assert_value_and_type("s", &result, -42.0, NumericType::mm());
3285 assert_value_and_type("t", &result, 3.0, NumericType::Unknown);
3286 assert_value_and_type("u", &result, 3.0, NumericType::Unknown);
3287 }
3288
3289 #[tokio::test(flavor = "multi_thread")]
3290 async fn bad_typed_arithmetic() {
3291 let program = r#"
3292a = 1rad
3293b = 180 / PI * a + 360
3294"#;
3295
3296 let result = parse_execute(program).await.unwrap();
3297
3298 assert_value_and_type("a", &result, 1.0, NumericType::radians());
3299 assert_value_and_type("b", &result, 417.0, NumericType::Unknown);
3300 }
3301
3302 #[tokio::test(flavor = "multi_thread")]
3303 async fn cos_coercions() {
3304 let program = r#"
3305a = cos(units::toRadians(30deg))
3306b = 3 / a
3307c = cos(30deg)
3308d = cos(1rad)
3309"#;
3310
3311 let result = parse_execute(program).await.unwrap();
3312 assert!(
3313 result.exec_state.issues().is_empty(),
3314 "{:?}",
3315 result.exec_state.issues()
3316 );
3317
3318 assert_value_and_type("a", &result, 1.0, NumericType::default());
3319 assert_value_and_type("b", &result, 3.0, NumericType::default());
3320 assert_value_and_type("c", &result, 1.0, NumericType::default());
3321 assert_value_and_type("d", &result, 1.0, NumericType::default());
3322 }
3323
3324 #[tokio::test(flavor = "multi_thread")]
3325 async fn coerce_nested_array() {
3326 let (ctx, mut exec_state) = new_exec_state().await;
3327
3328 let mixed1 = KclValue::HomArray {
3329 value: vec![
3330 KclValue::Number {
3331 value: 0.0,
3332 ty: NumericType::count(),
3333 meta: Vec::new(),
3334 },
3335 KclValue::Number {
3336 value: 1.0,
3337 ty: NumericType::count(),
3338 meta: Vec::new(),
3339 },
3340 KclValue::HomArray {
3341 value: vec![
3342 KclValue::Number {
3343 value: 2.0,
3344 ty: NumericType::count(),
3345 meta: Vec::new(),
3346 },
3347 KclValue::Number {
3348 value: 3.0,
3349 ty: NumericType::count(),
3350 meta: Vec::new(),
3351 },
3352 ],
3353 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
3354 },
3355 ],
3356 ty: RuntimeType::any(),
3357 };
3358
3359 let tym1 = RuntimeType::Array(
3361 Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
3362 ArrayLen::Minimum(1),
3363 );
3364
3365 let result = KclValue::HomArray {
3366 value: vec![
3367 KclValue::Number {
3368 value: 0.0,
3369 ty: NumericType::count(),
3370 meta: Vec::new(),
3371 },
3372 KclValue::Number {
3373 value: 1.0,
3374 ty: NumericType::count(),
3375 meta: Vec::new(),
3376 },
3377 KclValue::Number {
3378 value: 2.0,
3379 ty: NumericType::count(),
3380 meta: Vec::new(),
3381 },
3382 KclValue::Number {
3383 value: 3.0,
3384 ty: NumericType::count(),
3385 meta: Vec::new(),
3386 },
3387 ],
3388 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
3389 };
3390 assert_coerce_results(&mixed1, &tym1, &result, &mut exec_state);
3391 ctx.close().await;
3392 }
3393}