fix: keep repeated ! negations in a condition right-associative (#3946)

Co-authored-by: sxyazi <sxyazi@gmail.com>
This commit is contained in:
Immanuel Tikhonov 2026-05-09 18:32:18 +04:00 committed by GitHub
parent 92b9ea3794
commit 22fb9e0d09
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -1,4 +1,4 @@
use std::str::FromStr;
use std::{cmp::Ordering, str::FromStr};
use anyhow::bail;
use serde_with::DeserializeFromStr;
@ -33,8 +33,7 @@ impl ConditionOp {
}
}
#[inline]
pub fn prec(&self) -> u8 {
fn prec(&self) -> u8 {
match self {
Self::Or => 1,
Self::And => 2,
@ -44,6 +43,18 @@ impl ConditionOp {
}
}
impl PartialOrd for ConditionOp {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
use Ordering::*;
match self.prec().cmp(&other.prec()) {
// Keep repeated `!` right-associative by making `! >= !` false.
Equal if matches!((self, other), (Self::Not, Self::Not)) => None,
ordering => Some(ordering),
}
}
}
#[derive(Debug, DeserializeFromStr)]
pub struct Condition {
ops: Vec<ConditionOp>,
@ -72,7 +83,7 @@ impl Condition {
let op = ConditionOp::new(token);
match op {
ConditionOp::Or | ConditionOp::And | ConditionOp::Not => {
while matches!(stack.last(), Some(last) if last.prec() >= op.prec()) {
while matches!(stack.last(), Some(last) if last >= &op) {
output.push(stack.pop().unwrap());
}
stack.push(op);
@ -129,3 +140,21 @@ impl Condition {
if stack.len() == 1 { Some(stack[0]) } else { None }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_condition_not() -> anyhow::Result<()> {
let cond: Condition = "!dir".parse()?;
assert!(!cond.eval(|s| s == "dir").unwrap());
assert!(cond.eval(|_| false).unwrap());
let cond: Condition = "!!dir".parse()?;
assert!(cond.eval(|s| s == "dir").unwrap());
assert!(!cond.eval(|_| false).unwrap());
Ok(())
}
}