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