1use crate::compiler::prelude::*;
2
3fn assert_eq(left: &Value, right: &Value, message: Option<Value>) -> Resolved {
4 if left == right {
5 Ok(true.into())
6 } else if let Some(message) = message {
7 let message = message.try_bytes_utf8_lossy()?.into_owned();
8 Err(ExpressionError::Error {
9 message: message.clone(),
10 labels: vec![],
11 notes: vec![Note::UserErrorMessage(message)],
12 })
13 } else {
14 Err(ExpressionError::from(format!(
15 "assertion failed: {left} == {right}"
16 )))
17 }
18}
19
20#[derive(Clone, Copy, Debug)]
21pub struct AssertEq;
22
23impl Function for AssertEq {
24 fn identifier(&self) -> &'static str {
25 "assert_eq"
26 }
27
28 fn usage(&self) -> &'static str {
29 "Asserts that two expressions, `left` and `right`, have the same value. The program is aborted with `message` if they do not have the same value."
30 }
31
32 fn category(&self) -> &'static str {
33 Category::Debug.as_ref()
34 }
35
36 fn return_kind(&self) -> u16 {
37 kind::BOOLEAN
38 }
39
40 fn notices(&self) -> &'static [&'static str] {
41 &[indoc! {"
42 The `assert_eq` function should be used in a standalone fashion and only when you want
43 to abort the program. You should avoid it in logical expressions and other situations in
44 which you want the program to continue if the condition evaluates to `false`.
45 "}]
46 }
47
48 fn pure(&self) -> bool {
49 false
50 }
51
52 fn parameters(&self) -> &'static [Parameter] {
53 const PARAMETERS: &[Parameter] = &[
54 Parameter::required(
55 "left",
56 kind::ANY,
57 "The value to check for equality against `right`.",
58 ),
59 Parameter::required(
60 "right",
61 kind::ANY,
62 "The value to check for equality against `left`.",
63 ),
64 Parameter::optional(
65 "message",
66 kind::BYTES,
67 "An optional custom error message. If the equality assertion fails, `message` is
68appended to the default message prefix. See the [examples](#assert_eq-examples)
69below for a fully formed log message sample.",
70 ),
71 ];
72 PARAMETERS
73 }
74
75 fn examples(&self) -> &'static [Example] {
76 &[
77 example! {
78 title: "Successful assertion",
79 source: "assert_eq!(1, 1)",
80 result: Ok("true"),
81 },
82 example! {
83 title: "Unsuccessful assertion",
84 source: "assert_eq!(127, [1, 2, 3])",
85 result: Err(
86 r#"function call error for "assert_eq" at (0:26): assertion failed: 127 == [1, 2, 3]"#,
87 ),
88 },
89 example! {
90 title: "Unsuccessful assertion with custom log message",
91 source: r#"assert_eq!(1, 0, message: "Unequal integers")"#,
92 result: Err(r#"function call error for "assert_eq" at (0:45): Unequal integers"#),
93 },
94 ]
95 }
96
97 fn compile(
98 &self,
99 _state: &state::TypeState,
100 _ctx: &mut FunctionCompileContext,
101 arguments: ArgumentList,
102 ) -> Compiled {
103 let left = arguments.required("left");
104 let right = arguments.required("right");
105 let message = arguments.optional("message");
106
107 Ok(AssertEqFn {
108 left,
109 right,
110 message,
111 }
112 .as_expr())
113 }
114}
115
116#[derive(Debug, Clone)]
117struct AssertEqFn {
118 left: Box<dyn Expression>,
119 right: Box<dyn Expression>,
120 message: Option<Box<dyn Expression>>,
121}
122
123impl FunctionExpression for AssertEqFn {
124 fn resolve(&self, ctx: &mut Context) -> Resolved {
125 let left = self.left.resolve(ctx)?;
126 let right = self.right.resolve(ctx)?;
127 let message = self.message.as_ref().map(|m| m.resolve(ctx)).transpose()?;
128
129 assert_eq(&left, &right, message)
130 }
131
132 fn type_def(&self, _: &state::TypeState) -> TypeDef {
133 TypeDef::boolean().fallible()
134 }
135}
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140
141 test_function![
142 assert_eq => AssertEq;
143
144 pass {
145 args: func_args![left: "foo", right: "foo"],
146 want: Ok(true),
147 tdef: TypeDef::boolean().fallible(),
148 }
149
150 fail {
151 args: func_args![left: "foo", right: "bar"],
152 want: Err(r#"assertion failed: "foo" == "bar""#),
153 tdef: TypeDef::boolean().fallible(),
154 }
155
156 message {
157 args: func_args![left: "foo", right: "bar", message: "failure!"],
158 want: Err("failure!"),
159 tdef: TypeDef::boolean().fallible(),
160 }
161 ];
162}