From 5b05a3bb5ae0535c3d5b6e2c52b9458ec85d6e48 Mon Sep 17 00:00:00 2001 From: Weihang Lo Date: Tue, 9 Apr 2024 18:07:47 -0400 Subject: [PATCH 1/2] test(rustfix): demonstrate duplicate suggestions --- .../everything/dedup-suggestions.fixed.rs | 17 +++++++ .../tests/everything/dedup-suggestions.json | 5 +++ .../tests/everything/dedup-suggestions.rs | 17 +++++++ tests/testsuite/fix.rs | 45 +++++++++++++++++++ 4 files changed, 84 insertions(+) create mode 100644 crates/rustfix/tests/everything/dedup-suggestions.fixed.rs create mode 100644 crates/rustfix/tests/everything/dedup-suggestions.json create mode 100644 crates/rustfix/tests/everything/dedup-suggestions.rs diff --git a/crates/rustfix/tests/everything/dedup-suggestions.fixed.rs b/crates/rustfix/tests/everything/dedup-suggestions.fixed.rs new file mode 100644 index 000000000..9542382df --- /dev/null +++ b/crates/rustfix/tests/everything/dedup-suggestions.fixed.rs @@ -0,0 +1,17 @@ +// This fixes rust-lang/rust#123304. +// If that lint stops emitting duplicate suggestions, +// we might need to find a substitution. +#![warn(unsafe_op_in_unsafe_fn)] + +macro_rules! foo { + ($x:ident) => { + pub unsafe fn $x() { unsafe { unsafe { + let _ = String::new().as_mut_vec(); + }}} + }; +} + +fn main() { + foo!(a); + foo!(b); +} diff --git a/crates/rustfix/tests/everything/dedup-suggestions.json b/crates/rustfix/tests/everything/dedup-suggestions.json new file mode 100644 index 000000000..74943efc8 --- /dev/null +++ b/crates/rustfix/tests/everything/dedup-suggestions.json @@ -0,0 +1,5 @@ +{"$message_type":"diagnostic","message":"call to unsafe function `std::string::String::as_mut_vec` is unsafe and requires unsafe block (error E0133)","code":{"code":"unsafe_op_in_unsafe_fn","explanation":null},"level":"warning","spans":[{"file_name":"./tests/everything/dedup-suggestions.rs","byte_start":254,"byte_end":280,"line_start":9,"line_end":9,"column_start":21,"column_end":47,"is_primary":true,"text":[{"text":" let _ = String::new().as_mut_vec();","highlight_start":21,"highlight_end":47}],"label":"call to unsafe function","suggested_replacement":null,"suggestion_applicability":null,"expansion":{"span":{"file_name":"./tests/everything/dedup-suggestions.rs","byte_start":318,"byte_end":325,"line_start":15,"line_end":15,"column_start":5,"column_end":12,"is_primary":false,"text":[{"text":" foo!(a);","highlight_start":5,"highlight_end":12}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null},"macro_decl_name":"foo!","def_site_span":{"file_name":"./tests/everything/dedup-suggestions.rs","byte_start":166,"byte_end":182,"line_start":6,"line_end":6,"column_start":1,"column_end":17,"is_primary":false,"text":[{"text":"macro_rules! foo {","highlight_start":1,"highlight_end":17}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}}}],"children":[{"message":"for more information, see issue #71668 ","code":null,"level":"note","spans":[],"children":[],"rendered":null},{"message":"consult the function's documentation for information on how to avoid undefined behavior","code":null,"level":"note","spans":[],"children":[],"rendered":null},{"message":"an unsafe function restricts its caller, but its body is safe by default","code":null,"level":"note","spans":[{"file_name":"./tests/everything/dedup-suggestions.rs","byte_start":213,"byte_end":231,"line_start":8,"line_end":8,"column_start":9,"column_end":27,"is_primary":true,"text":[{"text":" pub unsafe fn $x() {","highlight_start":9,"highlight_end":27}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":{"span":{"file_name":"./tests/everything/dedup-suggestions.rs","byte_start":318,"byte_end":325,"line_start":15,"line_end":15,"column_start":5,"column_end":12,"is_primary":false,"text":[{"text":" foo!(a);","highlight_start":5,"highlight_end":12}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null},"macro_decl_name":"foo!","def_site_span":{"file_name":"./tests/everything/dedup-suggestions.rs","byte_start":166,"byte_end":182,"line_start":6,"line_end":6,"column_start":1,"column_end":17,"is_primary":false,"text":[{"text":"macro_rules! foo {","highlight_start":1,"highlight_end":17}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}}}],"children":[],"rendered":null},{"message":"the lint level is defined here","code":null,"level":"note","spans":[{"file_name":"./tests/everything/dedup-suggestions.rs","byte_start":140,"byte_end":162,"line_start":4,"line_end":4,"column_start":9,"column_end":31,"is_primary":true,"text":[{"text":"#![warn(unsafe_op_in_unsafe_fn)]","highlight_start":9,"highlight_end":31}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}],"children":[],"rendered":null},{"message":"consider wrapping the function body in an unsafe block","code":null,"level":"help","spans":[{"file_name":"./tests/everything/dedup-suggestions.rs","byte_start":232,"byte_end":232,"line_start":8,"line_end":8,"column_start":28,"column_end":28,"is_primary":true,"text":[{"text":" pub unsafe fn $x() {","highlight_start":28,"highlight_end":28}],"label":null,"suggested_replacement":"{ unsafe ","suggestion_applicability":"MachineApplicable","expansion":{"span":{"file_name":"./tests/everything/dedup-suggestions.rs","byte_start":318,"byte_end":325,"line_start":15,"line_end":15,"column_start":5,"column_end":12,"is_primary":false,"text":[{"text":" foo!(a);","highlight_start":5,"highlight_end":12}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null},"macro_decl_name":"foo!","def_site_span":{"file_name":"./tests/everything/dedup-suggestions.rs","byte_start":166,"byte_end":182,"line_start":6,"line_end":6,"column_start":1,"column_end":17,"is_primary":false,"text":[{"text":"macro_rules! foo {","highlight_start":1,"highlight_end":17}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}}},{"file_name":"./tests/everything/dedup-suggestions.rs","byte_start":291,"byte_end":291,"line_start":10,"line_end":10,"column_start":10,"column_end":10,"is_primary":true,"text":[{"text":" }","highlight_start":10,"highlight_end":10}],"label":null,"suggested_replacement":"}","suggestion_applicability":"MachineApplicable","expansion":{"span":{"file_name":"./tests/everything/dedup-suggestions.rs","byte_start":318,"byte_end":325,"line_start":15,"line_end":15,"column_start":5,"column_end":12,"is_primary":false,"text":[{"text":" foo!(a);","highlight_start":5,"highlight_end":12}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null},"macro_decl_name":"foo!","def_site_span":{"file_name":"./tests/everything/dedup-suggestions.rs","byte_start":166,"byte_end":182,"line_start":6,"line_end":6,"column_start":1,"column_end":17,"is_primary":false,"text":[{"text":"macro_rules! foo {","highlight_start":1,"highlight_end":17}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}}}],"children":[],"rendered":null}],"rendered":"warning: call to unsafe function `std::string::String::as_mut_vec` is unsafe and requires unsafe block (error E0133)\n --> ./tests/everything/dedup-suggestions.rs:9:21\n |\n9 | let _ = String::new().as_mut_vec();\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^ call to unsafe function\n...\n15 | foo!(a);\n | ------- in this macro invocation\n |\n = note: for more information, see issue #71668 \n = note: consult the function's documentation for information on how to avoid undefined behavior\nnote: an unsafe function restricts its caller, but its body is safe by default\n --> ./tests/everything/dedup-suggestions.rs:8:9\n |\n8 | pub unsafe fn $x() {\n | ^^^^^^^^^^^^^^^^^^\n...\n15 | foo!(a);\n | ------- in this macro invocation\nnote: the lint level is defined here\n --> ./tests/everything/dedup-suggestions.rs:4:9\n |\n4 | #![warn(unsafe_op_in_unsafe_fn)]\n | ^^^^^^^^^^^^^^^^^^^^^^\n = note: this warning originates in the macro `foo` (in Nightly builds, run with -Z macro-backtrace for more info)\n\n"} +{"$message_type":"diagnostic","message":"call to unsafe function `std::string::String::as_mut_vec` is unsafe and requires unsafe block (error E0133)","code":{"code":"unsafe_op_in_unsafe_fn","explanation":null},"level":"warning","spans":[{"file_name":"./tests/everything/dedup-suggestions.rs","byte_start":254,"byte_end":280,"line_start":9,"line_end":9,"column_start":21,"column_end":47,"is_primary":true,"text":[{"text":" let _ = String::new().as_mut_vec();","highlight_start":21,"highlight_end":47}],"label":"call to unsafe function","suggested_replacement":null,"suggestion_applicability":null,"expansion":{"span":{"file_name":"./tests/everything/dedup-suggestions.rs","byte_start":331,"byte_end":338,"line_start":16,"line_end":16,"column_start":5,"column_end":12,"is_primary":false,"text":[{"text":" foo!(b);","highlight_start":5,"highlight_end":12}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null},"macro_decl_name":"foo!","def_site_span":{"file_name":"./tests/everything/dedup-suggestions.rs","byte_start":166,"byte_end":182,"line_start":6,"line_end":6,"column_start":1,"column_end":17,"is_primary":false,"text":[{"text":"macro_rules! foo {","highlight_start":1,"highlight_end":17}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}}}],"children":[{"message":"for more information, see issue #71668 ","code":null,"level":"note","spans":[],"children":[],"rendered":null},{"message":"consult the function's documentation for information on how to avoid undefined behavior","code":null,"level":"note","spans":[],"children":[],"rendered":null},{"message":"an unsafe function restricts its caller, but its body is safe by default","code":null,"level":"note","spans":[{"file_name":"./tests/everything/dedup-suggestions.rs","byte_start":213,"byte_end":231,"line_start":8,"line_end":8,"column_start":9,"column_end":27,"is_primary":true,"text":[{"text":" pub unsafe fn $x() {","highlight_start":9,"highlight_end":27}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":{"span":{"file_name":"./tests/everything/dedup-suggestions.rs","byte_start":331,"byte_end":338,"line_start":16,"line_end":16,"column_start":5,"column_end":12,"is_primary":false,"text":[{"text":" foo!(b);","highlight_start":5,"highlight_end":12}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null},"macro_decl_name":"foo!","def_site_span":{"file_name":"./tests/everything/dedup-suggestions.rs","byte_start":166,"byte_end":182,"line_start":6,"line_end":6,"column_start":1,"column_end":17,"is_primary":false,"text":[{"text":"macro_rules! foo {","highlight_start":1,"highlight_end":17}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}}}],"children":[],"rendered":null},{"message":"consider wrapping the function body in an unsafe block","code":null,"level":"help","spans":[{"file_name":"./tests/everything/dedup-suggestions.rs","byte_start":232,"byte_end":232,"line_start":8,"line_end":8,"column_start":28,"column_end":28,"is_primary":true,"text":[{"text":" pub unsafe fn $x() {","highlight_start":28,"highlight_end":28}],"label":null,"suggested_replacement":"{ unsafe ","suggestion_applicability":"MachineApplicable","expansion":{"span":{"file_name":"./tests/everything/dedup-suggestions.rs","byte_start":331,"byte_end":338,"line_start":16,"line_end":16,"column_start":5,"column_end":12,"is_primary":false,"text":[{"text":" foo!(b);","highlight_start":5,"highlight_end":12}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null},"macro_decl_name":"foo!","def_site_span":{"file_name":"./tests/everything/dedup-suggestions.rs","byte_start":166,"byte_end":182,"line_start":6,"line_end":6,"column_start":1,"column_end":17,"is_primary":false,"text":[{"text":"macro_rules! foo {","highlight_start":1,"highlight_end":17}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}}},{"file_name":"./tests/everything/dedup-suggestions.rs","byte_start":291,"byte_end":291,"line_start":10,"line_end":10,"column_start":10,"column_end":10,"is_primary":true,"text":[{"text":" }","highlight_start":10,"highlight_end":10}],"label":null,"suggested_replacement":"}","suggestion_applicability":"MachineApplicable","expansion":{"span":{"file_name":"./tests/everything/dedup-suggestions.rs","byte_start":331,"byte_end":338,"line_start":16,"line_end":16,"column_start":5,"column_end":12,"is_primary":false,"text":[{"text":" foo!(b);","highlight_start":5,"highlight_end":12}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null},"macro_decl_name":"foo!","def_site_span":{"file_name":"./tests/everything/dedup-suggestions.rs","byte_start":166,"byte_end":182,"line_start":6,"line_end":6,"column_start":1,"column_end":17,"is_primary":false,"text":[{"text":"macro_rules! foo {","highlight_start":1,"highlight_end":17}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}}}],"children":[],"rendered":null}],"rendered":"warning: call to unsafe function `std::string::String::as_mut_vec` is unsafe and requires unsafe block (error E0133)\n --> ./tests/everything/dedup-suggestions.rs:9:21\n |\n9 | let _ = String::new().as_mut_vec();\n | ^^^^^^^^^^^^^^^^^^^^^^^^^^ call to unsafe function\n...\n16 | foo!(b);\n | ------- in this macro invocation\n |\n = note: for more information, see issue #71668 \n = note: consult the function's documentation for information on how to avoid undefined behavior\nnote: an unsafe function restricts its caller, but its body is safe by default\n --> ./tests/everything/dedup-suggestions.rs:8:9\n |\n8 | pub unsafe fn $x() {\n | ^^^^^^^^^^^^^^^^^^\n...\n16 | foo!(b);\n | ------- in this macro invocation\n = note: this warning originates in the macro `foo` (in Nightly builds, run with -Z macro-backtrace for more info)\n\n"} +{"$message_type":"diagnostic","message":"function `a` is never used","code":{"code":"dead_code","explanation":null},"level":"warning","spans":[{"file_name":"./tests/everything/dedup-suggestions.rs","byte_start":323,"byte_end":324,"line_start":15,"line_end":15,"column_start":10,"column_end":11,"is_primary":true,"text":[{"text":" foo!(a);","highlight_start":10,"highlight_end":11}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":{"span":{"file_name":"./tests/everything/dedup-suggestions.rs","byte_start":318,"byte_end":325,"line_start":15,"line_end":15,"column_start":5,"column_end":12,"is_primary":false,"text":[{"text":" foo!(a);","highlight_start":5,"highlight_end":12}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null},"macro_decl_name":"foo!","def_site_span":{"file_name":"./tests/everything/dedup-suggestions.rs","byte_start":166,"byte_end":182,"line_start":6,"line_end":6,"column_start":1,"column_end":17,"is_primary":false,"text":[{"text":"macro_rules! foo {","highlight_start":1,"highlight_end":17}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}}}],"children":[{"message":"`#[warn(dead_code)]` on by default","code":null,"level":"note","spans":[],"children":[],"rendered":null}],"rendered":"warning: function `a` is never used\n --> ./tests/everything/dedup-suggestions.rs:15:10\n |\n15 | foo!(a);\n | ^\n |\n = note: `#[warn(dead_code)]` on by default\n = note: this warning originates in the macro `foo` (in Nightly builds, run with -Z macro-backtrace for more info)\n\n"} +{"$message_type":"diagnostic","message":"function `b` is never used","code":{"code":"dead_code","explanation":null},"level":"warning","spans":[{"file_name":"./tests/everything/dedup-suggestions.rs","byte_start":336,"byte_end":337,"line_start":16,"line_end":16,"column_start":10,"column_end":11,"is_primary":true,"text":[{"text":" foo!(b);","highlight_start":10,"highlight_end":11}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":{"span":{"file_name":"./tests/everything/dedup-suggestions.rs","byte_start":331,"byte_end":338,"line_start":16,"line_end":16,"column_start":5,"column_end":12,"is_primary":false,"text":[{"text":" foo!(b);","highlight_start":5,"highlight_end":12}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null},"macro_decl_name":"foo!","def_site_span":{"file_name":"./tests/everything/dedup-suggestions.rs","byte_start":166,"byte_end":182,"line_start":6,"line_end":6,"column_start":1,"column_end":17,"is_primary":false,"text":[{"text":"macro_rules! foo {","highlight_start":1,"highlight_end":17}],"label":null,"suggested_replacement":null,"suggestion_applicability":null,"expansion":null}}}],"children":[],"rendered":"warning: function `b` is never used\n --> ./tests/everything/dedup-suggestions.rs:16:10\n |\n16 | foo!(b);\n | ^\n |\n = note: this warning originates in the macro `foo` (in Nightly builds, run with -Z macro-backtrace for more info)\n\n"} +{"$message_type":"diagnostic","message":"4 warnings emitted","code":null,"level":"warning","spans":[],"children":[],"rendered":"warning: 4 warnings emitted\n\n"} diff --git a/crates/rustfix/tests/everything/dedup-suggestions.rs b/crates/rustfix/tests/everything/dedup-suggestions.rs new file mode 100644 index 000000000..e8f57da17 --- /dev/null +++ b/crates/rustfix/tests/everything/dedup-suggestions.rs @@ -0,0 +1,17 @@ +// This fixes rust-lang/rust#123304. +// If that lint stops emitting duplicate suggestions, +// we might need to find a substitution. +#![warn(unsafe_op_in_unsafe_fn)] + +macro_rules! foo { + ($x:ident) => { + pub unsafe fn $x() { + let _ = String::new().as_mut_vec(); + } + }; +} + +fn main() { + foo!(a); + foo!(b); +} diff --git a/tests/testsuite/fix.rs b/tests/testsuite/fix.rs index fdabe792e..53014ae83 100644 --- a/tests/testsuite/fix.rs +++ b/tests/testsuite/fix.rs @@ -1897,3 +1897,48 @@ warning: `foo` (lib) generated 1 warning (run `cargo fix --lib -p foo` to apply ") .run(); } + +// This fixes rust-lang/rust#123304. +// If that lint stops emitting duplicate suggestions, +// we might need to find a substitution. +#[cargo_test] +fn fix_only_once_for_duplicates() { + let p = project() + .file( + "src/lib.rs", + r#" + #![warn(unsafe_op_in_unsafe_fn)] + + macro_rules! foo { + ($x:ident) => { + pub unsafe fn $x() { + let _ = String::new().as_mut_vec(); + } + }; + } + + foo!(a); + foo!(b); + "#, + ) + .build(); + + p.cargo("fix --allow-no-vcs") + .with_stderr_contains( + "\ +[CHECKING] foo v0.0.1 ([CWD]) +[FIXED] src/lib.rs (2 fixes) +", + ) + .with_stderr_contains("[WARNING] unnecessary `unsafe` block[..]") + .run(); + + assert_eq!( + p.read_file("src/lib.rs").matches("unsafe").count(), + 5, + "unsafe keyword in src/lib.rs:\n\ + 2 in lint name;\n\ + 1 from original unsafe fn;\n\ + 2 from newly-applied unsafe blocks" + ); +} From 95edc06e5bcfca0ca5e6c09d8843be8b60488165 Mon Sep 17 00:00:00 2001 From: Weihang Lo Date: Tue, 9 Apr 2024 18:10:11 -0400 Subject: [PATCH 2/2] fix(rustfix): dont apply same suggestion twice This assumes that if any of the machine applicable fixes in a diagnostic suggestion is a duplicate, we should see the entire suggestion as a duplicate. --- crates/rustfix/src/lib.rs | 11 +++++++++++ .../tests/everything/dedup-suggestions.fixed.rs | 4 ++-- src/cargo/ops/fix.rs | 17 ++++++++++++++--- tests/testsuite/fix.rs | 10 +++++----- 4 files changed, 32 insertions(+), 10 deletions(-) diff --git a/crates/rustfix/src/lib.rs b/crates/rustfix/src/lib.rs index 8af88cd50..2f6acaf30 100644 --- a/crates/rustfix/src/lib.rs +++ b/crates/rustfix/src/lib.rs @@ -253,8 +253,19 @@ impl CodeFix { /// Applies multiple `suggestions` to the given `code`. pub fn apply_suggestions(code: &str, suggestions: &[Suggestion]) -> Result { + let mut already_applied = HashSet::new(); let mut fix = CodeFix::new(code); for suggestion in suggestions.iter().rev() { + // This assumes that if any of the machine applicable fixes in + // a diagnostic suggestion is a duplicate, we should see the + // entire suggestion as a duplicate. + if suggestion + .solutions + .iter() + .any(|sol| !already_applied.insert(sol)) + { + continue; + } fix.apply(suggestion)?; } fix.finish() diff --git a/crates/rustfix/tests/everything/dedup-suggestions.fixed.rs b/crates/rustfix/tests/everything/dedup-suggestions.fixed.rs index 9542382df..cd3b8d153 100644 --- a/crates/rustfix/tests/everything/dedup-suggestions.fixed.rs +++ b/crates/rustfix/tests/everything/dedup-suggestions.fixed.rs @@ -5,9 +5,9 @@ macro_rules! foo { ($x:ident) => { - pub unsafe fn $x() { unsafe { unsafe { + pub unsafe fn $x() { unsafe { let _ = String::new().as_mut_vec(); - }}} + }} }; } diff --git a/src/cargo/ops/fix.rs b/src/cargo/ops/fix.rs index 703e118e9..6c34abc37 100644 --- a/src/cargo/ops/fix.rs +++ b/src/cargo/ops/fix.rs @@ -735,12 +735,23 @@ fn rustfix_and_fix( }); let mut fixed = CodeFix::new(&code); - // As mentioned above in `rustfix_crate`, we don't immediately warn - // about suggestions that fail to apply here, and instead we save them - // off for later processing. + let mut already_applied = HashSet::new(); for suggestion in suggestions.iter().rev() { + // This assumes that if any of the machine applicable fixes in + // a diagnostic suggestion is a duplicate, we should see the + // entire suggestion as a duplicate. + if suggestion + .solutions + .iter() + .any(|sol| !already_applied.insert(sol)) + { + continue; + } match fixed.apply(suggestion) { Ok(()) => fixed_file.fixes_applied += 1, + // As mentioned above in `rustfix_crate`, we don't immediately + // warn about suggestions that fail to apply here, and instead + // we save them off for later processing. Err(e) => fixed_file.errors_applying_fixes.push(e.to_string()), } } diff --git a/tests/testsuite/fix.rs b/tests/testsuite/fix.rs index 53014ae83..babdd3085 100644 --- a/tests/testsuite/fix.rs +++ b/tests/testsuite/fix.rs @@ -1924,21 +1924,21 @@ fn fix_only_once_for_duplicates() { .build(); p.cargo("fix --allow-no-vcs") - .with_stderr_contains( + .with_stderr( "\ [CHECKING] foo v0.0.1 ([CWD]) -[FIXED] src/lib.rs (2 fixes) +[FIXED] src/lib.rs (1 fix) +[FINISHED] `dev` profile [..] ", ) - .with_stderr_contains("[WARNING] unnecessary `unsafe` block[..]") .run(); assert_eq!( p.read_file("src/lib.rs").matches("unsafe").count(), - 5, + 4, "unsafe keyword in src/lib.rs:\n\ 2 in lint name;\n\ 1 from original unsafe fn;\n\ - 2 from newly-applied unsafe blocks" + 1 from newly-applied unsafe blocks" ); }