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