1use std::collections::HashMap;
2use std::str::FromStr;
3
4use anyhow::Result;
5use async_recursion::async_recursion;
6use kcl_api::NumericSuffix;
7pub use kcl_api::NumericType;
8use kcl_api::UnitAngle;
9use kcl_api::UnitLength;
10pub use kcl_api::UnitType;
11use serde::Deserialize;
12use serde::Serialize;
13
14use crate::CompilationIssue;
15use crate::KclError;
16use crate::SourceRange;
17use crate::errors::KclErrorDetails;
18use crate::exec::PlaneKind;
19use crate::execution::ExecState;
20use crate::execution::ExecutorContext;
21use crate::execution::Plane;
22use crate::execution::PlaneInfo;
23use crate::execution::Point3d;
24use crate::execution::SKETCH_OBJECT_META;
25use crate::execution::SKETCH_OBJECT_META_SKETCH;
26use crate::execution::annotations;
27use crate::execution::kcl_value::EnumTypeId;
28use crate::execution::kcl_value::KclValue;
29use crate::execution::kcl_value::TypeDef;
30use crate::execution::memory::{self};
31use crate::fmt;
32use crate::modules::ModuleItems;
33use crate::modules::ModulePath;
34use crate::parsing::ast::types::ABSOLUTE_PATHS_NOT_SUPPORTED;
35use crate::parsing::ast::types::Identifier;
36use crate::parsing::ast::types::Name;
37use crate::parsing::ast::types::Node;
38use crate::parsing::ast::types::PrimitiveType as AstPrimitiveType;
39use crate::parsing::ast::types::Type;
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
1306pub fn adjust_length(from: UnitLength, value: f64, to: UnitLength) -> (f64, UnitLength) {
1307 use UnitLength::*;
1308
1309 if from == to {
1310 return (value, to);
1311 }
1312
1313 let (base, base_unit) = match from {
1314 Millimeters => (value, Millimeters),
1315 Centimeters => (value * 10.0, Millimeters),
1316 Meters => (value * 1000.0, Millimeters),
1317 Inches => (value, Inches),
1318 Feet => (value * 12.0, Inches),
1319 Yards => (value * 36.0, Inches),
1320 };
1321 let (base, base_unit) = match (base_unit, to) {
1322 (Millimeters, Inches) | (Millimeters, Feet) | (Millimeters, Yards) => (base / 25.4, Inches),
1323 (Inches, Millimeters) | (Inches, Centimeters) | (Inches, Meters) => (base * 25.4, Millimeters),
1324 _ => (base, base_unit),
1325 };
1326
1327 let value = match (base_unit, to) {
1328 (Millimeters, Millimeters) => base,
1329 (Millimeters, Centimeters) => base / 10.0,
1330 (Millimeters, Meters) => base / 1000.0,
1331 (Inches, Inches) => base,
1332 (Inches, Feet) => base / 12.0,
1333 (Inches, Yards) => base / 36.0,
1334 _ => unreachable!(),
1335 };
1336
1337 (value, to)
1338}
1339
1340pub fn adjust_angle(from: UnitAngle, value: f64, to: UnitAngle) -> (f64, UnitAngle) {
1341 use std::f64::consts::PI;
1342
1343 use UnitAngle::*;
1344
1345 let value = match (from, to) {
1346 (Degrees, Degrees) => value,
1347 (Degrees, Radians) => (value / 180.0) * PI,
1348 (Radians, Degrees) => 180.0 * value / PI,
1349 (Radians, Radians) => value,
1350 };
1351
1352 (value, to)
1353}
1354
1355pub(super) fn length_from_str(s: &str, source_range: SourceRange) -> Result<UnitLength, KclError> {
1356 match s {
1358 "mm" => Ok(UnitLength::Millimeters),
1359 "cm" => Ok(UnitLength::Centimeters),
1360 "m" => Ok(UnitLength::Meters),
1361 "inch" | "in" => Ok(UnitLength::Inches),
1362 "ft" => Ok(UnitLength::Feet),
1363 "yd" => Ok(UnitLength::Yards),
1364 value => Err(KclError::new_semantic(KclErrorDetails::new(
1365 format!("Unexpected value for length units: `{value}`; expected one of `mm`, `cm`, `m`, `in`, `ft`, `yd`"),
1366 vec![source_range],
1367 ))),
1368 }
1369}
1370
1371pub(super) fn angle_from_str(s: &str, source_range: SourceRange) -> Result<UnitAngle, KclError> {
1372 UnitAngle::from_str(s).map_err(|_| {
1373 KclError::new_semantic(KclErrorDetails::new(
1374 format!("Unexpected value for angle units: `{s}`; expected one of `deg`, `rad`"),
1375 vec![source_range],
1376 ))
1377 })
1378}
1379
1380#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1386pub struct CoercionMode {
1387 convert_units: bool,
1388 project_enums: bool,
1389}
1390
1391impl CoercionMode {
1392 pub fn implicit() -> Self {
1397 CoercionMode {
1398 convert_units: true,
1399 project_enums: false,
1400 }
1401 }
1402
1403 pub fn explicit() -> Self {
1407 CoercionMode {
1408 convert_units: false,
1409 project_enums: true,
1410 }
1411 }
1412
1413 pub(crate) fn convert_units(self) -> bool {
1414 self.convert_units
1415 }
1416
1417 pub(crate) fn project_enums(self) -> bool {
1418 self.project_enums
1419 }
1420
1421 pub(crate) fn without_projection(self) -> Self {
1424 CoercionMode {
1425 project_enums: false,
1426 ..self
1427 }
1428 }
1429}
1430
1431#[derive(Debug, Clone)]
1432pub struct CoercionError {
1433 pub found: Option<RuntimeType>,
1434 pub explicit_coercion: Option<String>,
1435 pub message: Option<String>,
1438}
1439
1440impl CoercionError {
1441 fn with_explicit(mut self, c: String) -> Self {
1442 self.explicit_coercion = Some(c);
1443 self
1444 }
1445
1446 fn with_message(mut self, message: String) -> Self {
1447 self.message = Some(message);
1448 self
1449 }
1450}
1451
1452impl From<&'_ KclValue> for CoercionError {
1453 fn from(value: &'_ KclValue) -> Self {
1454 CoercionError {
1455 found: value.principal_type(),
1456 explicit_coercion: None,
1457 message: None,
1458 }
1459 }
1460}
1461
1462impl KclValue {
1463 pub fn has_type(&self, ty: &RuntimeType) -> bool {
1465 let Some(self_ty) = self.principal_type() else {
1466 return false;
1467 };
1468
1469 self_ty.subtype(ty)
1470 }
1471
1472 pub fn coerce(
1479 &self,
1480 ty: &RuntimeType,
1481 mode: CoercionMode,
1482 exec_state: &mut ExecState,
1483 ) -> Result<KclValue, CoercionError> {
1484 match self {
1485 KclValue::Tuple { value, .. }
1486 if value.len() == 1
1487 && !matches!(ty, RuntimeType::Primitive(PrimitiveType::Any) | RuntimeType::Tuple(..)) =>
1488 {
1489 if let Ok(coerced) = value[0].coerce(ty, mode, exec_state) {
1490 return Ok(coerced);
1491 }
1492 }
1493 KclValue::HomArray { value, .. }
1494 if value.len() == 1
1495 && !matches!(ty, RuntimeType::Primitive(PrimitiveType::Any) | RuntimeType::Array(..)) =>
1496 {
1497 if let Ok(coerced) = value[0].coerce(ty, mode, exec_state) {
1498 return Ok(coerced);
1499 }
1500 }
1501 _ => {}
1502 }
1503
1504 match ty {
1505 RuntimeType::Primitive(ty) => self.coerce_to_primitive_type(ty, mode, exec_state),
1506 RuntimeType::Array(ty, len) => self.coerce_to_array_type(ty, mode, *len, exec_state, false),
1507 RuntimeType::Tuple(tys) => self.coerce_to_tuple_type(tys, mode, exec_state),
1508 RuntimeType::Union(tys) => self.coerce_to_union_type(tys, mode, exec_state),
1509 RuntimeType::Object(tys, constrainable) => {
1510 self.coerce_to_object_type(tys, *constrainable, mode, exec_state)
1511 }
1512 RuntimeType::Enum(id) => self.coerce_to_enum_type(id),
1513 }
1514 }
1515
1516 fn coerce_to_enum_type(&self, id: &EnumTypeId) -> Result<KclValue, CoercionError> {
1521 match self {
1522 KclValue::Enum { value } if value.enum_id() == id => Ok(self.clone()),
1523 _ => Err(self.into()),
1524 }
1525 }
1526
1527 fn coerce_to_primitive_type(
1528 &self,
1529 ty: &PrimitiveType,
1530 mode: CoercionMode,
1531 exec_state: &mut ExecState,
1532 ) -> Result<KclValue, CoercionError> {
1533 match ty {
1534 PrimitiveType::Any => Ok(self.clone()),
1535 PrimitiveType::Never => Err(self.into()),
1536 PrimitiveType::None => match self {
1537 KclValue::KclNone { .. } => Ok(self.clone()),
1538 _ => Err(self.into()),
1539 },
1540 PrimitiveType::Number(ty) => {
1541 if let KclValue::Enum { value } = self
1546 && mode.project_enums()
1547 {
1548 return Err(CoercionError::from(self).with_message(format!(
1549 "Cannot project enum `{}` to a number. An enum projects to `string`; projecting to a number is not supported yet.",
1550 value.enum_id().declared_name()
1551 )));
1552 }
1553
1554 if mode.convert_units() {
1555 return ty.coerce(self);
1556 }
1557
1558 if let KclValue::Number { value: n, meta, .. } = &self
1565 && ty.is_fully_specified()
1566 {
1567 let value = KclValue::Number {
1568 ty: NumericType::Any,
1569 value: *n,
1570 meta: meta.clone(),
1571 };
1572 return ty.coerce(&value);
1573 }
1574 ty.coerce(self)
1575 }
1576 PrimitiveType::String => match self {
1577 KclValue::String { .. } => Ok(self.clone()),
1578 KclValue::Enum { value } if mode.project_enums() => Ok(KclValue::String {
1581 value: value.declared_string_repr(),
1582 meta: value.meta().to_vec(),
1583 }),
1584 _ => Err(self.into()),
1585 },
1586 PrimitiveType::Boolean => match self {
1587 KclValue::Bool { .. } => Ok(self.clone()),
1588 _ => Err(self.into()),
1589 },
1590 PrimitiveType::GdtAnnotation => match self {
1591 KclValue::GdtAnnotation { .. } => Ok(self.clone()),
1592 _ => Err(self.into()),
1593 },
1594 PrimitiveType::CameraView => match self {
1595 KclValue::CameraView { .. } => Ok(self.clone()),
1596 _ => Err(self.into()),
1597 },
1598 PrimitiveType::NamedView => match self {
1599 KclValue::NamedView { .. } => Ok(self.clone()),
1600 _ => Err(self.into()),
1601 },
1602 PrimitiveType::Segment => match self {
1603 KclValue::Segment { .. } => Ok(self.clone()),
1604 _ => Err(self.into()),
1605 },
1606 PrimitiveType::Sketch => match self {
1607 KclValue::Sketch { .. } => Ok(self.clone()),
1608 KclValue::Object { value, .. } => {
1609 let Some(meta) = value.get(SKETCH_OBJECT_META) else {
1610 return Err(self.into());
1611 };
1612 let KclValue::Object { value: meta_map, .. } = meta else {
1613 return Err(self.into());
1614 };
1615 let Some(sketch) = meta_map.get(SKETCH_OBJECT_META_SKETCH).and_then(KclValue::as_sketch) else {
1616 return Err(self.into());
1617 };
1618
1619 Ok(KclValue::Sketch {
1620 value: Box::new(sketch.clone()),
1621 })
1622 }
1623 _ => Err(self.into()),
1624 },
1625 PrimitiveType::Constraint => match self {
1626 KclValue::SketchConstraint { .. } => Ok(self.clone()),
1627 _ => Err(self.into()),
1628 },
1629 PrimitiveType::Solid => match self {
1630 KclValue::Solid { .. } => Ok(self.clone()),
1631 _ => Err(self.into()),
1632 },
1633 PrimitiveType::Plane => {
1634 match self {
1635 KclValue::String { value: s, .. }
1636 if [
1637 "xy", "xz", "yz", "-xy", "-xz", "-yz", "XY", "XZ", "YZ", "-XY", "-XZ", "-YZ",
1638 ]
1639 .contains(&&**s) =>
1640 {
1641 Ok(self.clone())
1642 }
1643 KclValue::Plane { .. } => Ok(self.clone()),
1644 KclValue::Object { value, meta, .. } => {
1645 let origin = value
1646 .get("origin")
1647 .and_then(Point3d::from_kcl_val)
1648 .ok_or(CoercionError::from(self))?;
1649 let x_axis = value
1650 .get("xAxis")
1651 .and_then(Point3d::from_kcl_val)
1652 .ok_or(CoercionError::from(self))?;
1653 let y_axis = value
1654 .get("yAxis")
1655 .and_then(Point3d::from_kcl_val)
1656 .ok_or(CoercionError::from(self))?;
1657 let z_axis = x_axis.axes_cross_product(&y_axis);
1658
1659 if value.get("zAxis").is_some() {
1660 exec_state.warn(CompilationIssue::err(
1661 self.into(),
1662 "Object with a zAxis field is being coerced into a plane, but the zAxis is ignored.",
1663 ), annotations::WARN_IGNORED_Z_AXIS);
1664 }
1665
1666 let id = exec_state.mod_local.id_generator.next_uuid();
1667 let info = PlaneInfo {
1668 origin,
1669 x_axis: x_axis.normalize(),
1670 y_axis: y_axis.normalize(),
1671 z_axis: z_axis.normalize(),
1672 };
1673 let plane = Plane {
1674 id,
1675 artifact_id: id.into(),
1676 object_id: None,
1677 kind: PlaneKind::from(&info),
1678 info,
1679 meta: meta.clone(),
1680 };
1681
1682 Ok(KclValue::Plane { value: Box::new(plane) })
1683 }
1684 _ => Err(self.into()),
1685 }
1686 }
1687 PrimitiveType::Face => match self {
1688 KclValue::Face { .. } => Ok(self.clone()),
1689 _ => Err(self.into()),
1690 },
1691 PrimitiveType::Helix => match self {
1692 KclValue::Helix { .. } => Ok(self.clone()),
1693 _ => Err(self.into()),
1694 },
1695 PrimitiveType::Edge => match self {
1696 KclValue::Uuid { .. } => Ok(self.clone()),
1697 KclValue::TagIdentifier { .. } => Ok(self.clone()),
1698 _ => Err(self.into()),
1699 },
1700 PrimitiveType::BoundedEdge => match self {
1701 KclValue::BoundedEdge { .. } => Ok(self.clone()),
1702 _ => Err(self.into()),
1703 },
1704 PrimitiveType::TaggedEdge => match self {
1705 KclValue::TagIdentifier { .. } => Ok(self.clone()),
1706 _ => Err(self.into()),
1707 },
1708 PrimitiveType::TaggedFace => match self {
1709 KclValue::TagIdentifier { .. } => Ok(self.clone()),
1710 s @ KclValue::String { value, .. } if ["start", "end", "START", "END"].contains(&&**value) => {
1711 Ok(s.clone())
1712 }
1713 _ => Err(self.into()),
1714 },
1715 PrimitiveType::Axis2d => match self {
1716 KclValue::Object {
1717 value: values, meta, ..
1718 } => {
1719 if values
1720 .get("origin")
1721 .ok_or(CoercionError::from(self))?
1722 .has_type(&RuntimeType::point2d())
1723 && values
1724 .get("direction")
1725 .ok_or(CoercionError::from(self))?
1726 .has_type(&RuntimeType::point2d())
1727 {
1728 return Ok(self.clone());
1729 }
1730
1731 let origin = values.get("origin").ok_or(self.into()).and_then(|p| {
1732 p.coerce_to_array_type(&RuntimeType::length(), mode, ArrayLen::Known(2), exec_state, true)
1733 })?;
1734 let direction = values.get("direction").ok_or(self.into()).and_then(|p| {
1735 p.coerce_to_array_type(&RuntimeType::length(), mode, ArrayLen::Known(2), exec_state, true)
1736 })?;
1737
1738 Ok(KclValue::Object {
1739 value: [("origin".to_owned(), origin), ("direction".to_owned(), direction)].into(),
1740 meta: meta.clone(),
1741 constrainable: false,
1742 object_kind: Default::default(),
1743 })
1744 }
1745 _ => Err(self.into()),
1746 },
1747 PrimitiveType::Axis3d => match self {
1748 KclValue::Object {
1749 value: values, meta, ..
1750 } => {
1751 if values
1752 .get("origin")
1753 .ok_or(CoercionError::from(self))?
1754 .has_type(&RuntimeType::point3d())
1755 && values
1756 .get("direction")
1757 .ok_or(CoercionError::from(self))?
1758 .has_type(&RuntimeType::point3d())
1759 {
1760 return Ok(self.clone());
1761 }
1762
1763 let origin = values.get("origin").ok_or(self.into()).and_then(|p| {
1764 p.coerce_to_array_type(&RuntimeType::length(), mode, ArrayLen::Known(3), exec_state, true)
1765 })?;
1766 let direction = values.get("direction").ok_or(self.into()).and_then(|p| {
1767 p.coerce_to_array_type(&RuntimeType::length(), mode, ArrayLen::Known(3), exec_state, true)
1768 })?;
1769
1770 Ok(KclValue::Object {
1771 value: [("origin".to_owned(), origin), ("direction".to_owned(), direction)].into(),
1772 meta: meta.clone(),
1773 constrainable: false,
1774 object_kind: Default::default(),
1775 })
1776 }
1777 _ => Err(self.into()),
1778 },
1779 PrimitiveType::ImportedGeometry => match self {
1780 KclValue::ImportedGeometry { .. } => Ok(self.clone()),
1781 _ => Err(self.into()),
1782 },
1783 PrimitiveType::Function => match self {
1784 KclValue::Function { .. } => Ok(self.clone()),
1785 _ => Err(self.into()),
1786 },
1787 PrimitiveType::TagDecl => match self {
1788 KclValue::TagDeclarator { .. } => Ok(self.clone()),
1789 _ => Err(self.into()),
1790 },
1791 }
1792 }
1793
1794 fn coerce_to_array_type(
1795 &self,
1796 ty: &RuntimeType,
1797 mode: CoercionMode,
1798 len: ArrayLen,
1799 exec_state: &mut ExecState,
1800 allow_shrink: bool,
1801 ) -> Result<KclValue, CoercionError> {
1802 match self {
1803 KclValue::HomArray { value, ty: aty, .. } => {
1804 let satisfied_len = len.satisfied(value.len(), allow_shrink);
1805
1806 if aty.subtype(ty) {
1807 return satisfied_len
1814 .map(|len| KclValue::HomArray {
1815 value: value[..len].to_vec(),
1816 ty: aty.clone(),
1817 })
1818 .ok_or(self.into());
1819 }
1820
1821 if let Some(satisfied_len) = satisfied_len {
1823 let value_result = value
1824 .iter()
1825 .take(satisfied_len)
1826 .map(|v| v.coerce(ty, mode, exec_state))
1827 .collect::<Result<Vec<_>, _>>();
1828
1829 if let Ok(value) = value_result {
1830 return Ok(KclValue::HomArray { value, ty: ty.clone() });
1832 }
1833 }
1834
1835 let mut values = Vec::new();
1837 for item in value {
1838 if let KclValue::HomArray { value: inner_value, .. } = item {
1839 for item in inner_value {
1841 values.push(item.coerce(ty, mode, exec_state)?);
1842 }
1843 } else {
1844 values.push(item.coerce(ty, mode, exec_state)?);
1845 }
1846 }
1847
1848 let len = len
1849 .satisfied(values.len(), allow_shrink)
1850 .ok_or(CoercionError::from(self))?;
1851
1852 if len > values.len() {
1853 let message = format!(
1854 "Internal: Expected coerced array length {len} to be less than or equal to original length {}",
1855 values.len()
1856 );
1857 exec_state.err(CompilationIssue::err(self.into(), message.clone()));
1858 #[cfg(debug_assertions)]
1859 panic!("{message}");
1860 }
1861 values.truncate(len);
1862
1863 Ok(KclValue::HomArray {
1864 value: values,
1865 ty: ty.clone(),
1866 })
1867 }
1868 KclValue::Tuple { value, .. } => {
1869 let len = len
1870 .satisfied(value.len(), allow_shrink)
1871 .ok_or(CoercionError::from(self))?;
1872 let value = value
1873 .iter()
1874 .map(|item| item.coerce(ty, mode, exec_state))
1875 .take(len)
1876 .collect::<Result<Vec<_>, _>>()?;
1877
1878 Ok(KclValue::HomArray { value, ty: ty.clone() })
1879 }
1880 KclValue::KclNone { .. } if len.satisfied(0, false).is_some() => Ok(KclValue::HomArray {
1881 value: Vec::new(),
1882 ty: ty.clone(),
1883 }),
1884 _ if len.satisfied(1, false).is_some() => self.coerce(ty, mode, exec_state),
1885 _ => Err(self.into()),
1886 }
1887 }
1888
1889 fn coerce_to_tuple_type(
1890 &self,
1891 tys: &[RuntimeType],
1892 mode: CoercionMode,
1893 exec_state: &mut ExecState,
1894 ) -> Result<KclValue, CoercionError> {
1895 match self {
1896 KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } if value.len() == tys.len() => {
1897 let mut result = Vec::new();
1898 for (i, t) in tys.iter().enumerate() {
1899 result.push(value[i].coerce(t, mode, exec_state)?);
1900 }
1901
1902 Ok(KclValue::Tuple {
1903 value: result,
1904 meta: Vec::new(),
1905 })
1906 }
1907 KclValue::KclNone { meta, .. } if tys.is_empty() => Ok(KclValue::Tuple {
1908 value: Vec::new(),
1909 meta: meta.clone(),
1910 }),
1911 _ if tys.len() == 1 => self.coerce(&tys[0], mode, exec_state),
1912 _ => Err(self.into()),
1913 }
1914 }
1915
1916 fn coerce_to_union_type(
1917 &self,
1918 tys: &[RuntimeType],
1919 mode: CoercionMode,
1920 exec_state: &mut ExecState,
1921 ) -> Result<KclValue, CoercionError> {
1922 if mode.project_enums() {
1928 let exact = mode.without_projection();
1929 for t in tys {
1930 if let Ok(v) = self.coerce(t, exact, exec_state) {
1931 return Ok(v);
1932 }
1933 }
1934 }
1935
1936 for t in tys {
1937 if let Ok(v) = self.coerce(t, mode, exec_state) {
1938 return Ok(v);
1939 }
1940 }
1941
1942 Err(self.into())
1943 }
1944
1945 fn coerce_to_object_type(
1946 &self,
1947 tys: &[(String, RuntimeType)],
1948 constrainable: bool,
1949 _mode: CoercionMode,
1950 _exec_state: &mut ExecState,
1951 ) -> Result<KclValue, CoercionError> {
1952 match self {
1953 KclValue::Object { value, meta, .. } => {
1954 for (s, t) in tys {
1955 if !value.get(s).ok_or(CoercionError::from(self))?.has_type(t) {
1957 return Err(self.into());
1958 }
1959 }
1960 Ok(KclValue::Object {
1962 value: value.clone(),
1963 meta: meta.clone(),
1964 constrainable,
1967 object_kind: Default::default(),
1968 })
1969 }
1970 KclValue::KclNone { meta, .. } if tys.is_empty() => Ok(KclValue::Object {
1971 value: HashMap::new(),
1972 meta: meta.clone(),
1973 constrainable,
1974 object_kind: Default::default(),
1975 }),
1976 _ => Err(self.into()),
1977 }
1978 }
1979
1980 pub fn principal_type(&self) -> Option<RuntimeType> {
1981 match self {
1982 KclValue::Bool { .. } => Some(RuntimeType::Primitive(PrimitiveType::Boolean)),
1983 KclValue::Number { ty, .. } => Some(RuntimeType::Primitive(PrimitiveType::Number(*ty))),
1984 KclValue::String { .. } => Some(RuntimeType::Primitive(PrimitiveType::String)),
1985 KclValue::Enum { value } => Some(RuntimeType::Enum(value.enum_id().clone())),
1986 KclValue::SketchVar { value, .. } => Some(RuntimeType::Primitive(PrimitiveType::Number(value.ty))),
1987 KclValue::SketchConstraint { .. } => Some(RuntimeType::Primitive(PrimitiveType::Constraint)),
1988 KclValue::Object {
1989 value, constrainable, ..
1990 } => {
1991 let properties = value
1992 .iter()
1993 .map(|(k, v)| v.principal_type().map(|t| (k.clone(), t)))
1994 .collect::<Option<Vec<_>>>()?;
1995 Some(RuntimeType::Object(properties, *constrainable))
1996 }
1997 KclValue::GdtAnnotation { .. } => Some(RuntimeType::Primitive(PrimitiveType::GdtAnnotation)),
1998 KclValue::CameraView { .. } => Some(RuntimeType::Primitive(PrimitiveType::CameraView)),
1999 KclValue::NamedView { .. } => Some(RuntimeType::Primitive(PrimitiveType::NamedView)),
2000 KclValue::Plane { .. } => Some(RuntimeType::Primitive(PrimitiveType::Plane)),
2001 KclValue::Sketch { .. } => Some(RuntimeType::Primitive(PrimitiveType::Sketch)),
2002 KclValue::Solid { .. } => Some(RuntimeType::Primitive(PrimitiveType::Solid)),
2003 KclValue::Face { .. } => Some(RuntimeType::Primitive(PrimitiveType::Face)),
2004 KclValue::Segment { .. } => Some(RuntimeType::Primitive(PrimitiveType::Segment)),
2005 KclValue::Helix { .. } => Some(RuntimeType::Primitive(PrimitiveType::Helix)),
2006 KclValue::ImportedGeometry(..) => Some(RuntimeType::Primitive(PrimitiveType::ImportedGeometry)),
2007 KclValue::Tuple { value, .. } => Some(RuntimeType::Tuple(
2008 value.iter().map(|v| v.principal_type()).collect::<Option<Vec<_>>>()?,
2009 )),
2010 KclValue::HomArray { ty, value, .. } => {
2011 Some(RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Known(value.len())))
2012 }
2013 KclValue::TagIdentifier(_) => Some(RuntimeType::Primitive(PrimitiveType::TaggedEdge)),
2014 KclValue::TagDeclarator(_) => Some(RuntimeType::Primitive(PrimitiveType::TagDecl)),
2015 KclValue::Uuid { .. } => Some(RuntimeType::Primitive(PrimitiveType::Edge)),
2016 KclValue::Function { .. } => Some(RuntimeType::Primitive(PrimitiveType::Function)),
2017 KclValue::KclNone { .. } => Some(RuntimeType::Primitive(PrimitiveType::None)),
2018 KclValue::Module { .. } | KclValue::Type { .. } => None,
2019 KclValue::BoundedEdge { .. } => Some(RuntimeType::Primitive(PrimitiveType::BoundedEdge)),
2020 }
2021 }
2022
2023 pub fn principal_type_string(&self) -> String {
2024 if let Some(ty) = self.principal_type() {
2025 return format!("`{ty}`");
2026 }
2027
2028 match self {
2029 KclValue::Module { .. } => "module",
2030 KclValue::KclNone { .. } => "none",
2031 KclValue::Type { .. } => "type",
2032 _ => {
2033 debug_assert!(false);
2034 "<unexpected type>"
2035 }
2036 }
2037 .to_owned()
2038 }
2039}
2040
2041#[cfg(test)]
2042mod test {
2043 use std::sync::Arc;
2044
2045 use super::*;
2046 use crate::ModuleId;
2047 use crate::execution::ExecTestResults;
2048 use crate::execution::kcl_value::EnumTypeDef;
2049 use crate::execution::kcl_value::EnumValue;
2050 use crate::execution::parse_execute;
2051
2052 async fn new_exec_state() -> (crate::ExecutorContext, ExecState) {
2053 let ctx = crate::ExecutorContext::new_mock(None).await;
2054 let exec_state = ExecState::new(&ctx);
2055 (ctx, exec_state)
2056 }
2057
2058 fn values(exec_state: &mut ExecState) -> Vec<KclValue> {
2059 vec![
2060 KclValue::Bool {
2061 value: true,
2062 meta: Vec::new(),
2063 },
2064 KclValue::Number {
2065 value: 1.0,
2066 ty: NumericType::count(),
2067 meta: Vec::new(),
2068 },
2069 KclValue::String {
2070 value: "hello".to_owned(),
2071 meta: Vec::new(),
2072 },
2073 KclValue::Tuple {
2074 value: Vec::new(),
2075 meta: Vec::new(),
2076 },
2077 KclValue::HomArray {
2078 value: Vec::new(),
2079 ty: RuntimeType::solid(),
2080 },
2081 KclValue::Object {
2082 value: crate::execution::KclObjectFields::new(),
2083 meta: Vec::new(),
2084 constrainable: false,
2085 object_kind: Default::default(),
2086 },
2087 KclValue::TagIdentifier(Box::new("foo".parse().unwrap())),
2088 KclValue::TagDeclarator(crate::parsing::ast::types::BoxNode::new(
2089 crate::parsing::ast::types::TagDeclarator::new("foo"),
2090 )),
2091 KclValue::Plane {
2092 value: Box::new(
2093 Plane::from_plane_data_skipping_engine(crate::std::sketch::PlaneData::XY, exec_state).unwrap(),
2094 ),
2095 },
2096 KclValue::ImportedGeometry(crate::execution::ImportedGeometry::new(
2098 uuid::Uuid::nil(),
2099 Vec::new(),
2100 Vec::new(),
2101 )),
2102 ]
2104 }
2105
2106 #[track_caller]
2107 fn assert_coerce_results(
2108 value: &KclValue,
2109 super_type: &RuntimeType,
2110 expected_value: &KclValue,
2111 exec_state: &mut ExecState,
2112 ) {
2113 let is_subtype = value == expected_value;
2114 let actual = value.coerce(super_type, CoercionMode::implicit(), exec_state).unwrap();
2115 assert_eq!(&actual, expected_value);
2116 assert_eq!(
2117 is_subtype,
2118 value.principal_type().is_some() && value.principal_type().unwrap().subtype(super_type),
2119 "{:?} <: {super_type:?} should be {is_subtype}",
2120 value.principal_type().unwrap()
2121 );
2122 assert!(
2123 expected_value.principal_type().unwrap().subtype(super_type),
2124 "{} <: {super_type}",
2125 expected_value.principal_type().unwrap()
2126 )
2127 }
2128
2129 #[tokio::test(flavor = "multi_thread")]
2130 async fn coerce_idempotent() {
2131 let (ctx, mut exec_state) = new_exec_state().await;
2132 let values = values(&mut exec_state);
2133 for v in &values {
2134 let ty = v.principal_type().unwrap();
2136 assert_coerce_results(v, &ty, v, &mut exec_state);
2137
2138 let uty1 = RuntimeType::Union(vec![ty.clone()]);
2140 let uty2 = RuntimeType::Union(vec![ty.clone(), RuntimeType::Primitive(PrimitiveType::Boolean)]);
2141 assert_coerce_results(v, &uty1, v, &mut exec_state);
2142 assert_coerce_results(v, &uty2, v, &mut exec_state);
2143
2144 let aty = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::None);
2146 let aty1 = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Known(1));
2147 let aty0 = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Minimum(1));
2148
2149 match v {
2150 KclValue::HomArray { .. } => {
2151 assert_coerce_results(
2153 v,
2154 &aty,
2155 &KclValue::HomArray {
2156 value: vec![],
2157 ty: ty.clone(),
2158 },
2159 &mut exec_state,
2160 );
2161 v.coerce(&aty1, CoercionMode::implicit(), &mut exec_state).unwrap_err();
2164 v.coerce(&aty0, CoercionMode::implicit(), &mut exec_state).unwrap_err();
2167 }
2168 KclValue::Tuple { .. } => {}
2169 _ => {
2170 assert_coerce_results(v, &aty, v, &mut exec_state);
2171 assert_coerce_results(v, &aty1, v, &mut exec_state);
2172 assert_coerce_results(v, &aty0, v, &mut exec_state);
2173
2174 let tty = RuntimeType::Tuple(vec![ty.clone()]);
2176 assert_coerce_results(v, &tty, v, &mut exec_state);
2177 }
2178 }
2179 }
2180
2181 for v in &values[1..] {
2182 v.coerce(
2184 &RuntimeType::Primitive(PrimitiveType::Boolean),
2185 CoercionMode::implicit(),
2186 &mut exec_state,
2187 )
2188 .unwrap_err();
2189 }
2190 ctx.close().await;
2191 }
2192
2193 #[tokio::test(flavor = "multi_thread")]
2194 async fn coerce_none() {
2195 let (ctx, mut exec_state) = new_exec_state().await;
2196 let none = KclValue::KclNone {
2197 value: crate::parsing::ast::types::KclNone::new(),
2198 meta: Vec::new(),
2199 };
2200
2201 let aty = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::None);
2202 let aty0 = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::Known(0));
2203 let aty1 = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::Known(1));
2204 let aty1p = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::Minimum(1));
2205 assert_coerce_results(
2206 &none,
2207 &aty,
2208 &KclValue::HomArray {
2209 value: Vec::new(),
2210 ty: RuntimeType::solid(),
2211 },
2212 &mut exec_state,
2213 );
2214 assert_coerce_results(
2215 &none,
2216 &aty0,
2217 &KclValue::HomArray {
2218 value: Vec::new(),
2219 ty: RuntimeType::solid(),
2220 },
2221 &mut exec_state,
2222 );
2223 none.coerce(&aty1, CoercionMode::implicit(), &mut exec_state)
2224 .unwrap_err();
2225 none.coerce(&aty1p, CoercionMode::implicit(), &mut exec_state)
2226 .unwrap_err();
2227
2228 let tty = RuntimeType::Tuple(vec![]);
2229 let tty1 = RuntimeType::Tuple(vec![RuntimeType::solid()]);
2230 assert_coerce_results(
2231 &none,
2232 &tty,
2233 &KclValue::Tuple {
2234 value: Vec::new(),
2235 meta: Vec::new(),
2236 },
2237 &mut exec_state,
2238 );
2239 none.coerce(&tty1, CoercionMode::implicit(), &mut exec_state)
2240 .unwrap_err();
2241
2242 let oty = RuntimeType::Object(vec![], false);
2243 assert_coerce_results(
2244 &none,
2245 &oty,
2246 &KclValue::Object {
2247 value: HashMap::new(),
2248 meta: Vec::new(),
2249 constrainable: false,
2250 object_kind: Default::default(),
2251 },
2252 &mut exec_state,
2253 );
2254 ctx.close().await;
2255 }
2256
2257 #[tokio::test(flavor = "multi_thread")]
2258 async fn coerce_record() {
2259 let (ctx, mut exec_state) = new_exec_state().await;
2260
2261 let obj0 = KclValue::Object {
2262 value: HashMap::new(),
2263 meta: Vec::new(),
2264 constrainable: false,
2265 object_kind: Default::default(),
2266 };
2267 let obj1 = KclValue::Object {
2268 value: [(
2269 "foo".to_owned(),
2270 KclValue::Bool {
2271 value: true,
2272 meta: Vec::new(),
2273 },
2274 )]
2275 .into(),
2276 meta: Vec::new(),
2277 constrainable: false,
2278 object_kind: Default::default(),
2279 };
2280 let obj2 = KclValue::Object {
2281 value: [
2282 (
2283 "foo".to_owned(),
2284 KclValue::Bool {
2285 value: true,
2286 meta: Vec::new(),
2287 },
2288 ),
2289 (
2290 "bar".to_owned(),
2291 KclValue::Number {
2292 value: 0.0,
2293 ty: NumericType::count(),
2294 meta: Vec::new(),
2295 },
2296 ),
2297 (
2298 "baz".to_owned(),
2299 KclValue::Number {
2300 value: 42.0,
2301 ty: NumericType::count(),
2302 meta: Vec::new(),
2303 },
2304 ),
2305 ]
2306 .into(),
2307 meta: Vec::new(),
2308 constrainable: false,
2309 object_kind: Default::default(),
2310 };
2311
2312 let ty0 = RuntimeType::Object(vec![], false);
2313 assert_coerce_results(&obj0, &ty0, &obj0, &mut exec_state);
2314 assert_coerce_results(&obj1, &ty0, &obj1, &mut exec_state);
2315 assert_coerce_results(&obj2, &ty0, &obj2, &mut exec_state);
2316
2317 let ty1 = RuntimeType::Object(
2318 vec![("foo".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean))],
2319 false,
2320 );
2321 obj0.coerce(&ty1, CoercionMode::implicit(), &mut exec_state)
2322 .unwrap_err();
2323 assert_coerce_results(&obj1, &ty1, &obj1, &mut exec_state);
2324 assert_coerce_results(&obj2, &ty1, &obj2, &mut exec_state);
2325
2326 let ty2 = RuntimeType::Object(
2328 vec![
2329 (
2330 "bar".to_owned(),
2331 RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2332 ),
2333 ("foo".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean)),
2334 ],
2335 false,
2336 );
2337 obj0.coerce(&ty2, CoercionMode::implicit(), &mut exec_state)
2338 .unwrap_err();
2339 obj1.coerce(&ty2, CoercionMode::implicit(), &mut exec_state)
2340 .unwrap_err();
2341 assert_coerce_results(&obj2, &ty2, &obj2, &mut exec_state);
2342
2343 let tyq = RuntimeType::Object(
2345 vec![("qux".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean))],
2346 false,
2347 );
2348 obj0.coerce(&tyq, CoercionMode::implicit(), &mut exec_state)
2349 .unwrap_err();
2350 obj1.coerce(&tyq, CoercionMode::implicit(), &mut exec_state)
2351 .unwrap_err();
2352 obj2.coerce(&tyq, CoercionMode::implicit(), &mut exec_state)
2353 .unwrap_err();
2354
2355 let ty1 = RuntimeType::Object(
2357 vec![("bar".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean))],
2358 false,
2359 );
2360 obj2.coerce(&ty1, CoercionMode::implicit(), &mut exec_state)
2361 .unwrap_err();
2362 ctx.close().await;
2363 }
2364
2365 #[tokio::test(flavor = "multi_thread")]
2366 async fn coerce_array() {
2367 let (ctx, mut exec_state) = new_exec_state().await;
2368
2369 let hom_arr = KclValue::HomArray {
2370 value: vec![
2371 KclValue::Number {
2372 value: 0.0,
2373 ty: NumericType::count(),
2374 meta: Vec::new(),
2375 },
2376 KclValue::Number {
2377 value: 1.0,
2378 ty: NumericType::count(),
2379 meta: Vec::new(),
2380 },
2381 KclValue::Number {
2382 value: 2.0,
2383 ty: NumericType::count(),
2384 meta: Vec::new(),
2385 },
2386 KclValue::Number {
2387 value: 3.0,
2388 ty: NumericType::count(),
2389 meta: Vec::new(),
2390 },
2391 ],
2392 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2393 };
2394 let mixed1 = KclValue::Tuple {
2395 value: vec![
2396 KclValue::Number {
2397 value: 0.0,
2398 ty: NumericType::count(),
2399 meta: Vec::new(),
2400 },
2401 KclValue::Number {
2402 value: 1.0,
2403 ty: NumericType::count(),
2404 meta: Vec::new(),
2405 },
2406 ],
2407 meta: Vec::new(),
2408 };
2409 let mixed2 = KclValue::Tuple {
2410 value: vec![
2411 KclValue::Number {
2412 value: 0.0,
2413 ty: NumericType::count(),
2414 meta: Vec::new(),
2415 },
2416 KclValue::Bool {
2417 value: true,
2418 meta: Vec::new(),
2419 },
2420 ],
2421 meta: Vec::new(),
2422 };
2423
2424 let tyh = RuntimeType::Array(
2426 Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2427 ArrayLen::Known(4),
2428 );
2429 let tym1 = RuntimeType::Tuple(vec![
2430 RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2431 RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2432 ]);
2433 let tym2 = RuntimeType::Tuple(vec![
2434 RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2435 RuntimeType::Primitive(PrimitiveType::Boolean),
2436 ]);
2437 assert_coerce_results(&hom_arr, &tyh, &hom_arr, &mut exec_state);
2438 assert_coerce_results(&mixed1, &tym1, &mixed1, &mut exec_state);
2439 assert_coerce_results(&mixed2, &tym2, &mixed2, &mut exec_state);
2440 mixed1
2441 .coerce(&tym2, CoercionMode::implicit(), &mut exec_state)
2442 .unwrap_err();
2443 mixed2
2444 .coerce(&tym1, CoercionMode::implicit(), &mut exec_state)
2445 .unwrap_err();
2446
2447 let tyhn = RuntimeType::Array(
2449 Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2450 ArrayLen::None,
2451 );
2452 let tyh1 = RuntimeType::Array(
2453 Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2454 ArrayLen::Minimum(1),
2455 );
2456 let tyh3 = RuntimeType::Array(
2457 Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2458 ArrayLen::Known(3),
2459 );
2460 let tyhm3 = RuntimeType::Array(
2461 Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2462 ArrayLen::Minimum(3),
2463 );
2464 let tyhm5 = RuntimeType::Array(
2465 Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2466 ArrayLen::Minimum(5),
2467 );
2468 assert_coerce_results(&hom_arr, &tyhn, &hom_arr, &mut exec_state);
2469 assert_coerce_results(&hom_arr, &tyh1, &hom_arr, &mut exec_state);
2470 hom_arr
2471 .coerce(&tyh3, CoercionMode::implicit(), &mut exec_state)
2472 .unwrap_err();
2473 assert_coerce_results(&hom_arr, &tyhm3, &hom_arr, &mut exec_state);
2474 hom_arr
2475 .coerce(&tyhm5, CoercionMode::implicit(), &mut exec_state)
2476 .unwrap_err();
2477
2478 let hom_arr0 = KclValue::HomArray {
2479 value: vec![],
2480 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2481 };
2482 assert_coerce_results(&hom_arr0, &tyhn, &hom_arr0, &mut exec_state);
2483 hom_arr0
2484 .coerce(&tyh1, CoercionMode::implicit(), &mut exec_state)
2485 .unwrap_err();
2486 hom_arr0
2487 .coerce(&tyh3, CoercionMode::implicit(), &mut exec_state)
2488 .unwrap_err();
2489
2490 let tym1 = RuntimeType::Tuple(vec![
2493 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2494 RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2495 ]);
2496 let tym2 = RuntimeType::Tuple(vec![
2497 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2498 RuntimeType::Primitive(PrimitiveType::Boolean),
2499 ]);
2500 assert_coerce_results(&mixed1, &tym1, &mixed1, &mut exec_state);
2503 assert_coerce_results(&mixed2, &tym2, &mixed2, &mut exec_state);
2504
2505 let hom_arr_2 = KclValue::HomArray {
2507 value: vec![
2508 KclValue::Number {
2509 value: 0.0,
2510 ty: NumericType::count(),
2511 meta: Vec::new(),
2512 },
2513 KclValue::Number {
2514 value: 1.0,
2515 ty: NumericType::count(),
2516 meta: Vec::new(),
2517 },
2518 ],
2519 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2520 };
2521 let mixed0 = KclValue::Tuple {
2522 value: vec![],
2523 meta: Vec::new(),
2524 };
2525 assert_coerce_results(&mixed1, &tyhn, &hom_arr_2, &mut exec_state);
2526 assert_coerce_results(&mixed1, &tyh1, &hom_arr_2, &mut exec_state);
2527 assert_coerce_results(&mixed0, &tyhn, &hom_arr0, &mut exec_state);
2528 mixed0
2529 .coerce(&tyh, CoercionMode::implicit(), &mut exec_state)
2530 .unwrap_err();
2531 mixed0
2532 .coerce(&tyh1, CoercionMode::implicit(), &mut exec_state)
2533 .unwrap_err();
2534
2535 assert_coerce_results(&hom_arr_2, &tym1, &mixed1, &mut exec_state);
2537 hom_arr
2538 .coerce(&tym1, CoercionMode::implicit(), &mut exec_state)
2539 .unwrap_err();
2540 hom_arr_2
2541 .coerce(&tym2, CoercionMode::implicit(), &mut exec_state)
2542 .unwrap_err();
2543
2544 mixed0
2545 .coerce(&tym1, CoercionMode::implicit(), &mut exec_state)
2546 .unwrap_err();
2547 mixed0
2548 .coerce(&tym2, CoercionMode::implicit(), &mut exec_state)
2549 .unwrap_err();
2550 ctx.close().await;
2551 }
2552
2553 #[tokio::test(flavor = "multi_thread")]
2554 async fn coerce_union() {
2555 let (ctx, mut exec_state) = new_exec_state().await;
2556
2557 assert!(RuntimeType::Union(vec![]).subtype(&RuntimeType::Union(vec![
2559 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2560 RuntimeType::Primitive(PrimitiveType::Boolean)
2561 ])));
2562 assert!(
2563 RuntimeType::Union(vec![RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any))]).subtype(
2564 &RuntimeType::Union(vec![
2565 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2566 RuntimeType::Primitive(PrimitiveType::Boolean)
2567 ])
2568 )
2569 );
2570 assert!(
2571 RuntimeType::Union(vec![
2572 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2573 RuntimeType::Primitive(PrimitiveType::Boolean)
2574 ])
2575 .subtype(&RuntimeType::Union(vec![
2576 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2577 RuntimeType::Primitive(PrimitiveType::Boolean)
2578 ]))
2579 );
2580
2581 let count = KclValue::Number {
2583 value: 1.0,
2584 ty: NumericType::count(),
2585 meta: Vec::new(),
2586 };
2587
2588 let tya = RuntimeType::Union(vec![RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any))]);
2589 let tya2 = RuntimeType::Union(vec![
2590 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2591 RuntimeType::Primitive(PrimitiveType::Boolean),
2592 ]);
2593 assert_coerce_results(&count, &tya, &count, &mut exec_state);
2594 assert_coerce_results(&count, &tya2, &count, &mut exec_state);
2595
2596 let tyb = RuntimeType::Union(vec![RuntimeType::Primitive(PrimitiveType::Boolean)]);
2598 let tyb2 = RuntimeType::Union(vec![
2599 RuntimeType::Primitive(PrimitiveType::Boolean),
2600 RuntimeType::Primitive(PrimitiveType::String),
2601 ]);
2602 count
2603 .coerce(&tyb, CoercionMode::implicit(), &mut exec_state)
2604 .unwrap_err();
2605 count
2606 .coerce(&tyb2, CoercionMode::implicit(), &mut exec_state)
2607 .unwrap_err();
2608 ctx.close().await;
2609 }
2610
2611 #[test]
2612 fn union_subtyping_uses_member_subtyping() {
2613 let tagged_edge = RuntimeType::Primitive(PrimitiveType::TaggedEdge);
2614 let edge = RuntimeType::Primitive(PrimitiveType::Edge);
2615 let string = RuntimeType::string();
2616 let boolean = RuntimeType::bool();
2617
2618 let tagged_edge_or_string = RuntimeType::Union(vec![tagged_edge.clone(), string.clone()]);
2619 let edge_or_string = RuntimeType::Union(vec![edge.clone(), string.clone()]);
2620
2621 assert!(tagged_edge_or_string.subtype(&edge_or_string));
2623 assert!(!edge_or_string.subtype(&tagged_edge_or_string));
2625
2626 assert!(RuntimeType::Union(vec![tagged_edge.clone(), edge.clone()]).subtype(&edge));
2628 assert!(!RuntimeType::Union(vec![tagged_edge, boolean]).subtype(&edge));
2630
2631 assert!(RuntimeType::Union(vec![]).subtype(&string));
2633 }
2634
2635 #[test]
2636 fn nested_union_subtyping_is_associative_and_recursive() {
2637 let tagged_edge = RuntimeType::Primitive(PrimitiveType::TaggedEdge);
2638 let edge = RuntimeType::Primitive(PrimitiveType::Edge);
2639 let string = RuntimeType::string();
2640 let boolean = RuntimeType::bool();
2641
2642 let left_associative = RuntimeType::Union(vec![
2643 RuntimeType::Union(vec![string.clone(), boolean.clone()]),
2644 edge.clone(),
2645 ]);
2646 let right_associative =
2647 RuntimeType::Union(vec![string, RuntimeType::Union(vec![boolean.clone(), edge.clone()])]);
2648
2649 assert!(left_associative.subtype(&right_associative));
2651 assert!(right_associative.subtype(&left_associative));
2653
2654 let nested_edges = RuntimeType::Union(vec![
2655 RuntimeType::Union(vec![tagged_edge.clone(), edge.clone()]),
2656 tagged_edge.clone(),
2657 ]);
2658 assert!(nested_edges.subtype(&edge));
2660
2661 let nested_with_bool = RuntimeType::Union(vec![RuntimeType::Union(vec![tagged_edge, boolean]), edge.clone()]);
2662 assert!(!nested_with_bool.subtype(&edge));
2664 }
2665
2666 fn enum_ty(module_id: u32, name: &str) -> RuntimeType {
2667 RuntimeType::Enum(EnumTypeId::new(ModuleId::from_usize(module_id as usize), name))
2668 }
2669
2670 fn enum_def(module_id: u32, name: &str, variants: &[&str]) -> Arc<EnumTypeDef> {
2673 Arc::new(
2674 EnumTypeDef::new(
2675 EnumTypeId::new(ModuleId::from_usize(module_id as usize), name),
2676 variants.iter().map(|v| (*v).to_owned()).collect(),
2677 false,
2678 )
2679 .unwrap(),
2680 )
2681 }
2682
2683 #[test]
2684 fn enum_subtyping_is_nominal() {
2685 let color = enum_ty(0, "Color");
2686 let shape = enum_ty(0, "Shape");
2687
2688 assert!(color.subtype(&color));
2691 assert!(!color.subtype(&shape));
2693 assert!(!shape.subtype(&color));
2694 }
2695
2696 #[test]
2697 fn enum_identity_is_module_plus_declared_name() {
2698 assert!(!enum_ty(0, "Color").subtype(&enum_ty(1, "Color")));
2700 assert!(enum_ty(1, "Color").subtype(&enum_ty(1, "Color")));
2703 }
2704
2705 #[test]
2706 fn enum_participates_in_the_general_type_rules() {
2707 let color = enum_ty(0, "Color");
2708
2709 assert!(color.subtype(&RuntimeType::any()));
2711 assert!(RuntimeType::never().subtype(&color));
2712 assert!(!color.subtype(&RuntimeType::never()));
2713
2714 assert!(color.subtype(&RuntimeType::Union(vec![color.clone(), RuntimeType::string()])));
2717 assert!(!color.subtype(&RuntimeType::Union(vec![RuntimeType::string(), enum_ty(0, "Shape")])));
2718 assert!(color.subtype(&RuntimeType::Array(Box::new(color.clone()), ArrayLen::Known(1))));
2719 assert!(RuntimeType::Array(Box::new(color.clone()), ArrayLen::Known(1)).subtype(&color));
2720
2721 assert!(!color.subtype(&RuntimeType::string()));
2723 assert!(!RuntimeType::string().subtype(&color));
2724 }
2725
2726 #[test]
2727 fn enum_values_report_their_own_type() {
2728 let red = KclValue::Enum {
2729 value: Box::new(EnumValue::new(enum_def(0, "Color", &["Red"]), "Red", Vec::new())),
2730 };
2731
2732 assert_eq!(red.principal_type(), Some(enum_ty(0, "Color")));
2733 assert!(red.has_type(&enum_ty(0, "Color")));
2734 assert!(!red.has_type(&enum_ty(0, "Shape")));
2736 assert!(!red.has_type(&RuntimeType::string()));
2737 }
2738
2739 #[test]
2740 fn enum_types_display_by_declared_name() {
2741 let color = enum_ty(0, "Color");
2742
2743 assert_eq!(color.to_string(), "Color");
2744 assert_eq!(color.human_friendly_type(), "Color");
2745 assert_eq!(
2746 RuntimeType::Array(Box::new(color), ArrayLen::Minimum(1)).human_friendly_type(),
2747 "one or more `Color` values"
2748 );
2749 }
2750
2751 #[tokio::test(flavor = "multi_thread")]
2754 async fn from_alias_resolves_a_declared_enum_to_its_nominal_type() {
2755 let result = parse_execute("x = 1").await.unwrap();
2758 let ctx = result.exec_ctxt;
2759 let mut exec_state = result.exec_state;
2760 let id = EnumTypeId::new(ModuleId::default(), "Color");
2761 let source_range = SourceRange::default();
2762
2763 exec_state.mut_stack().push_new_root_env(true).unwrap();
2765 exec_state
2766 .mut_stack()
2767 .add(
2768 format!("{}Color", memory::TYPE_PREFIX),
2769 KclValue::Type {
2770 value: TypeDef::Enum(Arc::new(
2771 EnumTypeDef::new(id.clone(), vec!["Red".to_owned()], false).unwrap(),
2772 )),
2773 experimental: false,
2774 meta: vec![],
2775 },
2776 source_range,
2777 )
2778 .unwrap();
2779
2780 assert_eq!(
2781 RuntimeType::from_alias(&Name::new("Color"), &mut exec_state, &ctx, source_range, false)
2782 .await
2783 .unwrap(),
2784 RuntimeType::Enum(id)
2785 );
2786 RuntimeType::from_alias(&Name::new("Shape"), &mut exec_state, &ctx, source_range, false)
2788 .await
2789 .unwrap_err();
2790 }
2791
2792 #[tokio::test(flavor = "multi_thread")]
2793 async fn enum_coercion_requires_the_same_declaration() {
2794 let (ctx, mut exec_state) = new_exec_state().await;
2795 let red = KclValue::Enum {
2796 value: Box::new(EnumValue::new(enum_def(0, "Color", &["Red"]), "Red", Vec::new())),
2797 };
2798
2799 assert_eq!(
2801 red.coerce(&enum_ty(0, "Color"), CoercionMode::implicit(), &mut exec_state)
2802 .unwrap(),
2803 red
2804 );
2805 red.coerce(&enum_ty(0, "Shape"), CoercionMode::implicit(), &mut exec_state)
2808 .unwrap_err();
2809 red.coerce(&enum_ty(1, "Color"), CoercionMode::implicit(), &mut exec_state)
2810 .unwrap_err();
2811 red.coerce(&RuntimeType::string(), CoercionMode::implicit(), &mut exec_state)
2812 .unwrap_err();
2813 let string = KclValue::String {
2815 value: "Red".to_owned(),
2816 meta: Vec::new(),
2817 };
2818 string
2819 .coerce(&enum_ty(0, "Color"), CoercionMode::implicit(), &mut exec_state)
2820 .unwrap_err();
2821
2822 ctx.close().await;
2823 }
2824
2825 fn enum_value(module_id: u32, name: &str, variants: &[&str], variant: &str) -> KclValue {
2826 KclValue::Enum {
2827 value: Box::new(EnumValue::new(enum_def(module_id, name, variants), variant, Vec::new())),
2828 }
2829 }
2830
2831 fn string_value(value: &str) -> KclValue {
2832 KclValue::String {
2833 value: value.to_owned(),
2834 meta: Vec::new(),
2835 }
2836 }
2837
2838 #[tokio::test(flavor = "multi_thread")]
2845 async fn enum_projects_by_target_shape() {
2846 let (ctx, mut exec_state) = new_exec_state().await;
2847 let variants = &["Red", "Green"];
2848 let red = enum_value(0, "Color", variants, "Red");
2849 let green = enum_value(0, "Color", variants, "Green");
2850 let color = enum_ty(0, "Color");
2851 let string = RuntimeType::string();
2852 let strings = RuntimeType::Array(Box::new(string.clone()), ArrayLen::None);
2853 let array = |value: Vec<KclValue>, ty: RuntimeType| KclValue::HomArray { value, ty };
2854 let tuple = |value: Vec<KclValue>| KclValue::Tuple {
2855 value,
2856 meta: Vec::new(),
2857 };
2858
2859 #[allow(clippy::type_complexity)]
2860 let rows: Vec<(&str, KclValue, RuntimeType, Option<KclValue>, Option<KclValue>)> = vec![
2861 (
2862 "a bare enum",
2863 red.clone(),
2864 string.clone(),
2865 Some(string_value("Red")),
2866 None,
2867 ),
2868 (
2869 "an array, element by element",
2870 array(vec![red.clone(), green.clone()], color.clone()),
2871 strings.clone(),
2872 Some(array(vec![string_value("Red"), string_value("Green")], string.clone())),
2873 None,
2874 ),
2875 (
2876 "an array of arrays, so more than one level down",
2877 array(vec![array(vec![green.clone()], RuntimeType::any())], RuntimeType::any()),
2878 RuntimeType::Array(Box::new(strings.clone()), ArrayLen::None),
2879 Some(array(
2880 vec![array(vec![string_value("Green")], string.clone())],
2881 strings.clone(),
2882 )),
2883 None,
2884 ),
2885 (
2886 "a tuple, positionally, beside a value that needs nothing done",
2888 tuple(vec![red.clone(), string_value("plain")]),
2889 RuntimeType::Tuple(vec![string.clone(), string.clone()]),
2890 Some(tuple(vec![string_value("Red"), string_value("plain")])),
2891 None,
2892 ),
2893 (
2894 "a one-element array against a bare string",
2897 array(vec![red.clone()], RuntimeType::any()),
2898 string.clone(),
2899 Some(string_value("Red")),
2900 None,
2901 ),
2902 (
2903 "an object field, which projects nothing",
2909 KclValue::Object {
2910 value: HashMap::from([("c".to_owned(), red.clone())]),
2911 constrainable: false,
2912 object_kind: Default::default(),
2913 meta: Vec::new(),
2914 },
2915 RuntimeType::Object(vec![("c".to_owned(), string.clone())], false),
2916 None,
2917 None,
2918 ),
2919 (
2920 "its own type, which is a check rather than a conversion",
2921 red.clone(),
2922 color.clone(),
2923 Some(red.clone()),
2924 Some(red.clone()),
2925 ),
2926 (
2927 "another declaration, which projection is not a way around",
2928 red.clone(),
2929 enum_ty(0, "Shade"),
2930 None,
2931 None,
2932 ),
2933 ];
2934
2935 for (case, value, target, explicit, implicit) in rows {
2936 assert_eq!(
2937 value.coerce(&target, CoercionMode::explicit(), &mut exec_state).ok(),
2938 explicit,
2939 "explicit mode, case: {case}"
2940 );
2941 assert_eq!(
2942 value.coerce(&target, CoercionMode::implicit(), &mut exec_state).ok(),
2943 implicit,
2944 "implicit mode, case: {case}"
2945 );
2946 }
2947
2948 ctx.close().await;
2949 }
2950
2951 #[tokio::test(flavor = "multi_thread")]
2956 async fn enum_projection_ignores_the_order_a_union_was_written_in() {
2957 let (ctx, mut exec_state) = new_exec_state().await;
2958 let red = enum_value(0, "Color", &["Red"], "Red");
2959 let string = RuntimeType::string();
2960 let color = enum_ty(0, "Color");
2961 let shade = enum_ty(0, "Shade");
2962
2963 let rows: Vec<(&str, Vec<RuntimeType>, Option<KclValue>)> = vec![
2964 ("the enum first", vec![color.clone(), string.clone()], Some(red.clone())),
2965 ("the enum last", vec![string.clone(), color.clone()], Some(red.clone())),
2966 (
2967 "no member accepts an enum, so projection is what satisfies it",
2968 vec![RuntimeType::bool(), string.clone()],
2969 Some(string_value("Red")),
2970 ),
2971 (
2972 "a different enum is not a match, so this projects too",
2973 vec![shade.clone(), string.clone()],
2974 Some(string_value("Red")),
2975 ),
2976 (
2977 "a different enum with no string member is unsatisfiable",
2978 vec![shade, RuntimeType::bool()],
2979 None,
2980 ),
2981 ];
2982
2983 for (case, tys, expected) in rows {
2984 let union = RuntimeType::Union(tys);
2985 assert_eq!(
2986 red.coerce(&union, CoercionMode::explicit(), &mut exec_state).ok(),
2987 expected,
2988 "case: {case} ({union})"
2989 );
2990 }
2991
2992 ctx.close().await;
2993 }
2994
2995 #[tokio::test(flavor = "multi_thread")]
2999 async fn enum_projection_to_a_number_explains_itself() {
3000 let (ctx, mut exec_state) = new_exec_state().await;
3001 let red = enum_value(0, "Color", &["Red"], "Red");
3002 let message = "Cannot project enum `Color` to a number. An enum projects to `string`; projecting to a number is not supported yet.";
3003
3004 for (case, mode, expected) in [
3005 ("explicit", CoercionMode::explicit(), Some(message)),
3006 ("implicit", CoercionMode::implicit(), None),
3007 ] {
3008 let err = red.coerce(&RuntimeType::count(), mode, &mut exec_state).unwrap_err();
3009 assert_eq!(err.message.as_deref(), expected, "case: {case}");
3010 }
3011
3012 ctx.close().await;
3013 }
3014
3015 #[tokio::test(flavor = "multi_thread")]
3016 async fn never_is_bottom_and_uninhabited() {
3017 let (ctx, mut exec_state) = new_exec_state().await;
3018 let never = RuntimeType::never();
3019 let string = RuntimeType::string();
3020
3021 for ty in [
3022 RuntimeType::any(),
3023 string.clone(),
3024 RuntimeType::Array(Box::new(string.clone()), ArrayLen::None),
3025 RuntimeType::Tuple(vec![string.clone()]),
3026 RuntimeType::Object(vec![("value".to_owned(), string.clone())], false),
3027 RuntimeType::Union(vec![string.clone(), RuntimeType::bool()]),
3028 ] {
3029 assert!(never.subtype(&ty), "`never` should be a subtype of {ty}");
3030 }
3031
3032 assert!(!string.subtype(&never));
3033 assert!(RuntimeType::Union(vec![never.clone(), string.clone()]).subtype(&string));
3034
3035 for value in values(&mut exec_state) {
3036 value
3037 .coerce(&never, CoercionMode::implicit(), &mut exec_state)
3038 .unwrap_err();
3039 }
3040 ctx.close().await;
3041 }
3042
3043 #[tokio::test(flavor = "multi_thread")]
3044 async fn coerce_axes() {
3045 let (ctx, mut exec_state) = new_exec_state().await;
3046
3047 assert!(RuntimeType::Primitive(PrimitiveType::Axis2d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis2d)));
3049 assert!(RuntimeType::Primitive(PrimitiveType::Axis3d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis3d)));
3050 assert!(!RuntimeType::Primitive(PrimitiveType::Axis3d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis2d)));
3051 assert!(!RuntimeType::Primitive(PrimitiveType::Axis2d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis3d)));
3052
3053 let a2d = KclValue::Object {
3055 value: [
3056 (
3057 "origin".to_owned(),
3058 KclValue::HomArray {
3059 value: vec![
3060 KclValue::Number {
3061 value: 0.0,
3062 ty: NumericType::mm(),
3063 meta: Vec::new(),
3064 },
3065 KclValue::Number {
3066 value: 0.0,
3067 ty: NumericType::mm(),
3068 meta: Vec::new(),
3069 },
3070 ],
3071 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
3072 },
3073 ),
3074 (
3075 "direction".to_owned(),
3076 KclValue::HomArray {
3077 value: vec![
3078 KclValue::Number {
3079 value: 1.0,
3080 ty: NumericType::mm(),
3081 meta: Vec::new(),
3082 },
3083 KclValue::Number {
3084 value: 0.0,
3085 ty: NumericType::mm(),
3086 meta: Vec::new(),
3087 },
3088 ],
3089 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
3090 },
3091 ),
3092 ]
3093 .into(),
3094 meta: Vec::new(),
3095 constrainable: false,
3096 object_kind: Default::default(),
3097 };
3098 let a3d = KclValue::Object {
3099 value: [
3100 (
3101 "origin".to_owned(),
3102 KclValue::HomArray {
3103 value: vec![
3104 KclValue::Number {
3105 value: 0.0,
3106 ty: NumericType::mm(),
3107 meta: Vec::new(),
3108 },
3109 KclValue::Number {
3110 value: 0.0,
3111 ty: NumericType::mm(),
3112 meta: Vec::new(),
3113 },
3114 KclValue::Number {
3115 value: 0.0,
3116 ty: NumericType::mm(),
3117 meta: Vec::new(),
3118 },
3119 ],
3120 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
3121 },
3122 ),
3123 (
3124 "direction".to_owned(),
3125 KclValue::HomArray {
3126 value: vec![
3127 KclValue::Number {
3128 value: 1.0,
3129 ty: NumericType::mm(),
3130 meta: Vec::new(),
3131 },
3132 KclValue::Number {
3133 value: 0.0,
3134 ty: NumericType::mm(),
3135 meta: Vec::new(),
3136 },
3137 KclValue::Number {
3138 value: 1.0,
3139 ty: NumericType::mm(),
3140 meta: Vec::new(),
3141 },
3142 ],
3143 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
3144 },
3145 ),
3146 ]
3147 .into(),
3148 meta: Vec::new(),
3149 constrainable: false,
3150 object_kind: Default::default(),
3151 };
3152
3153 let ty2d = RuntimeType::Primitive(PrimitiveType::Axis2d);
3154 let ty3d = RuntimeType::Primitive(PrimitiveType::Axis3d);
3155
3156 assert_coerce_results(&a2d, &ty2d, &a2d, &mut exec_state);
3157 assert_coerce_results(&a3d, &ty3d, &a3d, &mut exec_state);
3158 assert_coerce_results(&a3d, &ty2d, &a2d, &mut exec_state);
3159 a2d.coerce(&ty3d, CoercionMode::implicit(), &mut exec_state)
3160 .unwrap_err();
3161 ctx.close().await;
3162 }
3163
3164 #[tokio::test(flavor = "multi_thread")]
3165 async fn coerce_numeric() {
3166 let (ctx, mut exec_state) = new_exec_state().await;
3167
3168 let count = KclValue::Number {
3169 value: 1.0,
3170 ty: NumericType::count(),
3171 meta: Vec::new(),
3172 };
3173 let mm = KclValue::Number {
3174 value: 1.0,
3175 ty: NumericType::mm(),
3176 meta: Vec::new(),
3177 };
3178 let inches = KclValue::Number {
3179 value: 1.0,
3180 ty: NumericType::Known(UnitType::Length(UnitLength::Inches)),
3181 meta: Vec::new(),
3182 };
3183 let rads = KclValue::Number {
3184 value: 1.0,
3185 ty: NumericType::Known(UnitType::Angle(UnitAngle::Radians)),
3186 meta: Vec::new(),
3187 };
3188 let default = KclValue::Number {
3189 value: 1.0,
3190 ty: NumericType::default(),
3191 meta: Vec::new(),
3192 };
3193 let any = KclValue::Number {
3194 value: 1.0,
3195 ty: NumericType::Any,
3196 meta: Vec::new(),
3197 };
3198 let unknown = KclValue::Number {
3199 value: 1.0,
3200 ty: NumericType::Unknown,
3201 meta: Vec::new(),
3202 };
3203
3204 assert_coerce_results(&count, &NumericType::count().into(), &count, &mut exec_state);
3206 assert_coerce_results(&mm, &NumericType::mm().into(), &mm, &mut exec_state);
3207 assert_coerce_results(&any, &NumericType::Any.into(), &any, &mut exec_state);
3208 assert_coerce_results(&unknown, &NumericType::Unknown.into(), &unknown, &mut exec_state);
3209 assert_coerce_results(&default, &NumericType::default().into(), &default, &mut exec_state);
3210
3211 assert_coerce_results(&count, &NumericType::Any.into(), &count, &mut exec_state);
3212 assert_coerce_results(&mm, &NumericType::Any.into(), &mm, &mut exec_state);
3213 assert_coerce_results(&unknown, &NumericType::Any.into(), &unknown, &mut exec_state);
3214 assert_coerce_results(&default, &NumericType::Any.into(), &default, &mut exec_state);
3215
3216 assert_eq!(
3217 default
3218 .coerce(
3219 &NumericType::Default {
3220 len: UnitLength::Yards,
3221 angle: UnitAngle::Degrees,
3222 }
3223 .into(),
3224 CoercionMode::implicit(),
3225 &mut exec_state
3226 )
3227 .unwrap(),
3228 default
3229 );
3230
3231 count
3233 .coerce(&NumericType::mm().into(), CoercionMode::implicit(), &mut exec_state)
3234 .unwrap_err();
3235 mm.coerce(&NumericType::count().into(), CoercionMode::implicit(), &mut exec_state)
3236 .unwrap_err();
3237 unknown
3238 .coerce(&NumericType::mm().into(), CoercionMode::implicit(), &mut exec_state)
3239 .unwrap_err();
3240 unknown
3241 .coerce(
3242 &NumericType::default().into(),
3243 CoercionMode::implicit(),
3244 &mut exec_state,
3245 )
3246 .unwrap_err();
3247
3248 count
3249 .coerce(&NumericType::Unknown.into(), CoercionMode::implicit(), &mut exec_state)
3250 .unwrap_err();
3251 mm.coerce(&NumericType::Unknown.into(), CoercionMode::implicit(), &mut exec_state)
3252 .unwrap_err();
3253 default
3254 .coerce(&NumericType::Unknown.into(), CoercionMode::implicit(), &mut exec_state)
3255 .unwrap_err();
3256
3257 assert_eq!(
3258 inches
3259 .coerce(&NumericType::mm().into(), CoercionMode::implicit(), &mut exec_state)
3260 .unwrap()
3261 .as_f64()
3262 .unwrap()
3263 .round(),
3264 25.0
3265 );
3266 assert_eq!(
3267 rads.coerce(
3268 &NumericType::Known(UnitType::Angle(UnitAngle::Degrees)).into(),
3269 CoercionMode::implicit(),
3270 &mut exec_state
3271 )
3272 .unwrap()
3273 .as_f64()
3274 .unwrap()
3275 .round(),
3276 57.0
3277 );
3278 assert_eq!(
3279 inches
3280 .coerce(
3281 &NumericType::default().into(),
3282 CoercionMode::implicit(),
3283 &mut exec_state
3284 )
3285 .unwrap()
3286 .as_f64()
3287 .unwrap()
3288 .round(),
3289 1.0
3290 );
3291 assert_eq!(
3292 rads.coerce(
3293 &NumericType::default().into(),
3294 CoercionMode::implicit(),
3295 &mut exec_state
3296 )
3297 .unwrap()
3298 .as_f64()
3299 .unwrap()
3300 .round(),
3301 1.0
3302 );
3303 ctx.close().await;
3304 }
3305
3306 #[track_caller]
3307 fn assert_value_and_type(name: &str, result: &ExecTestResults, expected: f64, expected_ty: NumericType) {
3308 let mem = result.exec_state.stack();
3309 match mem
3310 .memory
3311 .get_from_owned(name, result.mem_env, SourceRange::default(), 0)
3312 .unwrap()
3313 {
3314 KclValue::Number { value, ty, .. } => {
3315 assert_eq!(value.round(), expected);
3316 assert_eq!(ty, expected_ty);
3317 }
3318 _ => unreachable!(),
3319 }
3320 }
3321
3322 #[tokio::test(flavor = "multi_thread")]
3323 async fn combine_numeric() {
3324 let program = r#"a = 5 + 4
3325b = 5 - 2
3326c = 5mm - 2mm + 10mm
3327d = 5mm - 2 + 10
3328e = 5 - 2mm + 10
3329f = 30mm - 1inch
3330
3331g = 2 * 10
3332h = 2 * 10mm
3333i = 2mm * 10mm
3334j = 2_ * 10
3335k = 2_ * 3mm * 3mm
3336
3337l = 1 / 10
3338m = 2mm / 1mm
3339n = 10inch / 2mm
3340o = 3mm / 3
3341p = 3_ / 4
3342q = 4inch / 2_
3343
3344r = min([0, 3, 42])
3345s = min([0, 3mm, -42])
3346t = min([100, 3in, 142mm])
3347u = min([3rad, 4in])
3348"#;
3349
3350 let result = parse_execute(program).await.unwrap();
3351 assert_eq!(
3352 result.exec_state.issues().len(),
3353 5,
3354 "errors: {:?}",
3355 result.exec_state.issues()
3356 );
3357
3358 assert_value_and_type("a", &result, 9.0, NumericType::default());
3359 assert_value_and_type("b", &result, 3.0, NumericType::default());
3360 assert_value_and_type("c", &result, 13.0, NumericType::mm());
3361 assert_value_and_type("d", &result, 13.0, NumericType::mm());
3362 assert_value_and_type("e", &result, 13.0, NumericType::mm());
3363 assert_value_and_type("f", &result, 5.0, NumericType::mm());
3364
3365 assert_value_and_type("g", &result, 20.0, NumericType::default());
3366 assert_value_and_type("h", &result, 20.0, NumericType::mm());
3367 assert_value_and_type("i", &result, 20.0, NumericType::Unknown);
3368 assert_value_and_type("j", &result, 20.0, NumericType::default());
3369 assert_value_and_type("k", &result, 18.0, NumericType::Unknown);
3370
3371 assert_value_and_type("l", &result, 0.0, NumericType::default());
3372 assert_value_and_type("m", &result, 2.0, NumericType::count());
3373 assert_value_and_type("n", &result, 5.0, NumericType::Unknown);
3374 assert_value_and_type("o", &result, 1.0, NumericType::mm());
3375 assert_value_and_type("p", &result, 1.0, NumericType::count());
3376 assert_value_and_type(
3377 "q",
3378 &result,
3379 2.0,
3380 NumericType::Known(UnitType::Length(UnitLength::Inches)),
3381 );
3382
3383 assert_value_and_type("r", &result, 0.0, NumericType::default());
3384 assert_value_and_type("s", &result, -42.0, NumericType::mm());
3385 assert_value_and_type("t", &result, 3.0, NumericType::Unknown);
3386 assert_value_and_type("u", &result, 3.0, NumericType::Unknown);
3387 }
3388
3389 #[tokio::test(flavor = "multi_thread")]
3390 async fn bad_typed_arithmetic() {
3391 let program = r#"
3392a = 1rad
3393b = 180 / PI * a + 360
3394"#;
3395
3396 let result = parse_execute(program).await.unwrap();
3397
3398 assert_value_and_type("a", &result, 1.0, NumericType::radians());
3399 assert_value_and_type("b", &result, 417.0, NumericType::Unknown);
3400 }
3401
3402 #[tokio::test(flavor = "multi_thread")]
3403 async fn cos_coercions() {
3404 let program = r#"
3405a = cos(units::toRadians(30deg))
3406b = 3 / a
3407c = cos(30deg)
3408d = cos(1rad)
3409"#;
3410
3411 let result = parse_execute(program).await.unwrap();
3412 assert!(
3413 result.exec_state.issues().is_empty(),
3414 "{:?}",
3415 result.exec_state.issues()
3416 );
3417
3418 assert_value_and_type("a", &result, 1.0, NumericType::default());
3419 assert_value_and_type("b", &result, 3.0, NumericType::default());
3420 assert_value_and_type("c", &result, 1.0, NumericType::default());
3421 assert_value_and_type("d", &result, 1.0, NumericType::default());
3422 }
3423
3424 #[tokio::test(flavor = "multi_thread")]
3425 async fn coerce_nested_array() {
3426 let (ctx, mut exec_state) = new_exec_state().await;
3427
3428 let mixed1 = KclValue::HomArray {
3429 value: vec![
3430 KclValue::Number {
3431 value: 0.0,
3432 ty: NumericType::count(),
3433 meta: Vec::new(),
3434 },
3435 KclValue::Number {
3436 value: 1.0,
3437 ty: NumericType::count(),
3438 meta: Vec::new(),
3439 },
3440 KclValue::HomArray {
3441 value: vec![
3442 KclValue::Number {
3443 value: 2.0,
3444 ty: NumericType::count(),
3445 meta: Vec::new(),
3446 },
3447 KclValue::Number {
3448 value: 3.0,
3449 ty: NumericType::count(),
3450 meta: Vec::new(),
3451 },
3452 ],
3453 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
3454 },
3455 ],
3456 ty: RuntimeType::any(),
3457 };
3458
3459 let tym1 = RuntimeType::Array(
3461 Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
3462 ArrayLen::Minimum(1),
3463 );
3464
3465 let result = KclValue::HomArray {
3466 value: vec![
3467 KclValue::Number {
3468 value: 0.0,
3469 ty: NumericType::count(),
3470 meta: Vec::new(),
3471 },
3472 KclValue::Number {
3473 value: 1.0,
3474 ty: NumericType::count(),
3475 meta: Vec::new(),
3476 },
3477 KclValue::Number {
3478 value: 2.0,
3479 ty: NumericType::count(),
3480 meta: Vec::new(),
3481 },
3482 KclValue::Number {
3483 value: 3.0,
3484 ty: NumericType::count(),
3485 meta: Vec::new(),
3486 },
3487 ],
3488 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
3489 };
3490 assert_coerce_results(&mixed1, &tym1, &result, &mut exec_state);
3491 ctx.close().await;
3492 }
3493}