From 9f5bfe24eb254fbe3f745062a4c9ce1c76ff800f Mon Sep 17 00:00:00 2001 From: GnomedDev Date: Sat, 21 Sep 2024 12:46:08 +0100 Subject: [PATCH 01/87] Implement lint for regex::Regex compilation inside a loop --- CHANGELOG.md | 1 + clippy_lints/src/declared_lints.rs | 1 + clippy_lints/src/regex.rs | 62 +++++++++++++++++++++++++++++- tests/ui/regex.rs | 28 +++++++++++++- tests/ui/regex.stderr | 52 ++++++++++++++++++++++++- 5 files changed, 141 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e5d1688e4a72..c385405e08a84 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5873,6 +5873,7 @@ Released 2018-09-13 [`ref_in_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_in_deref [`ref_option_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_option_ref [`ref_patterns`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_patterns +[`regex_creation_in_loops`]: https://rust-lang.github.io/rust-clippy/master/index.html#regex_creation_in_loops [`regex_macro`]: https://rust-lang.github.io/rust-clippy/master/index.html#regex_macro [`renamed_function_params`]: https://rust-lang.github.io/rust-clippy/master/index.html#renamed_function_params [`repeat_once`]: https://rust-lang.github.io/rust-clippy/master/index.html#repeat_once diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 16c64830e70dd..25a224a47ff76 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -637,6 +637,7 @@ pub static LINTS: &[&crate::LintInfo] = &[ crate::ref_patterns::REF_PATTERNS_INFO, crate::reference::DEREF_ADDROF_INFO, crate::regex::INVALID_REGEX_INFO, + crate::regex::REGEX_CREATION_IN_LOOPS_INFO, crate::regex::TRIVIAL_REGEX_INFO, crate::repeat_vec_with_capacity::REPEAT_VEC_WITH_CAPACITY_INFO, crate::reserve_after_initialization::RESERVE_AFTER_INITIALIZATION_INFO, diff --git a/clippy_lints/src/regex.rs b/clippy_lints/src/regex.rs index f6ef02b7c2330..1671737db0181 100644 --- a/clippy_lints/src/regex.rs +++ b/clippy_lints/src/regex.rs @@ -55,6 +55,44 @@ declare_clippy_lint! { "trivial regular expressions" } +declare_clippy_lint! { + /// ### What it does + /// + /// Checks for [regex](https://crates.io/crates/regex) compilation inside a loop with a literal. + /// + /// ### Why is this bad? + /// + /// Compiling a regex is a much more expensive operation than using one, and a compiled regex can be used multiple times. + /// This is documented as an antipattern [on the regex documentation](https://docs.rs/regex/latest/regex/#avoid-re-compiling-regexes-especially-in-a-loop) + /// + /// ### Example + /// ```no_run + /// # let haystacks = [""]; + /// # const MY_REGEX: &str = "a.b"; + /// for haystack in haystacks { + /// let regex = regex::Regex::new(MY_REGEX).unwrap(); + /// if regex.is_match(haystack) { + /// // Perform operation + /// } + /// } + /// ``` + /// can be replaced with + /// ```no_run + /// # let haystacks = [""]; + /// # const MY_REGEX: &str = "a.b"; + /// let regex = regex::Regex::new(MY_REGEX).unwrap(); + /// for haystack in haystacks { + /// if regex.is_match(haystack) { + /// // Perform operation + /// } + /// } + /// ``` + #[clippy::version = "1.83.0"] + pub REGEX_CREATION_IN_LOOPS, + perf, + "regular expression compilation performed in a loop" +} + #[derive(Copy, Clone)] enum RegexKind { Unicode, @@ -66,9 +104,10 @@ enum RegexKind { #[derive(Default)] pub struct Regex { definitions: DefIdMap, + loop_stack: Vec, } -impl_lint_pass!(Regex => [INVALID_REGEX, TRIVIAL_REGEX]); +impl_lint_pass!(Regex => [INVALID_REGEX, TRIVIAL_REGEX, REGEX_CREATION_IN_LOOPS]); impl<'tcx> LateLintPass<'tcx> for Regex { fn check_crate(&mut self, cx: &LateContext<'tcx>) { @@ -96,12 +135,33 @@ impl<'tcx> LateLintPass<'tcx> for Regex { && let Some(def_id) = path_def_id(cx, fun) && let Some(regex_kind) = self.definitions.get(&def_id) { + if let Some(&loop_span) = self.loop_stack.last() + && (matches!(arg.kind, ExprKind::Lit(_)) || const_str(cx, arg).is_some()) + { + span_lint_and_help( + cx, + REGEX_CREATION_IN_LOOPS, + fun.span, + "compiling a regex in a loop", + Some(loop_span), + "move the regex construction outside this loop", + ); + } + match regex_kind { RegexKind::Unicode => check_regex(cx, arg, true), RegexKind::UnicodeSet => check_set(cx, arg, true), RegexKind::Bytes => check_regex(cx, arg, false), RegexKind::BytesSet => check_set(cx, arg, false), } + } else if let ExprKind::Loop(_, _, _, span) = expr.kind { + self.loop_stack.push(span); + } + } + + fn check_expr_post(&mut self, _: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if matches!(expr.kind, ExprKind::Loop(..)) { + self.loop_stack.pop(); } } } diff --git a/tests/ui/regex.rs b/tests/ui/regex.rs index 4fb6c08bb4495..3352239892c53 100644 --- a/tests/ui/regex.rs +++ b/tests/ui/regex.rs @@ -5,7 +5,7 @@ clippy::needless_borrow, clippy::needless_borrows_for_generic_args )] -#![warn(clippy::invalid_regex, clippy::trivial_regex)] +#![warn(clippy::invalid_regex, clippy::trivial_regex, clippy::regex_creation_in_loops)] extern crate regex; @@ -118,7 +118,33 @@ fn trivial_regex() { let _ = BRegex::new(r"\b{start}word\b{end}"); } +fn regex_creation_in_loops() { + loop { + let regex = Regex::new("a.b"); + //~^ ERROR: compiling a regex in a loop + let regex = BRegex::new("a.b"); + //~^ ERROR: compiling a regex in a loop + #[allow(clippy::regex_creation_in_loops)] + let allowed_regex = Regex::new("a.b"); + + if true { + let regex = Regex::new("a.b"); + //~^ ERROR: compiling a regex in a loop + } + + for _ in 0..10 { + let nested_regex = Regex::new("a.b"); + //~^ ERROR: compiling a regex in a loop + } + } + + for i in 0..10 { + let dependant_regex = Regex::new(&format!("{i}")); + } +} + fn main() { syntax_error(); trivial_regex(); + regex_creation_in_loops(); } diff --git a/tests/ui/regex.stderr b/tests/ui/regex.stderr index e936208d8d7b6..38e14b1631678 100644 --- a/tests/ui/regex.stderr +++ b/tests/ui/regex.stderr @@ -195,5 +195,55 @@ LL | let binary_trivial_empty = BRegex::new("^$"); | = help: consider using `str::is_empty` -error: aborting due to 24 previous errors +error: compiling a regex in a loop + --> tests/ui/regex.rs:123:21 + | +LL | let regex = Regex::new("a.b"); + | ^^^^^^^^^^ + | +help: move the regex construction outside this loop + --> tests/ui/regex.rs:122:5 + | +LL | loop { + | ^^^^ + = note: `-D clippy::regex-creation-in-loops` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::regex_creation_in_loops)]` + +error: compiling a regex in a loop + --> tests/ui/regex.rs:125:21 + | +LL | let regex = BRegex::new("a.b"); + | ^^^^^^^^^^^ + | +help: move the regex construction outside this loop + --> tests/ui/regex.rs:122:5 + | +LL | loop { + | ^^^^ + +error: compiling a regex in a loop + --> tests/ui/regex.rs:131:25 + | +LL | let regex = Regex::new("a.b"); + | ^^^^^^^^^^ + | +help: move the regex construction outside this loop + --> tests/ui/regex.rs:122:5 + | +LL | loop { + | ^^^^ + +error: compiling a regex in a loop + --> tests/ui/regex.rs:136:32 + | +LL | let nested_regex = Regex::new("a.b"); + | ^^^^^^^^^^ + | +help: move the regex construction outside this loop + --> tests/ui/regex.rs:135:9 + | +LL | for _ in 0..10 { + | ^^^^^^^^^^^^^^ + +error: aborting due to 28 previous errors From b522e7a944341d463bf9091bd72faffcc807d875 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Mon, 12 Aug 2024 20:39:25 +0200 Subject: [PATCH 02/87] Generate lint list in HTML directly instead of JS --- .github/deploy.sh | 1 - .gitignore | 1 + Cargo.toml | 2 + clippy_dev/src/serve.rs | 2 +- rinja.toml | 8 ++ tests/compile-test.rs | 33 +++++- .../{index.html => index_template.html} | 103 ++++++++--------- util/gh-pages/script.js | 108 ++++++++++-------- 8 files changed, 152 insertions(+), 106 deletions(-) create mode 100644 rinja.toml rename util/gh-pages/{index.html => index_template.html} (79%) diff --git a/.github/deploy.sh b/.github/deploy.sh index 5b4b4be4e36b5..6cebbb7801b0c 100644 --- a/.github/deploy.sh +++ b/.github/deploy.sh @@ -9,7 +9,6 @@ echo "Making the docs for master" mkdir out/master/ cp util/gh-pages/index.html out/master cp util/gh-pages/script.js out/master -cp util/gh-pages/lints.json out/master cp util/gh-pages/style.css out/master if [[ -n $TAG_NAME ]]; then diff --git a/.gitignore b/.gitignore index 181b71a658b9a..a7c25b29021fb 100644 --- a/.gitignore +++ b/.gitignore @@ -34,6 +34,7 @@ out # gh pages docs util/gh-pages/lints.json +util/gh-pages/index.html # rustfmt backups *.rs.bk diff --git a/Cargo.toml b/Cargo.toml index cf810798d8cc1..c7383520741be 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,6 +39,8 @@ toml = "0.7.3" walkdir = "2.3" filetime = "0.2.9" itertools = "0.12" +pulldown-cmark = "0.11" +rinja = { version = "0.3", default-features = false, features = ["config"] } # UI test dependencies clippy_utils = { path = "clippy_utils" } diff --git a/clippy_dev/src/serve.rs b/clippy_dev/src/serve.rs index cc14cd8dae69e..0216d884e2d59 100644 --- a/clippy_dev/src/serve.rs +++ b/clippy_dev/src/serve.rs @@ -19,7 +19,7 @@ pub fn run(port: u16, lint: Option) -> ! { }); loop { - if mtime("util/gh-pages/lints.json") < mtime("clippy_lints/src") { + if mtime("util/gh-pages/index.html") < mtime("clippy_lints/src") { Command::new(env::var("CARGO").unwrap_or("cargo".into())) .arg("collect-metadata") .spawn() diff --git a/rinja.toml b/rinja.toml new file mode 100644 index 0000000000000..5fa682788bd0e --- /dev/null +++ b/rinja.toml @@ -0,0 +1,8 @@ +[general] +dirs = ["util/gh-pages/"] +default_syntax = "mixed" + +[[syntax]] +name = "mixed" +expr_start = "{(" +expr_end = ")}" diff --git a/tests/compile-test.rs b/tests/compile-test.rs index af2aa51925777..0bd6ac677702d 100644 --- a/tests/compile-test.rs +++ b/tests/compile-test.rs @@ -8,6 +8,8 @@ use clippy_config::ClippyConfiguration; use clippy_lints::LintInfo; use clippy_lints::declared_lints::LINTS; use clippy_lints::deprecated_lints::{DEPRECATED, DEPRECATED_VERSION, RENAMED}; +use pulldown_cmark::{Options, Parser, html}; +use rinja::{Template, filters::Safe}; use serde::{Deserialize, Serialize}; use test_utils::IS_RUSTC_TEST_SUITE; use ui_test::custom_flags::Flag; @@ -385,6 +387,22 @@ fn ui_cargo_toml_metadata() { } } +#[derive(Template)] +#[template(path = "index_template.html")] +struct Renderer<'a> { + lints: &'a Vec, +} + +impl<'a> Renderer<'a> { + fn markdown(&self, input: &str) -> Safe { + let parser = Parser::new_ext(input, Options::all()); + let mut html_output = String::new(); + html::push_html(&mut html_output, parser); + // Oh deer, what a hack :O + Safe(html_output.replace(", @@ -559,4 +576,14 @@ impl LintMetadata { applicability: Applicability::Unspecified, } } + + fn applicability_str(&self) -> &str { + match self.applicability { + Applicability::MachineApplicable => "MachineApplicable", + Applicability::HasPlaceholders => "HasPlaceholders", + Applicability::MaybeIncorrect => "MaybeIncorrect", + Applicability::Unspecified => "Unspecified", + _ => panic!("needs to update this code"), + } + } } diff --git a/util/gh-pages/index.html b/util/gh-pages/index_template.html similarity index 79% rename from util/gh-pages/index.html rename to util/gh-pages/index_template.html index f3d7e504fdf80..ee134eaaa98e3 100644 --- a/util/gh-pages/index.html +++ b/util/gh-pages/index_template.html @@ -55,16 +55,8 @@

Clippy Lints

-
- - - - -
+
+
@@ -188,9 +180,7 @@

Clippy Lints

- +
- -
-
-

-
- {{lint.id}} - - - 📋 - -
+ {% for lint in lints %} +
+
+

+
+ {(lint.id)} + + + 📋 + +
-
- {{lint.group}} +
+ {(lint.group)} - {{lint.level}} + {(lint.level)} - - + -
-

-
+ + + +

+ + -
-
-
- -
- Applicability: - {{lint.applicability}} - (?) -
- -
- {{lint.group == "deprecated" ? "Deprecated" : "Added"}} in: - {{lint.version}} -
- - - -
- View Source +
+
{(markdown(lint.docs))}
+
+ {# Applicability #} +
+ Applicability: + {( lint.applicability_str() )} + (?) +
+ +
+ {% if lint.group == "deprecated" %}Deprecated{% else %} Added{% endif %} in: + {(lint.version)} +
+ + + +
-
- + + {% endfor %}
diff --git a/util/gh-pages/script.js b/util/gh-pages/script.js index 1a5330bc0e571..8942628d5da04 100644 --- a/util/gh-pages/script.js +++ b/util/gh-pages/script.js @@ -1,21 +1,4 @@ (function () { - const md = window.markdownit({ - html: true, - linkify: true, - typographer: true, - highlight: function (str, lang) { - if (lang && hljs.getLanguage(lang)) { - try { - return '
' +
-                        hljs.highlight(str, { language: lang, ignoreIllegals: true }).value +
-                        '
'; - } catch (__) {} - } - - return '
' + md.utils.escapeHtml(str) + '
'; - } - }); - function scrollToLint(lintId) { const target = document.getElementById(lintId); if (!target) { @@ -41,15 +24,6 @@ } angular.module("clippy", []) - .filter('markdown', function ($sce) { - return function (text) { - return $sce.trustAsHtml( - md.render(text || '') - // Oh deer, what a hack :O - .replace(' { + if (searchState.timeout !== null) { + clearTimeout(searchState.timeout); + searchState.timeout = null + } + }, + resetInputTimeout: () => { + searchState.clearInputTimeout(); + setTimeout(searchState.filterLints, 50); + }, + filterLints: () => { + let searchStr = searchState.value.trim().toLowerCase(); + if (searchStr.startsWith("clippy::")) { + searchStr = searchStr.slice(8); + } + const terms = searchStr.split(" "); + + onEachLazy(document.querySelectorAll("article"), lint => { + // Search by id + if (lint.id.indexOf(searchStr.replaceAll("-", "_")) !== -1) { + el.style.display = ""; + return; + } + // Search the description + // The use of `for`-loops instead of `foreach` enables us to return early + const docsLowerCase = lint.docs.toLowerCase(); + for (index = 0; index < terms.length; index++) { + // This is more likely and will therefore be checked first + if (docsLowerCase.indexOf(terms[index]) !== -1) { + continue; + } + + if (lint.id.indexOf(terms[index]) !== -1) { + continue; + } + + return false; + } + }); + }, +}; + +function handleInputChanged(event) { + if (event.target !== document.activeElement) { + return; + } + searchState.resetInputTimeout(); +} + function storeValue(settingName, value) { try { localStorage.setItem(`clippy-lint-list-${settingName}`, value); @@ -627,3 +643,5 @@ if (prefersDark.matches && !theme) { } let disableShortcuts = loadValue('disable-shortcuts') === "true"; document.getElementById("disable-shortcuts").checked = disableShortcuts; + +hljs.highlightAll(); From 9661ba07408c901d9521e27ee8453f769ab12744 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 13 Aug 2024 20:47:42 +0200 Subject: [PATCH 03/87] Update `cargo dev serve` command to look over the correct files --- clippy_dev/src/serve.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/clippy_dev/src/serve.rs b/clippy_dev/src/serve.rs index 0216d884e2d59..d367fefec619c 100644 --- a/clippy_dev/src/serve.rs +++ b/clippy_dev/src/serve.rs @@ -19,7 +19,9 @@ pub fn run(port: u16, lint: Option) -> ! { }); loop { - if mtime("util/gh-pages/index.html") < mtime("clippy_lints/src") { + let index_time = mtime("util/gh-pages/index.html"); + + if index_time < mtime("clippy_lints/src") || index_time < mtime("util/gh-pages/index_template.html") { Command::new(env::var("CARGO").unwrap_or("cargo".into())) .arg("collect-metadata") .spawn() From 0055cebaa3c0ab3cb0921efa20b18b0f291ae3ea Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 13 Aug 2024 20:47:54 +0200 Subject: [PATCH 04/87] Replace search with vanilla JS --- tests/compile-test.rs | 2 +- util/gh-pages/index_template.html | 29 +++++++------ util/gh-pages/script.js | 68 ++++++++++++------------------- 3 files changed, 43 insertions(+), 56 deletions(-) diff --git a/tests/compile-test.rs b/tests/compile-test.rs index 0bd6ac677702d..00627dc0bb1f9 100644 --- a/tests/compile-test.rs +++ b/tests/compile-test.rs @@ -10,7 +10,7 @@ use clippy_lints::declared_lints::LINTS; use clippy_lints::deprecated_lints::{DEPRECATED, DEPRECATED_VERSION, RENAMED}; use pulldown_cmark::{Options, Parser, html}; use rinja::{Template, filters::Safe}; -use serde::{Deserialize, Serialize}; +use serde::Deserialize; use test_utils::IS_RUSTC_TEST_SUITE; use ui_test::custom_flags::Flag; use ui_test::custom_flags::rustfix::RustfixMode; diff --git a/util/gh-pages/index_template.html b/util/gh-pages/index_template.html index ee134eaaa98e3..5baa03da2ea40 100644 --- a/util/gh-pages/index_template.html +++ b/util/gh-pages/index_template.html @@ -180,9 +180,9 @@

Clippy Lints

- + - @@ -199,22 +199,22 @@

Clippy Lints

{% for lint in lints %} -
+

{(lint.id)} - - + 📋
- {(lint.group)} + {(lint.group)} - {(lint.level)} + {(lint.level)} @@ -223,7 +223,7 @@

-
+
{(markdown(lint.docs))}
{# Applicability #} @@ -232,18 +232,21 @@

{( lint.applicability_str() )} (?)

- + {# Clippy version #}
{% if lint.group == "deprecated" %}Deprecated{% else %} Added{% endif %} in: {(lint.version)}
- + {# Open related issues #} - -
- View Source + + {# Jump to source #} + {% if let Some(id_location) = lint.id_location %} +
+ View Source + {% endif %}
diff --git a/util/gh-pages/script.js b/util/gh-pages/script.js index 8942628d5da04..eb563a0f242c4 100644 --- a/util/gh-pages/script.js +++ b/util/gh-pages/script.js @@ -361,42 +361,6 @@ return $scope.groups[lint.group]; }; - $scope.bySearch = function (lint, index, array) { - let searchStr = $scope.search; - // It can be `null` I haven't missed this value - if (searchStr == null) { - return true; - } - searchStr = searchStr.toLowerCase(); - if (searchStr.startsWith("clippy::")) { - searchStr = searchStr.slice(8); - } - - // Search by id - if (lint.id.indexOf(searchStr.replaceAll("-", "_")) !== -1) { - return true; - } - - // Search the description - // The use of `for`-loops instead of `foreach` enables us to return early - const terms = searchStr.split(" "); - const docsLowerCase = lint.docs.toLowerCase(); - for (index = 0; index < terms.length; index++) { - // This is more likely and will therefore be checked first - if (docsLowerCase.indexOf(terms[index]) !== -1) { - continue; - } - - if (lint.id.indexOf(terms[index]) !== -1) { - continue; - } - - return false; - } - - return true; - } - $scope.byApplicabilities = function (lint) { return $scope.applicabilities[lint.applicability]; }; @@ -472,6 +436,11 @@ function getQueryVariable(variable) { window.searchState = { timeout: null, inputElem: document.getElementById("search-input"), + lastSearch: '', + clearInput: () => { + searchState.inputElem.value = ""; + searchState.filterLints(); + }, clearInputTimeout: () => { if (searchState.timeout !== null) { clearTimeout(searchState.timeout); @@ -483,32 +452,38 @@ window.searchState = { setTimeout(searchState.filterLints, 50); }, filterLints: () => { - let searchStr = searchState.value.trim().toLowerCase(); + searchState.clearInputTimeout(); + + let searchStr = searchState.inputElem.value.trim().toLowerCase(); if (searchStr.startsWith("clippy::")) { searchStr = searchStr.slice(8); } + if (searchState.lastSearch === searchStr) { + return; + } + searchState.lastSearch = searchStr; const terms = searchStr.split(" "); onEachLazy(document.querySelectorAll("article"), lint => { // Search by id if (lint.id.indexOf(searchStr.replaceAll("-", "_")) !== -1) { - el.style.display = ""; + lint.style.display = ""; return; } // Search the description // The use of `for`-loops instead of `foreach` enables us to return early - const docsLowerCase = lint.docs.toLowerCase(); + const docsLowerCase = lint.textContent.toLowerCase(); for (index = 0; index < terms.length; index++) { // This is more likely and will therefore be checked first if (docsLowerCase.indexOf(terms[index]) !== -1) { - continue; + return; } if (lint.id.indexOf(terms[index]) !== -1) { - continue; + return; } - return false; + lint.style.display = "none"; } }); }, @@ -631,7 +606,16 @@ function generateSettings() { ); } +function generateSearch() { + searchState.inputElem.addEventListener("change", handleInputChanged); + searchState.inputElem.addEventListener("input", handleInputChanged); + searchState.inputElem.addEventListener("keydown", handleInputChanged); + searchState.inputElem.addEventListener("keyup", handleInputChanged); + searchState.inputElem.addEventListener("paste", handleInputChanged); +} + generateSettings(); +generateSearch(); // loading the theme after the initial load const prefersDark = window.matchMedia("(prefers-color-scheme: dark)"); From 574e3dd9226774d5a11cec900ec5322fbf7d6f28 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 13 Aug 2024 22:34:14 +0200 Subject: [PATCH 05/87] Finish porting lint functionalities to vanilla JS --- util/gh-pages/index_template.html | 19 ++- util/gh-pages/script.js | 230 +++++++++++++++++------------- util/gh-pages/style.css | 4 + 3 files changed, 140 insertions(+), 113 deletions(-) diff --git a/util/gh-pages/index_template.html b/util/gh-pages/index_template.html index 5baa03da2ea40..6571283cbf4db 100644 --- a/util/gh-pages/index_template.html +++ b/util/gh-pages/index_template.html @@ -199,14 +199,13 @@

Clippy Lints

{% for lint in lints %} -
-
+
{# Open related issues #} {# Jump to source #} {% if let Some(id_location) = lint.id_location %}
- View Source + View Source {% endif %}
@@ -315,7 +313,6 @@

- diff --git a/util/gh-pages/script.js b/util/gh-pages/script.js index eb563a0f242c4..902827b97e492 100644 --- a/util/gh-pages/script.js +++ b/util/gh-pages/script.js @@ -1,19 +1,4 @@ (function () { - function scrollToLint(lintId) { - const target = document.getElementById(lintId); - if (!target) { - return; - } - target.scrollIntoView(); - } - - function scrollToLintByURL($scope, $location) { - const removeListener = $scope.$on('ngRepeatFinished', function (ngRepeatFinishedEvent) { - scrollToLint($location.path().substring(1)); - removeListener(); - }); - } - function selectGroup($scope, selectedGroup) { const groups = $scope.groups; for (const group in groups) { @@ -365,39 +350,12 @@ return $scope.applicabilities[lint.applicability]; }; - // Show details for one lint - $scope.openLint = function (lint) { - $scope.open[lint.id] = true; - $location.path(lint.id); - }; - $scope.toggleExpansion = function(lints, isExpanded) { lints.forEach(lint => { $scope.open[lint.id] = isExpanded; }); } - $scope.copyToClipboard = function (lint) { - const clipboard = document.getElementById("clipboard-" + lint.id); - if (clipboard) { - let resetClipboardTimeout = null; - const resetClipboardIcon = clipboard.innerHTML; - - function resetClipboard() { - resetClipboardTimeout = null; - clipboard.innerHTML = resetClipboardIcon; - } - - navigator.clipboard.writeText("clippy::" + lint.id); - - clipboard.innerHTML = "✓"; - if (resetClipboardTimeout !== null) { - clearTimeout(resetClipboardTimeout); - } - resetClipboardTimeout = setTimeout(resetClipboard, 1000); - } - } - // Get data $scope.open = {}; $scope.loading = true; @@ -413,8 +371,6 @@ selectGroup($scope, selectedGroup.toLowerCase()); } - scrollToLintByURL($scope, $location); - setTimeout(function () { const el = document.getElementById('filter-input'); if (el) { el.focus() } @@ -433,6 +389,65 @@ function getQueryVariable(variable) { } } +function storeValue(settingName, value) { + try { + localStorage.setItem(`clippy-lint-list-${settingName}`, value); + } catch (e) { } +} + +function loadValue(settingName) { + return localStorage.getItem(`clippy-lint-list-${settingName}`); +} + +function setTheme(theme, store) { + let enableHighlight = false; + let enableNight = false; + let enableAyu = false; + + switch(theme) { + case "ayu": + enableAyu = true; + break; + case "coal": + case "navy": + enableNight = true; + break; + case "rust": + enableHighlight = true; + break; + default: + enableHighlight = true; + theme = "light"; + break; + } + + document.getElementsByTagName("body")[0].className = theme; + + document.getElementById("githubLightHighlight").disabled = enableNight || !enableHighlight; + document.getElementById("githubDarkHighlight").disabled = !enableNight && !enableAyu; + + document.getElementById("styleHighlight").disabled = !enableHighlight; + document.getElementById("styleNight").disabled = !enableNight; + document.getElementById("styleAyu").disabled = !enableAyu; + + if (store) { + storeValue("theme", theme); + } else { + document.getElementById(`theme-choice`).value = theme; + } +} + +// loading the theme after the initial load +const prefersDark = window.matchMedia("(prefers-color-scheme: dark)"); +const theme = loadValue('theme'); +if (prefersDark.matches && !theme) { + setTheme("coal", false); +} else { + setTheme(theme, false); +} +let disableShortcuts = loadValue('disable-shortcuts') === "true"; +document.getElementById("disable-shortcuts").checked = disableShortcuts; + window.searchState = { timeout: null, inputElem: document.getElementById("search-input"), @@ -486,6 +501,11 @@ window.searchState = { lint.style.display = "none"; } }); + if (searchStr.length > 0) { + window.location.hash = `/${searchStr}`; + } else { + window.location.hash = ''; + } }, }; @@ -496,54 +516,6 @@ function handleInputChanged(event) { searchState.resetInputTimeout(); } -function storeValue(settingName, value) { - try { - localStorage.setItem(`clippy-lint-list-${settingName}`, value); - } catch (e) { } -} - -function loadValue(settingName) { - return localStorage.getItem(`clippy-lint-list-${settingName}`); -} - -function setTheme(theme, store) { - let enableHighlight = false; - let enableNight = false; - let enableAyu = false; - - switch(theme) { - case "ayu": - enableAyu = true; - break; - case "coal": - case "navy": - enableNight = true; - break; - case "rust": - enableHighlight = true; - break; - default: - enableHighlight = true; - theme = "light"; - break; - } - - document.getElementsByTagName("body")[0].className = theme; - - document.getElementById("githubLightHighlight").disabled = enableNight || !enableHighlight; - document.getElementById("githubDarkHighlight").disabled = !enableNight && !enableAyu; - - document.getElementById("styleHighlight").disabled = !enableHighlight; - document.getElementById("styleNight").disabled = !enableNight; - document.getElementById("styleAyu").disabled = !enableAyu; - - if (store) { - storeValue("theme", theme); - } else { - document.getElementById(`theme-choice`).value = theme; - } -} - function handleShortcut(ev) { if (ev.ctrlKey || ev.altKey || ev.metaKey || disableShortcuts) { return; @@ -584,6 +556,52 @@ function onEachLazy(lazyArray, func) { } } +function expandLintId(lintId) { + searchState.inputElem.value = lintId; + searchState.filterLints(); + + // Expand the lint. + const lintElem = document.getElementById(lintId); + const isCollapsed = lintElem.classList.remove("collapsed"); + lintElem.querySelector(".label-doc-folding").innerText = "-"; +} + +// Show details for one lint +function openLint(event) { + event.preventDefault(); + event.stopPropagation(); + expandLintId(event.target.getAttribute("href").slice(1)); +} + +function expandLint(lintId) { + const lintElem = document.getElementById(lintId); + const isCollapsed = lintElem.classList.toggle("collapsed"); + lintElem.querySelector(".label-doc-folding").innerText = isCollapsed ? "+" : "-"; +} + +function copyToClipboard(event) { + event.preventDefault(); + event.stopPropagation(); + + const clipboard = event.target; + + let resetClipboardTimeout = null; + const resetClipboardIcon = clipboard.innerHTML; + + function resetClipboard() { + resetClipboardTimeout = null; + clipboard.innerHTML = resetClipboardIcon; + } + + navigator.clipboard.writeText("clippy::" + clipboard.parentElement.id.slice(5)); + + clipboard.innerHTML = "✓"; + if (resetClipboardTimeout !== null) { + clearTimeout(resetClipboardTimeout); + } + resetClipboardTimeout = setTimeout(resetClipboard, 1000); +} + function handleBlur(event) { const parent = document.getElementById("settings-dropdown"); if (!parent.contains(document.activeElement) && @@ -617,15 +635,23 @@ function generateSearch() { generateSettings(); generateSearch(); -// loading the theme after the initial load -const prefersDark = window.matchMedia("(prefers-color-scheme: dark)"); -const theme = loadValue('theme'); -if (prefersDark.matches && !theme) { - setTheme("coal", false); -} else { - setTheme(theme, false); +function scrollToLint(lintId) { + const target = document.getElementById(lintId); + if (!target) { + return; + } + target.scrollIntoView(); + expandLintId(lintId); } -let disableShortcuts = loadValue('disable-shortcuts') === "true"; -document.getElementById("disable-shortcuts").checked = disableShortcuts; -hljs.highlightAll(); +// If the page we arrive on has link to a given lint, we scroll to it. +function scrollToLintByURL() { + const lintId = window.location.hash.substring(2); + if (lintId.length > 0) { + scrollToLint(lintId); + } +} + +scrollToLintByURL(); + +onEachLazy(document.querySelectorAll("pre > code.language-rust"), el => hljs.highlightElement(el)); diff --git a/util/gh-pages/style.css b/util/gh-pages/style.css index a9485d511047a..9af566b5aa9e7 100644 --- a/util/gh-pages/style.css +++ b/util/gh-pages/style.css @@ -396,3 +396,7 @@ body { background: var(--bg); color: var(--fg); } + +article.collapsed .lint-docs { + display: none; +} \ No newline at end of file From f2193c680cc5e2d9cf711af0a9bb92b15887c892 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Tue, 13 Aug 2024 23:43:35 +0200 Subject: [PATCH 06/87] Completely remove angular and generate parts of settings in JS --- util/gh-pages/index_template.html | 80 ++--- util/gh-pages/script.js | 469 +++++------------------------- 2 files changed, 102 insertions(+), 447 deletions(-) diff --git a/util/gh-pages/index_template.html b/util/gh-pages/index_template.html index 6571283cbf4db..5a7af7d562ed8 100644 --- a/util/gh-pages/index_template.html +++ b/util/gh-pages/index_template.html @@ -25,7 +25,7 @@ - +
@@ -59,121 +59,90 @@

Clippy Lints

-
+
-
-
+
-
-
+
-
+
-
@@ -189,10 +158,10 @@

Clippy Lints

- -
@@ -315,7 +284,6 @@

- diff --git a/util/gh-pages/script.js b/util/gh-pages/script.js index 902827b97e492..6f506956e7c92 100644 --- a/util/gh-pages/script.js +++ b/util/gh-pages/script.js @@ -1,394 +1,3 @@ -(function () { - function selectGroup($scope, selectedGroup) { - const groups = $scope.groups; - for (const group in groups) { - if (groups.hasOwnProperty(group)) { - groups[group] = group === selectedGroup; - } - } - } - - angular.module("clippy", []) - .directive('filterDropdown', function ($document) { - return { - restrict: 'A', - link: function ($scope, $element, $attr) { - $element.bind('click', function (event) { - if (event.target.closest('button')) { - $element.toggleClass('open'); - } else { - $element.addClass('open'); - } - $element.addClass('open-recent'); - }); - - $document.bind('click', function () { - if (!$element.hasClass('open-recent')) { - $element.removeClass('open'); - } - $element.removeClass('open-recent'); - }) - } - } - }) - .directive('onFinishRender', function ($timeout) { - return { - restrict: 'A', - link: function (scope, element, attr) { - if (scope.$last === true) { - $timeout(function () { - scope.$emit(attr.onFinishRender); - }); - } - } - }; - }) - .controller("lintList", function ($scope, $http, $location, $timeout) { - // Level filter - const LEVEL_FILTERS_DEFAULT = {allow: true, warn: true, deny: true, none: true}; - $scope.levels = { ...LEVEL_FILTERS_DEFAULT }; - $scope.byLevels = function (lint) { - return $scope.levels[lint.level]; - }; - - const GROUPS_FILTER_DEFAULT = { - cargo: true, - complexity: true, - correctness: true, - nursery: true, - pedantic: true, - perf: true, - restriction: true, - style: true, - suspicious: true, - deprecated: false, - } - - $scope.groups = { - ...GROUPS_FILTER_DEFAULT - }; - - $scope.versionFilters = { - "≥": {enabled: false, minorVersion: null }, - "≤": {enabled: false, minorVersion: null }, - "=": {enabled: false, minorVersion: null }, - }; - - // Map the versionFilters to the query parameters in a way that is easier to work with in a URL - const versionFilterKeyMap = { - "≥": "gte", - "≤": "lte", - "=": "eq" - }; - const reverseVersionFilterKeyMap = Object.fromEntries( - Object.entries(versionFilterKeyMap).map(([key, value]) => [value, key]) - ); - - const APPLICABILITIES_FILTER_DEFAULT = { - MachineApplicable: true, - MaybeIncorrect: true, - HasPlaceholders: true, - Unspecified: true, - }; - - $scope.applicabilities = { - ...APPLICABILITIES_FILTER_DEFAULT - } - - // loadFromURLParameters retrieves filter settings from the URL parameters and assigns them - // to corresponding $scope variables. - function loadFromURLParameters() { - // Extract parameters from URL - const urlParameters = $location.search(); - - // Define a helper function that assigns URL parameters to a provided scope variable - const handleParameter = (parameter, scopeVariable, defaultValues) => { - if (urlParameters[parameter]) { - const items = urlParameters[parameter].split(','); - for (const key in scopeVariable) { - if (scopeVariable.hasOwnProperty(key)) { - scopeVariable[key] = items.includes(key); - } - } - } else if (defaultValues) { - for (const key in defaultValues) { - if (scopeVariable.hasOwnProperty(key)) { - scopeVariable[key] = defaultValues[key]; - } - } - } - }; - - handleParameter('levels', $scope.levels, LEVEL_FILTERS_DEFAULT); - handleParameter('groups', $scope.groups, GROUPS_FILTER_DEFAULT); - handleParameter('applicabilities', $scope.applicabilities, APPLICABILITIES_FILTER_DEFAULT); - - // Handle 'versions' parameter separately because it needs additional processing - if (urlParameters.versions) { - const versionFilters = urlParameters.versions.split(','); - for (const versionFilter of versionFilters) { - const [key, minorVersion] = versionFilter.split(':'); - const parsedMinorVersion = parseInt(minorVersion); - - // Map the key from the URL parameter to its original form - const originalKey = reverseVersionFilterKeyMap[key]; - - if (originalKey in $scope.versionFilters && !isNaN(parsedMinorVersion)) { - $scope.versionFilters[originalKey].enabled = true; - $scope.versionFilters[originalKey].minorVersion = parsedMinorVersion; - } - } - } - - // Load the search parameter from the URL path - const searchParameter = $location.path().substring(1); // Remove the leading slash - if (searchParameter) { - $scope.search = searchParameter; - $scope.open[searchParameter] = true; - scrollToLintByURL($scope, $location); - } - } - - // updateURLParameter updates the URL parameter with the given key to the given value - function updateURLParameter(filterObj, urlKey, defaultValue = {}, processFilter = filter => filter) { - const parameter = Object.keys(filterObj) - .filter(filter => filterObj[filter]) - .sort() - .map(processFilter) - .filter(Boolean) // Filters out any falsy values, including null - .join(','); - - const defaultParameter = Object.keys(defaultValue) - .filter(filter => defaultValue[filter]) - .sort() - .map(processFilter) - .filter(Boolean) // Filters out any falsy values, including null - .join(','); - - // if we ended up back at the defaults, just remove it from the URL - if (parameter === defaultParameter) { - $location.search(urlKey, null); - } else { - $location.search(urlKey, parameter || null); - } - } - - // updateVersionURLParameter updates the version URL parameter with the given version filters - function updateVersionURLParameter(versionFilters) { - updateURLParameter( - versionFilters, - 'versions', {}, - versionFilter => versionFilters[versionFilter].enabled && versionFilters[versionFilter].minorVersion != null - ? `${versionFilterKeyMap[versionFilter]}:${versionFilters[versionFilter].minorVersion}` - : null - ); - } - - // updateAllURLParameters updates all the URL parameters with the current filter settings - function updateAllURLParameters() { - updateURLParameter($scope.levels, 'levels', LEVEL_FILTERS_DEFAULT); - updateURLParameter($scope.groups, 'groups', GROUPS_FILTER_DEFAULT); - updateVersionURLParameter($scope.versionFilters); - updateURLParameter($scope.applicabilities, 'applicabilities', APPLICABILITIES_FILTER_DEFAULT); - } - - // Add $watches to automatically update URL parameters when the data changes - $scope.$watch('levels', function (newVal, oldVal) { - if (newVal !== oldVal) { - updateURLParameter(newVal, 'levels', LEVEL_FILTERS_DEFAULT); - } - }, true); - - $scope.$watch('groups', function (newVal, oldVal) { - if (newVal !== oldVal) { - updateURLParameter(newVal, 'groups', GROUPS_FILTER_DEFAULT); - } - }, true); - - $scope.$watch('versionFilters', function (newVal, oldVal) { - if (newVal !== oldVal) { - updateVersionURLParameter(newVal); - } - }, true); - - $scope.$watch('applicabilities', function (newVal, oldVal) { - if (newVal !== oldVal) { - updateURLParameter(newVal, 'applicabilities', APPLICABILITIES_FILTER_DEFAULT) - } - }, true); - - // Watch for changes in the URL path and update the search and lint display - $scope.$watch(function () { return $location.path(); }, function (newPath) { - const searchParameter = newPath.substring(1); - if ($scope.search !== searchParameter) { - $scope.search = searchParameter; - $scope.open[searchParameter] = true; - scrollToLintByURL($scope, $location); - } - }); - - let debounceTimeout; - $scope.$watch('search', function (newVal, oldVal) { - if (newVal !== oldVal) { - if (debounceTimeout) { - $timeout.cancel(debounceTimeout); - } - - debounceTimeout = $timeout(function () { - $location.path(newVal); - }, 1000); - } - }); - - $scope.$watch(function () { return $location.search(); }, function (newParameters) { - loadFromURLParameters(); - }, true); - - $scope.updatePath = function () { - if (debounceTimeout) { - $timeout.cancel(debounceTimeout); - } - - $location.path($scope.search); - } - - $scope.toggleLevels = function (value) { - const levels = $scope.levels; - for (const key in levels) { - if (levels.hasOwnProperty(key)) { - levels[key] = value; - } - } - }; - - $scope.toggleGroups = function (value) { - const groups = $scope.groups; - for (const key in groups) { - if (groups.hasOwnProperty(key)) { - groups[key] = value; - } - } - }; - - $scope.toggleApplicabilities = function (value) { - const applicabilities = $scope.applicabilities; - for (const key in applicabilities) { - if (applicabilities.hasOwnProperty(key)) { - applicabilities[key] = value; - } - } - } - - $scope.resetGroupsToDefault = function () { - $scope.groups = { - ...GROUPS_FILTER_DEFAULT - }; - }; - - $scope.selectedValuesCount = function (obj) { - return Object.values(obj).filter(x => x).length; - } - - $scope.clearVersionFilters = function () { - for (const filter in $scope.versionFilters) { - $scope.versionFilters[filter] = { enabled: false, minorVersion: null }; - } - } - - $scope.versionFilterCount = function(obj) { - return Object.values(obj).filter(x => x.enabled).length; - } - - $scope.updateVersionFilters = function() { - for (const filter in $scope.versionFilters) { - const minorVersion = $scope.versionFilters[filter].minorVersion; - - // 1.29.0 and greater - if (minorVersion && minorVersion > 28) { - $scope.versionFilters[filter].enabled = true; - continue; - } - - $scope.versionFilters[filter].enabled = false; - } - } - - $scope.byVersion = function(lint) { - const filters = $scope.versionFilters; - for (const filter in filters) { - if (filters[filter].enabled) { - const minorVersion = filters[filter].minorVersion; - - // Strip the "pre " prefix for pre 1.29.0 lints - const lintVersion = lint.version.startsWith("pre ") ? lint.version.substring(4, lint.version.length) : lint.version; - const lintMinorVersion = lintVersion.substring(2, 4); - - switch (filter) { - // "=" gets the highest priority, since all filters are inclusive - case "=": - return (lintMinorVersion == minorVersion); - case "≥": - if (lintMinorVersion < minorVersion) { return false; } - break; - case "≤": - if (lintMinorVersion > minorVersion) { return false; } - break; - default: - return true - } - } - } - - return true; - } - - $scope.byGroups = function (lint) { - return $scope.groups[lint.group]; - }; - - $scope.byApplicabilities = function (lint) { - return $scope.applicabilities[lint.applicability]; - }; - - $scope.toggleExpansion = function(lints, isExpanded) { - lints.forEach(lint => { - $scope.open[lint.id] = isExpanded; - }); - } - - // Get data - $scope.open = {}; - $scope.loading = true; - - // This will be used to jump into the source code of the version that this documentation is for. - $scope.docVersion = window.location.pathname.split('/')[2] || "master"; - - // Set up the filters from the URL parameters before we start loading the data - loadFromURLParameters(); - - const selectedGroup = getQueryVariable("sel"); - if (selectedGroup) { - selectGroup($scope, selectedGroup.toLowerCase()); - } - - setTimeout(function () { - const el = document.getElementById('filter-input'); - if (el) { el.focus() } - }, 0); - }); -})(); - -function getQueryVariable(variable) { - const query = window.location.search.substring(1); - const vars = query.split('&'); - for (const entry of vars) { - const pair = entry.split('='); - if (decodeURIComponent(pair[0]) == variable) { - return decodeURIComponent(pair[1]); - } - } -} - function storeValue(settingName, value) { try { localStorage.setItem(`clippy-lint-list-${settingName}`, value); @@ -611,6 +220,34 @@ function handleBlur(event) { } } +function toggleExpansion(expand) { + onEachLazy( + document.querySelectorAll("article"), + expand ? el => el.classList.remove("collapsed") : el => el.classList.add("collapsed"), + ); +} + +function generateListOfOptions(list, elementId) { + let html = ''; + let nbEnabled = 0; + for (const [key, value] of Object.entries(list)) { + const attr = value ? " checked" : ""; + html += `\ +
  • \ + \ +
  • `; + if (value) { + nbEnabled += 1; + } + } + + const elem = document.getElementById(elementId); + elem.previousElementSibling.querySelector(".badge").innerText = `${nbEnabled}`; + elem.innerHTML += html; +} + function generateSettings() { const settings = document.getElementById("settings-dropdown"); const settingsButton = settings.querySelector(".settings-icon") @@ -622,6 +259,56 @@ function generateSettings() { settingsMenu.querySelectorAll("input"), el => el.onblur = handleBlur, ); + + const LEVEL_FILTERS_DEFAULT = {allow: true, warn: true, deny: true, none: true}; + generateListOfOptions(LEVEL_FILTERS_DEFAULT, "lint-levels"); + + // Generate lint groups. + const GROUPS_FILTER_DEFAULT = { + cargo: true, + complexity: true, + correctness: true, + deprecated: false, + nursery: true, + pedantic: true, + perf: true, + restriction: true, + style: true, + suspicious: true, + }; + generateListOfOptions(GROUPS_FILTER_DEFAULT, "lint-groups"); + + const APPLICABILITIES_FILTER_DEFAULT = { + Unspecified: true, + Unresolved: true, + MachineApplicable: true, + MaybeIncorrect: true, + HasPlaceholders: true + }; + generateListOfOptions(APPLICABILITIES_FILTER_DEFAULT, "lint-applicabilities"); + + const VERSIONS_FILTERS = { + "≥": {enabled: false, minorVersion: null }, + "≤": {enabled: false, minorVersion: null }, + "=": {enabled: false, minorVersion: null }, + }; + + let html = ''; + for (const kind of ["≥", "≤", "="]) { + html += `\ +
  • \ + \ + 1. \ + \ + .0\ +
  • `; + } + document.getElementById("version-filter-selector").innerHTML += html; } function generateSearch() { From a235cbd8ac347f323084c99d198f96d27269ec85 Mon Sep 17 00:00:00 2001 From: Kevin Reid Date: Mon, 23 Sep 2024 11:58:28 -0700 Subject: [PATCH 07/87] `module_name_repetitions`: don't warn if the item is in a private module. Fixes . There is still a warning (as there should be) if the item is reexported by name, but not by glob; that would require further work to examine the names in the glob, and I haven't looked into that. Credit to @Centri3 for suggesting approximately this simple fix in . However, per later comment , I am not making it configuration-dependent, but *always* checking public items in public modules only. --- clippy_lints/src/item_name_repetitions.rs | 33 +++++++++++++++---- clippy_lints/src/literal_representation.rs | 1 - clippy_lints/src/macro_use.rs | 1 - clippy_lints/src/vec.rs | 1 - .../allowed_prefixes/item_name_repetitions.rs | 2 +- .../item_name_repetitions.rs | 2 +- tests/ui/module_name_repetitions.rs | 18 +++++++++- tests/ui/module_name_repetitions.stderr | 8 ++++- 8 files changed, 53 insertions(+), 13 deletions(-) diff --git a/clippy_lints/src/item_name_repetitions.rs b/clippy_lints/src/item_name_repetitions.rs index 66a8a3167a4f2..511317c8b43f6 100644 --- a/clippy_lints/src/item_name_repetitions.rs +++ b/clippy_lints/src/item_name_repetitions.rs @@ -50,11 +50,28 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does - /// Detects type names that are prefixed or suffixed by the - /// containing module's name. + /// Detects public item names that are prefixed or suffixed by the + /// containing public module's name. /// /// ### Why is this bad? - /// It requires the user to type the module name twice. + /// It requires the user to type the module name twice in each usage, + /// especially if they choose to import the module rather than its contents. + /// + /// Lack of such repetition is also the style used in the Rust standard library; + /// e.g. `io::Error` and `fmt::Error` rather than `io::IoError` and `fmt::FmtError`; + /// and `array::from_ref` rather than `array::array_from_ref`. + /// + /// ### Known issues + /// Glob re-exports are ignored; e.g. this will not warn even though it should: + /// + /// ```no_run + /// pub mod foo { + /// mod iteration { + /// pub struct FooIter {} + /// } + /// pub use iteration::*; // creates the path `foo::FooIter` + /// } + /// ``` /// /// ### Example /// ```no_run @@ -389,12 +406,12 @@ impl LateLintPass<'_> for ItemNameRepetitions { let item_name = item.ident.name.as_str(); let item_camel = to_camel_case(item_name); if !item.span.from_expansion() && is_present_in_source(cx, item.span) { - if let [.., (mod_name, mod_camel, owner_id)] = &*self.modules { + if let [.., (mod_name, mod_camel, mod_owner_id)] = &*self.modules { // constants don't have surrounding modules if !mod_camel.is_empty() { if mod_name == &item.ident.name && let ItemKind::Mod(..) = item.kind - && (!self.allow_private_module_inception || cx.tcx.visibility(owner_id.def_id).is_public()) + && (!self.allow_private_module_inception || cx.tcx.visibility(mod_owner_id.def_id).is_public()) { span_lint( cx, @@ -403,9 +420,13 @@ impl LateLintPass<'_> for ItemNameRepetitions { "module has the same name as its containing module", ); } + // The `module_name_repetitions` lint should only trigger if the item has the module in its // name. Having the same name is accepted. - if cx.tcx.visibility(item.owner_id).is_public() && item_camel.len() > mod_camel.len() { + if cx.tcx.visibility(item.owner_id).is_public() + && cx.tcx.visibility(mod_owner_id.def_id).is_public() + && item_camel.len() > mod_camel.len() + { let matching = count_match_start(mod_camel, &item_camel); let rmatching = count_match_end(mod_camel, &item_camel); let nchars = mod_camel.chars().count(); diff --git a/clippy_lints/src/literal_representation.rs b/clippy_lints/src/literal_representation.rs index 81f2a03fb55fb..e2dcb20f906d3 100644 --- a/clippy_lints/src/literal_representation.rs +++ b/clippy_lints/src/literal_representation.rs @@ -412,7 +412,6 @@ impl LiteralDigitGrouping { } } -#[expect(clippy::module_name_repetitions)] pub struct DecimalLiteralRepresentation { threshold: u64, } diff --git a/clippy_lints/src/macro_use.rs b/clippy_lints/src/macro_use.rs index bd6b3f1a47b16..d5d6226f0aaaf 100644 --- a/clippy_lints/src/macro_use.rs +++ b/clippy_lints/src/macro_use.rs @@ -43,7 +43,6 @@ impl MacroRefData { } #[derive(Default)] -#[expect(clippy::module_name_repetitions)] pub struct MacroUseImports { /// the actual import path used and the span of the attribute above it. The value is /// the location, where the lint should be emitted. diff --git a/clippy_lints/src/vec.rs b/clippy_lints/src/vec.rs index ce4f41e854d5e..0fdef51d6fc50 100644 --- a/clippy_lints/src/vec.rs +++ b/clippy_lints/src/vec.rs @@ -17,7 +17,6 @@ use rustc_middle::ty::layout::LayoutOf; use rustc_session::impl_lint_pass; use rustc_span::{DesugaringKind, Span, sym}; -#[expect(clippy::module_name_repetitions)] pub struct UselessVec { too_large_for_stack: u64, msrv: Msrv, diff --git a/tests/ui-toml/item_name_repetitions/allowed_prefixes/item_name_repetitions.rs b/tests/ui-toml/item_name_repetitions/allowed_prefixes/item_name_repetitions.rs index 4142ced5f6b54..2ae673a6def08 100644 --- a/tests/ui-toml/item_name_repetitions/allowed_prefixes/item_name_repetitions.rs +++ b/tests/ui-toml/item_name_repetitions/allowed_prefixes/item_name_repetitions.rs @@ -1,7 +1,7 @@ #![warn(clippy::module_name_repetitions)] #![allow(dead_code)] -mod foo { +pub mod foo { // #12544 - shouldn't warn if item name consists only of an allowed prefix and a module name. // In this test, allowed prefixes are configured to be ["bar"]. diff --git a/tests/ui-toml/item_name_repetitions/allowed_prefixes_extend/item_name_repetitions.rs b/tests/ui-toml/item_name_repetitions/allowed_prefixes_extend/item_name_repetitions.rs index b132305d01cf3..dbd61992c0d6b 100644 --- a/tests/ui-toml/item_name_repetitions/allowed_prefixes_extend/item_name_repetitions.rs +++ b/tests/ui-toml/item_name_repetitions/allowed_prefixes_extend/item_name_repetitions.rs @@ -1,7 +1,7 @@ #![warn(clippy::module_name_repetitions)] #![allow(dead_code)] -mod foo { +pub mod foo { // #12544 - shouldn't warn if item name consists only of an allowed prefix and a module name. // In this test, allowed prefixes are configured to be all of the default prefixes and ["bar"]. diff --git a/tests/ui/module_name_repetitions.rs b/tests/ui/module_name_repetitions.rs index b75ef87ab3640..71d8ac7a1f0e0 100644 --- a/tests/ui/module_name_repetitions.rs +++ b/tests/ui/module_name_repetitions.rs @@ -3,7 +3,7 @@ #![warn(clippy::module_name_repetitions)] #![allow(dead_code)] -mod foo { +pub mod foo { pub fn foo() {} pub fn foo_bar() {} //~^ ERROR: item name starts with its containing module's name @@ -20,6 +20,22 @@ mod foo { // Should not warn pub struct Foobar; + // #8524 - shouldn't warn when item is declared in a private module... + mod error { + pub struct Error; + pub struct FooError; + } + pub use error::Error; + // ... but should still warn when the item is reexported to create a *public* path with repetition. + pub use error::FooError; + //~^ ERROR: item name starts with its containing module's name + + // FIXME: This should also warn because it creates the public path `foo::FooIter`. + mod iter { + pub struct FooIter; + } + pub use iter::*; + // #12544 - shouldn't warn if item name consists only of an allowed prefix and a module name. pub fn to_foo() {} pub fn into_foo() {} diff --git a/tests/ui/module_name_repetitions.stderr b/tests/ui/module_name_repetitions.stderr index bffb08f6f879f..8fd8b39487537 100644 --- a/tests/ui/module_name_repetitions.stderr +++ b/tests/ui/module_name_repetitions.stderr @@ -31,5 +31,11 @@ error: item name starts with its containing module's name LL | pub struct Foo7Bar; | ^^^^^^^ -error: aborting due to 5 previous errors +error: item name starts with its containing module's name + --> tests/ui/module_name_repetitions.rs:30:20 + | +LL | pub use error::FooError; + | ^^^^^^^^ + +error: aborting due to 6 previous errors From 4997ee7afcda865406352f755e9e3b44958560bd Mon Sep 17 00:00:00 2001 From: blyxyas Date: Fri, 6 Sep 2024 00:26:02 +0200 Subject: [PATCH 08/87] Turn declare_clippy_lint into a declarative macro --- clippy_lints/Cargo.toml | 1 - clippy_lints/src/declare_clippy_lint.rs | 162 +++++++++++++++++++++ clippy_lints/src/lib.rs | 6 +- clippy_utils/src/lib.rs | 2 +- declare_clippy_lint/Cargo.toml | 13 -- declare_clippy_lint/src/lib.rs | 182 ------------------------ tests/versioncheck.rs | 1 - 7 files changed, 167 insertions(+), 200 deletions(-) create mode 100644 clippy_lints/src/declare_clippy_lint.rs delete mode 100644 declare_clippy_lint/Cargo.toml delete mode 100644 declare_clippy_lint/src/lib.rs diff --git a/clippy_lints/Cargo.toml b/clippy_lints/Cargo.toml index d1188940b46a8..55f1d31b4addc 100644 --- a/clippy_lints/Cargo.toml +++ b/clippy_lints/Cargo.toml @@ -13,7 +13,6 @@ arrayvec = { version = "0.7", default-features = false } cargo_metadata = "0.18" clippy_config = { path = "../clippy_config" } clippy_utils = { path = "../clippy_utils" } -declare_clippy_lint = { path = "../declare_clippy_lint" } itertools = "0.12" quine-mc_cluskey = "0.2" regex-syntax = "0.8" diff --git a/clippy_lints/src/declare_clippy_lint.rs b/clippy_lints/src/declare_clippy_lint.rs new file mode 100644 index 0000000000000..b1e39c70baa2b --- /dev/null +++ b/clippy_lints/src/declare_clippy_lint.rs @@ -0,0 +1,162 @@ +#[macro_export] +#[allow(clippy::crate_in_macro_def)] +macro_rules! declare_clippy_lint { + (@ + $(#[doc = $lit:literal])* + pub $lint_name:ident, + $category:ident, + $lintcategory:expr, + $desc:literal, + $version_expr:expr, + $version_lit:literal + ) => { + rustc_session::declare_tool_lint! { + $(#[doc = $lit])* + #[clippy::version = $version_lit] + pub clippy::$lint_name, + $category, + $desc, + report_in_external_macro:true + } + + pub(crate) static ${concat($lint_name, _INFO)}: &'static crate::LintInfo = &crate::LintInfo { + lint: &$lint_name, + category: $lintcategory, + explanation: concat!($($lit,"\n",)*), + location: concat!(file!(), "#L", line!()), + version: $version_expr + }; + }; + ( + $(#[doc = $lit:literal])* + #[clippy::version = $version:literal] + pub $lint_name:ident, + restriction, + $desc:literal + ) => { + declare_clippy_lint! {@ + $(#[doc = $lit])* + pub $lint_name, Allow, crate::LintCategory::Restriction, $desc, + Some($version), $version + } + }; + ( + $(#[doc = $lit:literal])* + #[clippy::version = $version:literal] + pub $lint_name:ident, + style, + $desc:literal + ) => { + declare_clippy_lint! {@ + $(#[doc = $lit])* + pub $lint_name, Warn, crate::LintCategory::Style, $desc, + Some($version), $version + + } + }; + ( + $(#[doc = $lit:literal])* + #[clippy::version = $version:literal] + pub $lint_name:ident, + correctness, + $desc:literal + ) => { + declare_clippy_lint! {@ + $(#[doc = $lit])* + pub $lint_name, Deny, crate::LintCategory::Correctness, $desc, + Some($version), $version + + } + }; + ( + $(#[doc = $lit:literal])* + #[clippy::version = $version:literal] + pub $lint_name:ident, + perf, + $desc:literal + ) => { + declare_clippy_lint! {@ + $(#[doc = $lit])* + pub $lint_name, Warn, crate::LintCategory::Perf, $desc, + Some($version), $version + } + }; + ( + $(#[doc = $lit:literal])* + #[clippy::version = $version:literal] + pub $lint_name:ident, + complexity, + $desc:literal + ) => { + declare_clippy_lint! {@ + $(#[doc = $lit])* + pub $lint_name, Warn, crate::LintCategory::Complexity, $desc, + Some($version), $version + } + }; + ( + $(#[doc = $lit:literal])* + #[clippy::version = $version:literal] + pub $lint_name:ident, + suspicious, + $desc:literal + ) => { + declare_clippy_lint! {@ + $(#[doc = $lit])* + pub $lint_name, Warn, crate::LintCategory::Suspicious, $desc, + Some($version), $version + } + }; + ( + $(#[doc = $lit:literal])* + #[clippy::version = $version:literal] + pub $lint_name:ident, + nursery, + $desc:literal + ) => { + declare_clippy_lint! {@ + $(#[doc = $lit])* + pub $lint_name, Allow, crate::LintCategory::Nursery, $desc, + Some($version), $version + } + }; + ( + $(#[doc = $lit:literal])* + #[clippy::version = $version:literal] + pub $lint_name:ident, + pedantic, + $desc:literal + ) => { + declare_clippy_lint! {@ + $(#[doc = $lit])* + pub $lint_name, Allow, crate::LintCategory::Pedantic, $desc, + Some($version), $version + } + }; + ( + $(#[doc = $lit:literal])* + #[clippy::version = $version:literal] + pub $lint_name:ident, + cargo, + $desc:literal + ) => { + declare_clippy_lint! {@ + $(#[doc = $lit])* + pub $lint_name, Allow, crate::LintCategory::Cargo, $desc, + Some($version), $version + } + }; + + ( + $(#[doc = $lit:literal])* + pub $lint_name:ident, + internal, + $desc:literal + ) => { + declare_clippy_lint! {@ + $(#[doc = $lit])* + pub $lint_name, Allow, crate::LintCategory::Internal, $desc, + None, "0.0.0" + } + }; +} diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 1d41f568f3785..efc9fe6f3157d 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -1,6 +1,7 @@ #![feature(array_windows)] #![feature(binary_heap_into_iter_sorted)] #![feature(box_patterns)] +#![feature(macro_metavar_expr_concat)] #![feature(control_flow_enum)] #![feature(f128)] #![feature(f16)] @@ -59,9 +60,10 @@ extern crate rustc_trait_selection; extern crate thin_vec; #[macro_use] -extern crate clippy_utils; +mod declare_clippy_lint; + #[macro_use] -extern crate declare_clippy_lint; +extern crate clippy_utils; #[cfg_attr(feature = "internal", allow(clippy::missing_clippy_version_attribute))] mod utils; diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index a925549b0bff0..bf7484f7902bb 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -4,6 +4,7 @@ #![feature(f128)] #![feature(f16)] #![feature(if_let_guard)] +#![feature(macro_metavar_expr_concat)] #![feature(let_chains)] #![feature(never_type)] #![feature(rustc_private)] @@ -129,7 +130,6 @@ use crate::consts::{ConstEvalCtxt, Constant, mir_to_const}; use crate::higher::Range; use crate::ty::{adt_and_variant_of_res, can_partially_move_ty, expr_sig, is_copy, is_recursively_primitive_type}; use crate::visitors::for_each_expr_without_closures; - use rustc_middle::hir::nested_filter; #[macro_export] diff --git a/declare_clippy_lint/Cargo.toml b/declare_clippy_lint/Cargo.toml deleted file mode 100644 index 67a1f7cc72c54..0000000000000 --- a/declare_clippy_lint/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "declare_clippy_lint" -version = "0.1.83" -edition = "2021" -publish = false - -[lib] -proc-macro = true - -[dependencies] -itertools = "0.12" -quote = "1.0.21" -syn = "2.0" diff --git a/declare_clippy_lint/src/lib.rs b/declare_clippy_lint/src/lib.rs deleted file mode 100644 index fefc1a0a6c402..0000000000000 --- a/declare_clippy_lint/src/lib.rs +++ /dev/null @@ -1,182 +0,0 @@ -#![feature(let_chains, proc_macro_span)] -// warn on lints, that are included in `rust-lang/rust`s bootstrap -#![warn(rust_2018_idioms, unused_lifetimes)] - -use proc_macro::TokenStream; -use quote::{format_ident, quote}; -use syn::parse::{Parse, ParseStream}; -use syn::{Attribute, Error, Expr, ExprLit, Ident, Lit, LitStr, Meta, Result, Token, parse_macro_input}; - -fn parse_attr(path: [&'static str; LEN], attr: &Attribute) -> Option { - if let Meta::NameValue(name_value) = &attr.meta { - let path_idents = name_value.path.segments.iter().map(|segment| &segment.ident); - - if itertools::equal(path_idents, path) - && let Expr::Lit(ExprLit { lit: Lit::Str(s), .. }) = &name_value.value - { - return Some(s.clone()); - } - } - - None -} - -struct ClippyLint { - attrs: Vec, - version: Option, - explanation: String, - name: Ident, - category: Ident, - description: LitStr, -} - -impl Parse for ClippyLint { - fn parse(input: ParseStream<'_>) -> Result { - let attrs = input.call(Attribute::parse_outer)?; - - let mut in_code = false; - let mut explanation = String::new(); - let mut version = None; - for attr in &attrs { - if let Some(lit) = parse_attr(["doc"], attr) { - let value = lit.value(); - let line = value.strip_prefix(' ').unwrap_or(&value); - - if let Some(lang) = line.strip_prefix("```") { - let tag = lang.split_once(',').map_or(lang, |(left, _)| left); - if !in_code && matches!(tag, "" | "rust" | "ignore" | "should_panic" | "no_run" | "compile_fail") { - explanation += "```rust\n"; - } else { - explanation += line; - explanation.push('\n'); - } - in_code = !in_code; - } else if !(in_code && line.starts_with("# ")) { - explanation += line; - explanation.push('\n'); - } - } else if let Some(lit) = parse_attr(["clippy", "version"], attr) { - if let Some(duplicate) = version.replace(lit) { - return Err(Error::new_spanned(duplicate, "duplicate clippy::version")); - } - } else { - return Err(Error::new_spanned(attr, "unexpected attribute")); - } - } - - input.parse::()?; - let name = input.parse()?; - input.parse::()?; - - let category = input.parse()?; - input.parse::()?; - - let description = input.parse()?; - - Ok(Self { - attrs, - version, - explanation, - name, - category, - description, - }) - } -} - -/// Macro used to declare a Clippy lint. -/// -/// Every lint declaration consists of 4 parts: -/// -/// 1. The documentation, which is used for the website and `cargo clippy --explain` -/// 2. The `LINT_NAME`. See [lint naming][lint_naming] on lint naming conventions. -/// 3. The `lint_level`, which is a mapping from *one* of our lint groups to `Allow`, `Warn` or -/// `Deny`. The lint level here has nothing to do with what lint groups the lint is a part of. -/// 4. The `description` that contains a short explanation on what's wrong with code where the lint -/// is triggered. -/// -/// Currently the categories `style`, `correctness`, `suspicious`, `complexity` and `perf` are -/// enabled by default. As said in the README.md of this repository, if the lint level mapping -/// changes, please update README.md. -/// -/// # Example -/// -/// ```ignore -/// use rustc_session::declare_tool_lint; -/// -/// declare_clippy_lint! { -/// /// ### What it does -/// /// Checks for ... (describe what the lint matches). -/// /// -/// /// ### Why is this bad? -/// /// Supply the reason for linting the code. -/// /// -/// /// ### Example -/// /// ```rust -/// /// Insert a short example of code that triggers the lint -/// /// ``` -/// /// -/// /// Use instead: -/// /// ```rust -/// /// Insert a short example of improved code that doesn't trigger the lint -/// /// ``` -/// #[clippy::version = "1.65.0"] -/// pub LINT_NAME, -/// pedantic, -/// "description" -/// } -/// ``` -/// [lint_naming]: https://rust-lang.github.io/rfcs/0344-conventions-galore.html#lints -#[proc_macro] -pub fn declare_clippy_lint(input: TokenStream) -> TokenStream { - let ClippyLint { - attrs, - version, - explanation, - name, - category, - description, - } = parse_macro_input!(input as ClippyLint); - - let mut category = category.to_string(); - - let level = format_ident!("{}", match category.as_str() { - "correctness" => "Deny", - "style" | "suspicious" | "complexity" | "perf" => "Warn", - "pedantic" | "restriction" | "cargo" | "nursery" | "internal" => "Allow", - _ => panic!("unknown category {category}"), - },); - - let info_name = format_ident!("{name}_INFO"); - - (&mut category[0..1]).make_ascii_uppercase(); - let category_variant = format_ident!("{category}"); - - let name_span = name.span().unwrap(); - let location = format!("{}#L{}", name_span.source_file().path().display(), name_span.line()); - - let version = match version { - Some(version) => quote!(Some(#version)), - None => quote!(None), - }; - - let output = quote! { - rustc_session::declare_tool_lint! { - #(#attrs)* - pub clippy::#name, - #level, - #description, - report_in_external_macro: true - } - - pub(crate) static #info_name: &'static crate::LintInfo = &crate::LintInfo { - lint: &#name, - category: crate::LintCategory::#category_variant, - explanation: #explanation, - location: #location, - version: #version, - }; - }; - - TokenStream::from(output) -} diff --git a/tests/versioncheck.rs b/tests/versioncheck.rs index 6832833393766..e29898f068d37 100644 --- a/tests/versioncheck.rs +++ b/tests/versioncheck.rs @@ -24,7 +24,6 @@ fn consistent_clippy_crate_versions() { let clippy_version = read_version("Cargo.toml"); let paths = [ - "declare_clippy_lint/Cargo.toml", "clippy_config/Cargo.toml", "clippy_lints/Cargo.toml", "clippy_utils/Cargo.toml", From 0735031be94cd4f6a102e83e72630fae04a18090 Mon Sep 17 00:00:00 2001 From: blyxyas Date: Wed, 25 Sep 2024 13:42:21 +0200 Subject: [PATCH 09/87] Move tag removing to --explain --- clippy_lints/src/lib.rs | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index efc9fe6f3157d..e927d9a2d5bff 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -524,8 +524,28 @@ impl LintInfo { pub fn explain(name: &str) -> i32 { let target = format!("clippy::{}", name.to_ascii_uppercase()); + if let Some(info) = declared_lints::LINTS.iter().find(|info| info.lint.name == target) { - println!("{}", info.explanation); + // Remove tags and hidden code: + let mut explanation = String::with_capacity(128); + let mut in_code = false; + for line in info.explanation.lines().map(|line| line.trim()) { + if let Some(lang) = line.strip_prefix("```") { + let tag = lang.split_once(',').map_or(lang, |(left, _)| left); + if !in_code && matches!(tag, "" | "rust" | "ignore" | "should_panic" | "no_run" | "compile_fail") { + explanation += "```rust\n"; + } else { + explanation += line; + explanation.push('\n'); + } + in_code = !in_code; + } else if !(in_code && line.starts_with("# ")) { + explanation += line; + explanation.push('\n'); + } + } + + println!("{}", explanation); // Check if the lint has configuration let mut mdconf = get_configuration_metadata(); let name = name.to_ascii_lowercase(); From daf730caedb638696ee704e22942617cf5848e5f Mon Sep 17 00:00:00 2001 From: blyxyas Date: Thu, 26 Sep 2024 00:23:38 +0200 Subject: [PATCH 10/87] Add `sanitize_explanation` --- clippy_lints/src/lib.rs | 43 +++++++++++++++++++++++------------------ tests/compile-test.rs | 7 ++++++- 2 files changed, 30 insertions(+), 20 deletions(-) diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index e927d9a2d5bff..0bfbb92dfda55 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -522,30 +522,35 @@ impl LintInfo { } } -pub fn explain(name: &str) -> i32 { - let target = format!("clippy::{}", name.to_ascii_uppercase()); - - if let Some(info) = declared_lints::LINTS.iter().find(|info| info.lint.name == target) { - // Remove tags and hidden code: - let mut explanation = String::with_capacity(128); - let mut in_code = false; - for line in info.explanation.lines().map(|line| line.trim()) { - if let Some(lang) = line.strip_prefix("```") { - let tag = lang.split_once(',').map_or(lang, |(left, _)| left); - if !in_code && matches!(tag, "" | "rust" | "ignore" | "should_panic" | "no_run" | "compile_fail") { - explanation += "```rust\n"; - } else { - explanation += line; - explanation.push('\n'); - } - in_code = !in_code; - } else if !(in_code && line.starts_with("# ")) { +// Remove code tags and code behind '# 's, as they are not needed for the lint docs and --explain +pub fn sanitize_explanation(raw_docs: &str) -> String { + // Remove tags and hidden code: + let mut explanation = String::with_capacity(128); + let mut in_code = false; + for line in raw_docs.lines().map(|line| line.trim()) { + if let Some(lang) = line.strip_prefix("```") { + let tag = lang.split_once(',').map_or(lang, |(left, _)| left); + if !in_code && matches!(tag, "" | "rust" | "ignore" | "should_panic" | "no_run" | "compile_fail") { + explanation += "```rust\n"; + } else { explanation += line; explanation.push('\n'); } + in_code = !in_code; + } else if !(in_code && line.starts_with("# ")) { + explanation += line; + explanation.push('\n'); } + } - println!("{}", explanation); + explanation +} + +pub fn explain(name: &str) -> i32 { + let target = format!("clippy::{}", name.to_ascii_uppercase()); + + if let Some(info) = declared_lints::LINTS.iter().find(|info| info.lint.name == target) { + println!("{}", sanitize_explanation(info.explanation)); // Check if the lint has configuration let mut mdconf = get_configuration_metadata(); let name = name.to_ascii_lowercase(); diff --git a/tests/compile-test.rs b/tests/compile-test.rs index af2aa51925777..23dd41235bdbd 100644 --- a/tests/compile-test.rs +++ b/tests/compile-test.rs @@ -5,9 +5,9 @@ use cargo_metadata::Message; use cargo_metadata::diagnostic::{Applicability, Diagnostic}; use clippy_config::ClippyConfiguration; -use clippy_lints::LintInfo; use clippy_lints::declared_lints::LINTS; use clippy_lints::deprecated_lints::{DEPRECATED, DEPRECATED_VERSION, RENAMED}; +use clippy_lints::{LintInfo, sanitize_explanation}; use serde::{Deserialize, Serialize}; use test_utils::IS_RUSTC_TEST_SUITE; use ui_test::custom_flags::Flag; @@ -444,7 +444,12 @@ impl DiagnosticCollector { iter::zip(DEPRECATED, DEPRECATED_VERSION) .map(|((lint, reason), version)| LintMetadata::new_deprecated(lint, reason, version)), ) + .map(|mut metadata| { + metadata.docs = sanitize_explanation(&metadata.docs); + metadata + }) .collect(); + metadata.sort_unstable_by(|a, b| a.id.cmp(&b.id)); let json = serde_json::to_string_pretty(&metadata).unwrap(); From d232d094b8e2776a6a1e88f478442fd98e2e70b4 Mon Sep 17 00:00:00 2001 From: Scott McMurray Date: Wed, 18 Sep 2024 10:34:27 -0700 Subject: [PATCH 11/87] Remove the `control_flow_enum` feature from clippy --- clippy_lints/src/lib.rs | 1 - clippy_utils/src/lib.rs | 1 - 2 files changed, 2 deletions(-) diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 1d41f568f3785..54c3758459c72 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -1,7 +1,6 @@ #![feature(array_windows)] #![feature(binary_heap_into_iter_sorted)] #![feature(box_patterns)] -#![feature(control_flow_enum)] #![feature(f128)] #![feature(f16)] #![feature(if_let_guard)] diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 6dbc3334157e3..6c0413c180868 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -1,6 +1,5 @@ #![feature(array_chunks)] #![feature(box_patterns)] -#![feature(control_flow_enum)] #![feature(f128)] #![feature(f16)] #![feature(if_let_guard)] From c8725f70e77a08974a1d51bd360345a2ab7fabc6 Mon Sep 17 00:00:00 2001 From: y21 <30553356+y21@users.noreply.github.com> Date: Thu, 26 Sep 2024 17:44:59 +0200 Subject: [PATCH 12/87] add proc macro check to needless_return --- clippy_lints/src/returns.rs | 2 +- tests/ui/needless_return.fixed | 8 +++ tests/ui/needless_return.rs | 8 +++ tests/ui/needless_return.stderr | 108 ++++++++++++++++---------------- 4 files changed, 71 insertions(+), 55 deletions(-) diff --git a/clippy_lints/src/returns.rs b/clippy_lints/src/returns.rs index 3754fdddedfd1..6754912f76564 100644 --- a/clippy_lints/src/returns.rs +++ b/clippy_lints/src/returns.rs @@ -407,7 +407,7 @@ fn check_final_expr<'tcx>( } } - if ret_span.from_expansion() { + if ret_span.from_expansion() || is_from_proc_macro(cx, expr) { return; } diff --git a/tests/ui/needless_return.fixed b/tests/ui/needless_return.fixed index c5c570690b450..1507bc50a75e3 100644 --- a/tests/ui/needless_return.fixed +++ b/tests/ui/needless_return.fixed @@ -1,3 +1,4 @@ +//@aux-build:proc_macros.rs #![feature(yeet_expr)] #![allow(unused)] #![allow( @@ -9,6 +10,9 @@ )] #![warn(clippy::needless_return)] +extern crate proc_macros; +use proc_macros::with_span; + use std::cell::RefCell; macro_rules! the_answer { @@ -359,4 +363,8 @@ fn issue12907() -> String { "".split("").next().unwrap().to_string() } +fn issue13458() { + with_span!(span return); +} + fn main() {} diff --git a/tests/ui/needless_return.rs b/tests/ui/needless_return.rs index 738611391dfdf..c59bfced6d6f6 100644 --- a/tests/ui/needless_return.rs +++ b/tests/ui/needless_return.rs @@ -1,3 +1,4 @@ +//@aux-build:proc_macros.rs #![feature(yeet_expr)] #![allow(unused)] #![allow( @@ -9,6 +10,9 @@ )] #![warn(clippy::needless_return)] +extern crate proc_macros; +use proc_macros::with_span; + use std::cell::RefCell; macro_rules! the_answer { @@ -369,4 +373,8 @@ fn issue12907() -> String { return "".split("").next().unwrap().to_string(); } +fn issue13458() { + with_span!(span return); +} + fn main() {} diff --git a/tests/ui/needless_return.stderr b/tests/ui/needless_return.stderr index da0fa220d8c4a..d3c2a6badc0f4 100644 --- a/tests/ui/needless_return.stderr +++ b/tests/ui/needless_return.stderr @@ -1,5 +1,5 @@ error: unneeded `return` statement - --> tests/ui/needless_return.rs:25:5 + --> tests/ui/needless_return.rs:29:5 | LL | return true; | ^^^^^^^^^^^ @@ -13,7 +13,7 @@ LL + true | error: unneeded `return` statement - --> tests/ui/needless_return.rs:29:5 + --> tests/ui/needless_return.rs:33:5 | LL | return true; | ^^^^^^^^^^^ @@ -25,7 +25,7 @@ LL + true | error: unneeded `return` statement - --> tests/ui/needless_return.rs:34:5 + --> tests/ui/needless_return.rs:38:5 | LL | return true;;; | ^^^^^^^^^^^ @@ -37,7 +37,7 @@ LL + true | error: unneeded `return` statement - --> tests/ui/needless_return.rs:39:5 + --> tests/ui/needless_return.rs:43:5 | LL | return true;; ; ; | ^^^^^^^^^^^ @@ -49,7 +49,7 @@ LL + true | error: unneeded `return` statement - --> tests/ui/needless_return.rs:44:9 + --> tests/ui/needless_return.rs:48:9 | LL | return true; | ^^^^^^^^^^^ @@ -61,7 +61,7 @@ LL + true | error: unneeded `return` statement - --> tests/ui/needless_return.rs:46:9 + --> tests/ui/needless_return.rs:50:9 | LL | return false; | ^^^^^^^^^^^^ @@ -73,7 +73,7 @@ LL + false | error: unneeded `return` statement - --> tests/ui/needless_return.rs:52:17 + --> tests/ui/needless_return.rs:56:17 | LL | true => return false, | ^^^^^^^^^^^^ @@ -84,7 +84,7 @@ LL | true => false, | ~~~~~ error: unneeded `return` statement - --> tests/ui/needless_return.rs:54:13 + --> tests/ui/needless_return.rs:58:13 | LL | return true; | ^^^^^^^^^^^ @@ -96,7 +96,7 @@ LL + true | error: unneeded `return` statement - --> tests/ui/needless_return.rs:61:9 + --> tests/ui/needless_return.rs:65:9 | LL | return true; | ^^^^^^^^^^^ @@ -108,7 +108,7 @@ LL + true | error: unneeded `return` statement - --> tests/ui/needless_return.rs:63:16 + --> tests/ui/needless_return.rs:67:16 | LL | let _ = || return true; | ^^^^^^^^^^^ @@ -119,7 +119,7 @@ LL | let _ = || true; | ~~~~ error: unneeded `return` statement - --> tests/ui/needless_return.rs:67:5 + --> tests/ui/needless_return.rs:71:5 | LL | return the_answer!(); | ^^^^^^^^^^^^^^^^^^^^ @@ -131,7 +131,7 @@ LL + the_answer!() | error: unneeded `return` statement - --> tests/ui/needless_return.rs:70:21 + --> tests/ui/needless_return.rs:74:21 | LL | fn test_void_fun() { | _____________________^ @@ -146,7 +146,7 @@ LL + fn test_void_fun() { | error: unneeded `return` statement - --> tests/ui/needless_return.rs:75:11 + --> tests/ui/needless_return.rs:79:11 | LL | if b { | ___________^ @@ -161,7 +161,7 @@ LL + if b { | error: unneeded `return` statement - --> tests/ui/needless_return.rs:77:13 + --> tests/ui/needless_return.rs:81:13 | LL | } else { | _____________^ @@ -176,7 +176,7 @@ LL + } else { | error: unneeded `return` statement - --> tests/ui/needless_return.rs:85:14 + --> tests/ui/needless_return.rs:89:14 | LL | _ => return, | ^^^^^^ @@ -187,7 +187,7 @@ LL | _ => (), | ~~ error: unneeded `return` statement - --> tests/ui/needless_return.rs:93:24 + --> tests/ui/needless_return.rs:97:24 | LL | let _ = 42; | ________________________^ @@ -202,7 +202,7 @@ LL + let _ = 42; | error: unneeded `return` statement - --> tests/ui/needless_return.rs:96:14 + --> tests/ui/needless_return.rs:100:14 | LL | _ => return, | ^^^^^^ @@ -213,7 +213,7 @@ LL | _ => (), | ~~ error: unneeded `return` statement - --> tests/ui/needless_return.rs:109:9 + --> tests/ui/needless_return.rs:113:9 | LL | return String::from("test"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -225,7 +225,7 @@ LL + String::from("test") | error: unneeded `return` statement - --> tests/ui/needless_return.rs:111:9 + --> tests/ui/needless_return.rs:115:9 | LL | return String::new(); | ^^^^^^^^^^^^^^^^^^^^ @@ -237,7 +237,7 @@ LL + String::new() | error: unneeded `return` statement - --> tests/ui/needless_return.rs:133:32 + --> tests/ui/needless_return.rs:137:32 | LL | bar.unwrap_or_else(|_| return) | ^^^^^^ @@ -248,7 +248,7 @@ LL | bar.unwrap_or_else(|_| {}) | ~~ error: unneeded `return` statement - --> tests/ui/needless_return.rs:137:21 + --> tests/ui/needless_return.rs:141:21 | LL | let _ = || { | _____________________^ @@ -263,7 +263,7 @@ LL + let _ = || { | error: unneeded `return` statement - --> tests/ui/needless_return.rs:140:20 + --> tests/ui/needless_return.rs:144:20 | LL | let _ = || return; | ^^^^^^ @@ -274,7 +274,7 @@ LL | let _ = || {}; | ~~ error: unneeded `return` statement - --> tests/ui/needless_return.rs:146:32 + --> tests/ui/needless_return.rs:150:32 | LL | res.unwrap_or_else(|_| return Foo) | ^^^^^^^^^^ @@ -285,7 +285,7 @@ LL | res.unwrap_or_else(|_| Foo) | ~~~ error: unneeded `return` statement - --> tests/ui/needless_return.rs:155:5 + --> tests/ui/needless_return.rs:159:5 | LL | return true; | ^^^^^^^^^^^ @@ -297,7 +297,7 @@ LL + true | error: unneeded `return` statement - --> tests/ui/needless_return.rs:159:5 + --> tests/ui/needless_return.rs:163:5 | LL | return true; | ^^^^^^^^^^^ @@ -309,7 +309,7 @@ LL + true | error: unneeded `return` statement - --> tests/ui/needless_return.rs:164:9 + --> tests/ui/needless_return.rs:168:9 | LL | return true; | ^^^^^^^^^^^ @@ -321,7 +321,7 @@ LL + true | error: unneeded `return` statement - --> tests/ui/needless_return.rs:166:9 + --> tests/ui/needless_return.rs:170:9 | LL | return false; | ^^^^^^^^^^^^ @@ -333,7 +333,7 @@ LL + false | error: unneeded `return` statement - --> tests/ui/needless_return.rs:172:17 + --> tests/ui/needless_return.rs:176:17 | LL | true => return false, | ^^^^^^^^^^^^ @@ -344,7 +344,7 @@ LL | true => false, | ~~~~~ error: unneeded `return` statement - --> tests/ui/needless_return.rs:174:13 + --> tests/ui/needless_return.rs:178:13 | LL | return true; | ^^^^^^^^^^^ @@ -356,7 +356,7 @@ LL + true | error: unneeded `return` statement - --> tests/ui/needless_return.rs:181:9 + --> tests/ui/needless_return.rs:185:9 | LL | return true; | ^^^^^^^^^^^ @@ -368,7 +368,7 @@ LL + true | error: unneeded `return` statement - --> tests/ui/needless_return.rs:183:16 + --> tests/ui/needless_return.rs:187:16 | LL | let _ = || return true; | ^^^^^^^^^^^ @@ -379,7 +379,7 @@ LL | let _ = || true; | ~~~~ error: unneeded `return` statement - --> tests/ui/needless_return.rs:187:5 + --> tests/ui/needless_return.rs:191:5 | LL | return the_answer!(); | ^^^^^^^^^^^^^^^^^^^^ @@ -391,7 +391,7 @@ LL + the_answer!() | error: unneeded `return` statement - --> tests/ui/needless_return.rs:190:33 + --> tests/ui/needless_return.rs:194:33 | LL | async fn async_test_void_fun() { | _________________________________^ @@ -406,7 +406,7 @@ LL + async fn async_test_void_fun() { | error: unneeded `return` statement - --> tests/ui/needless_return.rs:195:11 + --> tests/ui/needless_return.rs:199:11 | LL | if b { | ___________^ @@ -421,7 +421,7 @@ LL + if b { | error: unneeded `return` statement - --> tests/ui/needless_return.rs:197:13 + --> tests/ui/needless_return.rs:201:13 | LL | } else { | _____________^ @@ -436,7 +436,7 @@ LL + } else { | error: unneeded `return` statement - --> tests/ui/needless_return.rs:205:14 + --> tests/ui/needless_return.rs:209:14 | LL | _ => return, | ^^^^^^ @@ -447,7 +447,7 @@ LL | _ => (), | ~~ error: unneeded `return` statement - --> tests/ui/needless_return.rs:218:9 + --> tests/ui/needless_return.rs:222:9 | LL | return String::from("test"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -459,7 +459,7 @@ LL + String::from("test") | error: unneeded `return` statement - --> tests/ui/needless_return.rs:220:9 + --> tests/ui/needless_return.rs:224:9 | LL | return String::new(); | ^^^^^^^^^^^^^^^^^^^^ @@ -471,7 +471,7 @@ LL + String::new() | error: unneeded `return` statement - --> tests/ui/needless_return.rs:236:5 + --> tests/ui/needless_return.rs:240:5 | LL | return format!("Hello {}", "world!"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -483,7 +483,7 @@ LL + format!("Hello {}", "world!") | error: unneeded `return` statement - --> tests/ui/needless_return.rs:277:9 + --> tests/ui/needless_return.rs:281:9 | LL | return true; | ^^^^^^^^^^^ @@ -497,7 +497,7 @@ LL ~ } | error: unneeded `return` statement - --> tests/ui/needless_return.rs:279:9 + --> tests/ui/needless_return.rs:283:9 | LL | return false; | ^^^^^^^^^^^^ @@ -509,7 +509,7 @@ LL ~ } | error: unneeded `return` statement - --> tests/ui/needless_return.rs:286:13 + --> tests/ui/needless_return.rs:290:13 | LL | return 10; | ^^^^^^^^^ @@ -524,7 +524,7 @@ LL ~ } | error: unneeded `return` statement - --> tests/ui/needless_return.rs:289:13 + --> tests/ui/needless_return.rs:293:13 | LL | return 100; | ^^^^^^^^^^ @@ -537,7 +537,7 @@ LL ~ } | error: unneeded `return` statement - --> tests/ui/needless_return.rs:297:9 + --> tests/ui/needless_return.rs:301:9 | LL | return 0; | ^^^^^^^^ @@ -549,7 +549,7 @@ LL ~ } | error: unneeded `return` statement - --> tests/ui/needless_return.rs:304:13 + --> tests/ui/needless_return.rs:308:13 | LL | return *(x as *const isize); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -564,7 +564,7 @@ LL ~ } | error: unneeded `return` statement - --> tests/ui/needless_return.rs:306:13 + --> tests/ui/needless_return.rs:310:13 | LL | return !*(x as *const isize); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -577,7 +577,7 @@ LL ~ } | error: unneeded `return` statement - --> tests/ui/needless_return.rs:313:20 + --> tests/ui/needless_return.rs:317:20 | LL | let _ = 42; | ____________________^ @@ -594,7 +594,7 @@ LL + let _ = 42; | error: unneeded `return` statement - --> tests/ui/needless_return.rs:320:20 + --> tests/ui/needless_return.rs:324:20 | LL | let _ = 42; return; | ^^^^^^^ @@ -606,7 +606,7 @@ LL + let _ = 42; | error: unneeded `return` statement - --> tests/ui/needless_return.rs:332:9 + --> tests/ui/needless_return.rs:336:9 | LL | return Ok(format!("ok!")); | ^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -618,7 +618,7 @@ LL + Ok(format!("ok!")) | error: unneeded `return` statement - --> tests/ui/needless_return.rs:334:9 + --> tests/ui/needless_return.rs:338:9 | LL | return Err(format!("err!")); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -630,7 +630,7 @@ LL + Err(format!("err!")) | error: unneeded `return` statement - --> tests/ui/needless_return.rs:340:9 + --> tests/ui/needless_return.rs:344:9 | LL | return if true { 1 } else { 2 }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -642,7 +642,7 @@ LL + if true { 1 } else { 2 } | error: unneeded `return` statement - --> tests/ui/needless_return.rs:344:9 + --> tests/ui/needless_return.rs:348:9 | LL | return if b1 { 0 } else { 1 } | if b2 { 2 } else { 3 } | if b3 { 4 } else { 5 }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -654,7 +654,7 @@ LL + (if b1 { 0 } else { 1 } | if b2 { 2 } else { 3 } | if b3 { 4 } else | error: unneeded `return` statement - --> tests/ui/needless_return.rs:365:5 + --> tests/ui/needless_return.rs:369:5 | LL | return { "a".to_string() } + "b" + { "c" }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -666,7 +666,7 @@ LL + ({ "a".to_string() } + "b" + { "c" }) | error: unneeded `return` statement - --> tests/ui/needless_return.rs:369:5 + --> tests/ui/needless_return.rs:373:5 | LL | return "".split("").next().unwrap().to_string(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ From 55834a362c1e849bbab9cb0adc641a22be598d7e Mon Sep 17 00:00:00 2001 From: y21 <30553356+y21@users.noreply.github.com> Date: Thu, 26 Sep 2024 17:49:14 +0200 Subject: [PATCH 13/87] deal with differing syntax contexts for subexpressions in check_proc_macro --- clippy_utils/src/check_proc_macro.rs | 135 ++++++++++++++++----------- tests/ui/dbg_macro/dbg_macro.fixed | 2 +- tests/ui/dbg_macro/dbg_macro.rs | 2 +- 3 files changed, 83 insertions(+), 56 deletions(-) diff --git a/clippy_utils/src/check_proc_macro.rs b/clippy_utils/src/check_proc_macro.rs index 9143d292f670f..9a18bff3c7afc 100644 --- a/clippy_utils/src/check_proc_macro.rs +++ b/clippy_utils/src/check_proc_macro.rs @@ -141,62 +141,89 @@ fn path_search_pat(path: &Path<'_>) -> (Pat, Pat) { /// Get the search patterns to use for the given expression fn expr_search_pat(tcx: TyCtxt<'_>, e: &Expr<'_>) -> (Pat, Pat) { - match e.kind { - ExprKind::ConstBlock(_) => (Pat::Str("const"), Pat::Str("}")), - // Parenthesis are trimmed from the text before the search patterns are matched. - // See: `span_matches_pat` - ExprKind::Tup([]) => (Pat::Str(")"), Pat::Str("(")), - ExprKind::Unary(UnOp::Deref, e) => (Pat::Str("*"), expr_search_pat(tcx, e).1), - ExprKind::Unary(UnOp::Not, e) => (Pat::Str("!"), expr_search_pat(tcx, e).1), - ExprKind::Unary(UnOp::Neg, e) => (Pat::Str("-"), expr_search_pat(tcx, e).1), - ExprKind::Lit(lit) => lit_search_pat(&lit.node), - ExprKind::Array(_) | ExprKind::Repeat(..) => (Pat::Str("["), Pat::Str("]")), - ExprKind::Call(e, []) | ExprKind::MethodCall(_, e, [], _) => (expr_search_pat(tcx, e).0, Pat::Str("(")), - ExprKind::Call(first, [.., last]) - | ExprKind::MethodCall(_, first, [.., last], _) - | ExprKind::Binary(_, first, last) - | ExprKind::Tup([first, .., last]) - | ExprKind::Assign(first, last, _) - | ExprKind::AssignOp(_, first, last) => (expr_search_pat(tcx, first).0, expr_search_pat(tcx, last).1), - ExprKind::Tup([e]) | ExprKind::DropTemps(e) => expr_search_pat(tcx, e), - ExprKind::Cast(e, _) | ExprKind::Type(e, _) => (expr_search_pat(tcx, e).0, Pat::Str("")), - ExprKind::Let(let_expr) => (Pat::Str("let"), expr_search_pat(tcx, let_expr.init).1), - ExprKind::If(..) => (Pat::Str("if"), Pat::Str("}")), - ExprKind::Loop(_, Some(_), _, _) | ExprKind::Block(_, Some(_)) => (Pat::Str("'"), Pat::Str("}")), - ExprKind::Loop(_, None, LoopSource::Loop, _) => (Pat::Str("loop"), Pat::Str("}")), - ExprKind::Loop(_, None, LoopSource::While, _) => (Pat::Str("while"), Pat::Str("}")), - ExprKind::Loop(_, None, LoopSource::ForLoop, _) | ExprKind::Match(_, _, MatchSource::ForLoopDesugar) => { - (Pat::Str("for"), Pat::Str("}")) - }, - ExprKind::Match(_, _, MatchSource::Normal) => (Pat::Str("match"), Pat::Str("}")), - ExprKind::Match(e, _, MatchSource::TryDesugar(_)) => (expr_search_pat(tcx, e).0, Pat::Str("?")), - ExprKind::Match(e, _, MatchSource::AwaitDesugar) | ExprKind::Yield(e, YieldSource::Await { .. }) => { - (expr_search_pat(tcx, e).0, Pat::Str("await")) - }, - ExprKind::Closure(&Closure { body, .. }) => (Pat::Str(""), expr_search_pat(tcx, tcx.hir().body(body).value).1), - ExprKind::Block( - Block { - rules: BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided), - .. + fn expr_search_pat_inner(tcx: TyCtxt<'_>, e: &Expr<'_>, outer_span: Span) -> (Pat, Pat) { + // The expression can have subexpressions in different contexts, in which case + // building up a search pattern from the macro expansion would lead to false positives; + // e.g. `return format!(..)` would be considered to be from a proc macro + // if we build up a pattern for the macro expansion and compare it to the invocation `format!()`. + // So instead we return an empty pattern such that `span_matches_pat` always returns true. + if !e.span.eq_ctxt(outer_span) { + return (Pat::Str(""), Pat::Str("")); + } + + match e.kind { + ExprKind::ConstBlock(_) => (Pat::Str("const"), Pat::Str("}")), + // Parenthesis are trimmed from the text before the search patterns are matched. + // See: `span_matches_pat` + ExprKind::Tup([]) => (Pat::Str(")"), Pat::Str("(")), + ExprKind::Unary(UnOp::Deref, e) => (Pat::Str("*"), expr_search_pat_inner(tcx, e, outer_span).1), + ExprKind::Unary(UnOp::Not, e) => (Pat::Str("!"), expr_search_pat_inner(tcx, e, outer_span).1), + ExprKind::Unary(UnOp::Neg, e) => (Pat::Str("-"), expr_search_pat_inner(tcx, e, outer_span).1), + ExprKind::Lit(lit) => lit_search_pat(&lit.node), + ExprKind::Array(_) | ExprKind::Repeat(..) => (Pat::Str("["), Pat::Str("]")), + ExprKind::Call(e, []) | ExprKind::MethodCall(_, e, [], _) => { + (expr_search_pat_inner(tcx, e, outer_span).0, Pat::Str("(")) }, - None, - ) => (Pat::Str("unsafe"), Pat::Str("}")), - ExprKind::Block(_, None) => (Pat::Str("{"), Pat::Str("}")), - ExprKind::Field(e, name) => (expr_search_pat(tcx, e).0, Pat::Sym(name.name)), - ExprKind::Index(e, _, _) => (expr_search_pat(tcx, e).0, Pat::Str("]")), - ExprKind::Path(ref path) => qpath_search_pat(path), - ExprKind::AddrOf(_, _, e) => (Pat::Str("&"), expr_search_pat(tcx, e).1), - ExprKind::Break(Destination { label: None, .. }, None) => (Pat::Str("break"), Pat::Str("break")), - ExprKind::Break(Destination { label: Some(name), .. }, None) => (Pat::Str("break"), Pat::Sym(name.ident.name)), - ExprKind::Break(_, Some(e)) => (Pat::Str("break"), expr_search_pat(tcx, e).1), - ExprKind::Continue(Destination { label: None, .. }) => (Pat::Str("continue"), Pat::Str("continue")), - ExprKind::Continue(Destination { label: Some(name), .. }) => (Pat::Str("continue"), Pat::Sym(name.ident.name)), - ExprKind::Ret(None) => (Pat::Str("return"), Pat::Str("return")), - ExprKind::Ret(Some(e)) => (Pat::Str("return"), expr_search_pat(tcx, e).1), - ExprKind::Struct(path, _, _) => (qpath_search_pat(path).0, Pat::Str("}")), - ExprKind::Yield(e, YieldSource::Yield) => (Pat::Str("yield"), expr_search_pat(tcx, e).1), - _ => (Pat::Str(""), Pat::Str("")), + ExprKind::Call(first, [.., last]) + | ExprKind::MethodCall(_, first, [.., last], _) + | ExprKind::Binary(_, first, last) + | ExprKind::Tup([first, .., last]) + | ExprKind::Assign(first, last, _) + | ExprKind::AssignOp(_, first, last) => ( + expr_search_pat_inner(tcx, first, outer_span).0, + expr_search_pat_inner(tcx, last, outer_span).1, + ), + ExprKind::Tup([e]) | ExprKind::DropTemps(e) => expr_search_pat_inner(tcx, e, outer_span), + ExprKind::Cast(e, _) | ExprKind::Type(e, _) => (expr_search_pat_inner(tcx, e, outer_span).0, Pat::Str("")), + ExprKind::Let(let_expr) => (Pat::Str("let"), expr_search_pat_inner(tcx, let_expr.init, outer_span).1), + ExprKind::If(..) => (Pat::Str("if"), Pat::Str("}")), + ExprKind::Loop(_, Some(_), _, _) | ExprKind::Block(_, Some(_)) => (Pat::Str("'"), Pat::Str("}")), + ExprKind::Loop(_, None, LoopSource::Loop, _) => (Pat::Str("loop"), Pat::Str("}")), + ExprKind::Loop(_, None, LoopSource::While, _) => (Pat::Str("while"), Pat::Str("}")), + ExprKind::Loop(_, None, LoopSource::ForLoop, _) | ExprKind::Match(_, _, MatchSource::ForLoopDesugar) => { + (Pat::Str("for"), Pat::Str("}")) + }, + ExprKind::Match(_, _, MatchSource::Normal) => (Pat::Str("match"), Pat::Str("}")), + ExprKind::Match(e, _, MatchSource::TryDesugar(_)) => { + (expr_search_pat_inner(tcx, e, outer_span).0, Pat::Str("?")) + }, + ExprKind::Match(e, _, MatchSource::AwaitDesugar) | ExprKind::Yield(e, YieldSource::Await { .. }) => { + (expr_search_pat_inner(tcx, e, outer_span).0, Pat::Str("await")) + }, + ExprKind::Closure(&Closure { body, .. }) => ( + Pat::Str(""), + expr_search_pat_inner(tcx, tcx.hir().body(body).value, outer_span).1, + ), + ExprKind::Block( + Block { + rules: BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided), + .. + }, + None, + ) => (Pat::Str("unsafe"), Pat::Str("}")), + ExprKind::Block(_, None) => (Pat::Str("{"), Pat::Str("}")), + ExprKind::Field(e, name) => (expr_search_pat_inner(tcx, e, outer_span).0, Pat::Sym(name.name)), + ExprKind::Index(e, _, _) => (expr_search_pat_inner(tcx, e, outer_span).0, Pat::Str("]")), + ExprKind::Path(ref path) => qpath_search_pat(path), + ExprKind::AddrOf(_, _, e) => (Pat::Str("&"), expr_search_pat_inner(tcx, e, outer_span).1), + ExprKind::Break(Destination { label: None, .. }, None) => (Pat::Str("break"), Pat::Str("break")), + ExprKind::Break(Destination { label: Some(name), .. }, None) => { + (Pat::Str("break"), Pat::Sym(name.ident.name)) + }, + ExprKind::Break(_, Some(e)) => (Pat::Str("break"), expr_search_pat_inner(tcx, e, outer_span).1), + ExprKind::Continue(Destination { label: None, .. }) => (Pat::Str("continue"), Pat::Str("continue")), + ExprKind::Continue(Destination { label: Some(name), .. }) => { + (Pat::Str("continue"), Pat::Sym(name.ident.name)) + }, + ExprKind::Ret(None) => (Pat::Str("return"), Pat::Str("return")), + ExprKind::Ret(Some(e)) => (Pat::Str("return"), expr_search_pat_inner(tcx, e, outer_span).1), + ExprKind::Struct(path, _, _) => (qpath_search_pat(path).0, Pat::Str("}")), + ExprKind::Yield(e, YieldSource::Yield) => (Pat::Str("yield"), expr_search_pat_inner(tcx, e, outer_span).1), + _ => (Pat::Str(""), Pat::Str("")), + } } + + expr_search_pat_inner(tcx, e, e.span) } fn fn_header_search_pat(header: FnHeader) -> Pat { diff --git a/tests/ui/dbg_macro/dbg_macro.fixed b/tests/ui/dbg_macro/dbg_macro.fixed index e352519142332..bda9221a5e1e9 100644 --- a/tests/ui/dbg_macro/dbg_macro.fixed +++ b/tests/ui/dbg_macro/dbg_macro.fixed @@ -1,5 +1,5 @@ #![warn(clippy::dbg_macro)] -#![allow(clippy::unnecessary_operation, clippy::no_effect)] +#![allow(clippy::unnecessary_operation, clippy::no_effect, clippy::unit_arg)] fn foo(n: u32) -> u32 { if let Some(n) = n.checked_sub(4) { n } else { n } diff --git a/tests/ui/dbg_macro/dbg_macro.rs b/tests/ui/dbg_macro/dbg_macro.rs index 80606c2db054a..8244254026be7 100644 --- a/tests/ui/dbg_macro/dbg_macro.rs +++ b/tests/ui/dbg_macro/dbg_macro.rs @@ -1,5 +1,5 @@ #![warn(clippy::dbg_macro)] -#![allow(clippy::unnecessary_operation, clippy::no_effect)] +#![allow(clippy::unnecessary_operation, clippy::no_effect, clippy::unit_arg)] fn foo(n: u32) -> u32 { if let Some(n) = dbg!(n.checked_sub(4)) { n } else { n } From d1dec199eea9678b0b3d728100ff356b0cd6f216 Mon Sep 17 00:00:00 2001 From: Yuri Astrakhan Date: Mon, 2 Sep 2024 02:20:35 -0400 Subject: [PATCH 14/87] Add manual_ignore_cast_cmp lint --- CHANGELOG.md | 1 + clippy_lints/src/declared_lints.rs | 1 + clippy_lints/src/lib.rs | 2 + clippy_lints/src/manual_ignore_case_cmp.rs | 127 +++++ tests/ui/manual_ignore_case_cmp.fixed | 107 ++++ tests/ui/manual_ignore_case_cmp.rs | 107 ++++ tests/ui/manual_ignore_case_cmp.stderr | 546 +++++++++++++++++++++ 7 files changed, 891 insertions(+) create mode 100644 clippy_lints/src/manual_ignore_case_cmp.rs create mode 100644 tests/ui/manual_ignore_case_cmp.fixed create mode 100644 tests/ui/manual_ignore_case_cmp.rs create mode 100644 tests/ui/manual_ignore_case_cmp.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d253d52531e6..98be47ec5a199 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5621,6 +5621,7 @@ Released 2018-09-13 [`manual_find_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_find_map [`manual_flatten`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_flatten [`manual_hash_one`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_hash_one +[`manual_ignore_case_cmp`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_ignore_case_cmp [`manual_inspect`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_inspect [`manual_instant_elapsed`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_instant_elapsed [`manual_is_ascii_check`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_is_ascii_check diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 9cec672beb004..0b37dffd7323e 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -306,6 +306,7 @@ pub static LINTS: &[&crate::LintInfo] = &[ crate::manual_float_methods::MANUAL_IS_FINITE_INFO, crate::manual_float_methods::MANUAL_IS_INFINITE_INFO, crate::manual_hash_one::MANUAL_HASH_ONE_INFO, + crate::manual_ignore_case_cmp::MANUAL_IGNORE_CASE_CMP_INFO, crate::manual_is_ascii_check::MANUAL_IS_ASCII_CHECK_INFO, crate::manual_is_power_of_two::MANUAL_IS_POWER_OF_TWO_INFO, crate::manual_let_else::MANUAL_LET_ELSE_INFO, diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 1d41f568f3785..56722e7f537b1 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -206,6 +206,7 @@ mod manual_clamp; mod manual_div_ceil; mod manual_float_methods; mod manual_hash_one; +mod manual_ignore_case_cmp; mod manual_is_ascii_check; mod manual_is_power_of_two; mod manual_let_else; @@ -944,5 +945,6 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { store.register_late_pass(|_| Box::new(manual_is_power_of_two::ManualIsPowerOfTwo)); store.register_late_pass(|_| Box::new(non_zero_suggestions::NonZeroSuggestions)); store.register_late_pass(move |_| Box::new(unused_trait_names::UnusedTraitNames::new(conf))); + store.register_late_pass(|_| Box::new(manual_ignore_case_cmp::ManualIgnoreCaseCmp)); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/clippy_lints/src/manual_ignore_case_cmp.rs b/clippy_lints/src/manual_ignore_case_cmp.rs new file mode 100644 index 0000000000000..dabfac3f6137b --- /dev/null +++ b/clippy_lints/src/manual_ignore_case_cmp.rs @@ -0,0 +1,127 @@ +use crate::manual_ignore_case_cmp::MatchType::{Literal, ToAscii}; +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::source::snippet_with_applicability; +use clippy_utils::ty::{get_type_diagnostic_name, is_type_diagnostic_item, is_type_lang_item}; +use rustc_ast::LitKind; +use rustc_errors::Applicability; +use rustc_hir::ExprKind::{Binary, Lit, MethodCall}; +use rustc_hir::{BinOpKind, Expr, LangItem}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty; +use rustc_middle::ty::{Ty, UintTy}; +use rustc_session::declare_lint_pass; +use rustc_span::{Span, sym}; + +declare_clippy_lint! { + /// ### What it does + /// Checks for manual case-insensitive ASCII comparison. + /// + /// ### Why is this bad? + /// The `eq_ignore_ascii_case` method is faster because it does not allocate + /// memory for the new strings, and it is more readable. + /// + /// ### Example + /// ```no_run + /// fn compare(a: &str, b: &str) -> bool { + /// a.to_ascii_lowercase() == b.to_ascii_lowercase() || a.to_ascii_lowercase() == "abc" + /// } + /// ``` + /// Use instead: + /// ```no_run + /// fn compare(a: &str, b: &str) -> bool { + /// a.eq_ignore_ascii_case(b) || a.eq_ignore_ascii_case("abc") + /// } + /// ``` + #[clippy::version = "1.82.0"] + pub MANUAL_IGNORE_CASE_CMP, + perf, + "manual case-insensitive ASCII comparison" +} + +declare_lint_pass!(ManualIgnoreCaseCmp => [MANUAL_IGNORE_CASE_CMP]); + +enum MatchType<'a, 'b> { + ToAscii(bool, Ty<'a>), + Literal(&'b LitKind), +} + +fn get_ascii_type<'a, 'b>(cx: &LateContext<'a>, kind: rustc_hir::ExprKind<'b>) -> Option<(Span, MatchType<'a, 'b>)> { + if let MethodCall(path, expr, _, _) = kind { + let is_lower = match path.ident.name.as_str() { + "to_ascii_lowercase" => true, + "to_ascii_uppercase" => false, + _ => return None, + }; + let ty_raw = cx.typeck_results().expr_ty(expr); + let ty = ty_raw.peel_refs(); + if needs_ref_to_cmp(cx, ty) + || ty.is_str() + || ty.is_slice() + || matches!(get_type_diagnostic_name(cx, ty), Some(sym::OsStr | sym::OsString)) + { + return Some((expr.span, ToAscii(is_lower, ty_raw))); + } + } else if let Lit(expr) = kind { + return Some((expr.span, Literal(&expr.node))); + } + None +} + +/// Returns true if the type needs to be dereferenced to be compared +fn needs_ref_to_cmp(cx: &LateContext<'_>, ty: Ty<'_>) -> bool { + ty.is_char() + || *ty.kind() == ty::Uint(UintTy::U8) + || is_type_diagnostic_item(cx, ty, sym::Vec) + || is_type_lang_item(cx, ty, LangItem::String) +} + +impl LateLintPass<'_> for ManualIgnoreCaseCmp { + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) { + // check if expression represents a comparison of two strings + // using .to_ascii_lowercase() or .to_ascii_uppercase() methods, + // or one of the sides is a literal + // Offer to replace it with .eq_ignore_ascii_case() method + if let Binary(op, left, right) = &expr.kind + && (op.node == BinOpKind::Eq || op.node == BinOpKind::Ne) + && let Some((left_span, left_val)) = get_ascii_type(cx, left.kind) + && let Some((right_span, right_val)) = get_ascii_type(cx, right.kind) + && match (&left_val, &right_val) { + (ToAscii(l_lower, ..), ToAscii(r_lower, ..)) if l_lower == r_lower => true, + (ToAscii(..), Literal(..)) | (Literal(..), ToAscii(..)) => true, + _ => false, + } + { + let deref = match right_val { + ToAscii(_, ty) if needs_ref_to_cmp(cx, ty) => "&", + ToAscii(..) => "", + Literal(ty) => { + if let LitKind::Char(_) | LitKind::Byte(_) = ty { + "&" + } else { + "" + } + }, + }; + let neg = if op.node == BinOpKind::Ne { "!" } else { "" }; + span_lint_and_then( + cx, + MANUAL_IGNORE_CASE_CMP, + expr.span, + "manual case-insensitive ASCII comparison", + |diag| { + let mut app = Applicability::MachineApplicable; + diag.span_suggestion_verbose( + expr.span, + "consider using `.eq_ignore_ascii_case()` instead", + format!( + "{neg}{}.eq_ignore_ascii_case({deref}{})", + snippet_with_applicability(cx, left_span, "_", &mut app), + snippet_with_applicability(cx, right_span, "_", &mut app) + ), + app, + ); + }, + ); + } + } +} diff --git a/tests/ui/manual_ignore_case_cmp.fixed b/tests/ui/manual_ignore_case_cmp.fixed new file mode 100644 index 0000000000000..53a124f59c8dc --- /dev/null +++ b/tests/ui/manual_ignore_case_cmp.fixed @@ -0,0 +1,107 @@ +#![allow(clippy::all)] +#![deny(clippy::manual_ignore_case_cmp)] + +use std::ffi::{OsStr, OsString}; + +fn main() {} + +fn variants(a: &str, b: &str) { + if a.eq_ignore_ascii_case(b) { + return; + } + if a.eq_ignore_ascii_case(b) { + return; + } + let r = a.eq_ignore_ascii_case(b); + let r = r || a.eq_ignore_ascii_case(b); + r && a.eq_ignore_ascii_case(&b.to_uppercase()); + // != + if !a.eq_ignore_ascii_case(b) { + return; + } + if !a.eq_ignore_ascii_case(b) { + return; + } + let r = !a.eq_ignore_ascii_case(b); + let r = r || !a.eq_ignore_ascii_case(b); + r && !a.eq_ignore_ascii_case(&b.to_uppercase()); +} + +fn unsupported(a: char, b: char) { + // TODO:: these are rare, and might not be worth supporting + a.to_ascii_lowercase() == char::to_ascii_lowercase(&b); + char::to_ascii_lowercase(&a) == b.to_ascii_lowercase(); + char::to_ascii_lowercase(&a) == char::to_ascii_lowercase(&b); +} + +fn char(a: char, b: char) { + a.eq_ignore_ascii_case(&b); + a.to_ascii_lowercase() == *&b.to_ascii_lowercase(); + *&a.to_ascii_lowercase() == b.to_ascii_lowercase(); + a.eq_ignore_ascii_case(&'a'); + 'a'.eq_ignore_ascii_case(&b); +} +fn u8(a: u8, b: u8) { + a.eq_ignore_ascii_case(&b); + a.eq_ignore_ascii_case(&b'a'); + b'a'.eq_ignore_ascii_case(&b); +} +fn ref_str(a: &str, b: &str) { + a.eq_ignore_ascii_case(b); + a.to_uppercase().eq_ignore_ascii_case(b); + a.eq_ignore_ascii_case("a"); + "a".eq_ignore_ascii_case(b); +} +fn ref_ref_str(a: &&str, b: &&str) { + a.eq_ignore_ascii_case(b); + a.to_uppercase().eq_ignore_ascii_case(b); + a.eq_ignore_ascii_case("a"); + "a".eq_ignore_ascii_case(b); +} +fn string(a: String, b: String) { + a.eq_ignore_ascii_case(&b); + a.eq_ignore_ascii_case("a"); + "a".eq_ignore_ascii_case(&b); + &a.to_ascii_lowercase() == &b.to_ascii_lowercase(); + &&a.to_ascii_lowercase() == &&b.to_ascii_lowercase(); + a.eq_ignore_ascii_case("a"); + "a".eq_ignore_ascii_case(&b); +} +fn ref_string(a: String, b: &String) { + a.eq_ignore_ascii_case(b); + a.eq_ignore_ascii_case("a"); + "a".eq_ignore_ascii_case(b); + + b.eq_ignore_ascii_case(&a); + b.eq_ignore_ascii_case("a"); + "a".eq_ignore_ascii_case(&a); +} +fn string_ref_str(a: String, b: &str) { + a.eq_ignore_ascii_case(b); + a.eq_ignore_ascii_case("a"); + "a".eq_ignore_ascii_case(b); + + b.eq_ignore_ascii_case(&a); + b.eq_ignore_ascii_case("a"); + "a".eq_ignore_ascii_case(&a); +} +fn ref_u8slice(a: &[u8], b: &[u8]) { + a.eq_ignore_ascii_case(b); +} +fn u8vec(a: Vec, b: Vec) { + a.eq_ignore_ascii_case(&b); +} +fn ref_u8vec(a: Vec, b: &Vec) { + a.eq_ignore_ascii_case(b); + b.eq_ignore_ascii_case(&a); +} +fn ref_osstr(a: &OsStr, b: &OsStr) { + a.eq_ignore_ascii_case(b); +} +fn osstring(a: OsString, b: OsString) { + a.eq_ignore_ascii_case(b); +} +fn ref_osstring(a: OsString, b: &OsString) { + a.eq_ignore_ascii_case(b); + b.eq_ignore_ascii_case(a); +} diff --git a/tests/ui/manual_ignore_case_cmp.rs b/tests/ui/manual_ignore_case_cmp.rs new file mode 100644 index 0000000000000..2a4d84b30acec --- /dev/null +++ b/tests/ui/manual_ignore_case_cmp.rs @@ -0,0 +1,107 @@ +#![allow(clippy::all)] +#![deny(clippy::manual_ignore_case_cmp)] + +use std::ffi::{OsStr, OsString}; + +fn main() {} + +fn variants(a: &str, b: &str) { + if a.to_ascii_lowercase() == b.to_ascii_lowercase() { + return; + } + if a.to_ascii_uppercase() == b.to_ascii_uppercase() { + return; + } + let r = a.to_ascii_lowercase() == b.to_ascii_lowercase(); + let r = r || a.to_ascii_uppercase() == b.to_ascii_uppercase(); + r && a.to_ascii_lowercase() == b.to_uppercase().to_ascii_lowercase(); + // != + if a.to_ascii_lowercase() != b.to_ascii_lowercase() { + return; + } + if a.to_ascii_uppercase() != b.to_ascii_uppercase() { + return; + } + let r = a.to_ascii_lowercase() != b.to_ascii_lowercase(); + let r = r || a.to_ascii_uppercase() != b.to_ascii_uppercase(); + r && a.to_ascii_lowercase() != b.to_uppercase().to_ascii_lowercase(); +} + +fn unsupported(a: char, b: char) { + // TODO:: these are rare, and might not be worth supporting + a.to_ascii_lowercase() == char::to_ascii_lowercase(&b); + char::to_ascii_lowercase(&a) == b.to_ascii_lowercase(); + char::to_ascii_lowercase(&a) == char::to_ascii_lowercase(&b); +} + +fn char(a: char, b: char) { + a.to_ascii_lowercase() == b.to_ascii_lowercase(); + a.to_ascii_lowercase() == *&b.to_ascii_lowercase(); + *&a.to_ascii_lowercase() == b.to_ascii_lowercase(); + a.to_ascii_lowercase() == 'a'; + 'a' == b.to_ascii_lowercase(); +} +fn u8(a: u8, b: u8) { + a.to_ascii_lowercase() == b.to_ascii_lowercase(); + a.to_ascii_lowercase() == b'a'; + b'a' == b.to_ascii_lowercase(); +} +fn ref_str(a: &str, b: &str) { + a.to_ascii_lowercase() == b.to_ascii_lowercase(); + a.to_uppercase().to_ascii_lowercase() == b.to_ascii_lowercase(); + a.to_ascii_lowercase() == "a"; + "a" == b.to_ascii_lowercase(); +} +fn ref_ref_str(a: &&str, b: &&str) { + a.to_ascii_lowercase() == b.to_ascii_lowercase(); + a.to_uppercase().to_ascii_lowercase() == b.to_ascii_lowercase(); + a.to_ascii_lowercase() == "a"; + "a" == b.to_ascii_lowercase(); +} +fn string(a: String, b: String) { + a.to_ascii_lowercase() == b.to_ascii_lowercase(); + a.to_ascii_lowercase() == "a"; + "a" == b.to_ascii_lowercase(); + &a.to_ascii_lowercase() == &b.to_ascii_lowercase(); + &&a.to_ascii_lowercase() == &&b.to_ascii_lowercase(); + a.to_ascii_lowercase() == "a"; + "a" == b.to_ascii_lowercase(); +} +fn ref_string(a: String, b: &String) { + a.to_ascii_lowercase() == b.to_ascii_lowercase(); + a.to_ascii_lowercase() == "a"; + "a" == b.to_ascii_lowercase(); + + b.to_ascii_lowercase() == a.to_ascii_lowercase(); + b.to_ascii_lowercase() == "a"; + "a" == a.to_ascii_lowercase(); +} +fn string_ref_str(a: String, b: &str) { + a.to_ascii_lowercase() == b.to_ascii_lowercase(); + a.to_ascii_lowercase() == "a"; + "a" == b.to_ascii_lowercase(); + + b.to_ascii_lowercase() == a.to_ascii_lowercase(); + b.to_ascii_lowercase() == "a"; + "a" == a.to_ascii_lowercase(); +} +fn ref_u8slice(a: &[u8], b: &[u8]) { + a.to_ascii_lowercase() == b.to_ascii_lowercase(); +} +fn u8vec(a: Vec, b: Vec) { + a.to_ascii_lowercase() == b.to_ascii_lowercase(); +} +fn ref_u8vec(a: Vec, b: &Vec) { + a.to_ascii_lowercase() == b.to_ascii_lowercase(); + b.to_ascii_lowercase() == a.to_ascii_lowercase(); +} +fn ref_osstr(a: &OsStr, b: &OsStr) { + a.to_ascii_lowercase() == b.to_ascii_lowercase(); +} +fn osstring(a: OsString, b: OsString) { + a.to_ascii_lowercase() == b.to_ascii_lowercase(); +} +fn ref_osstring(a: OsString, b: &OsString) { + a.to_ascii_lowercase() == b.to_ascii_lowercase(); + b.to_ascii_lowercase() == a.to_ascii_lowercase(); +} diff --git a/tests/ui/manual_ignore_case_cmp.stderr b/tests/ui/manual_ignore_case_cmp.stderr new file mode 100644 index 0000000000000..11e8b8aebb541 --- /dev/null +++ b/tests/ui/manual_ignore_case_cmp.stderr @@ -0,0 +1,546 @@ +error: manual case-insensitive ASCII comparison + --> tests/ui/manual_ignore_case_cmp.rs:9:8 + | +LL | if a.to_ascii_lowercase() == b.to_ascii_lowercase() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: the lint level is defined here + --> tests/ui/manual_ignore_case_cmp.rs:2:9 + | +LL | #![deny(clippy::manual_ignore_case_cmp)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +help: consider using `.eq_ignore_ascii_case()` instead + | +LL | if a.eq_ignore_ascii_case(b) { + | ~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: manual case-insensitive ASCII comparison + --> tests/ui/manual_ignore_case_cmp.rs:12:8 + | +LL | if a.to_ascii_uppercase() == b.to_ascii_uppercase() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using `.eq_ignore_ascii_case()` instead + | +LL | if a.eq_ignore_ascii_case(b) { + | ~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: manual case-insensitive ASCII comparison + --> tests/ui/manual_ignore_case_cmp.rs:15:13 + | +LL | let r = a.to_ascii_lowercase() == b.to_ascii_lowercase(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using `.eq_ignore_ascii_case()` instead + | +LL | let r = a.eq_ignore_ascii_case(b); + | ~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: manual case-insensitive ASCII comparison + --> tests/ui/manual_ignore_case_cmp.rs:16:18 + | +LL | let r = r || a.to_ascii_uppercase() == b.to_ascii_uppercase(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using `.eq_ignore_ascii_case()` instead + | +LL | let r = r || a.eq_ignore_ascii_case(b); + | ~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: manual case-insensitive ASCII comparison + --> tests/ui/manual_ignore_case_cmp.rs:17:10 + | +LL | r && a.to_ascii_lowercase() == b.to_uppercase().to_ascii_lowercase(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using `.eq_ignore_ascii_case()` instead + | +LL | r && a.eq_ignore_ascii_case(&b.to_uppercase()); + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: manual case-insensitive ASCII comparison + --> tests/ui/manual_ignore_case_cmp.rs:19:8 + | +LL | if a.to_ascii_lowercase() != b.to_ascii_lowercase() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using `.eq_ignore_ascii_case()` instead + | +LL | if !a.eq_ignore_ascii_case(b) { + | ~~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: manual case-insensitive ASCII comparison + --> tests/ui/manual_ignore_case_cmp.rs:22:8 + | +LL | if a.to_ascii_uppercase() != b.to_ascii_uppercase() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using `.eq_ignore_ascii_case()` instead + | +LL | if !a.eq_ignore_ascii_case(b) { + | ~~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: manual case-insensitive ASCII comparison + --> tests/ui/manual_ignore_case_cmp.rs:25:13 + | +LL | let r = a.to_ascii_lowercase() != b.to_ascii_lowercase(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using `.eq_ignore_ascii_case()` instead + | +LL | let r = !a.eq_ignore_ascii_case(b); + | ~~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: manual case-insensitive ASCII comparison + --> tests/ui/manual_ignore_case_cmp.rs:26:18 + | +LL | let r = r || a.to_ascii_uppercase() != b.to_ascii_uppercase(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using `.eq_ignore_ascii_case()` instead + | +LL | let r = r || !a.eq_ignore_ascii_case(b); + | ~~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: manual case-insensitive ASCII comparison + --> tests/ui/manual_ignore_case_cmp.rs:27:10 + | +LL | r && a.to_ascii_lowercase() != b.to_uppercase().to_ascii_lowercase(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using `.eq_ignore_ascii_case()` instead + | +LL | r && !a.eq_ignore_ascii_case(&b.to_uppercase()); + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: manual case-insensitive ASCII comparison + --> tests/ui/manual_ignore_case_cmp.rs:38:5 + | +LL | a.to_ascii_lowercase() == b.to_ascii_lowercase(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using `.eq_ignore_ascii_case()` instead + | +LL | a.eq_ignore_ascii_case(&b); + | ~~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: manual case-insensitive ASCII comparison + --> tests/ui/manual_ignore_case_cmp.rs:41:5 + | +LL | a.to_ascii_lowercase() == 'a'; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using `.eq_ignore_ascii_case()` instead + | +LL | a.eq_ignore_ascii_case(&'a'); + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: manual case-insensitive ASCII comparison + --> tests/ui/manual_ignore_case_cmp.rs:42:5 + | +LL | 'a' == b.to_ascii_lowercase(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using `.eq_ignore_ascii_case()` instead + | +LL | 'a'.eq_ignore_ascii_case(&b); + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: manual case-insensitive ASCII comparison + --> tests/ui/manual_ignore_case_cmp.rs:45:5 + | +LL | a.to_ascii_lowercase() == b.to_ascii_lowercase(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using `.eq_ignore_ascii_case()` instead + | +LL | a.eq_ignore_ascii_case(&b); + | ~~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: manual case-insensitive ASCII comparison + --> tests/ui/manual_ignore_case_cmp.rs:46:5 + | +LL | a.to_ascii_lowercase() == b'a'; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using `.eq_ignore_ascii_case()` instead + | +LL | a.eq_ignore_ascii_case(&b'a'); + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: manual case-insensitive ASCII comparison + --> tests/ui/manual_ignore_case_cmp.rs:47:5 + | +LL | b'a' == b.to_ascii_lowercase(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using `.eq_ignore_ascii_case()` instead + | +LL | b'a'.eq_ignore_ascii_case(&b); + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: manual case-insensitive ASCII comparison + --> tests/ui/manual_ignore_case_cmp.rs:50:5 + | +LL | a.to_ascii_lowercase() == b.to_ascii_lowercase(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using `.eq_ignore_ascii_case()` instead + | +LL | a.eq_ignore_ascii_case(b); + | ~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: manual case-insensitive ASCII comparison + --> tests/ui/manual_ignore_case_cmp.rs:51:5 + | +LL | a.to_uppercase().to_ascii_lowercase() == b.to_ascii_lowercase(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using `.eq_ignore_ascii_case()` instead + | +LL | a.to_uppercase().eq_ignore_ascii_case(b); + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: manual case-insensitive ASCII comparison + --> tests/ui/manual_ignore_case_cmp.rs:52:5 + | +LL | a.to_ascii_lowercase() == "a"; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using `.eq_ignore_ascii_case()` instead + | +LL | a.eq_ignore_ascii_case("a"); + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: manual case-insensitive ASCII comparison + --> tests/ui/manual_ignore_case_cmp.rs:53:5 + | +LL | "a" == b.to_ascii_lowercase(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using `.eq_ignore_ascii_case()` instead + | +LL | "a".eq_ignore_ascii_case(b); + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: manual case-insensitive ASCII comparison + --> tests/ui/manual_ignore_case_cmp.rs:56:5 + | +LL | a.to_ascii_lowercase() == b.to_ascii_lowercase(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using `.eq_ignore_ascii_case()` instead + | +LL | a.eq_ignore_ascii_case(b); + | ~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: manual case-insensitive ASCII comparison + --> tests/ui/manual_ignore_case_cmp.rs:57:5 + | +LL | a.to_uppercase().to_ascii_lowercase() == b.to_ascii_lowercase(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using `.eq_ignore_ascii_case()` instead + | +LL | a.to_uppercase().eq_ignore_ascii_case(b); + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: manual case-insensitive ASCII comparison + --> tests/ui/manual_ignore_case_cmp.rs:58:5 + | +LL | a.to_ascii_lowercase() == "a"; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using `.eq_ignore_ascii_case()` instead + | +LL | a.eq_ignore_ascii_case("a"); + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: manual case-insensitive ASCII comparison + --> tests/ui/manual_ignore_case_cmp.rs:59:5 + | +LL | "a" == b.to_ascii_lowercase(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using `.eq_ignore_ascii_case()` instead + | +LL | "a".eq_ignore_ascii_case(b); + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: manual case-insensitive ASCII comparison + --> tests/ui/manual_ignore_case_cmp.rs:62:5 + | +LL | a.to_ascii_lowercase() == b.to_ascii_lowercase(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using `.eq_ignore_ascii_case()` instead + | +LL | a.eq_ignore_ascii_case(&b); + | ~~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: manual case-insensitive ASCII comparison + --> tests/ui/manual_ignore_case_cmp.rs:63:5 + | +LL | a.to_ascii_lowercase() == "a"; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using `.eq_ignore_ascii_case()` instead + | +LL | a.eq_ignore_ascii_case("a"); + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: manual case-insensitive ASCII comparison + --> tests/ui/manual_ignore_case_cmp.rs:64:5 + | +LL | "a" == b.to_ascii_lowercase(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using `.eq_ignore_ascii_case()` instead + | +LL | "a".eq_ignore_ascii_case(&b); + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: manual case-insensitive ASCII comparison + --> tests/ui/manual_ignore_case_cmp.rs:67:5 + | +LL | a.to_ascii_lowercase() == "a"; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using `.eq_ignore_ascii_case()` instead + | +LL | a.eq_ignore_ascii_case("a"); + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: manual case-insensitive ASCII comparison + --> tests/ui/manual_ignore_case_cmp.rs:68:5 + | +LL | "a" == b.to_ascii_lowercase(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using `.eq_ignore_ascii_case()` instead + | +LL | "a".eq_ignore_ascii_case(&b); + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: manual case-insensitive ASCII comparison + --> tests/ui/manual_ignore_case_cmp.rs:71:5 + | +LL | a.to_ascii_lowercase() == b.to_ascii_lowercase(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using `.eq_ignore_ascii_case()` instead + | +LL | a.eq_ignore_ascii_case(b); + | ~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: manual case-insensitive ASCII comparison + --> tests/ui/manual_ignore_case_cmp.rs:72:5 + | +LL | a.to_ascii_lowercase() == "a"; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using `.eq_ignore_ascii_case()` instead + | +LL | a.eq_ignore_ascii_case("a"); + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: manual case-insensitive ASCII comparison + --> tests/ui/manual_ignore_case_cmp.rs:73:5 + | +LL | "a" == b.to_ascii_lowercase(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using `.eq_ignore_ascii_case()` instead + | +LL | "a".eq_ignore_ascii_case(b); + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: manual case-insensitive ASCII comparison + --> tests/ui/manual_ignore_case_cmp.rs:75:5 + | +LL | b.to_ascii_lowercase() == a.to_ascii_lowercase(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using `.eq_ignore_ascii_case()` instead + | +LL | b.eq_ignore_ascii_case(&a); + | ~~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: manual case-insensitive ASCII comparison + --> tests/ui/manual_ignore_case_cmp.rs:76:5 + | +LL | b.to_ascii_lowercase() == "a"; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using `.eq_ignore_ascii_case()` instead + | +LL | b.eq_ignore_ascii_case("a"); + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: manual case-insensitive ASCII comparison + --> tests/ui/manual_ignore_case_cmp.rs:77:5 + | +LL | "a" == a.to_ascii_lowercase(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using `.eq_ignore_ascii_case()` instead + | +LL | "a".eq_ignore_ascii_case(&a); + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: manual case-insensitive ASCII comparison + --> tests/ui/manual_ignore_case_cmp.rs:80:5 + | +LL | a.to_ascii_lowercase() == b.to_ascii_lowercase(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using `.eq_ignore_ascii_case()` instead + | +LL | a.eq_ignore_ascii_case(b); + | ~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: manual case-insensitive ASCII comparison + --> tests/ui/manual_ignore_case_cmp.rs:81:5 + | +LL | a.to_ascii_lowercase() == "a"; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using `.eq_ignore_ascii_case()` instead + | +LL | a.eq_ignore_ascii_case("a"); + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: manual case-insensitive ASCII comparison + --> tests/ui/manual_ignore_case_cmp.rs:82:5 + | +LL | "a" == b.to_ascii_lowercase(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using `.eq_ignore_ascii_case()` instead + | +LL | "a".eq_ignore_ascii_case(b); + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: manual case-insensitive ASCII comparison + --> tests/ui/manual_ignore_case_cmp.rs:84:5 + | +LL | b.to_ascii_lowercase() == a.to_ascii_lowercase(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using `.eq_ignore_ascii_case()` instead + | +LL | b.eq_ignore_ascii_case(&a); + | ~~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: manual case-insensitive ASCII comparison + --> tests/ui/manual_ignore_case_cmp.rs:85:5 + | +LL | b.to_ascii_lowercase() == "a"; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using `.eq_ignore_ascii_case()` instead + | +LL | b.eq_ignore_ascii_case("a"); + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: manual case-insensitive ASCII comparison + --> tests/ui/manual_ignore_case_cmp.rs:86:5 + | +LL | "a" == a.to_ascii_lowercase(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using `.eq_ignore_ascii_case()` instead + | +LL | "a".eq_ignore_ascii_case(&a); + | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: manual case-insensitive ASCII comparison + --> tests/ui/manual_ignore_case_cmp.rs:89:5 + | +LL | a.to_ascii_lowercase() == b.to_ascii_lowercase(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using `.eq_ignore_ascii_case()` instead + | +LL | a.eq_ignore_ascii_case(b); + | ~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: manual case-insensitive ASCII comparison + --> tests/ui/manual_ignore_case_cmp.rs:92:5 + | +LL | a.to_ascii_lowercase() == b.to_ascii_lowercase(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using `.eq_ignore_ascii_case()` instead + | +LL | a.eq_ignore_ascii_case(&b); + | ~~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: manual case-insensitive ASCII comparison + --> tests/ui/manual_ignore_case_cmp.rs:95:5 + | +LL | a.to_ascii_lowercase() == b.to_ascii_lowercase(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using `.eq_ignore_ascii_case()` instead + | +LL | a.eq_ignore_ascii_case(b); + | ~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: manual case-insensitive ASCII comparison + --> tests/ui/manual_ignore_case_cmp.rs:96:5 + | +LL | b.to_ascii_lowercase() == a.to_ascii_lowercase(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using `.eq_ignore_ascii_case()` instead + | +LL | b.eq_ignore_ascii_case(&a); + | ~~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: manual case-insensitive ASCII comparison + --> tests/ui/manual_ignore_case_cmp.rs:99:5 + | +LL | a.to_ascii_lowercase() == b.to_ascii_lowercase(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using `.eq_ignore_ascii_case()` instead + | +LL | a.eq_ignore_ascii_case(b); + | ~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: manual case-insensitive ASCII comparison + --> tests/ui/manual_ignore_case_cmp.rs:102:5 + | +LL | a.to_ascii_lowercase() == b.to_ascii_lowercase(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using `.eq_ignore_ascii_case()` instead + | +LL | a.eq_ignore_ascii_case(b); + | ~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: manual case-insensitive ASCII comparison + --> tests/ui/manual_ignore_case_cmp.rs:105:5 + | +LL | a.to_ascii_lowercase() == b.to_ascii_lowercase(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using `.eq_ignore_ascii_case()` instead + | +LL | a.eq_ignore_ascii_case(b); + | ~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: manual case-insensitive ASCII comparison + --> tests/ui/manual_ignore_case_cmp.rs:106:5 + | +LL | b.to_ascii_lowercase() == a.to_ascii_lowercase(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: consider using `.eq_ignore_ascii_case()` instead + | +LL | b.eq_ignore_ascii_case(a); + | ~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: aborting due to 49 previous errors + From 13e2633f193838eadcddd5bf5842b9ce0a1f17dd Mon Sep 17 00:00:00 2001 From: blyxyas Date: Tue, 1 Oct 2024 19:39:18 +0200 Subject: [PATCH 15/87] Also sanitize configuration --- clippy_config/src/conf.rs | 24 ++++++++++++++++++++++++ clippy_config/src/lib.rs | 2 +- clippy_lints/src/lib.rs | 26 +------------------------- tests/compile-test.rs | 6 +----- 4 files changed, 27 insertions(+), 31 deletions(-) diff --git a/clippy_config/src/conf.rs b/clippy_config/src/conf.rs index 757620341ccc1..df13be8009880 100644 --- a/clippy_config/src/conf.rs +++ b/clippy_config/src/conf.rs @@ -97,6 +97,30 @@ impl ConfError { } } +// Remove code tags and code behind '# 's, as they are not needed for the lint docs and --explain +pub fn sanitize_explanation(raw_docs: &str) -> String { + // Remove tags and hidden code: + let mut explanation = String::with_capacity(128); + let mut in_code = false; + for line in raw_docs.lines().map(str::trim) { + if let Some(lang) = line.strip_prefix("```") { + let tag = lang.split_once(',').map_or(lang, |(left, _)| left); + if !in_code && matches!(tag, "" | "rust" | "ignore" | "should_panic" | "no_run" | "compile_fail") { + explanation += "```rust\n"; + } else { + explanation += line; + explanation.push('\n'); + } + in_code = !in_code; + } else if !(in_code && line.starts_with("# ")) { + explanation += line; + explanation.push('\n'); + } + } + + explanation +} + macro_rules! wrap_option { () => { None diff --git a/clippy_config/src/lib.rs b/clippy_config/src/lib.rs index c63d98a0a13f9..42651521f8d7a 100644 --- a/clippy_config/src/lib.rs +++ b/clippy_config/src/lib.rs @@ -26,5 +26,5 @@ mod metadata; pub mod msrvs; pub mod types; -pub use conf::{Conf, get_configuration_metadata, lookup_conf_file}; +pub use conf::{Conf, get_configuration_metadata, lookup_conf_file, sanitize_explanation}; pub use metadata::ClippyConfiguration; diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 0bfbb92dfda55..71e7b56408e2f 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -396,7 +396,7 @@ mod zero_sized_map_values; mod zombie_processes; // end lints modules, do not remove this comment, it’s used in `update_lints` -use clippy_config::{Conf, get_configuration_metadata}; +use clippy_config::{Conf, get_configuration_metadata, sanitize_explanation}; use clippy_utils::macros::FormatArgsStorage; use rustc_data_structures::fx::FxHashSet; use rustc_lint::{Lint, LintId}; @@ -522,30 +522,6 @@ impl LintInfo { } } -// Remove code tags and code behind '# 's, as they are not needed for the lint docs and --explain -pub fn sanitize_explanation(raw_docs: &str) -> String { - // Remove tags and hidden code: - let mut explanation = String::with_capacity(128); - let mut in_code = false; - for line in raw_docs.lines().map(|line| line.trim()) { - if let Some(lang) = line.strip_prefix("```") { - let tag = lang.split_once(',').map_or(lang, |(left, _)| left); - if !in_code && matches!(tag, "" | "rust" | "ignore" | "should_panic" | "no_run" | "compile_fail") { - explanation += "```rust\n"; - } else { - explanation += line; - explanation.push('\n'); - } - in_code = !in_code; - } else if !(in_code && line.starts_with("# ")) { - explanation += line; - explanation.push('\n'); - } - } - - explanation -} - pub fn explain(name: &str) -> i32 { let target = format!("clippy::{}", name.to_ascii_uppercase()); diff --git a/tests/compile-test.rs b/tests/compile-test.rs index 23dd41235bdbd..8734bd4136492 100644 --- a/tests/compile-test.rs +++ b/tests/compile-test.rs @@ -5,9 +5,9 @@ use cargo_metadata::Message; use cargo_metadata::diagnostic::{Applicability, Diagnostic}; use clippy_config::ClippyConfiguration; +use clippy_lints::LintInfo; use clippy_lints::declared_lints::LINTS; use clippy_lints::deprecated_lints::{DEPRECATED, DEPRECATED_VERSION, RENAMED}; -use clippy_lints::{LintInfo, sanitize_explanation}; use serde::{Deserialize, Serialize}; use test_utils::IS_RUSTC_TEST_SUITE; use ui_test::custom_flags::Flag; @@ -444,10 +444,6 @@ impl DiagnosticCollector { iter::zip(DEPRECATED, DEPRECATED_VERSION) .map(|((lint, reason), version)| LintMetadata::new_deprecated(lint, reason, version)), ) - .map(|mut metadata| { - metadata.docs = sanitize_explanation(&metadata.docs); - metadata - }) .collect(); metadata.sort_unstable_by(|a, b| a.id.cmp(&b.id)); From 3ac3c3495173d2b93c585e0df37667f7633b9ab5 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Sun, 29 Sep 2024 22:23:44 -0400 Subject: [PATCH 16/87] Remove crashes and fix tests --- tests/ui/crashes/ice-12284.rs | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 tests/ui/crashes/ice-12284.rs diff --git a/tests/ui/crashes/ice-12284.rs b/tests/ui/crashes/ice-12284.rs deleted file mode 100644 index 8d1dbface8eba..0000000000000 --- a/tests/ui/crashes/ice-12284.rs +++ /dev/null @@ -1,10 +0,0 @@ -#![allow(incomplete_features)] -#![feature(unnamed_fields)] - -#[repr(C)] -struct Foo { - _: struct { - }, -} - -fn main() {} From ce22fd34d9c35ad08da74d82bea4227f8dba77f7 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Wed, 2 Oct 2024 21:52:44 -0400 Subject: [PATCH 17/87] Remove redundant in_trait from hir::TyKind::OpaqueDef --- clippy_lints/src/extra_unused_type_parameters.rs | 2 +- clippy_lints/src/lifetimes.rs | 2 +- clippy_lints/src/manual_async_fn.rs | 2 +- clippy_utils/src/hir_utils.rs | 3 +-- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/clippy_lints/src/extra_unused_type_parameters.rs b/clippy_lints/src/extra_unused_type_parameters.rs index bf9388b4a70fd..a7fb9535e7904 100644 --- a/clippy_lints/src/extra_unused_type_parameters.rs +++ b/clippy_lints/src/extra_unused_type_parameters.rs @@ -199,7 +199,7 @@ impl<'cx, 'tcx> Visitor<'tcx> for TypeWalker<'cx, 'tcx> { fn visit_ty(&mut self, t: &'tcx Ty<'tcx>) { if let Some((def_id, _)) = t.peel_refs().as_generic_param() { self.ty_params.remove(&def_id); - } else if let TyKind::OpaqueDef(id, _, _) = t.kind { + } else if let TyKind::OpaqueDef(id, _) = t.kind { // Explicitly walk OpaqueDef. Normally `walk_ty` would do the job, but it calls // `visit_nested_item`, which checks that `Self::NestedFilter::INTER` is set. We're // using `OnlyBodies`, so the check ends up failing and the type isn't fully walked. diff --git a/clippy_lints/src/lifetimes.rs b/clippy_lints/src/lifetimes.rs index 755afe959b37a..d3cc5ea628c72 100644 --- a/clippy_lints/src/lifetimes.rs +++ b/clippy_lints/src/lifetimes.rs @@ -523,7 +523,7 @@ impl<'a, 'tcx> Visitor<'tcx> for RefVisitor<'a, 'tcx> { fn visit_ty(&mut self, ty: &'tcx Ty<'_>) { match ty.kind { - TyKind::OpaqueDef(item, bounds, _) => { + TyKind::OpaqueDef(item, bounds) => { let map = self.cx.tcx.hir(); let item = map.item(item); let len = self.lts.len(); diff --git a/clippy_lints/src/manual_async_fn.rs b/clippy_lints/src/manual_async_fn.rs index fc3bba9e51232..7097c85156c34 100644 --- a/clippy_lints/src/manual_async_fn.rs +++ b/clippy_lints/src/manual_async_fn.rs @@ -105,7 +105,7 @@ fn future_trait_ref<'tcx>( cx: &LateContext<'tcx>, ty: &'tcx Ty<'tcx>, ) -> Option<(&'tcx TraitRef<'tcx>, Vec)> { - if let TyKind::OpaqueDef(item_id, bounds, false) = ty.kind + if let TyKind::OpaqueDef(item_id, bounds) = ty.kind && let item = cx.tcx.hir().item(item_id) && let ItemKind::OpaqueTy(opaque) = &item.kind && let Some(trait_ref) = opaque.bounds.iter().find_map(|bound| { diff --git a/clippy_utils/src/hir_utils.rs b/clippy_utils/src/hir_utils.rs index 76900379ac787..b2d75125ccdb1 100644 --- a/clippy_utils/src/hir_utils.rs +++ b/clippy_utils/src/hir_utils.rs @@ -1115,9 +1115,8 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { } }, TyKind::Path(ref qpath) => self.hash_qpath(qpath), - TyKind::OpaqueDef(_, arg_list, in_trait) => { + TyKind::OpaqueDef(_, arg_list) => { self.hash_generic_args(arg_list); - in_trait.hash(&mut self.s); }, TyKind::TraitObject(_, lifetime, _) => { self.hash_lifetime(lifetime); From cbd64a4868a84e980101be7779b7b17156dff383 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Wed, 2 Oct 2024 22:04:18 -0400 Subject: [PATCH 18/87] Use named fields for OpaqueTyOrigin --- clippy_lints/src/len_zero.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_lints/src/len_zero.rs b/clippy_lints/src/len_zero.rs index 8bc2a56af9938..0cbcdbeb90ae1 100644 --- a/clippy_lints/src/len_zero.rs +++ b/clippy_lints/src/len_zero.rs @@ -313,7 +313,7 @@ fn extract_future_output<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<& kind: ItemKind::OpaqueTy(opaque), .. } = item - && let OpaqueTyOrigin::AsyncFn(_) = opaque.origin + && let OpaqueTyOrigin::AsyncFn { .. } = opaque.origin && let [GenericBound::Trait(trait_ref, _)] = &opaque.bounds && let Some(segment) = trait_ref.trait_ref.path.segments.last() && let Some(generic_args) = segment.args From 2080634d336ac73a00aa256993ee035da7226aca Mon Sep 17 00:00:00 2001 From: coekjan Date: Wed, 2 Oct 2024 23:51:52 +0800 Subject: [PATCH 19/87] Fix lint `manual_slice_size_calculation` when the slice is ref more than once --- .../src/manual_slice_size_calculation.rs | 14 +++++----- tests/ui/manual_slice_size_calculation.fixed | 4 +++ tests/ui/manual_slice_size_calculation.rs | 4 +++ tests/ui/manual_slice_size_calculation.stderr | 26 ++++++++++++++----- 4 files changed, 35 insertions(+), 13 deletions(-) diff --git a/clippy_lints/src/manual_slice_size_calculation.rs b/clippy_lints/src/manual_slice_size_calculation.rs index b24a0f4695a6c..a984a7f952052 100644 --- a/clippy_lints/src/manual_slice_size_calculation.rs +++ b/clippy_lints/src/manual_slice_size_calculation.rs @@ -45,10 +45,11 @@ impl<'tcx> LateLintPass<'tcx> for ManualSliceSizeCalculation { && !expr.span.from_expansion() // Does not apply inside const because size_of_val is not cost in stable. && !is_in_const_context(cx) - && let Some(receiver) = simplify(cx, left, right) + && let Some((receiver, refs_count)) = simplify(cx, left, right) { let ctxt = expr.span.ctxt(); let mut app = Applicability::MachineApplicable; + let deref = "*".repeat(refs_count - 1); let val_name = snippet_with_context(cx, receiver.span, ctxt, "slice", &mut app).0; let Some(sugg) = std_or_core(cx) else { return }; @@ -58,7 +59,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualSliceSizeCalculation { expr.span, "manual slice size calculation", "try", - format!("{sugg}::mem::size_of_val({val_name})"), + format!("{sugg}::mem::size_of_val({deref}{val_name})"), app, ); } @@ -69,7 +70,7 @@ fn simplify<'tcx>( cx: &LateContext<'tcx>, expr1: &'tcx Expr<'tcx>, expr2: &'tcx Expr<'tcx>, -) -> Option<&'tcx Expr<'tcx>> { +) -> Option<(&'tcx Expr<'tcx>, usize)> { let expr1 = expr_or_init(cx, expr1); let expr2 = expr_or_init(cx, expr2); @@ -80,13 +81,14 @@ fn simplify_half<'tcx>( cx: &LateContext<'tcx>, expr1: &'tcx Expr<'tcx>, expr2: &'tcx Expr<'tcx>, -) -> Option<&'tcx Expr<'tcx>> { +) -> Option<(&'tcx Expr<'tcx>, usize)> { if !expr1.span.from_expansion() // expr1 is `[T1].len()`? && let ExprKind::MethodCall(method_path, receiver, _, _) = expr1.kind && method_path.ident.name == sym::len && let receiver_ty = cx.typeck_results().expr_ty(receiver) - && let ty::Slice(ty1) = receiver_ty.peel_refs().kind() + && let (receiver_ty, refs_count) = clippy_utils::ty::walk_ptrs_ty_depth(receiver_ty) + && let ty::Slice(ty1) = receiver_ty.kind() // expr2 is `size_of::()`? && let ExprKind::Call(func, _) = expr2.kind && let ExprKind::Path(ref func_qpath) = func.kind @@ -96,7 +98,7 @@ fn simplify_half<'tcx>( // T1 == T2? && *ty1 == ty2 { - Some(receiver) + Some((receiver, refs_count)) } else { None } diff --git a/tests/ui/manual_slice_size_calculation.fixed b/tests/ui/manual_slice_size_calculation.fixed index 62b372f4b8d9c..0603b30e34653 100644 --- a/tests/ui/manual_slice_size_calculation.fixed +++ b/tests/ui/manual_slice_size_calculation.fixed @@ -10,11 +10,15 @@ use proc_macros::external; fn main() { let v_i32 = Vec::::new(); let s_i32 = v_i32.as_slice(); + let s_i32_ref = &s_i32; + let s_i32_ref_ref = &s_i32_ref; // True positives: let _ = std::mem::size_of_val(s_i32); // WARNING let _ = std::mem::size_of_val(s_i32); // WARNING let _ = std::mem::size_of_val(s_i32) * 5; // WARNING + let _ = std::mem::size_of_val(*s_i32_ref); // WARNING + let _ = std::mem::size_of_val(**s_i32_ref_ref); // WARNING let len = s_i32.len(); let size = size_of::(); diff --git a/tests/ui/manual_slice_size_calculation.rs b/tests/ui/manual_slice_size_calculation.rs index d59f5fd8b9427..14093e653c09d 100644 --- a/tests/ui/manual_slice_size_calculation.rs +++ b/tests/ui/manual_slice_size_calculation.rs @@ -10,11 +10,15 @@ use proc_macros::external; fn main() { let v_i32 = Vec::::new(); let s_i32 = v_i32.as_slice(); + let s_i32_ref = &s_i32; + let s_i32_ref_ref = &s_i32_ref; // True positives: let _ = s_i32.len() * size_of::(); // WARNING let _ = size_of::() * s_i32.len(); // WARNING let _ = size_of::() * s_i32.len() * 5; // WARNING + let _ = size_of::() * s_i32_ref.len(); // WARNING + let _ = size_of::() * s_i32_ref_ref.len(); // WARNING let len = s_i32.len(); let size = size_of::(); diff --git a/tests/ui/manual_slice_size_calculation.stderr b/tests/ui/manual_slice_size_calculation.stderr index 4bd8a4fdf17b7..0397f3a4969a8 100644 --- a/tests/ui/manual_slice_size_calculation.stderr +++ b/tests/ui/manual_slice_size_calculation.stderr @@ -1,5 +1,5 @@ error: manual slice size calculation - --> tests/ui/manual_slice_size_calculation.rs:15:13 + --> tests/ui/manual_slice_size_calculation.rs:17:13 | LL | let _ = s_i32.len() * size_of::(); // WARNING | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `std::mem::size_of_val(s_i32)` @@ -8,40 +8,52 @@ LL | let _ = s_i32.len() * size_of::(); // WARNING = help: to override `-D warnings` add `#[allow(clippy::manual_slice_size_calculation)]` error: manual slice size calculation - --> tests/ui/manual_slice_size_calculation.rs:16:13 + --> tests/ui/manual_slice_size_calculation.rs:18:13 | LL | let _ = size_of::() * s_i32.len(); // WARNING | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `std::mem::size_of_val(s_i32)` error: manual slice size calculation - --> tests/ui/manual_slice_size_calculation.rs:17:13 + --> tests/ui/manual_slice_size_calculation.rs:19:13 | LL | let _ = size_of::() * s_i32.len() * 5; // WARNING | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `std::mem::size_of_val(s_i32)` +error: manual slice size calculation + --> tests/ui/manual_slice_size_calculation.rs:20:13 + | +LL | let _ = size_of::() * s_i32_ref.len(); // WARNING + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `std::mem::size_of_val(*s_i32_ref)` + error: manual slice size calculation --> tests/ui/manual_slice_size_calculation.rs:21:13 | +LL | let _ = size_of::() * s_i32_ref_ref.len(); // WARNING + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `std::mem::size_of_val(**s_i32_ref_ref)` + +error: manual slice size calculation + --> tests/ui/manual_slice_size_calculation.rs:25:13 + | LL | let _ = len * size_of::(); // WARNING | ^^^^^^^^^^^^^^^^^^^^^^ help: try: `std::mem::size_of_val(s_i32)` error: manual slice size calculation - --> tests/ui/manual_slice_size_calculation.rs:22:13 + --> tests/ui/manual_slice_size_calculation.rs:26:13 | LL | let _ = s_i32.len() * size; // WARNING | ^^^^^^^^^^^^^^^^^^ help: try: `std::mem::size_of_val(s_i32)` error: manual slice size calculation - --> tests/ui/manual_slice_size_calculation.rs:23:13 + --> tests/ui/manual_slice_size_calculation.rs:27:13 | LL | let _ = len * size; // WARNING | ^^^^^^^^^^ help: try: `std::mem::size_of_val(s_i32)` error: manual slice size calculation - --> tests/ui/manual_slice_size_calculation.rs:25:13 + --> tests/ui/manual_slice_size_calculation.rs:29:13 | LL | let _ = external!(&[1u64][..]).len() * size_of::(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `std::mem::size_of_val(external!(&[1u64][..]))` -error: aborting due to 7 previous errors +error: aborting due to 9 previous errors From aeb548a4f76c742608c2b7154461814440048506 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Wed, 14 Aug 2024 16:40:00 +0200 Subject: [PATCH 20/87] Put back interactions with settings menus --- util/gh-pages/index_template.html | 78 ++++++++++------------------ util/gh-pages/script.js | 85 +++++++++++++++++++------------ util/gh-pages/style.css | 14 ++++- 3 files changed, 91 insertions(+), 86 deletions(-) diff --git a/util/gh-pages/index_template.html b/util/gh-pages/index_template.html index 5a7af7d562ed8..a18948a206042 100644 --- a/util/gh-pages/index_template.html +++ b/util/gh-pages/index_template.html @@ -27,7 +27,7 @@
    -
    +
    Theme
    - All - +
  • - +
  • -
    +
    -
    -
    -
    - - -
    +
    + +
    -
    +
    -

    diff --git a/util/gh-pages/script.js b/util/gh-pages/script.js index 7dce72c18590c..4c61c5129df2f 100644 --- a/util/gh-pages/script.js +++ b/util/gh-pages/script.js @@ -76,6 +76,29 @@ window.searchState = { setTimeout(searchState.filterLints, 50); }, filterLints: () => { + function matchesSearch(lint, terms, searchStr) { + // Search by id + if (lint.elem.id.indexOf(searchStr) !== -1) { + return true; + } + // Search the description + // The use of `for`-loops instead of `foreach` enables us to return early + const docsLowerCase = lint.elem.textContent.toLowerCase(); + for (const term of terms) { + // This is more likely and will therefore be checked first + if (docsLowerCase.indexOf(term) !== -1) { + return true; + } + + if (lint.elem.id.indexOf(term) !== -1) { + return true; + } + + return false; + } + return true; + } + searchState.clearInputTimeout(); let searchStr = searchState.inputElem.value.trim().toLowerCase(); @@ -87,29 +110,19 @@ window.searchState = { } searchState.lastSearch = searchStr; const terms = searchStr.split(" "); + const cleanedSearchStr = searchStr.replaceAll("-", "_"); - onEachLazy(document.querySelectorAll("article"), lint => { - // Search by id - if (lint.id.indexOf(searchStr.replaceAll("-", "_")) !== -1) { - lint.style.display = ""; - return; + for (const lint of filters.getAllLints()) { + lint.searchFilteredOut = !matchesSearch(lint, terms, cleanedSearchStr); + if (lint.filteredOut) { + continue; } - // Search the description - // The use of `for`-loops instead of `foreach` enables us to return early - const docsLowerCase = lint.textContent.toLowerCase(); - for (index = 0; index < terms.length; index++) { - // This is more likely and will therefore be checked first - if (docsLowerCase.indexOf(terms[index]) !== -1) { - return; - } - - if (lint.id.indexOf(terms[index]) !== -1) { - return; - } - - lint.style.display = "none"; + if (lint.searchFilteredOut) { + lint.elem.style.display = "none"; + } else { + lint.elem.style.display = ""; } - }); + } if (searchStr.length > 0) { window.location.hash = `/${searchStr}`; } else { @@ -151,12 +164,26 @@ function handleShortcut(ev) { document.addEventListener("keypress", handleShortcut); document.addEventListener("keydown", handleShortcut); -function toggleElements(element, value) { - // `element` is always a button in a `li` in a `ul`. We want the `input` in the `ul`. +function toggleElements(filter, value) { + let needsUpdate = false; + let count = 0; + + const element = document.getElementById(filters[filter].id); onEachLazy( - element.parentElement.parentElement.getElementsByTagName("input"), - el => el.checked = value, + element.querySelectorAll("ul input"), + el => { + if (el.checked !== value) { + el.checked = value; + filters[filter][el.getAttribute("data-value")] = value; + needsUpdate = true; + } + count += 1; + } ); + element.querySelector(".badge").innerText = value ? count : 0; + if (needsUpdate) { + filters.filterLints(); + } } function changeSetting(elem) { @@ -251,15 +278,90 @@ const GROUPS_FILTER_DEFAULT = { style: true, suspicious: true, }; +const LEVEL_FILTERS_DEFAULT = { + allow: true, + warn: true, + deny: true, + none: true, +}; +const APPLICABILITIES_FILTER_DEFAULT = { + Unspecified: true, + Unresolved: true, + MachineApplicable: true, + MaybeIncorrect: true, + HasPlaceholders: true, +}; + +window.filters = { + groups_filter: { id: "lint-groups", ...GROUPS_FILTER_DEFAULT }, + levels_filter: { id: "lint-levels", ...LEVEL_FILTERS_DEFAULT }, + applicabilities_filter: { id: "lint-applicabilities", ...APPLICABILITIES_FILTER_DEFAULT }, + version_filter: { + "≥": null, + "≤": null, + "=": null, + }, + allLints: null, + getAllLints: () => { + if (filters.allLints === null) { + filters.allLints = Array.prototype.slice.call( + document.getElementsByTagName("article"), + ).map(elem => { + return { + elem: elem, + group: elem.querySelector(".label-lint-group").innerText, + level: elem.querySelector(".label-lint-level").innerText, + version: elem.querySelector(".label-version").innerText, + applicability: elem.querySelector(".label-applicability").innerText, + filteredOut: false, + searchFilteredOut: false, + }; + }); + } + return filters.allLints; + }, + filterLints: () => { + for (const lint of filters.getAllLints()) { + lint.filteredOut = (!filters.groups_filter[lint.group] + || !filters.levels_filter[lint.level] + || !filters.applicabilities_filter[lint.applicability]); + if (lint.filteredOut || lint.searchFilteredOut) { + lint.elem.style.display = "none"; + } else { + lint.elem.style.display = ""; + } + } + }, +}; + +function updateFilter(elem, filter) { + const value = elem.getAttribute("data-value"); + if (filters[filter][value] !== elem.checked) { + filters[filter][value] = elem.checked; + const counter = document.querySelector(`#${filters[filter].id} .badge`); + counter.innerText = parseInt(counter.innerText) + (elem.checked ? 1 : -1); + filters.filterLints(); + } +} function resetGroupsToDefault() { + let needsUpdate = false; + onEachLazy(document.querySelectorAll("#lint-groups-selector input"), el => { const key = el.getAttribute("data-value"); - el.checked = GROUPS_FILTER_DEFAULT[key]; + const value = GROUPS_FILTER_DEFAULT[key]; + if (filters.groups_filter[key] !== value) { + filters.groups_filter[key] = value; + el.checked = value; + needsUpdate = true; + } }); + if (needsUpdate) { + filters.filterLints(); + } } -function generateListOfOptions(list, elementId) { +function generateListOfOptions(list, elementId, filter) { let html = ''; let nbEnabled = 0; for (const [key, value] of Object.entries(list)) { @@ -267,7 +369,8 @@ function generateListOfOptions(list, elementId) { html += `\
  • \ \
  • `; if (value) { @@ -298,20 +401,10 @@ function setupDropdown(elementId) { function generateSettings() { setupDropdown("settings-dropdown"); - const LEVEL_FILTERS_DEFAULT = {allow: true, warn: true, deny: true, none: true}; - generateListOfOptions(LEVEL_FILTERS_DEFAULT, "lint-levels"); - - // Generate lint groups. - generateListOfOptions(GROUPS_FILTER_DEFAULT, "lint-groups"); - - const APPLICABILITIES_FILTER_DEFAULT = { - Unspecified: true, - Unresolved: true, - MachineApplicable: true, - MaybeIncorrect: true, - HasPlaceholders: true - }; - generateListOfOptions(APPLICABILITIES_FILTER_DEFAULT, "lint-applicabilities"); + generateListOfOptions(LEVEL_FILTERS_DEFAULT, "lint-levels", "levels_filter"); + generateListOfOptions(GROUPS_FILTER_DEFAULT, "lint-groups", "groups_filter"); + generateListOfOptions( + APPLICABILITIES_FILTER_DEFAULT, "lint-applicabilities", "applicabilities_filter"); let html = ''; for (const kind of ["≥", "≤", "="]) { @@ -361,5 +454,5 @@ function scrollToLintByURL() { } scrollToLintByURL(); - +filters.filterLints(); onEachLazy(document.querySelectorAll("pre > code.language-rust"), el => hljs.highlightElement(el)); From 00c11df107e27b8f37c63efbdf359b0d62e9f54c Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Thu, 15 Aug 2024 00:47:20 +0200 Subject: [PATCH 22/87] Add support for version filtering --- util/gh-pages/script.js | 58 +++++++++++++++++++++++++++++++++++------ 1 file changed, 50 insertions(+), 8 deletions(-) diff --git a/util/gh-pages/script.js b/util/gh-pages/script.js index 4c61c5129df2f..6b25bfa48bbec 100644 --- a/util/gh-pages/script.js +++ b/util/gh-pages/script.js @@ -262,10 +262,6 @@ function toggleExpansion(expand) { ); } -function clearVersionFilters() { - onEachLazy(document.querySelectorAll("#version-filter-count input"), el => el.value = ""); -} - const GROUPS_FILTER_DEFAULT = { cargo: true, complexity: true, @@ -307,11 +303,16 @@ window.filters = { filters.allLints = Array.prototype.slice.call( document.getElementsByTagName("article"), ).map(elem => { + let version = elem.querySelector(".label-version").innerText; + // Strip the "pre " prefix for pre 1.29.0 lints + if (version.startsWith("pre ")) { + version = version.slice(4); + } return { elem: elem, group: elem.querySelector(".label-lint-group").innerText, level: elem.querySelector(".label-lint-level").innerText, - version: elem.querySelector(".label-version").innerText, + version: parseInt(version.split(".")[1]), applicability: elem.querySelector(".label-applicability").innerText, filteredOut: false, searchFilteredOut: false, @@ -324,7 +325,11 @@ window.filters = { for (const lint of filters.getAllLints()) { lint.filteredOut = (!filters.groups_filter[lint.group] || !filters.levels_filter[lint.level] - || !filters.applicabilities_filter[lint.applicability]); + || !filters.applicabilities_filter[lint.applicability] + || !(filters.version_filter["="] === null || lint.version === filters.version_filter["="]) + || !(filters.version_filter["≥"] === null || lint.version > filters.version_filter["≥"]) + || !(filters.version_filter["≤"] === null || lint.version < filters.version_filter["≤"]) + ); if (lint.filteredOut || lint.searchFilteredOut) { lint.elem.style.display = "none"; } else { @@ -344,6 +349,38 @@ function updateFilter(elem, filter) { } } +function updateVersionFilters(elem, comparisonKind) { + let value = elem.value.trim(); + if (value.length === 0) { + value = null; + } else if (/^\d+$/.test(value)) { + value = parseInt(value); + } else { + console.error(`Failed to get version number from "${value}"`); + return; + } + if (filters.version_filter[comparisonKind] !== value) { + filters.version_filter[comparisonKind] = value; + filters.filterLints(); + } +} + +function clearVersionFilters() { + let needsUpdate = false; + + onEachLazy(document.querySelectorAll("#version-filter input"), el => { + el.value = ""; + const comparisonKind = el.getAttribute("data-value"); + if (filters.version_filter[comparisonKind] !== null) { + needsUpdate = true; + filters.version_filter[comparisonKind] = null; + } + }); + if (needsUpdate) { + filters.filterLints(); + } +} + function resetGroupsToDefault() { let needsUpdate = false; @@ -414,10 +451,15 @@ function generateSettings() { 1. \ \ + data-value="${kind}" \ + onchange="updateVersionFilters(this, '${kind}')" \ + oninput="updateVersionFilters(this, '${kind}')" \ + onkeydown="updateVersionFilters(this, '${kind}')" \ + onkeyup="updateVersionFilters(this, '${kind}')" \ + onpaste="updateVersionFilters(this, '${kind}')" \ + /> .0\ `; } From 4bb4cc6fcbc0fe1bacd44ab9004405c19ae52781 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Thu, 15 Aug 2024 01:56:53 +0200 Subject: [PATCH 23/87] Add support for URL arguments --- util/gh-pages/script.js | 124 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 115 insertions(+), 9 deletions(-) diff --git a/util/gh-pages/script.js b/util/gh-pages/script.js index 6b25bfa48bbec..cbe2a79934f72 100644 --- a/util/gh-pages/script.js +++ b/util/gh-pages/script.js @@ -262,6 +262,11 @@ function toggleExpansion(expand) { ); } +// Returns the current URL without any query parameter or hash. +function getNakedUrl() { + return window.location.href.split("?")[0].split("#")[0]; +} + const GROUPS_FILTER_DEFAULT = { cargo: true, complexity: true, @@ -287,6 +292,17 @@ const APPLICABILITIES_FILTER_DEFAULT = { MaybeIncorrect: true, HasPlaceholders: true, }; +const URL_PARAMS_CORRESPONDANCE = { + "groups_filter": "groups", + "levels_filter": "levels", + "applicabilities_filter": "applicabilities", + "version_filter": "versions", +}; +const VERSIONS_CORRESPONDANCE = { + "lte": "≤", + "gte": "≥", + "eq": "=", +}; window.filters = { groups_filter: { id: "lint-groups", ...GROUPS_FILTER_DEFAULT }, @@ -321,7 +337,65 @@ window.filters = { } return filters.allLints; }, + regenerateURLparams: () => { + const urlParams = new URLSearchParams(window.location.search); + + function compareObjects(obj1, obj2) { + return (JSON.stringify(obj1) === JSON.stringify({ id: obj1.id, ...obj2 })); + } + function updateIfNeeded(filterName, obj2) { + const obj1 = filters[filterName]; + const name = URL_PARAMS_CORRESPONDANCE[filterName]; + if (!compareObjects(obj1, obj2)) { + urlParams.set( + name, + Object.entries(obj1).filter( + ([key, value]) => value && key !== "id" + ).map( + ([key, _]) => key + ).join(","), + ); + } else { + urlParams.delete(name); + } + } + + updateIfNeeded("groups_filter", GROUPS_FILTER_DEFAULT); + updateIfNeeded("levels_filter", LEVEL_FILTERS_DEFAULT); + updateIfNeeded( + "applicabilities_filter", APPLICABILITIES_FILTER_DEFAULT); + + const versions = []; + if (filters.version_filter["="] !== null) { + versions.push(`eq:${filters.version_filter["="]}`); + } + if (filters.version_filter["≥"] !== null) { + versions.push(`gte:${filters.version_filter["≥"]}`); + } + if (filters.version_filter["≤"] !== null) { + versions.push(`lte:${filters.version_filter["≤"]}`); + } + if (versions.length !== 0) { + urlParams.set(URL_PARAMS_CORRESPONDANCE["version_filter"], versions.join(",")); + } else { + urlParams.delete(URL_PARAMS_CORRESPONDANCE["version_filter"]); + } + + let params = urlParams.toString(); + if (params.length !== 0) { + params = `?${params}`; + } + + const url = getNakedUrl() + params + window.location.hash + if (!history.state) { + history.pushState(null, "", url); + } else { + history.replaceState(null, "", url); + } + }, filterLints: () => { + // First we regenerate the URL parameters. + filters.regenerateURLparams(); for (const lint of filters.getAllLints()) { lint.filteredOut = (!filters.groups_filter[lint.group] || !filters.levels_filter[lint.level] @@ -339,17 +413,19 @@ window.filters = { }, }; -function updateFilter(elem, filter) { +function updateFilter(elem, filter, skipLintsFiltering) { const value = elem.getAttribute("data-value"); if (filters[filter][value] !== elem.checked) { filters[filter][value] = elem.checked; const counter = document.querySelector(`#${filters[filter].id} .badge`); counter.innerText = parseInt(counter.innerText) + (elem.checked ? 1 : -1); - filters.filterLints(); + if (!skipLintsFiltering) { + filters.filterLints(); + } } } -function updateVersionFilters(elem, comparisonKind) { +function updateVersionFilters(elem, skipLintsFiltering) { let value = elem.value.trim(); if (value.length === 0) { value = null; @@ -359,9 +435,12 @@ function updateVersionFilters(elem, comparisonKind) { console.error(`Failed to get version number from "${value}"`); return; } + const comparisonKind = elem.getAttribute("data-value"); if (filters.version_filter[comparisonKind] !== value) { filters.version_filter[comparisonKind] = value; - filters.filterLints(); + if (!skipLintsFiltering) { + filters.filterLints(); + } } } @@ -454,11 +533,11 @@ function generateSettings() { class="version-filter-input form-control filter-input" \ maxlength="2" \ data-value="${kind}" \ - onchange="updateVersionFilters(this, '${kind}')" \ - oninput="updateVersionFilters(this, '${kind}')" \ - onkeydown="updateVersionFilters(this, '${kind}')" \ - onkeyup="updateVersionFilters(this, '${kind}')" \ - onpaste="updateVersionFilters(this, '${kind}')" \ + onchange="updateVersionFilters(this)" \ + oninput="updateVersionFilters(this)" \ + onkeydown="updateVersionFilters(this)" \ + onkeyup="updateVersionFilters(this)" \ + onpaste="updateVersionFilters(this)" \ /> .0\ `; @@ -495,6 +574,33 @@ function scrollToLintByURL() { } } +function parseURLFilters() { + const urlParams = new URLSearchParams(window.location.search); + + for (const [key, value] of urlParams.entries()) { + for (const [corres_key, corres_value] of Object.entries(URL_PARAMS_CORRESPONDANCE)) { + if (corres_value === key) { + if (key !== "versions") { + const settings = new Set(value.split(",")); + onEachLazy(document.querySelectorAll(`#lint-${key} ul input`), elem => { + elem.checked = settings.has(elem.getAttribute("data-value")); + updateFilter(elem, corres_key, true); + }); + } else { + const settings = value.split(",").map(elem => elem.split(":")); + + for (const [kind, value] of settings) { + const elem = document.querySelector( + `#version-filter input[data-value="${VERSIONS_CORRESPONDANCE[kind]}"]`); + updateVersionFilters(elem, true); + } + } + } + } + } +} + +parseURLFilters(); scrollToLintByURL(); filters.filterLints(); onEachLazy(document.querySelectorAll("pre > code.language-rust"), el => hljs.highlightElement(el)); From e0b0851ba8a83d4f258a9d86ba5579c4f2c61b46 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Thu, 15 Aug 2024 16:08:48 +0200 Subject: [PATCH 24/87] Support version filter URL parameters --- util/gh-pages/script.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/util/gh-pages/script.js b/util/gh-pages/script.js index cbe2a79934f72..d3bc6f6fc9977 100644 --- a/util/gh-pages/script.js +++ b/util/gh-pages/script.js @@ -435,6 +435,16 @@ function updateVersionFilters(elem, skipLintsFiltering) { console.error(`Failed to get version number from "${value}"`); return; } + + const counter = document.querySelector("#version-filter .badge"); + let count = 0; + onEachLazy(document.querySelectorAll("#version-filter input"), el => { + if (el.value.trim().length !== 0) { + count += 1; + } + }); + counter.innerText = count; + const comparisonKind = elem.getAttribute("data-value"); if (filters.version_filter[comparisonKind] !== value) { filters.version_filter[comparisonKind] = value; @@ -455,6 +465,7 @@ function clearVersionFilters() { filters.version_filter[comparisonKind] = null; } }); + document.querySelector("#version-filter .badge").innerText = 0; if (needsUpdate) { filters.filterLints(); } @@ -462,6 +473,7 @@ function clearVersionFilters() { function resetGroupsToDefault() { let needsUpdate = false; + let count = 0; onEachLazy(document.querySelectorAll("#lint-groups-selector input"), el => { const key = el.getAttribute("data-value"); @@ -471,7 +483,11 @@ function resetGroupsToDefault() { el.checked = value; needsUpdate = true; } + if (value) { + count += 1; + } }); + document.querySelector("#lint-groups .badge").innerText = count; if (needsUpdate) { filters.filterLints(); } @@ -592,6 +608,7 @@ function parseURLFilters() { for (const [kind, value] of settings) { const elem = document.querySelector( `#version-filter input[data-value="${VERSIONS_CORRESPONDANCE[kind]}"]`); + elem.value = value; updateVersionFilters(elem, true); } } From 47f40d468acae7e37d101cee9a6d79bff3461c64 Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Thu, 15 Aug 2024 16:13:53 +0200 Subject: [PATCH 25/87] Improve rendering speed by moving settings generation after theme rendering --- tests/compile-test.rs | 11 +++++-- util/gh-pages/index_template.html | 4 +-- util/gh-pages/script.js | 49 ++++++++++++++++--------------- 3 files changed, 36 insertions(+), 28 deletions(-) diff --git a/tests/compile-test.rs b/tests/compile-test.rs index 00627dc0bb1f9..162aed393c461 100644 --- a/tests/compile-test.rs +++ b/tests/compile-test.rs @@ -9,7 +9,8 @@ use clippy_lints::LintInfo; use clippy_lints::declared_lints::LINTS; use clippy_lints::deprecated_lints::{DEPRECATED, DEPRECATED_VERSION, RENAMED}; use pulldown_cmark::{Options, Parser, html}; -use rinja::{Template, filters::Safe}; +use rinja::Template; +use rinja::filters::Safe; use serde::Deserialize; use test_utils::IS_RUSTC_TEST_SUITE; use ui_test::custom_flags::Flag; @@ -394,7 +395,7 @@ struct Renderer<'a> { } impl<'a> Renderer<'a> { - fn markdown(&self, input: &str) -> Safe { + fn markdown(input: &str) -> Safe { let parser = Parser::new_ext(input, Options::all()); let mut html_output = String::new(); html::push_html(&mut html_output, parser); @@ -465,7 +466,11 @@ impl DiagnosticCollector { .collect(); metadata.sort_unstable_by(|a, b| a.id.cmp(&b.id)); - fs::write("util/gh-pages/index.html", Renderer { lints: &metadata }.render().unwrap()).unwrap(); + fs::write( + "util/gh-pages/index.html", + Renderer { lints: &metadata }.render().unwrap(), + ) + .unwrap(); }); (Self { sender }, handle) diff --git a/util/gh-pages/index_template.html b/util/gh-pages/index_template.html index 774c7b487c166..d942cbe39e72d 100644 --- a/util/gh-pages/index_template.html +++ b/util/gh-pages/index_template.html @@ -1,7 +1,7 @@ - - - - - + {# #} + {# #} + {# #} + {# #} + {# #} - Clippy Lints + Clippy Lints {# #} - - - + {# #} + {# #} + {# #} - - - - - - - - -
    - -
    -
    Theme
    - - -
    -
    + {# #} + {# #} + {# #} + {# #} + {# #} + {# #} + {# #} + {# #} +
    {# #} + {# #} +
    {# #} +
    Theme
    {# #} + {# #} + {# #} +
    {# #} +
    {# #} -
    - +
    {# #} + {# #} - + {# #} -
    -
    -
    -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    - - -
    -
    -
    -
    - - - - - -
    -
    -
    - - -
    -
    +
    {# #} +
    {# #} +
    {# #} +
    {# #} +
    {# #} + {# #} + {# #} +
    {# #} +
    {# #} + {# #} + {# #} +
    {# #} +
    {# #} + {# #} + {# #} +
    {# #} +
    {# #} + {# #} + {# #} +
    {# #} +
    {# #} +
    {# #} +
    {# #} + {# #} + {# #} + {# #} + {# #} + {# #} +
    {# #} +
    {# #} +
    {# #} + {# #} + {# #} +
    {# #} +
    {# #}
    {% for lint in lints %} -
    {# #} +

    {# #} + {# #} -
    -
    {{Self::markdown(lint.docs)}}
    +
    {# #} +
    {{Self::markdown(lint.docs)}}
    {# #}
    {# Applicability #} -
    - Applicability: - {{ lint.applicability_str() }} - (?) +
    {# #} + Applicability: {# #} + {{ lint.applicability_str() }} {# #} + (?) {# #}
    {# Clippy version #} -
    - {% if lint.group == "deprecated" %}Deprecated{% else %} Added{% endif %} in: - {{lint.version}} +
    {# #} + {% if lint.group == "deprecated" %}Deprecated{% else %} Added{% endif +%} in: {# #} + {{lint.version}} {# #}
    {# Open related issues #} -
    - Related Issues +
    {# #} + Related Issues {# #}
    {# Jump to source #} {% if let Some(id_location) = lint.id_location %} -
    - View Source +
    {# #} + View Source {# #} +
    {% endif %} -
    -
    -
    +
    {# #} +
    {# #} {% endfor %} -
    -
    +
    {# #} +
    {# #} - - - + {# #} + {# #} + {# #} - - - - - + {# #} + {# #} + {# #} + {# #} + {# #} diff --git a/util/gh-pages/script.js b/util/gh-pages/script.js index 7f36021e1aa3a..cc22a39b3d190 100644 --- a/util/gh-pages/script.js +++ b/util/gh-pages/script.js @@ -140,19 +140,15 @@ function onEachLazy(lazyArray, func) { function highlightIfNeeded(elem) { onEachLazy(elem.querySelectorAll("pre > code.language-rust:not(.highlighted)"), el => { - hljs.highlightElement(el) + hljs.highlightElement(el.parentElement) el.classList.add("highlighted"); }); } -function expandLintId(lintId) { - searchState.inputElem.value = lintId; - searchState.filterLints(); - - // Expand the lint. +function expandLint(lintId) { const lintElem = document.getElementById(lintId); - const isCollapsed = lintElem.classList.remove("collapsed"); - lintElem.querySelector(".label-doc-folding").innerText = "-"; + const isCollapsed = lintElem.classList.toggle("collapsed"); + lintElem.querySelector(".label-doc-folding").innerText = isCollapsed ? "+" : "−"; highlightIfNeeded(lintElem); } @@ -160,14 +156,7 @@ function expandLintId(lintId) { function openLint(event) { event.preventDefault(); event.stopPropagation(); - expandLintId(event.target.getAttribute("href").slice(1)); -} - -function expandLint(lintId) { - const lintElem = document.getElementById(lintId); - const isCollapsed = lintElem.classList.toggle("collapsed"); - lintElem.querySelector(".label-doc-folding").innerText = isCollapsed ? "+" : "-"; - highlightIfNeeded(lintElem); + expandLint(event.target.getAttribute("href").slice(1)); } function copyToClipboard(event) { @@ -526,7 +515,7 @@ function scrollToLint(lintId) { return; } target.scrollIntoView(); - expandLintId(lintId); + expandLint(lintId); } // If the page we arrive on has link to a given lint, we scroll to it. diff --git a/util/gh-pages/style.css b/util/gh-pages/style.css index 43c6264220765..a68a10b14011a 100644 --- a/util/gh-pages/style.css +++ b/util/gh-pages/style.css @@ -272,6 +272,9 @@ L4.75,12h2.5l0.5393066-2.1572876 c0.2276001-0.1062012,0.4459839-0.2269287,0.649 height: 18px; display: block; filter: invert(0.7); + position: absolute; + top: 4px; + left: 5px; } .settings-menu * { From 277c4e4baff50ff0df4b63e86404b8eea70e61ec Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Thu, 3 Oct 2024 16:32:51 +0200 Subject: [PATCH 31/87] Merge commit 'aa0d551351a9c15d8a95fdb3e2946b505893dda8' into clippy-subtree-update --- .github/workflows/remark.yml | 2 +- CHANGELOG.md | 1 + book/src/lint_configuration.md | 1 + clippy_config/src/conf.rs | 1 + clippy_config/src/msrvs.rs | 1 + clippy_lints/src/booleans.rs | 86 +++- clippy_lints/src/box_default.rs | 2 +- clippy_lints/src/cargo/common_metadata.rs | 16 +- clippy_lints/src/casts/borrow_as_ptr.rs | 6 +- clippy_lints/src/casts/ref_as_ptr.rs | 6 +- clippy_lints/src/checked_conversions.rs | 2 +- clippy_lints/src/declared_lints.rs | 1 + clippy_lints/src/default_numeric_fallback.rs | 2 +- clippy_lints/src/derive.rs | 2 +- clippy_lints/src/doc/mod.rs | 2 +- clippy_lints/src/empty_enum.rs | 2 +- clippy_lints/src/escape.rs | 4 +- clippy_lints/src/excessive_nesting.rs | 2 +- .../src/extra_unused_type_parameters.rs | 2 +- clippy_lints/src/fallible_impl_from.rs | 2 +- clippy_lints/src/format_args.rs | 2 +- clippy_lints/src/format_impl.rs | 2 +- clippy_lints/src/from_over_into.rs | 2 +- clippy_lints/src/functions/mod.rs | 60 +++ clippy_lints/src/functions/ref_option.rs | 122 +++++ clippy_lints/src/implicit_hasher.rs | 4 +- clippy_lints/src/index_refutable_slice.rs | 2 +- clippy_lints/src/len_zero.rs | 4 +- clippy_lints/src/lib.rs | 2 +- clippy_lints/src/lifetimes.rs | 439 ++++++++++-------- clippy_lints/src/loops/manual_memcpy.rs | 2 +- clippy_lints/src/loops/missing_spin_loop.rs | 10 +- clippy_lints/src/loops/needless_range_loop.rs | 4 +- clippy_lints/src/loops/same_item_push.rs | 2 +- clippy_lints/src/loops/utils.rs | 4 +- .../src/loops/while_immutable_condition.rs | 4 +- .../src/loops/while_let_on_iterator.rs | 2 +- clippy_lints/src/macro_metavars_in_unsafe.rs | 2 +- clippy_lints/src/macro_use.rs | 2 +- clippy_lints/src/manual_clamp.rs | 2 +- clippy_lints/src/manual_strip.rs | 2 +- clippy_lints/src/map_unit_fn.rs | 2 +- .../src/matches/match_str_case_mismatch.rs | 4 +- clippy_lints/src/matches/mod.rs | 2 +- clippy_lints/src/matches/overlapping_arms.rs | 4 +- .../matches/significant_drop_in_scrutinee.rs | 4 +- clippy_lints/src/matches/wild_in_or_pats.rs | 14 +- clippy_lints/src/methods/mut_mutex_lock.rs | 6 +- clippy_lints/src/methods/needless_collect.rs | 2 +- .../src/methods/option_map_unwrap_or.rs | 4 +- clippy_lints/src/methods/or_fun_call.rs | 2 +- clippy_lints/src/methods/utils.rs | 4 +- .../src/mixed_read_write_in_expression.rs | 6 +- clippy_lints/src/mut_mut.rs | 2 +- clippy_lints/src/mutable_debug_assertion.rs | 2 +- clippy_lints/src/needless_for_each.rs | 2 +- clippy_lints/src/needless_pass_by_ref_mut.rs | 2 +- clippy_lints/src/non_expressive_names.rs | 10 +- .../src/non_send_fields_in_send_ty.rs | 2 +- clippy_lints/src/pass_by_ref_or_value.rs | 4 +- clippy_lints/src/pathbuf_init_then_push.rs | 2 +- clippy_lints/src/ptr.rs | 8 +- clippy_lints/src/redundant_closure_call.rs | 2 +- clippy_lints/src/returns.rs | 6 +- .../src/significant_drop_tightening.rs | 2 +- .../src/single_component_path_imports.rs | 2 +- .../src/slow_vector_initialization.rs | 4 +- clippy_lints/src/swap.rs | 2 +- clippy_lints/src/unnested_or_patterns.rs | 11 +- clippy_lints/src/unused_async.rs | 25 +- clippy_lints/src/unwrap.rs | 4 +- clippy_lints/src/use_self.rs | 2 +- .../interning_defined_symbol.rs | 2 +- .../src/utils/internal_lints/invalid_paths.rs | 3 +- .../internal_lints/lint_without_lint_pass.rs | 2 +- clippy_lints/src/zombie_processes.rs | 11 +- clippy_utils/src/ast_utils.rs | 109 +++-- clippy_utils/src/consts.rs | 6 +- clippy_utils/src/eager_or_lazy.rs | 2 +- clippy_utils/src/hir_utils.rs | 49 +- clippy_utils/src/lib.rs | 8 +- clippy_utils/src/mir/mod.rs | 2 +- clippy_utils/src/mir/possible_borrower.rs | 6 +- clippy_utils/src/mir/possible_origin.rs | 2 +- clippy_utils/src/qualify_min_const_fn.rs | 3 +- clippy_utils/src/ty.rs | 2 +- clippy_utils/src/ty/type_certainty/mod.rs | 2 +- clippy_utils/src/usage.rs | 6 +- clippy_utils/src/visitors.rs | 4 +- lintcheck/src/main.rs | 4 +- rust-toolchain | 2 +- src/driver.rs | 61 ++- tests/ui-toml/suppress_lint_in_const/test.rs | 3 +- .../suppress_lint_in_const/test.stderr | 12 +- tests/ui/as_ptr_cast_mut.rs | 2 +- tests/ui/borrow_box.fixed | 133 ++++++ tests/ui/borrow_box.rs | 20 +- tests/ui/borrow_box.stderr | 14 +- tests/ui/boxed_local.rs | 3 +- tests/ui/boxed_local.stderr | 8 +- tests/ui/bytecount.rs | 2 +- .../needless_lifetimes_impl_trait.fixed | 2 +- .../needless_lifetimes_impl_trait.stderr | 20 +- tests/ui/deref_addrof_double_trigger.rs | 4 +- tests/ui/derive.rs | 7 +- tests/ui/derive.stderr | 20 +- tests/ui/eta.fixed | 3 +- tests/ui/eta.rs | 3 +- tests/ui/eta.stderr | 68 +-- tests/ui/explicit_auto_deref.fixed | 3 +- tests/ui/explicit_auto_deref.rs | 3 +- tests/ui/explicit_auto_deref.stderr | 92 ++-- tests/ui/extra_unused_lifetimes.rs | 8 + tests/ui/extra_unused_lifetimes.stderr | 8 +- tests/ui/float_cmp.rs | 2 +- tests/ui/float_cmp_const.rs | 3 +- tests/ui/float_cmp_const.stderr | 16 +- tests/ui/float_equality_without_abs.rs | 2 +- .../if_let_slice_binding.rs | 2 +- tests/ui/invalid_null_ptr_usage.fixed | 7 +- tests/ui/invalid_null_ptr_usage.rs | 5 +- tests/ui/invalid_null_ptr_usage.stderr | 36 +- tests/ui/iter_without_into_iter.rs | 1 + tests/ui/iter_without_into_iter.stderr | 16 +- tests/ui/mem_replace.fixed | 3 +- tests/ui/mem_replace.rs | 3 +- tests/ui/mem_replace.stderr | 48 +- tests/ui/mem_replace_no_std.fixed | 3 +- tests/ui/mem_replace_no_std.rs | 3 +- tests/ui/mem_replace_no_std.stderr | 14 +- tests/ui/mismatching_type_param_order.rs | 2 +- tests/ui/mut_mutex_lock.fixed | 12 + tests/ui/mut_mutex_lock.rs | 12 + tests/ui/mut_mutex_lock.stderr | 8 +- tests/ui/needless_borrow.fixed | 3 +- tests/ui/needless_borrow.rs | 3 +- tests/ui/needless_borrow.stderr | 56 +-- tests/ui/needless_lifetimes.fixed | 23 +- tests/ui/needless_lifetimes.rs | 21 +- tests/ui/needless_lifetimes.stderr | 24 +- tests/ui/needless_pass_by_value.rs | 3 +- tests/ui/needless_pass_by_value.stderr | 52 +-- tests/ui/needless_return.fixed | 20 + tests/ui/needless_return.rs | 20 + tests/ui/new_without_default.fixed | 3 +- tests/ui/new_without_default.rs | 3 +- tests/ui/new_without_default.stderr | 18 +- tests/ui/nonminimal_bool_methods.fixed | 62 +++ tests/ui/nonminimal_bool_methods.rs | 62 +++ tests/ui/nonminimal_bool_methods.stderr | 110 ++++- tests/ui/nonminimal_bool_methods_unfixable.rs | 9 + .../nonminimal_bool_methods_unfixable.stderr | 17 + tests/ui/ref_as_ptr.fixed | 2 +- tests/ui/ref_as_ptr.rs | 2 +- tests/ui/ref_option/all/clippy.toml | 1 + tests/ui/ref_option/private/clippy.toml | 1 + tests/ui/ref_option/ref_option.all.fixed | 62 +++ tests/ui/ref_option/ref_option.all.stderr | 162 +++++++ tests/ui/ref_option/ref_option.private.fixed | 62 +++ tests/ui/ref_option/ref_option.private.stderr | 108 +++++ tests/ui/ref_option/ref_option.rs | 62 +++ .../ref_option/ref_option_traits.all.stderr | 37 ++ .../ref_option_traits.private.stderr | 21 + tests/ui/ref_option/ref_option_traits.rs | 37 ++ tests/ui/serde.rs | 2 +- tests/ui/significant_drop_in_scrutinee.rs | 7 +- tests/ui/significant_drop_in_scrutinee.stderr | 58 +-- tests/ui/str_split.fixed | 1 + tests/ui/str_split.rs | 1 + tests/ui/str_split.stderr | 20 +- tests/ui/temporary_assignment.rs | 2 +- tests/ui/unconditional_recursion.rs | 3 +- tests/ui/unconditional_recursion.stderr | 90 ++-- tests/ui/unused_async.rs | 16 + tests/ui/unused_async.stderr | 4 +- tests/ui/unused_format_specs.1.fixed | 35 ++ tests/ui/unused_format_specs.2.fixed | 35 ++ ...cs_unfixable.rs => unused_format_specs.rs} | 2 +- ...able.stderr => unused_format_specs.stderr} | 8 +- tests/ui/useful_asref.rs | 1 + tests/ui/wild_in_or_pats.rs | 68 +++ tests/ui/wild_in_or_pats.stderr | 18 +- tests/ui/zombie_processes.rs | 7 + 183 files changed, 2484 insertions(+), 848 deletions(-) create mode 100644 clippy_lints/src/functions/ref_option.rs create mode 100644 tests/ui/borrow_box.fixed create mode 100644 tests/ui/nonminimal_bool_methods_unfixable.rs create mode 100644 tests/ui/nonminimal_bool_methods_unfixable.stderr create mode 100644 tests/ui/ref_option/all/clippy.toml create mode 100644 tests/ui/ref_option/private/clippy.toml create mode 100644 tests/ui/ref_option/ref_option.all.fixed create mode 100644 tests/ui/ref_option/ref_option.all.stderr create mode 100644 tests/ui/ref_option/ref_option.private.fixed create mode 100644 tests/ui/ref_option/ref_option.private.stderr create mode 100644 tests/ui/ref_option/ref_option.rs create mode 100644 tests/ui/ref_option/ref_option_traits.all.stderr create mode 100644 tests/ui/ref_option/ref_option_traits.private.stderr create mode 100644 tests/ui/ref_option/ref_option_traits.rs create mode 100644 tests/ui/unused_format_specs.1.fixed create mode 100644 tests/ui/unused_format_specs.2.fixed rename tests/ui/{unused_format_specs_unfixable.rs => unused_format_specs.rs} (98%) rename tests/ui/{unused_format_specs_unfixable.stderr => unused_format_specs.stderr} (89%) diff --git a/.github/workflows/remark.yml b/.github/workflows/remark.yml index 348d52020fd23..a1b011dc32d84 100644 --- a/.github/workflows/remark.yml +++ b/.github/workflows/remark.yml @@ -19,7 +19,7 @@ jobs: uses: actions/checkout@v4 - name: Setup Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: '18.x' diff --git a/CHANGELOG.md b/CHANGELOG.md index 41a86e8ce5102..5d253d52531e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5871,6 +5871,7 @@ Released 2018-09-13 [`ref_as_ptr`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_as_ptr [`ref_binding_to_reference`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_binding_to_reference [`ref_in_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_in_deref +[`ref_option`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_option [`ref_option_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_option_ref [`ref_patterns`]: https://rust-lang.github.io/rust-clippy/master/index.html#ref_patterns [`regex_macro`]: https://rust-lang.github.io/rust-clippy/master/index.html#regex_macro diff --git a/book/src/lint_configuration.md b/book/src/lint_configuration.md index 91159bc79c511..07a56fb33df11 100644 --- a/book/src/lint_configuration.md +++ b/book/src/lint_configuration.md @@ -353,6 +353,7 @@ Suppress lints whenever the suggested change would cause breakage for other crat * [`rc_buffer`](https://rust-lang.github.io/rust-clippy/master/index.html#rc_buffer) * [`rc_mutex`](https://rust-lang.github.io/rust-clippy/master/index.html#rc_mutex) * [`redundant_allocation`](https://rust-lang.github.io/rust-clippy/master/index.html#redundant_allocation) +* [`ref_option`](https://rust-lang.github.io/rust-clippy/master/index.html#ref_option) * [`single_call_fn`](https://rust-lang.github.io/rust-clippy/master/index.html#single_call_fn) * [`trivially_copy_pass_by_ref`](https://rust-lang.github.io/rust-clippy/master/index.html#trivially_copy_pass_by_ref) * [`unnecessary_box_returns`](https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_box_returns) diff --git a/clippy_config/src/conf.rs b/clippy_config/src/conf.rs index 757620341ccc1..e4e2c97fdc1d9 100644 --- a/clippy_config/src/conf.rs +++ b/clippy_config/src/conf.rs @@ -378,6 +378,7 @@ define_Conf! { rc_buffer, rc_mutex, redundant_allocation, + ref_option, single_call_fn, trivially_copy_pass_by_ref, unnecessary_box_returns, diff --git a/clippy_config/src/msrvs.rs b/clippy_config/src/msrvs.rs index e30df3d32341a..68a3b11d3848f 100644 --- a/clippy_config/src/msrvs.rs +++ b/clippy_config/src/msrvs.rs @@ -19,6 +19,7 @@ macro_rules! msrv_aliases { msrv_aliases! { 1,83,0 { CONST_EXTERN_FN } 1,83,0 { CONST_FLOAT_BITS_CONV } + 1,82,0 { IS_NONE_OR } 1,81,0 { LINT_REASONS_STABILIZATION } 1,80,0 { BOX_INTO_ITER} 1,77,0 { C_STR_LITERALS } diff --git a/clippy_lints/src/booleans.rs b/clippy_lints/src/booleans.rs index 3c2af72624f60..26888f7e3a0ed 100644 --- a/clippy_lints/src/booleans.rs +++ b/clippy_lints/src/booleans.rs @@ -1,3 +1,5 @@ +use clippy_config::Conf; +use clippy_config::msrvs::{self, Msrv}; use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then}; use clippy_utils::eq_expr_value; use clippy_utils::source::SpanRangeExt; @@ -7,7 +9,7 @@ use rustc_errors::Applicability; use rustc_hir::intravisit::{FnKind, Visitor, walk_expr}; use rustc_hir::{BinOpKind, Body, Expr, ExprKind, FnDecl, UnOp}; use rustc_lint::{LateContext, LateLintPass, Level}; -use rustc_session::declare_lint_pass; +use rustc_session::{RustcVersion, impl_lint_pass}; use rustc_span::def_id::LocalDefId; use rustc_span::{Span, sym}; @@ -69,9 +71,25 @@ declare_clippy_lint! { } // For each pairs, both orders are considered. -const METHODS_WITH_NEGATION: [(&str, &str); 2] = [("is_some", "is_none"), ("is_err", "is_ok")]; +const METHODS_WITH_NEGATION: [(Option, &str, &str); 3] = [ + (None, "is_some", "is_none"), + (None, "is_err", "is_ok"), + (Some(msrvs::IS_NONE_OR), "is_some_and", "is_none_or"), +]; + +pub struct NonminimalBool { + msrv: Msrv, +} + +impl NonminimalBool { + pub fn new(conf: &'static Conf) -> Self { + Self { + msrv: conf.msrv.clone(), + } + } +} -declare_lint_pass!(NonminimalBool => [NONMINIMAL_BOOL, OVERLY_COMPLEX_BOOL_EXPR]); +impl_lint_pass!(NonminimalBool => [NONMINIMAL_BOOL, OVERLY_COMPLEX_BOOL_EXPR]); impl<'tcx> LateLintPass<'tcx> for NonminimalBool { fn check_fn( @@ -83,7 +101,7 @@ impl<'tcx> LateLintPass<'tcx> for NonminimalBool { _: Span, _: LocalDefId, ) { - NonminimalBoolVisitor { cx }.visit_body(body); + NonminimalBoolVisitor { cx, msrv: &self.msrv }.visit_body(body); } fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { @@ -100,6 +118,8 @@ impl<'tcx> LateLintPass<'tcx> for NonminimalBool { _ => {}, } } + + extract_msrv_attr!(LateContext); } fn inverted_bin_op_eq_str(op: BinOpKind) -> Option<&'static str> { @@ -176,11 +196,11 @@ fn check_inverted_bool_in_condition( ); } -fn check_simplify_not(cx: &LateContext<'_>, expr: &Expr<'_>) { +fn check_simplify_not(cx: &LateContext<'_>, msrv: &Msrv, expr: &Expr<'_>) { if let ExprKind::Unary(UnOp::Not, inner) = &expr.kind && !expr.span.from_expansion() && !inner.span.from_expansion() - && let Some(suggestion) = simplify_not(cx, inner) + && let Some(suggestion) = simplify_not(cx, msrv, inner) && cx.tcx.lint_level_at_node(NONMINIMAL_BOOL, expr.hir_id).0 != Level::Allow { span_lint_and_sugg( @@ -197,6 +217,7 @@ fn check_simplify_not(cx: &LateContext<'_>, expr: &Expr<'_>) { struct NonminimalBoolVisitor<'a, 'tcx> { cx: &'a LateContext<'tcx>, + msrv: &'a Msrv, } use quine_mc_cluskey::Bool; @@ -205,7 +226,7 @@ struct Hir2Qmm<'a, 'tcx, 'v> { cx: &'a LateContext<'tcx>, } -impl<'a, 'tcx, 'v> Hir2Qmm<'a, 'tcx, 'v> { +impl<'v> Hir2Qmm<'_, '_, 'v> { fn extract(&mut self, op: BinOpKind, a: &[&'v Expr<'_>], mut v: Vec) -> Result, String> { for a in a { if let ExprKind::Binary(binop, lhs, rhs) = &a.kind { @@ -289,10 +310,11 @@ impl<'a, 'tcx, 'v> Hir2Qmm<'a, 'tcx, 'v> { struct SuggestContext<'a, 'tcx, 'v> { terminals: &'v [&'v Expr<'v>], cx: &'a LateContext<'tcx>, + msrv: &'a Msrv, output: String, } -impl<'a, 'tcx, 'v> SuggestContext<'a, 'tcx, 'v> { +impl SuggestContext<'_, '_, '_> { fn recurse(&mut self, suggestion: &Bool) -> Option<()> { use quine_mc_cluskey::Bool::{And, False, Not, Or, Term, True}; match suggestion { @@ -311,7 +333,7 @@ impl<'a, 'tcx, 'v> SuggestContext<'a, 'tcx, 'v> { }, Term(n) => { let terminal = self.terminals[n as usize]; - if let Some(str) = simplify_not(self.cx, terminal) { + if let Some(str) = simplify_not(self.cx, self.msrv, terminal) { self.output.push_str(&str); } else { self.output.push('!'); @@ -358,7 +380,7 @@ impl<'a, 'tcx, 'v> SuggestContext<'a, 'tcx, 'v> { } } -fn simplify_not(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option { +fn simplify_not(cx: &LateContext<'_>, curr_msrv: &Msrv, expr: &Expr<'_>) -> Option { match &expr.kind { ExprKind::Binary(binop, lhs, rhs) => { if !implements_ord(cx, lhs) { @@ -389,7 +411,7 @@ fn simplify_not(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option { Some(format!("{lhs_snippet}{op}{rhs_snippet}")) }) }, - ExprKind::MethodCall(path, receiver, [], _) => { + ExprKind::MethodCall(path, receiver, args, _) => { let type_of_receiver = cx.typeck_results().expr_ty(receiver); if !is_type_diagnostic_item(cx, type_of_receiver, sym::Option) && !is_type_diagnostic_item(cx, type_of_receiver, sym::Result) @@ -399,21 +421,41 @@ fn simplify_not(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option { METHODS_WITH_NEGATION .iter() .copied() - .flat_map(|(a, b)| vec![(a, b), (b, a)]) - .find(|&(a, _)| { - let path: &str = path.ident.name.as_str(); - a == path + .flat_map(|(msrv, a, b)| vec![(msrv, a, b), (msrv, b, a)]) + .find(|&(msrv, a, _)| msrv.is_none_or(|msrv| curr_msrv.meets(msrv)) && a == path.ident.name.as_str()) + .and_then(|(_, _, neg_method)| { + let negated_args = args + .iter() + .map(|arg| simplify_not(cx, curr_msrv, arg)) + .collect::>>()? + .join(", "); + Some(format!( + "{}.{neg_method}({negated_args})", + receiver.span.get_source_text(cx)? + )) }) - .and_then(|(_, neg_method)| Some(format!("{}.{neg_method}()", receiver.span.get_source_text(cx)?))) }, + ExprKind::Closure(closure) => { + let body = cx.tcx.hir().body(closure.body); + let params = body + .params + .iter() + .map(|param| param.span.get_source_text(cx).map(|t| t.to_string())) + .collect::>>()? + .join(", "); + let negated = simplify_not(cx, curr_msrv, body.value)?; + Some(format!("|{params}| {negated}")) + }, + ExprKind::Unary(UnOp::Not, expr) => expr.span.get_source_text(cx).map(|t| t.to_string()), _ => None, } } -fn suggest(cx: &LateContext<'_>, suggestion: &Bool, terminals: &[&Expr<'_>]) -> String { +fn suggest(cx: &LateContext<'_>, msrv: &Msrv, suggestion: &Bool, terminals: &[&Expr<'_>]) -> String { let mut suggest_context = SuggestContext { terminals, cx, + msrv, output: String::new(), }; suggest_context.recurse(suggestion); @@ -475,7 +517,7 @@ fn terminal_stats(b: &Bool) -> Stats { stats } -impl<'a, 'tcx> NonminimalBoolVisitor<'a, 'tcx> { +impl<'tcx> NonminimalBoolVisitor<'_, 'tcx> { fn bool_expr(&self, e: &'tcx Expr<'_>) { let mut h2q = Hir2Qmm { terminals: Vec::new(), @@ -526,7 +568,7 @@ impl<'a, 'tcx> NonminimalBoolVisitor<'a, 'tcx> { diag.span_suggestion( e.span, "it would look like the following", - suggest(self.cx, suggestion, &h2q.terminals), + suggest(self.cx, self.msrv, suggestion, &h2q.terminals), // nonminimal_bool can produce minimal but // not human readable expressions (#3141) Applicability::Unspecified, @@ -569,12 +611,12 @@ impl<'a, 'tcx> NonminimalBoolVisitor<'a, 'tcx> { } }; if improvements.is_empty() { - check_simplify_not(self.cx, e); + check_simplify_not(self.cx, self.msrv, e); } else { nonminimal_bool_lint( improvements .into_iter() - .map(|suggestion| suggest(self.cx, suggestion, &h2q.terminals)) + .map(|suggestion| suggest(self.cx, self.msrv, suggestion, &h2q.terminals)) .collect(), ); } @@ -582,7 +624,7 @@ impl<'a, 'tcx> NonminimalBoolVisitor<'a, 'tcx> { } } -impl<'a, 'tcx> Visitor<'tcx> for NonminimalBoolVisitor<'a, 'tcx> { +impl<'tcx> Visitor<'tcx> for NonminimalBoolVisitor<'_, 'tcx> { fn visit_expr(&mut self, e: &'tcx Expr<'_>) { if !e.span.from_expansion() { match &e.kind { diff --git a/clippy_lints/src/box_default.rs b/clippy_lints/src/box_default.rs index 8261c65354fd5..40d154c0bdfe3 100644 --- a/clippy_lints/src/box_default.rs +++ b/clippy_lints/src/box_default.rs @@ -91,7 +91,7 @@ fn is_local_vec_expn(cx: &LateContext<'_>, expr: &Expr<'_>, ref_expr: &Expr<'_>) #[derive(Default)] struct InferVisitor(bool); -impl<'tcx> Visitor<'tcx> for InferVisitor { +impl Visitor<'_> for InferVisitor { fn visit_ty(&mut self, t: &Ty<'_>) { self.0 |= matches!(t.kind, TyKind::Infer | TyKind::OpaqueDef(..) | TyKind::TraitObject(..)); if !self.0 { diff --git a/clippy_lints/src/cargo/common_metadata.rs b/clippy_lints/src/cargo/common_metadata.rs index fed0aa8b2758b..6714c053913c9 100644 --- a/clippy_lints/src/cargo/common_metadata.rs +++ b/clippy_lints/src/cargo/common_metadata.rs @@ -10,27 +10,27 @@ pub(super) fn check(cx: &LateContext<'_>, metadata: &Metadata, ignore_publish: b // only run the lint if publish is `None` (`publish = true` or skipped entirely) // or if the vector isn't empty (`publish = ["something"]`) if package.publish.as_ref().filter(|publish| publish.is_empty()).is_none() || ignore_publish { - if is_empty_str(&package.description) { + if is_empty_str(package.description.as_ref()) { missing_warning(cx, package, "package.description"); } - if is_empty_str(&package.license) && is_empty_str(&package.license_file) { + if is_empty_str(package.license.as_ref()) && is_empty_str(package.license_file.as_ref()) { missing_warning(cx, package, "either package.license or package.license_file"); } - if is_empty_str(&package.repository) { + if is_empty_str(package.repository.as_ref()) { missing_warning(cx, package, "package.repository"); } - if is_empty_str(&package.readme) { + if is_empty_str(package.readme.as_ref()) { missing_warning(cx, package, "package.readme"); } - if is_empty_vec(&package.keywords) { + if is_empty_vec(package.keywords.as_ref()) { missing_warning(cx, package, "package.keywords"); } - if is_empty_vec(&package.categories) { + if is_empty_vec(package.categories.as_ref()) { missing_warning(cx, package, "package.categories"); } } @@ -42,8 +42,8 @@ fn missing_warning(cx: &LateContext<'_>, package: &cargo_metadata::Package, fiel span_lint(cx, CARGO_COMMON_METADATA, DUMMY_SP, message); } -fn is_empty_str>(value: &Option) -> bool { - value.as_ref().map_or(true, |s| s.as_ref().is_empty()) +fn is_empty_str>(value: Option<&T>) -> bool { + value.map_or(true, |s| s.as_ref().is_empty()) } fn is_empty_vec(value: &[String]) -> bool { diff --git a/clippy_lints/src/casts/borrow_as_ptr.rs b/clippy_lints/src/casts/borrow_as_ptr.rs index b7256dd2eae9d..4dd51dcbc9a20 100644 --- a/clippy_lints/src/casts/borrow_as_ptr.rs +++ b/clippy_lints/src/casts/borrow_as_ptr.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::is_no_std_crate; use clippy_utils::source::snippet_with_context; +use clippy_utils::std_or_core; use rustc_errors::Applicability; use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability, Ty, TyKind}; use rustc_lint::LateContext; @@ -16,8 +16,8 @@ pub(super) fn check<'tcx>( ) { if matches!(cast_to.kind, TyKind::Ptr(_)) && let ExprKind::AddrOf(BorrowKind::Ref, mutability, e) = cast_expr.kind + && let Some(std_or_core) = std_or_core(cx) { - let core_or_std = if is_no_std_crate(cx) { "core" } else { "std" }; let macro_name = match mutability { Mutability::Not => "addr_of", Mutability::Mut => "addr_of_mut", @@ -40,7 +40,7 @@ pub(super) fn check<'tcx>( expr.span, "borrow as raw pointer", "try", - format!("{core_or_std}::ptr::{macro_name}!({snip})"), + format!("{std_or_core}::ptr::{macro_name}!({snip})"), Applicability::MachineApplicable, ); } diff --git a/clippy_lints/src/casts/ref_as_ptr.rs b/clippy_lints/src/casts/ref_as_ptr.rs index dfa240ccec623..f699bba20ed01 100644 --- a/clippy_lints/src/casts/ref_as_ptr.rs +++ b/clippy_lints/src/casts/ref_as_ptr.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_with_applicability; use clippy_utils::sugg::Sugg; -use clippy_utils::{ExprUseNode, expr_use_ctxt, is_no_std_crate}; +use clippy_utils::{ExprUseNode, expr_use_ctxt, std_or_core}; use rustc_errors::Applicability; use rustc_hir::{Expr, Mutability, Ty, TyKind}; use rustc_lint::LateContext; @@ -25,8 +25,8 @@ pub(super) fn check<'tcx>( && let use_cx = expr_use_ctxt(cx, expr) // TODO: only block the lint if `cast_expr` is a temporary && !matches!(use_cx.use_node(cx), ExprUseNode::LetStmt(_) | ExprUseNode::ConstStatic(_)) + && let Some(std_or_core) = std_or_core(cx) { - let core_or_std = if is_no_std_crate(cx) { "core" } else { "std" }; let fn_name = match to_mutbl { Mutability::Not => "from_ref", Mutability::Mut => "from_mut", @@ -56,7 +56,7 @@ pub(super) fn check<'tcx>( expr.span, "reference as raw pointer", "try", - format!("{core_or_std}::ptr::{fn_name}{turbofish}({cast_expr_sugg})"), + format!("{std_or_core}::ptr::{fn_name}{turbofish}({cast_expr_sugg})"), app, ); } diff --git a/clippy_lints/src/checked_conversions.rs b/clippy_lints/src/checked_conversions.rs index d3aa2fd1ea12c..2e7f91a842e1e 100644 --- a/clippy_lints/src/checked_conversions.rs +++ b/clippy_lints/src/checked_conversions.rs @@ -48,7 +48,7 @@ impl CheckedConversions { impl_lint_pass!(CheckedConversions => [CHECKED_CONVERSIONS]); -impl<'tcx> LateLintPass<'tcx> for CheckedConversions { +impl LateLintPass<'_> for CheckedConversions { fn check_expr(&mut self, cx: &LateContext<'_>, item: &Expr<'_>) { if let ExprKind::Binary(op, lhs, rhs) = item.kind && let (lt1, gt1, op2) = match op.node { diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 2b229d2fe6ac5..9cec672beb004 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -202,6 +202,7 @@ pub static LINTS: &[&crate::LintInfo] = &[ crate::functions::MUST_USE_CANDIDATE_INFO, crate::functions::MUST_USE_UNIT_INFO, crate::functions::NOT_UNSAFE_PTR_ARG_DEREF_INFO, + crate::functions::REF_OPTION_INFO, crate::functions::RENAMED_FUNCTION_PARAMS_INFO, crate::functions::RESULT_LARGE_ERR_INFO, crate::functions::RESULT_UNIT_ERR_INFO, diff --git a/clippy_lints/src/default_numeric_fallback.rs b/clippy_lints/src/default_numeric_fallback.rs index a065dc2cf7e52..4808c372754c4 100644 --- a/clippy_lints/src/default_numeric_fallback.rs +++ b/clippy_lints/src/default_numeric_fallback.rs @@ -119,7 +119,7 @@ impl<'a, 'tcx> NumericFallbackVisitor<'a, 'tcx> { } } -impl<'a, 'tcx> Visitor<'tcx> for NumericFallbackVisitor<'a, 'tcx> { +impl<'tcx> Visitor<'tcx> for NumericFallbackVisitor<'_, 'tcx> { fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { match &expr.kind { ExprKind::Block( diff --git a/clippy_lints/src/derive.rs b/clippy_lints/src/derive.rs index 35a97adfb452a..82a66cc92029b 100644 --- a/clippy_lints/src/derive.rs +++ b/clippy_lints/src/derive.rs @@ -385,7 +385,7 @@ fn check_unsafe_derive_deserialize<'tcx>( && cx .tcx .inherent_impls(def.did()) - .into_iter() + .iter() .map(|imp_did| cx.tcx.hir().expect_item(imp_did.expect_local())) .any(|imp| has_unsafe(cx, imp)) { diff --git a/clippy_lints/src/doc/mod.rs b/clippy_lints/src/doc/mod.rs index 0ae1fad569213..e090644ae4410 100644 --- a/clippy_lints/src/doc/mod.rs +++ b/clippy_lints/src/doc/mod.rs @@ -970,7 +970,7 @@ impl<'a, 'tcx> FindPanicUnwrap<'a, 'tcx> { } } -impl<'a, 'tcx> Visitor<'tcx> for FindPanicUnwrap<'a, 'tcx> { +impl<'tcx> Visitor<'tcx> for FindPanicUnwrap<'_, 'tcx> { type NestedFilter = nested_filter::OnlyBodies; fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { diff --git a/clippy_lints/src/empty_enum.rs b/clippy_lints/src/empty_enum.rs index f4c55738cb83a..70eb81fa09c1a 100644 --- a/clippy_lints/src/empty_enum.rs +++ b/clippy_lints/src/empty_enum.rs @@ -60,7 +60,7 @@ declare_clippy_lint! { declare_lint_pass!(EmptyEnum => [EMPTY_ENUM]); -impl<'tcx> LateLintPass<'tcx> for EmptyEnum { +impl LateLintPass<'_> for EmptyEnum { fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { if let ItemKind::Enum(..) = item.kind // Only suggest the `never_type` if the feature is enabled diff --git a/clippy_lints/src/escape.rs b/clippy_lints/src/escape.rs index 5588124e791ea..a89f0d9c43274 100644 --- a/clippy_lints/src/escape.rs +++ b/clippy_lints/src/escape.rs @@ -141,7 +141,7 @@ fn is_argument(tcx: TyCtxt<'_>, id: HirId) -> bool { matches!(tcx.parent_hir_node(id), Node::Param(_)) } -impl<'a, 'tcx> Delegate<'tcx> for EscapeDelegate<'a, 'tcx> { +impl<'tcx> Delegate<'tcx> for EscapeDelegate<'_, 'tcx> { fn consume(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId) { if cmt.place.projections.is_empty() { if let PlaceBase::Local(lid) = cmt.place.base { @@ -188,7 +188,7 @@ impl<'a, 'tcx> Delegate<'tcx> for EscapeDelegate<'a, 'tcx> { fn fake_read(&mut self, _: &PlaceWithHirId<'tcx>, _: FakeReadCause, _: HirId) {} } -impl<'a, 'tcx> EscapeDelegate<'a, 'tcx> { +impl<'tcx> EscapeDelegate<'_, 'tcx> { fn is_large_box(&self, ty: Ty<'tcx>) -> bool { // Large types need to be boxed to avoid stack overflows. if let Some(boxed_ty) = ty.boxed_ty() { diff --git a/clippy_lints/src/excessive_nesting.rs b/clippy_lints/src/excessive_nesting.rs index ce0e0faa01489..ffc76366983aa 100644 --- a/clippy_lints/src/excessive_nesting.rs +++ b/clippy_lints/src/excessive_nesting.rs @@ -135,7 +135,7 @@ impl NestingVisitor<'_, '_> { } } -impl<'conf, 'cx> Visitor<'_> for NestingVisitor<'conf, 'cx> { +impl Visitor<'_> for NestingVisitor<'_, '_> { fn visit_block(&mut self, block: &Block) { if block.span.from_expansion() { return; diff --git a/clippy_lints/src/extra_unused_type_parameters.rs b/clippy_lints/src/extra_unused_type_parameters.rs index bf9388b4a70fd..3b93d3ff93ed4 100644 --- a/clippy_lints/src/extra_unused_type_parameters.rs +++ b/clippy_lints/src/extra_unused_type_parameters.rs @@ -193,7 +193,7 @@ fn bound_to_trait_def_id(bound: &GenericBound<'_>) -> Option { bound.trait_ref()?.trait_def_id()?.as_local() } -impl<'cx, 'tcx> Visitor<'tcx> for TypeWalker<'cx, 'tcx> { +impl<'tcx> Visitor<'tcx> for TypeWalker<'_, 'tcx> { type NestedFilter = nested_filter::OnlyBodies; fn visit_ty(&mut self, t: &'tcx Ty<'tcx>) { diff --git a/clippy_lints/src/fallible_impl_from.rs b/clippy_lints/src/fallible_impl_from.rs index 747ea9a434464..f822432cce63a 100644 --- a/clippy_lints/src/fallible_impl_from.rs +++ b/clippy_lints/src/fallible_impl_from.rs @@ -73,7 +73,7 @@ fn lint_impl_body(cx: &LateContext<'_>, impl_span: Span, impl_items: &[hir::Impl result: Vec, } - impl<'a, 'tcx> Visitor<'tcx> for FindPanicUnwrap<'a, 'tcx> { + impl<'tcx> Visitor<'tcx> for FindPanicUnwrap<'_, 'tcx> { fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { if let Some(macro_call) = root_macro_call_first_node(self.lcx, expr) { if is_panic(self.lcx, macro_call.def_id) { diff --git a/clippy_lints/src/format_args.rs b/clippy_lints/src/format_args.rs index 83ab9f6557bdd..4c043f8dc14b7 100644 --- a/clippy_lints/src/format_args.rs +++ b/clippy_lints/src/format_args.rs @@ -219,7 +219,7 @@ struct FormatArgsExpr<'a, 'tcx> { ignore_mixed: bool, } -impl<'a, 'tcx> FormatArgsExpr<'a, 'tcx> { +impl FormatArgsExpr<'_, '_> { fn check_templates(&self) { for piece in &self.format_args.template { if let FormatArgsPiece::Placeholder(placeholder) = piece diff --git a/clippy_lints/src/format_impl.rs b/clippy_lints/src/format_impl.rs index c196f404ce677..7c0515b8c5608 100644 --- a/clippy_lints/src/format_impl.rs +++ b/clippy_lints/src/format_impl.rs @@ -148,7 +148,7 @@ struct FormatImplExpr<'a, 'tcx> { format_trait_impl: FormatTraitNames, } -impl<'a, 'tcx> FormatImplExpr<'a, 'tcx> { +impl FormatImplExpr<'_, '_> { fn check_to_string_in_display(&self) { if self.format_trait_impl.name == sym::Display && let ExprKind::MethodCall(path, self_arg, ..) = self.expr.kind diff --git a/clippy_lints/src/from_over_into.rs b/clippy_lints/src/from_over_into.rs index 8832cd42dd98f..d5a2e06863dc5 100644 --- a/clippy_lints/src/from_over_into.rs +++ b/clippy_lints/src/from_over_into.rs @@ -126,7 +126,7 @@ struct SelfFinder<'a, 'tcx> { invalid: bool, } -impl<'a, 'tcx> Visitor<'tcx> for SelfFinder<'a, 'tcx> { +impl<'tcx> Visitor<'tcx> for SelfFinder<'_, 'tcx> { type NestedFilter = OnlyBodies; fn nested_visit_map(&mut self) -> Self::Map { diff --git a/clippy_lints/src/functions/mod.rs b/clippy_lints/src/functions/mod.rs index ba8a459c917e6..91d73c2a9c962 100644 --- a/clippy_lints/src/functions/mod.rs +++ b/clippy_lints/src/functions/mod.rs @@ -2,6 +2,7 @@ mod impl_trait_in_params; mod misnamed_getters; mod must_use; mod not_unsafe_ptr_arg_deref; +mod ref_option; mod renamed_function_params; mod result; mod too_many_arguments; @@ -399,6 +400,53 @@ declare_clippy_lint! { "renamed function parameters in trait implementation" } +declare_clippy_lint! { + /// ### What it does + /// Warns when a function signature uses `&Option` instead of `Option<&T>`. + /// + /// ### Why is this bad? + /// More flexibility, better memory optimization, and more idiomatic Rust code. + /// + /// `&Option` in a function signature breaks encapsulation because the caller must own T + /// and move it into an Option to call with it. When returned, the owner must internally store + /// it as `Option` in order to return it. + /// At a lower level, `&Option` points to memory with the `presence` bit flag plus the `T` value, + /// whereas `Option<&T>` is usually [optimized](https://doc.rust-lang.org/1.81.0/std/option/index.html#representation) + /// to a single pointer, so it may be more optimal. + /// + /// See this [YouTube video](https://www.youtube.com/watch?v=6c7pZYP_iIE) by + /// Logan Smith for an in-depth explanation of why this is important. + /// + /// ### Known problems + /// This lint recommends changing the function signatures, but it cannot + /// automatically change the function calls or the function implementations. + /// + /// ### Example + /// ```no_run + /// // caller uses foo(&opt) + /// fn foo(a: &Option) {} + /// # struct Unit {} + /// # impl Unit { + /// fn bar(&self) -> &Option { &None } + /// # } + /// ``` + /// Use instead: + /// ```no_run + /// // caller should use `foo1(opt.as_ref())` + /// fn foo1(a: Option<&String>) {} + /// // better yet, use string slice `foo2(opt.as_deref())` + /// fn foo2(a: Option<&str>) {} + /// # struct Unit {} + /// # impl Unit { + /// fn bar(&self) -> Option<&String> { None } + /// # } + /// ``` + #[clippy::version = "1.82.0"] + pub REF_OPTION, + pedantic, + "function signature uses `&Option` instead of `Option<&T>`" +} + pub struct Functions { too_many_arguments_threshold: u64, too_many_lines_threshold: u64, @@ -437,6 +485,7 @@ impl_lint_pass!(Functions => [ MISNAMED_GETTERS, IMPL_TRAIT_IN_PARAMS, RENAMED_FUNCTION_PARAMS, + REF_OPTION, ]); impl<'tcx> LateLintPass<'tcx> for Functions { @@ -455,6 +504,16 @@ impl<'tcx> LateLintPass<'tcx> for Functions { not_unsafe_ptr_arg_deref::check_fn(cx, kind, decl, body, def_id); misnamed_getters::check_fn(cx, kind, decl, body, span); impl_trait_in_params::check_fn(cx, &kind, body, hir_id); + ref_option::check_fn( + cx, + kind, + decl, + span, + hir_id, + def_id, + body, + self.avoid_breaking_exported_api, + ); } fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) { @@ -475,5 +534,6 @@ impl<'tcx> LateLintPass<'tcx> for Functions { must_use::check_trait_item(cx, item); result::check_trait_item(cx, item, self.large_error_threshold); impl_trait_in_params::check_trait_item(cx, item, self.avoid_breaking_exported_api); + ref_option::check_trait_item(cx, item, self.avoid_breaking_exported_api); } } diff --git a/clippy_lints/src/functions/ref_option.rs b/clippy_lints/src/functions/ref_option.rs new file mode 100644 index 0000000000000..373ecd74cb379 --- /dev/null +++ b/clippy_lints/src/functions/ref_option.rs @@ -0,0 +1,122 @@ +use crate::functions::REF_OPTION; +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::is_trait_impl_item; +use clippy_utils::source::snippet; +use clippy_utils::ty::is_type_diagnostic_item; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_hir::intravisit::FnKind; +use rustc_hir::{FnDecl, HirId}; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, GenericArgKind, Mutability, Ty}; +use rustc_span::def_id::LocalDefId; +use rustc_span::{Span, sym}; + +fn check_ty<'a>(cx: &LateContext<'a>, param: &rustc_hir::Ty<'a>, param_ty: Ty<'a>, fixes: &mut Vec<(Span, String)>) { + if let ty::Ref(_, opt_ty, Mutability::Not) = param_ty.kind() + && is_type_diagnostic_item(cx, *opt_ty, sym::Option) + && let ty::Adt(_, opt_gen) = opt_ty.kind() + && let [gen] = opt_gen.as_slice() + && let GenericArgKind::Type(gen_ty) = gen.unpack() + && !gen_ty.is_ref() + // Need to gen the original spans, so first parsing mid, and hir parsing afterward + && let hir::TyKind::Ref(lifetime, hir::MutTy { ty, .. }) = param.kind + && let hir::TyKind::Path(hir::QPath::Resolved(_, path)) = ty.kind + && let (Some(first), Some(last)) = (path.segments.first(), path.segments.last()) + && let Some(hir::GenericArgs { + args: [hir::GenericArg::Type(opt_ty)], + .. + }) = last.args + { + let lifetime = snippet(cx, lifetime.ident.span, ".."); + fixes.push(( + param.span, + format!( + "{}<&{lifetime}{}{}>", + snippet(cx, first.ident.span.to(last.ident.span), ".."), + if lifetime.is_empty() { "" } else { " " }, + snippet(cx, opt_ty.span, "..") + ), + )); + } +} + +fn check_fn_sig<'a>(cx: &LateContext<'a>, decl: &FnDecl<'a>, span: Span, sig: ty::FnSig<'a>) { + let mut fixes = Vec::new(); + // Check function arguments' types + for (param, param_ty) in decl.inputs.iter().zip(sig.inputs()) { + check_ty(cx, param, *param_ty, &mut fixes); + } + // Check return type + if let hir::FnRetTy::Return(ty) = &decl.output { + check_ty(cx, ty, sig.output(), &mut fixes); + } + if !fixes.is_empty() { + span_lint_and_then( + cx, + REF_OPTION, + span, + "it is more idiomatic to use `Option<&T>` instead of `&Option`", + |diag| { + diag.multipart_suggestion("change this to", fixes, Applicability::Unspecified); + }, + ); + } +} + +#[allow(clippy::too_many_arguments)] +pub(crate) fn check_fn<'a>( + cx: &LateContext<'a>, + kind: FnKind<'_>, + decl: &FnDecl<'a>, + span: Span, + hir_id: HirId, + def_id: LocalDefId, + body: &hir::Body<'_>, + avoid_breaking_exported_api: bool, +) { + if avoid_breaking_exported_api && cx.effective_visibilities.is_exported(def_id) { + return; + } + + if let FnKind::Closure = kind { + // Compute the span of the closure parameters + return type if set + let span = if let hir::FnRetTy::Return(out_ty) = &decl.output { + if decl.inputs.is_empty() { + out_ty.span + } else { + span.with_hi(out_ty.span.hi()) + } + } else if let (Some(first), Some(last)) = (decl.inputs.first(), decl.inputs.last()) { + first.span.to(last.span) + } else { + // No parameters - no point in checking + return; + }; + + // Figure out the signature of the closure + let ty::Closure(_, args) = cx.typeck_results().expr_ty(body.value).kind() else { + return; + }; + let sig = args.as_closure().sig().skip_binder(); + + check_fn_sig(cx, decl, span, sig); + } else if !is_trait_impl_item(cx, hir_id) { + let sig = cx.tcx.fn_sig(def_id).instantiate_identity().skip_binder(); + check_fn_sig(cx, decl, span, sig); + } +} + +pub(super) fn check_trait_item<'a>( + cx: &LateContext<'a>, + trait_item: &hir::TraitItem<'a>, + avoid_breaking_exported_api: bool, +) { + if let hir::TraitItemKind::Fn(ref sig, _) = trait_item.kind + && !(avoid_breaking_exported_api && cx.effective_visibilities.is_exported(trait_item.owner_id.def_id)) + { + let def_id = trait_item.owner_id.def_id; + let ty_sig = cx.tcx.fn_sig(def_id).instantiate_identity().skip_binder(); + check_fn_sig(cx, sig.decl, sig.span, ty_sig); + } +} diff --git a/clippy_lints/src/implicit_hasher.rs b/clippy_lints/src/implicit_hasher.rs index f683925145a28..4c5375730b8e3 100644 --- a/clippy_lints/src/implicit_hasher.rs +++ b/clippy_lints/src/implicit_hasher.rs @@ -281,7 +281,7 @@ impl<'a, 'tcx> ImplicitHasherTypeVisitor<'a, 'tcx> { } } -impl<'a, 'tcx> Visitor<'tcx> for ImplicitHasherTypeVisitor<'a, 'tcx> { +impl<'tcx> Visitor<'tcx> for ImplicitHasherTypeVisitor<'_, 'tcx> { fn visit_ty(&mut self, t: &'tcx hir::Ty<'_>) { if let Some(target) = ImplicitHasherType::new(self.cx, t) { self.found.push(target); @@ -318,7 +318,7 @@ impl<'a, 'b, 'tcx> ImplicitHasherConstructorVisitor<'a, 'b, 'tcx> { } } -impl<'a, 'b, 'tcx> Visitor<'tcx> for ImplicitHasherConstructorVisitor<'a, 'b, 'tcx> { +impl<'tcx> Visitor<'tcx> for ImplicitHasherConstructorVisitor<'_, '_, 'tcx> { type NestedFilter = nested_filter::OnlyBodies; fn visit_body(&mut self, body: &Body<'tcx>) { diff --git a/clippy_lints/src/index_refutable_slice.rs b/clippy_lints/src/index_refutable_slice.rs index 39afb6810b8e2..73ebe6aec15a0 100644 --- a/clippy_lints/src/index_refutable_slice.rs +++ b/clippy_lints/src/index_refutable_slice.rs @@ -226,7 +226,7 @@ struct SliceIndexLintingVisitor<'a, 'tcx> { max_suggested_slice: u64, } -impl<'a, 'tcx> Visitor<'tcx> for SliceIndexLintingVisitor<'a, 'tcx> { +impl<'tcx> Visitor<'tcx> for SliceIndexLintingVisitor<'_, 'tcx> { type NestedFilter = nested_filter::OnlyBodies; fn nested_visit_map(&mut self) -> Self::Map { diff --git a/clippy_lints/src/len_zero.rs b/clippy_lints/src/len_zero.rs index 8bc2a56af9938..c88eb70384333 100644 --- a/clippy_lints/src/len_zero.rs +++ b/clippy_lints/src/len_zero.rs @@ -460,7 +460,7 @@ fn check_for_is_empty( let is_empty = cx .tcx .inherent_impls(impl_ty) - .into_iter() + .iter() .flat_map(|&id| cx.tcx.associated_items(id).filter_by_name_unhygienic(is_empty)) .find(|item| item.kind == AssocKind::Fn); @@ -628,7 +628,7 @@ fn has_is_empty(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { /// Checks the inherent impl's items for an `is_empty(self)` method. fn has_is_empty_impl(cx: &LateContext<'_>, id: DefId) -> bool { let is_empty = sym!(is_empty); - cx.tcx.inherent_impls(id).into_iter().any(|imp| { + cx.tcx.inherent_impls(id).iter().any(|imp| { cx.tcx .associated_items(*imp) .filter_by_name_unhygienic(is_empty) diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 1d41f568f3785..2eb6d99b761f7 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -609,7 +609,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { store.register_late_pass(move |tcx| Box::new(await_holding_invalid::AwaitHolding::new(tcx, conf))); store.register_late_pass(|_| Box::new(serde_api::SerdeApi)); store.register_late_pass(move |_| Box::new(types::Types::new(conf))); - store.register_late_pass(|_| Box::new(booleans::NonminimalBool)); + store.register_late_pass(move |_| Box::new(booleans::NonminimalBool::new(conf))); store.register_late_pass(|_| Box::new(enum_clike::UnportableVariant)); store.register_late_pass(|_| Box::new(float_literal::FloatLiteral)); store.register_late_pass(|_| Box::new(ptr::Ptr)); diff --git a/clippy_lints/src/lifetimes.rs b/clippy_lints/src/lifetimes.rs index 755afe959b37a..034cab7202523 100644 --- a/clippy_lints/src/lifetimes.rs +++ b/clippy_lints/src/lifetimes.rs @@ -6,12 +6,12 @@ use rustc_errors::Applicability; use rustc_hir::FnRetTy::Return; use rustc_hir::intravisit::nested_filter::{self as hir_nested_filter, NestedFilter}; use rustc_hir::intravisit::{ - Visitor, walk_fn_decl, walk_generic_param, walk_generics, walk_impl_item_ref, walk_item, walk_param_bound, - walk_poly_trait_ref, walk_trait_ref, walk_ty, + Visitor, walk_fn_decl, walk_generic_args, walk_generics, walk_impl_item_ref, walk_item, walk_param_bound, + walk_poly_trait_ref, walk_trait_ref, walk_ty, walk_where_predicate, }; use rustc_hir::{ - BareFnTy, BodyId, FnDecl, FnSig, GenericArg, GenericBound, GenericParam, GenericParamKind, Generics, Impl, - ImplItem, ImplItemKind, Item, ItemKind, Lifetime, LifetimeName, LifetimeParamKind, Node, PolyTraitRef, + BareFnTy, BodyId, FnDecl, FnSig, GenericArg, GenericArgs, GenericBound, GenericParam, GenericParamKind, Generics, + Impl, ImplItem, ImplItemKind, Item, ItemKind, Lifetime, LifetimeName, LifetimeParamKind, Node, PolyTraitRef, PredicateOrigin, TraitFn, TraitItem, TraitItemKind, Ty, TyKind, WherePredicate, lang_items, }; use rustc_lint::{LateContext, LateLintPass, LintContext}; @@ -21,7 +21,7 @@ use rustc_middle::lint::in_external_macro; use rustc_session::declare_lint_pass; use rustc_span::Span; use rustc_span::def_id::LocalDefId; -use rustc_span::symbol::{Ident, Symbol, kw}; +use rustc_span::symbol::{Ident, kw}; use std::ops::ControlFlow; declare_clippy_lint! { @@ -191,45 +191,10 @@ fn check_fn_inner<'tcx>( if usages.iter().any(|usage| !usage.ident.span.eq_ctxt(span)) { return; } - - let lts = elidable_lts - .iter() - // In principle, the result of the call to `Node::ident` could be `unwrap`ped, as `DefId` should refer to a - // `Node::GenericParam`. - .filter_map(|&def_id| cx.tcx.hir_node_by_def_id(def_id).ident()) - .map(|ident| ident.to_string()) - .collect::>() - .join(", "); - - span_lint_and_then( - cx, - NEEDLESS_LIFETIMES, - elidable_lts - .iter() - .map(|<| cx.tcx.def_span(lt)) - .chain(usages.iter().filter_map(|usage| { - if let LifetimeName::Param(def_id) = usage.res - && elidable_lts.contains(&def_id) - { - return Some(usage.ident.span); - } - - None - })) - .collect_vec(), - format!("the following explicit lifetimes could be elided: {lts}"), - |diag| { - if sig.header.is_async() { - // async functions have usages whose spans point at the lifetime declaration which messes up - // suggestions - return; - }; - - if let Some(suggestions) = elision_suggestions(cx, generics, &elidable_lts, &usages) { - diag.multipart_suggestion("elide the lifetimes", suggestions, Applicability::MachineApplicable); - } - }, - ); + // async functions have usages whose spans point at the lifetime declaration which messes up + // suggestions + let include_suggestions = !sig.header.is_async(); + report_elidable_lifetimes(cx, generics, &elidable_lts, &usages, include_suggestions); } if report_extra_lifetimes { @@ -237,97 +202,6 @@ fn check_fn_inner<'tcx>( } } -fn elision_suggestions( - cx: &LateContext<'_>, - generics: &Generics<'_>, - elidable_lts: &[LocalDefId], - usages: &[Lifetime], -) -> Option> { - let explicit_params = generics - .params - .iter() - .filter(|param| !param.is_elided_lifetime() && !param.is_impl_trait()) - .collect::>(); - - let mut suggestions = if elidable_lts.len() == explicit_params.len() { - // if all the params are elided remove the whole generic block - // - // fn x<'a>() {} - // ^^^^ - vec![(generics.span, String::new())] - } else { - elidable_lts - .iter() - .map(|&id| { - let pos = explicit_params.iter().position(|param| param.def_id == id)?; - let param = explicit_params.get(pos)?; - - let span = if let Some(next) = explicit_params.get(pos + 1) { - // fn x<'prev, 'a, 'next>() {} - // ^^^^ - param.span.until(next.span) - } else { - // `pos` should be at least 1 here, because the param in position 0 would either have a `next` - // param or would have taken the `elidable_lts.len() == explicit_params.len()` branch. - let prev = explicit_params.get(pos - 1)?; - - // fn x<'prev, 'a>() {} - // ^^^^ - param.span.with_lo(prev.span.hi()) - }; - - Some((span, String::new())) - }) - .collect::>>()? - }; - - suggestions.extend( - usages - .iter() - .filter(|usage| named_lifetime(usage).map_or(false, |id| elidable_lts.contains(&id))) - .map(|usage| { - match cx.tcx.parent_hir_node(usage.hir_id) { - Node::Ty(Ty { - kind: TyKind::Ref(..), .. - }) => { - // expand `&'a T` to `&'a T` - // ^^ ^^^ - let span = cx.sess().source_map().span_extend_while_whitespace(usage.ident.span); - - (span, String::new()) - }, - // `T<'a>` and `impl Foo + 'a` should be replaced by `'_` - _ => (usage.ident.span, String::from("'_")), - } - }), - ); - - Some(suggestions) -} - -// elision doesn't work for explicit self types, see rust-lang/rust#69064 -fn explicit_self_type<'tcx>(cx: &LateContext<'tcx>, func: &FnDecl<'tcx>, ident: Option) -> bool { - if let Some(ident) = ident - && ident.name == kw::SelfLower - && !func.implicit_self.has_implicit_self() - && let Some(self_ty) = func.inputs.first() - { - let mut visitor = RefVisitor::new(cx); - visitor.visit_ty(self_ty); - - !visitor.all_lts().is_empty() - } else { - false - } -} - -fn named_lifetime(lt: &Lifetime) -> Option { - match lt.res { - LifetimeName::Param(id) if !lt.is_anonymous() => Some(id), - _ => None, - } -} - fn could_use_elision<'tcx>( cx: &LateContext<'tcx>, func: &'tcx FnDecl<'_>, @@ -450,6 +324,22 @@ fn allowed_lts_from(named_generics: &[GenericParam<'_>]) -> FxHashSet(cx: &LateContext<'tcx>, func: &FnDecl<'tcx>, ident: Option) -> bool { + if let Some(ident) = ident + && ident.name == kw::SelfLower + && !func.implicit_self.has_implicit_self() + && let Some(self_ty) = func.inputs.first() + { + let mut visitor = RefVisitor::new(cx); + visitor.visit_ty(self_ty); + + !visitor.all_lts().is_empty() + } else { + false + } +} + /// Number of times each named lifetime occurs in the given slice. Returns a vector to preserve /// relative order. #[must_use] @@ -470,6 +360,13 @@ fn named_lifetime_occurrences(lts: &[Lifetime]) -> Vec<(LocalDefId, usize)> { occurrences } +fn named_lifetime(lt: &Lifetime) -> Option { + match lt.res { + LifetimeName::Param(id) if !lt.is_anonymous() => Some(id), + _ => None, + } +} + struct RefVisitor<'a, 'tcx> { cx: &'a LateContext<'tcx>, lts: Vec, @@ -500,7 +397,7 @@ impl<'a, 'tcx> RefVisitor<'a, 'tcx> { } } -impl<'a, 'tcx> Visitor<'tcx> for RefVisitor<'a, 'tcx> { +impl<'tcx> Visitor<'tcx> for RefVisitor<'_, 'tcx> { // for lifetimes as parameters of generics fn visit_lifetime(&mut self, lifetime: &'tcx Lifetime) { self.lts.push(*lifetime); @@ -594,23 +491,43 @@ fn has_where_lifetimes<'tcx>(cx: &LateContext<'tcx>, generics: &'tcx Generics<'_ false } +struct Usage { + lifetime: Lifetime, + in_where_predicate: bool, + in_generics_arg: bool, +} + struct LifetimeChecker<'cx, 'tcx, F> { cx: &'cx LateContext<'tcx>, - map: FxHashMap, + map: FxHashMap>, + where_predicate_depth: usize, + generic_args_depth: usize, phantom: std::marker::PhantomData, } impl<'cx, 'tcx, F> LifetimeChecker<'cx, 'tcx, F> { - fn new(cx: &'cx LateContext<'tcx>, map: FxHashMap) -> LifetimeChecker<'cx, 'tcx, F> { + fn new(cx: &'cx LateContext<'tcx>, generics: &'tcx Generics<'_>) -> LifetimeChecker<'cx, 'tcx, F> { + let map = generics + .params + .iter() + .filter_map(|par| match par.kind { + GenericParamKind::Lifetime { + kind: LifetimeParamKind::Explicit, + } => Some((par.def_id, Vec::new())), + _ => None, + }) + .collect(); Self { cx, map, + where_predicate_depth: 0, + generic_args_depth: 0, phantom: std::marker::PhantomData, } } } -impl<'cx, 'tcx, F> Visitor<'tcx> for LifetimeChecker<'cx, 'tcx, F> +impl<'tcx, F> Visitor<'tcx> for LifetimeChecker<'_, 'tcx, F> where F: NestedFilter<'tcx>, { @@ -619,18 +536,27 @@ where // for lifetimes as parameters of generics fn visit_lifetime(&mut self, lifetime: &'tcx Lifetime) { - self.map.remove(&lifetime.ident.name); + if let LifetimeName::Param(def_id) = lifetime.res + && let Some(usages) = self.map.get_mut(&def_id) + { + usages.push(Usage { + lifetime: *lifetime, + in_where_predicate: self.where_predicate_depth != 0, + in_generics_arg: self.generic_args_depth != 0, + }); + } } - fn visit_generic_param(&mut self, param: &'tcx GenericParam<'_>) { - // don't actually visit `<'a>` or `<'a: 'b>` - // we've already visited the `'a` declarations and - // don't want to spuriously remove them - // `'b` in `'a: 'b` is useless unless used elsewhere in - // a non-lifetime bound - if let GenericParamKind::Type { .. } = param.kind { - walk_generic_param(self, param); - } + fn visit_where_predicate(&mut self, predicate: &'tcx WherePredicate<'tcx>) { + self.where_predicate_depth += 1; + walk_where_predicate(self, predicate); + self.where_predicate_depth -= 1; + } + + fn visit_generic_args(&mut self, generic_args: &'tcx GenericArgs<'tcx>) -> Self::Result { + self.generic_args_depth += 1; + walk_generic_args(self, generic_args); + self.generic_args_depth -= 1; } fn nested_visit_map(&mut self) -> Self::Map { @@ -639,44 +565,28 @@ where } fn report_extra_lifetimes<'tcx>(cx: &LateContext<'tcx>, func: &'tcx FnDecl<'_>, generics: &'tcx Generics<'_>) { - let hs = generics - .params - .iter() - .filter_map(|par| match par.kind { - GenericParamKind::Lifetime { - kind: LifetimeParamKind::Explicit, - } => Some((par.name.ident().name, par.span)), - _ => None, - }) - .collect(); - let mut checker = LifetimeChecker::::new(cx, hs); + let mut checker = LifetimeChecker::::new(cx, generics); walk_generics(&mut checker, generics); walk_fn_decl(&mut checker, func); - for &v in checker.map.values() { - span_lint( - cx, - EXTRA_UNUSED_LIFETIMES, - v, - "this lifetime isn't used in the function definition", - ); + for (def_id, usages) in checker.map { + if usages + .iter() + .all(|usage| usage.in_where_predicate && !usage.in_generics_arg) + { + span_lint( + cx, + EXTRA_UNUSED_LIFETIMES, + cx.tcx.def_span(def_id), + "this lifetime isn't used in the function definition", + ); + } } } fn report_extra_impl_lifetimes<'tcx>(cx: &LateContext<'tcx>, impl_: &'tcx Impl<'_>) { - let hs = impl_ - .generics - .params - .iter() - .filter_map(|par| match par.kind { - GenericParamKind::Lifetime { - kind: LifetimeParamKind::Explicit, - } => Some((par.name.ident().name, par.span)), - _ => None, - }) - .collect(); - let mut checker = LifetimeChecker::::new(cx, hs); + let mut checker = LifetimeChecker::::new(cx, impl_.generics); walk_generics(&mut checker, impl_.generics); if let Some(ref trait_ref) = impl_.of_trait { @@ -687,9 +597,176 @@ fn report_extra_impl_lifetimes<'tcx>(cx: &LateContext<'tcx>, impl_: &'tcx Impl<' walk_impl_item_ref(&mut checker, item); } - for &v in checker.map.values() { - span_lint(cx, EXTRA_UNUSED_LIFETIMES, v, "this lifetime isn't used in the impl"); + for (&def_id, usages) in &checker.map { + if usages + .iter() + .all(|usage| usage.in_where_predicate && !usage.in_generics_arg) + { + span_lint( + cx, + EXTRA_UNUSED_LIFETIMES, + cx.tcx.def_span(def_id), + "this lifetime isn't used in the impl", + ); + } + } + + report_elidable_impl_lifetimes(cx, impl_, &checker.map); +} + +// An `impl` lifetime is elidable if it satisfies the following conditions: +// - It is used exactly once. +// - That single use is not in `GenericArgs` in a `WherePredicate`. (Note that `GenericArgs` are +// different from `GenericParam`s.) +fn report_elidable_impl_lifetimes<'tcx>( + cx: &LateContext<'tcx>, + impl_: &'tcx Impl<'_>, + map: &FxHashMap>, +) { + let single_usages = map + .iter() + .filter_map(|(def_id, usages)| { + if let [ + Usage { + lifetime, + in_where_predicate: false, + .. + } + | Usage { + lifetime, + in_generics_arg: false, + .. + }, + ] = usages.as_slice() + { + Some((def_id, lifetime)) + } else { + None + } + }) + .collect::>(); + + if single_usages.is_empty() { + return; } + + let (elidable_lts, usages): (Vec<_>, Vec<_>) = single_usages.into_iter().unzip(); + + report_elidable_lifetimes(cx, impl_.generics, &elidable_lts, &usages, true); +} + +/// Generate diagnostic messages for elidable lifetimes. +fn report_elidable_lifetimes( + cx: &LateContext<'_>, + generics: &Generics<'_>, + elidable_lts: &[LocalDefId], + usages: &[Lifetime], + include_suggestions: bool, +) { + let lts = elidable_lts + .iter() + // In principle, the result of the call to `Node::ident` could be `unwrap`ped, as `DefId` should refer to a + // `Node::GenericParam`. + .filter_map(|&def_id| cx.tcx.hir_node_by_def_id(def_id).ident()) + .map(|ident| ident.to_string()) + .collect::>() + .join(", "); + + span_lint_and_then( + cx, + NEEDLESS_LIFETIMES, + elidable_lts + .iter() + .map(|<| cx.tcx.def_span(lt)) + .chain(usages.iter().filter_map(|usage| { + if let LifetimeName::Param(def_id) = usage.res + && elidable_lts.contains(&def_id) + { + return Some(usage.ident.span); + } + + None + })) + .collect_vec(), + format!("the following explicit lifetimes could be elided: {lts}"), + |diag| { + if !include_suggestions { + return; + }; + + if let Some(suggestions) = elision_suggestions(cx, generics, elidable_lts, usages) { + diag.multipart_suggestion("elide the lifetimes", suggestions, Applicability::MachineApplicable); + } + }, + ); +} + +fn elision_suggestions( + cx: &LateContext<'_>, + generics: &Generics<'_>, + elidable_lts: &[LocalDefId], + usages: &[Lifetime], +) -> Option> { + let explicit_params = generics + .params + .iter() + .filter(|param| !param.is_elided_lifetime() && !param.is_impl_trait()) + .collect::>(); + + let mut suggestions = if elidable_lts.len() == explicit_params.len() { + // if all the params are elided remove the whole generic block + // + // fn x<'a>() {} + // ^^^^ + vec![(generics.span, String::new())] + } else { + elidable_lts + .iter() + .map(|&id| { + let pos = explicit_params.iter().position(|param| param.def_id == id)?; + let param = explicit_params.get(pos)?; + + let span = if let Some(next) = explicit_params.get(pos + 1) { + // fn x<'prev, 'a, 'next>() {} + // ^^^^ + param.span.until(next.span) + } else { + // `pos` should be at least 1 here, because the param in position 0 would either have a `next` + // param or would have taken the `elidable_lts.len() == explicit_params.len()` branch. + let prev = explicit_params.get(pos - 1)?; + + // fn x<'prev, 'a>() {} + // ^^^^ + param.span.with_lo(prev.span.hi()) + }; + + Some((span, String::new())) + }) + .collect::>>()? + }; + + suggestions.extend( + usages + .iter() + .filter(|usage| named_lifetime(usage).map_or(false, |id| elidable_lts.contains(&id))) + .map(|usage| { + match cx.tcx.parent_hir_node(usage.hir_id) { + Node::Ty(Ty { + kind: TyKind::Ref(..), .. + }) => { + // expand `&'a T` to `&'a T` + // ^^ ^^^ + let span = cx.sess().source_map().span_extend_while_whitespace(usage.ident.span); + + (span, String::new()) + }, + // `T<'a>` and `impl Foo + 'a` should be replaced by `'_` + _ => (usage.ident.span, String::from("'_")), + } + }), + ); + + Some(suggestions) } struct BodyLifetimeChecker; diff --git a/clippy_lints/src/loops/manual_memcpy.rs b/clippy_lints/src/loops/manual_memcpy.rs index a7c1d1bd6cd36..68d063ad5e54e 100644 --- a/clippy_lints/src/loops/manual_memcpy.rs +++ b/clippy_lints/src/loops/manual_memcpy.rs @@ -209,7 +209,7 @@ fn build_manual_memcpy_suggestion<'tcx>( #[derive(Clone)] struct MinifyingSugg<'a>(Sugg<'a>); -impl<'a> Display for MinifyingSugg<'a> { +impl Display for MinifyingSugg<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.0.fmt(f) } diff --git a/clippy_lints/src/loops/missing_spin_loop.rs b/clippy_lints/src/loops/missing_spin_loop.rs index e405829b2f4ba..a9944d64ce2dd 100644 --- a/clippy_lints/src/loops/missing_spin_loop.rs +++ b/clippy_lints/src/loops/missing_spin_loop.rs @@ -1,6 +1,6 @@ use super::MISSING_SPIN_LOOP; use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::is_no_std_crate; +use clippy_utils::std_or_core; use rustc_errors::Applicability; use rustc_hir::{Block, Expr, ExprKind}; use rustc_lint::LateContext; @@ -41,6 +41,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, cond: &'tcx Expr<'_>, body: &' && [sym::load, sym::compare_exchange, sym::compare_exchange_weak].contains(&method.ident.name) && let ty::Adt(def, _args) = cx.typeck_results().expr_ty(callee).kind() && cx.tcx.is_diagnostic_item(sym::AtomicBool, def.did()) + && let Some(std_or_core) = std_or_core(cx) { span_lint_and_sugg( cx, @@ -48,12 +49,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, cond: &'tcx Expr<'_>, body: &' body.span, "busy-waiting loop should at least have a spin loop hint", "try", - (if is_no_std_crate(cx) { - "{ core::hint::spin_loop() }" - } else { - "{ std::hint::spin_loop() }" - }) - .into(), + format!("{{ {std_or_core}::hint::spin_loop() }}"), Applicability::MachineApplicable, ); } diff --git a/clippy_lints/src/loops/needless_range_loop.rs b/clippy_lints/src/loops/needless_range_loop.rs index 20dd5a311dca0..745f070a577d5 100644 --- a/clippy_lints/src/loops/needless_range_loop.rs +++ b/clippy_lints/src/loops/needless_range_loop.rs @@ -241,7 +241,7 @@ struct VarVisitor<'a, 'tcx> { prefer_mutable: bool, } -impl<'a, 'tcx> VarVisitor<'a, 'tcx> { +impl<'tcx> VarVisitor<'_, 'tcx> { fn check(&mut self, idx: &'tcx Expr<'_>, seqexpr: &'tcx Expr<'_>, expr: &'tcx Expr<'_>) -> bool { if let ExprKind::Path(ref seqpath) = seqexpr.kind // the indexed container is referenced by a name @@ -292,7 +292,7 @@ impl<'a, 'tcx> VarVisitor<'a, 'tcx> { } } -impl<'a, 'tcx> Visitor<'tcx> for VarVisitor<'a, 'tcx> { +impl<'tcx> Visitor<'tcx> for VarVisitor<'_, 'tcx> { fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { if let ExprKind::MethodCall(meth, args_0, [args_1, ..], _) = &expr.kind // a range index op diff --git a/clippy_lints/src/loops/same_item_push.rs b/clippy_lints/src/loops/same_item_push.rs index 5662d3013e15b..f8659897ffe97 100644 --- a/clippy_lints/src/loops/same_item_push.rs +++ b/clippy_lints/src/loops/same_item_push.rs @@ -123,7 +123,7 @@ impl<'a, 'tcx> SameItemPushVisitor<'a, 'tcx> { } } -impl<'a, 'tcx> Visitor<'tcx> for SameItemPushVisitor<'a, 'tcx> { +impl<'tcx> Visitor<'tcx> for SameItemPushVisitor<'_, 'tcx> { fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { match &expr.kind { // Non-determinism may occur ... don't give a lint diff --git a/clippy_lints/src/loops/utils.rs b/clippy_lints/src/loops/utils.rs index 9a89a41d2b395..c4c504e1ae4ff 100644 --- a/clippy_lints/src/loops/utils.rs +++ b/clippy_lints/src/loops/utils.rs @@ -44,7 +44,7 @@ impl<'a, 'tcx> IncrementVisitor<'a, 'tcx> { } } -impl<'a, 'tcx> Visitor<'tcx> for IncrementVisitor<'a, 'tcx> { +impl<'tcx> Visitor<'tcx> for IncrementVisitor<'_, 'tcx> { fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { // If node is a variable if let Some(def_id) = path_to_local(expr) { @@ -138,7 +138,7 @@ impl<'a, 'tcx> InitializeVisitor<'a, 'tcx> { } } -impl<'a, 'tcx> Visitor<'tcx> for InitializeVisitor<'a, 'tcx> { +impl<'tcx> Visitor<'tcx> for InitializeVisitor<'_, 'tcx> { type NestedFilter = nested_filter::OnlyBodies; fn visit_local(&mut self, l: &'tcx LetStmt<'_>) { diff --git a/clippy_lints/src/loops/while_immutable_condition.rs b/clippy_lints/src/loops/while_immutable_condition.rs index eab096e9a227d..1a1cde3c5bd63 100644 --- a/clippy_lints/src/loops/while_immutable_condition.rs +++ b/clippy_lints/src/loops/while_immutable_condition.rs @@ -84,7 +84,7 @@ struct VarCollectorVisitor<'a, 'tcx> { skip: bool, } -impl<'a, 'tcx> VarCollectorVisitor<'a, 'tcx> { +impl<'tcx> VarCollectorVisitor<'_, 'tcx> { fn insert_def_id(&mut self, ex: &'tcx Expr<'_>) { if let ExprKind::Path(ref qpath) = ex.kind && let QPath::Resolved(None, _) = *qpath @@ -103,7 +103,7 @@ impl<'a, 'tcx> VarCollectorVisitor<'a, 'tcx> { } } -impl<'a, 'tcx> Visitor<'tcx> for VarCollectorVisitor<'a, 'tcx> { +impl<'tcx> Visitor<'tcx> for VarCollectorVisitor<'_, 'tcx> { fn visit_expr(&mut self, ex: &'tcx Expr<'_>) { match ex.kind { ExprKind::Path(_) => self.insert_def_id(ex), diff --git a/clippy_lints/src/loops/while_let_on_iterator.rs b/clippy_lints/src/loops/while_let_on_iterator.rs index 7d9fbaf3ceabf..74467522619e9 100644 --- a/clippy_lints/src/loops/while_let_on_iterator.rs +++ b/clippy_lints/src/loops/while_let_on_iterator.rs @@ -283,7 +283,7 @@ fn needs_mutable_borrow(cx: &LateContext<'_>, iter_expr: &IterExpr, loop_expr: & found_local: bool, used_after: bool, } - impl<'a, 'b, 'tcx> Visitor<'tcx> for NestedLoopVisitor<'a, 'b, 'tcx> { + impl<'tcx> Visitor<'tcx> for NestedLoopVisitor<'_, '_, 'tcx> { type NestedFilter = OnlyBodies; fn nested_visit_map(&mut self) -> Self::Map { self.cx.tcx.hir() diff --git a/clippy_lints/src/macro_metavars_in_unsafe.rs b/clippy_lints/src/macro_metavars_in_unsafe.rs index ccc554042d685..312bcb55a9533 100644 --- a/clippy_lints/src/macro_metavars_in_unsafe.rs +++ b/clippy_lints/src/macro_metavars_in_unsafe.rs @@ -149,7 +149,7 @@ fn is_public_macro(cx: &LateContext<'_>, def_id: LocalDefId) -> bool { && !cx.tcx.is_doc_hidden(def_id) } -impl<'a, 'tcx> Visitor<'tcx> for BodyVisitor<'a, 'tcx> { +impl<'tcx> Visitor<'tcx> for BodyVisitor<'_, 'tcx> { fn visit_stmt(&mut self, s: &'tcx Stmt<'tcx>) { let from_expn = s.span.from_expansion(); if from_expn { diff --git a/clippy_lints/src/macro_use.rs b/clippy_lints/src/macro_use.rs index bd6b3f1a47b16..50680331fbc4e 100644 --- a/clippy_lints/src/macro_use.rs +++ b/clippy_lints/src/macro_use.rs @@ -81,7 +81,7 @@ impl MacroUseImports { } } -impl<'tcx> LateLintPass<'tcx> for MacroUseImports { +impl LateLintPass<'_> for MacroUseImports { fn check_item(&mut self, cx: &LateContext<'_>, item: &hir::Item<'_>) { if cx.sess().opts.edition >= Edition::Edition2018 && let hir::ItemKind::Use(path, _kind) = &item.kind diff --git a/clippy_lints/src/manual_clamp.rs b/clippy_lints/src/manual_clamp.rs index 788649fd4f90e..fd66cacdfe933 100644 --- a/clippy_lints/src/manual_clamp.rs +++ b/clippy_lints/src/manual_clamp.rs @@ -741,7 +741,7 @@ enum MaybeBorrowedStmtKind<'a> { Owned(StmtKind<'a>), } -impl<'a> Clone for MaybeBorrowedStmtKind<'a> { +impl Clone for MaybeBorrowedStmtKind<'_> { fn clone(&self) -> Self { match self { Self::Borrowed(t) => Self::Borrowed(t), diff --git a/clippy_lints/src/manual_strip.rs b/clippy_lints/src/manual_strip.rs index 9aceca66bf774..828c5a3f6ffd2 100644 --- a/clippy_lints/src/manual_strip.rs +++ b/clippy_lints/src/manual_strip.rs @@ -203,7 +203,7 @@ fn find_stripping<'tcx>( results: Vec, } - impl<'a, 'tcx> Visitor<'tcx> for StrippingFinder<'a, 'tcx> { + impl<'tcx> Visitor<'tcx> for StrippingFinder<'_, 'tcx> { fn visit_expr(&mut self, ex: &'tcx Expr<'_>) { if is_ref_str(self.cx, ex) && let unref = peel_ref(ex) diff --git a/clippy_lints/src/map_unit_fn.rs b/clippy_lints/src/map_unit_fn.rs index a97dbbbc33fd0..3221a04d2d03f 100644 --- a/clippy_lints/src/map_unit_fn.rs +++ b/clippy_lints/src/map_unit_fn.rs @@ -251,7 +251,7 @@ fn lint_map_unit_fn( } } -impl<'tcx> LateLintPass<'tcx> for MapUnit { +impl LateLintPass<'_> for MapUnit { fn check_stmt(&mut self, cx: &LateContext<'_>, stmt: &hir::Stmt<'_>) { if let hir::StmtKind::Semi(expr) = stmt.kind && !stmt.span.from_expansion() diff --git a/clippy_lints/src/matches/match_str_case_mismatch.rs b/clippy_lints/src/matches/match_str_case_mismatch.rs index 40518ce2ca775..463aa602bc8b9 100644 --- a/clippy_lints/src/matches/match_str_case_mismatch.rs +++ b/clippy_lints/src/matches/match_str_case_mismatch.rs @@ -40,7 +40,7 @@ struct MatchExprVisitor<'a, 'tcx> { case_method: Option, } -impl<'a, 'tcx> Visitor<'tcx> for MatchExprVisitor<'a, 'tcx> { +impl<'tcx> Visitor<'tcx> for MatchExprVisitor<'_, 'tcx> { fn visit_expr(&mut self, ex: &'tcx Expr<'_>) { match ex.kind { ExprKind::MethodCall(segment, receiver, [], _) if self.case_altered(segment.ident.as_str(), receiver) => {}, @@ -49,7 +49,7 @@ impl<'a, 'tcx> Visitor<'tcx> for MatchExprVisitor<'a, 'tcx> { } } -impl<'a, 'tcx> MatchExprVisitor<'a, 'tcx> { +impl MatchExprVisitor<'_, '_> { fn case_altered(&mut self, segment_ident: &str, receiver: &Expr<'_>) -> bool { if let Some(case_method) = get_case_method(segment_ident) { let ty = self.cx.typeck_results().expr_ty(receiver).peel_refs(); diff --git a/clippy_lints/src/matches/mod.rs b/clippy_lints/src/matches/mod.rs index 686fc4a0fa0a5..28adcc2f22746 100644 --- a/clippy_lints/src/matches/mod.rs +++ b/clippy_lints/src/matches/mod.rs @@ -1045,7 +1045,7 @@ impl<'tcx> LateLintPass<'tcx> for Matches { if !from_expansion { // These don't depend on a relationship between multiple arms match_wild_err_arm::check(cx, ex, arms); - wild_in_or_pats::check(cx, arms); + wild_in_or_pats::check(cx, ex, arms); } if let MatchSource::TryDesugar(_) = source { diff --git a/clippy_lints/src/matches/overlapping_arms.rs b/clippy_lints/src/matches/overlapping_arms.rs index 6a4c553cee0ee..856311899f268 100644 --- a/clippy_lints/src/matches/overlapping_arms.rs +++ b/clippy_lints/src/matches/overlapping_arms.rs @@ -96,13 +96,13 @@ where #[derive(Copy, Clone, Debug, Eq, PartialEq)] struct RangeBound<'a, T>(T, BoundKind, &'a SpannedRange); - impl<'a, T: Copy + Ord> PartialOrd for RangeBound<'a, T> { + impl PartialOrd for RangeBound<'_, T> { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } - impl<'a, T: Copy + Ord> Ord for RangeBound<'a, T> { + impl Ord for RangeBound<'_, T> { fn cmp(&self, RangeBound(other_value, other_kind, _): &Self) -> Ordering { let RangeBound(self_value, self_kind, _) = *self; (self_value, self_kind).cmp(&(*other_value, *other_kind)) diff --git a/clippy_lints/src/matches/significant_drop_in_scrutinee.rs b/clippy_lints/src/matches/significant_drop_in_scrutinee.rs index 537f7272f7f34..7372f52e1e5bd 100644 --- a/clippy_lints/src/matches/significant_drop_in_scrutinee.rs +++ b/clippy_lints/src/matches/significant_drop_in_scrutinee.rs @@ -424,7 +424,7 @@ fn ty_has_erased_regions(ty: Ty<'_>) -> bool { ty.visit_with(&mut V).is_break() } -impl<'a, 'tcx> Visitor<'tcx> for SigDropHelper<'a, 'tcx> { +impl<'tcx> Visitor<'tcx> for SigDropHelper<'_, 'tcx> { fn visit_expr(&mut self, ex: &'tcx Expr<'_>) { // We've emitted a lint on some neighborhood expression. That lint will suggest to move out the // _parent_ expression (not the expression itself). Since we decide to move out the parent @@ -495,7 +495,7 @@ fn has_significant_drop_in_arms<'tcx>(cx: &LateContext<'tcx>, arms: &[&'tcx Expr helper.found_sig_drop_spans } -impl<'a, 'tcx> Visitor<'tcx> for ArmSigDropHelper<'a, 'tcx> { +impl<'tcx> Visitor<'tcx> for ArmSigDropHelper<'_, 'tcx> { fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) { if self.sig_drop_checker.is_sig_drop_expr(ex) { self.found_sig_drop_spans.insert(ex.span); diff --git a/clippy_lints/src/matches/wild_in_or_pats.rs b/clippy_lints/src/matches/wild_in_or_pats.rs index 459513e65bfad..390ba889fd2e6 100644 --- a/clippy_lints/src/matches/wild_in_or_pats.rs +++ b/clippy_lints/src/matches/wild_in_or_pats.rs @@ -1,11 +1,19 @@ use clippy_utils::diagnostics::span_lint_and_help; -use clippy_utils::is_wild; -use rustc_hir::{Arm, PatKind}; +use clippy_utils::{has_non_exhaustive_attr, is_wild}; +use rustc_hir::{Arm, Expr, PatKind}; use rustc_lint::LateContext; +use rustc_middle::ty; use super::WILDCARD_IN_OR_PATTERNS; -pub(crate) fn check(cx: &LateContext<'_>, arms: &[Arm<'_>]) { +pub(crate) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, arms: &[Arm<'_>]) { + // first check if we are matching on an enum that has the non_exhaustive attribute + let ty = cx.typeck_results().expr_ty(expr).peel_refs(); + if let ty::Adt(adt_def, _) = ty.kind() + && has_non_exhaustive_attr(cx.tcx, *adt_def) + { + return; + }; for arm in arms { if let PatKind::Or(fields) = arm.pat.kind { // look for multiple fields in this arm that contains at least one Wild pattern diff --git a/clippy_lints/src/methods/mut_mutex_lock.rs b/clippy_lints/src/methods/mut_mutex_lock.rs index 83e8370f939bc..320523aceb675 100644 --- a/clippy_lints/src/methods/mut_mutex_lock.rs +++ b/clippy_lints/src/methods/mut_mutex_lock.rs @@ -1,17 +1,17 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::expr_custom_deref_adjustment; -use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::ty::{is_type_diagnostic_item, peel_mid_ty_refs_is_mutable}; use rustc_errors::Applicability; use rustc_hir::{Expr, Mutability}; use rustc_lint::LateContext; -use rustc_middle::ty; use rustc_span::{Span, sym}; use super::MUT_MUTEX_LOCK; pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, ex: &'tcx Expr<'tcx>, recv: &'tcx Expr<'tcx>, name_span: Span) { if matches!(expr_custom_deref_adjustment(cx, recv), None | Some(Mutability::Mut)) - && let ty::Ref(_, _, Mutability::Mut) = cx.typeck_results().expr_ty(recv).kind() + && let (_, ref_depth, Mutability::Mut) = peel_mid_ty_refs_is_mutable(cx.typeck_results().expr_ty(recv)) + && ref_depth >= 1 && let Some(method_id) = cx.typeck_results().type_dependent_def_id(ex.hir_id) && let Some(impl_id) = cx.tcx.impl_of_method(method_id) && is_type_diagnostic_item(cx, cx.tcx.type_of(impl_id).instantiate_identity(), sym::Mutex) diff --git a/clippy_lints/src/methods/needless_collect.rs b/clippy_lints/src/methods/needless_collect.rs index 421c7a5e070d9..c58e27e37ad31 100644 --- a/clippy_lints/src/methods/needless_collect.rs +++ b/clippy_lints/src/methods/needless_collect.rs @@ -441,7 +441,7 @@ struct UsedCountVisitor<'a, 'tcx> { count: usize, } -impl<'a, 'tcx> Visitor<'tcx> for UsedCountVisitor<'a, 'tcx> { +impl<'tcx> Visitor<'tcx> for UsedCountVisitor<'_, 'tcx> { type NestedFilter = nested_filter::OnlyBodies; fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { diff --git a/clippy_lints/src/methods/option_map_unwrap_or.rs b/clippy_lints/src/methods/option_map_unwrap_or.rs index b160ab6de8e95..528e2204cf8f7 100644 --- a/clippy_lints/src/methods/option_map_unwrap_or.rs +++ b/clippy_lints/src/methods/option_map_unwrap_or.rs @@ -130,7 +130,7 @@ struct UnwrapVisitor<'a, 'tcx> { identifiers: FxHashSet, } -impl<'a, 'tcx> Visitor<'tcx> for UnwrapVisitor<'a, 'tcx> { +impl<'tcx> Visitor<'tcx> for UnwrapVisitor<'_, 'tcx> { type NestedFilter = nested_filter::All; fn visit_path(&mut self, path: &Path<'tcx>, _: HirId) { @@ -154,7 +154,7 @@ struct ReferenceVisitor<'a, 'tcx> { unwrap_or_span: Span, } -impl<'a, 'tcx> Visitor<'tcx> for ReferenceVisitor<'a, 'tcx> { +impl<'tcx> Visitor<'tcx> for ReferenceVisitor<'_, 'tcx> { type NestedFilter = nested_filter::All; type Result = ControlFlow<()>; fn visit_expr(&mut self, expr: &'tcx rustc_hir::Expr<'_>) -> ControlFlow<()> { diff --git a/clippy_lints/src/methods/or_fun_call.rs b/clippy_lints/src/methods/or_fun_call.rs index c60a839432c97..b971f60d41699 100644 --- a/clippy_lints/src/methods/or_fun_call.rs +++ b/clippy_lints/src/methods/or_fun_call.rs @@ -77,7 +77,7 @@ pub(super) fn check<'tcx>( let Some(suggested_method_def_id) = receiver_ty.ty_adt_def().and_then(|adt_def| { cx.tcx .inherent_impls(adt_def.did()) - .into_iter() + .iter() .flat_map(|impl_id| cx.tcx.associated_items(impl_id).filter_by_name_unhygienic(sugg)) .find_map(|assoc| { if assoc.fn_has_self_parameter diff --git a/clippy_lints/src/methods/utils.rs b/clippy_lints/src/methods/utils.rs index 4e33dc1df54d8..cf0ee569f1310 100644 --- a/clippy_lints/src/methods/utils.rs +++ b/clippy_lints/src/methods/utils.rs @@ -86,7 +86,7 @@ struct CloneOrCopyVisitor<'cx, 'tcx> { references_to_binding: Vec<(Span, String)>, } -impl<'cx, 'tcx> Visitor<'tcx> for CloneOrCopyVisitor<'cx, 'tcx> { +impl<'tcx> Visitor<'tcx> for CloneOrCopyVisitor<'_, 'tcx> { type NestedFilter = nested_filter::OnlyBodies; fn nested_visit_map(&mut self) -> Self::Map { @@ -123,7 +123,7 @@ impl<'cx, 'tcx> Visitor<'tcx> for CloneOrCopyVisitor<'cx, 'tcx> { } } -impl<'cx, 'tcx> CloneOrCopyVisitor<'cx, 'tcx> { +impl<'tcx> CloneOrCopyVisitor<'_, 'tcx> { fn is_binding(&self, expr: &Expr<'tcx>) -> bool { self.binding_hir_ids .iter() diff --git a/clippy_lints/src/mixed_read_write_in_expression.rs b/clippy_lints/src/mixed_read_write_in_expression.rs index d333b71edb1ae..a7452c8a3c84e 100644 --- a/clippy_lints/src/mixed_read_write_in_expression.rs +++ b/clippy_lints/src/mixed_read_write_in_expression.rs @@ -116,7 +116,7 @@ struct DivergenceVisitor<'a, 'tcx> { cx: &'a LateContext<'tcx>, } -impl<'a, 'tcx> DivergenceVisitor<'a, 'tcx> { +impl<'tcx> DivergenceVisitor<'_, 'tcx> { fn maybe_walk_expr(&mut self, e: &'tcx Expr<'_>) { match e.kind { ExprKind::Closure(..) | ExprKind::If(..) | ExprKind::Loop(..) => {}, @@ -148,7 +148,7 @@ fn stmt_might_diverge(stmt: &Stmt<'_>) -> bool { !matches!(stmt.kind, StmtKind::Item(..)) } -impl<'a, 'tcx> Visitor<'tcx> for DivergenceVisitor<'a, 'tcx> { +impl<'tcx> Visitor<'tcx> for DivergenceVisitor<'_, 'tcx> { fn visit_expr(&mut self, e: &'tcx Expr<'_>) { match e.kind { // fix #10776 @@ -321,7 +321,7 @@ struct ReadVisitor<'a, 'tcx> { last_expr: &'tcx Expr<'tcx>, } -impl<'a, 'tcx> Visitor<'tcx> for ReadVisitor<'a, 'tcx> { +impl<'tcx> Visitor<'tcx> for ReadVisitor<'_, 'tcx> { fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { if expr.hir_id == self.last_expr.hir_id { return; diff --git a/clippy_lints/src/mut_mut.rs b/clippy_lints/src/mut_mut.rs index 60372121a7a4c..6cddd7ea813b2 100644 --- a/clippy_lints/src/mut_mut.rs +++ b/clippy_lints/src/mut_mut.rs @@ -55,7 +55,7 @@ pub struct MutVisitor<'a, 'tcx> { cx: &'a LateContext<'tcx>, } -impl<'a, 'tcx> intravisit::Visitor<'tcx> for MutVisitor<'a, 'tcx> { +impl<'tcx> intravisit::Visitor<'tcx> for MutVisitor<'_, 'tcx> { fn visit_expr(&mut self, expr: &'tcx hir::Expr<'_>) { if in_external_macro(self.cx.sess(), expr.span) { return; diff --git a/clippy_lints/src/mutable_debug_assertion.rs b/clippy_lints/src/mutable_debug_assertion.rs index 785bf70a3ec9f..152635a5c35c7 100644 --- a/clippy_lints/src/mutable_debug_assertion.rs +++ b/clippy_lints/src/mutable_debug_assertion.rs @@ -87,7 +87,7 @@ impl<'a, 'tcx> MutArgVisitor<'a, 'tcx> { } } -impl<'a, 'tcx> Visitor<'tcx> for MutArgVisitor<'a, 'tcx> { +impl<'tcx> Visitor<'tcx> for MutArgVisitor<'_, 'tcx> { type NestedFilter = nested_filter::OnlyBodies; fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { diff --git a/clippy_lints/src/needless_for_each.rs b/clippy_lints/src/needless_for_each.rs index b54eb164e81d5..93e20f37ef8ac 100644 --- a/clippy_lints/src/needless_for_each.rs +++ b/clippy_lints/src/needless_for_each.rs @@ -133,7 +133,7 @@ struct RetCollector { loop_depth: u16, } -impl<'tcx> Visitor<'tcx> for RetCollector { +impl Visitor<'_> for RetCollector { fn visit_expr(&mut self, expr: &Expr<'_>) { match expr.kind { ExprKind::Ret(..) => { diff --git a/clippy_lints/src/needless_pass_by_ref_mut.rs b/clippy_lints/src/needless_pass_by_ref_mut.rs index 19cbf59590840..c2facb2fcf68c 100644 --- a/clippy_lints/src/needless_pass_by_ref_mut.rs +++ b/clippy_lints/src/needless_pass_by_ref_mut.rs @@ -311,7 +311,7 @@ struct MutablyUsedVariablesCtxt<'tcx> { tcx: TyCtxt<'tcx>, } -impl<'tcx> MutablyUsedVariablesCtxt<'tcx> { +impl MutablyUsedVariablesCtxt<'_> { fn add_mutably_used_var(&mut self, used_id: HirId) { self.mutably_used_vars.insert(used_id); } diff --git a/clippy_lints/src/non_expressive_names.rs b/clippy_lints/src/non_expressive_names.rs index d85032e9eee84..2fee1c72a91b5 100644 --- a/clippy_lints/src/non_expressive_names.rs +++ b/clippy_lints/src/non_expressive_names.rs @@ -104,7 +104,7 @@ struct SimilarNamesLocalVisitor<'a, 'tcx> { single_char_names: Vec>, } -impl<'a, 'tcx> SimilarNamesLocalVisitor<'a, 'tcx> { +impl SimilarNamesLocalVisitor<'_, '_> { fn check_single_char_names(&self) { if self.single_char_names.last().map(Vec::len) == Some(0) { return; @@ -152,7 +152,7 @@ fn chars_are_similar(a: char, b: char) -> bool { struct SimilarNamesNameVisitor<'a, 'tcx, 'b>(&'b mut SimilarNamesLocalVisitor<'a, 'tcx>); -impl<'a, 'tcx, 'b> Visitor<'tcx> for SimilarNamesNameVisitor<'a, 'tcx, 'b> { +impl<'tcx> Visitor<'tcx> for SimilarNamesNameVisitor<'_, 'tcx, '_> { fn visit_pat(&mut self, pat: &'tcx Pat) { match pat.kind { PatKind::Ident(_, ident, _) => { @@ -189,7 +189,7 @@ fn allowed_to_be_similar(interned_name: &str, list: &[&str]) -> bool { .any(|&name| interned_name.starts_with(name) || interned_name.ends_with(name)) } -impl<'a, 'tcx, 'b> SimilarNamesNameVisitor<'a, 'tcx, 'b> { +impl SimilarNamesNameVisitor<'_, '_, '_> { fn check_short_ident(&mut self, ident: Ident) { // Ignore shadowing if self @@ -329,7 +329,7 @@ impl<'a, 'tcx, 'b> SimilarNamesNameVisitor<'a, 'tcx, 'b> { } } -impl<'a, 'b> SimilarNamesLocalVisitor<'a, 'b> { +impl SimilarNamesLocalVisitor<'_, '_> { /// ensure scoping rules work fn apply Fn(&'c mut Self)>(&mut self, f: F) { let n = self.names.len(); @@ -340,7 +340,7 @@ impl<'a, 'b> SimilarNamesLocalVisitor<'a, 'b> { } } -impl<'a, 'tcx> Visitor<'tcx> for SimilarNamesLocalVisitor<'a, 'tcx> { +impl<'tcx> Visitor<'tcx> for SimilarNamesLocalVisitor<'_, 'tcx> { fn visit_local(&mut self, local: &'tcx Local) { if let Some((init, els)) = &local.kind.init_else_opt() { self.apply(|this| walk_expr(this, init)); diff --git a/clippy_lints/src/non_send_fields_in_send_ty.rs b/clippy_lints/src/non_send_fields_in_send_ty.rs index a60988ac5dbc2..793eb5d945603 100644 --- a/clippy_lints/src/non_send_fields_in_send_ty.rs +++ b/clippy_lints/src/non_send_fields_in_send_ty.rs @@ -159,7 +159,7 @@ struct NonSendField<'tcx> { generic_params: Vec>, } -impl<'tcx> NonSendField<'tcx> { +impl NonSendField<'_> { fn generic_params_string(&self) -> String { self.generic_params .iter() diff --git a/clippy_lints/src/pass_by_ref_or_value.rs b/clippy_lints/src/pass_by_ref_or_value.rs index 75d8c09f2b08d..1bddfab39c694 100644 --- a/clippy_lints/src/pass_by_ref_or_value.rs +++ b/clippy_lints/src/pass_by_ref_or_value.rs @@ -110,7 +110,7 @@ pub struct PassByRefOrValue { avoid_breaking_exported_api: bool, } -impl<'tcx> PassByRefOrValue { +impl PassByRefOrValue { pub fn new(tcx: TyCtxt<'_>, conf: &'static Conf) -> Self { let ref_min_size = conf.trivial_copy_size_limit.unwrap_or_else(|| { let bit_width = u64::from(tcx.sess.target.pointer_width); @@ -130,7 +130,7 @@ impl<'tcx> PassByRefOrValue { } } - fn check_poly_fn(&mut self, cx: &LateContext<'tcx>, def_id: LocalDefId, decl: &FnDecl<'_>, span: Option) { + fn check_poly_fn(&mut self, cx: &LateContext<'_>, def_id: LocalDefId, decl: &FnDecl<'_>, span: Option) { if self.avoid_breaking_exported_api && cx.effective_visibilities.is_exported(def_id) { return; } diff --git a/clippy_lints/src/pathbuf_init_then_push.rs b/clippy_lints/src/pathbuf_init_then_push.rs index 1b9a5a4438297..9f84686a0b12e 100644 --- a/clippy_lints/src/pathbuf_init_then_push.rs +++ b/clippy_lints/src/pathbuf_init_then_push.rs @@ -60,7 +60,7 @@ struct PathbufPushSearcher<'tcx> { err_span: Span, } -impl<'tcx> PathbufPushSearcher<'tcx> { +impl PathbufPushSearcher<'_> { /// Try to generate a suggestion with `PathBuf::from`. /// Returns `None` if the suggestion would be invalid. fn gen_pathbuf_from(&self, cx: &LateContext<'_>) -> Option { diff --git a/clippy_lints/src/ptr.rs b/clippy_lints/src/ptr.rs index 807636bb642b2..bb8a9b6fca85b 100644 --- a/clippy_lints/src/ptr.rs +++ b/clippy_lints/src/ptr.rs @@ -271,14 +271,18 @@ fn check_invalid_ptr_usage<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { && let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id() && let Some(name) = cx.tcx.get_diagnostic_name(fun_def_id) { + // TODO: `ptr_slice_from_raw_parts` and its mutable variant should probably still be linted + // conditionally based on how the return value is used, but not universally like the other + // functions since there are valid uses for null slice pointers. + // + // See: https://github.com/rust-lang/rust-clippy/pull/13452/files#r1773772034 + // `arg` positions where null would cause U.B. let arg_indices: &[_] = match name { sym::ptr_read | sym::ptr_read_unaligned | sym::ptr_read_volatile | sym::ptr_replace - | sym::ptr_slice_from_raw_parts - | sym::ptr_slice_from_raw_parts_mut | sym::ptr_write | sym::ptr_write_bytes | sym::ptr_write_unaligned diff --git a/clippy_lints/src/redundant_closure_call.rs b/clippy_lints/src/redundant_closure_call.rs index 6930a01d48be4..41a44de536b15 100644 --- a/clippy_lints/src/redundant_closure_call.rs +++ b/clippy_lints/src/redundant_closure_call.rs @@ -228,7 +228,7 @@ impl<'tcx> LateLintPass<'tcx> for RedundantClosureCall { path: &'tcx hir::Path<'tcx>, count: usize, } - impl<'a, 'tcx> Visitor<'tcx> for ClosureUsageCount<'a, 'tcx> { + impl<'tcx> Visitor<'tcx> for ClosureUsageCount<'_, 'tcx> { type NestedFilter = nested_filter::OnlyBodies; fn visit_expr(&mut self, expr: &'tcx hir::Expr<'tcx>) { diff --git a/clippy_lints/src/returns.rs b/clippy_lints/src/returns.rs index 3754fdddedfd1..1f223048ce5ce 100644 --- a/clippy_lints/src/returns.rs +++ b/clippy_lints/src/returns.rs @@ -140,7 +140,7 @@ enum RetReplacement<'tcx> { Expr(Cow<'tcx, str>, Applicability), } -impl<'tcx> RetReplacement<'tcx> { +impl RetReplacement<'_> { fn sugg_help(&self) -> &'static str { match self { Self::Empty | Self::Expr(..) => "remove `return`", @@ -158,7 +158,7 @@ impl<'tcx> RetReplacement<'tcx> { } } -impl<'tcx> Display for RetReplacement<'tcx> { +impl Display for RetReplacement<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Empty => write!(f, ""), @@ -421,7 +421,7 @@ fn check_final_expr<'tcx>( if matches!(Level::from_attr(attr), Some(Level::Expect(_))) && let metas = attr.meta_item_list() && let Some(lst) = metas - && let [NestedMetaItem::MetaItem(meta_item)] = lst.as_slice() + && let [NestedMetaItem::MetaItem(meta_item), ..] = lst.as_slice() && let [tool, lint_name] = meta_item.path.segments.as_slice() && tool.ident.name == sym::clippy && matches!( diff --git a/clippy_lints/src/significant_drop_tightening.rs b/clippy_lints/src/significant_drop_tightening.rs index d1114cb29f7a3..0eece9221438a 100644 --- a/clippy_lints/src/significant_drop_tightening.rs +++ b/clippy_lints/src/significant_drop_tightening.rs @@ -249,7 +249,7 @@ impl<'ap, 'lc, 'others, 'stmt, 'tcx> StmtsChecker<'ap, 'lc, 'others, 'stmt, 'tcx } } -impl<'ap, 'lc, 'others, 'stmt, 'tcx> Visitor<'tcx> for StmtsChecker<'ap, 'lc, 'others, 'stmt, 'tcx> { +impl<'tcx> Visitor<'tcx> for StmtsChecker<'_, '_, '_, '_, 'tcx> { fn visit_block(&mut self, block: &'tcx hir::Block<'tcx>) { self.ap.curr_block_hir_id = block.hir_id; self.ap.curr_block_span = block.span; diff --git a/clippy_lints/src/single_component_path_imports.rs b/clippy_lints/src/single_component_path_imports.rs index c986c3e8aa6e8..9fdee8543a854 100644 --- a/clippy_lints/src/single_component_path_imports.rs +++ b/clippy_lints/src/single_component_path_imports.rs @@ -102,7 +102,7 @@ struct ImportUsageVisitor { imports_referenced_with_self: Vec, } -impl<'tcx> Visitor<'tcx> for ImportUsageVisitor { +impl Visitor<'_> for ImportUsageVisitor { fn visit_expr(&mut self, expr: &Expr) { if let ExprKind::Path(_, path) = &expr.kind && path.segments.len() > 1 diff --git a/clippy_lints/src/slow_vector_initialization.rs b/clippy_lints/src/slow_vector_initialization.rs index 5129bbf26655e..fc799cad67e8b 100644 --- a/clippy_lints/src/slow_vector_initialization.rs +++ b/clippy_lints/src/slow_vector_initialization.rs @@ -229,7 +229,7 @@ struct VectorInitializationVisitor<'a, 'tcx> { initialization_found: bool, } -impl<'a, 'tcx> VectorInitializationVisitor<'a, 'tcx> { +impl<'tcx> VectorInitializationVisitor<'_, 'tcx> { /// Checks if the given expression is extending a vector with `repeat(0).take(..)` fn search_slow_extend_filling(&mut self, expr: &'tcx Expr<'_>) { if self.initialization_found @@ -299,7 +299,7 @@ impl<'a, 'tcx> VectorInitializationVisitor<'a, 'tcx> { } } -impl<'a, 'tcx> Visitor<'tcx> for VectorInitializationVisitor<'a, 'tcx> { +impl<'tcx> Visitor<'tcx> for VectorInitializationVisitor<'_, 'tcx> { fn visit_stmt(&mut self, stmt: &'tcx Stmt<'_>) { if self.initialization_found { match stmt.kind { diff --git a/clippy_lints/src/swap.rs b/clippy_lints/src/swap.rs index e05fa4095b8e3..a3145c4647cab 100644 --- a/clippy_lints/src/swap.rs +++ b/clippy_lints/src/swap.rs @@ -332,7 +332,7 @@ struct IndexBinding<'a, 'tcx> { applicability: &'a mut Applicability, } -impl<'a, 'tcx> IndexBinding<'a, 'tcx> { +impl<'tcx> IndexBinding<'_, 'tcx> { fn snippet_index_bindings(&mut self, exprs: &[&'tcx Expr<'tcx>]) -> String { let mut bindings = FxHashSet::default(); for expr in exprs { diff --git a/clippy_lints/src/unnested_or_patterns.rs b/clippy_lints/src/unnested_or_patterns.rs index 104be63bb1579..c7c837de505e4 100644 --- a/clippy_lints/src/unnested_or_patterns.rs +++ b/clippy_lints/src/unnested_or_patterns.rs @@ -275,12 +275,15 @@ fn transform_with_focus_on_idx(alternatives: &mut ThinVec>, focus_idx: us |k, ps1, idx| matches!( k, TupleStruct(qself2, path2, ps2) - if eq_maybe_qself(qself1, qself2) && eq_path(path1, path2) && eq_pre_post(ps1, ps2, idx) + if eq_maybe_qself(qself1.as_ref(), qself2.as_ref()) + && eq_path(path1, path2) && eq_pre_post(ps1, ps2, idx) ), |k| always_pat!(k, TupleStruct(_, _, ps) => ps), ), // Transform a record pattern `S { fp_0, ..., fp_n }`. - Struct(qself1, path1, fps1, rest1) => extend_with_struct_pat(qself1, path1, fps1, *rest1, start, alternatives), + Struct(qself1, path1, fps1, rest1) => { + extend_with_struct_pat(qself1.as_ref(), path1, fps1, *rest1, start, alternatives) + }, }; alternatives[focus_idx].kind = focus_kind; @@ -292,7 +295,7 @@ fn transform_with_focus_on_idx(alternatives: &mut ThinVec>, focus_idx: us /// So when we fixate on some `ident_k: pat_k`, we try to find `ident_k` in the other pattern /// and check that all `fp_i` where `i ∈ ((0...n) \ k)` between two patterns are equal. fn extend_with_struct_pat( - qself1: &Option>, + qself1: Option<&P>, path1: &ast::Path, fps1: &mut [ast::PatField], rest1: ast::PatFieldsRest, @@ -307,7 +310,7 @@ fn extend_with_struct_pat( |k| { matches!(k, Struct(qself2, path2, fps2, rest2) if rest1 == *rest2 // If one struct pattern has `..` so must the other. - && eq_maybe_qself(qself1, qself2) + && eq_maybe_qself(qself1, qself2.as_ref()) && eq_path(path1, path2) && fps1.len() == fps2.len() && fps1.iter().enumerate().all(|(idx_1, fp1)| { diff --git a/clippy_lints/src/unused_async.rs b/clippy_lints/src/unused_async.rs index a1f08cf6623bc..c899b1868a6cf 100644 --- a/clippy_lints/src/unused_async.rs +++ b/clippy_lints/src/unused_async.rs @@ -2,7 +2,7 @@ use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::is_def_id_trait_method; use rustc_hir::def::DefKind; use rustc_hir::intravisit::{FnKind, Visitor, walk_expr, walk_fn}; -use rustc_hir::{Body, Expr, ExprKind, FnDecl, Node, YieldSource}; +use rustc_hir::{Body, Expr, ExprKind, FnDecl, HirId, Node, YieldSource}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::hir::nested_filter; use rustc_session::impl_lint_pass; @@ -67,7 +67,7 @@ struct AsyncFnVisitor<'a, 'tcx> { async_depth: usize, } -impl<'a, 'tcx> Visitor<'tcx> for AsyncFnVisitor<'a, 'tcx> { +impl<'tcx> Visitor<'tcx> for AsyncFnVisitor<'_, 'tcx> { type NestedFilter = nested_filter::OnlyBodies; fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) { @@ -137,17 +137,7 @@ impl<'tcx> LateLintPass<'tcx> for UnusedAsync { } } - fn check_path(&mut self, cx: &LateContext<'tcx>, path: &rustc_hir::Path<'tcx>, hir_id: rustc_hir::HirId) { - fn is_node_func_call(node: Node<'_>, expected_receiver: Span) -> bool { - matches!( - node, - Node::Expr(Expr { - kind: ExprKind::Call(Expr { span, .. }, _) | ExprKind::MethodCall(_, Expr { span, .. }, ..), - .. - }) if *span == expected_receiver - ) - } - + fn check_path(&mut self, cx: &LateContext<'tcx>, path: &rustc_hir::Path<'tcx>, hir_id: HirId) { // Find paths to local async functions that aren't immediately called. // E.g. `async fn f() {}; let x = f;` // Depending on how `x` is used, f's asyncness might be required despite not having any `await` @@ -156,7 +146,14 @@ impl<'tcx> LateLintPass<'tcx> for UnusedAsync { && let Some(local_def_id) = def_id.as_local() && cx.tcx.def_kind(def_id) == DefKind::Fn && cx.tcx.asyncness(def_id).is_async() - && !is_node_func_call(cx.tcx.parent_hir_node(hir_id), path.span) + && let parent = cx.tcx.parent_hir_node(hir_id) + && !matches!( + parent, + Node::Expr(Expr { + kind: ExprKind::Call(Expr { span, .. }, _), + .. + }) if *span == path.span + ) { self.async_fns_as_value.insert(local_def_id); } diff --git a/clippy_lints/src/unwrap.rs b/clippy_lints/src/unwrap.rs index 596f0fd9c8b02..6fe660b6a479f 100644 --- a/clippy_lints/src/unwrap.rs +++ b/clippy_lints/src/unwrap.rs @@ -235,7 +235,7 @@ impl<'tcx> Delegate<'tcx> for MutationVisitor<'tcx> { fn fake_read(&mut self, _: &PlaceWithHirId<'tcx>, _: FakeReadCause, _: HirId) {} } -impl<'a, 'tcx> UnwrappableVariablesVisitor<'a, 'tcx> { +impl<'tcx> UnwrappableVariablesVisitor<'_, 'tcx> { fn visit_branch( &mut self, if_expr: &'tcx Expr<'_>, @@ -288,7 +288,7 @@ fn consume_option_as_ref<'tcx>(expr: &'tcx Expr<'tcx>) -> (&'tcx Expr<'tcx>, Opt } } -impl<'a, 'tcx> Visitor<'tcx> for UnwrappableVariablesVisitor<'a, 'tcx> { +impl<'tcx> Visitor<'tcx> for UnwrappableVariablesVisitor<'_, 'tcx> { type NestedFilter = nested_filter::OnlyBodies; fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { diff --git a/clippy_lints/src/use_self.rs b/clippy_lints/src/use_self.rs index e340b419bd07b..08449de79b364 100644 --- a/clippy_lints/src/use_self.rs +++ b/clippy_lints/src/use_self.rs @@ -280,7 +280,7 @@ struct SkipTyCollector { types_to_skip: Vec, } -impl<'tcx> Visitor<'tcx> for SkipTyCollector { +impl Visitor<'_> for SkipTyCollector { fn visit_infer(&mut self, inf: &hir::InferArg) { self.types_to_skip.push(inf.hir_id); diff --git a/clippy_lints/src/utils/internal_lints/interning_defined_symbol.rs b/clippy_lints/src/utils/internal_lints/interning_defined_symbol.rs index 7c2e23995c1d3..9e400d2391f02 100644 --- a/clippy_lints/src/utils/internal_lints/interning_defined_symbol.rs +++ b/clippy_lints/src/utils/internal_lints/interning_defined_symbol.rs @@ -80,7 +80,7 @@ impl<'tcx> LateLintPass<'tcx> for InterningDefinedSymbol { && let ty = cx.tcx.type_of(item_def_id).instantiate_identity() && match_type(cx, ty, &paths::SYMBOL) && let Ok(ConstValue::Scalar(value)) = cx.tcx.const_eval_poly(item_def_id) - && let Ok(value) = value.to_u32() + && let Some(value) = value.to_u32().discard_err() { self.symbol_map.insert(value, item_def_id); } diff --git a/clippy_lints/src/utils/internal_lints/invalid_paths.rs b/clippy_lints/src/utils/internal_lints/invalid_paths.rs index d444ad45087cf..96397375d5edb 100644 --- a/clippy_lints/src/utils/internal_lints/invalid_paths.rs +++ b/clippy_lints/src/utils/internal_lints/invalid_paths.rs @@ -72,8 +72,7 @@ pub fn check_path(cx: &LateContext<'_>, path: &[&str]) -> bool { SimplifiedType::Bool, ] .iter() - .flat_map(|&ty| cx.tcx.incoherent_impls(ty).into_iter()) - .flatten() + .flat_map(|&ty| cx.tcx.incoherent_impls(ty).iter()) .copied(); for item_def_id in lang_items.iter().map(|(_, def_id)| def_id).chain(incoherent_impls) { let lang_item_path = cx.get_def_path(item_def_id); diff --git a/clippy_lints/src/utils/internal_lints/lint_without_lint_pass.rs b/clippy_lints/src/utils/internal_lints/lint_without_lint_pass.rs index 7c45a5b2f0971..dd45602221267 100644 --- a/clippy_lints/src/utils/internal_lints/lint_without_lint_pass.rs +++ b/clippy_lints/src/utils/internal_lints/lint_without_lint_pass.rs @@ -270,7 +270,7 @@ struct LintCollector<'a, 'tcx> { cx: &'a LateContext<'tcx>, } -impl<'a, 'tcx> Visitor<'tcx> for LintCollector<'a, 'tcx> { +impl<'tcx> Visitor<'tcx> for LintCollector<'_, 'tcx> { type NestedFilter = nested_filter::All; fn visit_path(&mut self, path: &Path<'_>, _: HirId) { diff --git a/clippy_lints/src/zombie_processes.rs b/clippy_lints/src/zombie_processes.rs index ba2a80ee66b05..8d9241cc7d9a2 100644 --- a/clippy_lints/src/zombie_processes.rs +++ b/clippy_lints/src/zombie_processes.rs @@ -6,6 +6,7 @@ use rustc_errors::Applicability; use rustc_hir::intravisit::{Visitor, walk_block, walk_expr, walk_local}; use rustc_hir::{Expr, ExprKind, HirId, LetStmt, Node, PatKind, Stmt, StmtKind}; use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::hir::nested_filter; use rustc_session::declare_lint_pass; use rustc_span::sym; use std::ops::ControlFlow; @@ -118,7 +119,8 @@ enum WaitFinder<'a, 'tcx> { Found(&'a LateContext<'tcx>, HirId), } -impl<'a, 'tcx> Visitor<'tcx> for WaitFinder<'a, 'tcx> { +impl<'tcx> Visitor<'tcx> for WaitFinder<'_, 'tcx> { + type NestedFilter = nested_filter::OnlyBodies; type Result = ControlFlow; fn visit_local(&mut self, l: &'tcx LetStmt<'tcx>) -> Self::Result { @@ -204,6 +206,11 @@ impl<'a, 'tcx> Visitor<'tcx> for WaitFinder<'a, 'tcx> { walk_expr(self, ex) } + + fn nested_visit_map(&mut self) -> Self::Map { + let (Self::Found(cx, _) | Self::WalkUpTo(cx, _)) = self; + cx.tcx.hir() + } } /// This function has shared logic between the different kinds of nodes that can trigger the lint. @@ -300,7 +307,7 @@ struct ExitPointFinder<'a, 'tcx> { struct ExitCallFound; -impl<'a, 'tcx> Visitor<'tcx> for ExitPointFinder<'a, 'tcx> { +impl<'tcx> Visitor<'tcx> for ExitPointFinder<'_, 'tcx> { type Result = ControlFlow; fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) -> Self::Result { diff --git a/clippy_utils/src/ast_utils.rs b/clippy_utils/src/ast_utils.rs index 49323492e124d..68f74e52ed7b7 100644 --- a/clippy_utils/src/ast_utils.rs +++ b/clippy_utils/src/ast_utils.rs @@ -37,20 +37,27 @@ pub fn eq_pat(l: &Pat, r: &Pat) -> bool { (_, Paren(r)) => eq_pat(l, r), (Wild, Wild) | (Rest, Rest) => true, (Lit(l), Lit(r)) => eq_expr(l, r), - (Ident(b1, i1, s1), Ident(b2, i2, s2)) => b1 == b2 && eq_id(*i1, *i2) && both(s1, s2, |l, r| eq_pat(l, r)), + (Ident(b1, i1, s1), Ident(b2, i2, s2)) => { + b1 == b2 && eq_id(*i1, *i2) && both(s1.as_deref(), s2.as_deref(), eq_pat) + }, (Range(lf, lt, le), Range(rf, rt, re)) => { - eq_expr_opt(lf, rf) && eq_expr_opt(lt, rt) && eq_range_end(&le.node, &re.node) + eq_expr_opt(lf.as_ref(), rf.as_ref()) + && eq_expr_opt(lt.as_ref(), rt.as_ref()) + && eq_range_end(&le.node, &re.node) }, (Box(l), Box(r)) | (Ref(l, Mutability::Not), Ref(r, Mutability::Not)) | (Ref(l, Mutability::Mut), Ref(r, Mutability::Mut)) => eq_pat(l, r), (Tuple(l), Tuple(r)) | (Slice(l), Slice(r)) => over(l, r, |l, r| eq_pat(l, r)), - (Path(lq, lp), Path(rq, rp)) => both(lq, rq, eq_qself) && eq_path(lp, rp), + (Path(lq, lp), Path(rq, rp)) => both(lq.as_ref(), rq.as_ref(), eq_qself) && eq_path(lp, rp), (TupleStruct(lqself, lp, lfs), TupleStruct(rqself, rp, rfs)) => { - eq_maybe_qself(lqself, rqself) && eq_path(lp, rp) && over(lfs, rfs, |l, r| eq_pat(l, r)) + eq_maybe_qself(lqself.as_ref(), rqself.as_ref()) && eq_path(lp, rp) && over(lfs, rfs, |l, r| eq_pat(l, r)) }, (Struct(lqself, lp, lfs, lr), Struct(rqself, rp, rfs, rr)) => { - lr == rr && eq_maybe_qself(lqself, rqself) && eq_path(lp, rp) && unordered_over(lfs, rfs, eq_field_pat) + lr == rr + && eq_maybe_qself(lqself.as_ref(), rqself.as_ref()) + && eq_path(lp, rp) + && unordered_over(lfs, rfs, eq_field_pat) }, (Or(ls), Or(rs)) => unordered_over(ls, rs, |l, r| eq_pat(l, r)), (MacCall(l), MacCall(r)) => eq_mac_call(l, r), @@ -79,7 +86,7 @@ pub fn eq_qself(l: &P, r: &P) -> bool { l.position == r.position && eq_ty(&l.ty, &r.ty) } -pub fn eq_maybe_qself(l: &Option>, r: &Option>) -> bool { +pub fn eq_maybe_qself(l: Option<&P>, r: Option<&P>) -> bool { match (l, r) { (Some(l), Some(r)) => eq_qself(l, r), (None, None) => true, @@ -92,7 +99,7 @@ pub fn eq_path(l: &Path, r: &Path) -> bool { } pub fn eq_path_seg(l: &PathSegment, r: &PathSegment) -> bool { - eq_id(l.ident, r.ident) && both(&l.args, &r.args, |l, r| eq_generic_args(l, r)) + eq_id(l.ident, r.ident) && both(l.args.as_ref(), r.args.as_ref(), |l, r| eq_generic_args(l, r)) } pub fn eq_generic_args(l: &GenericArgs, r: &GenericArgs) -> bool { @@ -122,7 +129,7 @@ pub fn eq_generic_arg(l: &GenericArg, r: &GenericArg) -> bool { } } -pub fn eq_expr_opt(l: &Option>, r: &Option>) -> bool { +pub fn eq_expr_opt(l: Option<&P>, r: Option<&P>) -> bool { both(l, r, |l, r| eq_expr(l, r)) } @@ -169,8 +176,12 @@ pub fn eq_expr(l: &Expr, r: &Expr) -> bool { (Lit(l), Lit(r)) => l == r, (Cast(l, lt), Cast(r, rt)) | (Type(l, lt), Type(r, rt)) => eq_expr(l, r) && eq_ty(lt, rt), (Let(lp, le, _, _), Let(rp, re, _, _)) => eq_pat(lp, rp) && eq_expr(le, re), - (If(lc, lt, le), If(rc, rt, re)) => eq_expr(lc, rc) && eq_block(lt, rt) && eq_expr_opt(le, re), - (While(lc, lt, ll), While(rc, rt, rl)) => eq_label(ll, rl) && eq_expr(lc, rc) && eq_block(lt, rt), + (If(lc, lt, le), If(rc, rt, re)) => { + eq_expr(lc, rc) && eq_block(lt, rt) && eq_expr_opt(le.as_ref(), re.as_ref()) + }, + (While(lc, lt, ll), While(rc, rt, rl)) => { + eq_label(ll.as_ref(), rl.as_ref()) && eq_expr(lc, rc) && eq_block(lt, rt) + }, ( ForLoop { pat: lp, @@ -186,13 +197,13 @@ pub fn eq_expr(l: &Expr, r: &Expr) -> bool { label: rl, kind: rk, }, - ) => eq_label(ll, rl) && eq_pat(lp, rp) && eq_expr(li, ri) && eq_block(lt, rt) && lk == rk, - (Loop(lt, ll, _), Loop(rt, rl, _)) => eq_label(ll, rl) && eq_block(lt, rt), - (Block(lb, ll), Block(rb, rl)) => eq_label(ll, rl) && eq_block(lb, rb), + ) => eq_label(ll.as_ref(), rl.as_ref()) && eq_pat(lp, rp) && eq_expr(li, ri) && eq_block(lt, rt) && lk == rk, + (Loop(lt, ll, _), Loop(rt, rl, _)) => eq_label(ll.as_ref(), rl.as_ref()) && eq_block(lt, rt), + (Block(lb, ll), Block(rb, rl)) => eq_label(ll.as_ref(), rl.as_ref()) && eq_block(lb, rb), (TryBlock(l), TryBlock(r)) => eq_block(l, r), - (Yield(l), Yield(r)) | (Ret(l), Ret(r)) => eq_expr_opt(l, r), - (Break(ll, le), Break(rl, re)) => eq_label(ll, rl) && eq_expr_opt(le, re), - (Continue(ll), Continue(rl)) => eq_label(ll, rl), + (Yield(l), Yield(r)) | (Ret(l), Ret(r)) => eq_expr_opt(l.as_ref(), r.as_ref()), + (Break(ll, le), Break(rl, re)) => eq_label(ll.as_ref(), rl.as_ref()) && eq_expr_opt(le.as_ref(), re.as_ref()), + (Continue(ll), Continue(rl)) => eq_label(ll.as_ref(), rl.as_ref()), (Assign(l1, l2, _), Assign(r1, r2, _)) | (Index(l1, l2, _), Index(r1, r2, _)) => { eq_expr(l1, r1) && eq_expr(l2, r2) }, @@ -227,12 +238,14 @@ pub fn eq_expr(l: &Expr, r: &Expr) -> bool { && eq_expr(le, re) }, (Gen(lc, lb, lk, _), Gen(rc, rb, rk, _)) => lc == rc && eq_block(lb, rb) && lk == rk, - (Range(lf, lt, ll), Range(rf, rt, rl)) => ll == rl && eq_expr_opt(lf, rf) && eq_expr_opt(lt, rt), + (Range(lf, lt, ll), Range(rf, rt, rl)) => { + ll == rl && eq_expr_opt(lf.as_ref(), rf.as_ref()) && eq_expr_opt(lt.as_ref(), rt.as_ref()) + }, (AddrOf(lbk, lm, le), AddrOf(rbk, rm, re)) => lbk == rbk && lm == rm && eq_expr(le, re), - (Path(lq, lp), Path(rq, rp)) => both(lq, rq, eq_qself) && eq_path(lp, rp), + (Path(lq, lp), Path(rq, rp)) => both(lq.as_ref(), rq.as_ref(), eq_qself) && eq_path(lp, rp), (MacCall(l), MacCall(r)) => eq_mac_call(l, r), (Struct(lse), Struct(rse)) => { - eq_maybe_qself(&lse.qself, &rse.qself) + eq_maybe_qself(lse.qself.as_ref(), rse.qself.as_ref()) && eq_path(&lse.path, &rse.path) && eq_struct_rest(&lse.rest, &rse.rest) && unordered_over(&lse.fields, &rse.fields, eq_field) @@ -264,12 +277,12 @@ pub fn eq_field(l: &ExprField, r: &ExprField) -> bool { pub fn eq_arm(l: &Arm, r: &Arm) -> bool { l.is_placeholder == r.is_placeholder && eq_pat(&l.pat, &r.pat) - && eq_expr_opt(&l.body, &r.body) - && eq_expr_opt(&l.guard, &r.guard) + && eq_expr_opt(l.body.as_ref(), r.body.as_ref()) + && eq_expr_opt(l.guard.as_ref(), r.guard.as_ref()) && over(&l.attrs, &r.attrs, eq_attr) } -pub fn eq_label(l: &Option