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}
520
521impl PrimitiveType {
522 fn display_multiple(&self) -> String {
523 match self {
524 PrimitiveType::Any => "any values".to_owned(),
525 PrimitiveType::Never => "values of type `never`".to_owned(),
526 PrimitiveType::None => "none values".to_owned(),
527 PrimitiveType::Number(NumericType::Known(unit)) => format!("numbers({unit})"),
528 PrimitiveType::Number(_) => "numbers".to_owned(),
529 PrimitiveType::String => "strings".to_owned(),
530 PrimitiveType::Boolean => "bools".to_owned(),
531 PrimitiveType::GdtAnnotation => "GD&T Annotations".to_owned(),
532 PrimitiveType::Segment => "Segments".to_owned(),
533 PrimitiveType::Sketch => "Sketches".to_owned(),
534 PrimitiveType::Constraint => "Constraints".to_owned(),
535 PrimitiveType::Solid => "Solids".to_owned(),
536 PrimitiveType::Plane => "Planes".to_owned(),
537 PrimitiveType::Helix => "Helices".to_owned(),
538 PrimitiveType::Face => "Faces".to_owned(),
539 PrimitiveType::Edge => "Edges".to_owned(),
540 PrimitiveType::BoundedEdge => "BoundedEdges".to_owned(),
541 PrimitiveType::Axis2d => "2d axes".to_owned(),
542 PrimitiveType::Axis3d => "3d axes".to_owned(),
543 PrimitiveType::ImportedGeometry => "imported geometries".to_owned(),
544 PrimitiveType::Function => "functions".to_owned(),
545 PrimitiveType::TagDecl => "tag declarators".to_owned(),
546 PrimitiveType::TaggedEdge => "tagged edges".to_owned(),
547 PrimitiveType::TaggedFace => "tagged faces".to_owned(),
548 }
549 }
550
551 fn subtype(&self, other: &PrimitiveType) -> bool {
552 match (self, other) {
553 (PrimitiveType::Never, _) => true,
554 (_, PrimitiveType::Any) => true,
555 (PrimitiveType::Number(n1), PrimitiveType::Number(n2)) => n1.subtype(n2),
556 (PrimitiveType::TaggedEdge, PrimitiveType::TaggedFace)
557 | (PrimitiveType::TaggedEdge, PrimitiveType::Edge) => true,
558 (t1, t2) => t1 == t2,
559 }
560 }
561}
562
563impl std::fmt::Display for PrimitiveType {
564 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
565 match self {
566 PrimitiveType::Any => write!(f, "any"),
567 PrimitiveType::Never => write!(f, "never"),
568 PrimitiveType::None => write!(f, "none"),
569 PrimitiveType::Number(NumericType::Known(unit)) => write!(f, "number({unit})"),
570 PrimitiveType::Number(NumericType::Unknown) => write!(f, "number(unknown units)"),
571 PrimitiveType::Number(NumericType::Default { .. }) => write!(f, "number"),
572 PrimitiveType::Number(NumericType::Any) => write!(f, "number(any units)"),
573 PrimitiveType::String => write!(f, "string"),
574 PrimitiveType::Boolean => write!(f, "bool"),
575 PrimitiveType::TagDecl => write!(f, "tag declarator"),
576 PrimitiveType::TaggedEdge => write!(f, "tagged edge"),
577 PrimitiveType::TaggedFace => write!(f, "tagged face"),
578 PrimitiveType::GdtAnnotation => write!(f, "GD&T Annotation"),
579 PrimitiveType::Segment => write!(f, "Segment"),
580 PrimitiveType::Sketch => write!(f, "Sketch"),
581 PrimitiveType::Constraint => write!(f, "Constraint"),
582 PrimitiveType::Solid => write!(f, "Solid"),
583 PrimitiveType::Plane => write!(f, "Plane"),
584 PrimitiveType::Face => write!(f, "Face"),
585 PrimitiveType::Edge => write!(f, "Edge"),
586 PrimitiveType::BoundedEdge => write!(f, "BoundedEdge"),
587 PrimitiveType::Axis2d => write!(f, "Axis2d"),
588 PrimitiveType::Axis3d => write!(f, "Axis3d"),
589 PrimitiveType::Helix => write!(f, "Helix"),
590 PrimitiveType::ImportedGeometry => write!(f, "ImportedGeometry"),
591 PrimitiveType::Function => write!(f, "fn"),
592 }
593 }
594}
595
596pub trait NumericTypeExt {
597 fn count() -> Self;
598
599 fn mm() -> Self;
600
601 fn radians() -> Self;
602
603 fn degrees() -> Self;
604
605 fn length(unit: UnitLength) -> Self;
606
607 fn optional_length(unit: Option<UnitLength>) -> Self;
608
609 fn angle(unit: UnitAngle) -> Self;
610
611 fn combine_eq(a: TyF64, b: TyF64, exec_state: &mut ExecState, source_range: SourceRange)
617 -> (f64, f64, NumericType);
618
619 fn combine_eq_coerce(
627 a: TyF64,
628 b: TyF64,
629 for_errs: Option<(&mut ExecState, SourceRange)>,
630 ) -> (f64, f64, NumericType);
631
632 fn combine_eq_array(input: &[TyF64]) -> (Vec<f64>, NumericType);
633
634 fn combine_mul(a: TyF64, b: TyF64) -> (f64, f64, NumericType);
636
637 fn combine_div(a: TyF64, b: TyF64) -> (f64, f64, NumericType);
639
640 fn combine_mod(a: TyF64, b: TyF64) -> (f64, f64, NumericType);
642
643 fn combine_range(
649 a: TyF64,
650 b: TyF64,
651 exec_state: &mut ExecState,
652 source_range: SourceRange,
653 ) -> Result<(f64, f64, NumericType), KclError>;
654
655 fn from_parsed(suffix: NumericSuffix, settings: &super::MetaSettings) -> Self;
656
657 fn subtype(&self, other: &NumericType) -> bool;
658
659 fn is_unknown(&self) -> bool;
660
661 fn is_fully_specified(&self) -> bool;
662
663 fn example_ty(&self) -> Option<String>;
664
665 fn coerce(&self, val: &KclValue) -> Result<KclValue, CoercionError>;
666
667 fn as_length(&self) -> Option<UnitLength>;
668}
669
670impl NumericTypeExt for NumericType {
671 fn count() -> Self {
672 NumericType::Known(UnitType::Count)
673 }
674
675 fn mm() -> Self {
676 NumericType::Known(UnitType::Length(UnitLength::Millimeters))
677 }
678
679 fn radians() -> Self {
680 NumericType::Known(UnitType::Angle(UnitAngle::Radians))
681 }
682
683 fn degrees() -> Self {
684 NumericType::Known(UnitType::Angle(UnitAngle::Degrees))
685 }
686
687 fn length(unit: UnitLength) -> Self {
688 NumericType::Known(UnitType::Length(unit))
689 }
690
691 fn optional_length(unit: Option<UnitLength>) -> Self {
692 match unit {
693 Some(unit) => Self::length(unit),
694 None => NumericType::Unknown,
695 }
696 }
697
698 fn angle(unit: UnitAngle) -> Self {
699 NumericType::Known(UnitType::Angle(unit))
700 }
701
702 fn combine_eq(
708 a: TyF64,
709 b: TyF64,
710 exec_state: &mut ExecState,
711 source_range: SourceRange,
712 ) -> (f64, f64, NumericType) {
713 use NumericType::*;
714 match (a.ty, b.ty) {
715 (at, bt) if at == bt => (a.n, b.n, at),
716 (at, Any) => (a.n, b.n, at),
717 (Any, bt) => (a.n, b.n, bt),
718
719 (t @ Known(UnitType::Length(l1)), Known(UnitType::Length(l2))) => (a.n, adjust_length(l2, b.n, l1).0, t),
720 (t @ Known(UnitType::Angle(a1)), Known(UnitType::Angle(a2))) => (a.n, adjust_angle(a2, b.n, a1).0, t),
721
722 (t @ Known(UnitType::Length(_)), Known(UnitType::GenericLength)) => (a.n, b.n, t),
723 (Known(UnitType::GenericLength), t @ Known(UnitType::Length(_))) => (a.n, b.n, t),
724 (t @ Known(UnitType::Angle(_)), Known(UnitType::GenericAngle)) => (a.n, b.n, t),
725 (Known(UnitType::GenericAngle), t @ Known(UnitType::Angle(_))) => (a.n, b.n, t),
726
727 (Known(UnitType::Count), Default { .. }) | (Default { .. }, Known(UnitType::Count)) => {
728 (a.n, b.n, Known(UnitType::Count))
729 }
730 (t @ Known(UnitType::Length(l1)), Default { len: l2, .. }) if l1 == l2 => (a.n, b.n, t),
731 (Default { len: l1, .. }, t @ Known(UnitType::Length(l2))) if l1 == l2 => (a.n, b.n, t),
732 (t @ Known(UnitType::Angle(a1)), Default { angle: a2, .. }) if a1 == a2 => {
733 if b.n != 0.0 {
734 exec_state.warn(
735 CompilationIssue::err(source_range, "Prefer to use explicit units for angles"),
736 annotations::WARN_ANGLE_UNITS,
737 );
738 }
739 (a.n, b.n, t)
740 }
741 (Default { angle: a1, .. }, t @ Known(UnitType::Angle(a2))) if a1 == a2 => {
742 if a.n != 0.0 {
743 exec_state.warn(
744 CompilationIssue::err(source_range, "Prefer to use explicit units for angles"),
745 annotations::WARN_ANGLE_UNITS,
746 );
747 }
748 (a.n, b.n, t)
749 }
750
751 _ => (a.n, b.n, Unknown),
752 }
753 }
754
755 fn combine_eq_coerce(
763 a: TyF64,
764 b: TyF64,
765 for_errs: Option<(&mut ExecState, SourceRange)>,
766 ) -> (f64, f64, NumericType) {
767 use NumericType::*;
768 match (a.ty, b.ty) {
769 (at, bt) if at == bt => (a.n, b.n, at),
770 (at, Any) => (a.n, b.n, at),
771 (Any, bt) => (a.n, b.n, bt),
772
773 (t @ Known(UnitType::Length(l1)), Known(UnitType::Length(l2))) => (a.n, adjust_length(l2, b.n, l1).0, t),
775 (t @ Known(UnitType::Angle(a1)), Known(UnitType::Angle(a2))) => (a.n, adjust_angle(a2, b.n, a1).0, t),
776
777 (t @ Known(UnitType::Length(_)), Known(UnitType::GenericLength)) => (a.n, b.n, t),
778 (Known(UnitType::GenericLength), t @ Known(UnitType::Length(_))) => (a.n, b.n, t),
779 (t @ Known(UnitType::Angle(_)), Known(UnitType::GenericAngle)) => (a.n, b.n, t),
780 (Known(UnitType::GenericAngle), t @ Known(UnitType::Angle(_))) => (a.n, b.n, t),
781
782 (Known(UnitType::Count), Default { .. }) | (Default { .. }, Known(UnitType::Count)) => {
784 (a.n, b.n, Known(UnitType::Count))
785 }
786
787 (t @ Known(UnitType::Length(l1)), Default { len: l2, .. }) => (a.n, adjust_length(l2, b.n, l1).0, t),
788 (Default { len: l1, .. }, t @ Known(UnitType::Length(l2))) => (adjust_length(l1, a.n, l2).0, b.n, t),
789 (t @ Known(UnitType::Angle(a1)), Default { angle: a2, .. }) => {
790 if let Some((exec_state, source_range)) = for_errs
791 && b.n != 0.0
792 {
793 exec_state.warn(
794 CompilationIssue::err(source_range, "Prefer to use explicit units for angles"),
795 annotations::WARN_ANGLE_UNITS,
796 );
797 }
798 (a.n, adjust_angle(a2, b.n, a1).0, t)
799 }
800 (Default { angle: a1, .. }, t @ Known(UnitType::Angle(a2))) => {
801 if let Some((exec_state, source_range)) = for_errs
802 && a.n != 0.0
803 {
804 exec_state.warn(
805 CompilationIssue::err(source_range, "Prefer to use explicit units for angles"),
806 annotations::WARN_ANGLE_UNITS,
807 );
808 }
809 (adjust_angle(a1, a.n, a2).0, b.n, t)
810 }
811
812 (Default { len: l1, .. }, Known(UnitType::GenericLength)) => (a.n, b.n, Self::length(l1)),
813 (Known(UnitType::GenericLength), Default { len: l2, .. }) => (a.n, b.n, Self::length(l2)),
814 (Default { angle: a1, .. }, Known(UnitType::GenericAngle)) => {
815 if let Some((exec_state, source_range)) = for_errs
816 && b.n != 0.0
817 {
818 exec_state.warn(
819 CompilationIssue::err(source_range, "Prefer to use explicit units for angles"),
820 annotations::WARN_ANGLE_UNITS,
821 );
822 }
823 (a.n, b.n, Self::angle(a1))
824 }
825 (Known(UnitType::GenericAngle), Default { angle: a2, .. }) => {
826 if let Some((exec_state, source_range)) = for_errs
827 && a.n != 0.0
828 {
829 exec_state.warn(
830 CompilationIssue::err(source_range, "Prefer to use explicit units for angles"),
831 annotations::WARN_ANGLE_UNITS,
832 );
833 }
834 (a.n, b.n, Self::angle(a2))
835 }
836
837 (Known(_), Known(_)) | (Default { .. }, Default { .. }) | (_, Unknown) | (Unknown, _) => {
838 (a.n, b.n, Unknown)
839 }
840 }
841 }
842
843 fn combine_eq_array(input: &[TyF64]) -> (Vec<f64>, NumericType) {
844 use NumericType::*;
845 let result = input.iter().map(|t| t.n).collect();
846
847 let mut ty = Any;
848 for i in input {
849 if i.ty == Any || ty == i.ty {
850 continue;
851 }
852
853 match (&ty, &i.ty) {
855 (Any, Default { .. }) if i.n == 0.0 => {}
856 (Any, t) => {
857 ty = *t;
858 }
859 (_, Unknown) | (Default { .. }, Default { .. }) => return (result, Unknown),
860
861 (Known(UnitType::Count), Default { .. }) | (Default { .. }, Known(UnitType::Count)) => {
862 ty = Known(UnitType::Count);
863 }
864
865 (Known(UnitType::Length(l1)), Default { len: l2, .. }) if l1 == l2 || i.n == 0.0 => {}
866 (Known(UnitType::Angle(a1)), Default { angle: a2, .. }) if a1 == a2 || i.n == 0.0 => {}
867
868 (Default { len: l1, .. }, Known(UnitType::Length(l2))) if l1 == l2 => {
869 ty = Known(UnitType::Length(*l2));
870 }
871 (Default { angle: a1, .. }, Known(UnitType::Angle(a2))) if a1 == a2 => {
872 ty = Known(UnitType::Angle(*a2));
873 }
874
875 _ => return (result, Unknown),
876 }
877 }
878
879 if ty == Any && !input.is_empty() {
880 ty = input[0].ty;
881 }
882
883 (result, ty)
884 }
885
886 fn combine_mul(a: TyF64, b: TyF64) -> (f64, f64, NumericType) {
888 use NumericType::*;
889 match (a.ty, b.ty) {
890 (at @ Default { .. }, bt @ Default { .. }) if at == bt => (a.n, b.n, at),
891 (Default { .. }, Default { .. }) => (a.n, b.n, Unknown),
892 (Known(UnitType::Count), bt) => (a.n, b.n, bt),
893 (at, Known(UnitType::Count)) => (a.n, b.n, at),
894 (at @ Known(_), Default { .. }) | (Default { .. }, at @ Known(_)) => (a.n, b.n, at),
895 (Any, Any) => (a.n, b.n, Any),
896 _ => (a.n, b.n, Unknown),
897 }
898 }
899
900 fn combine_div(a: TyF64, b: TyF64) -> (f64, f64, NumericType) {
902 use NumericType::*;
903 match (a.ty, b.ty) {
904 (at @ Default { .. }, bt @ Default { .. }) if at == bt => (a.n, b.n, at),
905 (at, bt) if at == bt => (a.n, b.n, Known(UnitType::Count)),
906 (Default { .. }, Default { .. }) => (a.n, b.n, Unknown),
907 (at, Known(UnitType::Count) | Any) => (a.n, b.n, at),
908 (at @ Known(_), Default { .. }) => (a.n, b.n, at),
909 (Known(UnitType::Count), _) => (a.n, b.n, Known(UnitType::Count)),
910 _ => (a.n, b.n, Unknown),
911 }
912 }
913
914 fn combine_mod(a: TyF64, b: TyF64) -> (f64, f64, NumericType) {
916 use NumericType::*;
917 match (a.ty, b.ty) {
918 (at @ Default { .. }, bt @ Default { .. }) if at == bt => (a.n, b.n, at),
919 (at, bt) if at == bt => (a.n, b.n, at),
920 (Default { .. }, Default { .. }) => (a.n, b.n, Unknown),
921 (at, Known(UnitType::Count) | Any) => (a.n, b.n, at),
922 (at @ Known(_), Default { .. }) => (a.n, b.n, at),
923 (Known(UnitType::Count), _) => (a.n, b.n, Known(UnitType::Count)),
924 _ => (a.n, b.n, Unknown),
925 }
926 }
927
928 fn combine_range(
934 a: TyF64,
935 b: TyF64,
936 exec_state: &mut ExecState,
937 source_range: SourceRange,
938 ) -> Result<(f64, f64, NumericType), KclError> {
939 use NumericType::*;
940 match (a.ty, b.ty) {
941 (at, bt) if at == bt => Ok((a.n, b.n, at)),
942 (at, Any) => Ok((a.n, b.n, at)),
943 (Any, bt) => Ok((a.n, b.n, bt)),
944
945 (Known(UnitType::Length(l1)), Known(UnitType::Length(l2))) => {
946 Err(KclError::new_semantic(KclErrorDetails::new(
947 format!("Range start and range end have incompatible units: {l1} and {l2}"),
948 vec![source_range],
949 )))
950 }
951 (Known(UnitType::Angle(a1)), Known(UnitType::Angle(a2))) => {
952 Err(KclError::new_semantic(KclErrorDetails::new(
953 format!("Range start and range end have incompatible units: {a1} and {a2}"),
954 vec![source_range],
955 )))
956 }
957
958 (t @ Known(UnitType::Length(_)), Known(UnitType::GenericLength)) => Ok((a.n, b.n, t)),
959 (Known(UnitType::GenericLength), t @ Known(UnitType::Length(_))) => Ok((a.n, b.n, t)),
960 (t @ Known(UnitType::Angle(_)), Known(UnitType::GenericAngle)) => Ok((a.n, b.n, t)),
961 (Known(UnitType::GenericAngle), t @ Known(UnitType::Angle(_))) => Ok((a.n, b.n, t)),
962
963 (Known(UnitType::Count), Default { .. }) | (Default { .. }, Known(UnitType::Count)) => {
964 Ok((a.n, b.n, Known(UnitType::Count)))
965 }
966 (t @ Known(UnitType::Length(l1)), Default { len: l2, .. }) if l1 == l2 => Ok((a.n, b.n, t)),
967 (Default { len: l1, .. }, t @ Known(UnitType::Length(l2))) if l1 == l2 => Ok((a.n, b.n, t)),
968 (t @ Known(UnitType::Angle(a1)), Default { angle: a2, .. }) if a1 == a2 => {
969 if b.n != 0.0 {
970 exec_state.warn(
971 CompilationIssue::err(source_range, "Prefer to use explicit units for angles"),
972 annotations::WARN_ANGLE_UNITS,
973 );
974 }
975 Ok((a.n, b.n, t))
976 }
977 (Default { angle: a1, .. }, t @ Known(UnitType::Angle(a2))) if a1 == a2 => {
978 if a.n != 0.0 {
979 exec_state.warn(
980 CompilationIssue::err(source_range, "Prefer to use explicit units for angles"),
981 annotations::WARN_ANGLE_UNITS,
982 );
983 }
984 Ok((a.n, b.n, t))
985 }
986
987 _ => {
988 let a = fmt::human_display_number(a.n, a.ty);
989 let b = fmt::human_display_number(b.n, b.ty);
990 Err(KclError::new_semantic(KclErrorDetails::new(
991 format!(
992 "Range start and range end must be of the same type and have compatible units, but found {a} and {b}",
993 ),
994 vec![source_range],
995 )))
996 }
997 }
998 }
999
1000 fn from_parsed(suffix: NumericSuffix, settings: &super::MetaSettings) -> Self {
1001 match suffix {
1002 NumericSuffix::None => NumericType::Default {
1003 len: settings.default_length_units,
1004 angle: settings.default_angle_units,
1005 },
1006 NumericSuffix::Count => NumericType::Known(UnitType::Count),
1007 NumericSuffix::Length => NumericType::Known(UnitType::GenericLength),
1008 NumericSuffix::Angle => NumericType::Known(UnitType::GenericAngle),
1009 NumericSuffix::Mm => NumericType::Known(UnitType::Length(UnitLength::Millimeters)),
1010 NumericSuffix::Cm => NumericType::Known(UnitType::Length(UnitLength::Centimeters)),
1011 NumericSuffix::M => NumericType::Known(UnitType::Length(UnitLength::Meters)),
1012 NumericSuffix::Inch => NumericType::Known(UnitType::Length(UnitLength::Inches)),
1013 NumericSuffix::Ft => NumericType::Known(UnitType::Length(UnitLength::Feet)),
1014 NumericSuffix::Yd => NumericType::Known(UnitType::Length(UnitLength::Yards)),
1015 NumericSuffix::Deg => NumericType::Known(UnitType::Angle(UnitAngle::Degrees)),
1016 NumericSuffix::Rad => NumericType::Known(UnitType::Angle(UnitAngle::Radians)),
1017 NumericSuffix::Unknown => NumericType::Unknown,
1018 }
1019 }
1020
1021 fn subtype(&self, other: &NumericType) -> bool {
1022 use NumericType::*;
1023
1024 match (self, other) {
1025 (_, Any) => true,
1026 (a, b) if a == b => true,
1027 (
1028 NumericType::Known(UnitType::Length(_))
1029 | NumericType::Known(UnitType::GenericLength)
1030 | NumericType::Default { .. },
1031 NumericType::Known(UnitType::GenericLength),
1032 )
1033 | (
1034 NumericType::Known(UnitType::Angle(_))
1035 | NumericType::Known(UnitType::GenericAngle)
1036 | NumericType::Default { .. },
1037 NumericType::Known(UnitType::GenericAngle),
1038 ) => true,
1039 (Unknown, _) | (_, Unknown) => false,
1040 (_, _) => false,
1041 }
1042 }
1043
1044 fn is_unknown(&self) -> bool {
1045 matches!(
1046 self,
1047 NumericType::Unknown
1048 | NumericType::Known(UnitType::GenericAngle)
1049 | NumericType::Known(UnitType::GenericLength)
1050 )
1051 }
1052
1053 fn is_fully_specified(&self) -> bool {
1054 !matches!(
1055 self,
1056 NumericType::Unknown
1057 | NumericType::Known(UnitType::GenericAngle)
1058 | NumericType::Known(UnitType::GenericLength)
1059 | NumericType::Any
1060 | NumericType::Default { .. }
1061 )
1062 }
1063
1064 fn example_ty(&self) -> Option<String> {
1065 match self {
1066 Self::Known(t) if !self.is_unknown() => Some(t.to_string()),
1067 Self::Default { len, .. } => Some(len.to_string()),
1068 _ => None,
1069 }
1070 }
1071
1072 fn coerce(&self, val: &KclValue) -> Result<KclValue, CoercionError> {
1073 let (value, ty, meta) = match val {
1074 KclValue::Number { value, ty, meta } => (value, ty, meta),
1075 KclValue::SketchVar { .. } => return Ok(val.clone()),
1079 _ => return Err(val.into()),
1080 };
1081
1082 if ty.subtype(self) {
1083 return Ok(KclValue::Number {
1084 value: *value,
1085 ty: *ty,
1086 meta: meta.clone(),
1087 });
1088 }
1089
1090 use NumericType::*;
1092 match (ty, self) {
1093 (Unknown, _) => Err(CoercionError::from(val).with_explicit(self.example_ty().unwrap_or("mm".to_owned()))),
1095 (_, Unknown) => Err(val.into()),
1096
1097 (Any, _) => Ok(KclValue::Number {
1098 value: *value,
1099 ty: *self,
1100 meta: meta.clone(),
1101 }),
1102
1103 (_, Default { .. }) => Ok(KclValue::Number {
1106 value: *value,
1107 ty: *ty,
1108 meta: meta.clone(),
1109 }),
1110
1111 (Known(UnitType::Length(l1)), Known(UnitType::Length(l2))) => {
1113 let (value, ty) = adjust_length(*l1, *value, *l2);
1114 Ok(KclValue::Number {
1115 value,
1116 ty: Known(UnitType::Length(ty)),
1117 meta: meta.clone(),
1118 })
1119 }
1120 (Known(UnitType::Angle(a1)), Known(UnitType::Angle(a2))) => {
1121 let (value, ty) = adjust_angle(*a1, *value, *a2);
1122 Ok(KclValue::Number {
1123 value,
1124 ty: Known(UnitType::Angle(ty)),
1125 meta: meta.clone(),
1126 })
1127 }
1128
1129 (Known(_), Known(_)) => Err(val.into()),
1131
1132 (Default { .. }, Known(UnitType::Count)) => Ok(KclValue::Number {
1134 value: *value,
1135 ty: Known(UnitType::Count),
1136 meta: meta.clone(),
1137 }),
1138
1139 (Default { len: l1, .. }, Known(UnitType::Length(l2))) => {
1140 let (value, ty) = adjust_length(*l1, *value, *l2);
1141 Ok(KclValue::Number {
1142 value,
1143 ty: Known(UnitType::Length(ty)),
1144 meta: meta.clone(),
1145 })
1146 }
1147
1148 (Default { angle: a1, .. }, Known(UnitType::Angle(a2))) => {
1149 let (value, ty) = adjust_angle(*a1, *value, *a2);
1150 Ok(KclValue::Number {
1151 value,
1152 ty: Known(UnitType::Angle(ty)),
1153 meta: meta.clone(),
1154 })
1155 }
1156
1157 (_, _) => unreachable!(),
1158 }
1159 }
1160
1161 fn as_length(&self) -> Option<UnitLength> {
1162 match self {
1163 Self::Known(UnitType::Length(len)) | Self::Default { len, .. } => Some(*len),
1164 _ => None,
1165 }
1166 }
1167}
1168
1169impl From<NumericType> for RuntimeType {
1170 fn from(t: NumericType) -> RuntimeType {
1171 RuntimeType::Primitive(PrimitiveType::Number(t))
1172 }
1173}
1174
1175impl From<UnitLength> for NumericSuffix {
1176 fn from(value: UnitLength) -> Self {
1177 match value {
1178 UnitLength::Millimeters => NumericSuffix::Mm,
1179 UnitLength::Centimeters => NumericSuffix::Cm,
1180 UnitLength::Meters => NumericSuffix::M,
1181 UnitLength::Inches => NumericSuffix::Inch,
1182 UnitLength::Feet => NumericSuffix::Ft,
1183 UnitLength::Yards => NumericSuffix::Yd,
1184 }
1185 }
1186}
1187
1188#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, ts_rs::TS)]
1189pub struct NumericSuffixTypeConvertError;
1190
1191impl TryFrom<NumericType> for NumericSuffix {
1192 type Error = NumericSuffixTypeConvertError;
1193
1194 fn try_from(value: NumericType) -> Result<Self, Self::Error> {
1195 match value {
1196 NumericType::Known(UnitType::Count) => Ok(NumericSuffix::Count),
1197 NumericType::Known(UnitType::Length(unit_length)) => Ok(NumericSuffix::from(unit_length)),
1198 NumericType::Known(UnitType::GenericLength) => Ok(NumericSuffix::Length),
1199 NumericType::Known(UnitType::Angle(UnitAngle::Degrees)) => Ok(NumericSuffix::Deg),
1200 NumericType::Known(UnitType::Angle(UnitAngle::Radians)) => Ok(NumericSuffix::Rad),
1201 NumericType::Known(UnitType::GenericAngle) => Ok(NumericSuffix::Angle),
1202 NumericType::Default { .. } => Ok(NumericSuffix::None),
1203 NumericType::Unknown => Ok(NumericSuffix::Unknown),
1204 NumericType::Any => Err(NumericSuffixTypeConvertError),
1205 }
1206 }
1207}
1208
1209pub fn adjust_length(from: UnitLength, value: f64, to: UnitLength) -> (f64, UnitLength) {
1210 use UnitLength::*;
1211
1212 if from == to {
1213 return (value, to);
1214 }
1215
1216 let (base, base_unit) = match from {
1217 Millimeters => (value, Millimeters),
1218 Centimeters => (value * 10.0, Millimeters),
1219 Meters => (value * 1000.0, Millimeters),
1220 Inches => (value, Inches),
1221 Feet => (value * 12.0, Inches),
1222 Yards => (value * 36.0, Inches),
1223 };
1224 let (base, base_unit) = match (base_unit, to) {
1225 (Millimeters, Inches) | (Millimeters, Feet) | (Millimeters, Yards) => (base / 25.4, Inches),
1226 (Inches, Millimeters) | (Inches, Centimeters) | (Inches, Meters) => (base * 25.4, Millimeters),
1227 _ => (base, base_unit),
1228 };
1229
1230 let value = match (base_unit, to) {
1231 (Millimeters, Millimeters) => base,
1232 (Millimeters, Centimeters) => base / 10.0,
1233 (Millimeters, Meters) => base / 1000.0,
1234 (Inches, Inches) => base,
1235 (Inches, Feet) => base / 12.0,
1236 (Inches, Yards) => base / 36.0,
1237 _ => unreachable!(),
1238 };
1239
1240 (value, to)
1241}
1242
1243pub fn adjust_angle(from: UnitAngle, value: f64, to: UnitAngle) -> (f64, UnitAngle) {
1244 use std::f64::consts::PI;
1245
1246 use UnitAngle::*;
1247
1248 let value = match (from, to) {
1249 (Degrees, Degrees) => value,
1250 (Degrees, Radians) => (value / 180.0) * PI,
1251 (Radians, Degrees) => 180.0 * value / PI,
1252 (Radians, Radians) => value,
1253 };
1254
1255 (value, to)
1256}
1257
1258pub(super) fn length_from_str(s: &str, source_range: SourceRange) -> Result<UnitLength, KclError> {
1259 match s {
1261 "mm" => Ok(UnitLength::Millimeters),
1262 "cm" => Ok(UnitLength::Centimeters),
1263 "m" => Ok(UnitLength::Meters),
1264 "inch" | "in" => Ok(UnitLength::Inches),
1265 "ft" => Ok(UnitLength::Feet),
1266 "yd" => Ok(UnitLength::Yards),
1267 value => Err(KclError::new_semantic(KclErrorDetails::new(
1268 format!("Unexpected value for length units: `{value}`; expected one of `mm`, `cm`, `m`, `in`, `ft`, `yd`"),
1269 vec![source_range],
1270 ))),
1271 }
1272}
1273
1274pub(super) fn angle_from_str(s: &str, source_range: SourceRange) -> Result<UnitAngle, KclError> {
1275 UnitAngle::from_str(s).map_err(|_| {
1276 KclError::new_semantic(KclErrorDetails::new(
1277 format!("Unexpected value for angle units: `{s}`; expected one of `deg`, `rad`"),
1278 vec![source_range],
1279 ))
1280 })
1281}
1282
1283#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1289pub struct CoercionMode {
1290 convert_units: bool,
1291 project_enums: bool,
1292}
1293
1294impl CoercionMode {
1295 pub fn implicit() -> Self {
1300 CoercionMode {
1301 convert_units: true,
1302 project_enums: false,
1303 }
1304 }
1305
1306 pub fn explicit() -> Self {
1310 CoercionMode {
1311 convert_units: false,
1312 project_enums: true,
1313 }
1314 }
1315
1316 pub(crate) fn convert_units(self) -> bool {
1317 self.convert_units
1318 }
1319
1320 pub(crate) fn project_enums(self) -> bool {
1321 self.project_enums
1322 }
1323
1324 pub(crate) fn without_projection(self) -> Self {
1327 CoercionMode {
1328 project_enums: false,
1329 ..self
1330 }
1331 }
1332}
1333
1334#[derive(Debug, Clone)]
1335pub struct CoercionError {
1336 pub found: Option<RuntimeType>,
1337 pub explicit_coercion: Option<String>,
1338 pub message: Option<String>,
1341}
1342
1343impl CoercionError {
1344 fn with_explicit(mut self, c: String) -> Self {
1345 self.explicit_coercion = Some(c);
1346 self
1347 }
1348
1349 fn with_message(mut self, message: String) -> Self {
1350 self.message = Some(message);
1351 self
1352 }
1353}
1354
1355impl From<&'_ KclValue> for CoercionError {
1356 fn from(value: &'_ KclValue) -> Self {
1357 CoercionError {
1358 found: value.principal_type(),
1359 explicit_coercion: None,
1360 message: None,
1361 }
1362 }
1363}
1364
1365impl KclValue {
1366 pub fn has_type(&self, ty: &RuntimeType) -> bool {
1368 let Some(self_ty) = self.principal_type() else {
1369 return false;
1370 };
1371
1372 self_ty.subtype(ty)
1373 }
1374
1375 pub fn coerce(
1382 &self,
1383 ty: &RuntimeType,
1384 mode: CoercionMode,
1385 exec_state: &mut ExecState,
1386 ) -> Result<KclValue, CoercionError> {
1387 match self {
1388 KclValue::Tuple { value, .. }
1389 if value.len() == 1
1390 && !matches!(ty, RuntimeType::Primitive(PrimitiveType::Any) | RuntimeType::Tuple(..)) =>
1391 {
1392 if let Ok(coerced) = value[0].coerce(ty, mode, exec_state) {
1393 return Ok(coerced);
1394 }
1395 }
1396 KclValue::HomArray { value, .. }
1397 if value.len() == 1
1398 && !matches!(ty, RuntimeType::Primitive(PrimitiveType::Any) | RuntimeType::Array(..)) =>
1399 {
1400 if let Ok(coerced) = value[0].coerce(ty, mode, exec_state) {
1401 return Ok(coerced);
1402 }
1403 }
1404 _ => {}
1405 }
1406
1407 match ty {
1408 RuntimeType::Primitive(ty) => self.coerce_to_primitive_type(ty, mode, exec_state),
1409 RuntimeType::Array(ty, len) => self.coerce_to_array_type(ty, mode, *len, exec_state, false),
1410 RuntimeType::Tuple(tys) => self.coerce_to_tuple_type(tys, mode, exec_state),
1411 RuntimeType::Union(tys) => self.coerce_to_union_type(tys, mode, exec_state),
1412 RuntimeType::Object(tys, constrainable) => {
1413 self.coerce_to_object_type(tys, *constrainable, mode, exec_state)
1414 }
1415 RuntimeType::Enum(id) => self.coerce_to_enum_type(id),
1416 }
1417 }
1418
1419 fn coerce_to_enum_type(&self, id: &EnumTypeId) -> Result<KclValue, CoercionError> {
1424 match self {
1425 KclValue::Enum { value } if value.enum_id() == id => Ok(self.clone()),
1426 _ => Err(self.into()),
1427 }
1428 }
1429
1430 fn coerce_to_primitive_type(
1431 &self,
1432 ty: &PrimitiveType,
1433 mode: CoercionMode,
1434 exec_state: &mut ExecState,
1435 ) -> Result<KclValue, CoercionError> {
1436 match ty {
1437 PrimitiveType::Any => Ok(self.clone()),
1438 PrimitiveType::Never => Err(self.into()),
1439 PrimitiveType::None => match self {
1440 KclValue::KclNone { .. } => Ok(self.clone()),
1441 _ => Err(self.into()),
1442 },
1443 PrimitiveType::Number(ty) => {
1444 if let KclValue::Enum { value } = self
1449 && mode.project_enums()
1450 {
1451 return Err(CoercionError::from(self).with_message(format!(
1452 "Cannot project enum `{}` to a number. An enum projects to `string`; projecting to a number is not supported yet.",
1453 value.enum_id().declared_name()
1454 )));
1455 }
1456
1457 if mode.convert_units() {
1458 return ty.coerce(self);
1459 }
1460
1461 if let KclValue::Number { value: n, meta, .. } = &self
1468 && ty.is_fully_specified()
1469 {
1470 let value = KclValue::Number {
1471 ty: NumericType::Any,
1472 value: *n,
1473 meta: meta.clone(),
1474 };
1475 return ty.coerce(&value);
1476 }
1477 ty.coerce(self)
1478 }
1479 PrimitiveType::String => match self {
1480 KclValue::String { .. } => Ok(self.clone()),
1481 KclValue::Enum { value } if mode.project_enums() => Ok(KclValue::String {
1484 value: value.declared_string_repr(),
1485 meta: value.meta().to_vec(),
1486 }),
1487 _ => Err(self.into()),
1488 },
1489 PrimitiveType::Boolean => match self {
1490 KclValue::Bool { .. } => Ok(self.clone()),
1491 _ => Err(self.into()),
1492 },
1493 PrimitiveType::GdtAnnotation => match self {
1494 KclValue::GdtAnnotation { .. } => Ok(self.clone()),
1495 _ => Err(self.into()),
1496 },
1497 PrimitiveType::Segment => match self {
1498 KclValue::Segment { .. } => Ok(self.clone()),
1499 _ => Err(self.into()),
1500 },
1501 PrimitiveType::Sketch => match self {
1502 KclValue::Sketch { .. } => Ok(self.clone()),
1503 KclValue::Object { value, .. } => {
1504 let Some(meta) = value.get(SKETCH_OBJECT_META) else {
1505 return Err(self.into());
1506 };
1507 let KclValue::Object { value: meta_map, .. } = meta else {
1508 return Err(self.into());
1509 };
1510 let Some(sketch) = meta_map.get(SKETCH_OBJECT_META_SKETCH).and_then(KclValue::as_sketch) else {
1511 return Err(self.into());
1512 };
1513
1514 Ok(KclValue::Sketch {
1515 value: Box::new(sketch.clone()),
1516 })
1517 }
1518 _ => Err(self.into()),
1519 },
1520 PrimitiveType::Constraint => match self {
1521 KclValue::SketchConstraint { .. } => Ok(self.clone()),
1522 _ => Err(self.into()),
1523 },
1524 PrimitiveType::Solid => match self {
1525 KclValue::Solid { .. } => Ok(self.clone()),
1526 _ => Err(self.into()),
1527 },
1528 PrimitiveType::Plane => {
1529 match self {
1530 KclValue::String { value: s, .. }
1531 if [
1532 "xy", "xz", "yz", "-xy", "-xz", "-yz", "XY", "XZ", "YZ", "-XY", "-XZ", "-YZ",
1533 ]
1534 .contains(&&**s) =>
1535 {
1536 Ok(self.clone())
1537 }
1538 KclValue::Plane { .. } => Ok(self.clone()),
1539 KclValue::Object { value, meta, .. } => {
1540 let origin = value
1541 .get("origin")
1542 .and_then(Point3d::from_kcl_val)
1543 .ok_or(CoercionError::from(self))?;
1544 let x_axis = value
1545 .get("xAxis")
1546 .and_then(Point3d::from_kcl_val)
1547 .ok_or(CoercionError::from(self))?;
1548 let y_axis = value
1549 .get("yAxis")
1550 .and_then(Point3d::from_kcl_val)
1551 .ok_or(CoercionError::from(self))?;
1552 let z_axis = x_axis.axes_cross_product(&y_axis);
1553
1554 if value.get("zAxis").is_some() {
1555 exec_state.warn(CompilationIssue::err(
1556 self.into(),
1557 "Object with a zAxis field is being coerced into a plane, but the zAxis is ignored.",
1558 ), annotations::WARN_IGNORED_Z_AXIS);
1559 }
1560
1561 let id = exec_state.mod_local.id_generator.next_uuid();
1562 let info = PlaneInfo {
1563 origin,
1564 x_axis: x_axis.normalize(),
1565 y_axis: y_axis.normalize(),
1566 z_axis: z_axis.normalize(),
1567 };
1568 let plane = Plane {
1569 id,
1570 artifact_id: id.into(),
1571 object_id: None,
1572 kind: PlaneKind::from(&info),
1573 info,
1574 meta: meta.clone(),
1575 };
1576
1577 Ok(KclValue::Plane { value: Box::new(plane) })
1578 }
1579 _ => Err(self.into()),
1580 }
1581 }
1582 PrimitiveType::Face => match self {
1583 KclValue::Face { .. } => Ok(self.clone()),
1584 _ => Err(self.into()),
1585 },
1586 PrimitiveType::Helix => match self {
1587 KclValue::Helix { .. } => Ok(self.clone()),
1588 _ => Err(self.into()),
1589 },
1590 PrimitiveType::Edge => match self {
1591 KclValue::Uuid { .. } => Ok(self.clone()),
1592 KclValue::TagIdentifier { .. } => Ok(self.clone()),
1593 _ => Err(self.into()),
1594 },
1595 PrimitiveType::BoundedEdge => match self {
1596 KclValue::BoundedEdge { .. } => Ok(self.clone()),
1597 _ => Err(self.into()),
1598 },
1599 PrimitiveType::TaggedEdge => match self {
1600 KclValue::TagIdentifier { .. } => Ok(self.clone()),
1601 _ => Err(self.into()),
1602 },
1603 PrimitiveType::TaggedFace => match self {
1604 KclValue::TagIdentifier { .. } => Ok(self.clone()),
1605 s @ KclValue::String { value, .. } if ["start", "end", "START", "END"].contains(&&**value) => {
1606 Ok(s.clone())
1607 }
1608 _ => Err(self.into()),
1609 },
1610 PrimitiveType::Axis2d => match self {
1611 KclValue::Object {
1612 value: values, meta, ..
1613 } => {
1614 if values
1615 .get("origin")
1616 .ok_or(CoercionError::from(self))?
1617 .has_type(&RuntimeType::point2d())
1618 && values
1619 .get("direction")
1620 .ok_or(CoercionError::from(self))?
1621 .has_type(&RuntimeType::point2d())
1622 {
1623 return Ok(self.clone());
1624 }
1625
1626 let origin = values.get("origin").ok_or(self.into()).and_then(|p| {
1627 p.coerce_to_array_type(&RuntimeType::length(), mode, ArrayLen::Known(2), exec_state, true)
1628 })?;
1629 let direction = values.get("direction").ok_or(self.into()).and_then(|p| {
1630 p.coerce_to_array_type(&RuntimeType::length(), mode, ArrayLen::Known(2), exec_state, true)
1631 })?;
1632
1633 Ok(KclValue::Object {
1634 value: [("origin".to_owned(), origin), ("direction".to_owned(), direction)].into(),
1635 meta: meta.clone(),
1636 constrainable: false,
1637 object_kind: Default::default(),
1638 })
1639 }
1640 _ => Err(self.into()),
1641 },
1642 PrimitiveType::Axis3d => match self {
1643 KclValue::Object {
1644 value: values, meta, ..
1645 } => {
1646 if values
1647 .get("origin")
1648 .ok_or(CoercionError::from(self))?
1649 .has_type(&RuntimeType::point3d())
1650 && values
1651 .get("direction")
1652 .ok_or(CoercionError::from(self))?
1653 .has_type(&RuntimeType::point3d())
1654 {
1655 return Ok(self.clone());
1656 }
1657
1658 let origin = values.get("origin").ok_or(self.into()).and_then(|p| {
1659 p.coerce_to_array_type(&RuntimeType::length(), mode, ArrayLen::Known(3), exec_state, true)
1660 })?;
1661 let direction = values.get("direction").ok_or(self.into()).and_then(|p| {
1662 p.coerce_to_array_type(&RuntimeType::length(), mode, ArrayLen::Known(3), exec_state, true)
1663 })?;
1664
1665 Ok(KclValue::Object {
1666 value: [("origin".to_owned(), origin), ("direction".to_owned(), direction)].into(),
1667 meta: meta.clone(),
1668 constrainable: false,
1669 object_kind: Default::default(),
1670 })
1671 }
1672 _ => Err(self.into()),
1673 },
1674 PrimitiveType::ImportedGeometry => match self {
1675 KclValue::ImportedGeometry { .. } => Ok(self.clone()),
1676 _ => Err(self.into()),
1677 },
1678 PrimitiveType::Function => match self {
1679 KclValue::Function { .. } => Ok(self.clone()),
1680 _ => Err(self.into()),
1681 },
1682 PrimitiveType::TagDecl => match self {
1683 KclValue::TagDeclarator { .. } => Ok(self.clone()),
1684 _ => Err(self.into()),
1685 },
1686 }
1687 }
1688
1689 fn coerce_to_array_type(
1690 &self,
1691 ty: &RuntimeType,
1692 mode: CoercionMode,
1693 len: ArrayLen,
1694 exec_state: &mut ExecState,
1695 allow_shrink: bool,
1696 ) -> Result<KclValue, CoercionError> {
1697 match self {
1698 KclValue::HomArray { value, ty: aty, .. } => {
1699 let satisfied_len = len.satisfied(value.len(), allow_shrink);
1700
1701 if aty.subtype(ty) {
1702 return satisfied_len
1709 .map(|len| KclValue::HomArray {
1710 value: value[..len].to_vec(),
1711 ty: aty.clone(),
1712 })
1713 .ok_or(self.into());
1714 }
1715
1716 if let Some(satisfied_len) = satisfied_len {
1718 let value_result = value
1719 .iter()
1720 .take(satisfied_len)
1721 .map(|v| v.coerce(ty, mode, exec_state))
1722 .collect::<Result<Vec<_>, _>>();
1723
1724 if let Ok(value) = value_result {
1725 return Ok(KclValue::HomArray { value, ty: ty.clone() });
1727 }
1728 }
1729
1730 let mut values = Vec::new();
1732 for item in value {
1733 if let KclValue::HomArray { value: inner_value, .. } = item {
1734 for item in inner_value {
1736 values.push(item.coerce(ty, mode, exec_state)?);
1737 }
1738 } else {
1739 values.push(item.coerce(ty, mode, exec_state)?);
1740 }
1741 }
1742
1743 let len = len
1744 .satisfied(values.len(), allow_shrink)
1745 .ok_or(CoercionError::from(self))?;
1746
1747 if len > values.len() {
1748 let message = format!(
1749 "Internal: Expected coerced array length {len} to be less than or equal to original length {}",
1750 values.len()
1751 );
1752 exec_state.err(CompilationIssue::err(self.into(), message.clone()));
1753 #[cfg(debug_assertions)]
1754 panic!("{message}");
1755 }
1756 values.truncate(len);
1757
1758 Ok(KclValue::HomArray {
1759 value: values,
1760 ty: ty.clone(),
1761 })
1762 }
1763 KclValue::Tuple { value, .. } => {
1764 let len = len
1765 .satisfied(value.len(), allow_shrink)
1766 .ok_or(CoercionError::from(self))?;
1767 let value = value
1768 .iter()
1769 .map(|item| item.coerce(ty, mode, exec_state))
1770 .take(len)
1771 .collect::<Result<Vec<_>, _>>()?;
1772
1773 Ok(KclValue::HomArray { value, ty: ty.clone() })
1774 }
1775 KclValue::KclNone { .. } if len.satisfied(0, false).is_some() => Ok(KclValue::HomArray {
1776 value: Vec::new(),
1777 ty: ty.clone(),
1778 }),
1779 _ if len.satisfied(1, false).is_some() => self.coerce(ty, mode, exec_state),
1780 _ => Err(self.into()),
1781 }
1782 }
1783
1784 fn coerce_to_tuple_type(
1785 &self,
1786 tys: &[RuntimeType],
1787 mode: CoercionMode,
1788 exec_state: &mut ExecState,
1789 ) -> Result<KclValue, CoercionError> {
1790 match self {
1791 KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } if value.len() == tys.len() => {
1792 let mut result = Vec::new();
1793 for (i, t) in tys.iter().enumerate() {
1794 result.push(value[i].coerce(t, mode, exec_state)?);
1795 }
1796
1797 Ok(KclValue::Tuple {
1798 value: result,
1799 meta: Vec::new(),
1800 })
1801 }
1802 KclValue::KclNone { meta, .. } if tys.is_empty() => Ok(KclValue::Tuple {
1803 value: Vec::new(),
1804 meta: meta.clone(),
1805 }),
1806 _ if tys.len() == 1 => self.coerce(&tys[0], mode, exec_state),
1807 _ => Err(self.into()),
1808 }
1809 }
1810
1811 fn coerce_to_union_type(
1812 &self,
1813 tys: &[RuntimeType],
1814 mode: CoercionMode,
1815 exec_state: &mut ExecState,
1816 ) -> Result<KclValue, CoercionError> {
1817 if mode.project_enums() {
1823 let exact = mode.without_projection();
1824 for t in tys {
1825 if let Ok(v) = self.coerce(t, exact, exec_state) {
1826 return Ok(v);
1827 }
1828 }
1829 }
1830
1831 for t in tys {
1832 if let Ok(v) = self.coerce(t, mode, exec_state) {
1833 return Ok(v);
1834 }
1835 }
1836
1837 Err(self.into())
1838 }
1839
1840 fn coerce_to_object_type(
1841 &self,
1842 tys: &[(String, RuntimeType)],
1843 constrainable: bool,
1844 _mode: CoercionMode,
1845 _exec_state: &mut ExecState,
1846 ) -> Result<KclValue, CoercionError> {
1847 match self {
1848 KclValue::Object { value, meta, .. } => {
1849 for (s, t) in tys {
1850 if !value.get(s).ok_or(CoercionError::from(self))?.has_type(t) {
1852 return Err(self.into());
1853 }
1854 }
1855 Ok(KclValue::Object {
1857 value: value.clone(),
1858 meta: meta.clone(),
1859 constrainable,
1862 object_kind: Default::default(),
1863 })
1864 }
1865 KclValue::KclNone { meta, .. } if tys.is_empty() => Ok(KclValue::Object {
1866 value: HashMap::new(),
1867 meta: meta.clone(),
1868 constrainable,
1869 object_kind: Default::default(),
1870 }),
1871 _ => Err(self.into()),
1872 }
1873 }
1874
1875 pub fn principal_type(&self) -> Option<RuntimeType> {
1876 match self {
1877 KclValue::Bool { .. } => Some(RuntimeType::Primitive(PrimitiveType::Boolean)),
1878 KclValue::Number { ty, .. } => Some(RuntimeType::Primitive(PrimitiveType::Number(*ty))),
1879 KclValue::String { .. } => Some(RuntimeType::Primitive(PrimitiveType::String)),
1880 KclValue::Enum { value } => Some(RuntimeType::Enum(value.enum_id().clone())),
1881 KclValue::SketchVar { value, .. } => Some(RuntimeType::Primitive(PrimitiveType::Number(value.ty))),
1882 KclValue::SketchConstraint { .. } => Some(RuntimeType::Primitive(PrimitiveType::Constraint)),
1883 KclValue::Object {
1884 value, constrainable, ..
1885 } => {
1886 let properties = value
1887 .iter()
1888 .map(|(k, v)| v.principal_type().map(|t| (k.clone(), t)))
1889 .collect::<Option<Vec<_>>>()?;
1890 Some(RuntimeType::Object(properties, *constrainable))
1891 }
1892 KclValue::GdtAnnotation { .. } => Some(RuntimeType::Primitive(PrimitiveType::GdtAnnotation)),
1893 KclValue::Plane { .. } => Some(RuntimeType::Primitive(PrimitiveType::Plane)),
1894 KclValue::Sketch { .. } => Some(RuntimeType::Primitive(PrimitiveType::Sketch)),
1895 KclValue::Solid { .. } => Some(RuntimeType::Primitive(PrimitiveType::Solid)),
1896 KclValue::Face { .. } => Some(RuntimeType::Primitive(PrimitiveType::Face)),
1897 KclValue::Segment { .. } => Some(RuntimeType::Primitive(PrimitiveType::Segment)),
1898 KclValue::Helix { .. } => Some(RuntimeType::Primitive(PrimitiveType::Helix)),
1899 KclValue::ImportedGeometry(..) => Some(RuntimeType::Primitive(PrimitiveType::ImportedGeometry)),
1900 KclValue::Tuple { value, .. } => Some(RuntimeType::Tuple(
1901 value.iter().map(|v| v.principal_type()).collect::<Option<Vec<_>>>()?,
1902 )),
1903 KclValue::HomArray { ty, value, .. } => {
1904 Some(RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Known(value.len())))
1905 }
1906 KclValue::TagIdentifier(_) => Some(RuntimeType::Primitive(PrimitiveType::TaggedEdge)),
1907 KclValue::TagDeclarator(_) => Some(RuntimeType::Primitive(PrimitiveType::TagDecl)),
1908 KclValue::Uuid { .. } => Some(RuntimeType::Primitive(PrimitiveType::Edge)),
1909 KclValue::Function { .. } => Some(RuntimeType::Primitive(PrimitiveType::Function)),
1910 KclValue::KclNone { .. } => Some(RuntimeType::Primitive(PrimitiveType::None)),
1911 KclValue::Module { .. } | KclValue::Type { .. } => None,
1912 KclValue::BoundedEdge { .. } => Some(RuntimeType::Primitive(PrimitiveType::BoundedEdge)),
1913 }
1914 }
1915
1916 pub fn principal_type_string(&self) -> String {
1917 if let Some(ty) = self.principal_type() {
1918 return format!("`{ty}`");
1919 }
1920
1921 match self {
1922 KclValue::Module { .. } => "module",
1923 KclValue::KclNone { .. } => "none",
1924 KclValue::Type { .. } => "type",
1925 _ => {
1926 debug_assert!(false);
1927 "<unexpected type>"
1928 }
1929 }
1930 .to_owned()
1931 }
1932}
1933
1934#[cfg(test)]
1935mod test {
1936 use std::sync::Arc;
1937
1938 use super::*;
1939 use crate::ModuleId;
1940 use crate::execution::ExecTestResults;
1941 use crate::execution::kcl_value::EnumTypeDef;
1942 use crate::execution::kcl_value::EnumValue;
1943 use crate::execution::parse_execute;
1944
1945 async fn new_exec_state() -> (crate::ExecutorContext, ExecState) {
1946 let ctx = crate::ExecutorContext::new_mock(None).await;
1947 let exec_state = ExecState::new(&ctx);
1948 (ctx, exec_state)
1949 }
1950
1951 fn values(exec_state: &mut ExecState) -> Vec<KclValue> {
1952 vec![
1953 KclValue::Bool {
1954 value: true,
1955 meta: Vec::new(),
1956 },
1957 KclValue::Number {
1958 value: 1.0,
1959 ty: NumericType::count(),
1960 meta: Vec::new(),
1961 },
1962 KclValue::String {
1963 value: "hello".to_owned(),
1964 meta: Vec::new(),
1965 },
1966 KclValue::Tuple {
1967 value: Vec::new(),
1968 meta: Vec::new(),
1969 },
1970 KclValue::HomArray {
1971 value: Vec::new(),
1972 ty: RuntimeType::solid(),
1973 },
1974 KclValue::Object {
1975 value: crate::execution::KclObjectFields::new(),
1976 meta: Vec::new(),
1977 constrainable: false,
1978 object_kind: Default::default(),
1979 },
1980 KclValue::TagIdentifier(Box::new("foo".parse().unwrap())),
1981 KclValue::TagDeclarator(Box::new(crate::parsing::ast::types::TagDeclarator::new("foo"))),
1982 KclValue::Plane {
1983 value: Box::new(
1984 Plane::from_plane_data_skipping_engine(crate::std::sketch::PlaneData::XY, exec_state).unwrap(),
1985 ),
1986 },
1987 KclValue::ImportedGeometry(crate::execution::ImportedGeometry::new(
1989 uuid::Uuid::nil(),
1990 Vec::new(),
1991 Vec::new(),
1992 )),
1993 ]
1995 }
1996
1997 #[track_caller]
1998 fn assert_coerce_results(
1999 value: &KclValue,
2000 super_type: &RuntimeType,
2001 expected_value: &KclValue,
2002 exec_state: &mut ExecState,
2003 ) {
2004 let is_subtype = value == expected_value;
2005 let actual = value.coerce(super_type, CoercionMode::implicit(), exec_state).unwrap();
2006 assert_eq!(&actual, expected_value);
2007 assert_eq!(
2008 is_subtype,
2009 value.principal_type().is_some() && value.principal_type().unwrap().subtype(super_type),
2010 "{:?} <: {super_type:?} should be {is_subtype}",
2011 value.principal_type().unwrap()
2012 );
2013 assert!(
2014 expected_value.principal_type().unwrap().subtype(super_type),
2015 "{} <: {super_type}",
2016 expected_value.principal_type().unwrap()
2017 )
2018 }
2019
2020 #[tokio::test(flavor = "multi_thread")]
2021 async fn coerce_idempotent() {
2022 let (ctx, mut exec_state) = new_exec_state().await;
2023 let values = values(&mut exec_state);
2024 for v in &values {
2025 let ty = v.principal_type().unwrap();
2027 assert_coerce_results(v, &ty, v, &mut exec_state);
2028
2029 let uty1 = RuntimeType::Union(vec![ty.clone()]);
2031 let uty2 = RuntimeType::Union(vec![ty.clone(), RuntimeType::Primitive(PrimitiveType::Boolean)]);
2032 assert_coerce_results(v, &uty1, v, &mut exec_state);
2033 assert_coerce_results(v, &uty2, v, &mut exec_state);
2034
2035 let aty = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::None);
2037 let aty1 = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Known(1));
2038 let aty0 = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Minimum(1));
2039
2040 match v {
2041 KclValue::HomArray { .. } => {
2042 assert_coerce_results(
2044 v,
2045 &aty,
2046 &KclValue::HomArray {
2047 value: vec![],
2048 ty: ty.clone(),
2049 },
2050 &mut exec_state,
2051 );
2052 v.coerce(&aty1, CoercionMode::implicit(), &mut exec_state).unwrap_err();
2055 v.coerce(&aty0, CoercionMode::implicit(), &mut exec_state).unwrap_err();
2058 }
2059 KclValue::Tuple { .. } => {}
2060 _ => {
2061 assert_coerce_results(v, &aty, v, &mut exec_state);
2062 assert_coerce_results(v, &aty1, v, &mut exec_state);
2063 assert_coerce_results(v, &aty0, v, &mut exec_state);
2064
2065 let tty = RuntimeType::Tuple(vec![ty.clone()]);
2067 assert_coerce_results(v, &tty, v, &mut exec_state);
2068 }
2069 }
2070 }
2071
2072 for v in &values[1..] {
2073 v.coerce(
2075 &RuntimeType::Primitive(PrimitiveType::Boolean),
2076 CoercionMode::implicit(),
2077 &mut exec_state,
2078 )
2079 .unwrap_err();
2080 }
2081 ctx.close().await;
2082 }
2083
2084 #[tokio::test(flavor = "multi_thread")]
2085 async fn coerce_none() {
2086 let (ctx, mut exec_state) = new_exec_state().await;
2087 let none = KclValue::KclNone {
2088 value: crate::parsing::ast::types::KclNone::new(),
2089 meta: Vec::new(),
2090 };
2091
2092 let aty = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::None);
2093 let aty0 = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::Known(0));
2094 let aty1 = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::Known(1));
2095 let aty1p = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::Minimum(1));
2096 assert_coerce_results(
2097 &none,
2098 &aty,
2099 &KclValue::HomArray {
2100 value: Vec::new(),
2101 ty: RuntimeType::solid(),
2102 },
2103 &mut exec_state,
2104 );
2105 assert_coerce_results(
2106 &none,
2107 &aty0,
2108 &KclValue::HomArray {
2109 value: Vec::new(),
2110 ty: RuntimeType::solid(),
2111 },
2112 &mut exec_state,
2113 );
2114 none.coerce(&aty1, CoercionMode::implicit(), &mut exec_state)
2115 .unwrap_err();
2116 none.coerce(&aty1p, CoercionMode::implicit(), &mut exec_state)
2117 .unwrap_err();
2118
2119 let tty = RuntimeType::Tuple(vec![]);
2120 let tty1 = RuntimeType::Tuple(vec![RuntimeType::solid()]);
2121 assert_coerce_results(
2122 &none,
2123 &tty,
2124 &KclValue::Tuple {
2125 value: Vec::new(),
2126 meta: Vec::new(),
2127 },
2128 &mut exec_state,
2129 );
2130 none.coerce(&tty1, CoercionMode::implicit(), &mut exec_state)
2131 .unwrap_err();
2132
2133 let oty = RuntimeType::Object(vec![], false);
2134 assert_coerce_results(
2135 &none,
2136 &oty,
2137 &KclValue::Object {
2138 value: HashMap::new(),
2139 meta: Vec::new(),
2140 constrainable: false,
2141 object_kind: Default::default(),
2142 },
2143 &mut exec_state,
2144 );
2145 ctx.close().await;
2146 }
2147
2148 #[tokio::test(flavor = "multi_thread")]
2149 async fn coerce_record() {
2150 let (ctx, mut exec_state) = new_exec_state().await;
2151
2152 let obj0 = KclValue::Object {
2153 value: HashMap::new(),
2154 meta: Vec::new(),
2155 constrainable: false,
2156 object_kind: Default::default(),
2157 };
2158 let obj1 = KclValue::Object {
2159 value: [(
2160 "foo".to_owned(),
2161 KclValue::Bool {
2162 value: true,
2163 meta: Vec::new(),
2164 },
2165 )]
2166 .into(),
2167 meta: Vec::new(),
2168 constrainable: false,
2169 object_kind: Default::default(),
2170 };
2171 let obj2 = KclValue::Object {
2172 value: [
2173 (
2174 "foo".to_owned(),
2175 KclValue::Bool {
2176 value: true,
2177 meta: Vec::new(),
2178 },
2179 ),
2180 (
2181 "bar".to_owned(),
2182 KclValue::Number {
2183 value: 0.0,
2184 ty: NumericType::count(),
2185 meta: Vec::new(),
2186 },
2187 ),
2188 (
2189 "baz".to_owned(),
2190 KclValue::Number {
2191 value: 42.0,
2192 ty: NumericType::count(),
2193 meta: Vec::new(),
2194 },
2195 ),
2196 ]
2197 .into(),
2198 meta: Vec::new(),
2199 constrainable: false,
2200 object_kind: Default::default(),
2201 };
2202
2203 let ty0 = RuntimeType::Object(vec![], false);
2204 assert_coerce_results(&obj0, &ty0, &obj0, &mut exec_state);
2205 assert_coerce_results(&obj1, &ty0, &obj1, &mut exec_state);
2206 assert_coerce_results(&obj2, &ty0, &obj2, &mut exec_state);
2207
2208 let ty1 = RuntimeType::Object(
2209 vec![("foo".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean))],
2210 false,
2211 );
2212 obj0.coerce(&ty1, CoercionMode::implicit(), &mut exec_state)
2213 .unwrap_err();
2214 assert_coerce_results(&obj1, &ty1, &obj1, &mut exec_state);
2215 assert_coerce_results(&obj2, &ty1, &obj2, &mut exec_state);
2216
2217 let ty2 = RuntimeType::Object(
2219 vec![
2220 (
2221 "bar".to_owned(),
2222 RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2223 ),
2224 ("foo".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean)),
2225 ],
2226 false,
2227 );
2228 obj0.coerce(&ty2, CoercionMode::implicit(), &mut exec_state)
2229 .unwrap_err();
2230 obj1.coerce(&ty2, CoercionMode::implicit(), &mut exec_state)
2231 .unwrap_err();
2232 assert_coerce_results(&obj2, &ty2, &obj2, &mut exec_state);
2233
2234 let tyq = RuntimeType::Object(
2236 vec![("qux".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean))],
2237 false,
2238 );
2239 obj0.coerce(&tyq, CoercionMode::implicit(), &mut exec_state)
2240 .unwrap_err();
2241 obj1.coerce(&tyq, CoercionMode::implicit(), &mut exec_state)
2242 .unwrap_err();
2243 obj2.coerce(&tyq, CoercionMode::implicit(), &mut exec_state)
2244 .unwrap_err();
2245
2246 let ty1 = RuntimeType::Object(
2248 vec![("bar".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean))],
2249 false,
2250 );
2251 obj2.coerce(&ty1, CoercionMode::implicit(), &mut exec_state)
2252 .unwrap_err();
2253 ctx.close().await;
2254 }
2255
2256 #[tokio::test(flavor = "multi_thread")]
2257 async fn coerce_array() {
2258 let (ctx, mut exec_state) = new_exec_state().await;
2259
2260 let hom_arr = KclValue::HomArray {
2261 value: vec![
2262 KclValue::Number {
2263 value: 0.0,
2264 ty: NumericType::count(),
2265 meta: Vec::new(),
2266 },
2267 KclValue::Number {
2268 value: 1.0,
2269 ty: NumericType::count(),
2270 meta: Vec::new(),
2271 },
2272 KclValue::Number {
2273 value: 2.0,
2274 ty: NumericType::count(),
2275 meta: Vec::new(),
2276 },
2277 KclValue::Number {
2278 value: 3.0,
2279 ty: NumericType::count(),
2280 meta: Vec::new(),
2281 },
2282 ],
2283 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2284 };
2285 let mixed1 = KclValue::Tuple {
2286 value: vec![
2287 KclValue::Number {
2288 value: 0.0,
2289 ty: NumericType::count(),
2290 meta: Vec::new(),
2291 },
2292 KclValue::Number {
2293 value: 1.0,
2294 ty: NumericType::count(),
2295 meta: Vec::new(),
2296 },
2297 ],
2298 meta: Vec::new(),
2299 };
2300 let mixed2 = KclValue::Tuple {
2301 value: vec![
2302 KclValue::Number {
2303 value: 0.0,
2304 ty: NumericType::count(),
2305 meta: Vec::new(),
2306 },
2307 KclValue::Bool {
2308 value: true,
2309 meta: Vec::new(),
2310 },
2311 ],
2312 meta: Vec::new(),
2313 };
2314
2315 let tyh = RuntimeType::Array(
2317 Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2318 ArrayLen::Known(4),
2319 );
2320 let tym1 = RuntimeType::Tuple(vec![
2321 RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2322 RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2323 ]);
2324 let tym2 = RuntimeType::Tuple(vec![
2325 RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2326 RuntimeType::Primitive(PrimitiveType::Boolean),
2327 ]);
2328 assert_coerce_results(&hom_arr, &tyh, &hom_arr, &mut exec_state);
2329 assert_coerce_results(&mixed1, &tym1, &mixed1, &mut exec_state);
2330 assert_coerce_results(&mixed2, &tym2, &mixed2, &mut exec_state);
2331 mixed1
2332 .coerce(&tym2, CoercionMode::implicit(), &mut exec_state)
2333 .unwrap_err();
2334 mixed2
2335 .coerce(&tym1, CoercionMode::implicit(), &mut exec_state)
2336 .unwrap_err();
2337
2338 let tyhn = RuntimeType::Array(
2340 Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2341 ArrayLen::None,
2342 );
2343 let tyh1 = RuntimeType::Array(
2344 Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2345 ArrayLen::Minimum(1),
2346 );
2347 let tyh3 = RuntimeType::Array(
2348 Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2349 ArrayLen::Known(3),
2350 );
2351 let tyhm3 = RuntimeType::Array(
2352 Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2353 ArrayLen::Minimum(3),
2354 );
2355 let tyhm5 = RuntimeType::Array(
2356 Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2357 ArrayLen::Minimum(5),
2358 );
2359 assert_coerce_results(&hom_arr, &tyhn, &hom_arr, &mut exec_state);
2360 assert_coerce_results(&hom_arr, &tyh1, &hom_arr, &mut exec_state);
2361 hom_arr
2362 .coerce(&tyh3, CoercionMode::implicit(), &mut exec_state)
2363 .unwrap_err();
2364 assert_coerce_results(&hom_arr, &tyhm3, &hom_arr, &mut exec_state);
2365 hom_arr
2366 .coerce(&tyhm5, CoercionMode::implicit(), &mut exec_state)
2367 .unwrap_err();
2368
2369 let hom_arr0 = KclValue::HomArray {
2370 value: vec![],
2371 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2372 };
2373 assert_coerce_results(&hom_arr0, &tyhn, &hom_arr0, &mut exec_state);
2374 hom_arr0
2375 .coerce(&tyh1, CoercionMode::implicit(), &mut exec_state)
2376 .unwrap_err();
2377 hom_arr0
2378 .coerce(&tyh3, CoercionMode::implicit(), &mut exec_state)
2379 .unwrap_err();
2380
2381 let tym1 = RuntimeType::Tuple(vec![
2384 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2385 RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2386 ]);
2387 let tym2 = RuntimeType::Tuple(vec![
2388 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2389 RuntimeType::Primitive(PrimitiveType::Boolean),
2390 ]);
2391 assert_coerce_results(&mixed1, &tym1, &mixed1, &mut exec_state);
2394 assert_coerce_results(&mixed2, &tym2, &mixed2, &mut exec_state);
2395
2396 let hom_arr_2 = KclValue::HomArray {
2398 value: vec![
2399 KclValue::Number {
2400 value: 0.0,
2401 ty: NumericType::count(),
2402 meta: Vec::new(),
2403 },
2404 KclValue::Number {
2405 value: 1.0,
2406 ty: NumericType::count(),
2407 meta: Vec::new(),
2408 },
2409 ],
2410 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2411 };
2412 let mixed0 = KclValue::Tuple {
2413 value: vec![],
2414 meta: Vec::new(),
2415 };
2416 assert_coerce_results(&mixed1, &tyhn, &hom_arr_2, &mut exec_state);
2417 assert_coerce_results(&mixed1, &tyh1, &hom_arr_2, &mut exec_state);
2418 assert_coerce_results(&mixed0, &tyhn, &hom_arr0, &mut exec_state);
2419 mixed0
2420 .coerce(&tyh, CoercionMode::implicit(), &mut exec_state)
2421 .unwrap_err();
2422 mixed0
2423 .coerce(&tyh1, CoercionMode::implicit(), &mut exec_state)
2424 .unwrap_err();
2425
2426 assert_coerce_results(&hom_arr_2, &tym1, &mixed1, &mut exec_state);
2428 hom_arr
2429 .coerce(&tym1, CoercionMode::implicit(), &mut exec_state)
2430 .unwrap_err();
2431 hom_arr_2
2432 .coerce(&tym2, CoercionMode::implicit(), &mut exec_state)
2433 .unwrap_err();
2434
2435 mixed0
2436 .coerce(&tym1, CoercionMode::implicit(), &mut exec_state)
2437 .unwrap_err();
2438 mixed0
2439 .coerce(&tym2, CoercionMode::implicit(), &mut exec_state)
2440 .unwrap_err();
2441 ctx.close().await;
2442 }
2443
2444 #[tokio::test(flavor = "multi_thread")]
2445 async fn coerce_union() {
2446 let (ctx, mut exec_state) = new_exec_state().await;
2447
2448 assert!(RuntimeType::Union(vec![]).subtype(&RuntimeType::Union(vec![
2450 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2451 RuntimeType::Primitive(PrimitiveType::Boolean)
2452 ])));
2453 assert!(
2454 RuntimeType::Union(vec![RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any))]).subtype(
2455 &RuntimeType::Union(vec![
2456 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2457 RuntimeType::Primitive(PrimitiveType::Boolean)
2458 ])
2459 )
2460 );
2461 assert!(
2462 RuntimeType::Union(vec![
2463 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2464 RuntimeType::Primitive(PrimitiveType::Boolean)
2465 ])
2466 .subtype(&RuntimeType::Union(vec![
2467 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2468 RuntimeType::Primitive(PrimitiveType::Boolean)
2469 ]))
2470 );
2471
2472 let count = KclValue::Number {
2474 value: 1.0,
2475 ty: NumericType::count(),
2476 meta: Vec::new(),
2477 };
2478
2479 let tya = RuntimeType::Union(vec![RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any))]);
2480 let tya2 = RuntimeType::Union(vec![
2481 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2482 RuntimeType::Primitive(PrimitiveType::Boolean),
2483 ]);
2484 assert_coerce_results(&count, &tya, &count, &mut exec_state);
2485 assert_coerce_results(&count, &tya2, &count, &mut exec_state);
2486
2487 let tyb = RuntimeType::Union(vec![RuntimeType::Primitive(PrimitiveType::Boolean)]);
2489 let tyb2 = RuntimeType::Union(vec![
2490 RuntimeType::Primitive(PrimitiveType::Boolean),
2491 RuntimeType::Primitive(PrimitiveType::String),
2492 ]);
2493 count
2494 .coerce(&tyb, CoercionMode::implicit(), &mut exec_state)
2495 .unwrap_err();
2496 count
2497 .coerce(&tyb2, CoercionMode::implicit(), &mut exec_state)
2498 .unwrap_err();
2499 ctx.close().await;
2500 }
2501
2502 #[test]
2503 fn union_subtyping_uses_member_subtyping() {
2504 let tagged_edge = RuntimeType::Primitive(PrimitiveType::TaggedEdge);
2505 let edge = RuntimeType::Primitive(PrimitiveType::Edge);
2506 let string = RuntimeType::string();
2507 let boolean = RuntimeType::bool();
2508
2509 let tagged_edge_or_string = RuntimeType::Union(vec![tagged_edge.clone(), string.clone()]);
2510 let edge_or_string = RuntimeType::Union(vec![edge.clone(), string.clone()]);
2511
2512 assert!(tagged_edge_or_string.subtype(&edge_or_string));
2514 assert!(!edge_or_string.subtype(&tagged_edge_or_string));
2516
2517 assert!(RuntimeType::Union(vec![tagged_edge.clone(), edge.clone()]).subtype(&edge));
2519 assert!(!RuntimeType::Union(vec![tagged_edge, boolean]).subtype(&edge));
2521
2522 assert!(RuntimeType::Union(vec![]).subtype(&string));
2524 }
2525
2526 #[test]
2527 fn nested_union_subtyping_is_associative_and_recursive() {
2528 let tagged_edge = RuntimeType::Primitive(PrimitiveType::TaggedEdge);
2529 let edge = RuntimeType::Primitive(PrimitiveType::Edge);
2530 let string = RuntimeType::string();
2531 let boolean = RuntimeType::bool();
2532
2533 let left_associative = RuntimeType::Union(vec![
2534 RuntimeType::Union(vec![string.clone(), boolean.clone()]),
2535 edge.clone(),
2536 ]);
2537 let right_associative =
2538 RuntimeType::Union(vec![string, RuntimeType::Union(vec![boolean.clone(), edge.clone()])]);
2539
2540 assert!(left_associative.subtype(&right_associative));
2542 assert!(right_associative.subtype(&left_associative));
2544
2545 let nested_edges = RuntimeType::Union(vec![
2546 RuntimeType::Union(vec![tagged_edge.clone(), edge.clone()]),
2547 tagged_edge.clone(),
2548 ]);
2549 assert!(nested_edges.subtype(&edge));
2551
2552 let nested_with_bool = RuntimeType::Union(vec![RuntimeType::Union(vec![tagged_edge, boolean]), edge.clone()]);
2553 assert!(!nested_with_bool.subtype(&edge));
2555 }
2556
2557 fn enum_ty(module_id: u32, name: &str) -> RuntimeType {
2558 RuntimeType::Enum(EnumTypeId::new(ModuleId::from_usize(module_id as usize), name))
2559 }
2560
2561 fn enum_def(module_id: u32, name: &str, variants: &[&str]) -> Arc<EnumTypeDef> {
2564 Arc::new(
2565 EnumTypeDef::new(
2566 EnumTypeId::new(ModuleId::from_usize(module_id as usize), name),
2567 variants.iter().map(|v| (*v).to_owned()).collect(),
2568 )
2569 .unwrap(),
2570 )
2571 }
2572
2573 #[test]
2574 fn enum_subtyping_is_nominal() {
2575 let color = enum_ty(0, "Color");
2576 let shape = enum_ty(0, "Shape");
2577
2578 assert!(color.subtype(&color));
2581 assert!(!color.subtype(&shape));
2583 assert!(!shape.subtype(&color));
2584 }
2585
2586 #[test]
2587 fn enum_identity_is_module_plus_declared_name() {
2588 assert!(!enum_ty(0, "Color").subtype(&enum_ty(1, "Color")));
2590 assert!(enum_ty(1, "Color").subtype(&enum_ty(1, "Color")));
2593 }
2594
2595 #[test]
2596 fn enum_participates_in_the_general_type_rules() {
2597 let color = enum_ty(0, "Color");
2598
2599 assert!(color.subtype(&RuntimeType::any()));
2601 assert!(RuntimeType::never().subtype(&color));
2602 assert!(!color.subtype(&RuntimeType::never()));
2603
2604 assert!(color.subtype(&RuntimeType::Union(vec![color.clone(), RuntimeType::string()])));
2607 assert!(!color.subtype(&RuntimeType::Union(vec![RuntimeType::string(), enum_ty(0, "Shape")])));
2608 assert!(color.subtype(&RuntimeType::Array(Box::new(color.clone()), ArrayLen::Known(1))));
2609 assert!(RuntimeType::Array(Box::new(color.clone()), ArrayLen::Known(1)).subtype(&color));
2610
2611 assert!(!color.subtype(&RuntimeType::string()));
2613 assert!(!RuntimeType::string().subtype(&color));
2614 }
2615
2616 #[test]
2617 fn enum_values_report_their_own_type() {
2618 let red = KclValue::Enum {
2619 value: Box::new(EnumValue::new(enum_def(0, "Color", &["Red"]), "Red", Vec::new())),
2620 };
2621
2622 assert_eq!(red.principal_type(), Some(enum_ty(0, "Color")));
2623 assert!(red.has_type(&enum_ty(0, "Color")));
2624 assert!(!red.has_type(&enum_ty(0, "Shape")));
2626 assert!(!red.has_type(&RuntimeType::string()));
2627 }
2628
2629 #[test]
2630 fn enum_types_display_by_declared_name() {
2631 let color = enum_ty(0, "Color");
2632
2633 assert_eq!(color.to_string(), "Color");
2634 assert_eq!(color.human_friendly_type(), "Color");
2635 assert_eq!(
2636 RuntimeType::Array(Box::new(color), ArrayLen::Minimum(1)).human_friendly_type(),
2637 "one or more `Color` values"
2638 );
2639 }
2640
2641 #[tokio::test(flavor = "multi_thread")]
2644 async fn from_alias_resolves_a_declared_enum_to_its_nominal_type() {
2645 let mut exec_state = parse_execute("x = 1").await.unwrap().exec_state;
2648 let id = EnumTypeId::new(ModuleId::default(), "Color");
2649 let source_range = SourceRange::default();
2650
2651 exec_state.mut_stack().push_new_root_env(true).unwrap();
2653 exec_state
2654 .mut_stack()
2655 .add(
2656 format!("{}Color", memory::TYPE_PREFIX),
2657 KclValue::Type {
2658 value: TypeDef::Enum(Arc::new(EnumTypeDef::new(id.clone(), vec!["Red".to_owned()]).unwrap())),
2659 experimental: false,
2660 meta: vec![],
2661 },
2662 source_range,
2663 )
2664 .unwrap();
2665
2666 assert_eq!(
2667 RuntimeType::from_alias("Color", &mut exec_state, source_range, false).unwrap(),
2668 RuntimeType::Enum(id)
2669 );
2670 RuntimeType::from_alias("Shape", &mut exec_state, source_range, false).unwrap_err();
2672 }
2673
2674 #[tokio::test(flavor = "multi_thread")]
2675 async fn enum_coercion_requires_the_same_declaration() {
2676 let (ctx, mut exec_state) = new_exec_state().await;
2677 let red = KclValue::Enum {
2678 value: Box::new(EnumValue::new(enum_def(0, "Color", &["Red"]), "Red", Vec::new())),
2679 };
2680
2681 assert_eq!(
2683 red.coerce(&enum_ty(0, "Color"), CoercionMode::implicit(), &mut exec_state)
2684 .unwrap(),
2685 red
2686 );
2687 red.coerce(&enum_ty(0, "Shape"), CoercionMode::implicit(), &mut exec_state)
2690 .unwrap_err();
2691 red.coerce(&enum_ty(1, "Color"), CoercionMode::implicit(), &mut exec_state)
2692 .unwrap_err();
2693 red.coerce(&RuntimeType::string(), CoercionMode::implicit(), &mut exec_state)
2694 .unwrap_err();
2695 let string = KclValue::String {
2697 value: "Red".to_owned(),
2698 meta: Vec::new(),
2699 };
2700 string
2701 .coerce(&enum_ty(0, "Color"), CoercionMode::implicit(), &mut exec_state)
2702 .unwrap_err();
2703
2704 ctx.close().await;
2705 }
2706
2707 fn enum_value(module_id: u32, name: &str, variants: &[&str], variant: &str) -> KclValue {
2708 KclValue::Enum {
2709 value: Box::new(EnumValue::new(enum_def(module_id, name, variants), variant, Vec::new())),
2710 }
2711 }
2712
2713 fn string_value(value: &str) -> KclValue {
2714 KclValue::String {
2715 value: value.to_owned(),
2716 meta: Vec::new(),
2717 }
2718 }
2719
2720 #[tokio::test(flavor = "multi_thread")]
2727 async fn enum_projects_by_target_shape() {
2728 let (ctx, mut exec_state) = new_exec_state().await;
2729 let variants = &["Red", "Green"];
2730 let red = enum_value(0, "Color", variants, "Red");
2731 let green = enum_value(0, "Color", variants, "Green");
2732 let color = enum_ty(0, "Color");
2733 let string = RuntimeType::string();
2734 let strings = RuntimeType::Array(Box::new(string.clone()), ArrayLen::None);
2735 let array = |value: Vec<KclValue>, ty: RuntimeType| KclValue::HomArray { value, ty };
2736 let tuple = |value: Vec<KclValue>| KclValue::Tuple {
2737 value,
2738 meta: Vec::new(),
2739 };
2740
2741 #[allow(clippy::type_complexity)]
2742 let rows: Vec<(&str, KclValue, RuntimeType, Option<KclValue>, Option<KclValue>)> = vec![
2743 (
2744 "a bare enum",
2745 red.clone(),
2746 string.clone(),
2747 Some(string_value("Red")),
2748 None,
2749 ),
2750 (
2751 "an array, element by element",
2752 array(vec![red.clone(), green.clone()], color.clone()),
2753 strings.clone(),
2754 Some(array(vec![string_value("Red"), string_value("Green")], string.clone())),
2755 None,
2756 ),
2757 (
2758 "an array of arrays, so more than one level down",
2759 array(vec![array(vec![green.clone()], RuntimeType::any())], RuntimeType::any()),
2760 RuntimeType::Array(Box::new(strings.clone()), ArrayLen::None),
2761 Some(array(
2762 vec![array(vec![string_value("Green")], string.clone())],
2763 strings.clone(),
2764 )),
2765 None,
2766 ),
2767 (
2768 "a tuple, positionally, beside a value that needs nothing done",
2770 tuple(vec![red.clone(), string_value("plain")]),
2771 RuntimeType::Tuple(vec![string.clone(), string.clone()]),
2772 Some(tuple(vec![string_value("Red"), string_value("plain")])),
2773 None,
2774 ),
2775 (
2776 "a one-element array against a bare string",
2779 array(vec![red.clone()], RuntimeType::any()),
2780 string.clone(),
2781 Some(string_value("Red")),
2782 None,
2783 ),
2784 (
2785 "an object field, which projects nothing",
2791 KclValue::Object {
2792 value: HashMap::from([("c".to_owned(), red.clone())]),
2793 constrainable: false,
2794 object_kind: Default::default(),
2795 meta: Vec::new(),
2796 },
2797 RuntimeType::Object(vec![("c".to_owned(), string.clone())], false),
2798 None,
2799 None,
2800 ),
2801 (
2802 "its own type, which is a check rather than a conversion",
2803 red.clone(),
2804 color.clone(),
2805 Some(red.clone()),
2806 Some(red.clone()),
2807 ),
2808 (
2809 "another declaration, which projection is not a way around",
2810 red.clone(),
2811 enum_ty(0, "Shade"),
2812 None,
2813 None,
2814 ),
2815 ];
2816
2817 for (case, value, target, explicit, implicit) in rows {
2818 assert_eq!(
2819 value.coerce(&target, CoercionMode::explicit(), &mut exec_state).ok(),
2820 explicit,
2821 "explicit mode, case: {case}"
2822 );
2823 assert_eq!(
2824 value.coerce(&target, CoercionMode::implicit(), &mut exec_state).ok(),
2825 implicit,
2826 "implicit mode, case: {case}"
2827 );
2828 }
2829
2830 ctx.close().await;
2831 }
2832
2833 #[tokio::test(flavor = "multi_thread")]
2838 async fn enum_projection_ignores_the_order_a_union_was_written_in() {
2839 let (ctx, mut exec_state) = new_exec_state().await;
2840 let red = enum_value(0, "Color", &["Red"], "Red");
2841 let string = RuntimeType::string();
2842 let color = enum_ty(0, "Color");
2843 let shade = enum_ty(0, "Shade");
2844
2845 let rows: Vec<(&str, Vec<RuntimeType>, Option<KclValue>)> = vec![
2846 ("the enum first", vec![color.clone(), string.clone()], Some(red.clone())),
2847 ("the enum last", vec![string.clone(), color.clone()], Some(red.clone())),
2848 (
2849 "no member accepts an enum, so projection is what satisfies it",
2850 vec![RuntimeType::bool(), string.clone()],
2851 Some(string_value("Red")),
2852 ),
2853 (
2854 "a different enum is not a match, so this projects too",
2855 vec![shade.clone(), string.clone()],
2856 Some(string_value("Red")),
2857 ),
2858 (
2859 "a different enum with no string member is unsatisfiable",
2860 vec![shade, RuntimeType::bool()],
2861 None,
2862 ),
2863 ];
2864
2865 for (case, tys, expected) in rows {
2866 let union = RuntimeType::Union(tys);
2867 assert_eq!(
2868 red.coerce(&union, CoercionMode::explicit(), &mut exec_state).ok(),
2869 expected,
2870 "case: {case} ({union})"
2871 );
2872 }
2873
2874 ctx.close().await;
2875 }
2876
2877 #[tokio::test(flavor = "multi_thread")]
2881 async fn enum_projection_to_a_number_explains_itself() {
2882 let (ctx, mut exec_state) = new_exec_state().await;
2883 let red = enum_value(0, "Color", &["Red"], "Red");
2884 let message = "Cannot project enum `Color` to a number. An enum projects to `string`; projecting to a number is not supported yet.";
2885
2886 for (case, mode, expected) in [
2887 ("explicit", CoercionMode::explicit(), Some(message)),
2888 ("implicit", CoercionMode::implicit(), None),
2889 ] {
2890 let err = red.coerce(&RuntimeType::count(), mode, &mut exec_state).unwrap_err();
2891 assert_eq!(err.message.as_deref(), expected, "case: {case}");
2892 }
2893
2894 ctx.close().await;
2895 }
2896
2897 #[tokio::test(flavor = "multi_thread")]
2898 async fn never_is_bottom_and_uninhabited() {
2899 let (ctx, mut exec_state) = new_exec_state().await;
2900 let never = RuntimeType::never();
2901 let string = RuntimeType::string();
2902
2903 for ty in [
2904 RuntimeType::any(),
2905 string.clone(),
2906 RuntimeType::Array(Box::new(string.clone()), ArrayLen::None),
2907 RuntimeType::Tuple(vec![string.clone()]),
2908 RuntimeType::Object(vec![("value".to_owned(), string.clone())], false),
2909 RuntimeType::Union(vec![string.clone(), RuntimeType::bool()]),
2910 ] {
2911 assert!(never.subtype(&ty), "`never` should be a subtype of {ty}");
2912 }
2913
2914 assert!(!string.subtype(&never));
2915 assert!(RuntimeType::Union(vec![never.clone(), string.clone()]).subtype(&string));
2916
2917 for value in values(&mut exec_state) {
2918 value
2919 .coerce(&never, CoercionMode::implicit(), &mut exec_state)
2920 .unwrap_err();
2921 }
2922 ctx.close().await;
2923 }
2924
2925 #[tokio::test(flavor = "multi_thread")]
2926 async fn coerce_axes() {
2927 let (ctx, mut exec_state) = new_exec_state().await;
2928
2929 assert!(RuntimeType::Primitive(PrimitiveType::Axis2d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis2d)));
2931 assert!(RuntimeType::Primitive(PrimitiveType::Axis3d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis3d)));
2932 assert!(!RuntimeType::Primitive(PrimitiveType::Axis3d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis2d)));
2933 assert!(!RuntimeType::Primitive(PrimitiveType::Axis2d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis3d)));
2934
2935 let a2d = KclValue::Object {
2937 value: [
2938 (
2939 "origin".to_owned(),
2940 KclValue::HomArray {
2941 value: vec![
2942 KclValue::Number {
2943 value: 0.0,
2944 ty: NumericType::mm(),
2945 meta: Vec::new(),
2946 },
2947 KclValue::Number {
2948 value: 0.0,
2949 ty: NumericType::mm(),
2950 meta: Vec::new(),
2951 },
2952 ],
2953 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
2954 },
2955 ),
2956 (
2957 "direction".to_owned(),
2958 KclValue::HomArray {
2959 value: vec![
2960 KclValue::Number {
2961 value: 1.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 .into(),
2976 meta: Vec::new(),
2977 constrainable: false,
2978 object_kind: Default::default(),
2979 };
2980 let a3d = KclValue::Object {
2981 value: [
2982 (
2983 "origin".to_owned(),
2984 KclValue::HomArray {
2985 value: vec![
2986 KclValue::Number {
2987 value: 0.0,
2988 ty: NumericType::mm(),
2989 meta: Vec::new(),
2990 },
2991 KclValue::Number {
2992 value: 0.0,
2993 ty: NumericType::mm(),
2994 meta: Vec::new(),
2995 },
2996 KclValue::Number {
2997 value: 0.0,
2998 ty: NumericType::mm(),
2999 meta: Vec::new(),
3000 },
3001 ],
3002 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
3003 },
3004 ),
3005 (
3006 "direction".to_owned(),
3007 KclValue::HomArray {
3008 value: vec![
3009 KclValue::Number {
3010 value: 1.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 KclValue::Number {
3020 value: 1.0,
3021 ty: NumericType::mm(),
3022 meta: Vec::new(),
3023 },
3024 ],
3025 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
3026 },
3027 ),
3028 ]
3029 .into(),
3030 meta: Vec::new(),
3031 constrainable: false,
3032 object_kind: Default::default(),
3033 };
3034
3035 let ty2d = RuntimeType::Primitive(PrimitiveType::Axis2d);
3036 let ty3d = RuntimeType::Primitive(PrimitiveType::Axis3d);
3037
3038 assert_coerce_results(&a2d, &ty2d, &a2d, &mut exec_state);
3039 assert_coerce_results(&a3d, &ty3d, &a3d, &mut exec_state);
3040 assert_coerce_results(&a3d, &ty2d, &a2d, &mut exec_state);
3041 a2d.coerce(&ty3d, CoercionMode::implicit(), &mut exec_state)
3042 .unwrap_err();
3043 ctx.close().await;
3044 }
3045
3046 #[tokio::test(flavor = "multi_thread")]
3047 async fn coerce_numeric() {
3048 let (ctx, mut exec_state) = new_exec_state().await;
3049
3050 let count = KclValue::Number {
3051 value: 1.0,
3052 ty: NumericType::count(),
3053 meta: Vec::new(),
3054 };
3055 let mm = KclValue::Number {
3056 value: 1.0,
3057 ty: NumericType::mm(),
3058 meta: Vec::new(),
3059 };
3060 let inches = KclValue::Number {
3061 value: 1.0,
3062 ty: NumericType::Known(UnitType::Length(UnitLength::Inches)),
3063 meta: Vec::new(),
3064 };
3065 let rads = KclValue::Number {
3066 value: 1.0,
3067 ty: NumericType::Known(UnitType::Angle(UnitAngle::Radians)),
3068 meta: Vec::new(),
3069 };
3070 let default = KclValue::Number {
3071 value: 1.0,
3072 ty: NumericType::default(),
3073 meta: Vec::new(),
3074 };
3075 let any = KclValue::Number {
3076 value: 1.0,
3077 ty: NumericType::Any,
3078 meta: Vec::new(),
3079 };
3080 let unknown = KclValue::Number {
3081 value: 1.0,
3082 ty: NumericType::Unknown,
3083 meta: Vec::new(),
3084 };
3085
3086 assert_coerce_results(&count, &NumericType::count().into(), &count, &mut exec_state);
3088 assert_coerce_results(&mm, &NumericType::mm().into(), &mm, &mut exec_state);
3089 assert_coerce_results(&any, &NumericType::Any.into(), &any, &mut exec_state);
3090 assert_coerce_results(&unknown, &NumericType::Unknown.into(), &unknown, &mut exec_state);
3091 assert_coerce_results(&default, &NumericType::default().into(), &default, &mut exec_state);
3092
3093 assert_coerce_results(&count, &NumericType::Any.into(), &count, &mut exec_state);
3094 assert_coerce_results(&mm, &NumericType::Any.into(), &mm, &mut exec_state);
3095 assert_coerce_results(&unknown, &NumericType::Any.into(), &unknown, &mut exec_state);
3096 assert_coerce_results(&default, &NumericType::Any.into(), &default, &mut exec_state);
3097
3098 assert_eq!(
3099 default
3100 .coerce(
3101 &NumericType::Default {
3102 len: UnitLength::Yards,
3103 angle: UnitAngle::Degrees,
3104 }
3105 .into(),
3106 CoercionMode::implicit(),
3107 &mut exec_state
3108 )
3109 .unwrap(),
3110 default
3111 );
3112
3113 count
3115 .coerce(&NumericType::mm().into(), CoercionMode::implicit(), &mut exec_state)
3116 .unwrap_err();
3117 mm.coerce(&NumericType::count().into(), CoercionMode::implicit(), &mut exec_state)
3118 .unwrap_err();
3119 unknown
3120 .coerce(&NumericType::mm().into(), CoercionMode::implicit(), &mut exec_state)
3121 .unwrap_err();
3122 unknown
3123 .coerce(
3124 &NumericType::default().into(),
3125 CoercionMode::implicit(),
3126 &mut exec_state,
3127 )
3128 .unwrap_err();
3129
3130 count
3131 .coerce(&NumericType::Unknown.into(), CoercionMode::implicit(), &mut exec_state)
3132 .unwrap_err();
3133 mm.coerce(&NumericType::Unknown.into(), CoercionMode::implicit(), &mut exec_state)
3134 .unwrap_err();
3135 default
3136 .coerce(&NumericType::Unknown.into(), CoercionMode::implicit(), &mut exec_state)
3137 .unwrap_err();
3138
3139 assert_eq!(
3140 inches
3141 .coerce(&NumericType::mm().into(), CoercionMode::implicit(), &mut exec_state)
3142 .unwrap()
3143 .as_f64()
3144 .unwrap()
3145 .round(),
3146 25.0
3147 );
3148 assert_eq!(
3149 rads.coerce(
3150 &NumericType::Known(UnitType::Angle(UnitAngle::Degrees)).into(),
3151 CoercionMode::implicit(),
3152 &mut exec_state
3153 )
3154 .unwrap()
3155 .as_f64()
3156 .unwrap()
3157 .round(),
3158 57.0
3159 );
3160 assert_eq!(
3161 inches
3162 .coerce(
3163 &NumericType::default().into(),
3164 CoercionMode::implicit(),
3165 &mut exec_state
3166 )
3167 .unwrap()
3168 .as_f64()
3169 .unwrap()
3170 .round(),
3171 1.0
3172 );
3173 assert_eq!(
3174 rads.coerce(
3175 &NumericType::default().into(),
3176 CoercionMode::implicit(),
3177 &mut exec_state
3178 )
3179 .unwrap()
3180 .as_f64()
3181 .unwrap()
3182 .round(),
3183 1.0
3184 );
3185 ctx.close().await;
3186 }
3187
3188 #[track_caller]
3189 fn assert_value_and_type(name: &str, result: &ExecTestResults, expected: f64, expected_ty: NumericType) {
3190 let mem = result.exec_state.stack();
3191 match mem
3192 .memory
3193 .get_from_owned(name, result.mem_env, SourceRange::default(), 0)
3194 .unwrap()
3195 {
3196 KclValue::Number { value, ty, .. } => {
3197 assert_eq!(value.round(), expected);
3198 assert_eq!(ty, expected_ty);
3199 }
3200 _ => unreachable!(),
3201 }
3202 }
3203
3204 #[tokio::test(flavor = "multi_thread")]
3205 async fn combine_numeric() {
3206 let program = r#"a = 5 + 4
3207b = 5 - 2
3208c = 5mm - 2mm + 10mm
3209d = 5mm - 2 + 10
3210e = 5 - 2mm + 10
3211f = 30mm - 1inch
3212
3213g = 2 * 10
3214h = 2 * 10mm
3215i = 2mm * 10mm
3216j = 2_ * 10
3217k = 2_ * 3mm * 3mm
3218
3219l = 1 / 10
3220m = 2mm / 1mm
3221n = 10inch / 2mm
3222o = 3mm / 3
3223p = 3_ / 4
3224q = 4inch / 2_
3225
3226r = min([0, 3, 42])
3227s = min([0, 3mm, -42])
3228t = min([100, 3in, 142mm])
3229u = min([3rad, 4in])
3230"#;
3231
3232 let result = parse_execute(program).await.unwrap();
3233 assert_eq!(
3234 result.exec_state.issues().len(),
3235 5,
3236 "errors: {:?}",
3237 result.exec_state.issues()
3238 );
3239
3240 assert_value_and_type("a", &result, 9.0, NumericType::default());
3241 assert_value_and_type("b", &result, 3.0, NumericType::default());
3242 assert_value_and_type("c", &result, 13.0, NumericType::mm());
3243 assert_value_and_type("d", &result, 13.0, NumericType::mm());
3244 assert_value_and_type("e", &result, 13.0, NumericType::mm());
3245 assert_value_and_type("f", &result, 5.0, NumericType::mm());
3246
3247 assert_value_and_type("g", &result, 20.0, NumericType::default());
3248 assert_value_and_type("h", &result, 20.0, NumericType::mm());
3249 assert_value_and_type("i", &result, 20.0, NumericType::Unknown);
3250 assert_value_and_type("j", &result, 20.0, NumericType::default());
3251 assert_value_and_type("k", &result, 18.0, NumericType::Unknown);
3252
3253 assert_value_and_type("l", &result, 0.0, NumericType::default());
3254 assert_value_and_type("m", &result, 2.0, NumericType::count());
3255 assert_value_and_type("n", &result, 5.0, NumericType::Unknown);
3256 assert_value_and_type("o", &result, 1.0, NumericType::mm());
3257 assert_value_and_type("p", &result, 1.0, NumericType::count());
3258 assert_value_and_type(
3259 "q",
3260 &result,
3261 2.0,
3262 NumericType::Known(UnitType::Length(UnitLength::Inches)),
3263 );
3264
3265 assert_value_and_type("r", &result, 0.0, NumericType::default());
3266 assert_value_and_type("s", &result, -42.0, NumericType::mm());
3267 assert_value_and_type("t", &result, 3.0, NumericType::Unknown);
3268 assert_value_and_type("u", &result, 3.0, NumericType::Unknown);
3269 }
3270
3271 #[tokio::test(flavor = "multi_thread")]
3272 async fn bad_typed_arithmetic() {
3273 let program = r#"
3274a = 1rad
3275b = 180 / PI * a + 360
3276"#;
3277
3278 let result = parse_execute(program).await.unwrap();
3279
3280 assert_value_and_type("a", &result, 1.0, NumericType::radians());
3281 assert_value_and_type("b", &result, 417.0, NumericType::Unknown);
3282 }
3283
3284 #[tokio::test(flavor = "multi_thread")]
3285 async fn cos_coercions() {
3286 let program = r#"
3287a = cos(units::toRadians(30deg))
3288b = 3 / a
3289c = cos(30deg)
3290d = cos(1rad)
3291"#;
3292
3293 let result = parse_execute(program).await.unwrap();
3294 assert!(
3295 result.exec_state.issues().is_empty(),
3296 "{:?}",
3297 result.exec_state.issues()
3298 );
3299
3300 assert_value_and_type("a", &result, 1.0, NumericType::default());
3301 assert_value_and_type("b", &result, 3.0, NumericType::default());
3302 assert_value_and_type("c", &result, 1.0, NumericType::default());
3303 assert_value_and_type("d", &result, 1.0, NumericType::default());
3304 }
3305
3306 #[tokio::test(flavor = "multi_thread")]
3307 async fn coerce_nested_array() {
3308 let (ctx, mut exec_state) = new_exec_state().await;
3309
3310 let mixed1 = KclValue::HomArray {
3311 value: vec![
3312 KclValue::Number {
3313 value: 0.0,
3314 ty: NumericType::count(),
3315 meta: Vec::new(),
3316 },
3317 KclValue::Number {
3318 value: 1.0,
3319 ty: NumericType::count(),
3320 meta: Vec::new(),
3321 },
3322 KclValue::HomArray {
3323 value: vec![
3324 KclValue::Number {
3325 value: 2.0,
3326 ty: NumericType::count(),
3327 meta: Vec::new(),
3328 },
3329 KclValue::Number {
3330 value: 3.0,
3331 ty: NumericType::count(),
3332 meta: Vec::new(),
3333 },
3334 ],
3335 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
3336 },
3337 ],
3338 ty: RuntimeType::any(),
3339 };
3340
3341 let tym1 = RuntimeType::Array(
3343 Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
3344 ArrayLen::Minimum(1),
3345 );
3346
3347 let result = KclValue::HomArray {
3348 value: vec![
3349 KclValue::Number {
3350 value: 0.0,
3351 ty: NumericType::count(),
3352 meta: Vec::new(),
3353 },
3354 KclValue::Number {
3355 value: 1.0,
3356 ty: NumericType::count(),
3357 meta: Vec::new(),
3358 },
3359 KclValue::Number {
3360 value: 2.0,
3361 ty: NumericType::count(),
3362 meta: Vec::new(),
3363 },
3364 KclValue::Number {
3365 value: 3.0,
3366 ty: NumericType::count(),
3367 meta: Vec::new(),
3368 },
3369 ],
3370 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
3371 };
3372 assert_coerce_results(&mixed1, &tym1, &result, &mut exec_state);
3373 ctx.close().await;
3374 }
3375}