Repository: clarkmcc/cel-rust (language: rust)

Issue:
Implement short-circuit evaluation for AND expression
Description:

In the provided example, the AND expression within the compiled program causes an error, which is counterintuitive and violates common programming language conventions.
```rust
let mut context = Context::default();
let data: HashMap<String, String>  = HashMap::new();
context.add_variable_from_value("data", data);
let program = Program::compile("has(data.x) && data.x.startsWith(\"foo\")").unwrap();
```

Expected Behavior:

The AND expression should support short-circuit evaluation. In this case, the data.x.startsWith("foo") part should only be evaluated if has(data.x) returns true. This would prevent unnecessary errors and align with typical programming language behavior.

I have prepared a pull request to address this issue by implementing short-circuit evaluation for AND expressions.

Thank you for your time and effort in maintaining this project. 


Discussion / hints:

Fix patch:
diff --git a/interpreter/src/context.rs b/interpreter/src/context.rs
index bdf0a20..51a99a6 100644
--- a/interpreter/src/context.rs
+++ b/interpreter/src/context.rs
@@ -40,7 +40,7 @@ pub enum Context<'a> {
     },
 }
 
-impl<'a> Context<'a> {
+impl Context<'_> {
     pub fn add_variable<S, V>(
         &mut self,
         name: S,
@@ -159,7 +159,7 @@ impl<'a> Context<'a> {
     }
 }
 
-impl<'a> Default for Context<'a> {
+impl Default for Context<'_> {
     fn default() -> Self {
         let mut ctx = Context::Root {
             variables: Default::default(),
diff --git a/interpreter/src/functions.rs b/interpreter/src/functions.rs
index 82f49a0..22696b1 100644
--- a/interpreter/src/functions.rs
+++ b/interpreter/src/functions.rs
@@ -381,7 +381,7 @@ pub fn all(
     ident: Identifier,
     expr: Expression,
 ) -> Result<bool> {
-    return match this {
+    match this {
         Value::List(items) => {
             let mut ptx = ftx.ptx.new_inner_scope();
             for item in items.iter() {
@@ -402,8 +402,8 @@ pub fn all(
             }
             Ok(true)
         }
-        _ => return Err(this.error_expected_type(ValueType::List)),
-    };
+        _ => Err(this.error_expected_type(ValueType::List)),
+    }
 }
 
 /// Returns a boolean value indicating whether a or more values in the provided
diff --git a/interpreter/src/magic.rs b/interpreter/src/magic.rs
index 0b8a373..4bdfaff 100644
--- a/interpreter/src/magic.rs
+++ b/interpreter/src/magic.rs
@@ -230,7 +230,7 @@ impl From<Identifier> for String {
 #[derive(Clone)]
 pub struct Arguments(pub Arc<Vec<Value>>);
 
-impl<'a, 'context> FromContext<'a, 'context> for Arguments {
+impl<'a> FromContext<'a, '_> for Arguments {
     fn from_context(ctx: &'a mut FunctionContext) -> Result<Self, ExecutionError>
     where
         Self: Sized,
@@ -389,7 +389,7 @@ impl<'a, 'context, H, T> HandlerCallable<'a, 'context, H, T> {
     }
 }
 
-impl<'a, 'context, H, T> Callable for HandlerCallable<'a, 'context, H, T>
+impl<H, T> Callable for HandlerCallable<'_, '_, H, T>
 where
     H: Handler<T> + Clone + 'static,
 {
diff --git a/interpreter/src/objects.rs b/interpreter/src/objects.rs
index e3b4cd2..5a7da4c 100644
--- a/interpreter/src/objects.rs
+++ b/interpreter/src/objects.rs
@@ -405,7 +405,7 @@ impl From<Value> for ResolveResult {
     }
 }
 
-impl<'a> Value {
+impl Value {
     pub fn resolve_all(expr: &[Expression], ctx: &Context) -> ResolveResult {
         let mut res = Vec::with_capacity(expr.len());
         for expr in expr {
@@ -415,7 +415,7 @@ impl<'a> Value {
     }
 
     #[inline(always)]
-    pub fn resolve(expr: &'a Expression, ctx: &Context) -> ResolveResult {
+    pub fn resolve(expr: &Expression, ctx: &Context) -> ResolveResult {
         match expr {
             Expression::Atom(atom) => Ok(atom.into()),
             Expression::Arithmetic(left, op, right) => {
@@ -486,8 +486,12 @@ impl<'a> Value {
             }
             Expression::And(left, right) => {
                 let left = Value::resolve(left, ctx)?;
-                let right = Value::resolve(right, ctx)?;
-                Value::Bool(left.to_bool() && right.to_bool()).into()
+                if !left.to_bool() {
+                    Value::Bool(false).into()
+                } else {
+                    let right = Value::resolve(right, ctx)?;
+                    Value::Bool(right.to_bool()).into()
+                }
             }
             Expression::Unary(op, expr) => {
                 let expr = Value::resolve(expr, ctx)?;
@@ -973,4 +977,18 @@ mod tests {
             )))]))
         );
     }
+
+    #[test]
+    fn test_short_curcuit_and() {
+        let mut context = Context::default();
+        let data: HashMap<String, String> = HashMap::new();
+        context.add_variable_from_value("data", data);
+
+        let program = Program::compile("has(data.x) && data.x.startsWith(\"foo\")").unwrap();
+        let value = program.execute(&context);
+        assert!(
+            value.is_ok(),
+            "The AND expression should support short-circuit evaluation."
+        );
+    }
 }
diff --git a/parser/src/ast.rs b/parser/src/ast.rs
index 6eeb54f..fd55029 100644
--- a/parser/src/ast.rs
+++ b/parser/src/ast.rs
@@ -73,7 +73,7 @@ pub struct ExpressionReferences<'expr> {
     functions: HashSet<&'expr str>,
 }
 
-impl<'expr> ExpressionReferences<'expr> {
+impl ExpressionReferences<'_> {
     /// Returns true if the expression references the provided variable name.
     ///
     /// # Example


Test patch: