Skip to content

Commit 8f08855

Browse files
committed
Better assertion error messages
1 parent b0958c0 commit 8f08855

12 files changed

Lines changed: 161 additions & 70 deletions

book/src/examples/example-numbat_syntax.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ print("value of π ≈ {π:.3}") # Format specifiers
124124
125125
assert(1 yard < 1 meter) # Assertion
126126
127-
assert_eq(1 ft, 12 in) # Assert that two quantities are equal
127+
assert_eq(12 in, 1 ft) # Assert that two quantities are equal
128128
assert_eq(1 yd, 1 m, 10 cm) # Assert that two quantities are equal, up to
129129
# the given precision
130130
type(2 m/s) # Print the type of an expression

examples/numbat_syntax.nbt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ print("value of π ≈ {π:.3}") # Format specifiers
115115

116116
assert(1 yard < 1 meter) # Assertion
117117

118-
assert_eq(1 ft, 12 in) # Assert that two quantities are equal
118+
assert_eq(12 in, 1 ft) # Assert that two quantities are equal
119119
assert_eq(1 yd, 1 m, 10 cm) # Assert that two quantities are equal, up to
120120
# the given precision
121121
type(2 m/s) # Print the type of an expression
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
assert_eq(2 + 2, 2 + 1)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
assert_eq(2 + 2e-12, 2 + 1e-12)

