diff --git a/lib/src/revset.rs b/lib/src/revset.rs index 93a8ff0e1..454d259fe 100644 --- a/lib/src/revset.rs +++ b/lib/src/revset.rs @@ -1276,9 +1276,23 @@ fn fold_difference(expression: &Rc) -> Option) -> Option> { + transform_expression_bottom_up(expression, |expression| match expression.as_ref() { + RevsetExpression::Difference(expression1, expression2) => { + Some(expression1.intersection(&expression2.negated())) + } + _ => None, + }) +} + /// Rewrites the given `expression` tree to reduce evaluation cost. Returns new /// tree. pub fn optimize(expression: Rc) -> Rc { + let expression = unfold_difference(&expression).unwrap_or(expression); let expression = internalize_filter(&expression).unwrap_or(expression); let expression = fold_redundant_expression(&expression).unwrap_or(expression); fold_difference(&expression).unwrap_or(expression) @@ -2513,10 +2527,6 @@ mod tests { let optimized = optimize(parsed.clone()); assert!(Rc::ptr_eq(&parsed, &optimized)); - let parsed = parse("branches() ~ tags()").unwrap(); - let optimized = optimize(parsed.clone()); - assert!(Rc::ptr_eq(&parsed, &optimized)); - // Only left subtree should be rewritten. let parsed = parse("(branches() & all()) | tags()").unwrap(); let optimized = optimize(parsed.clone()); @@ -2587,6 +2597,28 @@ mod tests { ) "###); + // Binary difference operation should go through the same optimization passes. + insta::assert_debug_snapshot!(optimize(parse("foo ~ bar").unwrap()), @r###" + Difference( + Symbol( + "foo", + ), + Symbol( + "bar", + ), + ) + "###); + insta::assert_debug_snapshot!(optimize(parse("(all() ~ foo) & bar").unwrap()), @r###" + Difference( + Symbol( + "bar", + ), + Symbol( + "foo", + ), + ) + "###); + // Double/triple negates. insta::assert_debug_snapshot!(optimize(parse("foo & ~~bar").unwrap()), @r###" Intersection(