1use std::{collections::HashMap, str::FromStr};
2
3use anyhow::Result;
4use kittycad_modeling_cmds::units::{UnitAngle, UnitLength};
5use serde::{Deserialize, Serialize};
6
7use crate::{
8 CompilationError, KclError, SourceRange,
9 errors::KclErrorDetails,
10 exec::PlaneKind,
11 execution::{
12 ExecState, Plane, PlaneInfo, Point3d, annotations,
13 kcl_value::{KclValue, TypeDef},
14 memory::{self},
15 },
16 fmt,
17 parsing::{
18 ast::types::{PrimitiveType as AstPrimitiveType, Type},
19 token::NumericSuffix,
20 },
21 std::args::{FromKclValue, TyF64},
22};
23
24#[derive(Debug, Clone, PartialEq)]
25pub enum RuntimeType {
26 Primitive(PrimitiveType),
27 Array(Box<RuntimeType>, ArrayLen),
28 Union(Vec<RuntimeType>),
29 Tuple(Vec<RuntimeType>),
30 Object(Vec<(String, RuntimeType)>, bool),
31}
32
33impl RuntimeType {
34 pub fn any() -> Self {
35 RuntimeType::Primitive(PrimitiveType::Any)
36 }
37
38 pub fn any_array() -> Self {
39 RuntimeType::Array(Box::new(RuntimeType::Primitive(PrimitiveType::Any)), ArrayLen::None)
40 }
41
42 pub fn edge() -> Self {
43 RuntimeType::Primitive(PrimitiveType::Edge)
44 }
45
46 pub fn function() -> Self {
47 RuntimeType::Primitive(PrimitiveType::Function)
48 }
49
50 pub fn segment() -> Self {
51 RuntimeType::Primitive(PrimitiveType::Segment)
52 }
53
54 pub fn segments() -> Self {
56 RuntimeType::Array(Box::new(Self::segment()), ArrayLen::Minimum(1))
57 }
58
59 pub fn sketch() -> Self {
60 RuntimeType::Primitive(PrimitiveType::Sketch)
61 }
62
63 pub fn sketch_or_surface() -> Self {
64 RuntimeType::Union(vec![Self::sketch(), Self::plane(), Self::face()])
65 }
66
67 pub fn sketches() -> Self {
69 RuntimeType::Array(
70 Box::new(RuntimeType::Primitive(PrimitiveType::Sketch)),
71 ArrayLen::Minimum(1),
72 )
73 }
74
75 pub fn faces() -> Self {
77 RuntimeType::Array(
78 Box::new(RuntimeType::Primitive(PrimitiveType::Face)),
79 ArrayLen::Minimum(1),
80 )
81 }
82
83 pub fn tagged_faces() -> Self {
85 RuntimeType::Array(
86 Box::new(RuntimeType::Primitive(PrimitiveType::TaggedFace)),
87 ArrayLen::Minimum(1),
88 )
89 }
90
91 pub fn solids() -> Self {
93 RuntimeType::Array(
94 Box::new(RuntimeType::Primitive(PrimitiveType::Solid)),
95 ArrayLen::Minimum(1),
96 )
97 }
98
99 pub fn solid() -> Self {
100 RuntimeType::Primitive(PrimitiveType::Solid)
101 }
102
103 pub fn helices() -> Self {
105 RuntimeType::Array(
106 Box::new(RuntimeType::Primitive(PrimitiveType::Helix)),
107 ArrayLen::Minimum(1),
108 )
109 }
110 pub fn helix() -> Self {
111 RuntimeType::Primitive(PrimitiveType::Helix)
112 }
113
114 pub fn plane() -> Self {
115 RuntimeType::Primitive(PrimitiveType::Plane)
116 }
117
118 pub fn face() -> Self {
119 RuntimeType::Primitive(PrimitiveType::Face)
120 }
121
122 pub fn tag_decl() -> Self {
123 RuntimeType::Primitive(PrimitiveType::TagDecl)
124 }
125
126 pub fn tagged_face() -> Self {
127 RuntimeType::Primitive(PrimitiveType::TaggedFace)
128 }
129
130 pub fn tagged_face_or_segment() -> Self {
131 RuntimeType::Union(vec![
132 RuntimeType::Primitive(PrimitiveType::TaggedFace),
133 RuntimeType::Primitive(PrimitiveType::Segment),
134 ])
135 }
136
137 pub fn tagged_edge() -> Self {
138 RuntimeType::Primitive(PrimitiveType::TaggedEdge)
139 }
140
141 pub fn bool() -> Self {
142 RuntimeType::Primitive(PrimitiveType::Boolean)
143 }
144
145 pub fn string() -> Self {
146 RuntimeType::Primitive(PrimitiveType::String)
147 }
148
149 pub fn imported() -> Self {
150 RuntimeType::Primitive(PrimitiveType::ImportedGeometry)
151 }
152
153 pub fn point2d() -> Self {
155 RuntimeType::Array(Box::new(RuntimeType::length()), ArrayLen::Known(2))
156 }
157
158 pub fn point3d() -> Self {
160 RuntimeType::Array(Box::new(RuntimeType::length()), ArrayLen::Known(3))
161 }
162
163 pub fn length() -> Self {
164 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Known(UnitType::GenericLength)))
165 }
166
167 pub fn known_length(len: UnitLength) -> Self {
168 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Known(UnitType::Length(len))))
169 }
170
171 pub fn angle() -> Self {
172 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Known(UnitType::GenericAngle)))
173 }
174
175 pub fn radians() -> Self {
176 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Known(UnitType::Angle(
177 UnitAngle::Radians,
178 ))))
179 }
180
181 pub fn degrees() -> Self {
182 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Known(UnitType::Angle(
183 UnitAngle::Degrees,
184 ))))
185 }
186
187 pub fn count() -> Self {
188 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Known(UnitType::Count)))
189 }
190
191 pub fn num_any() -> Self {
192 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any))
193 }
194
195 pub fn from_parsed(
196 value: Type,
197 exec_state: &mut ExecState,
198 source_range: SourceRange,
199 constrainable: bool,
200 suppress_warnings: bool,
201 ) -> Result<Self, CompilationError> {
202 match value {
203 Type::Primitive(pt) => Self::from_parsed_primitive(pt, exec_state, source_range, suppress_warnings),
204 Type::Array { ty, len } => {
205 Self::from_parsed(*ty, exec_state, source_range, constrainable, suppress_warnings)
206 .map(|t| RuntimeType::Array(Box::new(t), len))
207 }
208 Type::Union { tys } => tys
209 .into_iter()
210 .map(|t| Self::from_parsed(t.inner, exec_state, source_range, constrainable, suppress_warnings))
211 .collect::<Result<Vec<_>, CompilationError>>()
212 .map(RuntimeType::Union),
213 Type::Object { properties } => properties
214 .into_iter()
215 .map(|(id, ty)| {
216 RuntimeType::from_parsed(ty.inner, exec_state, source_range, constrainable, suppress_warnings)
217 .map(|ty| (id.name.clone(), ty))
218 })
219 .collect::<Result<Vec<_>, CompilationError>>()
220 .map(|values| RuntimeType::Object(values, constrainable)),
221 }
222 }
223
224 fn from_parsed_primitive(
225 value: AstPrimitiveType,
226 exec_state: &mut ExecState,
227 source_range: SourceRange,
228 suppress_warnings: bool,
229 ) -> Result<Self, CompilationError> {
230 Ok(match value {
231 AstPrimitiveType::Any => RuntimeType::Primitive(PrimitiveType::Any),
232 AstPrimitiveType::None => RuntimeType::Primitive(PrimitiveType::None),
233 AstPrimitiveType::String => RuntimeType::Primitive(PrimitiveType::String),
234 AstPrimitiveType::Boolean => RuntimeType::Primitive(PrimitiveType::Boolean),
235 AstPrimitiveType::Number(suffix) => {
236 let ty = match suffix {
237 NumericSuffix::None => NumericType::Any,
238 _ => NumericType::from_parsed(suffix, &exec_state.mod_local.settings),
239 };
240 RuntimeType::Primitive(PrimitiveType::Number(ty))
241 }
242 AstPrimitiveType::Named { id } => Self::from_alias(&id.name, exec_state, source_range, suppress_warnings)?,
243 AstPrimitiveType::TagDecl => RuntimeType::Primitive(PrimitiveType::TagDecl),
244 AstPrimitiveType::ImportedGeometry => RuntimeType::Primitive(PrimitiveType::ImportedGeometry),
245 AstPrimitiveType::Function(_) => RuntimeType::Primitive(PrimitiveType::Function),
246 })
247 }
248
249 pub fn from_alias(
250 alias: &str,
251 exec_state: &mut ExecState,
252 source_range: SourceRange,
253 suppress_warnings: bool,
254 ) -> Result<Self, CompilationError> {
255 let ty_val = exec_state
256 .stack()
257 .get(&format!("{}{}", memory::TYPE_PREFIX, alias), source_range)
258 .map_err(|_| CompilationError::err(source_range, format!("Unknown type: {alias}")))?;
259
260 Ok(match ty_val {
261 KclValue::Type {
262 value, experimental, ..
263 } => {
264 let result = match value {
265 TypeDef::RustRepr(ty, _) => RuntimeType::Primitive(ty.clone()),
266 TypeDef::Alias(ty) => ty.clone(),
267 };
268 if *experimental && !suppress_warnings {
269 exec_state.warn_experimental(&format!("the type `{alias}`"), source_range);
270 }
271 result
272 }
273 _ => unreachable!(),
274 })
275 }
276
277 pub fn human_friendly_type(&self) -> String {
278 match self {
279 RuntimeType::Primitive(ty) => ty.to_string(),
280 RuntimeType::Array(ty, ArrayLen::None | ArrayLen::Minimum(0)) => {
281 format!("an array of {}", ty.display_multiple())
282 }
283 RuntimeType::Array(ty, ArrayLen::Minimum(1)) => format!("one or more {}", ty.display_multiple()),
284 RuntimeType::Array(ty, ArrayLen::Minimum(n)) => {
285 format!("an array of {n} or more {}", ty.display_multiple())
286 }
287 RuntimeType::Array(ty, ArrayLen::Known(n)) => format!("an array of {n} {}", ty.display_multiple()),
288 RuntimeType::Union(tys) => tys
289 .iter()
290 .map(Self::human_friendly_type)
291 .collect::<Vec<_>>()
292 .join(" or "),
293 RuntimeType::Tuple(tys) => format!(
294 "a tuple with values of types ({})",
295 tys.iter().map(Self::human_friendly_type).collect::<Vec<_>>().join(", ")
296 ),
297 RuntimeType::Object(..) => format!("an object with fields {self}"),
298 }
299 }
300
301 pub(crate) fn subtype(&self, sup: &RuntimeType) -> bool {
303 use RuntimeType::*;
304
305 match (self, sup) {
306 (_, Primitive(PrimitiveType::Any)) => true,
307 (Primitive(t1), Primitive(t2)) => t1.subtype(t2),
308 (Array(t1, l1), Array(t2, l2)) => t1.subtype(t2) && l1.subtype(*l2),
309 (Tuple(t1), Tuple(t2)) => t1.len() == t2.len() && t1.iter().zip(t2).all(|(t1, t2)| t1.subtype(t2)),
310
311 (Union(ts1), Union(ts2)) => ts1.iter().all(|t| ts2.contains(t)),
312 (t1, Union(ts2)) => ts2.iter().any(|t| t1.subtype(t)),
313
314 (Object(t1, _), Object(t2, _)) => t2
315 .iter()
316 .all(|(f, t)| t1.iter().any(|(ff, tt)| f == ff && tt.subtype(t))),
317
318 (t1, RuntimeType::Array(t2, l)) if t1.subtype(t2) && ArrayLen::Known(1).subtype(*l) => true,
320 (RuntimeType::Array(t1, ArrayLen::Known(1)), t2) if t1.subtype(t2) => true,
321 (t1, RuntimeType::Tuple(t2)) if !t2.is_empty() && t1.subtype(&t2[0]) => true,
322 (RuntimeType::Tuple(t1), t2) if t1.len() == 1 && t1[0].subtype(t2) => true,
323
324 (Object(t1, _), Primitive(PrimitiveType::Axis2d)) => {
326 t1.iter()
327 .any(|(n, t)| n == "origin" && t.subtype(&RuntimeType::point2d()))
328 && t1
329 .iter()
330 .any(|(n, t)| n == "direction" && t.subtype(&RuntimeType::point2d()))
331 }
332 (Object(t1, _), Primitive(PrimitiveType::Axis3d)) => {
333 t1.iter()
334 .any(|(n, t)| n == "origin" && t.subtype(&RuntimeType::point3d()))
335 && t1
336 .iter()
337 .any(|(n, t)| n == "direction" && t.subtype(&RuntimeType::point3d()))
338 }
339 (Primitive(PrimitiveType::Axis2d), Object(t2, _)) => {
340 t2.iter()
341 .any(|(n, t)| n == "origin" && t.subtype(&RuntimeType::point2d()))
342 && t2
343 .iter()
344 .any(|(n, t)| n == "direction" && t.subtype(&RuntimeType::point2d()))
345 }
346 (Primitive(PrimitiveType::Axis3d), Object(t2, _)) => {
347 t2.iter()
348 .any(|(n, t)| n == "origin" && t.subtype(&RuntimeType::point3d()))
349 && t2
350 .iter()
351 .any(|(n, t)| n == "direction" && t.subtype(&RuntimeType::point3d()))
352 }
353 _ => false,
354 }
355 }
356
357 fn display_multiple(&self) -> String {
358 match self {
359 RuntimeType::Primitive(ty) => ty.display_multiple(),
360 RuntimeType::Array(..) => "arrays".to_owned(),
361 RuntimeType::Union(tys) => tys
362 .iter()
363 .map(|t| t.display_multiple())
364 .collect::<Vec<_>>()
365 .join(" or "),
366 RuntimeType::Tuple(_) => "tuples".to_owned(),
367 RuntimeType::Object(..) => format!("objects with fields {self}"),
368 }
369 }
370}
371
372impl std::fmt::Display for RuntimeType {
373 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
374 match self {
375 RuntimeType::Primitive(t) => t.fmt(f),
376 RuntimeType::Array(t, l) => match l {
377 ArrayLen::None => write!(f, "[{t}]"),
378 ArrayLen::Minimum(n) => write!(f, "[{t}; {n}+]"),
379 ArrayLen::Known(n) => write!(f, "[{t}; {n}]"),
380 },
381 RuntimeType::Tuple(ts) => write!(
382 f,
383 "({})",
384 ts.iter().map(|t| t.to_string()).collect::<Vec<_>>().join(", ")
385 ),
386 RuntimeType::Union(ts) => write!(
387 f,
388 "{}",
389 ts.iter().map(|t| t.to_string()).collect::<Vec<_>>().join(" | ")
390 ),
391 RuntimeType::Object(items, _) => write!(
392 f,
393 "{{ {} }}",
394 items
395 .iter()
396 .map(|(n, t)| format!("{n}: {t}"))
397 .collect::<Vec<_>>()
398 .join(", ")
399 ),
400 }
401 }
402}
403
404#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, ts_rs::TS)]
405pub enum ArrayLen {
406 None,
407 Minimum(usize),
408 Known(usize),
409}
410
411impl ArrayLen {
412 pub fn subtype(self, other: ArrayLen) -> bool {
413 match (self, other) {
414 (_, ArrayLen::None) => true,
415 (ArrayLen::Minimum(s1), ArrayLen::Minimum(s2)) if s1 >= s2 => true,
416 (ArrayLen::Known(s1), ArrayLen::Minimum(s2)) if s1 >= s2 => true,
417 (ArrayLen::None, ArrayLen::Minimum(0)) => true,
418 (ArrayLen::Known(s1), ArrayLen::Known(s2)) if s1 == s2 => true,
419 _ => false,
420 }
421 }
422
423 pub fn satisfied(self, len: usize, allow_shrink: bool) -> Option<usize> {
425 match self {
426 ArrayLen::None => Some(len),
427 ArrayLen::Minimum(s) => (len >= s).then_some(len),
428 ArrayLen::Known(s) => (if allow_shrink { len >= s } else { len == s }).then_some(s),
429 }
430 }
431
432 pub fn human_friendly_type(self) -> String {
433 match self {
434 ArrayLen::None | ArrayLen::Minimum(0) => "any number of elements".to_owned(),
435 ArrayLen::Minimum(1) => "at least 1 element".to_owned(),
436 ArrayLen::Minimum(n) => format!("at least {n} elements"),
437 ArrayLen::Known(0) => "no elements".to_owned(),
438 ArrayLen::Known(1) => "exactly 1 element".to_owned(),
439 ArrayLen::Known(n) => format!("exactly {n} elements"),
440 }
441 }
442}
443
444#[derive(Debug, Clone, PartialEq)]
445pub enum PrimitiveType {
446 Any,
447 None,
448 Number(NumericType),
449 String,
450 Boolean,
451 TaggedEdge,
452 TaggedFace,
453 TagDecl,
454 GdtAnnotation,
455 Segment,
456 Sketch,
457 Constraint,
458 Solid,
459 Plane,
460 Helix,
461 Face,
462 Edge,
463 BoundedEdge,
464 Axis2d,
465 Axis3d,
466 ImportedGeometry,
467 Function,
468}
469
470impl PrimitiveType {
471 fn display_multiple(&self) -> String {
472 match self {
473 PrimitiveType::Any => "any values".to_owned(),
474 PrimitiveType::None => "none values".to_owned(),
475 PrimitiveType::Number(NumericType::Known(unit)) => format!("numbers({unit})"),
476 PrimitiveType::Number(_) => "numbers".to_owned(),
477 PrimitiveType::String => "strings".to_owned(),
478 PrimitiveType::Boolean => "bools".to_owned(),
479 PrimitiveType::GdtAnnotation => "GD&T Annotations".to_owned(),
480 PrimitiveType::Segment => "Segments".to_owned(),
481 PrimitiveType::Sketch => "Sketches".to_owned(),
482 PrimitiveType::Constraint => "Constraints".to_owned(),
483 PrimitiveType::Solid => "Solids".to_owned(),
484 PrimitiveType::Plane => "Planes".to_owned(),
485 PrimitiveType::Helix => "Helices".to_owned(),
486 PrimitiveType::Face => "Faces".to_owned(),
487 PrimitiveType::Edge => "Edges".to_owned(),
488 PrimitiveType::BoundedEdge => "BoundedEdges".to_owned(),
489 PrimitiveType::Axis2d => "2d axes".to_owned(),
490 PrimitiveType::Axis3d => "3d axes".to_owned(),
491 PrimitiveType::ImportedGeometry => "imported geometries".to_owned(),
492 PrimitiveType::Function => "functions".to_owned(),
493 PrimitiveType::TagDecl => "tag declarators".to_owned(),
494 PrimitiveType::TaggedEdge => "tagged edges".to_owned(),
495 PrimitiveType::TaggedFace => "tagged faces".to_owned(),
496 }
497 }
498
499 fn subtype(&self, other: &PrimitiveType) -> bool {
500 match (self, other) {
501 (_, PrimitiveType::Any) => true,
502 (PrimitiveType::Number(n1), PrimitiveType::Number(n2)) => n1.subtype(n2),
503 (PrimitiveType::TaggedEdge, PrimitiveType::TaggedFace)
504 | (PrimitiveType::TaggedEdge, PrimitiveType::Edge) => true,
505 (t1, t2) => t1 == t2,
506 }
507 }
508}
509
510impl std::fmt::Display for PrimitiveType {
511 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
512 match self {
513 PrimitiveType::Any => write!(f, "any"),
514 PrimitiveType::None => write!(f, "none"),
515 PrimitiveType::Number(NumericType::Known(unit)) => write!(f, "number({unit})"),
516 PrimitiveType::Number(NumericType::Unknown) => write!(f, "number(unknown units)"),
517 PrimitiveType::Number(NumericType::Default { .. }) => write!(f, "number"),
518 PrimitiveType::Number(NumericType::Any) => write!(f, "number(any units)"),
519 PrimitiveType::String => write!(f, "string"),
520 PrimitiveType::Boolean => write!(f, "bool"),
521 PrimitiveType::TagDecl => write!(f, "tag declarator"),
522 PrimitiveType::TaggedEdge => write!(f, "tagged edge"),
523 PrimitiveType::TaggedFace => write!(f, "tagged face"),
524 PrimitiveType::GdtAnnotation => write!(f, "GD&T Annotation"),
525 PrimitiveType::Segment => write!(f, "Segment"),
526 PrimitiveType::Sketch => write!(f, "Sketch"),
527 PrimitiveType::Constraint => write!(f, "Constraint"),
528 PrimitiveType::Solid => write!(f, "Solid"),
529 PrimitiveType::Plane => write!(f, "Plane"),
530 PrimitiveType::Face => write!(f, "Face"),
531 PrimitiveType::Edge => write!(f, "Edge"),
532 PrimitiveType::BoundedEdge => write!(f, "BoundedEdge"),
533 PrimitiveType::Axis2d => write!(f, "Axis2d"),
534 PrimitiveType::Axis3d => write!(f, "Axis3d"),
535 PrimitiveType::Helix => write!(f, "Helix"),
536 PrimitiveType::ImportedGeometry => write!(f, "ImportedGeometry"),
537 PrimitiveType::Function => write!(f, "fn"),
538 }
539 }
540}
541
542#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, ts_rs::TS)]
543#[ts(export)]
544#[serde(tag = "type")]
545pub enum NumericType {
546 Known(UnitType),
548 Default { len: UnitLength, angle: UnitAngle },
550 Unknown,
552 Any,
554}
555
556impl Default for NumericType {
557 fn default() -> Self {
558 NumericType::Default {
559 len: UnitLength::Millimeters,
560 angle: UnitAngle::Degrees,
561 }
562 }
563}
564
565impl NumericType {
566 pub const fn count() -> Self {
567 NumericType::Known(UnitType::Count)
568 }
569
570 pub const fn mm() -> Self {
571 NumericType::Known(UnitType::Length(UnitLength::Millimeters))
572 }
573
574 pub const fn radians() -> Self {
575 NumericType::Known(UnitType::Angle(UnitAngle::Radians))
576 }
577
578 pub const fn degrees() -> Self {
579 NumericType::Known(UnitType::Angle(UnitAngle::Degrees))
580 }
581
582 pub fn combine_eq(
588 a: TyF64,
589 b: TyF64,
590 exec_state: &mut ExecState,
591 source_range: SourceRange,
592 ) -> (f64, f64, NumericType) {
593 use NumericType::*;
594 match (a.ty, b.ty) {
595 (at, bt) if at == bt => (a.n, b.n, at),
596 (at, Any) => (a.n, b.n, at),
597 (Any, bt) => (a.n, b.n, bt),
598
599 (t @ Known(UnitType::Length(l1)), Known(UnitType::Length(l2))) => (a.n, adjust_length(l2, b.n, l1).0, t),
600 (t @ Known(UnitType::Angle(a1)), Known(UnitType::Angle(a2))) => (a.n, adjust_angle(a2, b.n, a1).0, t),
601
602 (t @ Known(UnitType::Length(_)), Known(UnitType::GenericLength)) => (a.n, b.n, t),
603 (Known(UnitType::GenericLength), t @ Known(UnitType::Length(_))) => (a.n, b.n, t),
604 (t @ Known(UnitType::Angle(_)), Known(UnitType::GenericAngle)) => (a.n, b.n, t),
605 (Known(UnitType::GenericAngle), t @ Known(UnitType::Angle(_))) => (a.n, b.n, t),
606
607 (Known(UnitType::Count), Default { .. }) | (Default { .. }, Known(UnitType::Count)) => {
608 (a.n, b.n, Known(UnitType::Count))
609 }
610 (t @ Known(UnitType::Length(l1)), Default { len: l2, .. }) if l1 == l2 => (a.n, b.n, t),
611 (Default { len: l1, .. }, t @ Known(UnitType::Length(l2))) if l1 == l2 => (a.n, b.n, t),
612 (t @ Known(UnitType::Angle(a1)), Default { angle: a2, .. }) if a1 == a2 => {
613 if b.n != 0.0 {
614 exec_state.warn(
615 CompilationError::err(source_range, "Prefer to use explicit units for angles"),
616 annotations::WARN_ANGLE_UNITS,
617 );
618 }
619 (a.n, b.n, t)
620 }
621 (Default { angle: a1, .. }, t @ Known(UnitType::Angle(a2))) if a1 == a2 => {
622 if a.n != 0.0 {
623 exec_state.warn(
624 CompilationError::err(source_range, "Prefer to use explicit units for angles"),
625 annotations::WARN_ANGLE_UNITS,
626 );
627 }
628 (a.n, b.n, t)
629 }
630
631 _ => (a.n, b.n, Unknown),
632 }
633 }
634
635 pub fn combine_eq_coerce(
643 a: TyF64,
644 b: TyF64,
645 for_errs: Option<(&mut ExecState, SourceRange)>,
646 ) -> (f64, f64, NumericType) {
647 use NumericType::*;
648 match (a.ty, b.ty) {
649 (at, bt) if at == bt => (a.n, b.n, at),
650 (at, Any) => (a.n, b.n, at),
651 (Any, bt) => (a.n, b.n, bt),
652
653 (t @ Known(UnitType::Length(l1)), Known(UnitType::Length(l2))) => (a.n, adjust_length(l2, b.n, l1).0, t),
655 (t @ Known(UnitType::Angle(a1)), Known(UnitType::Angle(a2))) => (a.n, adjust_angle(a2, b.n, a1).0, t),
656
657 (t @ Known(UnitType::Length(_)), Known(UnitType::GenericLength)) => (a.n, b.n, t),
658 (Known(UnitType::GenericLength), t @ Known(UnitType::Length(_))) => (a.n, b.n, t),
659 (t @ Known(UnitType::Angle(_)), Known(UnitType::GenericAngle)) => (a.n, b.n, t),
660 (Known(UnitType::GenericAngle), t @ Known(UnitType::Angle(_))) => (a.n, b.n, t),
661
662 (Known(UnitType::Count), Default { .. }) | (Default { .. }, Known(UnitType::Count)) => {
664 (a.n, b.n, Known(UnitType::Count))
665 }
666
667 (t @ Known(UnitType::Length(l1)), Default { len: l2, .. }) => (a.n, adjust_length(l2, b.n, l1).0, t),
668 (Default { len: l1, .. }, t @ Known(UnitType::Length(l2))) => (adjust_length(l1, a.n, l2).0, b.n, t),
669 (t @ Known(UnitType::Angle(a1)), Default { angle: a2, .. }) => {
670 if let Some((exec_state, source_range)) = for_errs
671 && b.n != 0.0
672 {
673 exec_state.warn(
674 CompilationError::err(source_range, "Prefer to use explicit units for angles"),
675 annotations::WARN_ANGLE_UNITS,
676 );
677 }
678 (a.n, adjust_angle(a2, b.n, a1).0, t)
679 }
680 (Default { angle: a1, .. }, t @ Known(UnitType::Angle(a2))) => {
681 if let Some((exec_state, source_range)) = for_errs
682 && a.n != 0.0
683 {
684 exec_state.warn(
685 CompilationError::err(source_range, "Prefer to use explicit units for angles"),
686 annotations::WARN_ANGLE_UNITS,
687 );
688 }
689 (adjust_angle(a1, a.n, a2).0, b.n, t)
690 }
691
692 (Default { len: l1, .. }, Known(UnitType::GenericLength)) => (a.n, b.n, l1.into()),
693 (Known(UnitType::GenericLength), Default { len: l2, .. }) => (a.n, b.n, l2.into()),
694 (Default { angle: a1, .. }, Known(UnitType::GenericAngle)) => {
695 if let Some((exec_state, source_range)) = for_errs
696 && b.n != 0.0
697 {
698 exec_state.warn(
699 CompilationError::err(source_range, "Prefer to use explicit units for angles"),
700 annotations::WARN_ANGLE_UNITS,
701 );
702 }
703 (a.n, b.n, a1.into())
704 }
705 (Known(UnitType::GenericAngle), Default { angle: a2, .. }) => {
706 if let Some((exec_state, source_range)) = for_errs
707 && a.n != 0.0
708 {
709 exec_state.warn(
710 CompilationError::err(source_range, "Prefer to use explicit units for angles"),
711 annotations::WARN_ANGLE_UNITS,
712 );
713 }
714 (a.n, b.n, a2.into())
715 }
716
717 (Known(_), Known(_)) | (Default { .. }, Default { .. }) | (_, Unknown) | (Unknown, _) => {
718 (a.n, b.n, Unknown)
719 }
720 }
721 }
722
723 pub fn combine_eq_array(input: &[TyF64]) -> (Vec<f64>, NumericType) {
724 use NumericType::*;
725 let result = input.iter().map(|t| t.n).collect();
726
727 let mut ty = Any;
728 for i in input {
729 if i.ty == Any || ty == i.ty {
730 continue;
731 }
732
733 match (&ty, &i.ty) {
735 (Any, Default { .. }) if i.n == 0.0 => {}
736 (Any, t) => {
737 ty = *t;
738 }
739 (_, Unknown) | (Default { .. }, Default { .. }) => return (result, Unknown),
740
741 (Known(UnitType::Count), Default { .. }) | (Default { .. }, Known(UnitType::Count)) => {
742 ty = Known(UnitType::Count);
743 }
744
745 (Known(UnitType::Length(l1)), Default { len: l2, .. }) if l1 == l2 || i.n == 0.0 => {}
746 (Known(UnitType::Angle(a1)), Default { angle: a2, .. }) if a1 == a2 || i.n == 0.0 => {}
747
748 (Default { len: l1, .. }, Known(UnitType::Length(l2))) if l1 == l2 => {
749 ty = Known(UnitType::Length(*l2));
750 }
751 (Default { angle: a1, .. }, Known(UnitType::Angle(a2))) if a1 == a2 => {
752 ty = Known(UnitType::Angle(*a2));
753 }
754
755 _ => return (result, Unknown),
756 }
757 }
758
759 if ty == Any && !input.is_empty() {
760 ty = input[0].ty;
761 }
762
763 (result, ty)
764 }
765
766 pub fn combine_mul(a: TyF64, b: TyF64) -> (f64, f64, NumericType) {
768 use NumericType::*;
769 match (a.ty, b.ty) {
770 (at @ Default { .. }, bt @ Default { .. }) if at == bt => (a.n, b.n, at),
771 (Default { .. }, Default { .. }) => (a.n, b.n, Unknown),
772 (Known(UnitType::Count), bt) => (a.n, b.n, bt),
773 (at, Known(UnitType::Count)) => (a.n, b.n, at),
774 (at @ Known(_), Default { .. }) | (Default { .. }, at @ Known(_)) => (a.n, b.n, at),
775 (Any, Any) => (a.n, b.n, Any),
776 _ => (a.n, b.n, Unknown),
777 }
778 }
779
780 pub fn combine_div(a: TyF64, b: TyF64) -> (f64, f64, NumericType) {
782 use NumericType::*;
783 match (a.ty, b.ty) {
784 (at @ Default { .. }, bt @ Default { .. }) if at == bt => (a.n, b.n, at),
785 (at, bt) if at == bt => (a.n, b.n, Known(UnitType::Count)),
786 (Default { .. }, Default { .. }) => (a.n, b.n, Unknown),
787 (at, Known(UnitType::Count) | Any) => (a.n, b.n, at),
788 (at @ Known(_), Default { .. }) => (a.n, b.n, at),
789 (Known(UnitType::Count), _) => (a.n, b.n, Known(UnitType::Count)),
790 _ => (a.n, b.n, Unknown),
791 }
792 }
793
794 pub fn combine_mod(a: TyF64, b: TyF64) -> (f64, f64, NumericType) {
796 use NumericType::*;
797 match (a.ty, b.ty) {
798 (at @ Default { .. }, bt @ Default { .. }) if at == bt => (a.n, b.n, at),
799 (at, bt) if at == bt => (a.n, b.n, at),
800 (Default { .. }, Default { .. }) => (a.n, b.n, Unknown),
801 (at, Known(UnitType::Count) | Any) => (a.n, b.n, at),
802 (at @ Known(_), Default { .. }) => (a.n, b.n, at),
803 (Known(UnitType::Count), _) => (a.n, b.n, Known(UnitType::Count)),
804 _ => (a.n, b.n, Unknown),
805 }
806 }
807
808 pub fn combine_range(
814 a: TyF64,
815 b: TyF64,
816 exec_state: &mut ExecState,
817 source_range: SourceRange,
818 ) -> Result<(f64, f64, NumericType), KclError> {
819 use NumericType::*;
820 match (a.ty, b.ty) {
821 (at, bt) if at == bt => Ok((a.n, b.n, at)),
822 (at, Any) => Ok((a.n, b.n, at)),
823 (Any, bt) => Ok((a.n, b.n, bt)),
824
825 (Known(UnitType::Length(l1)), Known(UnitType::Length(l2))) => {
826 Err(KclError::new_semantic(KclErrorDetails::new(
827 format!("Range start and range end have incompatible units: {l1} and {l2}"),
828 vec![source_range],
829 )))
830 }
831 (Known(UnitType::Angle(a1)), Known(UnitType::Angle(a2))) => {
832 Err(KclError::new_semantic(KclErrorDetails::new(
833 format!("Range start and range end have incompatible units: {a1} and {a2}"),
834 vec![source_range],
835 )))
836 }
837
838 (t @ Known(UnitType::Length(_)), Known(UnitType::GenericLength)) => Ok((a.n, b.n, t)),
839 (Known(UnitType::GenericLength), t @ Known(UnitType::Length(_))) => Ok((a.n, b.n, t)),
840 (t @ Known(UnitType::Angle(_)), Known(UnitType::GenericAngle)) => Ok((a.n, b.n, t)),
841 (Known(UnitType::GenericAngle), t @ Known(UnitType::Angle(_))) => Ok((a.n, b.n, t)),
842
843 (Known(UnitType::Count), Default { .. }) | (Default { .. }, Known(UnitType::Count)) => {
844 Ok((a.n, b.n, Known(UnitType::Count)))
845 }
846 (t @ Known(UnitType::Length(l1)), Default { len: l2, .. }) if l1 == l2 => Ok((a.n, b.n, t)),
847 (Default { len: l1, .. }, t @ Known(UnitType::Length(l2))) if l1 == l2 => Ok((a.n, b.n, t)),
848 (t @ Known(UnitType::Angle(a1)), Default { angle: a2, .. }) if a1 == a2 => {
849 if b.n != 0.0 {
850 exec_state.warn(
851 CompilationError::err(source_range, "Prefer to use explicit units for angles"),
852 annotations::WARN_ANGLE_UNITS,
853 );
854 }
855 Ok((a.n, b.n, t))
856 }
857 (Default { angle: a1, .. }, t @ Known(UnitType::Angle(a2))) if a1 == a2 => {
858 if a.n != 0.0 {
859 exec_state.warn(
860 CompilationError::err(source_range, "Prefer to use explicit units for angles"),
861 annotations::WARN_ANGLE_UNITS,
862 );
863 }
864 Ok((a.n, b.n, t))
865 }
866
867 _ => {
868 let a = fmt::human_display_number(a.n, a.ty);
869 let b = fmt::human_display_number(b.n, b.ty);
870 Err(KclError::new_semantic(KclErrorDetails::new(
871 format!(
872 "Range start and range end must be of the same type and have compatible units, but found {a} and {b}",
873 ),
874 vec![source_range],
875 )))
876 }
877 }
878 }
879
880 pub fn from_parsed(suffix: NumericSuffix, settings: &super::MetaSettings) -> Self {
881 match suffix {
882 NumericSuffix::None => NumericType::Default {
883 len: settings.default_length_units,
884 angle: settings.default_angle_units,
885 },
886 NumericSuffix::Count => NumericType::Known(UnitType::Count),
887 NumericSuffix::Length => NumericType::Known(UnitType::GenericLength),
888 NumericSuffix::Angle => NumericType::Known(UnitType::GenericAngle),
889 NumericSuffix::Mm => NumericType::Known(UnitType::Length(UnitLength::Millimeters)),
890 NumericSuffix::Cm => NumericType::Known(UnitType::Length(UnitLength::Centimeters)),
891 NumericSuffix::M => NumericType::Known(UnitType::Length(UnitLength::Meters)),
892 NumericSuffix::Inch => NumericType::Known(UnitType::Length(UnitLength::Inches)),
893 NumericSuffix::Ft => NumericType::Known(UnitType::Length(UnitLength::Feet)),
894 NumericSuffix::Yd => NumericType::Known(UnitType::Length(UnitLength::Yards)),
895 NumericSuffix::Deg => NumericType::Known(UnitType::Angle(UnitAngle::Degrees)),
896 NumericSuffix::Rad => NumericType::Known(UnitType::Angle(UnitAngle::Radians)),
897 NumericSuffix::Unknown => NumericType::Unknown,
898 }
899 }
900
901 fn subtype(&self, other: &NumericType) -> bool {
902 use NumericType::*;
903
904 match (self, other) {
905 (_, Any) => true,
906 (a, b) if a == b => true,
907 (
908 NumericType::Known(UnitType::Length(_))
909 | NumericType::Known(UnitType::GenericLength)
910 | NumericType::Default { .. },
911 NumericType::Known(UnitType::GenericLength),
912 )
913 | (
914 NumericType::Known(UnitType::Angle(_))
915 | NumericType::Known(UnitType::GenericAngle)
916 | NumericType::Default { .. },
917 NumericType::Known(UnitType::GenericAngle),
918 ) => true,
919 (Unknown, _) | (_, Unknown) => false,
920 (_, _) => false,
921 }
922 }
923
924 fn is_unknown(&self) -> bool {
925 matches!(
926 self,
927 NumericType::Unknown
928 | NumericType::Known(UnitType::GenericAngle)
929 | NumericType::Known(UnitType::GenericLength)
930 )
931 }
932
933 pub fn is_fully_specified(&self) -> bool {
934 !matches!(
935 self,
936 NumericType::Unknown
937 | NumericType::Known(UnitType::GenericAngle)
938 | NumericType::Known(UnitType::GenericLength)
939 | NumericType::Any
940 | NumericType::Default { .. }
941 )
942 }
943
944 fn example_ty(&self) -> Option<String> {
945 match self {
946 Self::Known(t) if !self.is_unknown() => Some(t.to_string()),
947 Self::Default { len, .. } => Some(len.to_string()),
948 _ => None,
949 }
950 }
951
952 fn coerce(&self, val: &KclValue) -> Result<KclValue, CoercionError> {
953 let (value, ty, meta) = match val {
954 KclValue::Number { value, ty, meta } => (value, ty, meta),
955 KclValue::SketchVar { .. } => return Ok(val.clone()),
959 _ => return Err(val.into()),
960 };
961
962 if ty.subtype(self) {
963 return Ok(KclValue::Number {
964 value: *value,
965 ty: *ty,
966 meta: meta.clone(),
967 });
968 }
969
970 use NumericType::*;
972 match (ty, self) {
973 (Unknown, _) => Err(CoercionError::from(val).with_explicit(self.example_ty().unwrap_or("mm".to_owned()))),
975 (_, Unknown) => Err(val.into()),
976
977 (Any, _) => Ok(KclValue::Number {
978 value: *value,
979 ty: *self,
980 meta: meta.clone(),
981 }),
982
983 (_, Default { .. }) => Ok(KclValue::Number {
986 value: *value,
987 ty: *ty,
988 meta: meta.clone(),
989 }),
990
991 (Known(UnitType::Length(l1)), Known(UnitType::Length(l2))) => {
993 let (value, ty) = adjust_length(*l1, *value, *l2);
994 Ok(KclValue::Number {
995 value,
996 ty: Known(UnitType::Length(ty)),
997 meta: meta.clone(),
998 })
999 }
1000 (Known(UnitType::Angle(a1)), Known(UnitType::Angle(a2))) => {
1001 let (value, ty) = adjust_angle(*a1, *value, *a2);
1002 Ok(KclValue::Number {
1003 value,
1004 ty: Known(UnitType::Angle(ty)),
1005 meta: meta.clone(),
1006 })
1007 }
1008
1009 (Known(_), Known(_)) => Err(val.into()),
1011
1012 (Default { .. }, Known(UnitType::Count)) => Ok(KclValue::Number {
1014 value: *value,
1015 ty: Known(UnitType::Count),
1016 meta: meta.clone(),
1017 }),
1018
1019 (Default { len: l1, .. }, Known(UnitType::Length(l2))) => {
1020 let (value, ty) = adjust_length(*l1, *value, *l2);
1021 Ok(KclValue::Number {
1022 value,
1023 ty: Known(UnitType::Length(ty)),
1024 meta: meta.clone(),
1025 })
1026 }
1027
1028 (Default { angle: a1, .. }, Known(UnitType::Angle(a2))) => {
1029 let (value, ty) = adjust_angle(*a1, *value, *a2);
1030 Ok(KclValue::Number {
1031 value,
1032 ty: Known(UnitType::Angle(ty)),
1033 meta: meta.clone(),
1034 })
1035 }
1036
1037 (_, _) => unreachable!(),
1038 }
1039 }
1040
1041 pub fn as_length(&self) -> Option<UnitLength> {
1042 match self {
1043 Self::Known(UnitType::Length(len)) | Self::Default { len, .. } => Some(*len),
1044 _ => None,
1045 }
1046 }
1047}
1048
1049impl From<NumericType> for RuntimeType {
1050 fn from(t: NumericType) -> RuntimeType {
1051 RuntimeType::Primitive(PrimitiveType::Number(t))
1052 }
1053}
1054
1055impl From<UnitLength> for NumericType {
1056 fn from(value: UnitLength) -> Self {
1057 NumericType::Known(UnitType::Length(value))
1058 }
1059}
1060
1061impl From<Option<UnitLength>> for NumericType {
1062 fn from(value: Option<UnitLength>) -> Self {
1063 match value {
1064 Some(v) => v.into(),
1065 None => NumericType::Unknown,
1066 }
1067 }
1068}
1069
1070impl From<UnitAngle> for NumericType {
1071 fn from(value: UnitAngle) -> Self {
1072 NumericType::Known(UnitType::Angle(value))
1073 }
1074}
1075
1076impl From<UnitLength> for NumericSuffix {
1077 fn from(value: UnitLength) -> Self {
1078 match value {
1079 UnitLength::Millimeters => NumericSuffix::Mm,
1080 UnitLength::Centimeters => NumericSuffix::Cm,
1081 UnitLength::Meters => NumericSuffix::M,
1082 UnitLength::Inches => NumericSuffix::Inch,
1083 UnitLength::Feet => NumericSuffix::Ft,
1084 UnitLength::Yards => NumericSuffix::Yd,
1085 }
1086 }
1087}
1088
1089#[derive(Debug, Clone, Copy, PartialEq, Eq, Deserialize, Serialize, ts_rs::TS)]
1090pub struct NumericSuffixTypeConvertError;
1091
1092impl TryFrom<NumericType> for NumericSuffix {
1093 type Error = NumericSuffixTypeConvertError;
1094
1095 fn try_from(value: NumericType) -> Result<Self, Self::Error> {
1096 match value {
1097 NumericType::Known(UnitType::Count) => Ok(NumericSuffix::Count),
1098 NumericType::Known(UnitType::Length(unit_length)) => Ok(NumericSuffix::from(unit_length)),
1099 NumericType::Known(UnitType::GenericLength) => Ok(NumericSuffix::Length),
1100 NumericType::Known(UnitType::Angle(UnitAngle::Degrees)) => Ok(NumericSuffix::Deg),
1101 NumericType::Known(UnitType::Angle(UnitAngle::Radians)) => Ok(NumericSuffix::Rad),
1102 NumericType::Known(UnitType::GenericAngle) => Ok(NumericSuffix::Angle),
1103 NumericType::Default { .. } => Ok(NumericSuffix::None),
1104 NumericType::Unknown => Ok(NumericSuffix::Unknown),
1105 NumericType::Any => Err(NumericSuffixTypeConvertError),
1106 }
1107 }
1108}
1109
1110#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, ts_rs::TS)]
1111#[ts(export)]
1112#[serde(tag = "type")]
1113pub enum UnitType {
1114 Count,
1115 Length(UnitLength),
1116 GenericLength,
1117 Angle(UnitAngle),
1118 GenericAngle,
1119}
1120
1121impl UnitType {
1122 pub(crate) fn to_suffix(self) -> Option<String> {
1123 match self {
1124 UnitType::Count => Some("_".to_owned()),
1125 UnitType::GenericLength | UnitType::GenericAngle => None,
1126 UnitType::Length(l) => Some(l.to_string()),
1127 UnitType::Angle(a) => Some(a.to_string()),
1128 }
1129 }
1130}
1131
1132impl std::fmt::Display for UnitType {
1133 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1134 match self {
1135 UnitType::Count => write!(f, "Count"),
1136 UnitType::Length(l) => l.fmt(f),
1137 UnitType::GenericLength => write!(f, "Length"),
1138 UnitType::Angle(a) => a.fmt(f),
1139 UnitType::GenericAngle => write!(f, "Angle"),
1140 }
1141 }
1142}
1143
1144pub fn adjust_length(from: UnitLength, value: f64, to: UnitLength) -> (f64, UnitLength) {
1145 use UnitLength::*;
1146
1147 if from == to {
1148 return (value, to);
1149 }
1150
1151 let (base, base_unit) = match from {
1152 Millimeters => (value, Millimeters),
1153 Centimeters => (value * 10.0, Millimeters),
1154 Meters => (value * 1000.0, Millimeters),
1155 Inches => (value, Inches),
1156 Feet => (value * 12.0, Inches),
1157 Yards => (value * 36.0, Inches),
1158 };
1159 let (base, base_unit) = match (base_unit, to) {
1160 (Millimeters, Inches) | (Millimeters, Feet) | (Millimeters, Yards) => (base / 25.4, Inches),
1161 (Inches, Millimeters) | (Inches, Centimeters) | (Inches, Meters) => (base * 25.4, Millimeters),
1162 _ => (base, base_unit),
1163 };
1164
1165 let value = match (base_unit, to) {
1166 (Millimeters, Millimeters) => base,
1167 (Millimeters, Centimeters) => base / 10.0,
1168 (Millimeters, Meters) => base / 1000.0,
1169 (Inches, Inches) => base,
1170 (Inches, Feet) => base / 12.0,
1171 (Inches, Yards) => base / 36.0,
1172 _ => unreachable!(),
1173 };
1174
1175 (value, to)
1176}
1177
1178pub fn adjust_angle(from: UnitAngle, value: f64, to: UnitAngle) -> (f64, UnitAngle) {
1179 use std::f64::consts::PI;
1180
1181 use UnitAngle::*;
1182
1183 let value = match (from, to) {
1184 (Degrees, Degrees) => value,
1185 (Degrees, Radians) => (value / 180.0) * PI,
1186 (Radians, Degrees) => 180.0 * value / PI,
1187 (Radians, Radians) => value,
1188 };
1189
1190 (value, to)
1191}
1192
1193pub(super) fn length_from_str(s: &str, source_range: SourceRange) -> Result<UnitLength, KclError> {
1194 match s {
1196 "mm" => Ok(UnitLength::Millimeters),
1197 "cm" => Ok(UnitLength::Centimeters),
1198 "m" => Ok(UnitLength::Meters),
1199 "inch" | "in" => Ok(UnitLength::Inches),
1200 "ft" => Ok(UnitLength::Feet),
1201 "yd" => Ok(UnitLength::Yards),
1202 value => Err(KclError::new_semantic(KclErrorDetails::new(
1203 format!("Unexpected value for length units: `{value}`; expected one of `mm`, `cm`, `m`, `in`, `ft`, `yd`"),
1204 vec![source_range],
1205 ))),
1206 }
1207}
1208
1209pub(super) fn angle_from_str(s: &str, source_range: SourceRange) -> Result<UnitAngle, KclError> {
1210 UnitAngle::from_str(s).map_err(|_| {
1211 KclError::new_semantic(KclErrorDetails::new(
1212 format!("Unexpected value for angle units: `{s}`; expected one of `deg`, `rad`"),
1213 vec![source_range],
1214 ))
1215 })
1216}
1217
1218#[derive(Debug, Clone)]
1219pub struct CoercionError {
1220 pub found: Option<RuntimeType>,
1221 pub explicit_coercion: Option<String>,
1222}
1223
1224impl CoercionError {
1225 fn with_explicit(mut self, c: String) -> Self {
1226 self.explicit_coercion = Some(c);
1227 self
1228 }
1229}
1230
1231impl From<&'_ KclValue> for CoercionError {
1232 fn from(value: &'_ KclValue) -> Self {
1233 CoercionError {
1234 found: value.principal_type(),
1235 explicit_coercion: None,
1236 }
1237 }
1238}
1239
1240impl KclValue {
1241 pub fn has_type(&self, ty: &RuntimeType) -> bool {
1243 let Some(self_ty) = self.principal_type() else {
1244 return false;
1245 };
1246
1247 self_ty.subtype(ty)
1248 }
1249
1250 pub fn coerce(
1257 &self,
1258 ty: &RuntimeType,
1259 convert_units: bool,
1260 exec_state: &mut ExecState,
1261 ) -> Result<KclValue, CoercionError> {
1262 match self {
1263 KclValue::Tuple { value, .. }
1264 if value.len() == 1
1265 && !matches!(ty, RuntimeType::Primitive(PrimitiveType::Any) | RuntimeType::Tuple(..)) =>
1266 {
1267 if let Ok(coerced) = value[0].coerce(ty, convert_units, exec_state) {
1268 return Ok(coerced);
1269 }
1270 }
1271 KclValue::HomArray { value, .. }
1272 if value.len() == 1
1273 && !matches!(ty, RuntimeType::Primitive(PrimitiveType::Any) | RuntimeType::Array(..)) =>
1274 {
1275 if let Ok(coerced) = value[0].coerce(ty, convert_units, exec_state) {
1276 return Ok(coerced);
1277 }
1278 }
1279 _ => {}
1280 }
1281
1282 match ty {
1283 RuntimeType::Primitive(ty) => self.coerce_to_primitive_type(ty, convert_units, exec_state),
1284 RuntimeType::Array(ty, len) => self.coerce_to_array_type(ty, convert_units, *len, exec_state, false),
1285 RuntimeType::Tuple(tys) => self.coerce_to_tuple_type(tys, convert_units, exec_state),
1286 RuntimeType::Union(tys) => self.coerce_to_union_type(tys, convert_units, exec_state),
1287 RuntimeType::Object(tys, constrainable) => {
1288 self.coerce_to_object_type(tys, *constrainable, convert_units, exec_state)
1289 }
1290 }
1291 }
1292
1293 fn coerce_to_primitive_type(
1294 &self,
1295 ty: &PrimitiveType,
1296 convert_units: bool,
1297 exec_state: &mut ExecState,
1298 ) -> Result<KclValue, CoercionError> {
1299 match ty {
1300 PrimitiveType::Any => Ok(self.clone()),
1301 PrimitiveType::None => match self {
1302 KclValue::KclNone { .. } => Ok(self.clone()),
1303 _ => Err(self.into()),
1304 },
1305 PrimitiveType::Number(ty) => {
1306 if convert_units {
1307 return ty.coerce(self);
1308 }
1309
1310 if let KclValue::Number { value: n, meta, .. } = &self
1317 && ty.is_fully_specified()
1318 {
1319 let value = KclValue::Number {
1320 ty: NumericType::Any,
1321 value: *n,
1322 meta: meta.clone(),
1323 };
1324 return ty.coerce(&value);
1325 }
1326 ty.coerce(self)
1327 }
1328 PrimitiveType::String => match self {
1329 KclValue::String { .. } => Ok(self.clone()),
1330 _ => Err(self.into()),
1331 },
1332 PrimitiveType::Boolean => match self {
1333 KclValue::Bool { .. } => Ok(self.clone()),
1334 _ => Err(self.into()),
1335 },
1336 PrimitiveType::GdtAnnotation => match self {
1337 KclValue::GdtAnnotation { .. } => Ok(self.clone()),
1338 _ => Err(self.into()),
1339 },
1340 PrimitiveType::Segment => match self {
1341 KclValue::Segment { .. } => Ok(self.clone()),
1342 _ => Err(self.into()),
1343 },
1344 PrimitiveType::Sketch => match self {
1345 KclValue::Sketch { .. } => Ok(self.clone()),
1346 _ => Err(self.into()),
1347 },
1348 PrimitiveType::Constraint => match self {
1349 KclValue::SketchConstraint { .. } => Ok(self.clone()),
1350 _ => Err(self.into()),
1351 },
1352 PrimitiveType::Solid => match self {
1353 KclValue::Solid { .. } => Ok(self.clone()),
1354 _ => Err(self.into()),
1355 },
1356 PrimitiveType::Plane => {
1357 match self {
1358 KclValue::String { value: s, .. }
1359 if [
1360 "xy", "xz", "yz", "-xy", "-xz", "-yz", "XY", "XZ", "YZ", "-XY", "-XZ", "-YZ",
1361 ]
1362 .contains(&&**s) =>
1363 {
1364 Ok(self.clone())
1365 }
1366 KclValue::Plane { .. } => Ok(self.clone()),
1367 KclValue::Object { value, meta, .. } => {
1368 let origin = value
1369 .get("origin")
1370 .and_then(Point3d::from_kcl_val)
1371 .ok_or(CoercionError::from(self))?;
1372 let x_axis = value
1373 .get("xAxis")
1374 .and_then(Point3d::from_kcl_val)
1375 .ok_or(CoercionError::from(self))?;
1376 let y_axis = value
1377 .get("yAxis")
1378 .and_then(Point3d::from_kcl_val)
1379 .ok_or(CoercionError::from(self))?;
1380 let z_axis = x_axis.axes_cross_product(&y_axis);
1381
1382 if value.get("zAxis").is_some() {
1383 exec_state.warn(CompilationError::err(
1384 self.into(),
1385 "Object with a zAxis field is being coerced into a plane, but the zAxis is ignored.",
1386 ), annotations::WARN_IGNORED_Z_AXIS);
1387 }
1388
1389 let id = exec_state.mod_local.id_generator.next_uuid();
1390 let info = PlaneInfo {
1391 origin,
1392 x_axis: x_axis.normalize(),
1393 y_axis: y_axis.normalize(),
1394 z_axis: z_axis.normalize(),
1395 };
1396 let plane = Plane {
1397 id,
1398 artifact_id: id.into(),
1399 object_id: None,
1400 kind: PlaneKind::from(&info),
1401 info,
1402 meta: meta.clone(),
1403 };
1404
1405 Ok(KclValue::Plane { value: Box::new(plane) })
1406 }
1407 _ => Err(self.into()),
1408 }
1409 }
1410 PrimitiveType::Face => match self {
1411 KclValue::Face { .. } => Ok(self.clone()),
1412 _ => Err(self.into()),
1413 },
1414 PrimitiveType::Helix => match self {
1415 KclValue::Helix { .. } => Ok(self.clone()),
1416 _ => Err(self.into()),
1417 },
1418 PrimitiveType::Edge => match self {
1419 KclValue::Uuid { .. } => Ok(self.clone()),
1420 KclValue::TagIdentifier { .. } => Ok(self.clone()),
1421 _ => Err(self.into()),
1422 },
1423 PrimitiveType::BoundedEdge => match self {
1424 KclValue::BoundedEdge { .. } => Ok(self.clone()),
1425 _ => Err(self.into()),
1426 },
1427 PrimitiveType::TaggedEdge => match self {
1428 KclValue::TagIdentifier { .. } => Ok(self.clone()),
1429 _ => Err(self.into()),
1430 },
1431 PrimitiveType::TaggedFace => match self {
1432 KclValue::TagIdentifier { .. } => Ok(self.clone()),
1433 s @ KclValue::String { value, .. } if ["start", "end", "START", "END"].contains(&&**value) => {
1434 Ok(s.clone())
1435 }
1436 _ => Err(self.into()),
1437 },
1438 PrimitiveType::Axis2d => match self {
1439 KclValue::Object {
1440 value: values, meta, ..
1441 } => {
1442 if values
1443 .get("origin")
1444 .ok_or(CoercionError::from(self))?
1445 .has_type(&RuntimeType::point2d())
1446 && values
1447 .get("direction")
1448 .ok_or(CoercionError::from(self))?
1449 .has_type(&RuntimeType::point2d())
1450 {
1451 return Ok(self.clone());
1452 }
1453
1454 let origin = values.get("origin").ok_or(self.into()).and_then(|p| {
1455 p.coerce_to_array_type(
1456 &RuntimeType::length(),
1457 convert_units,
1458 ArrayLen::Known(2),
1459 exec_state,
1460 true,
1461 )
1462 })?;
1463 let direction = values.get("direction").ok_or(self.into()).and_then(|p| {
1464 p.coerce_to_array_type(
1465 &RuntimeType::length(),
1466 convert_units,
1467 ArrayLen::Known(2),
1468 exec_state,
1469 true,
1470 )
1471 })?;
1472
1473 Ok(KclValue::Object {
1474 value: [("origin".to_owned(), origin), ("direction".to_owned(), direction)].into(),
1475 meta: meta.clone(),
1476 constrainable: false,
1477 })
1478 }
1479 _ => Err(self.into()),
1480 },
1481 PrimitiveType::Axis3d => match self {
1482 KclValue::Object {
1483 value: values, meta, ..
1484 } => {
1485 if values
1486 .get("origin")
1487 .ok_or(CoercionError::from(self))?
1488 .has_type(&RuntimeType::point3d())
1489 && values
1490 .get("direction")
1491 .ok_or(CoercionError::from(self))?
1492 .has_type(&RuntimeType::point3d())
1493 {
1494 return Ok(self.clone());
1495 }
1496
1497 let origin = values.get("origin").ok_or(self.into()).and_then(|p| {
1498 p.coerce_to_array_type(
1499 &RuntimeType::length(),
1500 convert_units,
1501 ArrayLen::Known(3),
1502 exec_state,
1503 true,
1504 )
1505 })?;
1506 let direction = values.get("direction").ok_or(self.into()).and_then(|p| {
1507 p.coerce_to_array_type(
1508 &RuntimeType::length(),
1509 convert_units,
1510 ArrayLen::Known(3),
1511 exec_state,
1512 true,
1513 )
1514 })?;
1515
1516 Ok(KclValue::Object {
1517 value: [("origin".to_owned(), origin), ("direction".to_owned(), direction)].into(),
1518 meta: meta.clone(),
1519 constrainable: false,
1520 })
1521 }
1522 _ => Err(self.into()),
1523 },
1524 PrimitiveType::ImportedGeometry => match self {
1525 KclValue::ImportedGeometry { .. } => Ok(self.clone()),
1526 _ => Err(self.into()),
1527 },
1528 PrimitiveType::Function => match self {
1529 KclValue::Function { .. } => Ok(self.clone()),
1530 _ => Err(self.into()),
1531 },
1532 PrimitiveType::TagDecl => match self {
1533 KclValue::TagDeclarator { .. } => Ok(self.clone()),
1534 _ => Err(self.into()),
1535 },
1536 }
1537 }
1538
1539 fn coerce_to_array_type(
1540 &self,
1541 ty: &RuntimeType,
1542 convert_units: bool,
1543 len: ArrayLen,
1544 exec_state: &mut ExecState,
1545 allow_shrink: bool,
1546 ) -> Result<KclValue, CoercionError> {
1547 match self {
1548 KclValue::HomArray { value, ty: aty, .. } => {
1549 let satisfied_len = len.satisfied(value.len(), allow_shrink);
1550
1551 if aty.subtype(ty) {
1552 return satisfied_len
1559 .map(|len| KclValue::HomArray {
1560 value: value[..len].to_vec(),
1561 ty: aty.clone(),
1562 })
1563 .ok_or(self.into());
1564 }
1565
1566 if let Some(satisfied_len) = satisfied_len {
1568 let value_result = value
1569 .iter()
1570 .take(satisfied_len)
1571 .map(|v| v.coerce(ty, convert_units, exec_state))
1572 .collect::<Result<Vec<_>, _>>();
1573
1574 if let Ok(value) = value_result {
1575 return Ok(KclValue::HomArray { value, ty: ty.clone() });
1577 }
1578 }
1579
1580 let mut values = Vec::new();
1582 for item in value {
1583 if let KclValue::HomArray { value: inner_value, .. } = item {
1584 for item in inner_value {
1586 values.push(item.coerce(ty, convert_units, exec_state)?);
1587 }
1588 } else {
1589 values.push(item.coerce(ty, convert_units, exec_state)?);
1590 }
1591 }
1592
1593 let len = len
1594 .satisfied(values.len(), allow_shrink)
1595 .ok_or(CoercionError::from(self))?;
1596
1597 if len > values.len() {
1598 let message = format!(
1599 "Internal: Expected coerced array length {len} to be less than or equal to original length {}",
1600 values.len()
1601 );
1602 exec_state.err(CompilationError::err(self.into(), message.clone()));
1603 #[cfg(debug_assertions)]
1604 panic!("{message}");
1605 }
1606 values.truncate(len);
1607
1608 Ok(KclValue::HomArray {
1609 value: values,
1610 ty: ty.clone(),
1611 })
1612 }
1613 KclValue::Tuple { value, .. } => {
1614 let len = len
1615 .satisfied(value.len(), allow_shrink)
1616 .ok_or(CoercionError::from(self))?;
1617 let value = value
1618 .iter()
1619 .map(|item| item.coerce(ty, convert_units, exec_state))
1620 .take(len)
1621 .collect::<Result<Vec<_>, _>>()?;
1622
1623 Ok(KclValue::HomArray { value, ty: ty.clone() })
1624 }
1625 KclValue::KclNone { .. } if len.satisfied(0, false).is_some() => Ok(KclValue::HomArray {
1626 value: Vec::new(),
1627 ty: ty.clone(),
1628 }),
1629 _ if len.satisfied(1, false).is_some() => self.coerce(ty, convert_units, exec_state),
1630 _ => Err(self.into()),
1631 }
1632 }
1633
1634 fn coerce_to_tuple_type(
1635 &self,
1636 tys: &[RuntimeType],
1637 convert_units: bool,
1638 exec_state: &mut ExecState,
1639 ) -> Result<KclValue, CoercionError> {
1640 match self {
1641 KclValue::Tuple { value, .. } | KclValue::HomArray { value, .. } if value.len() == tys.len() => {
1642 let mut result = Vec::new();
1643 for (i, t) in tys.iter().enumerate() {
1644 result.push(value[i].coerce(t, convert_units, exec_state)?);
1645 }
1646
1647 Ok(KclValue::Tuple {
1648 value: result,
1649 meta: Vec::new(),
1650 })
1651 }
1652 KclValue::KclNone { meta, .. } if tys.is_empty() => Ok(KclValue::Tuple {
1653 value: Vec::new(),
1654 meta: meta.clone(),
1655 }),
1656 _ if tys.len() == 1 => self.coerce(&tys[0], convert_units, exec_state),
1657 _ => Err(self.into()),
1658 }
1659 }
1660
1661 fn coerce_to_union_type(
1662 &self,
1663 tys: &[RuntimeType],
1664 convert_units: bool,
1665 exec_state: &mut ExecState,
1666 ) -> Result<KclValue, CoercionError> {
1667 for t in tys {
1668 if let Ok(v) = self.coerce(t, convert_units, exec_state) {
1669 return Ok(v);
1670 }
1671 }
1672
1673 Err(self.into())
1674 }
1675
1676 fn coerce_to_object_type(
1677 &self,
1678 tys: &[(String, RuntimeType)],
1679 constrainable: bool,
1680 _convert_units: bool,
1681 _exec_state: &mut ExecState,
1682 ) -> Result<KclValue, CoercionError> {
1683 match self {
1684 KclValue::Object { value, meta, .. } => {
1685 for (s, t) in tys {
1686 if !value.get(s).ok_or(CoercionError::from(self))?.has_type(t) {
1688 return Err(self.into());
1689 }
1690 }
1691 Ok(KclValue::Object {
1693 value: value.clone(),
1694 meta: meta.clone(),
1695 constrainable,
1698 })
1699 }
1700 KclValue::KclNone { meta, .. } if tys.is_empty() => Ok(KclValue::Object {
1701 value: HashMap::new(),
1702 meta: meta.clone(),
1703 constrainable,
1704 }),
1705 _ => Err(self.into()),
1706 }
1707 }
1708
1709 pub fn principal_type(&self) -> Option<RuntimeType> {
1710 match self {
1711 KclValue::Bool { .. } => Some(RuntimeType::Primitive(PrimitiveType::Boolean)),
1712 KclValue::Number { ty, .. } => Some(RuntimeType::Primitive(PrimitiveType::Number(*ty))),
1713 KclValue::String { .. } => Some(RuntimeType::Primitive(PrimitiveType::String)),
1714 KclValue::SketchVar { value, .. } => Some(RuntimeType::Primitive(PrimitiveType::Number(value.ty))),
1715 KclValue::SketchConstraint { .. } => Some(RuntimeType::Primitive(PrimitiveType::Constraint)),
1716 KclValue::Object {
1717 value, constrainable, ..
1718 } => {
1719 let properties = value
1720 .iter()
1721 .map(|(k, v)| v.principal_type().map(|t| (k.clone(), t)))
1722 .collect::<Option<Vec<_>>>()?;
1723 Some(RuntimeType::Object(properties, *constrainable))
1724 }
1725 KclValue::GdtAnnotation { .. } => Some(RuntimeType::Primitive(PrimitiveType::GdtAnnotation)),
1726 KclValue::Plane { .. } => Some(RuntimeType::Primitive(PrimitiveType::Plane)),
1727 KclValue::Sketch { .. } => Some(RuntimeType::Primitive(PrimitiveType::Sketch)),
1728 KclValue::Solid { .. } => Some(RuntimeType::Primitive(PrimitiveType::Solid)),
1729 KclValue::Face { .. } => Some(RuntimeType::Primitive(PrimitiveType::Face)),
1730 KclValue::Segment { .. } => Some(RuntimeType::Primitive(PrimitiveType::Segment)),
1731 KclValue::Helix { .. } => Some(RuntimeType::Primitive(PrimitiveType::Helix)),
1732 KclValue::ImportedGeometry(..) => Some(RuntimeType::Primitive(PrimitiveType::ImportedGeometry)),
1733 KclValue::Tuple { value, .. } => Some(RuntimeType::Tuple(
1734 value.iter().map(|v| v.principal_type()).collect::<Option<Vec<_>>>()?,
1735 )),
1736 KclValue::HomArray { ty, value, .. } => {
1737 Some(RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Known(value.len())))
1738 }
1739 KclValue::TagIdentifier(_) => Some(RuntimeType::Primitive(PrimitiveType::TaggedEdge)),
1740 KclValue::TagDeclarator(_) => Some(RuntimeType::Primitive(PrimitiveType::TagDecl)),
1741 KclValue::Uuid { .. } => Some(RuntimeType::Primitive(PrimitiveType::Edge)),
1742 KclValue::Function { .. } => Some(RuntimeType::Primitive(PrimitiveType::Function)),
1743 KclValue::KclNone { .. } => Some(RuntimeType::Primitive(PrimitiveType::None)),
1744 KclValue::Module { .. } | KclValue::Type { .. } => None,
1745 KclValue::BoundedEdge { .. } => Some(RuntimeType::Primitive(PrimitiveType::BoundedEdge)),
1746 }
1747 }
1748
1749 pub fn principal_type_string(&self) -> String {
1750 if let Some(ty) = self.principal_type() {
1751 return format!("`{ty}`");
1752 }
1753
1754 match self {
1755 KclValue::Module { .. } => "module",
1756 KclValue::KclNone { .. } => "none",
1757 KclValue::Type { .. } => "type",
1758 _ => {
1759 debug_assert!(false);
1760 "<unexpected type>"
1761 }
1762 }
1763 .to_owned()
1764 }
1765}
1766
1767#[cfg(test)]
1768mod test {
1769 use super::*;
1770 use crate::execution::{ExecTestResults, parse_execute};
1771
1772 async fn new_exec_state() -> (crate::ExecutorContext, ExecState) {
1773 let ctx = crate::ExecutorContext::new_mock(None).await;
1774 let exec_state = ExecState::new(&ctx);
1775 (ctx, exec_state)
1776 }
1777
1778 fn values(exec_state: &mut ExecState) -> Vec<KclValue> {
1779 vec![
1780 KclValue::Bool {
1781 value: true,
1782 meta: Vec::new(),
1783 },
1784 KclValue::Number {
1785 value: 1.0,
1786 ty: NumericType::count(),
1787 meta: Vec::new(),
1788 },
1789 KclValue::String {
1790 value: "hello".to_owned(),
1791 meta: Vec::new(),
1792 },
1793 KclValue::Tuple {
1794 value: Vec::new(),
1795 meta: Vec::new(),
1796 },
1797 KclValue::HomArray {
1798 value: Vec::new(),
1799 ty: RuntimeType::solid(),
1800 },
1801 KclValue::Object {
1802 value: crate::execution::KclObjectFields::new(),
1803 meta: Vec::new(),
1804 constrainable: false,
1805 },
1806 KclValue::TagIdentifier(Box::new("foo".parse().unwrap())),
1807 KclValue::TagDeclarator(Box::new(crate::parsing::ast::types::TagDeclarator::new("foo"))),
1808 KclValue::Plane {
1809 value: Box::new(
1810 Plane::from_plane_data_skipping_engine(crate::std::sketch::PlaneData::XY, exec_state).unwrap(),
1811 ),
1812 },
1813 KclValue::ImportedGeometry(crate::execution::ImportedGeometry::new(
1815 uuid::Uuid::nil(),
1816 Vec::new(),
1817 Vec::new(),
1818 )),
1819 ]
1821 }
1822
1823 #[track_caller]
1824 fn assert_coerce_results(
1825 value: &KclValue,
1826 super_type: &RuntimeType,
1827 expected_value: &KclValue,
1828 exec_state: &mut ExecState,
1829 ) {
1830 let is_subtype = value == expected_value;
1831 let actual = value.coerce(super_type, true, exec_state).unwrap();
1832 assert_eq!(&actual, expected_value);
1833 assert_eq!(
1834 is_subtype,
1835 value.principal_type().is_some() && value.principal_type().unwrap().subtype(super_type),
1836 "{:?} <: {super_type:?} should be {is_subtype}",
1837 value.principal_type().unwrap()
1838 );
1839 assert!(
1840 expected_value.principal_type().unwrap().subtype(super_type),
1841 "{} <: {super_type}",
1842 expected_value.principal_type().unwrap()
1843 )
1844 }
1845
1846 #[tokio::test(flavor = "multi_thread")]
1847 async fn coerce_idempotent() {
1848 let (ctx, mut exec_state) = new_exec_state().await;
1849 let values = values(&mut exec_state);
1850 for v in &values {
1851 let ty = v.principal_type().unwrap();
1853 assert_coerce_results(v, &ty, v, &mut exec_state);
1854
1855 let uty1 = RuntimeType::Union(vec![ty.clone()]);
1857 let uty2 = RuntimeType::Union(vec![ty.clone(), RuntimeType::Primitive(PrimitiveType::Boolean)]);
1858 assert_coerce_results(v, &uty1, v, &mut exec_state);
1859 assert_coerce_results(v, &uty2, v, &mut exec_state);
1860
1861 let aty = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::None);
1863 let aty1 = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Known(1));
1864 let aty0 = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Minimum(1));
1865
1866 match v {
1867 KclValue::HomArray { .. } => {
1868 assert_coerce_results(
1870 v,
1871 &aty,
1872 &KclValue::HomArray {
1873 value: vec![],
1874 ty: ty.clone(),
1875 },
1876 &mut exec_state,
1877 );
1878 v.coerce(&aty1, true, &mut exec_state).unwrap_err();
1881 v.coerce(&aty0, true, &mut exec_state).unwrap_err();
1884 }
1885 KclValue::Tuple { .. } => {}
1886 _ => {
1887 assert_coerce_results(v, &aty, v, &mut exec_state);
1888 assert_coerce_results(v, &aty1, v, &mut exec_state);
1889 assert_coerce_results(v, &aty0, v, &mut exec_state);
1890
1891 let tty = RuntimeType::Tuple(vec![ty.clone()]);
1893 assert_coerce_results(v, &tty, v, &mut exec_state);
1894 }
1895 }
1896 }
1897
1898 for v in &values[1..] {
1899 v.coerce(&RuntimeType::Primitive(PrimitiveType::Boolean), true, &mut exec_state)
1901 .unwrap_err();
1902 }
1903 ctx.close().await;
1904 }
1905
1906 #[tokio::test(flavor = "multi_thread")]
1907 async fn coerce_none() {
1908 let (ctx, mut exec_state) = new_exec_state().await;
1909 let none = KclValue::KclNone {
1910 value: crate::parsing::ast::types::KclNone::new(),
1911 meta: Vec::new(),
1912 };
1913
1914 let aty = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::None);
1915 let aty0 = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::Known(0));
1916 let aty1 = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::Known(1));
1917 let aty1p = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::Minimum(1));
1918 assert_coerce_results(
1919 &none,
1920 &aty,
1921 &KclValue::HomArray {
1922 value: Vec::new(),
1923 ty: RuntimeType::solid(),
1924 },
1925 &mut exec_state,
1926 );
1927 assert_coerce_results(
1928 &none,
1929 &aty0,
1930 &KclValue::HomArray {
1931 value: Vec::new(),
1932 ty: RuntimeType::solid(),
1933 },
1934 &mut exec_state,
1935 );
1936 none.coerce(&aty1, true, &mut exec_state).unwrap_err();
1937 none.coerce(&aty1p, true, &mut exec_state).unwrap_err();
1938
1939 let tty = RuntimeType::Tuple(vec![]);
1940 let tty1 = RuntimeType::Tuple(vec![RuntimeType::solid()]);
1941 assert_coerce_results(
1942 &none,
1943 &tty,
1944 &KclValue::Tuple {
1945 value: Vec::new(),
1946 meta: Vec::new(),
1947 },
1948 &mut exec_state,
1949 );
1950 none.coerce(&tty1, true, &mut exec_state).unwrap_err();
1951
1952 let oty = RuntimeType::Object(vec![], false);
1953 assert_coerce_results(
1954 &none,
1955 &oty,
1956 &KclValue::Object {
1957 value: HashMap::new(),
1958 meta: Vec::new(),
1959 constrainable: false,
1960 },
1961 &mut exec_state,
1962 );
1963 ctx.close().await;
1964 }
1965
1966 #[tokio::test(flavor = "multi_thread")]
1967 async fn coerce_record() {
1968 let (ctx, mut exec_state) = new_exec_state().await;
1969
1970 let obj0 = KclValue::Object {
1971 value: HashMap::new(),
1972 meta: Vec::new(),
1973 constrainable: false,
1974 };
1975 let obj1 = KclValue::Object {
1976 value: [(
1977 "foo".to_owned(),
1978 KclValue::Bool {
1979 value: true,
1980 meta: Vec::new(),
1981 },
1982 )]
1983 .into(),
1984 meta: Vec::new(),
1985 constrainable: false,
1986 };
1987 let obj2 = KclValue::Object {
1988 value: [
1989 (
1990 "foo".to_owned(),
1991 KclValue::Bool {
1992 value: true,
1993 meta: Vec::new(),
1994 },
1995 ),
1996 (
1997 "bar".to_owned(),
1998 KclValue::Number {
1999 value: 0.0,
2000 ty: NumericType::count(),
2001 meta: Vec::new(),
2002 },
2003 ),
2004 (
2005 "baz".to_owned(),
2006 KclValue::Number {
2007 value: 42.0,
2008 ty: NumericType::count(),
2009 meta: Vec::new(),
2010 },
2011 ),
2012 ]
2013 .into(),
2014 meta: Vec::new(),
2015 constrainable: false,
2016 };
2017
2018 let ty0 = RuntimeType::Object(vec![], false);
2019 assert_coerce_results(&obj0, &ty0, &obj0, &mut exec_state);
2020 assert_coerce_results(&obj1, &ty0, &obj1, &mut exec_state);
2021 assert_coerce_results(&obj2, &ty0, &obj2, &mut exec_state);
2022
2023 let ty1 = RuntimeType::Object(
2024 vec![("foo".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean))],
2025 false,
2026 );
2027 obj0.coerce(&ty1, true, &mut exec_state).unwrap_err();
2028 assert_coerce_results(&obj1, &ty1, &obj1, &mut exec_state);
2029 assert_coerce_results(&obj2, &ty1, &obj2, &mut exec_state);
2030
2031 let ty2 = RuntimeType::Object(
2033 vec![
2034 (
2035 "bar".to_owned(),
2036 RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2037 ),
2038 ("foo".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean)),
2039 ],
2040 false,
2041 );
2042 obj0.coerce(&ty2, true, &mut exec_state).unwrap_err();
2043 obj1.coerce(&ty2, true, &mut exec_state).unwrap_err();
2044 assert_coerce_results(&obj2, &ty2, &obj2, &mut exec_state);
2045
2046 let tyq = RuntimeType::Object(
2048 vec![("qux".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean))],
2049 false,
2050 );
2051 obj0.coerce(&tyq, true, &mut exec_state).unwrap_err();
2052 obj1.coerce(&tyq, true, &mut exec_state).unwrap_err();
2053 obj2.coerce(&tyq, true, &mut exec_state).unwrap_err();
2054
2055 let ty1 = RuntimeType::Object(
2057 vec![("bar".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean))],
2058 false,
2059 );
2060 obj2.coerce(&ty1, true, &mut exec_state).unwrap_err();
2061 ctx.close().await;
2062 }
2063
2064 #[tokio::test(flavor = "multi_thread")]
2065 async fn coerce_array() {
2066 let (ctx, mut exec_state) = new_exec_state().await;
2067
2068 let hom_arr = KclValue::HomArray {
2069 value: vec![
2070 KclValue::Number {
2071 value: 0.0,
2072 ty: NumericType::count(),
2073 meta: Vec::new(),
2074 },
2075 KclValue::Number {
2076 value: 1.0,
2077 ty: NumericType::count(),
2078 meta: Vec::new(),
2079 },
2080 KclValue::Number {
2081 value: 2.0,
2082 ty: NumericType::count(),
2083 meta: Vec::new(),
2084 },
2085 KclValue::Number {
2086 value: 3.0,
2087 ty: NumericType::count(),
2088 meta: Vec::new(),
2089 },
2090 ],
2091 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2092 };
2093 let mixed1 = KclValue::Tuple {
2094 value: vec![
2095 KclValue::Number {
2096 value: 0.0,
2097 ty: NumericType::count(),
2098 meta: Vec::new(),
2099 },
2100 KclValue::Number {
2101 value: 1.0,
2102 ty: NumericType::count(),
2103 meta: Vec::new(),
2104 },
2105 ],
2106 meta: Vec::new(),
2107 };
2108 let mixed2 = KclValue::Tuple {
2109 value: vec![
2110 KclValue::Number {
2111 value: 0.0,
2112 ty: NumericType::count(),
2113 meta: Vec::new(),
2114 },
2115 KclValue::Bool {
2116 value: true,
2117 meta: Vec::new(),
2118 },
2119 ],
2120 meta: Vec::new(),
2121 };
2122
2123 let tyh = RuntimeType::Array(
2125 Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2126 ArrayLen::Known(4),
2127 );
2128 let tym1 = RuntimeType::Tuple(vec![
2129 RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2130 RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2131 ]);
2132 let tym2 = RuntimeType::Tuple(vec![
2133 RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2134 RuntimeType::Primitive(PrimitiveType::Boolean),
2135 ]);
2136 assert_coerce_results(&hom_arr, &tyh, &hom_arr, &mut exec_state);
2137 assert_coerce_results(&mixed1, &tym1, &mixed1, &mut exec_state);
2138 assert_coerce_results(&mixed2, &tym2, &mixed2, &mut exec_state);
2139 mixed1.coerce(&tym2, true, &mut exec_state).unwrap_err();
2140 mixed2.coerce(&tym1, true, &mut exec_state).unwrap_err();
2141
2142 let tyhn = RuntimeType::Array(
2144 Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2145 ArrayLen::None,
2146 );
2147 let tyh1 = RuntimeType::Array(
2148 Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2149 ArrayLen::Minimum(1),
2150 );
2151 let tyh3 = RuntimeType::Array(
2152 Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2153 ArrayLen::Known(3),
2154 );
2155 let tyhm3 = RuntimeType::Array(
2156 Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2157 ArrayLen::Minimum(3),
2158 );
2159 let tyhm5 = RuntimeType::Array(
2160 Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2161 ArrayLen::Minimum(5),
2162 );
2163 assert_coerce_results(&hom_arr, &tyhn, &hom_arr, &mut exec_state);
2164 assert_coerce_results(&hom_arr, &tyh1, &hom_arr, &mut exec_state);
2165 hom_arr.coerce(&tyh3, true, &mut exec_state).unwrap_err();
2166 assert_coerce_results(&hom_arr, &tyhm3, &hom_arr, &mut exec_state);
2167 hom_arr.coerce(&tyhm5, true, &mut exec_state).unwrap_err();
2168
2169 let hom_arr0 = KclValue::HomArray {
2170 value: vec![],
2171 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2172 };
2173 assert_coerce_results(&hom_arr0, &tyhn, &hom_arr0, &mut exec_state);
2174 hom_arr0.coerce(&tyh1, true, &mut exec_state).unwrap_err();
2175 hom_arr0.coerce(&tyh3, true, &mut exec_state).unwrap_err();
2176
2177 let tym1 = RuntimeType::Tuple(vec![
2180 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2181 RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2182 ]);
2183 let tym2 = RuntimeType::Tuple(vec![
2184 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2185 RuntimeType::Primitive(PrimitiveType::Boolean),
2186 ]);
2187 assert_coerce_results(&mixed1, &tym1, &mixed1, &mut exec_state);
2190 assert_coerce_results(&mixed2, &tym2, &mixed2, &mut exec_state);
2191
2192 let hom_arr_2 = KclValue::HomArray {
2194 value: vec![
2195 KclValue::Number {
2196 value: 0.0,
2197 ty: NumericType::count(),
2198 meta: Vec::new(),
2199 },
2200 KclValue::Number {
2201 value: 1.0,
2202 ty: NumericType::count(),
2203 meta: Vec::new(),
2204 },
2205 ],
2206 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2207 };
2208 let mixed0 = KclValue::Tuple {
2209 value: vec![],
2210 meta: Vec::new(),
2211 };
2212 assert_coerce_results(&mixed1, &tyhn, &hom_arr_2, &mut exec_state);
2213 assert_coerce_results(&mixed1, &tyh1, &hom_arr_2, &mut exec_state);
2214 assert_coerce_results(&mixed0, &tyhn, &hom_arr0, &mut exec_state);
2215 mixed0.coerce(&tyh, true, &mut exec_state).unwrap_err();
2216 mixed0.coerce(&tyh1, true, &mut exec_state).unwrap_err();
2217
2218 assert_coerce_results(&hom_arr_2, &tym1, &mixed1, &mut exec_state);
2220 hom_arr.coerce(&tym1, true, &mut exec_state).unwrap_err();
2221 hom_arr_2.coerce(&tym2, true, &mut exec_state).unwrap_err();
2222
2223 mixed0.coerce(&tym1, true, &mut exec_state).unwrap_err();
2224 mixed0.coerce(&tym2, true, &mut exec_state).unwrap_err();
2225 ctx.close().await;
2226 }
2227
2228 #[tokio::test(flavor = "multi_thread")]
2229 async fn coerce_union() {
2230 let (ctx, mut exec_state) = new_exec_state().await;
2231
2232 assert!(RuntimeType::Union(vec![]).subtype(&RuntimeType::Union(vec![
2234 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2235 RuntimeType::Primitive(PrimitiveType::Boolean)
2236 ])));
2237 assert!(
2238 RuntimeType::Union(vec![RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any))]).subtype(
2239 &RuntimeType::Union(vec![
2240 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2241 RuntimeType::Primitive(PrimitiveType::Boolean)
2242 ])
2243 )
2244 );
2245 assert!(
2246 RuntimeType::Union(vec![
2247 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2248 RuntimeType::Primitive(PrimitiveType::Boolean)
2249 ])
2250 .subtype(&RuntimeType::Union(vec![
2251 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2252 RuntimeType::Primitive(PrimitiveType::Boolean)
2253 ]))
2254 );
2255
2256 let count = KclValue::Number {
2258 value: 1.0,
2259 ty: NumericType::count(),
2260 meta: Vec::new(),
2261 };
2262
2263 let tya = RuntimeType::Union(vec![RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any))]);
2264 let tya2 = RuntimeType::Union(vec![
2265 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2266 RuntimeType::Primitive(PrimitiveType::Boolean),
2267 ]);
2268 assert_coerce_results(&count, &tya, &count, &mut exec_state);
2269 assert_coerce_results(&count, &tya2, &count, &mut exec_state);
2270
2271 let tyb = RuntimeType::Union(vec![RuntimeType::Primitive(PrimitiveType::Boolean)]);
2273 let tyb2 = RuntimeType::Union(vec![
2274 RuntimeType::Primitive(PrimitiveType::Boolean),
2275 RuntimeType::Primitive(PrimitiveType::String),
2276 ]);
2277 count.coerce(&tyb, true, &mut exec_state).unwrap_err();
2278 count.coerce(&tyb2, true, &mut exec_state).unwrap_err();
2279 ctx.close().await;
2280 }
2281
2282 #[tokio::test(flavor = "multi_thread")]
2283 async fn coerce_axes() {
2284 let (ctx, mut exec_state) = new_exec_state().await;
2285
2286 assert!(RuntimeType::Primitive(PrimitiveType::Axis2d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis2d)));
2288 assert!(RuntimeType::Primitive(PrimitiveType::Axis3d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis3d)));
2289 assert!(!RuntimeType::Primitive(PrimitiveType::Axis3d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis2d)));
2290 assert!(!RuntimeType::Primitive(PrimitiveType::Axis2d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis3d)));
2291
2292 let a2d = KclValue::Object {
2294 value: [
2295 (
2296 "origin".to_owned(),
2297 KclValue::HomArray {
2298 value: vec![
2299 KclValue::Number {
2300 value: 0.0,
2301 ty: NumericType::mm(),
2302 meta: Vec::new(),
2303 },
2304 KclValue::Number {
2305 value: 0.0,
2306 ty: NumericType::mm(),
2307 meta: Vec::new(),
2308 },
2309 ],
2310 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
2311 },
2312 ),
2313 (
2314 "direction".to_owned(),
2315 KclValue::HomArray {
2316 value: vec![
2317 KclValue::Number {
2318 value: 1.0,
2319 ty: NumericType::mm(),
2320 meta: Vec::new(),
2321 },
2322 KclValue::Number {
2323 value: 0.0,
2324 ty: NumericType::mm(),
2325 meta: Vec::new(),
2326 },
2327 ],
2328 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
2329 },
2330 ),
2331 ]
2332 .into(),
2333 meta: Vec::new(),
2334 constrainable: false,
2335 };
2336 let a3d = KclValue::Object {
2337 value: [
2338 (
2339 "origin".to_owned(),
2340 KclValue::HomArray {
2341 value: vec![
2342 KclValue::Number {
2343 value: 0.0,
2344 ty: NumericType::mm(),
2345 meta: Vec::new(),
2346 },
2347 KclValue::Number {
2348 value: 0.0,
2349 ty: NumericType::mm(),
2350 meta: Vec::new(),
2351 },
2352 KclValue::Number {
2353 value: 0.0,
2354 ty: NumericType::mm(),
2355 meta: Vec::new(),
2356 },
2357 ],
2358 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
2359 },
2360 ),
2361 (
2362 "direction".to_owned(),
2363 KclValue::HomArray {
2364 value: vec![
2365 KclValue::Number {
2366 value: 1.0,
2367 ty: NumericType::mm(),
2368 meta: Vec::new(),
2369 },
2370 KclValue::Number {
2371 value: 0.0,
2372 ty: NumericType::mm(),
2373 meta: Vec::new(),
2374 },
2375 KclValue::Number {
2376 value: 1.0,
2377 ty: NumericType::mm(),
2378 meta: Vec::new(),
2379 },
2380 ],
2381 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
2382 },
2383 ),
2384 ]
2385 .into(),
2386 meta: Vec::new(),
2387 constrainable: false,
2388 };
2389
2390 let ty2d = RuntimeType::Primitive(PrimitiveType::Axis2d);
2391 let ty3d = RuntimeType::Primitive(PrimitiveType::Axis3d);
2392
2393 assert_coerce_results(&a2d, &ty2d, &a2d, &mut exec_state);
2394 assert_coerce_results(&a3d, &ty3d, &a3d, &mut exec_state);
2395 assert_coerce_results(&a3d, &ty2d, &a2d, &mut exec_state);
2396 a2d.coerce(&ty3d, true, &mut exec_state).unwrap_err();
2397 ctx.close().await;
2398 }
2399
2400 #[tokio::test(flavor = "multi_thread")]
2401 async fn coerce_numeric() {
2402 let (ctx, mut exec_state) = new_exec_state().await;
2403
2404 let count = KclValue::Number {
2405 value: 1.0,
2406 ty: NumericType::count(),
2407 meta: Vec::new(),
2408 };
2409 let mm = KclValue::Number {
2410 value: 1.0,
2411 ty: NumericType::mm(),
2412 meta: Vec::new(),
2413 };
2414 let inches = KclValue::Number {
2415 value: 1.0,
2416 ty: NumericType::Known(UnitType::Length(UnitLength::Inches)),
2417 meta: Vec::new(),
2418 };
2419 let rads = KclValue::Number {
2420 value: 1.0,
2421 ty: NumericType::Known(UnitType::Angle(UnitAngle::Radians)),
2422 meta: Vec::new(),
2423 };
2424 let default = KclValue::Number {
2425 value: 1.0,
2426 ty: NumericType::default(),
2427 meta: Vec::new(),
2428 };
2429 let any = KclValue::Number {
2430 value: 1.0,
2431 ty: NumericType::Any,
2432 meta: Vec::new(),
2433 };
2434 let unknown = KclValue::Number {
2435 value: 1.0,
2436 ty: NumericType::Unknown,
2437 meta: Vec::new(),
2438 };
2439
2440 assert_coerce_results(&count, &NumericType::count().into(), &count, &mut exec_state);
2442 assert_coerce_results(&mm, &NumericType::mm().into(), &mm, &mut exec_state);
2443 assert_coerce_results(&any, &NumericType::Any.into(), &any, &mut exec_state);
2444 assert_coerce_results(&unknown, &NumericType::Unknown.into(), &unknown, &mut exec_state);
2445 assert_coerce_results(&default, &NumericType::default().into(), &default, &mut exec_state);
2446
2447 assert_coerce_results(&count, &NumericType::Any.into(), &count, &mut exec_state);
2448 assert_coerce_results(&mm, &NumericType::Any.into(), &mm, &mut exec_state);
2449 assert_coerce_results(&unknown, &NumericType::Any.into(), &unknown, &mut exec_state);
2450 assert_coerce_results(&default, &NumericType::Any.into(), &default, &mut exec_state);
2451
2452 assert_eq!(
2453 default
2454 .coerce(
2455 &NumericType::Default {
2456 len: UnitLength::Yards,
2457 angle: UnitAngle::Degrees,
2458 }
2459 .into(),
2460 true,
2461 &mut exec_state
2462 )
2463 .unwrap(),
2464 default
2465 );
2466
2467 count
2469 .coerce(&NumericType::mm().into(), true, &mut exec_state)
2470 .unwrap_err();
2471 mm.coerce(&NumericType::count().into(), true, &mut exec_state)
2472 .unwrap_err();
2473 unknown
2474 .coerce(&NumericType::mm().into(), true, &mut exec_state)
2475 .unwrap_err();
2476 unknown
2477 .coerce(&NumericType::default().into(), true, &mut exec_state)
2478 .unwrap_err();
2479
2480 count
2481 .coerce(&NumericType::Unknown.into(), true, &mut exec_state)
2482 .unwrap_err();
2483 mm.coerce(&NumericType::Unknown.into(), true, &mut exec_state)
2484 .unwrap_err();
2485 default
2486 .coerce(&NumericType::Unknown.into(), true, &mut exec_state)
2487 .unwrap_err();
2488
2489 assert_eq!(
2490 inches
2491 .coerce(&NumericType::mm().into(), true, &mut exec_state)
2492 .unwrap()
2493 .as_f64()
2494 .unwrap()
2495 .round(),
2496 25.0
2497 );
2498 assert_eq!(
2499 rads.coerce(
2500 &NumericType::Known(UnitType::Angle(UnitAngle::Degrees)).into(),
2501 true,
2502 &mut exec_state
2503 )
2504 .unwrap()
2505 .as_f64()
2506 .unwrap()
2507 .round(),
2508 57.0
2509 );
2510 assert_eq!(
2511 inches
2512 .coerce(&NumericType::default().into(), true, &mut exec_state)
2513 .unwrap()
2514 .as_f64()
2515 .unwrap()
2516 .round(),
2517 1.0
2518 );
2519 assert_eq!(
2520 rads.coerce(&NumericType::default().into(), true, &mut exec_state)
2521 .unwrap()
2522 .as_f64()
2523 .unwrap()
2524 .round(),
2525 1.0
2526 );
2527 ctx.close().await;
2528 }
2529
2530 #[track_caller]
2531 fn assert_value_and_type(name: &str, result: &ExecTestResults, expected: f64, expected_ty: NumericType) {
2532 let mem = result.exec_state.stack();
2533 match mem
2534 .memory
2535 .get_from(name, result.mem_env, SourceRange::default(), 0)
2536 .unwrap()
2537 {
2538 KclValue::Number { value, ty, .. } => {
2539 assert_eq!(value.round(), expected);
2540 assert_eq!(*ty, expected_ty);
2541 }
2542 _ => unreachable!(),
2543 }
2544 }
2545
2546 #[tokio::test(flavor = "multi_thread")]
2547 async fn combine_numeric() {
2548 let program = r#"a = 5 + 4
2549b = 5 - 2
2550c = 5mm - 2mm + 10mm
2551d = 5mm - 2 + 10
2552e = 5 - 2mm + 10
2553f = 30mm - 1inch
2554
2555g = 2 * 10
2556h = 2 * 10mm
2557i = 2mm * 10mm
2558j = 2_ * 10
2559k = 2_ * 3mm * 3mm
2560
2561l = 1 / 10
2562m = 2mm / 1mm
2563n = 10inch / 2mm
2564o = 3mm / 3
2565p = 3_ / 4
2566q = 4inch / 2_
2567
2568r = min([0, 3, 42])
2569s = min([0, 3mm, -42])
2570t = min([100, 3in, 142mm])
2571u = min([3rad, 4in])
2572"#;
2573
2574 let result = parse_execute(program).await.unwrap();
2575 assert_eq!(
2576 result.exec_state.errors().len(),
2577 5,
2578 "errors: {:?}",
2579 result.exec_state.errors()
2580 );
2581
2582 assert_value_and_type("a", &result, 9.0, NumericType::default());
2583 assert_value_and_type("b", &result, 3.0, NumericType::default());
2584 assert_value_and_type("c", &result, 13.0, NumericType::mm());
2585 assert_value_and_type("d", &result, 13.0, NumericType::mm());
2586 assert_value_and_type("e", &result, 13.0, NumericType::mm());
2587 assert_value_and_type("f", &result, 5.0, NumericType::mm());
2588
2589 assert_value_and_type("g", &result, 20.0, NumericType::default());
2590 assert_value_and_type("h", &result, 20.0, NumericType::mm());
2591 assert_value_and_type("i", &result, 20.0, NumericType::Unknown);
2592 assert_value_and_type("j", &result, 20.0, NumericType::default());
2593 assert_value_and_type("k", &result, 18.0, NumericType::Unknown);
2594
2595 assert_value_and_type("l", &result, 0.0, NumericType::default());
2596 assert_value_and_type("m", &result, 2.0, NumericType::count());
2597 assert_value_and_type("n", &result, 5.0, NumericType::Unknown);
2598 assert_value_and_type("o", &result, 1.0, NumericType::mm());
2599 assert_value_and_type("p", &result, 1.0, NumericType::count());
2600 assert_value_and_type(
2601 "q",
2602 &result,
2603 2.0,
2604 NumericType::Known(UnitType::Length(UnitLength::Inches)),
2605 );
2606
2607 assert_value_and_type("r", &result, 0.0, NumericType::default());
2608 assert_value_and_type("s", &result, -42.0, NumericType::mm());
2609 assert_value_and_type("t", &result, 3.0, NumericType::Unknown);
2610 assert_value_and_type("u", &result, 3.0, NumericType::Unknown);
2611 }
2612
2613 #[tokio::test(flavor = "multi_thread")]
2614 async fn bad_typed_arithmetic() {
2615 let program = r#"
2616a = 1rad
2617b = 180 / PI * a + 360
2618"#;
2619
2620 let result = parse_execute(program).await.unwrap();
2621
2622 assert_value_and_type("a", &result, 1.0, NumericType::radians());
2623 assert_value_and_type("b", &result, 417.0, NumericType::Unknown);
2624 }
2625
2626 #[tokio::test(flavor = "multi_thread")]
2627 async fn cos_coercions() {
2628 let program = r#"
2629a = cos(units::toRadians(30deg))
2630b = 3 / a
2631c = cos(30deg)
2632d = cos(1rad)
2633"#;
2634
2635 let result = parse_execute(program).await.unwrap();
2636 assert!(
2637 result.exec_state.errors().is_empty(),
2638 "{:?}",
2639 result.exec_state.errors()
2640 );
2641
2642 assert_value_and_type("a", &result, 1.0, NumericType::default());
2643 assert_value_and_type("b", &result, 3.0, NumericType::default());
2644 assert_value_and_type("c", &result, 1.0, NumericType::default());
2645 assert_value_and_type("d", &result, 1.0, NumericType::default());
2646 }
2647
2648 #[tokio::test(flavor = "multi_thread")]
2649 async fn coerce_nested_array() {
2650 let (ctx, mut exec_state) = new_exec_state().await;
2651
2652 let mixed1 = KclValue::HomArray {
2653 value: vec![
2654 KclValue::Number {
2655 value: 0.0,
2656 ty: NumericType::count(),
2657 meta: Vec::new(),
2658 },
2659 KclValue::Number {
2660 value: 1.0,
2661 ty: NumericType::count(),
2662 meta: Vec::new(),
2663 },
2664 KclValue::HomArray {
2665 value: vec![
2666 KclValue::Number {
2667 value: 2.0,
2668 ty: NumericType::count(),
2669 meta: Vec::new(),
2670 },
2671 KclValue::Number {
2672 value: 3.0,
2673 ty: NumericType::count(),
2674 meta: Vec::new(),
2675 },
2676 ],
2677 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2678 },
2679 ],
2680 ty: RuntimeType::any(),
2681 };
2682
2683 let tym1 = RuntimeType::Array(
2685 Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2686 ArrayLen::Minimum(1),
2687 );
2688
2689 let result = KclValue::HomArray {
2690 value: vec![
2691 KclValue::Number {
2692 value: 0.0,
2693 ty: NumericType::count(),
2694 meta: Vec::new(),
2695 },
2696 KclValue::Number {
2697 value: 1.0,
2698 ty: NumericType::count(),
2699 meta: Vec::new(),
2700 },
2701 KclValue::Number {
2702 value: 2.0,
2703 ty: NumericType::count(),
2704 meta: Vec::new(),
2705 },
2706 KclValue::Number {
2707 value: 3.0,
2708 ty: NumericType::count(),
2709 meta: Vec::new(),
2710 },
2711 ],
2712 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2713 };
2714 assert_coerce_results(&mixed1, &tym1, &result, &mut exec_state);
2715 ctx.close().await;
2716 }
2717}