numbat/src/diagnostic.rs

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -552,21 +552,25 @@ impl ErrorDiagnostic for ResolverDiagnostic<'_, RuntimeError> {
552552
.with_message("assertion failed"),
553553
]),
554554
),
555-
RuntimeErrorKind::AssertEq2Failed(assert_eq2_error) => diag.push(
556-
Diagnostic::error()
557-
.with_message("Assertion failed")
558-
.with_labels(vec![
559-
assert_eq2_error
560-
.span_lhs
561-
.diagnostic_label(LabelStyle::Secondary)
562-
.with_message(format!("{}", assert_eq2_error.lhs)),
563-
assert_eq2_error
564-
.span_rhs
565-
.diagnostic_label(LabelStyle::Primary)
566-
.with_message(format!("{}", assert_eq2_error.rhs)),
567-
])
568-
.with_notes(vec![inner]),
569-
),
555+
RuntimeErrorKind::AssertEq2Failed(assert_eq2_error) => {
556+
let (lhs, rhs) = assert_eq2_error.fmt_values();
557+
558+
diag.push(
559+
Diagnostic::error()
560+
.with_message("Assertion failed")
561+
.with_labels(vec![
562+
assert_eq2_error
563+
.span_lhs
564+
.diagnostic_label(LabelStyle::Secondary)
565+
.with_message(lhs),
566+
assert_eq2_error
567+
.span_rhs
568+
.diagnostic_label(LabelStyle::Primary)
569+
.with_message(rhs),
570+
])
571+
.with_notes(vec![inner]),
572+
)
573+
}
570574
RuntimeErrorKind::AssertEq3Failed(assert_eq3_error) => {
571575
let (lhs, rhs) = assert_eq3_error.fmt_comparands();
572576

numbat/src/ffi/procedures.rs

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -85,30 +85,38 @@ fn assert_eq(_: &mut ExecutionContext, mut args: Args) -> ControlFlow {
8585
let lhs = lhs_arg.value;
8686
let rhs = rhs_arg.value;
8787

88-
let error = ControlFlow::Break(RuntimeErrorKind::AssertEq2Failed(AssertEq2Error {
89-
span_lhs: lhs_arg.span,
90-
lhs: lhs.clone(),
91-
span_rhs: rhs_arg.span,
92-
rhs: rhs.clone(),
93-
}));
94-
95-
if lhs.is_quantity() {
96-
let lhs = lhs.unsafe_as_quantity();
97-
let rhs = rhs.unsafe_as_quantity();
98-
99-
if let Ok(args1_converted) = rhs.convert_to(lhs.unit()) {
100-
if lhs == args1_converted {
101-
ControlFlow::Continue(())
102-
} else {
103-
error
88+
let mut lhs_converted = None;
89+
let mut diff = None;
90+
91+
let values_equal = if lhs.is_quantity() {
92+
let lhs_q = lhs.clone().unsafe_as_quantity();
93+
let rhs_q = rhs.clone().unsafe_as_quantity();
94+
95+
if let Ok(converted) = lhs_q.convert_to(rhs_q.unit()) {
96+
let equal = converted == rhs_q;
97+
if !equal {
98+
diff = (&converted - &rhs_q).ok().map(|d| d.abs());
10499
}
100+
lhs_converted = Some(converted);
101+
equal
105102
} else {
106-
error
103+
false
107104
}
108-
} else if lhs == rhs {
105+
} else {
106+
lhs == rhs
107+
};
108+
109+
if values_equal {
109110
ControlFlow::Continue(())
110111
} else {
111-
error
112+
ControlFlow::Break(RuntimeErrorKind::AssertEq2Failed(AssertEq2Error {
113+
span_lhs: lhs_arg.span,
114+
lhs,
115+
span_rhs: rhs_arg.span,
116+
rhs,
117+
lhs_converted,
118+
diff,
119+
}))
112120
}
113121
} else {
114122
let lhs_original = lhs_arg.value.unsafe_as_quantity();

numbat/src/interpreter/assert_eq.rs

Lines changed: 59 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,73 @@ pub struct AssertEq2Error {
99
pub lhs: Value,
1010
pub span_rhs: Span,
1111
pub rhs: Value,
12+
/// When both values are quantities, stores lhs converted to rhs's unit.
13+
pub lhs_converted: Option<Quantity>,
14+
/// When both values are quantities, stores the absolute difference (in rhs's unit).
15+
pub diff: Option<Quantity>,
16+
}
17+
18+
impl AssertEq2Error {
19+
/// Returns formatted display strings for lhs and rhs values.
20+
/// When both values are quantities with different units, lhs is shown
21+
/// converted to rhs's unit with the original in parentheses.
22+
pub fn fmt_values(&self) -> (String, String) {
23+
let rhs_str = format!("{}", self.rhs);
24+
let lhs_str = if let Some(ref lhs_converted) = self.lhs_converted {
25+
let lhs_converted_str = format!("{lhs_converted}");
26+
if let Value::Quantity(ref lhs_q) = self.lhs
27+
&& lhs_converted.unit() != lhs_q.unit()
28+
{
29+
return (format!("{lhs_converted_str} ({})", self.lhs), rhs_str);
30+
}
31+
lhs_converted_str
32+
} else {
33+
format!("{}", self.lhs)
34+
};
35+
(lhs_str, rhs_str)
36+
}
37+
38+
fn is_floating_point_inaccuracy(&self) -> bool {
39+
if let Some(ref diff) = self.diff {
40+
if let Some(ref lhs_converted) = self.lhs_converted {
41+
let diff_val = diff.unsafe_value().to_f64().abs();
42+
let lhs_val = lhs_converted.unsafe_value().to_f64().abs();
43+
44+
if let Value::Quantity(ref rhs_q) = self.rhs {
45+
let rhs_val = rhs_q.unsafe_value().to_f64().abs();
46+
let max_val = lhs_val.max(rhs_val);
47+
return max_val > 0.0 && diff_val / max_val < 1e-9;
48+
}
49+
}
50+
}
51+
false
52+
}
1253
}
1354

1455
impl Display for AssertEq2Error {
1556
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
16-
let optional_message = if format!("{}", self.lhs) == format!("{}", self.rhs) {
17-
"\nNote: The two printed values appear to be the same, this may be due to floating point precision errors.\n \
18-
For dimension types you may want to test approximate equality instead: assert_eq(q1, q2, ε)."
57+
let (lhs_str, rhs_str) = self.fmt_values();
58+
59+
let fp_note = if self.is_floating_point_inaccuracy() {
60+
"\nNote: this is likely due to floating point inaccuracy. \
61+
Consider testing approximate equality instead: assert_eq(q1, q2, ε)."
1962
} else {
2063
""
2164
};
2265

23-
write!(
24-
f,
25-
"Assertion failed because the following two values are not the same:\n {}\n {}{}",
26-
self.lhs, self.rhs, optional_message
27-
)
66+
if let Some(ref diff) = self.diff {
67+
write!(
68+
f,
69+
"Assertion failed because the following two quantities differ by {diff}:\
70+
\n {lhs_str}\n {rhs_str}{fp_note}",
71+
)
72+
} else {
73+
write!(
74+
f,
75+
"Assertion failed because the following two values are not the same:\
76+
\n {lhs_str}\n {rhs_str}",
77+
)
78+
}
2879
}
2980
}
3081

numbat/tests/interpreter.rs

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1337,21 +1337,5 @@ mod tests {
13371337
-77.0089°
13381338
"###);
13391339
}
1340-
1341-
#[test]
1342-
fn test_floating_point_warning() {
1343-
insta::assert_snapshot!(fail("assert_eq(2+ 2, 2 + 1)"), @r###"
1344-
Assertion failed because the following two values are not the same:
1345-
4
1346-
3
1347-
"###);
1348-
insta::assert_snapshot!(fail("assert_eq(2 + 2e-12, 2 + 1e-12)"), @r###"
1349-
Assertion failed because the following two values are not the same:
1350-
2.0
1351-
2.0
1352-
Note: The two printed values appear to be the same, this may be due to floating point precision errors.
1353-
For dimension types you may want to test approximate equality instead: assert_eq(q1, q2, ε).
1354-
"###);
1355-
}
13561340
}
13571341
}

numbat/tests/snapshots/prelude_and_examples__runtime_error_snapshots@assert_eq_1.nbt.snap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ error: Assertion failed
1111
│ │
1212
2 m
1313
14-
= Assertion failed because the following two values are not the same:
14+
= Assertion failed because the following two quantities differ by 0.1 m:
1515
2 m
1616
2.1 m
1717

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
source: numbat/tests/prelude_and_examples.rs
3+
expression: output
4+
input_file: examples/runtime_error/assert_eq_4.nbt
5+
---
6+
error: Assertion failed
7+
┌─ <internal:3>:1:18
8+
9+
1assert_eq(2 + 2, 2 + 1)
10+
----- ^^^^^ 3
11+
│ │
12+
4
13+
14+
= Assertion failed because the following two quantities differ by 1:
15+
4
16+
3
17+
18+
help: Backtrace:
19+
= 0: assert_eq(2 + 2, 2 + 1)
20+
at <main> - <internal:3>:1:0

0 commit comments

Comments
 (0)