Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RFC-2011] Expand matches! #110382

Closed
wants to merge 2 commits into from
Closed

[RFC-2011] Expand matches! #110382

wants to merge 2 commits into from

Conversation

c410-f3r
Copy link
Contributor

@c410-f3r c410-f3r commented Apr 16, 2023

cc #44838

I really thought for months how to expand matches and the best approach I could find was the introduction of a new ExprKind variant as well as turning matches! into a built-in macro. Please, let me known if there is a better method.

fn fun(a: Option<i32>, b: Option<i32>, c: Option<i32>) {
  assert!(
    [matches!(a, None), matches!(b, Some(1)] == [true, true] && matches!(c, Some(x) if x == 2)
  );
}

fn main() {
  fun(Some(1), None, Some(2));
}

// Assertion failed: [matches!(a, None), matches!(b, Some(1)] == [true, true] && matches!(c, Some(x) if x == 2)
//
// With captures:
//   a = Some(1)
//   b = None
//   c = Some(2)

cc @oli-obk Reviewed the last PRs
cc @petrochenkov We talked a bit about this subject

@rustbot
Copy link
Collaborator

rustbot commented Apr 16, 2023

r? @wesleywiser

(rustbot has picked a reviewer for you, use r? to override)

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-libs Relevant to the library team, which will review and decide on the PR/issue. labels Apr 16, 2023
@rustbot
Copy link
Collaborator

rustbot commented Apr 16, 2023

Hey! It looks like you've submitted a new PR for the library teams!

If this PR contains changes to any rust-lang/rust public library APIs then please comment with @rustbot label +T-libs-api -T-libs to tag it appropriately. If this PR contains changes to any unstable APIs please edit the PR description to add a link to the relevant API Change Proposal or create one if you haven't already. If you're unsure where your change falls no worries, just leave it as is and the reviewer will take a look and make a decision to forward on if necessary.

Examples of T-libs-api changes:

  • Stabilizing library features
  • Introducing insta-stable changes such as new implementations of existing stable traits on existing stable types
  • Introducing new or changing existing unstable library APIs (excluding permanently unstable features / features without a tracking issue)
  • Changing public documentation in ways that create new stability guarantees
  • Changing observable runtime behavior of library APIs

@rust-log-analyzer

This comment has been minimized.

@Ezrashaw
Copy link
Contributor

Ezrashaw commented Apr 16, 2023

It looks like there is an issue with bindings defined in the pattern and then being used in the guard. The following code is broken:

fn main() {
matches!(Some(1), Some(x) if x == 2);
}

EDIT: not sure I have time to help but since you can't build libstd, here is code to reproduce:

#![feature(no_core, staged_api, rustc_attrs, lang_items, decl_macro)]
#![no_core]

#![stable(feature = "rust", since ="1.0")]

#[rustc_builtin_macro]
#[stable(feature = "matches_macro", since = "1.42.0")]
pub macro matches($expression:expr, $pattern:pat $(if $guard:expr)? $(,)?) {{ /* compiler built-in */ }}

#[lang = "sized"]
trait Sized {}

enum Option<T> {
    Some(T),
    None
}

use Option::Some;

fn main() {
    matches!(Some(1), Some(x) if x == 2);
}

Copy link
Member

@est31 est31 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for this PR! I think this is a good example use of the builtin# syntax. I have a draft branch for it, and matches is a really good macro to start with because it is comparatively easy to parse.

Edit: branch lives here. I'm not suggesting that matches should use builtin# right in this PR, I would port matches in a future builtin# PR.

@petrochenkov petrochenkov self-assigned this Apr 16, 2023
@c410-f3r
Copy link
Contributor Author

Thank you @Ezrashaw and @est31! It is a pleasant surprise to see this type of engagement on Rustc.

I will try to address your comments as soon as possible.

@petrochenkov
Copy link
Contributor

The only thing I don't understand is how this helps nicer asserts #44838.

fn manage_cond_expr can never encounter ExprKind::Matches because it works with unexpanded expressions.

@rustbot author

@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Apr 17, 2023
@rust-log-analyzer

This comment has been minimized.

@c410-f3r
Copy link
Contributor Author

The only thing I don't understand is how this helps nicer asserts #44838.

fn manage_cond_expr can never encounter ExprKind::Matches because it works with unexpanded expressions.

@rustbot author

Well, that is unfortunate.

Taking a different approach, assert! is now trying to expand matches! on-the-fly in ExprKind::MacCall through the sharing of expand_matches.

This new approach doesn't look semantically appropriated and might incur a negative performance impact but as commented before, I am open to new suggestions. matches! elements are by far the things I want the most to be displayed in assert!.

There is also a plan to include a mechanism that will allow developers to tweak how much information assert! can output to mitigate potential runtime slowdowns and matches! can be part of this system for what it is worth it.

@bors

This comment was marked as resolved.

@rust-log-analyzer

This comment has been minimized.

@c410-f3r
Copy link
Contributor Author

c410-f3r commented May 18, 2023

@rustbot labels -S-waiting-on-author +S-waiting-on-review

@petrochenkov Macros are now being expanded to their ExprKind counterpart if they contain a supported built-in in parse_expr_path_start so anything like bar!( ... ) will be expanded to ExprKind::BAR( ... ) instead of ExprKind::MacCall( ... ). Again, this was the best approach that I could find.

Thanks @est31 for the builtin# implementation.

@@ -86,6 +86,9 @@ fn main() {
// index
[ [1i32, 1][elem as usize] == 3 ] => "Assertion failed: [1i32, 1][elem as usize] == 3\nWith captures:\n elem = 1\n"

// matches
[ matches!(Some(elem == 1i32), None) ] => "Assertion failed: builtin # matches(Some(elem == 1i32), None)\nWith captures:\n elem = 1\n"
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assertion failed: builtin # matches ... 🤷

@rustbot
Copy link
Collaborator

rustbot commented May 19, 2023

Some changes occurred in src/tools/clippy

cc @rust-lang/clippy

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels May 19, 2023
@rust-log-analyzer

This comment has been minimized.

@rustbot
Copy link
Collaborator

rustbot commented May 19, 2023

Some changes occurred in src/tools/rustfmt

cc @rust-lang/rustfmt

@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer
Copy link
Collaborator

The job x86_64-gnu-tools failed! Check out the build log: (web) (plain)

Click to see the possible cause of the failure (guessed by this bot)

 error: this `if` statement can be collapsed
   --> $DIR/collapsible_if.rs:9:5
    |
 LL | /     if x == "hello" {
 LL | |         if y == "world" {
 LL | |             println!("Hello world!");
 LL | |         }
 LL | |     }
    |
    |
    = note: `-D clippy::collapsible-if` implied by `-D warnings`
 help: collapse nested if block
    |
 LL ~     if x == "hello" && y == "world" {
 LL +         println!("Hello world!");
 LL +     }
 
 error: this `if` statement can be collapsed
   --> $DIR/collapsible_if.rs:15:5
    |
    |
 LL | /     if x == "hello" || x == "world" {
 LL | |         if y == "world" || y == "hello" {
 LL | |             println!("Hello world!");
 LL | |         }
 LL | |     }
    |
 help: collapse nested if block
    |
    |
 LL ~     if (x == "hello" || x == "world") && (y == "world" || y == "hello") {
 LL +         println!("Hello world!");
 LL +     }
 
 error: this `if` statement can be collapsed
   --> $DIR/collapsible_if.rs:21:5
    |
    |
 LL | /     if x == "hello" && x == "world" {
 LL | |         if y == "world" || y == "hello" {
 LL | |             println!("Hello world!");
 LL | |         }
 LL | |     }
    |
 help: collapse nested if block
    |
    |
 LL ~     if x == "hello" && x == "world" && (y == "world" || y == "hello") {
 LL +         println!("Hello world!");
 LL +     }
 
 error: this `if` statement can be collapsed
   --> $DIR/collapsible_if.rs:27:5
    |
    |
 LL | /     if x == "hello" || x == "world" {
 LL | |         if y == "world" && y == "hello" {
 LL | |             println!("Hello world!");
 LL | |         }
 LL | |     }
    |
 help: collapse nested if block
    |
    |
 LL ~     if (x == "hello" || x == "world") && y == "world" && y == "hello" {
 LL +         println!("Hello world!");
 LL +     }
 
 error: this `if` statement can be collapsed
   --> $DIR/collapsible_if.rs:33:5
    |
    |
 LL | /     if x == "hello" && x == "world" {
 LL | |         if y == "world" && y == "hello" {
 LL | |             println!("Hello world!");
 LL | |         }
 LL | |     }
    |
 help: collapse nested if block
    |
    |
 LL ~     if x == "hello" && x == "world" && y == "world" && y == "hello" {
 LL +         println!("Hello world!");
 LL +     }
 
 error: this `if` statement can be collapsed
   --> $DIR/collapsible_if.rs:39:5
    |
    |
 LL | /     if 42 == 1337 {
 LL | |         if 'a' != 'A' {
 LL | |             println!("world!")
 LL | |         }
 LL | |     }
    |
 help: collapse nested if block
    |
    |
 LL ~     if 42 == 1337 && 'a' != 'A' {
 LL +         println!("world!")
 LL +     }
 
 error: this `if` statement can be collapsed
   --> $DIR/collapsible_if.rs:95:5
    |
    |
 LL | /     if x == "hello" {
 LL | |         if y == "world" { // Collapsible
 LL | |             println!("Hello world!");
 LL | |         }
 LL | |     }
    |
 help: collapse nested if block
    |
    |
 LL ~     if x == "hello" && y == "world" { // Collapsible
 LL +         println!("Hello world!");
 LL +     }
 
 error: this `if` statement can be collapsed
   --> $DIR/collapsible_if.rs:154:5
    |
    |
 LL | /     if matches!(true, true) {
 LL | |         if matches!(true, true) {}
 LL | |     }
-   | |_____^ help: collapse nested if block: `if matches!(true, true) && matches!(true, true) {}`
+   | |_____^ help: collapse nested if block: `if matches!(true, true && matches!(true, true {}`
 error: this `if` statement can be collapsed
   --> $DIR/collapsible_if.rs:159:5
    |
    |
 LL | /     if matches!(true, true) && truth() {
 LL | |         if matches!(true, true) {}
 LL | |     }
-   | |_____^ help: collapse nested if block: `if matches!(true, true) && truth() && matches!(true, true) {}`
+   | |_____^ help: collapse nested if block: `if matches!(true, true && truth() && matches!(true, true {}`
 error: aborting due to 9 previous errors
 
 


The actual stderr differed from the expected stderr.
Actual stderr saved to /checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/x86_64-unknown-linux-gnu/release/test/ui/collapsible_if.stage-id.stderr
diff of fixed:

 //@run-rustfix
 #![allow(clippy::assertions_on_constants, clippy::equatable_if_let)]
 #[rustfmt::skip]
 #[rustfmt::skip]
 #[warn(clippy::collapsible_if)]
     let x = "hello";
     let x = "hello";
     let y = "world";
     if x == "hello" && y == "world" {
     }
 
 
     if (x == "hello" || x == "world") && (y == "world" || y == "hello") {
     }
 
 
     if x == "hello" && x == "world" && (y == "world" || y == "hello") {
     }
 
 
     if (x == "hello" || x == "world") && y == "world" && y == "hello" {
     }
 
 
     if x == "hello" && x == "world" && y == "world" && y == "hello" {
     }
 
 
     if 42 == 1337 && 'a' != 'A' {
         println!("world!")
 
 
     // Works because any if with an else statement cannot be collapsed.
     if x == "hello" {
         if y == "world" {
         }
     } else {
         println!("Not Hello world");
     }
     }
 
     if x == "hello" {
         if y == "world" {
         } else {
             println!("Hello something else");
         }
     }
     }
 
     if x == "hello" {
         print!("Hello ");
         if y == "world" {
             println!("world!")
     }
 
     if true {
     } else {
     } else {
         assert!(true); // assert! is just an `if`
 
 
     // The following tests check for the fix of https://github.com/rust-lang/rust-clippy/issues/798
     // The following tests check for the fix of https://github.com/rust-lang/rust-clippy/issues/798
     if x == "hello" {// Not collapsible
         if y == "world" {
         }
     }
 
 
     if x == "hello" { // Not collapsible
         if y == "world" {
         }
     }
 
 
     if x == "hello" {
         // Not collapsible
         if y == "world" {
         }
     }
 
 
     if x == "hello" && y == "world" { // Collapsible
     }
 
 
     if x == "hello" {
         print!("Hello ");
         // Not collapsible
         // Not collapsible
         if y == "world" {
             println!("world!")
     }
 
 
     if x == "hello" {
         print!("Hello ");
         // Not collapsible
         if let Some(42) = Some(42) {
             println!("world!")
         }
         }
     }
 
error: test failed, to rerun pass `--test compile-test`
Build completed unsuccessfully in 0:02:06
     if x == "hello" {
         /* Not collapsible */
         if y == "world" {
         }
     }
 
 
     if x == "hello" { /* Not collapsible */
         if y == "world" {
         }
     }
 
 
     // Test behavior wrt. `let_chains`.
     fn truth() -> bool { true }
 
     // Prefix:
     if let 0 = 1 {
     if let 0 = 1 {
         if truth() {}
     }
 
     // Suffix:
     if truth() {
         if let 0 = 1 {}
 
     // Midfix:
     if truth() {
         if let 0 = 1 {
         if let 0 = 1 {
             if truth() {}
         }
     }
 
     // Fix #5962
-    if matches!(true, true) && matches!(true, true) {}
+    if matches!(true, true && matches!(true, true {}
     // Issue #9375
     // Issue #9375
-    if matches!(true, true) && truth() && matches!(true, true) {}
+    if matches!(true, true && truth() && matches!(true, true {}
     if true {
     if true {
         #[cfg(not(teehee))]
             println!("Hello world!");
         }
     }
 }
 }
 

The actual fixed differed from the expected fixed.
Actual fixed saved to /checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/x86_64-unknown-linux-gnu/release/test/ui/collapsible_if.stage-id.fixed
To only update this specific test, also pass `--test-args collapsible_if.rs`

error: 2 errors occurred comparing output.
status: exit status: 1
status: exit status: 1
command: "/checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/x86_64-unknown-linux-gnu/release/clippy-driver" "tests/ui/collapsible_if.rs" "-L" "/checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/x86_64-unknown-linux-gnu/release/test/ui" "--target=x86_64-unknown-linux-gnu" "--error-format" "json" "-C" "prefer-dynamic" "-o" "/checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/x86_64-unknown-linux-gnu/release/test/ui/collapsible_if.stage-id" "-A" "unused" "--emit=metadata" "-Dwarnings" "-Zui-testing" "-L" "dependency=/checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/x86_64-unknown-linux-gnu/release/deps" "-L" "dependency=/checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/release/deps" "--extern" "serde=/checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/x86_64-unknown-linux-gnu/release/deps/libserde-426d0cca316c35ed.rlib" "--extern" "tokio=/checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/x86_64-unknown-linux-gnu/release/deps/libtokio-e0524b7e2611e851.rlib" "--extern" "derive_new=/checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/release/deps/libderive_new-c7b10cf951dd44cc.so" "--extern" "regex=/checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/x86_64-unknown-linux-gnu/release/deps/libregex-619ac20e364f2b2c.rlib" "--extern" "clippy_lints=/checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/x86_64-unknown-linux-gnu/release/deps/libclippy_lints-25f7a610b7fafce5.rlib" "--extern" "rustc_semver=/checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/x86_64-unknown-linux-gnu/release/deps/librustc_semver-963bbd3f89834643.rlib" "--extern" "itertools=/checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/x86_64-unknown-linux-gnu/release/deps/libitertools-b6f83e8bf7b1d2e3.rlib" "--extern" "parking_lot=/checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/x86_64-unknown-linux-gnu/release/deps/libparking_lot-64c69a0c8d27494b.rlib" "--extern" "syn=/checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/x86_64-unknown-linux-gnu/release/deps/libsyn-bd373ba7cdb9c07f.rlib" "--extern" "clippy_utils=/checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/x86_64-unknown-linux-gnu/release/deps/libclippy_utils-2d8078885e283441.rlib" "--extern" "serde_derive=/checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/release/deps/libserde_derive-a36a3333b2a93763.so" "--extern" "if_chain=/checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/x86_64-unknown-linux-gnu/release/deps/libif_chain-03f75cdc6d4d3afc.rlib" "--extern" "futures=/checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/x86_64-unknown-linux-gnu/release/deps/libfutures-2c5d99d6327d1048.rlib" "--extern" "quote=/checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/x86_64-unknown-linux-gnu/release/deps/libquote-3035b3cfcafc3b31.rlib" "--edition=2021" "-L" "/checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/x86_64-unknown-linux-gnu/release/test/ui/collapsible_if.stage-id.aux"
------------------------------------------

------------------------------------------
stderr:
stderr:
------------------------------------------
{"message":"this `if` statement can be collapsed","code":{"code":"clippy::collapsible_if","explanation":null},"level":"error","spans":[{"file_name":"tests/ui/collapsible_if.rs","byte_start":192,"byte_end":289,"line_start":9,"line_end":13,"column_start":5,"column_end":6,"is_primary":true,"text":[{"text":"    if x == \"hello\" {","highlight_start":5,"highlight_end":22},{"text":"        if y == \"world\" {","highlight_start":1,"highlight_end":26},{"text":"            println!(\"Hello world!\");","highlight_start":1,"highlight_end":38},{"text":"        }","highlight_start":1,"highlight_end":10},{"text":"    }","highlight_start":1,"highlight_end":6}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[{"message":"`-D clippy::collapsible-if` implied by `-D warnings`","code":null,"level":"note","spans":[],"children":[],"rendered":null},{"message":"collapse nested if block","code":null,"level":"help","spans":[{"file_name":"tests/ui/collapsible_if.rs","byte_start":192,"byte_end":289,"line_start":9,"line_end":13,"column_start":5,"column_end":6,"is_primary":true,"text":[{"text":"    if x == \"hello\" {","highlight_start":5,"highlight_end":22},{"text":"        if y == \"world\" {","highlight_start":1,"highlight_end":26},{"text":"            println!(\"Hello world!\");","highlight_start":1,"highlight_end":38},{"text":"        }","highlight_start":1,"highlight_end":10},{"text":"    }","highlight_start":1,"highlight_end":6}],"label":null,"suggested_replacement":"if x == \"hello\" && y == \"world\" {\n        println!(\"Hello world!\");\n    }","suggestion_applicability":"MachineApplicable","expansion":null}],"children":[],"rendered":null}],"rendered":"error: this `if` statement can be collapsed\n  --> tests/ui/collapsible_if.rs:9:5\n   |\nLL | /     if x == \"hello\" {\nLL | |         if y == \"world\" {\nLL | |             println!(\"Hello world!\");\nLL | |         }\nLL | |     }\n   | |_____^\n   |\n   = note: `-D clippy::collapsible-if` implied by `-D warnings`\nhelp: collapse nested if block\n   |\nLL ~     if x == \"hello\" && y == \"world\" {\nLL +         println!(\"Hello world!\");\nLL +     }\n   |\n\n"}
{"message":"this `if` statement can be collapsed","code":{"code":"clippy::collapsible_if","explanation":null},"level":"error","spans":[{"file_name":"tests/ui/collapsible_if.rs","byte_start":295,"byte_end":424,"line_start":15,"line_end":19,"column_start":5,"column_end":6,"is_primary":true,"text":[{"text":"    if x == \"hello\" || x == \"world\" {","highlight_start":5,"highlight_end":38},{"text":"        if y == \"world\" || y == \"hello\" {","highlight_start":1,"highlight_end":42},{"text":"            println!(\"Hello world!\");","highlight_start":1,"highlight_end":38},{"text":"        }","highlight_start":1,"highlight_end":10},{"text":"    }","highlight_start":1,"highlight_end":6}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[{"message":"collapse nested if block","code":null,"level":"help","spans":[{"file_name":"tests/ui/collapsible_if.rs","byte_start":295,"byte_end":424,"line_start":15,"line_end":19,"column_start":5,"column_end":6,"is_primary":true,"text":[{"text":"    if x == \"hello\" || x == \"world\" {","highlight_start":5,"highlight_end":38},{"text":"        if y == \"world\" || y == \"hello\" {","highlight_start":1,"highlight_end":42},{"text":"            println!(\"Hello world!\");","highlight_start":1,"highlight_end":38},{"text":"        }","highlight_start":1,"highlight_end":10},{"text":"    }","highlight_start":1,"highlight_end":6}],"label":null,"suggested_replacement":"if (x == \"hello\" || x == \"world\") && (y == \"world\" || y == \"hello\") {\n        println!(\"Hello world!\");\n    }","suggestion_applicability":"MachineApplicable","expansion":null}],"children":[],"rendered":null}],"rendered":"error: this `if` statement can be collapsed\n  --> tests/ui/collapsible_if.rs:15:5\n   |\nLL | /     if x == \"hello\" || x == \"world\" {\nLL | |         if y == \"world\" || y == \"hello\" {\nLL | |             println!(\"Hello world!\");\nLL | |         }\nLL | |     }\n   | |_____^\n   |\nhelp: collapse nested if block\n   |\nLL ~     if (x == \"hello\" || x == \"world\") && (y == \"world\" || y == \"hello\") {\nLL +         println!(\"Hello world!\");\nLL +     }\n   |\n\n"}
{"message":"this `if` statement can be collapsed","code":{"code":"clippy::collapsible_if","explanation":null},"level":"error","spans":[{"file_name":"tests/ui/collapsible_if.rs","byte_start":430,"byte_end":559,"line_start":21,"line_end":25,"column_start":5,"column_end":6,"is_primary":true,"text":[{"text":"    if x == \"hello\" && x == \"world\" {","highlight_start":5,"highlight_end":38},{"text":"        if y == \"world\" || y == \"hello\" {","highlight_start":1,"highlight_end":42},{"text":"            println!(\"Hello world!\");","highlight_start":1,"highlight_end":38},{"text":"        }","highlight_start":1,"highlight_end":10},{"text":"    }","highlight_start":1,"highlight_end":6}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[{"message":"collapse nested if block","code":null,"level":"help","spans":[{"file_name":"tests/ui/collapsible_if.rs","byte_start":430,"byte_end":559,"line_start":21,"line_end":25,"column_start":5,"column_end":6,"is_primary":true,"text":[{"text":"    if x == \"hello\" && x == \"world\" {","highlight_start":5,"highlight_end":38},{"text":"        if y == \"world\" || y == \"hello\" {","highlight_start":1,"highlight_end":42},{"text":"            println!(\"Hello world!\");","highlight_start":1,"highlight_end":38},{"text":"        }","highlight_start":1,"highlight_end":10},{"text":"    }","highlight_start":1,"highlight_end":6}],"label":null,"suggested_replacement":"if x == \"hello\" && x == \"world\" && (y == \"world\" || y == \"hello\") {\n        println!(\"Hello world!\");\n    }","suggestion_applicability":"MachineApplicable","expansion":null}],"children":[],"rendered":null}],"rendered":"error: this `if` statement can be collapsed\n  --> tests/ui/collapsible_if.rs:21:5\n   |\nLL | /     if x == \"hello\" && x == \"world\" {\nLL | |         if y == \"world\" || y == \"hello\" {\nLL | |             println!(\"Hello world!\");\nLL | |         }\nLL | |     }\n   | |_____^\n   |\nhelp: collapse nested if block\n   |\nLL ~     if x == \"hello\" && x == \"world\" && (y == \"world\" || y == \"hello\") {\nLL +         println!(\"Hello world!\");\nLL +     }\n   |\n\n"}
{"message":"this `if` statement can be collapsed","code":{"code":"clippy::collapsible_if","explanation":null},"level":"error","spans":[{"file_name":"tests/ui/collapsible_if.rs","byte_start":565,"byte_end":694,"line_start":27,"line_end":31,"column_start":5,"column_end":6,"is_primary":true,"text":[{"text":"    if x == \"hello\" || x == \"world\" {","highlight_start":5,"highlight_end":38},{"text":"        if y == \"world\" && y == \"hello\" {","highlight_start":1,"highlight_end":42},{"text":"            println!(\"Hello world!\");","highlight_start":1,"highlight_end":38},{"text":"        }","highlight_start":1,"highlight_end":10},{"text":"    }","highlight_start":1,"highlight_end":6}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[{"message":"collapse nested if block","code":null,"level":"help","spans":[{"file_name":"tests/ui/collapsible_if.rs","byte_start":565,"byte_end":694,"line_start":27,"line_end":31,"column_start":5,"column_end":6,"is_primary":true,"text":[{"text":"    if x == \"hello\" || x == \"world\" {","highlight_start":5,"highlight_end":38},{"text":"        if y == \"world\" && y == \"hello\" {","highlight_start":1,"highlight_end":42},{"text":"            println!(\"Hello world!\");","highlight_start":1,"highlight_end":38},{"text":"        }","highlight_start":1,"highlight_end":10},{"text":"    }","highlight_start":1,"highlight_end":6}],"label":null,"suggested_replacement":"if (x == \"hello\" || x == \"world\") && y == \"world\" && y == \"hello\" {\n        println!(\"Hello world!\");\n    }","suggestion_applicability":"MachineApplicable","expansion":null}],"children":[],"rendered":null}],"rendered":"error: this `if` statement can be collapsed\n  --> tests/ui/collapsible_if.rs:27:5\n   |\nLL | /     if x == \"hello\" || x == \"world\" {\nLL | |         if y == \"world\" && y == \"hello\" {\nLL | |             println!(\"Hello world!\");\nLL | |         }\nLL | |     }\n   | |_____^\n   |\nhelp: collapse nested if block\n   |\nLL ~     if (x == \"hello\" || x == \"world\") && y == \"world\" && y == \"hello\" {\nLL +         println!(\"Hello world!\");\nLL +     }\n   |\n\n"}
{"message":"this `if` statement can be collapsed","code":{"code":"clippy::collapsible_if","explanation":null},"level":"error","spans":[{"file_name":"tests/ui/collapsible_if.rs","byte_start":700,"byte_end":829,"line_start":33,"line_end":37,"column_start":5,"column_end":6,"is_primary":true,"text":[{"text":"    if x == \"hello\" && x == \"world\" {","highlight_start":5,"highlight_end":38},{"text":"        if y == \"world\" && y == \"hello\" {","highlight_start":1,"highlight_end":42},{"text":"            println!(\"Hello world!\");","highlight_start":1,"highlight_end":38},{"text":"        }","highlight_start":1,"highlight_end":10},{"text":"    }","highlight_start":1,"highlight_end":6}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[{"message":"collapse nested if block","code":null,"level":"help","spans":[{"file_name":"tests/ui/collapsible_if.rs","byte_start":700,"byte_end":829,"line_start":33,"line_end":37,"column_start":5,"column_end":6,"is_primary":true,"text":[{"text":"    if x == \"hello\" && x == \"world\" {","highlight_start":5,"highlight_end":38},{"text":"        if y == \"world\" && y == \"hello\" {","highlight_start":1,"highlight_end":42},{"text":"            println!(\"Hello world!\");","highlight_start":1,"highlight_end":38},{"text":"        }","highlight_start":1,"highlight_end":10},{"text":"    }","highlight_start":1,"highlight_end":6}],"label":null,"suggested_replacement":"if x == \"hello\" && x == \"world\" && y == \"world\" && y == \"hello\" {\n        println!(\"Hello world!\");\n    }","suggestion_applicability":"MachineApplicable","expansion":null}],"children":[],"rendered":null}],"rendered":"error: this `if` statement can be collapsed\n  --> tests/ui/collapsible_if.rs:33:5\n   |\nLL | /     if x == \"hello\" && x == \"world\" {\nLL | |         if y == \"world\" && y == \"hello\" {\nLL | |             println!(\"Hello world!\");\nLL | |         }\nLL | |     }\n   | |_____^\n   |\nhelp: collapse nested if block\n   |\nLL ~     if x == \"hello\" && x == \"world\" && y == \"world\" && y == \"hello\" {\nLL +         println!(\"Hello world!\");\nLL +     }\n   |\n\n"}
{"message":"this `if` statement can be collapsed","code":{"code":"clippy::collapsible_if","explanation":null},"level":"error","spans":[{"file_name":"tests/ui/collapsible_if.rs","byte_start":835,"byte_end":921,"line_start":39,"line_end":43,"column_start":5,"column_end":6,"is_primary":true,"text":[{"text":"    if 42 == 1337 {","highlight_start":5,"highlight_end":20},{"text":"        if 'a' != 'A' {","highlight_start":1,"highlight_end":24},{"text":"            println!(\"world!\")","highlight_start":1,"highlight_end":31},{"text":"        }","highlight_start":1,"highlight_end":10},{"text":"    }","highlight_start":1,"highlight_end":6}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[{"message":"collapse nested if block","code":null,"level":"help","spans":[{"file_name":"tests/ui/collapsible_if.rs","byte_start":835,"byte_end":921,"line_start":39,"line_end":43,"column_start":5,"column_end":6,"is_primary":true,"text":[{"text":"    if 42 == 1337 {","highlight_start":5,"highlight_end":20},{"text":"        if 'a' != 'A' {","highlight_start":1,"highlight_end":24},{"text":"            println!(\"world!\")","highlight_start":1,"highlight_end":31},{"text":"        }","highlight_start":1,"highlight_end":10},{"text":"    }","highlight_start":1,"highlight_end":6}],"label":null,"suggested_replacement":"if 42 == 1337 && 'a' != 'A' {\n        println!(\"world!\")\n    }","suggestion_applicability":"MachineApplicable","expansion":null}],"children":[],"rendered":null}],"rendered":"error: this `if` statement can be collapsed\n  --> tests/ui/collapsible_if.rs:39:5\n   |\nLL | /     if 42 == 1337 {\nLL | |         if 'a' != 'A' {\nLL | |             println!(\"world!\")\nLL | |         }\nLL | |     }\n   | |_____^\n   |\nhelp: collapse nested if block\n   |\nLL ~     if 42 == 1337 && 'a' != 'A' {\nLL +         println!(\"world!\")\nLL +     }\n   |\n\n"}
{"message":"this `if` statement can be collapsed","code":{"code":"clippy::collapsible_if","explanation":null},"level":"error","spans":[{"file_name":"tests/ui/collapsible_if.rs","byte_start":1998,"byte_end":2110,"line_start":95,"line_end":99,"column_start":5,"column_end":6,"is_primary":true,"text":[{"text":"    if x == \"hello\" {","highlight_start":5,"highlight_end":22},{"text":"        if y == \"world\" { // Collapsible","highlight_start":1,"highlight_end":41},{"text":"            println!(\"Hello world!\");","highlight_start":1,"highlight_end":38},{"text":"        }","highlight_start":1,"highlight_end":10},{"text":"    }","highlight_start":1,"highlight_end":6}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[{"message":"collapse nested if block","code":null,"level":"help","spans":[{"file_name":"tests/ui/collapsible_if.rs","byte_start":1998,"byte_end":2110,"line_start":95,"line_end":99,"column_start":5,"column_end":6,"is_primary":true,"text":[{"text":"    if x == \"hello\" {","highlight_start":5,"highlight_end":22},{"text":"        if y == \"world\" { // Collapsible","highlight_start":1,"highlight_end":41},{"text":"            println!(\"Hello world!\");","highlight_start":1,"highlight_end":38},{"text":"        }","highlight_start":1,"highlight_end":10},{"text":"    }","highlight_start":1,"highlight_end":6}],"label":null,"suggested_replacement":"if x == \"hello\" && y == \"world\" { // Collapsible\n        println!(\"Hello world!\");\n    }","suggestion_applicability":"MachineApplicable","expansion":null}],"children":[],"rendered":null}],"rendered":"error: this `if` statement can be collapsed\n  --> tests/ui/collapsible_if.rs:95:5\n   |\nLL | /     if x == \"hello\" {\nLL | |         if y == \"world\" { // Collapsible\nLL | |             println!(\"Hello world!\");\nLL | |         }\nLL | |     }\n   | |_____^\n   |\nhelp: collapse nested if block\n   |\nLL ~     if x == \"hello\" && y == \"world\" { // Collapsible\nLL +         println!(\"Hello world!\");\nLL +     }\n   |\n\n"}
{"message":"this `if` statement can be collapsed","code":{"code":"clippy::collapsible_if","explanation":null},"level":"error","spans":[{"file_name":"tests/ui/collapsible_if.rs","byte_start":3075,"byte_end":3141,"line_start":154,"line_end":156,"column_start":5,"column_end":6,"is_primary":true,"text":[{"text":"    if matches!(true, true) {","highlight_start":5,"highlight_end":30},{"text":"        if matches!(true, true) {}","highlight_start":1,"highlight_end":35},{"text":"    }","highlight_start":1,"highlight_end":6}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[{"message":"collapse nested if block","code":null,"level":"help","spans":[{"file_name":"tests/ui/collapsible_if.rs","byte_start":3075,"byte_end":3141,"line_start":154,"line_end":156,"column_start":5,"column_end":6,"is_primary":true,"text":[{"text":"    if matches!(true, true) {","highlight_start":5,"highlight_end":30},{"text":"        if matches!(true, true) {}","highlight_start":1,"highlight_end":35},{"text":"    }","highlight_start":1,"highlight_end":6}],"label":null,"suggested_replacement":"if matches!(true, true && matches!(true, true {}","suggestion_applicability":"MachineApplicable","expansion":null}],"children":[],"rendered":null}],"rendered":"error: this `if` statement can be collapsed\n  --> tests/ui/collapsible_if.rs:154:5\n   |\nLL | /     if matches!(true, true) {\nLL | |         if matches!(true, true) {}\nLL | |     }\n   | |_____^ help: collapse nested if block: `if matches!(true, true && matches!(true, true {}`\n\n"}
{"message":"this `if` statement can be collapsed","code":{"code":"clippy::collapsible_if","explanation":null},"level":"error","spans":[{"file_name":"tests/ui/collapsible_if.rs","byte_start":3166,"byte_end":3243,"line_start":159,"line_end":161,"column_start":5,"column_end":6,"is_primary":true,"text":[{"text":"    if matches!(true, true) && truth() {","highlight_start":5,"highlight_end":41},{"text":"        if matches!(true, true) {}","highlight_start":1,"highlight_end":35},{"text":"    }","highlight_start":1,"highlight_end":6}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[{"message":"collapse nested if block","code":null,"level":"help","spans":[{"file_name":"tests/ui/collapsible_if.rs","byte_start":3166,"byte_end":3243,"line_start":159,"line_end":161,"column_start":5,"column_end":6,"is_primary":true,"text":[{"text":"    if matches!(true, true) && truth() {","highlight_start":5,"highlight_end":41},{"text":"        if matches!(true, true) {}","highlight_start":1,"highlight_end":35},{"text":"    }","highlight_start":1,"highlight_end":6}],"label":null,"suggested_replacement":"if matches!(true, true && truth() && matches!(true, true {}","suggestion_applicability":"MachineApplicable","expansion":null}],"children":[],"rendered":null}],"rendered":"error: this `if` statement can be collapsed\n  --> tests/ui/collapsible_if.rs:159:5\n   |\nLL | /     if matches!(true, true) && truth() {\nLL | |         if matches!(true, true) {}\nLL | |     }\n   | |_____^ help: collapse nested if block: `if matches!(true, true && truth() && matches!(true, true {}`\n\n"}

------------------------------------------

diff of stderr:
diff of stderr:

 error: this could be simplified with `bool::then`
   --> $DIR/if_then_some_else_none.rs:5:13
    |
 LL |       let _ = if foo() {
    |  _____________^
 LL | |         println!("true!");
 LL | |         Some("foo")
 LL | |     } else {
 LL | |         None
 LL | |     };
    |
    |
    = help: consider using `bool::then` like: `foo().then(|| { /* snippet */ "foo" })`
    = note: `-D clippy::if-then-some-else-none` implied by `-D warnings`
 error: this could be simplified with `bool::then`
   --> $DIR/if_then_some_else_none.rs:13:13
    |
    |
 LL |       let _ = if matches!(true, true) {
    |  _____________^
 LL | |         println!("true!");
 LL | |         Some(matches!(true, false))
 LL | |     } else {
 LL | |         None
 LL | |     };
    |
    |
-   = help: consider using `bool::then` like: `matches!(true, true).then(|| { /* snippet */ matches!(true, false) })`
+   = help: consider using `bool::then` like: `(matches!(true, true).then(|| { /* snippet */ matches!(true, false })`
 error: this could be simplified with `bool::then_some`
   --> $DIR/if_then_some_else_none.rs:22:28
    |
    |
 LL |     let _ = x.and_then(|o| if o < 32 { Some(o) } else { None });
    |
    |
    = help: consider using `bool::then_some` like: `(o < 32).then_some(o)`
 error: this could be simplified with `bool::then_some`
   --> $DIR/if_then_some_else_none.rs:26:13
    |
    |
 LL |     let _ = if !x { Some(0) } else { None };
    |
    |
    = help: consider using `bool::then_some` like: `(!x).then_some(0)`
 error: this could be simplified with `bool::then`
   --> $DIR/if_then_some_else_none.rs:81:13
    |
 LL |       let _ = if foo() {
 LL |       let _ = if foo() {
    |  _____________^
 LL | |         println!("true!");
 LL | |         Some(150)
 LL | |     } else {
 LL | |         None
 LL | |     };
    |
    |
    = help: consider using `bool::then` like: `foo().then(|| { /* snippet */ 150 })`
 error: aborting due to 5 previous errors
 
 


The actual stderr differed from the expected stderr.
Actual stderr saved to /checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/x86_64-unknown-linux-gnu/release/test/ui/if_then_some_else_none.stage-id.stderr
To update references, rerun the tests and pass the `--bless` flag
To only update this specific test, also pass `--test-args if_then_some_else_none.rs`

error: 1 errors occurred comparing output.
status: exit status: 1
command: "/checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/x86_64-unknown-linux-gnu/release/clippy-driver" "tests/ui/if_then_some_else_none.rs" "-L" "/checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/x86_64-unknown-linux-gnu/release/test/ui" "--target=x86_64-unknown-linux-gnu" "--error-format" "json" "-C" "prefer-dynamic" "-o" "/checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/x86_64-unknown-linux-gnu/release/test/ui/if_then_some_else_none.stage-id" "-A" "unused" "--emit=metadata" "-Dwarnings" "-Zui-testing" "-L" "dependency=/checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/x86_64-unknown-linux-gnu/release/deps" "-L" "dependency=/checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/release/deps" "--extern" "serde=/checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/x86_64-unknown-linux-gnu/release/deps/libserde-426d0cca316c35ed.rlib" "--extern" "tokio=/checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/x86_64-unknown-linux-gnu/release/deps/libtokio-e0524b7e2611e851.rlib" "--extern" "derive_new=/checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/release/deps/libderive_new-c7b10cf951dd44cc.so" "--extern" "regex=/checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/x86_64-unknown-linux-gnu/release/deps/libregex-619ac20e364f2b2c.rlib" "--extern" "clippy_lints=/checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/x86_64-unknown-linux-gnu/release/deps/libclippy_lints-25f7a610b7fafce5.rlib" "--extern" "rustc_semver=/checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/x86_64-unknown-linux-gnu/release/deps/librustc_semver-963bbd3f89834643.rlib" "--extern" "itertools=/checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/x86_64-unknown-linux-gnu/release/deps/libitertools-b6f83e8bf7b1d2e3.rlib" "--extern" "parking_lot=/checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/x86_64-unknown-linux-gnu/release/deps/libparking_lot-64c69a0c8d27494b.rlib" "--extern" "syn=/checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/x86_64-unknown-linux-gnu/release/deps/libsyn-bd373ba7cdb9c07f.rlib" "--extern" "clippy_utils=/checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/x86_64-unknown-linux-gnu/release/deps/libclippy_utils-2d8078885e283441.rlib" "--extern" "serde_derive=/checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/release/deps/libserde_derive-a36a3333b2a93763.so" "--extern" "if_chain=/checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/x86_64-unknown-linux-gnu/release/deps/libif_chain-03f75cdc6d4d3afc.rlib" "--extern" "futures=/checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/x86_64-unknown-linux-gnu/release/deps/libfutures-2c5d99d6327d1048.rlib" "--extern" "quote=/checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/x86_64-unknown-linux-gnu/release/deps/libquote-3035b3cfcafc3b31.rlib" "--edition=2021" "-L" "/checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/x86_64-unknown-linux-gnu/release/test/ui/if_then_some_else_none.stage-id.aux"
------------------------------------------

------------------------------------------
stderr:
stderr:
------------------------------------------
{"message":"this could be simplified with `bool::then`","code":{"code":"clippy::if_then_some_else_none","explanation":null},"level":"error","spans":[{"file_name":"tests/ui/if_then_some_else_none.rs","byte_start":96,"byte_end":185,"line_start":5,"line_end":10,"column_start":13,"column_end":6,"is_primary":true,"text":[{"text":"    let _ = if foo() {","highlight_start":13,"highlight_end":23},{"text":"        println!(\"true!\");","highlight_start":1,"highlight_end":27},{"text":"        Some(\"foo\")","highlight_start":1,"highlight_end":20},{"text":"    } else {","highlight_start":1,"highlight_end":13},{"text":"        None","highlight_start":1,"highlight_end":13},{"text":"    };","highlight_start":1,"highlight_end":6}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[{"message":"consider using `bool::then` like: `foo().then(|| { /* snippet */ \"foo\" })`","code":null,"level":"help","spans":[],"children":[],"rendered":null},{"message":"`-D clippy::if-then-some-else-none` implied by `-D warnings`","code":null,"level":"note","spans":[],"children":[],"rendered":null}],"rendered":"error: this could be simplified with `bool::then`\n  --> tests/ui/if_then_some_else_none.rs:5:13\n   |\nLL |       let _ = if foo() {\n   |  _____________^\nLL | |         println!(\"true!\");\nLL | |         Some(\"foo\")\nLL | |     } else {\nLL | |         None\nLL | |     };\n   | |_____^\n   |\n   = help: consider using `bool::then` like: `foo().then(|| { /* snippet */ \"foo\" })`\n   = note: `-D clippy::if-then-some-else-none` implied by `-D warnings`\n\n"}
{"message":"this could be simplified with `bool::then`","code":{"code":"clippy::if_then_some_else_none","explanation":null},"level":"error","spans":[{"file_name":"tests/ui/if_then_some_else_none.rs","byte_start":251,"byte_end":371,"line_start":13,"line_end":18,"column_start":13,"column_end":6,"is_primary":true,"text":[{"text":"    let _ = if matches!(true, true) {","highlight_start":13,"highlight_end":38},{"text":"        println!(\"true!\");","highlight_start":1,"highlight_end":27},{"text":"        Some(matches!(true, false))","highlight_start":1,"highlight_end":36},{"text":"    } else {","highlight_start":1,"highlight_end":13},{"text":"        None","highlight_start":1,"highlight_end":13},{"text":"    };","highlight_start":1,"highlight_end":6}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[{"message":"consider using `bool::then` like: `(matches!(true, true).then(|| { /* snippet */ matches!(true, false })`","code":null,"level":"help","spans":[],"children":[],"rendered":null}],"rendered":"error: this could be simplified with `bool::then`\n  --> tests/ui/if_then_some_else_none.rs:13:13\n   |\nLL |       let _ = if matches!(true, true) {\n   |  _____________^\nLL | |         println!(\"true!\");\nLL | |         Some(matches!(true, false))\nLL | |     } else {\nLL | |         None\nLL | |     };\n   | |_____^\n   |\n   = help: consider using `bool::then` like: `(matches!(true, true).then(|| { /* snippet */ matches!(true, false })`\n\n"}
{"message":"this could be simplified with `bool::then_some`","code":{"code":"clippy::if_then_some_else_none","explanation":null},"level":"error","spans":[{"file_name":"tests/ui/if_then_some_else_none.rs","byte_start":504,"byte_end":539,"line_start":22,"line_end":22,"column_start":28,"column_end":63,"is_primary":true,"text":[{"text":"    let _ = x.and_then(|o| if o < 32 { Some(o) } else { None });","highlight_start":28,"highlight_end":63}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[{"message":"consider using `bool::then_some` like: `(o < 32).then_some(o)`","code":null,"level":"help","spans":[],"children":[],"rendered":null}],"rendered":"error: this could be simplified with `bool::then_some`\n  --> tests/ui/if_then_some_else_none.rs:22:28\n   |\nLL |     let _ = x.and_then(|o| if o < 32 { Some(o) } else { None });\n   |                            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n   |\n   = help: consider using `bool::then_some` like: `(o < 32).then_some(o)`\n\n"}
{"message":"this could be simplified with `bool::then_some`","code":{"code":"clippy::if_then_some_else_none","explanation":null},"level":"error","spans":[{"file_name":"tests/ui/if_then_some_else_none.rs","byte_start":650,"byte_end":681,"line_start":26,"line_end":26,"column_start":13,"column_end":44,"is_primary":true,"text":[{"text":"    let _ = if !x { Some(0) } else { None };","highlight_start":13,"highlight_end":44}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[{"message":"consider using `bool::then_some` like: `(!x).then_some(0)`","code":null,"level":"help","spans":[],"children":[],"rendered":null}],"rendered":"error: this could be simplified with `bool::then_some`\n  --> tests/ui/if_then_some_else_none.rs:26:13\n   |\nLL |     let _ = if !x { Some(0) } else { None };\n   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n   |\n   = help: consider using `bool::then_some` like: `(!x).then_some(0)`\n\n"}
{"message":"this could be simplified with `bool::then`","code":{"code":"clippy::if_then_some_else_none","explanation":null},"level":"error","spans":[{"file_name":"tests/ui/if_then_some_else_none.rs","byte_start":2064,"byte_end":2151,"line_start":81,"line_end":86,"column_start":13,"column_end":6,"is_primary":true,"text":[{"text":"    let _ = if foo() {","highlight_start":13,"highlight_end":23},{"text":"        println!(\"true!\");","highlight_start":1,"highlight_end":27},{"text":"        Some(150)","highlight_start":1,"highlight_end":18},{"text":"    } else {","highlight_start":1,"highlight_end":13},{"text":"        None","highlight_start":1,"highlight_end":13},{"text":"    };","highlight_start":1,"highlight_end":6}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[{"message":"consider using `bool::then` like: `foo().then(|| { /* snippet */ 150 })`","code":null,"level":"help","spans":[],"children":[],"rendered":null}],"rendered":"error: this could be simplified with `bool::then`\n  --> tests/ui/if_then_some_else_none.rs:81:13\n   |\nLL |       let _ = if foo() {\n   |  _____________^\nLL | |         println!(\"true!\");\nLL | |         Some(150)\nLL | |     } else {\nLL | |         None\nLL | |     };\n   | |_____^\n   |\n   = help: consider using `bool::then` like: `foo().then(|| { /* snippet */ 150 })`\n\n"}

------------------------------------------



error: failed to compile fixed code
status: exit status: 1
command: "/checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/x86_64-unknown-linux-gnu/release/clippy-driver" "tests/ui/equatable_if_let.fixed" "-L" "/checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/x86_64-unknown-linux-gnu/release/test/ui" "--target=x86_64-unknown-linux-gnu" "--error-format" "json" "-C" "prefer-dynamic" "-o" "/checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/x86_64-unknown-linux-gnu/release/test/ui/equatable_if_let.stage-id" "--emit=metadata" "-Dwarnings" "-Zui-testing" "-L" "dependency=/checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/x86_64-unknown-linux-gnu/release/deps" "-L" "dependency=/checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/release/deps" "--extern" "serde=/checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/x86_64-unknown-linux-gnu/release/deps/libserde-426d0cca316c35ed.rlib" "--extern" "tokio=/checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/x86_64-unknown-linux-gnu/release/deps/libtokio-e0524b7e2611e851.rlib" "--extern" "derive_new=/checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/release/deps/libderive_new-c7b10cf951dd44cc.so" "--extern" "regex=/checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/x86_64-unknown-linux-gnu/release/deps/libregex-619ac20e364f2b2c.rlib" "--extern" "clippy_lints=/checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/x86_64-unknown-linux-gnu/release/deps/libclippy_lints-25f7a610b7fafce5.rlib" "--extern" "rustc_semver=/checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/x86_64-unknown-linux-gnu/release/deps/librustc_semver-963bbd3f89834643.rlib" "--extern" "itertools=/checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/x86_64-unknown-linux-gnu/release/deps/libitertools-b6f83e8bf7b1d2e3.rlib" "--extern" "parking_lot=/checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/x86_64-unknown-linux-gnu/release/deps/libparking_lot-64c69a0c8d27494b.rlib" "--extern" "syn=/checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/x86_64-unknown-linux-gnu/release/deps/libsyn-bd373ba7cdb9c07f.rlib" "--extern" "clippy_utils=/checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/x86_64-unknown-linux-gnu/release/deps/libclippy_utils-2d8078885e283441.rlib" "--extern" "serde_derive=/checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/release/deps/libserde_derive-a36a3333b2a93763.so" "--extern" "if_chain=/checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/x86_64-unknown-linux-gnu/release/deps/libif_chain-03f75cdc6d4d3afc.rlib" "--extern" "futures=/checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/x86_64-unknown-linux-gnu/release/deps/libfutures-2c5d99d6327d1048.rlib" "--extern" "quote=/checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/x86_64-unknown-linux-gnu/release/deps/libquote-3035b3cfcafc3b31.rlib" "--edition=2021" "--crate-name=fixed" "-L" "/checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/x86_64-unknown-linux-gnu/release/test/ui/equatable_if_let.stage-id.aux"
------------------------------------------

------------------------------------------
stderr:
stderr:
------------------------------------------
{"message":"match expression looks like `matches!` macro","code":{"code":"clippy::match_like_matches_macro","explanation":null},"level":"error","spans":[{"file_name":"tests/ui/equatable_if_let.fixed","byte_start":1648,"byte_end":1696,"line_start":80,"line_end":80,"column_start":8,"column_end":56,"is_primary":true,"text":[{"text":"    if matches!(h, NoPartialEqStruct { a: 2, b: false }) {}","highlight_start":8,"highlight_end":56}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[{"message":"`-D clippy::match-like-matches-macro` implied by `-D warnings`","code":null,"level":"note","spans":[],"children":[],"rendered":null},{"message":"try this","code":null,"level":"help","spans":[{"file_name":"tests/ui/equatable_if_let.fixed","byte_start":1648,"byte_end":1696,"line_start":80,"line_end":80,"column_start":8,"column_end":56,"is_primary":true,"text":[{"text":"    if matches!(h, NoPartialEqStruct { a: 2, b: false }) {}","highlight_start":8,"highlight_end":56}],"label":null,"suggested_replacement":"matches!(h, NoPartialEqStruct { a: 2, b: false })","suggestion_applicability":"MaybeIncorrect","expansion":null}],"children":[],"rendered":null}],"rendered":"error: match expression looks like `matches!` macro\n  --> tests/ui/equatable_if_let.fixed:80:8\n   |\nLL |     if matches!(h, NoPartialEqStruct { a: 2, b: false }) {}\n   |        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `matches!(h, NoPartialEqStruct { a: 2, b: false })`\n   |\n   = note: `-D clippy::match-like-matches-macro` implied by `-D warnings`\n\n"}

------------------------------------------

diff of stderr:
diff of stderr:

 error: manual check for common ascii range
-  --> $DIR/manual_is_ascii_check.rs:7:13
-   |
-LL |     assert!(matches!('x', 'a'..='z'));
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `'x'.is_ascii_lowercase()`
-   |
-   = note: `-D clippy::manual-is-ascii-check` implied by `-D warnings`
-error: manual check for common ascii range
-  --> $DIR/manual_is_ascii_check.rs:8:13
-   |
-   |
-LL |     assert!(matches!('X', 'A'..='Z'));
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `'X'.is_ascii_uppercase()`
-error: manual check for common ascii range
-  --> $DIR/manual_is_ascii_check.rs:9:13
-   |
-   |
-LL |     assert!(matches!(b'x', b'a'..=b'z'));
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `b'x'.is_ascii_lowercase()`
-error: manual check for common ascii range
-  --> $DIR/manual_is_ascii_check.rs:10:13
-   |
-   |
-LL |     assert!(matches!(b'X', b'A'..=b'Z'));
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `b'X'.is_ascii_uppercase()`
-error: manual check for common ascii range
-  --> $DIR/manual_is_ascii_check.rs:13:13
-   |
-   |
-LL |     assert!(matches!(num, '0'..='9'));
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `num.is_ascii_digit()`
-error: manual check for common ascii range
-  --> $DIR/manual_is_ascii_check.rs:14:13
-   |
-   |
-LL |     assert!(matches!(b'1', b'0'..=b'9'));
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `b'1'.is_ascii_digit()`
-error: manual check for common ascii range
-  --> $DIR/manual_is_ascii_check.rs:15:13
-   |
-   |
-LL |     assert!(matches!('x', 'A'..='Z' | 'a'..='z'));
-   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `'x'.is_ascii_alphabetic()`
-error: manual check for common ascii range
   --> $DIR/manual_is_ascii_check.rs:19:5
    |
    |
 LL |     (b'0'..=b'9').contains(&b'0');
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `b'0'.is_ascii_digit()`
+   |
+   = note: `-D clippy::manual-is-ascii-check` implied by `-D warnings`
 error: manual check for common ascii range
   --> $DIR/manual_is_ascii_check.rs:20:5
    |
    |
 LL |     (b'a'..=b'z').contains(&b'a');
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `b'a'.is_ascii_lowercase()`
 error: manual check for common ascii range
   --> $DIR/manual_is_ascii_check.rs:21:5
    |
    |
 LL |     (b'A'..=b'Z').contains(&b'A');
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `b'A'.is_ascii_uppercase()`

@est31
Copy link
Member

est31 commented May 19, 2023

Macros are now being expanded to their ExprKind counterpart if they contain a supported built-in in parse_expr_path_start so anything like bar!( ... ) will be expanded to ExprKind::BAR( ... ) instead of ExprKind::MacCall( ... ).

I don't think this is needed at all. It is better to have the macro be a normal macro_rules or 2.0 macro instead.

Comment on lines +240 to +242
ExprKind::Matches(expr, _, _) => {
self.manage_cond_expr(expr);
}
Copy link
Contributor Author

@c410-f3r c410-f3r May 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@est31 Someway, somehow it is necessary to have an indicator pointing to the scrutinee expression of matches! in order to capture its content and the use of regular declarative macros makes matches! in this context a opaque ExprKind::MacCall which can't be captured without further processing. In regards to macro 2.0, I personally don't know how it can help here.

Copy link
Member

@est31 est31 May 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm yeah then I have misunderstood and this PR is not an use case for builtin # really. I'm sorry for having suggested it earlier.

Your PR currently hardcodes the matches! macro, meaning that it can't be shadowed or used via a different name. The same is true for offset_of! which currently isn't part of the prelude, and which is also unstable, but this PR exposes it without feature gating. Also the builtin equivalent of offset_of is not meant to be used directly as it doesnt have as good error reporting or recovery (and it tolerates some things that are caught one layer higher by the macro_rules macro).

So this is not an approach that reviewers will merge I think.

If you want to do hardcoding, you could confine it at least to what happens inside the assert!() call and just check the path of the MacCall and if it only has one item and that one is matches, then processing the content. This would also not work via shadowing and use with a different name, but would be only confined to uses inside assert!(), not everywhere, and would not touch offset_of. It would also not need a new AST item which should still be added sparingly.

That would not be perfect but many times better than what there is now in this PR.

If you want to be a bit more advanced you could maybe try to resolve the macro first, then check if it is the same SyntaxExtension as the one of a pre-stored matches syntax extension.

The correct way of using builtin # here is to both turn matches and assert into builtin # constructs, but personally I'm more of a fan of re-parsing the same info multiple times.

Copy link
Contributor Author

@c410-f3r c410-f3r May 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @est31

Hum... Looks like a way forward is unclear, three different approaches were already presented (including adhoc processing inside assert as you commented) without much apparent success.

To avoid further back-and-forth, I think it is better to wait for @petrochenkov. Personally, I am happy with any strategy as long as matches! are expanded :)

If you want to be a bit more advanced you could maybe try to resolve the macro first, then check if it is the same SyntaxExtension as the one of a pre-stored matches syntax extension.

I hadn't thought that, looks promising!

@petrochenkov
Copy link
Contributor

I'm still skeptical about this whole idea.
Yes, it would be nice for assert!(matches!(...)) to report something nicer looking, but not at this cost.

@petrochenkov
Copy link
Contributor

Maybe we shouldn't even pretend resolving matches!(...) inside the assert!(...) and simply document that assert!(matches!(...)) (literally the sequence of tokens IDENT(matches) ! PARENTHESIZED_GROUP) has special meaning in the language accepted by the assert macro.

Is that the same thing as @est31 suggests in #110382 (comment)?
In that case we shouldn't do the "If you want to be a bit more advanced" part, but rather do something dumb but well-documented.

@petrochenkov petrochenkov added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels May 22, 2023
@petrochenkov
Copy link
Contributor

Another alternative is to "just use assert_matches!(...)".
It's even less noisy than assert!(matches!(...)).

The only problem is backward incompatibility of adding assert_matches to the prelude, but it can be resolved by adding it only on new editions.

@est31
Copy link
Member

est31 commented May 22, 2023

Is that the same thing as @est31 suggests in #110382 (comment)?

Yes, and indeed without the "bit more advanced" part.

No change of the AST is needed for that, nor is it using special abilities of builtin macros that aren't available to ordinary proc-macros. It will weird out people a little bit I guess when they shadow the matches!() macro and it doesn't work inside assert!(), but I think it's worth that cost. Shadowing macros should be rare anyways.

@c410-f3r
Copy link
Contributor Author

For the record, the expansion of matches! is useful to see variants of a theoretical Error enum composed by third-parties structures that do not implement PartialEq (which is almost always the case).

pub enum MyError {
  ExternalBarError(BarErrorStructure),
  ExternalFooError(FooErrorStructure),
  MyCustomErrorVariant,
  ...
}

fn do_some_testing(error: &MyError) {
  // It is not possible to use `assert_eq!` because third-parties don't implement `PartialEq`
  assert!(matches!(error, MyError::MyCustomErrorVariant));
}

It is possible to restrain the parsing of matches! inside assert! or use assert_matches! instead but it is very likely that people will request the expansion of more macros with the future stabilization of generic_assert.

With all discussion so far, I think the situation needs more investigation in order to create an acceptable and reasonable solution. Right? Should we then postpone or go ahead with a special assert!(matches!(..)) exact-sequence case?

@est31
Copy link
Member

est31 commented May 25, 2023

it is very likely that people will request the expansion of more macros with the future stabilization of generic_assert.

Which macros would those be? I cannot think of many macros in the standard library that have a bool output.

I could think that one wants assert!(matches!(foo, Some(_)) || matches!(bar, Ok(_))) to work, but in theory this should be easily implemented via an AST visitor, something that's available even to proc macros that use syn. I suppose for assert this would be found in the manage_cond_expr function that your PR is already modifying.

@c410-f3r
Copy link
Contributor Author

c410-f3r commented May 25, 2023

Ooopss, sorry. I was expanding the term to anything that accepts $fragment:expr but in reality users can move any potential macro outside of assert! because paths are captured.

  let elem = 1;
  // assert!(vec![elem, 2] == vec![]);
  let v = vec![elem, 2];
  asset!(v == vec![]);

  // Other potential examples are `env!`, `format!` and `offset_of!`.

@bors
Copy link
Contributor

bors commented May 27, 2023

☔ The latest upstream changes (presumably #111928) made this pull request unmergeable. Please resolve the merge conflicts.

@c410-f3r
Copy link
Contributor Author

Well, I guess further progress will require more discussion or even a RFC. Hope the future holds better opportunities for this feature.

@c410-f3r c410-f3r closed this Jun 18, 2023
@fmease fmease added the F-generic_assert `#![feature(generic_assert)]` label Apr 8, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
F-generic_assert `#![feature(generic_assert)]` S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-libs Relevant to the library team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants