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::Object {
1535 value, constrainable, ..
1536 } => {
1537 let properties = value
1538 .iter()
1539 .map(|(k, v)| v.principal_type().map(|t| (k.clone(), t)))
1540 .collect::<Option<Vec<_>>>()?;
1541 Some(RuntimeType::Object(properties, *constrainable))
1542 }
1543 KclValue::GdtAnnotation { .. } => Some(RuntimeType::Primitive(PrimitiveType::GdtAnnotation)),
1544 KclValue::Plane { .. } => Some(RuntimeType::Primitive(PrimitiveType::Plane)),
1545 KclValue::Sketch { .. } => Some(RuntimeType::Primitive(PrimitiveType::Sketch)),
1546 KclValue::Solid { .. } => Some(RuntimeType::Primitive(PrimitiveType::Solid)),
1547 KclValue::Face { .. } => Some(RuntimeType::Primitive(PrimitiveType::Face)),
1548 KclValue::Helix { .. } => Some(RuntimeType::Primitive(PrimitiveType::Helix)),
1549 KclValue::ImportedGeometry(..) => Some(RuntimeType::Primitive(PrimitiveType::ImportedGeometry)),
1550 KclValue::Tuple { value, .. } => Some(RuntimeType::Tuple(
1551 value.iter().map(|v| v.principal_type()).collect::<Option<Vec<_>>>()?,
1552 )),
1553 KclValue::HomArray { ty, value, .. } => {
1554 Some(RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Known(value.len())))
1555 }
1556 KclValue::TagIdentifier(_) => Some(RuntimeType::Primitive(PrimitiveType::TaggedEdge)),
1557 KclValue::TagDeclarator(_) => Some(RuntimeType::Primitive(PrimitiveType::TagDecl)),
1558 KclValue::Uuid { .. } => Some(RuntimeType::Primitive(PrimitiveType::Edge)),
1559 KclValue::Function { .. } => Some(RuntimeType::Primitive(PrimitiveType::Function)),
1560 KclValue::KclNone { .. } => Some(RuntimeType::Primitive(PrimitiveType::None)),
1561 KclValue::Module { .. } | KclValue::Type { .. } => None,
1562 }
1563 }
1564
1565 pub fn principal_type_string(&self) -> String {
1566 if let Some(ty) = self.principal_type() {
1567 return format!("`{ty}`");
1568 }
1569
1570 match self {
1571 KclValue::Module { .. } => "module",
1572 KclValue::KclNone { .. } => "none",
1573 KclValue::Type { .. } => "type",
1574 _ => {
1575 debug_assert!(false);
1576 "<unexpected type>"
1577 }
1578 }
1579 .to_owned()
1580 }
1581}
1582
1583#[cfg(test)]
1584mod test {
1585 use super::*;
1586 use crate::execution::{ExecTestResults, parse_execute};
1587
1588 fn values(exec_state: &mut ExecState) -> Vec<KclValue> {
1589 vec![
1590 KclValue::Bool {
1591 value: true,
1592 meta: Vec::new(),
1593 },
1594 KclValue::Number {
1595 value: 1.0,
1596 ty: NumericType::count(),
1597 meta: Vec::new(),
1598 },
1599 KclValue::String {
1600 value: "hello".to_owned(),
1601 meta: Vec::new(),
1602 },
1603 KclValue::Tuple {
1604 value: Vec::new(),
1605 meta: Vec::new(),
1606 },
1607 KclValue::HomArray {
1608 value: Vec::new(),
1609 ty: RuntimeType::solid(),
1610 },
1611 KclValue::Object {
1612 value: crate::execution::KclObjectFields::new(),
1613 meta: Vec::new(),
1614 constrainable: false,
1615 },
1616 KclValue::TagIdentifier(Box::new("foo".parse().unwrap())),
1617 KclValue::TagDeclarator(Box::new(crate::parsing::ast::types::TagDeclarator::new("foo"))),
1618 KclValue::Plane {
1619 value: Box::new(Plane::from_plane_data(crate::std::sketch::PlaneData::XY, exec_state).unwrap()),
1620 },
1621 KclValue::ImportedGeometry(crate::execution::ImportedGeometry::new(
1623 uuid::Uuid::nil(),
1624 Vec::new(),
1625 Vec::new(),
1626 )),
1627 ]
1629 }
1630
1631 #[track_caller]
1632 fn assert_coerce_results(
1633 value: &KclValue,
1634 super_type: &RuntimeType,
1635 expected_value: &KclValue,
1636 exec_state: &mut ExecState,
1637 ) {
1638 let is_subtype = value == expected_value;
1639 let actual = value.coerce(super_type, true, exec_state).unwrap();
1640 assert_eq!(&actual, expected_value);
1641 assert_eq!(
1642 is_subtype,
1643 value.principal_type().is_some() && value.principal_type().unwrap().subtype(super_type),
1644 "{:?} <: {super_type:?} should be {is_subtype}",
1645 value.principal_type().unwrap()
1646 );
1647 assert!(
1648 expected_value.principal_type().unwrap().subtype(super_type),
1649 "{} <: {super_type}",
1650 expected_value.principal_type().unwrap()
1651 )
1652 }
1653
1654 #[tokio::test(flavor = "multi_thread")]
1655 async fn coerce_idempotent() {
1656 let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock(None).await);
1657 let values = values(&mut exec_state);
1658 for v in &values {
1659 let ty = v.principal_type().unwrap();
1661 assert_coerce_results(v, &ty, v, &mut exec_state);
1662
1663 let uty1 = RuntimeType::Union(vec![ty.clone()]);
1665 let uty2 = RuntimeType::Union(vec![ty.clone(), RuntimeType::Primitive(PrimitiveType::Boolean)]);
1666 assert_coerce_results(v, &uty1, v, &mut exec_state);
1667 assert_coerce_results(v, &uty2, v, &mut exec_state);
1668
1669 let aty = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::None);
1671 let aty1 = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Known(1));
1672 let aty0 = RuntimeType::Array(Box::new(ty.clone()), ArrayLen::Minimum(1));
1673
1674 match v {
1675 KclValue::HomArray { .. } => {
1676 assert_coerce_results(
1678 v,
1679 &aty,
1680 &KclValue::HomArray {
1681 value: vec![],
1682 ty: ty.clone(),
1683 },
1684 &mut exec_state,
1685 );
1686 v.coerce(&aty1, true, &mut exec_state).unwrap_err();
1689 v.coerce(&aty0, true, &mut exec_state).unwrap_err();
1692 }
1693 KclValue::Tuple { .. } => {}
1694 _ => {
1695 assert_coerce_results(v, &aty, v, &mut exec_state);
1696 assert_coerce_results(v, &aty1, v, &mut exec_state);
1697 assert_coerce_results(v, &aty0, v, &mut exec_state);
1698
1699 let tty = RuntimeType::Tuple(vec![ty.clone()]);
1701 assert_coerce_results(v, &tty, v, &mut exec_state);
1702 }
1703 }
1704 }
1705
1706 for v in &values[1..] {
1707 v.coerce(&RuntimeType::Primitive(PrimitiveType::Boolean), true, &mut exec_state)
1709 .unwrap_err();
1710 }
1711 }
1712
1713 #[tokio::test(flavor = "multi_thread")]
1714 async fn coerce_none() {
1715 let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock(None).await);
1716 let none = KclValue::KclNone {
1717 value: crate::parsing::ast::types::KclNone::new(),
1718 meta: Vec::new(),
1719 };
1720
1721 let aty = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::None);
1722 let aty0 = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::Known(0));
1723 let aty1 = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::Known(1));
1724 let aty1p = RuntimeType::Array(Box::new(RuntimeType::solid()), ArrayLen::Minimum(1));
1725 assert_coerce_results(
1726 &none,
1727 &aty,
1728 &KclValue::HomArray {
1729 value: Vec::new(),
1730 ty: RuntimeType::solid(),
1731 },
1732 &mut exec_state,
1733 );
1734 assert_coerce_results(
1735 &none,
1736 &aty0,
1737 &KclValue::HomArray {
1738 value: Vec::new(),
1739 ty: RuntimeType::solid(),
1740 },
1741 &mut exec_state,
1742 );
1743 none.coerce(&aty1, true, &mut exec_state).unwrap_err();
1744 none.coerce(&aty1p, true, &mut exec_state).unwrap_err();
1745
1746 let tty = RuntimeType::Tuple(vec![]);
1747 let tty1 = RuntimeType::Tuple(vec![RuntimeType::solid()]);
1748 assert_coerce_results(
1749 &none,
1750 &tty,
1751 &KclValue::Tuple {
1752 value: Vec::new(),
1753 meta: Vec::new(),
1754 },
1755 &mut exec_state,
1756 );
1757 none.coerce(&tty1, true, &mut exec_state).unwrap_err();
1758
1759 let oty = RuntimeType::Object(vec![], false);
1760 assert_coerce_results(
1761 &none,
1762 &oty,
1763 &KclValue::Object {
1764 value: HashMap::new(),
1765 meta: Vec::new(),
1766 constrainable: false,
1767 },
1768 &mut exec_state,
1769 );
1770 }
1771
1772 #[tokio::test(flavor = "multi_thread")]
1773 async fn coerce_record() {
1774 let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock(None).await);
1775
1776 let obj0 = KclValue::Object {
1777 value: HashMap::new(),
1778 meta: Vec::new(),
1779 constrainable: false,
1780 };
1781 let obj1 = KclValue::Object {
1782 value: [(
1783 "foo".to_owned(),
1784 KclValue::Bool {
1785 value: true,
1786 meta: Vec::new(),
1787 },
1788 )]
1789 .into(),
1790 meta: Vec::new(),
1791 constrainable: false,
1792 };
1793 let obj2 = KclValue::Object {
1794 value: [
1795 (
1796 "foo".to_owned(),
1797 KclValue::Bool {
1798 value: true,
1799 meta: Vec::new(),
1800 },
1801 ),
1802 (
1803 "bar".to_owned(),
1804 KclValue::Number {
1805 value: 0.0,
1806 ty: NumericType::count(),
1807 meta: Vec::new(),
1808 },
1809 ),
1810 (
1811 "baz".to_owned(),
1812 KclValue::Number {
1813 value: 42.0,
1814 ty: NumericType::count(),
1815 meta: Vec::new(),
1816 },
1817 ),
1818 ]
1819 .into(),
1820 meta: Vec::new(),
1821 constrainable: false,
1822 };
1823
1824 let ty0 = RuntimeType::Object(vec![], false);
1825 assert_coerce_results(&obj0, &ty0, &obj0, &mut exec_state);
1826 assert_coerce_results(&obj1, &ty0, &obj1, &mut exec_state);
1827 assert_coerce_results(&obj2, &ty0, &obj2, &mut exec_state);
1828
1829 let ty1 = RuntimeType::Object(
1830 vec![("foo".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean))],
1831 false,
1832 );
1833 obj0.coerce(&ty1, true, &mut exec_state).unwrap_err();
1834 assert_coerce_results(&obj1, &ty1, &obj1, &mut exec_state);
1835 assert_coerce_results(&obj2, &ty1, &obj2, &mut exec_state);
1836
1837 let ty2 = RuntimeType::Object(
1839 vec![
1840 (
1841 "bar".to_owned(),
1842 RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1843 ),
1844 ("foo".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean)),
1845 ],
1846 false,
1847 );
1848 obj0.coerce(&ty2, true, &mut exec_state).unwrap_err();
1849 obj1.coerce(&ty2, true, &mut exec_state).unwrap_err();
1850 assert_coerce_results(&obj2, &ty2, &obj2, &mut exec_state);
1851
1852 let tyq = RuntimeType::Object(
1854 vec![("qux".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean))],
1855 false,
1856 );
1857 obj0.coerce(&tyq, true, &mut exec_state).unwrap_err();
1858 obj1.coerce(&tyq, true, &mut exec_state).unwrap_err();
1859 obj2.coerce(&tyq, true, &mut exec_state).unwrap_err();
1860
1861 let ty1 = RuntimeType::Object(
1863 vec![("bar".to_owned(), RuntimeType::Primitive(PrimitiveType::Boolean))],
1864 false,
1865 );
1866 obj2.coerce(&ty1, true, &mut exec_state).unwrap_err();
1867 }
1868
1869 #[tokio::test(flavor = "multi_thread")]
1870 async fn coerce_array() {
1871 let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock(None).await);
1872
1873 let hom_arr = KclValue::HomArray {
1874 value: vec![
1875 KclValue::Number {
1876 value: 0.0,
1877 ty: NumericType::count(),
1878 meta: Vec::new(),
1879 },
1880 KclValue::Number {
1881 value: 1.0,
1882 ty: NumericType::count(),
1883 meta: Vec::new(),
1884 },
1885 KclValue::Number {
1886 value: 2.0,
1887 ty: NumericType::count(),
1888 meta: Vec::new(),
1889 },
1890 KclValue::Number {
1891 value: 3.0,
1892 ty: NumericType::count(),
1893 meta: Vec::new(),
1894 },
1895 ],
1896 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1897 };
1898 let mixed1 = KclValue::Tuple {
1899 value: vec![
1900 KclValue::Number {
1901 value: 0.0,
1902 ty: NumericType::count(),
1903 meta: Vec::new(),
1904 },
1905 KclValue::Number {
1906 value: 1.0,
1907 ty: NumericType::count(),
1908 meta: Vec::new(),
1909 },
1910 ],
1911 meta: Vec::new(),
1912 };
1913 let mixed2 = KclValue::Tuple {
1914 value: vec![
1915 KclValue::Number {
1916 value: 0.0,
1917 ty: NumericType::count(),
1918 meta: Vec::new(),
1919 },
1920 KclValue::Bool {
1921 value: true,
1922 meta: Vec::new(),
1923 },
1924 ],
1925 meta: Vec::new(),
1926 };
1927
1928 let tyh = RuntimeType::Array(
1930 Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
1931 ArrayLen::Known(4),
1932 );
1933 let tym1 = RuntimeType::Tuple(vec![
1934 RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1935 RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1936 ]);
1937 let tym2 = RuntimeType::Tuple(vec![
1938 RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1939 RuntimeType::Primitive(PrimitiveType::Boolean),
1940 ]);
1941 assert_coerce_results(&hom_arr, &tyh, &hom_arr, &mut exec_state);
1942 assert_coerce_results(&mixed1, &tym1, &mixed1, &mut exec_state);
1943 assert_coerce_results(&mixed2, &tym2, &mixed2, &mut exec_state);
1944 mixed1.coerce(&tym2, true, &mut exec_state).unwrap_err();
1945 mixed2.coerce(&tym1, true, &mut exec_state).unwrap_err();
1946
1947 let tyhn = RuntimeType::Array(
1949 Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
1950 ArrayLen::None,
1951 );
1952 let tyh1 = RuntimeType::Array(
1953 Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
1954 ArrayLen::Minimum(1),
1955 );
1956 let tyh3 = RuntimeType::Array(
1957 Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
1958 ArrayLen::Known(3),
1959 );
1960 let tyhm3 = RuntimeType::Array(
1961 Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
1962 ArrayLen::Minimum(3),
1963 );
1964 let tyhm5 = RuntimeType::Array(
1965 Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
1966 ArrayLen::Minimum(5),
1967 );
1968 assert_coerce_results(&hom_arr, &tyhn, &hom_arr, &mut exec_state);
1969 assert_coerce_results(&hom_arr, &tyh1, &hom_arr, &mut exec_state);
1970 hom_arr.coerce(&tyh3, true, &mut exec_state).unwrap_err();
1971 assert_coerce_results(&hom_arr, &tyhm3, &hom_arr, &mut exec_state);
1972 hom_arr.coerce(&tyhm5, true, &mut exec_state).unwrap_err();
1973
1974 let hom_arr0 = KclValue::HomArray {
1975 value: vec![],
1976 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1977 };
1978 assert_coerce_results(&hom_arr0, &tyhn, &hom_arr0, &mut exec_state);
1979 hom_arr0.coerce(&tyh1, true, &mut exec_state).unwrap_err();
1980 hom_arr0.coerce(&tyh3, true, &mut exec_state).unwrap_err();
1981
1982 let tym1 = RuntimeType::Tuple(vec![
1985 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
1986 RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
1987 ]);
1988 let tym2 = RuntimeType::Tuple(vec![
1989 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
1990 RuntimeType::Primitive(PrimitiveType::Boolean),
1991 ]);
1992 assert_coerce_results(&mixed1, &tym1, &mixed1, &mut exec_state);
1995 assert_coerce_results(&mixed2, &tym2, &mixed2, &mut exec_state);
1996
1997 let hom_arr_2 = KclValue::HomArray {
1999 value: vec![
2000 KclValue::Number {
2001 value: 0.0,
2002 ty: NumericType::count(),
2003 meta: Vec::new(),
2004 },
2005 KclValue::Number {
2006 value: 1.0,
2007 ty: NumericType::count(),
2008 meta: Vec::new(),
2009 },
2010 ],
2011 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2012 };
2013 let mixed0 = KclValue::Tuple {
2014 value: vec![],
2015 meta: Vec::new(),
2016 };
2017 assert_coerce_results(&mixed1, &tyhn, &hom_arr_2, &mut exec_state);
2018 assert_coerce_results(&mixed1, &tyh1, &hom_arr_2, &mut exec_state);
2019 assert_coerce_results(&mixed0, &tyhn, &hom_arr0, &mut exec_state);
2020 mixed0.coerce(&tyh, true, &mut exec_state).unwrap_err();
2021 mixed0.coerce(&tyh1, true, &mut exec_state).unwrap_err();
2022
2023 assert_coerce_results(&hom_arr_2, &tym1, &mixed1, &mut exec_state);
2025 hom_arr.coerce(&tym1, true, &mut exec_state).unwrap_err();
2026 hom_arr_2.coerce(&tym2, true, &mut exec_state).unwrap_err();
2027
2028 mixed0.coerce(&tym1, true, &mut exec_state).unwrap_err();
2029 mixed0.coerce(&tym2, true, &mut exec_state).unwrap_err();
2030 }
2031
2032 #[tokio::test(flavor = "multi_thread")]
2033 async fn coerce_union() {
2034 let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock(None).await);
2035
2036 assert!(RuntimeType::Union(vec![]).subtype(&RuntimeType::Union(vec![
2038 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2039 RuntimeType::Primitive(PrimitiveType::Boolean)
2040 ])));
2041 assert!(
2042 RuntimeType::Union(vec![RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any))]).subtype(
2043 &RuntimeType::Union(vec![
2044 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2045 RuntimeType::Primitive(PrimitiveType::Boolean)
2046 ])
2047 )
2048 );
2049 assert!(
2050 RuntimeType::Union(vec![
2051 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2052 RuntimeType::Primitive(PrimitiveType::Boolean)
2053 ])
2054 .subtype(&RuntimeType::Union(vec![
2055 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2056 RuntimeType::Primitive(PrimitiveType::Boolean)
2057 ]))
2058 );
2059
2060 let count = KclValue::Number {
2062 value: 1.0,
2063 ty: NumericType::count(),
2064 meta: Vec::new(),
2065 };
2066
2067 let tya = RuntimeType::Union(vec![RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any))]);
2068 let tya2 = RuntimeType::Union(vec![
2069 RuntimeType::Primitive(PrimitiveType::Number(NumericType::Any)),
2070 RuntimeType::Primitive(PrimitiveType::Boolean),
2071 ]);
2072 assert_coerce_results(&count, &tya, &count, &mut exec_state);
2073 assert_coerce_results(&count, &tya2, &count, &mut exec_state);
2074
2075 let tyb = RuntimeType::Union(vec![RuntimeType::Primitive(PrimitiveType::Boolean)]);
2077 let tyb2 = RuntimeType::Union(vec![
2078 RuntimeType::Primitive(PrimitiveType::Boolean),
2079 RuntimeType::Primitive(PrimitiveType::String),
2080 ]);
2081 count.coerce(&tyb, true, &mut exec_state).unwrap_err();
2082 count.coerce(&tyb2, true, &mut exec_state).unwrap_err();
2083 }
2084
2085 #[tokio::test(flavor = "multi_thread")]
2086 async fn coerce_axes() {
2087 let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock(None).await);
2088
2089 assert!(RuntimeType::Primitive(PrimitiveType::Axis2d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis2d)));
2091 assert!(RuntimeType::Primitive(PrimitiveType::Axis3d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis3d)));
2092 assert!(!RuntimeType::Primitive(PrimitiveType::Axis3d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis2d)));
2093 assert!(!RuntimeType::Primitive(PrimitiveType::Axis2d).subtype(&RuntimeType::Primitive(PrimitiveType::Axis3d)));
2094
2095 let a2d = KclValue::Object {
2097 value: [
2098 (
2099 "origin".to_owned(),
2100 KclValue::HomArray {
2101 value: vec![
2102 KclValue::Number {
2103 value: 0.0,
2104 ty: NumericType::mm(),
2105 meta: Vec::new(),
2106 },
2107 KclValue::Number {
2108 value: 0.0,
2109 ty: NumericType::mm(),
2110 meta: Vec::new(),
2111 },
2112 ],
2113 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
2114 },
2115 ),
2116 (
2117 "direction".to_owned(),
2118 KclValue::HomArray {
2119 value: vec![
2120 KclValue::Number {
2121 value: 1.0,
2122 ty: NumericType::mm(),
2123 meta: Vec::new(),
2124 },
2125 KclValue::Number {
2126 value: 0.0,
2127 ty: NumericType::mm(),
2128 meta: Vec::new(),
2129 },
2130 ],
2131 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
2132 },
2133 ),
2134 ]
2135 .into(),
2136 meta: Vec::new(),
2137 constrainable: false,
2138 };
2139 let a3d = KclValue::Object {
2140 value: [
2141 (
2142 "origin".to_owned(),
2143 KclValue::HomArray {
2144 value: vec![
2145 KclValue::Number {
2146 value: 0.0,
2147 ty: NumericType::mm(),
2148 meta: Vec::new(),
2149 },
2150 KclValue::Number {
2151 value: 0.0,
2152 ty: NumericType::mm(),
2153 meta: Vec::new(),
2154 },
2155 KclValue::Number {
2156 value: 0.0,
2157 ty: NumericType::mm(),
2158 meta: Vec::new(),
2159 },
2160 ],
2161 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
2162 },
2163 ),
2164 (
2165 "direction".to_owned(),
2166 KclValue::HomArray {
2167 value: vec![
2168 KclValue::Number {
2169 value: 1.0,
2170 ty: NumericType::mm(),
2171 meta: Vec::new(),
2172 },
2173 KclValue::Number {
2174 value: 0.0,
2175 ty: NumericType::mm(),
2176 meta: Vec::new(),
2177 },
2178 KclValue::Number {
2179 value: 1.0,
2180 ty: NumericType::mm(),
2181 meta: Vec::new(),
2182 },
2183 ],
2184 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::mm())),
2185 },
2186 ),
2187 ]
2188 .into(),
2189 meta: Vec::new(),
2190 constrainable: false,
2191 };
2192
2193 let ty2d = RuntimeType::Primitive(PrimitiveType::Axis2d);
2194 let ty3d = RuntimeType::Primitive(PrimitiveType::Axis3d);
2195
2196 assert_coerce_results(&a2d, &ty2d, &a2d, &mut exec_state);
2197 assert_coerce_results(&a3d, &ty3d, &a3d, &mut exec_state);
2198 assert_coerce_results(&a3d, &ty2d, &a2d, &mut exec_state);
2199 a2d.coerce(&ty3d, true, &mut exec_state).unwrap_err();
2200 }
2201
2202 #[tokio::test(flavor = "multi_thread")]
2203 async fn coerce_numeric() {
2204 let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock(None).await);
2205
2206 let count = KclValue::Number {
2207 value: 1.0,
2208 ty: NumericType::count(),
2209 meta: Vec::new(),
2210 };
2211 let mm = KclValue::Number {
2212 value: 1.0,
2213 ty: NumericType::mm(),
2214 meta: Vec::new(),
2215 };
2216 let inches = KclValue::Number {
2217 value: 1.0,
2218 ty: NumericType::Known(UnitType::Length(UnitLength::Inches)),
2219 meta: Vec::new(),
2220 };
2221 let rads = KclValue::Number {
2222 value: 1.0,
2223 ty: NumericType::Known(UnitType::Angle(UnitAngle::Radians)),
2224 meta: Vec::new(),
2225 };
2226 let default = KclValue::Number {
2227 value: 1.0,
2228 ty: NumericType::default(),
2229 meta: Vec::new(),
2230 };
2231 let any = KclValue::Number {
2232 value: 1.0,
2233 ty: NumericType::Any,
2234 meta: Vec::new(),
2235 };
2236 let unknown = KclValue::Number {
2237 value: 1.0,
2238 ty: NumericType::Unknown,
2239 meta: Vec::new(),
2240 };
2241
2242 assert_coerce_results(&count, &NumericType::count().into(), &count, &mut exec_state);
2244 assert_coerce_results(&mm, &NumericType::mm().into(), &mm, &mut exec_state);
2245 assert_coerce_results(&any, &NumericType::Any.into(), &any, &mut exec_state);
2246 assert_coerce_results(&unknown, &NumericType::Unknown.into(), &unknown, &mut exec_state);
2247 assert_coerce_results(&default, &NumericType::default().into(), &default, &mut exec_state);
2248
2249 assert_coerce_results(&count, &NumericType::Any.into(), &count, &mut exec_state);
2250 assert_coerce_results(&mm, &NumericType::Any.into(), &mm, &mut exec_state);
2251 assert_coerce_results(&unknown, &NumericType::Any.into(), &unknown, &mut exec_state);
2252 assert_coerce_results(&default, &NumericType::Any.into(), &default, &mut exec_state);
2253
2254 assert_eq!(
2255 default
2256 .coerce(
2257 &NumericType::Default {
2258 len: UnitLength::Yards,
2259 angle: UnitAngle::Degrees,
2260 }
2261 .into(),
2262 true,
2263 &mut exec_state
2264 )
2265 .unwrap(),
2266 default
2267 );
2268
2269 count
2271 .coerce(&NumericType::mm().into(), true, &mut exec_state)
2272 .unwrap_err();
2273 mm.coerce(&NumericType::count().into(), true, &mut exec_state)
2274 .unwrap_err();
2275 unknown
2276 .coerce(&NumericType::mm().into(), true, &mut exec_state)
2277 .unwrap_err();
2278 unknown
2279 .coerce(&NumericType::default().into(), true, &mut exec_state)
2280 .unwrap_err();
2281
2282 count
2283 .coerce(&NumericType::Unknown.into(), true, &mut exec_state)
2284 .unwrap_err();
2285 mm.coerce(&NumericType::Unknown.into(), true, &mut exec_state)
2286 .unwrap_err();
2287 default
2288 .coerce(&NumericType::Unknown.into(), true, &mut exec_state)
2289 .unwrap_err();
2290
2291 assert_eq!(
2292 inches
2293 .coerce(&NumericType::mm().into(), true, &mut exec_state)
2294 .unwrap()
2295 .as_f64()
2296 .unwrap()
2297 .round(),
2298 25.0
2299 );
2300 assert_eq!(
2301 rads.coerce(
2302 &NumericType::Known(UnitType::Angle(UnitAngle::Degrees)).into(),
2303 true,
2304 &mut exec_state
2305 )
2306 .unwrap()
2307 .as_f64()
2308 .unwrap()
2309 .round(),
2310 57.0
2311 );
2312 assert_eq!(
2313 inches
2314 .coerce(&NumericType::default().into(), true, &mut exec_state)
2315 .unwrap()
2316 .as_f64()
2317 .unwrap()
2318 .round(),
2319 1.0
2320 );
2321 assert_eq!(
2322 rads.coerce(&NumericType::default().into(), true, &mut exec_state)
2323 .unwrap()
2324 .as_f64()
2325 .unwrap()
2326 .round(),
2327 1.0
2328 );
2329 }
2330
2331 #[track_caller]
2332 fn assert_value_and_type(name: &str, result: &ExecTestResults, expected: f64, expected_ty: NumericType) {
2333 let mem = result.exec_state.stack();
2334 match mem
2335 .memory
2336 .get_from(name, result.mem_env, SourceRange::default(), 0)
2337 .unwrap()
2338 {
2339 KclValue::Number { value, ty, .. } => {
2340 assert_eq!(value.round(), expected);
2341 assert_eq!(*ty, expected_ty);
2342 }
2343 _ => unreachable!(),
2344 }
2345 }
2346
2347 #[tokio::test(flavor = "multi_thread")]
2348 async fn combine_numeric() {
2349 let program = r#"a = 5 + 4
2350b = 5 - 2
2351c = 5mm - 2mm + 10mm
2352d = 5mm - 2 + 10
2353e = 5 - 2mm + 10
2354f = 30mm - 1inch
2355
2356g = 2 * 10
2357h = 2 * 10mm
2358i = 2mm * 10mm
2359j = 2_ * 10
2360k = 2_ * 3mm * 3mm
2361
2362l = 1 / 10
2363m = 2mm / 1mm
2364n = 10inch / 2mm
2365o = 3mm / 3
2366p = 3_ / 4
2367q = 4inch / 2_
2368
2369r = min([0, 3, 42])
2370s = min([0, 3mm, -42])
2371t = min([100, 3in, 142mm])
2372u = min([3rad, 4in])
2373"#;
2374
2375 let result = parse_execute(program).await.unwrap();
2376 assert_eq!(
2377 result.exec_state.errors().len(),
2378 5,
2379 "errors: {:?}",
2380 result.exec_state.errors()
2381 );
2382
2383 assert_value_and_type("a", &result, 9.0, NumericType::default());
2384 assert_value_and_type("b", &result, 3.0, NumericType::default());
2385 assert_value_and_type("c", &result, 13.0, NumericType::mm());
2386 assert_value_and_type("d", &result, 13.0, NumericType::mm());
2387 assert_value_and_type("e", &result, 13.0, NumericType::mm());
2388 assert_value_and_type("f", &result, 5.0, NumericType::mm());
2389
2390 assert_value_and_type("g", &result, 20.0, NumericType::default());
2391 assert_value_and_type("h", &result, 20.0, NumericType::mm());
2392 assert_value_and_type("i", &result, 20.0, NumericType::Unknown);
2393 assert_value_and_type("j", &result, 20.0, NumericType::default());
2394 assert_value_and_type("k", &result, 18.0, NumericType::Unknown);
2395
2396 assert_value_and_type("l", &result, 0.0, NumericType::default());
2397 assert_value_and_type("m", &result, 2.0, NumericType::count());
2398 assert_value_and_type("n", &result, 5.0, NumericType::Unknown);
2399 assert_value_and_type("o", &result, 1.0, NumericType::mm());
2400 assert_value_and_type("p", &result, 1.0, NumericType::count());
2401 assert_value_and_type(
2402 "q",
2403 &result,
2404 2.0,
2405 NumericType::Known(UnitType::Length(UnitLength::Inches)),
2406 );
2407
2408 assert_value_and_type("r", &result, 0.0, NumericType::default());
2409 assert_value_and_type("s", &result, -42.0, NumericType::mm());
2410 assert_value_and_type("t", &result, 3.0, NumericType::Unknown);
2411 assert_value_and_type("u", &result, 3.0, NumericType::Unknown);
2412 }
2413
2414 #[tokio::test(flavor = "multi_thread")]
2415 async fn bad_typed_arithmetic() {
2416 let program = r#"
2417a = 1rad
2418b = 180 / PI * a + 360
2419"#;
2420
2421 let result = parse_execute(program).await.unwrap();
2422
2423 assert_value_and_type("a", &result, 1.0, NumericType::radians());
2424 assert_value_and_type("b", &result, 417.0, NumericType::Unknown);
2425 }
2426
2427 #[tokio::test(flavor = "multi_thread")]
2428 async fn cos_coercions() {
2429 let program = r#"
2430a = cos(units::toRadians(30deg))
2431b = 3 / a
2432c = cos(30deg)
2433d = cos(1rad)
2434"#;
2435
2436 let result = parse_execute(program).await.unwrap();
2437 assert!(
2438 result.exec_state.errors().is_empty(),
2439 "{:?}",
2440 result.exec_state.errors()
2441 );
2442
2443 assert_value_and_type("a", &result, 1.0, NumericType::default());
2444 assert_value_and_type("b", &result, 3.0, NumericType::default());
2445 assert_value_and_type("c", &result, 1.0, NumericType::default());
2446 assert_value_and_type("d", &result, 1.0, NumericType::default());
2447 }
2448
2449 #[tokio::test(flavor = "multi_thread")]
2450 async fn coerce_nested_array() {
2451 let mut exec_state = ExecState::new(&crate::ExecutorContext::new_mock(None).await);
2452
2453 let mixed1 = KclValue::HomArray {
2454 value: vec![
2455 KclValue::Number {
2456 value: 0.0,
2457 ty: NumericType::count(),
2458 meta: Vec::new(),
2459 },
2460 KclValue::Number {
2461 value: 1.0,
2462 ty: NumericType::count(),
2463 meta: Vec::new(),
2464 },
2465 KclValue::HomArray {
2466 value: vec![
2467 KclValue::Number {
2468 value: 2.0,
2469 ty: NumericType::count(),
2470 meta: Vec::new(),
2471 },
2472 KclValue::Number {
2473 value: 3.0,
2474 ty: NumericType::count(),
2475 meta: Vec::new(),
2476 },
2477 ],
2478 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2479 },
2480 ],
2481 ty: RuntimeType::any(),
2482 };
2483
2484 let tym1 = RuntimeType::Array(
2486 Box::new(RuntimeType::Primitive(PrimitiveType::Number(NumericType::count()))),
2487 ArrayLen::Minimum(1),
2488 );
2489
2490 let result = KclValue::HomArray {
2491 value: vec![
2492 KclValue::Number {
2493 value: 0.0,
2494 ty: NumericType::count(),
2495 meta: Vec::new(),
2496 },
2497 KclValue::Number {
2498 value: 1.0,
2499 ty: NumericType::count(),
2500 meta: Vec::new(),
2501 },
2502 KclValue::Number {
2503 value: 2.0,
2504 ty: NumericType::count(),
2505 meta: Vec::new(),
2506 },
2507 KclValue::Number {
2508 value: 3.0,
2509 ty: NumericType::count(),
2510 meta: Vec::new(),
2511 },
2512 ],
2513 ty: RuntimeType::Primitive(PrimitiveType::Number(NumericType::count())),
2514 };
2515 assert_coerce_results(&mixed1, &tym1, &result, &mut exec_state);
2516 }
2517}