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