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