Turn on units of measure (BREAKING CHANGE) (#6343)

* Turn on uom checks

Signed-off-by: Nick Cameron <nrc@ncameron.org>

* Convert all lengths to mm for engine calls

Signed-off-by: Nick Cameron <nrc@ncameron.org>

---------

Signed-off-by: Nick Cameron <nrc@ncameron.org>
This commit is contained in:
Nick Cameron
2025-04-23 10:58:35 +12:00
committed by GitHub
parent 3d22f6cd66
commit b7385d5f25
339 changed files with 35471 additions and 17237 deletions

View File

@ -3,11 +3,7 @@ use std::collections::HashMap;
use async_recursion::async_recursion;
use indexmap::IndexMap;
use super::{
cad_op::Group,
kcl_value::TypeDef,
types::{PrimitiveType, CHECK_NUMERIC_TYPES},
};
use super::{cad_op::Group, kcl_value::TypeDef, types::PrimitiveType};
use crate::{
errors::{KclError, KclErrorDetails},
execution::{
@ -64,14 +60,6 @@ impl ExecutorContext {
if exec_state.mod_local.settings.update_from_annotation(annotation)? {
exec_state.mod_local.explicit_length_units = true;
}
let new_units = exec_state.length_unit();
self.engine
.set_units(
new_units.into(),
annotation.as_source_range(),
exec_state.id_generator(),
)
.await?;
} else {
exec_state.err(CompilationError::err(
annotation.as_source_range(),
@ -873,7 +861,10 @@ impl Node<MemberExpression> {
source_ranges: vec![self.clone().into()],
}))
}
(KclValue::MixedArray { value: arr, meta: _ }, Property::UInt(index)) => {
(
KclValue::MixedArray { value: arr, .. } | KclValue::HomArray { value: arr, .. },
Property::UInt(index),
) => {
let value_of_arr = arr.get(index);
if let Some(value) = value_of_arr {
Ok(value.to_owned())
@ -884,7 +875,7 @@ impl Node<MemberExpression> {
}))
}
}
(KclValue::MixedArray { .. }, p) => {
(KclValue::MixedArray { .. } | KclValue::HomArray { .. }, p) => {
let t = p.type_name();
let article = article_for(t);
Err(KclError::Semantic(KclErrorDetails {
@ -1051,7 +1042,7 @@ impl Node<BinaryExpression> {
BinaryOperator::Pow => KclValue::Number {
value: left.n.powf(right.n),
meta,
ty: NumericType::Unknown,
ty: exec_state.current_default_units(),
},
BinaryOperator::Neq => {
let (l, r, ty) = NumericType::combine_eq(left, right);
@ -1090,7 +1081,7 @@ impl Node<BinaryExpression> {
}
fn warn_on_unknown(&self, ty: &NumericType, verb: &str, exec_state: &mut ExecState) {
if *CHECK_NUMERIC_TYPES && ty == &NumericType::Unknown {
if ty == &NumericType::Unknown {
// TODO suggest how to fix this
exec_state.warn(CompilationError::err(
self.as_source_range(),
@ -1999,11 +1990,39 @@ fn assign_args_to_params(
for (index, param) in function_expression.params.iter().enumerate() {
if let Some(arg) = args.get(index) {
// Argument was provided.
exec_state.mut_stack().add(
param.identifier.name.clone(),
arg.value.clone(),
(&param.identifier).into(),
)?;
if let Some(ty) = &param.type_ {
let value = arg
.value
.coerce(
&RuntimeType::from_parsed(ty.inner.clone(), exec_state, arg.source_range).unwrap(),
exec_state,
)
.map_err(|e| {
let mut message = format!(
"Argument requires a value with type `{}`, but found {}",
ty.inner,
arg.value.human_friendly_type(),
);
if let Some(ty) = e.explicit_coercion {
// TODO if we have access to the AST for the argument we could choose which example to suggest.
message = format!("{message}\n\nYou may need to add information about the type of the argument, for example:\n using a numeric suffix: `42{ty}`\n or using type ascription: `foo(): number({ty})`");
}
KclError::Semantic(KclErrorDetails {
message,
source_ranges: vec![arg.source_range],
})
})?;
exec_state
.mut_stack()
.add(param.identifier.name.clone(), value, (&param.identifier).into())?;
} else {
exec_state.mut_stack().add(
param.identifier.name.clone(),
arg.value.clone(),
(&param.identifier).into(),
)?;
}
} else {
// Argument was not provided.
if let Some(ref default_val) = param.default_value {

View File

@ -10,14 +10,13 @@ use serde::{Deserialize, Serialize};
use crate::{
errors::KclError,
execution::{types::NumericType, ArtifactId, ExecState, Metadata, TagEngineInfo, TagIdentifier, UnitLen},
execution::{
types::NumericType, ArtifactId, ExecState, ExecutorContext, Metadata, TagEngineInfo, TagIdentifier, UnitLen,
},
parsing::ast::types::{Node, NodeRef, TagDeclarator, TagNode},
std::{args::TyF64, sketch::PlaneData},
};
use super::ExecutorContext;
type Point2D = kcmc::shared::Point2d<f64>;
type Point3D = kcmc::shared::Point3d<f64>;
/// A geometry.
@ -265,7 +264,6 @@ pub struct Plane {
pub y_axis: Point3d,
/// The z-axis (normal).
pub z_axis: Point3d,
pub units: UnitLen,
#[serde(skip)]
pub meta: Vec<Metadata>,
}
@ -287,6 +285,8 @@ impl Plane {
x: 1.0,
y: 0.0,
z: 0.0,
// TODO axes must be normalized, so maybe these should all be count
// rather than mm?
units: UnitLen::Mm,
},
y_axis:
@ -483,7 +483,6 @@ impl Plane {
y_axis: Point3d::new(0.0, 1.0, 0.0, UnitLen::Mm),
z_axis: Point3d::new(0.0, 0.0, 1.0, UnitLen::Mm),
value: PlaneType::XY,
units: exec_state.length_unit(),
meta: vec![],
},
PlaneData::NegXY => Plane {
@ -494,7 +493,6 @@ impl Plane {
y_axis: Point3d::new(0.0, 1.0, 0.0, UnitLen::Mm),
z_axis: Point3d::new(0.0, 0.0, -1.0, UnitLen::Mm),
value: PlaneType::XY,
units: exec_state.length_unit(),
meta: vec![],
},
PlaneData::XZ => Plane {
@ -505,7 +503,6 @@ impl Plane {
y_axis: Point3d::new(0.0, 0.0, 1.0, UnitLen::Mm),
z_axis: Point3d::new(0.0, -1.0, 0.0, UnitLen::Mm),
value: PlaneType::XZ,
units: exec_state.length_unit(),
meta: vec![],
},
PlaneData::NegXZ => Plane {
@ -516,7 +513,6 @@ impl Plane {
y_axis: Point3d::new(0.0, 0.0, 1.0, UnitLen::Mm),
z_axis: Point3d::new(0.0, 1.0, 0.0, UnitLen::Mm),
value: PlaneType::XZ,
units: exec_state.length_unit(),
meta: vec![],
},
PlaneData::YZ => Plane {
@ -527,7 +523,6 @@ impl Plane {
y_axis: Point3d::new(0.0, 0.0, 1.0, UnitLen::Mm),
z_axis: Point3d::new(1.0, 0.0, 0.0, UnitLen::Mm),
value: PlaneType::YZ,
units: exec_state.length_unit(),
meta: vec![],
},
PlaneData::NegYZ => Plane {
@ -538,7 +533,6 @@ impl Plane {
y_axis: Point3d::new(0.0, 0.0, 1.0, UnitLen::Mm),
z_axis: Point3d::new(-1.0, 0.0, 0.0, UnitLen::Mm),
value: PlaneType::YZ,
units: exec_state.length_unit(),
meta: vec![],
},
PlaneData::Plane {
@ -556,7 +550,6 @@ impl Plane {
y_axis,
z_axis,
value: PlaneType::Custom,
units: exec_state.length_unit(),
meta: vec![],
}
}
@ -713,12 +706,6 @@ impl SketchSurface {
SketchSurface::Face(face) => face.z_axis,
}
}
pub(crate) fn units(&self) -> UnitLen {
match self {
SketchSurface::Plane(plane) => plane.units,
SketchSurface::Face(face) => face.units,
}
}
}
#[derive(Debug, Clone)]
@ -787,7 +774,8 @@ impl Sketch {
return Ok(Point2d::new(self.start.to[0], self.start.to[1], self.start.units));
};
Ok(path.get_to().into())
let to = path.get_base().to;
Ok(Point2d::new(to[0], to[1], path.get_base().units))
}
pub(crate) fn get_tangential_info_from_paths(&self) -> GetTangentialInfoFromPathsResult {
@ -829,6 +817,10 @@ impl Solid {
pub(crate) fn get_all_edge_cut_ids(&self) -> impl Iterator<Item = uuid::Uuid> + '_ {
self.edge_cuts.iter().map(|foc| foc.id())
}
pub(crate) fn height_in_mm(&self) -> f64 {
self.units.adjust_to(self.height, UnitLen::Mm).0
}
}
/// A fillet or a chamfer.
@ -889,28 +881,6 @@ pub struct Point2d {
pub units: UnitLen,
}
impl From<[TyF64; 2]> for Point2d {
fn from(p: [TyF64; 2]) -> Self {
Self {
x: p[0].n,
y: p[1].n,
units: p[0].ty.expect_length(),
}
}
}
impl From<Point2d> for [f64; 2] {
fn from(p: Point2d) -> Self {
[p.x, p.y]
}
}
impl From<Point2d> for Point2D {
fn from(p: Point2d) -> Self {
Self { x: p.x, y: p.y }
}
}
impl Point2d {
pub const ZERO: Self = Self {
x: 0.0,
@ -921,6 +891,18 @@ impl Point2d {
pub fn new(x: f64, y: f64, units: UnitLen) -> Self {
Self { x, y, units }
}
pub fn into_x(self) -> TyF64 {
TyF64::new(self.x, self.units.into())
}
pub fn into_y(self) -> TyF64 {
TyF64::new(self.y, self.units.into())
}
pub fn ignore_units(self) -> [f64; 2] {
[self.x, self.y]
}
}
#[derive(Debug, Deserialize, Serialize, PartialEq, Clone, Copy, ts_rs::TS, JsonSchema, Default)]
@ -968,9 +950,9 @@ impl From<Point3d> for Point3D {
impl From<Point3d> for kittycad_modeling_cmds::shared::Point3d<LengthUnit> {
fn from(p: Point3d) -> Self {
Self {
x: LengthUnit(p.x),
y: LengthUnit(p.y),
z: LengthUnit(p.z),
x: LengthUnit(p.units.adjust_to(p.x, UnitLen::Mm).0),
y: LengthUnit(p.units.adjust_to(p.y, UnitLen::Mm).0),
z: LengthUnit(p.units.adjust_to(p.z, UnitLen::Mm).0),
}
}
}
@ -1318,9 +1300,9 @@ impl Path {
ccw: *ccw,
},
Path::ArcThreePoint { p1, p2, p3, .. } => {
let circle_center = crate::std::utils::calculate_circle_from_3_points([*p1, *p2, *p3]);
let circle = crate::std::utils::calculate_circle_from_3_points([*p1, *p2, *p3]);
GetTangentialInfoFromPathsResult::Arc {
center: circle_center.center,
center: circle.center,
ccw: crate::std::utils::is_points_ccw(&[*p1, *p2, *p3]) > 0,
}
}
@ -1332,14 +1314,13 @@ impl Path {
radius: *radius,
},
Path::CircleThreePoint { p1, p2, p3, .. } => {
let circle_center = crate::std::utils::calculate_circle_from_3_points([*p1, *p2, *p3]);
let radius = linear_distance(&[circle_center.center[0], circle_center.center[1]], p1);
let center_point = [circle_center.center[0], circle_center.center[1]];
let circle = crate::std::utils::calculate_circle_from_3_points([*p1, *p2, *p3]);
let center_point = [circle.center[0], circle.center[1]];
GetTangentialInfoFromPathsResult::Circle {
center: center_point,
// Note: a circle is always ccw regardless of the order of points
ccw: true,
radius,
radius: circle.radius,
}
}
Path::ToPoint { .. } | Path::Horizontal { .. } | Path::AngledLineTo { .. } | Path::Base { .. } => {

View File

@ -18,16 +18,6 @@ use crate::{
CompilationError, SourceRange,
};
lazy_static::lazy_static! {
pub(crate) static ref CHECK_NUMERIC_TYPES: bool = {
let env_var = std::env::var("ZOO_NUM_TYS");
let Ok(env_var) = env_var else {
return false;
};
!env_var.is_empty()
};
}
#[derive(Debug, Clone, PartialEq)]
pub enum RuntimeType {
Primitive(PrimitiveType),
@ -62,6 +52,10 @@ impl RuntimeType {
RuntimeType::Primitive(PrimitiveType::Solid)
}
pub fn helix() -> Self {
RuntimeType::Primitive(PrimitiveType::Helix)
}
pub fn plane() -> Self {
RuntimeType::Primitive(PrimitiveType::Plane)
}
@ -94,6 +88,10 @@ impl RuntimeType {
))))
}
pub fn known_length(len: UnitLen) -> Self {
RuntimeType::Primitive(PrimitiveType::Number(NumericType::Known(UnitType::Length(len))))
}
pub fn angle() -> Self {
RuntimeType::Primitive(PrimitiveType::Number(NumericType::Known(UnitType::Angle(
UnitAngle::Unknown,
@ -370,6 +368,7 @@ impl fmt::Display for PrimitiveType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PrimitiveType::Number(NumericType::Known(unit)) => write!(f, "number({unit})"),
PrimitiveType::Number(NumericType::Unknown) => write!(f, "number(unknown units)"),
PrimitiveType::Number(_) => write!(f, "number"),
PrimitiveType::String => write!(f, "string"),
PrimitiveType::Boolean => write!(f, "bool"),
@ -427,13 +426,61 @@ impl NumericType {
NumericType::Known(UnitType::Angle(UnitAngle::Degrees))
}
/// Combine two types when we expect them to be equal.
pub fn expect_default_length(&self) -> Self {
match self {
NumericType::Default { len, .. } => NumericType::Known(UnitType::Length(*len)),
_ => unreachable!(),
}
}
pub fn expect_default_angle(&self) -> Self {
match self {
NumericType::Default { angle, .. } => NumericType::Known(UnitType::Angle(*angle)),
_ => unreachable!(),
}
}
/// Combine two types when we expect them to be equal, erring on the side of less coercion. To be
/// precise, only adjusting one number or the other when they are of known types.
///
/// This combinator function is suitable for comparisons or arithmetic where uncertainty should
/// be handled by the user.
pub fn combine_eq(a: TyF64, b: TyF64) -> (f64, f64, NumericType) {
use NumericType::*;
match (a.ty, b.ty) {
(at, bt) if at == bt => (a.n, b.n, at),
(at, Any) => (a.n, b.n, at),
(Any, bt) => (a.n, b.n, bt),
(t @ Known(UnitType::Length(l1)), Known(UnitType::Length(l2))) => (a.n, l2.adjust_to(b.n, l1).0, t),
(t @ Known(UnitType::Angle(a1)), Known(UnitType::Angle(a2))) => (a.n, a2.adjust_to(b.n, a1).0, t),
(Known(UnitType::Count), Default { .. }) | (Default { .. }, Known(UnitType::Count)) => {
(a.n, b.n, Known(UnitType::Count))
}
(t @ Known(UnitType::Length(l1)), Default { len: l2, .. }) if l1 == l2 => (a.n, b.n, t),
(Default { len: l1, .. }, t @ Known(UnitType::Length(l2))) if l1 == l2 => (a.n, b.n, t),
(t @ Known(UnitType::Angle(a1)), Default { angle: a2, .. }) if a1 == a2 => (a.n, b.n, t),
(Default { angle: a1, .. }, t @ Known(UnitType::Angle(a2))) if a1 == a2 => (a.n, b.n, t),
_ => (a.n, b.n, Unknown),
}
}
/// Combine two types when we expect them to be equal, erring on the side of more coercion. Including adjusting when
/// we are certain about only one type.
///
/// This combinator function is suitable for situations where the user would almost certainly want the types to be
/// coerced together, for example two arguments to the same function or two numbers in an array being used as a point.
///
/// Prefer to use `combine_eq` if possible since using that prioritises correctness over ergonomics.
pub fn combine_eq_coerce(a: TyF64, b: TyF64) -> (f64, f64, NumericType) {
use NumericType::*;
match (a.ty, b.ty) {
(at, bt) if at == bt => (a.n, b.n, at),
(at, Any) => (a.n, b.n, at),
(Any, bt) => (a.n, b.n, bt),
(Default { .. }, Default { .. }) | (_, Unknown) | (Unknown, _) => (a.n, b.n, Unknown),
// Known types and compatible, but needs adjustment.
@ -458,12 +505,9 @@ impl NumericType {
pub fn combine_eq_array(input: &[TyF64]) -> (Vec<f64>, NumericType) {
use NumericType::*;
let mut result = input.iter().map(|t| t.n).collect();
let result = input.iter().map(|t| t.n).collect();
let mut ty = Any;
// Invariant mismatch is true => ty is fully known
let mut mismatch = false;
for i in input {
if i.ty == Any || ty == i.ty {
continue;
@ -475,58 +519,24 @@ impl NumericType {
}
(_, Unknown) | (Default { .. }, Default { .. }) => return (result, Unknown),
// Known types and compatible, but needs adjustment.
(Known(UnitType::Length(_)), Known(UnitType::Length(_)))
| (Known(UnitType::Angle(_)), Known(UnitType::Angle(_))) => {
mismatch = true;
}
// Known but incompatible.
(Known(_), Known(_)) => return (result, Unknown),
// Known and unknown, no adjustment for counting numbers.
(Known(UnitType::Count), Default { .. }) | (Default { .. }, Known(UnitType::Count)) => {
ty = Known(UnitType::Count);
}
(Known(UnitType::Length(l1)), Default { len: l2, .. }) => {
mismatch |= l1 != l2;
}
(Known(UnitType::Angle(a1)), Default { angle: a2, .. }) => {
mismatch |= a1 != a2;
}
(Known(UnitType::Length(l1)), Default { len: l2, .. }) if l1 == l2 => {}
(Known(UnitType::Angle(a1)), Default { angle: a2, .. }) if a1 == a2 => {}
(Default { len: l1, .. }, Known(UnitType::Length(l2))) => {
mismatch |= l1 != l2;
(Default { len: l1, .. }, Known(UnitType::Length(l2))) if l1 == l2 => {
ty = Known(UnitType::Length(*l2));
}
(Default { angle: a1, .. }, Known(UnitType::Angle(a2))) => {
mismatch |= a1 != a2;
(Default { angle: a1, .. }, Known(UnitType::Angle(a2))) if a1 == a2 => {
ty = Known(UnitType::Angle(*a2));
}
(Unknown, _) | (_, Any) => unreachable!(),
_ => return (result, Unknown),
}
}
if !mismatch {
return (result, ty);
}
result = result
.into_iter()
.zip(input)
.map(|(n, i)| match (&ty, &i.ty) {
(Known(UnitType::Length(l1)), Known(UnitType::Length(l2)) | Default { len: l2, .. }) => {
l2.adjust_to(n, *l1).0
}
(Known(UnitType::Angle(a1)), Known(UnitType::Angle(a2)) | Default { angle: a2, .. }) => {
a2.adjust_to(n, *a1).0
}
_ => unreachable!(),
})
.collect();
(result, ty)
}
@ -534,11 +544,11 @@ impl NumericType {
pub fn combine_mul(a: TyF64, b: TyF64) -> (f64, f64, NumericType) {
use NumericType::*;
match (a.ty, b.ty) {
(at @ Default { .. }, bt @ Default { .. }) if at != bt => (a.n, b.n, Unknown),
(at @ Default { .. }, bt @ Default { .. }) if at == bt => (a.n, b.n, at),
(Default { .. }, Default { .. }) => (a.n, b.n, Unknown),
(Known(UnitType::Count), bt) => (a.n, b.n, bt),
(at, Known(UnitType::Count)) => (a.n, b.n, at),
(Default { .. }, bt) => (a.n, b.n, bt),
(at, Default { .. }) => (a.n, b.n, at),
(at @ Known(_), Default { .. }) | (Default { .. }, at @ Known(_)) => (a.n, b.n, at),
(Any, Any) => (a.n, b.n, Any),
_ => (a.n, b.n, Unknown),
}
@ -552,18 +562,7 @@ impl NumericType {
(at, bt) if at == bt => (a.n, b.n, Known(UnitType::Count)),
(Default { .. }, Default { .. }) => (a.n, b.n, Unknown),
(at, Known(UnitType::Count) | Any) => (a.n, b.n, at),
(Known(UnitType::Length(l1)), Known(UnitType::Length(l2))) => {
(a.n, l2.adjust_to(b.n, l1).0, Known(UnitType::Count))
}
(Known(UnitType::Angle(a1)), Known(UnitType::Angle(a2))) => {
(a.n, a2.adjust_to(b.n, a1).0, Known(UnitType::Count))
}
(Default { len: l1, .. }, Known(UnitType::Length(l2))) => {
(l1.adjust_to(a.n, l2).0, b.n, Known(UnitType::Count))
}
(Default { angle: a1, .. }, Known(UnitType::Angle(a2))) => {
(a1.adjust_to(a.n, a2).0, b.n, Known(UnitType::Count))
}
(at @ Known(_), Default { .. }) => (a.n, b.n, at),
(Known(UnitType::Count), _) => (a.n, b.n, Known(UnitType::Count)),
_ => (a.n, b.n, Unknown),
}
@ -608,9 +607,18 @@ impl NumericType {
}
}
fn is_unknown(&self) -> bool {
matches!(
self,
NumericType::Unknown
| NumericType::Known(UnitType::Angle(UnitAngle::Unknown))
| NumericType::Known(UnitType::Length(UnitLen::Unknown))
)
}
fn example_ty(&self) -> Option<String> {
match self {
Self::Known(t) => Some(t.to_string()),
Self::Known(t) if !self.is_unknown() => Some(t.to_string()),
Self::Default { len, .. } => Some(len.to_string()),
_ => None,
}
@ -621,10 +629,6 @@ impl NumericType {
return Err(val.into());
};
if !*CHECK_NUMERIC_TYPES {
return Ok(val.clone());
}
if ty.subtype(self) {
return Ok(KclValue::Number {
value: *value,
@ -775,10 +779,10 @@ pub enum UnitLen {
}
impl UnitLen {
fn adjust_to(self, value: f64, to: UnitLen) -> (f64, UnitLen) {
pub fn adjust_to(self, value: f64, to: UnitLen) -> (f64, UnitLen) {
use UnitLen::*;
if !*CHECK_NUMERIC_TYPES || self == to {
if self == to {
return (value, to);
}
@ -898,15 +902,11 @@ pub enum UnitAngle {
}
impl UnitAngle {
fn adjust_to(self, value: f64, to: UnitAngle) -> (f64, UnitAngle) {
pub fn adjust_to(self, value: f64, to: UnitAngle) -> (f64, UnitAngle) {
use std::f64::consts::PI;
use UnitAngle::*;
if !*CHECK_NUMERIC_TYPES {
return (value, to);
}
if to == Unknown {
return (value, self);
}
@ -1057,8 +1057,6 @@ impl KclValue {
y_axis,
z_axis,
value: super::PlaneType::Uninit,
// TODO use length unit from origin
units: exec_state.length_unit(),
meta: meta.clone(),
};
@ -1952,10 +1950,6 @@ mod test {
assert_coerce_results(&unknown, &NumericType::Any.into(), &unknown, &mut exec_state);
assert_coerce_results(&default, &NumericType::Any.into(), &default, &mut exec_state);
if !*CHECK_NUMERIC_TYPES {
return;
}
assert_eq!(
default
.coerce(
@ -2068,20 +2062,14 @@ u = min(3rad, 4in)
"#;
let result = parse_execute(program).await.unwrap();
if *CHECK_NUMERIC_TYPES {
assert_eq!(result.exec_state.errors().len(), 3);
} else {
assert!(result.exec_state.errors().is_empty());
}
assert_eq!(result.exec_state.errors().len(), 5);
assert_value_and_type("a", &result, 9.0, NumericType::default());
assert_value_and_type("b", &result, 3.0, NumericType::default());
assert_value_and_type("c", &result, 13.0, NumericType::mm());
assert_value_and_type("d", &result, 13.0, NumericType::mm());
assert_value_and_type("e", &result, 13.0, NumericType::mm());
if *CHECK_NUMERIC_TYPES {
assert_value_and_type("f", &result, 5.0, NumericType::mm());
}
assert_value_and_type("f", &result, 5.0, NumericType::mm());
assert_value_and_type("g", &result, 20.0, NumericType::default());
assert_value_and_type("h", &result, 20.0, NumericType::mm());
@ -2091,16 +2079,14 @@ u = min(3rad, 4in)
assert_value_and_type("l", &result, 0.0, NumericType::default());
assert_value_and_type("m", &result, 2.0, NumericType::count());
if *CHECK_NUMERIC_TYPES {
assert_value_and_type("n", &result, 127.0, NumericType::count());
}
assert_value_and_type("o", &result, 1.0, NumericType::Unknown);
assert_value_and_type("n", &result, 5.0, NumericType::Unknown);
assert_value_and_type("o", &result, 1.0, NumericType::mm());
assert_value_and_type("p", &result, 1.0, NumericType::count());
assert_value_and_type("q", &result, 2.0, NumericType::Known(UnitType::Length(UnitLen::Inches)));
assert_value_and_type("r", &result, 0.0, NumericType::default());
assert_value_and_type("s", &result, -42.0, NumericType::mm());
assert_value_and_type("t", &result, 3.0, NumericType::Known(UnitType::Length(UnitLen::Inches)));
assert_value_and_type("t", &result, 3.0, NumericType::Unknown);
assert_value_and_type("u", &result, 3.0, NumericType::Unknown);
}
@ -2114,7 +2100,24 @@ b = 180 / PI * a + 360
let result = parse_execute(program).await.unwrap();
assert_value_and_type("a", &result, 1.0, NumericType::radians());
// TODO type is not ideal
assert_value_and_type("b", &result, 417.0, NumericType::radians());
assert_value_and_type("b", &result, 417.0, NumericType::Unknown);
}
#[tokio::test(flavor = "multi_thread")]
async fn cos_coercions() {
let program = r#"
a = cos(toRadians(30))
b = 3 / a
c = cos(30deg)
d = cos(30)
"#;
let result = parse_execute(program).await.unwrap();
assert_eq!(result.exec_state.errors().len(), 1);
assert_value_and_type("a", &result, 1.0, NumericType::count());
assert_value_and_type("b", &result, 3.0, NumericType::default());
assert_value_and_type("c", &result, 1.0, NumericType::count());
assert_value_and_type("d", &result, 0.0, NumericType::count());
}
}