More numeric type propagations (#6221)

Last few numeric type propagations

Signed-off-by: Nick Cameron <nrc@ncameron.org>
This commit is contained in:
Nick Cameron
2025-04-09 11:46:54 +12:00
committed by GitHub
parent 83f74faaf7
commit 997f539a8c
10 changed files with 132 additions and 37 deletions

View File

@ -78,7 +78,7 @@ assertEqual(sum, 6, 0.00001, "1 + 2 + 3 summed is 6")
```js ```js
// Declare a function that sketches a decagon. // Declare a function that sketches a decagon.
fn decagon(radius) { fn decagon(radius) {
// Each side of the decagon is turned this many degrees from the previous angle. // Each side of the decagon is turned this many radians from the previous angle.
stepAngle = 1 / 10 * TAU stepAngle = 1 / 10 * TAU
// Start the decagon sketch at this point. // Start the decagon sketch at this point.

File diff suppressed because one or more lines are too long

View File

@ -235702,7 +235702,7 @@
"examples": [ "examples": [
"// This function adds two numbers.\nfn add(a, b) {\n return a + b\n}\n\n// This function adds an array of numbers.\n// It uses the `reduce` function, to call the `add` function on every\n// element of the `arr` parameter. The starting value is 0.\nfn sum(arr) {\n return reduce(arr, 0, add)\n}\n\n/* The above is basically like this pseudo-code:\nfn sum(arr):\n sumSoFar = 0\n for i in arr:\n sumSoFar = add(sumSoFar, i)\n return sumSoFar */\n\n// We use `assertEqual` to check that our `sum` function gives the\n// expected result. It's good to check your work!\nassertEqual(sum([1, 2, 3]), 6, 0.00001, \"1 + 2 + 3 summed is 6\")", "// This function adds two numbers.\nfn add(a, b) {\n return a + b\n}\n\n// This function adds an array of numbers.\n// It uses the `reduce` function, to call the `add` function on every\n// element of the `arr` parameter. The starting value is 0.\nfn sum(arr) {\n return reduce(arr, 0, add)\n}\n\n/* The above is basically like this pseudo-code:\nfn sum(arr):\n sumSoFar = 0\n for i in arr:\n sumSoFar = add(sumSoFar, i)\n return sumSoFar */\n\n// We use `assertEqual` to check that our `sum` function gives the\n// expected result. It's good to check your work!\nassertEqual(sum([1, 2, 3]), 6, 0.00001, \"1 + 2 + 3 summed is 6\")",
"// This example works just like the previous example above, but it uses\n// an anonymous `add` function as its parameter, instead of declaring a\n// named function outside.\narr = [1, 2, 3]\nsum = reduce(arr, 0, fn(i, result_so_far) {\n return i + result_so_far\n})\n\n// We use `assertEqual` to check that our `sum` function gives the\n// expected result. It's good to check your work!\nassertEqual(sum, 6, 0.00001, \"1 + 2 + 3 summed is 6\")", "// This example works just like the previous example above, but it uses\n// an anonymous `add` function as its parameter, instead of declaring a\n// named function outside.\narr = [1, 2, 3]\nsum = reduce(arr, 0, fn(i, result_so_far) {\n return i + result_so_far\n})\n\n// We use `assertEqual` to check that our `sum` function gives the\n// expected result. It's good to check your work!\nassertEqual(sum, 6, 0.00001, \"1 + 2 + 3 summed is 6\")",
"// Declare a function that sketches a decagon.\nfn decagon(radius) {\n // Each side of the decagon is turned this many degrees from the previous angle.\n stepAngle = 1 / 10 * TAU\n\n // Start the decagon sketch at this point.\n startOfDecagonSketch = startSketchOn(XY)\n |> startProfileAt([cos(0) * radius, sin(0) * radius], %)\n\n // Use a `reduce` to draw the remaining decagon sides.\n // For each number in the array 1..10, run the given function,\n // which takes a partially-sketched decagon and adds one more edge to it.\n fullDecagon = reduce([1..10], startOfDecagonSketch, fn(i, partialDecagon) {\n // Draw one edge of the decagon.\n x = cos(stepAngle * i) * radius\n y = sin(stepAngle * i) * radius\n return line(partialDecagon, end = [x, y])\n })\n\n return fullDecagon\n}\n\n/* The `decagon` above is basically like this pseudo-code:\nfn decagon(radius):\n stepAngle = (1/10) * TAU\n plane = startSketchOn('XY')\n startOfDecagonSketch = startProfileAt([(cos(0)*radius), (sin(0) * radius)], plane)\n\n // Here's the reduce part.\n partialDecagon = startOfDecagonSketch\n for i in [1..10]:\n x = cos(stepAngle * i) * radius\n y = sin(stepAngle * i) * radius\n partialDecagon = line(partialDecagon, end = [x, y])\n fullDecagon = partialDecagon // it's now full\n return fullDecagon */\n\n// Use the `decagon` function declared above, to sketch a decagon with radius 5.\ndecagon(5.0)\n |> close()" "// Declare a function that sketches a decagon.\nfn decagon(radius) {\n // Each side of the decagon is turned this many radians from the previous angle.\n stepAngle = 1 / 10 * TAU\n\n // Start the decagon sketch at this point.\n startOfDecagonSketch = startSketchOn(XY)\n |> startProfileAt([cos(0) * radius, sin(0) * radius], %)\n\n // Use a `reduce` to draw the remaining decagon sides.\n // For each number in the array 1..10, run the given function,\n // which takes a partially-sketched decagon and adds one more edge to it.\n fullDecagon = reduce([1..10], startOfDecagonSketch, fn(i, partialDecagon) {\n // Draw one edge of the decagon.\n x = cos(stepAngle * i) * radius\n y = sin(stepAngle * i) * radius\n return line(partialDecagon, end = [x, y])\n })\n\n return fullDecagon\n}\n\n/* The `decagon` above is basically like this pseudo-code:\nfn decagon(radius):\n stepAngle = (1/10) * TAU\n plane = startSketchOn('XY')\n startOfDecagonSketch = startProfileAt([(cos(0)*radius), (sin(0) * radius)], plane)\n\n // Here's the reduce part.\n partialDecagon = startOfDecagonSketch\n for i in [1..10]:\n x = cos(stepAngle * i) * radius\n y = sin(stepAngle * i) * radius\n partialDecagon = line(partialDecagon, end = [x, y])\n fullDecagon = partialDecagon // it's now full\n return fullDecagon */\n\n// Use the `decagon` function declared above, to sketch a decagon with radius 5.\ndecagon(5.0)\n |> close()"
] ]
}, },
{ {

View File

@ -360,15 +360,6 @@ impl KclValue {
result result
} }
/// Put the number into a KCL value.
pub const fn from_number(f: f64, meta: Vec<Metadata>) -> Self {
Self::Number {
value: f,
meta,
ty: NumericType::Unknown,
}
}
pub const fn from_number_with_type(f: f64, ty: NumericType, meta: Vec<Metadata>) -> Self { pub const fn from_number_with_type(f: f64, ty: NumericType, meta: Vec<Metadata>) -> Self {
Self::Number { value: f, meta, ty } Self::Number { value: f, meta, ty }
} }

View File

@ -19,7 +19,7 @@ use crate::{
}; };
lazy_static::lazy_static! { lazy_static::lazy_static! {
pub(super) static ref CHECK_NUMERIC_TYPES: bool = { pub(crate) static ref CHECK_NUMERIC_TYPES: bool = {
let env_var = std::env::var("ZOO_NUM_TYS"); let env_var = std::env::var("ZOO_NUM_TYS");
let Ok(env_var) = env_var else { let Ok(env_var) = env_var else {
return false; return false;
@ -416,6 +416,80 @@ 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 mut ty = Any;
// Invariant mismatch is true => ty is Known
let mut mismatch = false;
for i in input {
if i.ty == Any || ty == i.ty {
continue;
}
match (&ty, &i.ty) {
(Any, t) => {
ty = t.clone();
}
(_, 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;
}
(Default { len: l1, .. }, Known(UnitType::Length(l2))) => {
mismatch |= l1 != l2;
ty = Known(UnitType::Length(*l2));
}
(Default { angle: a1, .. }, Known(UnitType::Angle(a2))) => {
mismatch |= a1 != a2;
ty = Known(UnitType::Angle(*a2));
}
(Unknown, _) | (_, Any) => unreachable!(),
}
}
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)
}
(Known(UnitType::Angle(a1)), Known(UnitType::Angle(a2)) | Default { angle: a2, .. }) => {
a2.adjust_to(n, *a1)
}
_ => unreachable!(),
})
.collect();
(result, ty)
}
/// Combine two types for multiplication-like operations. /// Combine two types for multiplication-like operations.
pub fn combine_mul(a: TyF64, b: TyF64) -> (f64, f64, NumericType) { pub fn combine_mul(a: TyF64, b: TyF64) -> (f64, f64, NumericType) {
use NumericType::*; use NumericType::*;
@ -1847,11 +1921,16 @@ n = 10inch / 2mm
o = 3mm / 3 o = 3mm / 3
p = 3_ / 4 p = 3_ / 4
q = 4inch / 2_ q = 4inch / 2_
r = min(0, 3, 42)
s = min(0, 3mm, -42)
t = min(100, 3in, 142mm)
u = min(3rad, 4in)
"#; "#;
let result = parse_execute(program).await.unwrap(); let result = parse_execute(program).await.unwrap();
if *CHECK_NUMERIC_TYPES { if *CHECK_NUMERIC_TYPES {
assert_eq!(result.exec_state.errors().len(), 2); assert_eq!(result.exec_state.errors().len(), 3);
} else { } else {
assert!(result.exec_state.errors().is_empty()); assert!(result.exec_state.errors().is_empty());
} }
@ -1875,5 +1954,10 @@ q = 4inch / 2_
assert_value_and_type("o", &result, 1.0, NumericType::mm()); assert_value_and_type("o", &result, 1.0, NumericType::mm());
assert_value_and_type("p", &result, 1.0, NumericType::count()); 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("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("u", &result, 3.0, NumericType::Unknown);
} }
} }

View File

@ -523,15 +523,6 @@ impl Args {
}) })
} }
pub(super) fn make_user_val_from_f64(&self, f: f64) -> KclValue {
KclValue::from_number(
f,
vec![Metadata {
source_range: self.source_range,
}],
)
}
pub(super) fn make_user_val_from_f64_with_type(&self, f: TyF64) -> KclValue { pub(super) fn make_user_val_from_f64_with_type(&self, f: TyF64) -> KclValue {
KclValue::from_number_with_type( KclValue::from_number_with_type(
f.n, f.n,

View File

@ -133,7 +133,7 @@ pub async fn reduce(exec_state: &mut ExecState, args: Args) -> Result<KclValue,
/// ```no_run /// ```no_run
/// // Declare a function that sketches a decagon. /// // Declare a function that sketches a decagon.
/// fn decagon(radius) { /// fn decagon(radius) {
/// // Each side of the decagon is turned this many degrees from the previous angle. /// // Each side of the decagon is turned this many radians from the previous angle.
/// stepAngle = (1/10) * TAU /// stepAngle = (1/10) * TAU
/// ///
/// // Start the decagon sketch at this point. /// // Start the decagon sketch at this point.

View File

@ -6,18 +6,31 @@ use kcl_derive_docs::stdlib;
use super::args::FromArgs; use super::args::FromArgs;
use crate::{ use crate::{
errors::{KclError, KclErrorDetails}, errors::{KclError, KclErrorDetails},
execution::{types::NumericType, ExecState, KclValue}, execution::{
types::{self, NumericType},
ExecState, KclValue,
},
std::args::{Args, TyF64}, std::args::{Args, TyF64},
CompilationError,
}; };
/// Compute the remainder after dividing `num` by `div`. /// Compute the remainder after dividing `num` by `div`.
/// If `num` is negative, the result will be too. /// If `num` is negative, the result will be too.
pub async fn rem(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn rem(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let n = args.get_unlabeled_kw_arg("number to divide")?; let n: TyF64 = args.get_unlabeled_kw_arg("number to divide")?;
let d = args.get_kw_arg("divisor")?; let d: TyF64 = args.get_kw_arg("divisor")?;
let (n, d, ty) = NumericType::combine_div(n, d);
if *types::CHECK_NUMERIC_TYPES && ty == NumericType::Unknown {
// TODO suggest how to fix this
exec_state.warn(CompilationError::err(
args.source_range,
"Remainder of numbers which have unknown or incompatible units.",
));
}
let remainder = inner_rem(n, d); let remainder = inner_rem(n, d);
Ok(args.make_user_val_from_f64(remainder)) Ok(args.make_user_val_from_f64_with_type(TyF64::new(remainder, ty)))
} }
/// Compute the remainder after dividing `num` by `div`. /// Compute the remainder after dividing `num` by `div`.
@ -243,11 +256,19 @@ fn inner_ceil(num: f64) -> Result<f64, KclError> {
} }
/// Compute the minimum of the given arguments. /// Compute the minimum of the given arguments.
pub async fn min(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn min(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let nums = args.get_number_array()?; let nums = args.get_number_array_with_types()?;
let (nums, ty) = NumericType::combine_eq_array(&nums);
if *types::CHECK_NUMERIC_TYPES && ty == NumericType::Unknown {
// TODO suggest how to fix this
exec_state.warn(CompilationError::err(
args.source_range,
"Calling `min` on numbers which have unknown or incompatible units.",
));
}
let result = inner_min(nums); let result = inner_min(nums);
Ok(args.make_user_val_from_f64(result)) Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, ty)))
} }
/// Compute the minimum of the given arguments. /// Compute the minimum of the given arguments.
@ -280,11 +301,19 @@ fn inner_min(args: Vec<f64>) -> f64 {
} }
/// Compute the maximum of the given arguments. /// Compute the maximum of the given arguments.
pub async fn max(_exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> { pub async fn max(exec_state: &mut ExecState, args: Args) -> Result<KclValue, KclError> {
let nums = args.get_number_array()?; let nums = args.get_number_array_with_types()?;
let (nums, ty) = NumericType::combine_eq_array(&nums);
if *types::CHECK_NUMERIC_TYPES && ty == NumericType::Unknown {
// TODO suggest how to fix this
exec_state.warn(CompilationError::err(
args.source_range,
"Calling `max` on numbers which have unknown or incompatible units.",
));
}
let result = inner_max(nums); let result = inner_max(nums);
Ok(args.make_user_val_from_f64(result)) Ok(args.make_user_val_from_f64_with_type(TyF64::new(result, ty)))
} }
/// Compute the maximum of the given arguments. /// Compute the maximum of the given arguments.

View File

@ -69,7 +69,7 @@ export fn cos(@num: number(rad)): number(_) {}
/// |> startProfileAt([0, 0], %) /// |> startProfileAt([0, 0], %)
/// |> angledLine({ /// |> angledLine({
/// angle = 50, /// angle = 50,
/// length = 15 / sin(toDegrees(135)), /// length = 15 / sin(toRadians(135)),
/// }, %) /// }, %)
/// |> yLine(endAbsolute = 0) /// |> yLine(endAbsolute = 0)
/// |> close() /// |> close()

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 58 KiB