From 7fc38a76936950b4ce6aa10c21e544ea09688f54 Mon Sep 17 00:00:00 2001 From: AudaciousAxiom <179637270+AudaciousAxiom@users.noreply.github.com> Date: Thu, 28 Aug 2025 22:03:10 +0200 Subject: [PATCH 01/60] feat: introduce a `doc_comments_missing_terminal_punctuation` lint --- CHANGELOG.md | 1 + clippy_lints/src/declared_lints.rs | 1 + ...c_comments_missing_terminal_punctuation.rs | 211 ++++++++++++++++++ clippy_lints/src/doc/mod.rs | 25 +++ ...omments_missing_terminal_punctuation.fixed | 142 ++++++++++++ ...c_comments_missing_terminal_punctuation.rs | 142 ++++++++++++ ...mments_missing_terminal_punctuation.stderr | 83 +++++++ ..._missing_terminal_punctuation_unfixable.rs | 12 + ...sing_terminal_punctuation_unfixable.stderr | 15 ++ 9 files changed, 632 insertions(+) create mode 100644 clippy_lints/src/doc/doc_comments_missing_terminal_punctuation.rs create mode 100644 tests/ui/doc/doc_comments_missing_terminal_punctuation.fixed create mode 100644 tests/ui/doc/doc_comments_missing_terminal_punctuation.rs create mode 100644 tests/ui/doc/doc_comments_missing_terminal_punctuation.stderr create mode 100644 tests/ui/doc/doc_comments_missing_terminal_punctuation_unfixable.rs create mode 100644 tests/ui/doc/doc_comments_missing_terminal_punctuation_unfixable.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index 37d46d3496677..42d9b2ea60f29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6224,6 +6224,7 @@ Released 2018-09-13 [`diverging_sub_expression`]: https://rust-lang.github.io/rust-clippy/master/index.html#diverging_sub_expression [`doc_broken_link`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_broken_link [`doc_comment_double_space_linebreaks`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_comment_double_space_linebreaks +[`doc_comments_missing_terminal_punctuation`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_comments_missing_terminal_punctuation [`doc_include_without_cfg`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_include_without_cfg [`doc_lazy_continuation`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_lazy_continuation [`doc_link_code`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_link_code diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 375d179681da3..fbeafef2e4554 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -110,6 +110,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::disallowed_script_idents::DISALLOWED_SCRIPT_IDENTS_INFO, crate::disallowed_types::DISALLOWED_TYPES_INFO, crate::doc::DOC_BROKEN_LINK_INFO, + crate::doc::DOC_COMMENTS_MISSING_TERMINAL_PUNCTUATION_INFO, crate::doc::DOC_COMMENT_DOUBLE_SPACE_LINEBREAKS_INFO, crate::doc::DOC_INCLUDE_WITHOUT_CFG_INFO, crate::doc::DOC_LAZY_CONTINUATION_INFO, diff --git a/clippy_lints/src/doc/doc_comments_missing_terminal_punctuation.rs b/clippy_lints/src/doc/doc_comments_missing_terminal_punctuation.rs new file mode 100644 index 0000000000000..47a721ce76a17 --- /dev/null +++ b/clippy_lints/src/doc/doc_comments_missing_terminal_punctuation.rs @@ -0,0 +1,211 @@ +use rustc_ast::ast::{AttrKind, AttrStyle, Attribute}; +use rustc_errors::Applicability; +use rustc_lint::EarlyContext; + +use super::DOC_COMMENTS_MISSING_TERMINAL_PUNCTUATION; + +const MSG: &str = "doc comments should end with a terminal punctuation mark"; +const PUNCTUATION_SUGGESTION: char = '.'; + +pub fn check(cx: &EarlyContext<'_>, attrs: &[Attribute]) { + let mut doc_comment_attrs = attrs.iter().enumerate().filter(|(_, a)| is_doc_comment(a)); + + let Some((i, mut last_doc_attr)) = doc_comment_attrs.next_back() else { + return; + }; + + // Check that the next attribute is not a `#[doc]` attribute. + if let Some(next_attr) = attrs.get(i + 1) + && is_doc_attr(next_attr) + { + return; + } + + // Find the last, non-blank, non-refdef line of multiline doc comments: this is enough to check that + // the doc comment ends with proper punctuation. + while is_doc_comment_trailer(last_doc_attr) { + if let Some(doc_attr) = doc_comment_attrs.next_back() { + (_, last_doc_attr) = doc_attr; + } else { + // The doc comment looks (functionally) empty. + return; + } + } + + if let Some(doc_string) = is_missing_punctuation(last_doc_attr) { + let span = last_doc_attr.span; + + if is_line_doc_comment(last_doc_attr) { + let suggestion = generate_suggestion(last_doc_attr, doc_string); + + clippy_utils::diagnostics::span_lint_and_sugg( + cx, + DOC_COMMENTS_MISSING_TERMINAL_PUNCTUATION, + span, + MSG, + "end the doc comment with some punctuation", + suggestion, + Applicability::MaybeIncorrect, + ); + } else { + // Seems more difficult to preserve the formatting of block doc comments, so we do not provide + // suggestions for them; they are much rarer anyway. + clippy_utils::diagnostics::span_lint(cx, DOC_COMMENTS_MISSING_TERMINAL_PUNCTUATION, span, MSG); + } + } +} + +#[must_use] +fn is_missing_punctuation(attr: &Attribute) -> Option<&str> { + const TERMINAL_PUNCTUATION_MARKS: &[char] = &['.', '?', '!', '…']; + const EXCEPTIONS: &[char] = &[ + '>', // Raw HTML or (unfortunately) Markdown autolinks. + '|', // Markdown tables. + ]; + + let doc_string = get_doc_string(attr)?; + + // Doc comments could have some trailing whitespace, but that is not this lint's job. + let trimmed = doc_string.trim_end(); + + // Doc comments are also allowed to end with fenced code blocks. + if trimmed.ends_with(TERMINAL_PUNCTUATION_MARKS) || trimmed.ends_with(EXCEPTIONS) || trimmed.ends_with("```") { + return None; + } + + // Ignore single-line list items: they may not require any terminal punctuation. + if looks_like_list_item(trimmed) { + return None; + } + + if let Some(stripped) = strip_sentence_trailers(trimmed) + && stripped.ends_with(TERMINAL_PUNCTUATION_MARKS) + { + return None; + } + + Some(doc_string) +} + +#[must_use] +fn generate_suggestion(doc_attr: &Attribute, doc_string: &str) -> String { + let doc_comment_prefix = match doc_attr.style { + AttrStyle::Outer => "///", + AttrStyle::Inner => "//!", + }; + + let mut original_line = format!("{doc_comment_prefix}{doc_string}"); + + if let Some(stripped) = strip_sentence_trailers(doc_string) { + // Insert the punctuation mark just before the sentence trailer. + original_line.insert(doc_comment_prefix.len() + stripped.len(), PUNCTUATION_SUGGESTION); + } else { + original_line.push(PUNCTUATION_SUGGESTION); + } + + original_line +} + +/// Strips closing parentheses and Markdown emphasis delimiters. +#[must_use] +fn strip_sentence_trailers(string: &str) -> Option<&str> { + // The std has a few occurrences of doc comments ending with a sentence in parentheses. + const TRAILERS: &[char] = &[')', '*', '_']; + + if let Some(stripped) = string.strip_suffix("**") { + return Some(stripped); + } + + if let Some(stripped) = string.strip_suffix("__") { + return Some(stripped); + } + + // Markdown inline links should not be mistaken for sentences in parentheses. + if looks_like_inline_link(string) { + return None; + } + + string.strip_suffix(TRAILERS) +} + +/// Returns whether the doc comment looks like a Markdown reference definition or a blank line. +#[must_use] +fn is_doc_comment_trailer(attr: &Attribute) -> bool { + let Some(doc_string) = get_doc_string(attr) else { + return false; + }; + + super::looks_like_refdef(doc_string, 0..doc_string.len()).is_some() || doc_string.trim_end().is_empty() +} + +/// Returns whether the string looks like it ends with a Markdown inline link. +#[must_use] +fn looks_like_inline_link(string: &str) -> bool { + let Some(sub) = string.strip_suffix(')') else { + return false; + }; + let Some((sub, _)) = sub.rsplit_once('(') else { + return false; + }; + + // Check whether there is closing bracket just before the opening parenthesis. + sub.ends_with(']') +} + +/// Returns whether the string looks like a Markdown list item. +#[must_use] +fn looks_like_list_item(string: &str) -> bool { + const BULLET_LIST_MARKERS: &[char] = &['-', '+', '*']; + const ORDERED_LIST_MARKER_SYMBOL: &[char] = &['.', ')']; + + let trimmed = string.trim_start(); + + if let Some(sub) = trimmed.strip_prefix(BULLET_LIST_MARKERS) + && sub.starts_with(char::is_whitespace) + { + return true; + } + + let mut stripped = trimmed; + while let Some(sub) = stripped.strip_prefix(|c| char::is_digit(c, 10)) { + stripped = sub; + } + if let Some(sub) = stripped.strip_prefix(ORDERED_LIST_MARKER_SYMBOL) + && sub.starts_with(char::is_whitespace) + { + return true; + } + + false +} + +#[must_use] +fn is_doc_attr(attr: &Attribute) -> bool { + if let AttrKind::Normal(normal_attr) = &attr.kind + && let Some(segment) = &normal_attr.item.path.segments.first() + && segment.ident.name == clippy_utils::sym::doc + { + true + } else { + false + } +} + +#[must_use] +fn get_doc_string(attr: &Attribute) -> Option<&str> { + if let AttrKind::DocComment(_, symbol) = &attr.kind { + Some(symbol.as_str()) + } else { + None + } +} + +#[must_use] +fn is_doc_comment(attr: &Attribute) -> bool { + matches!(attr.kind, AttrKind::DocComment(_, _)) +} + +#[must_use] +fn is_line_doc_comment(attr: &Attribute) -> bool { + matches!(attr.kind, AttrKind::DocComment(rustc_ast::token::CommentKind::Line, _)) +} diff --git a/clippy_lints/src/doc/mod.rs b/clippy_lints/src/doc/mod.rs index 2a3fb82946117..97a0fb7143396 100644 --- a/clippy_lints/src/doc/mod.rs +++ b/clippy_lints/src/doc/mod.rs @@ -28,6 +28,7 @@ use url::Url; mod broken_link; mod doc_comment_double_space_linebreaks; +mod doc_comments_missing_terminal_punctuation; mod doc_suspicious_footnotes; mod include_in_doc_without_cfg; mod lazy_continuation; @@ -670,6 +671,28 @@ declare_clippy_lint! { "looks like a link or footnote ref, but with no definition" } +declare_clippy_lint! { + /// ### What it does + /// Checks for doc comments that do not end with a period or another punctuation mark. + /// Various Markdowns constructs are taken into account to avoid false positives. + /// + /// ### Why is this bad? + /// A project may wish to enforce consistent doc comments by making sure they end with a punctuation mark. + /// + /// ### Example + /// ```no_run + /// /// Returns the Answer to the Ultimate Question of Life, the Universe, and Everything + /// ``` + /// Use instead: + /// ```no_run + /// /// Returns the Answer to the Ultimate Question of Life, the Universe, and Everything. + /// ``` + #[clippy::version = "1.92.0"] + pub DOC_COMMENTS_MISSING_TERMINAL_PUNCTUATION, + nursery, + "missing terminal punctuation in doc comments" +} + pub struct Documentation { valid_idents: FxHashSet, check_private_items: bool, @@ -704,11 +727,13 @@ impl_lint_pass!(Documentation => [ DOC_INCLUDE_WITHOUT_CFG, DOC_COMMENT_DOUBLE_SPACE_LINEBREAKS, DOC_SUSPICIOUS_FOOTNOTES, + DOC_COMMENTS_MISSING_TERMINAL_PUNCTUATION, ]); impl EarlyLintPass for Documentation { fn check_attributes(&mut self, cx: &EarlyContext<'_>, attrs: &[rustc_ast::Attribute]) { include_in_doc_without_cfg::check(cx, attrs); + doc_comments_missing_terminal_punctuation::check(cx, attrs); } } diff --git a/tests/ui/doc/doc_comments_missing_terminal_punctuation.fixed b/tests/ui/doc/doc_comments_missing_terminal_punctuation.fixed new file mode 100644 index 0000000000000..555b16d7022bf --- /dev/null +++ b/tests/ui/doc/doc_comments_missing_terminal_punctuation.fixed @@ -0,0 +1,142 @@ +#![feature(custom_inner_attributes)] +#![rustfmt::skip] +#![warn(clippy::doc_comments_missing_terminal_punctuation)] + +/// Returns the Answer to the Ultimate Question of Life, the Universe, and Everything. +//~^ doc_comments_missing_terminal_punctuation +fn answer() -> i32 { + 42 +} + +/// The `Option` type. +//~^ doc_comments_missing_terminal_punctuation +// Triggers even in the presence of another attribute. +#[derive(Debug)] +enum MyOption { + /// No value. + //~^ doc_comments_missing_terminal_punctuation + None, + /// Some value of type `T`. + Some(T), +} + +// Triggers correctly even when interleaved with other attributes. +/// A multiline +#[derive(Debug)] +/// doc comment: +/// only the last line triggers the lint. +//~^ doc_comments_missing_terminal_punctuation +enum Exceptions { + /// Question marks are fine? + QuestionMark, + /// Exclamation marks are fine! + ExclamationMark, + /// Ellipses are ok too… + Ellipsis, + /// HTML content is however not checked: + /// Raw HTML is allowed as well + RawHtml, + /// The raw HTML exception also unfortunately results in ignoring autolinks too: + /// + MarkdownAutolink, + /// | Exception | Note | + /// | -------------- | ----- | + /// | Markdown table | A-ok | + MarkdownTable, + /// ``` + /// // Code blocks are no issues. + /// ``` + CodeBlock, +} + +// Check the lint can be expected on a whole enum at once. +#[expect(clippy::doc_comments_missing_terminal_punctuation)] +enum Char { + /// U+0000 + Null, + /// U+0001 + StartOfHeading, +} + +// Check the lint can be expected on a single variant without affecting others. +enum Char2 { + #[expect(clippy::doc_comments_missing_terminal_punctuation)] + /// U+0000 + Null, + /// U+0001. + //~^ doc_comments_missing_terminal_punctuation + StartOfHeading, +} + +mod module { + //! Works on + //! inner attributes too. + //~^ doc_comments_missing_terminal_punctuation +} + +enum Trailers { + /// (Sometimes the last sentence is in parentheses, and that's ok.) + ParensPassing, + /// (But sometimes it is missing a period.) + //~^ doc_comments_missing_terminal_punctuation + ParensFailing, + /// **Sometimes the last sentence is in bold, and that's ok.** + DoubleStarPassing, + /// **But sometimes it is missing a period.** + //~^ doc_comments_missing_terminal_punctuation + DoubleStarFailing, + /// _Sometimes the last sentence is in italics, and that's ok._ + UnderscorePassing, + /// _But sometimes it is missing a period._ + //~^ doc_comments_missing_terminal_punctuation + UnderscoreFailing, +} + +/// Doc comments can end with an [inline link](#anchor). +//~^ doc_comments_missing_terminal_punctuation +struct InlineLink; + +/// Some doc comments contain [link reference definitions][spec]. +//~^ doc_comments_missing_terminal_punctuation +/// +/// [spec]: https://spec.commonmark.org/0.31.2/#link-reference-definitions +struct LinkRefDefinition; + +// List items do not always need to end with a period. +enum UnorderedLists { + /// - A list item + Dash, + /// + A list item + Plus, + /// * A list item + Star, +} + +enum OrderedLists { + /// 1. A list item + Dot, + /// 42) A list item + Paren, +} + +/// Doc comments with trailing blank lines are supported. +//~^ doc_comments_missing_terminal_punctuation +/// +struct TrailingBlankLine; + +/// The first paragraph is not checked +/// +/// Other sentences are not either +/// Only the last sentence is. +//~^ doc_comments_missing_terminal_punctuation +struct OnlyLastSentence; + +/// Sometimes a doc attribute is used for concatenation: +/// ``` +#[doc = ""] +/// ``` +struct DocAttribute; + +#[expect(clippy::empty_docs)] +/// +struct EmptyDocComment; diff --git a/tests/ui/doc/doc_comments_missing_terminal_punctuation.rs b/tests/ui/doc/doc_comments_missing_terminal_punctuation.rs new file mode 100644 index 0000000000000..f2c6f0d610e91 --- /dev/null +++ b/tests/ui/doc/doc_comments_missing_terminal_punctuation.rs @@ -0,0 +1,142 @@ +#![feature(custom_inner_attributes)] +#![rustfmt::skip] +#![warn(clippy::doc_comments_missing_terminal_punctuation)] + +/// Returns the Answer to the Ultimate Question of Life, the Universe, and Everything +//~^ doc_comments_missing_terminal_punctuation +fn answer() -> i32 { + 42 +} + +/// The `Option` type +//~^ doc_comments_missing_terminal_punctuation +// Triggers even in the presence of another attribute. +#[derive(Debug)] +enum MyOption { + /// No value + //~^ doc_comments_missing_terminal_punctuation + None, + /// Some value of type `T`. + Some(T), +} + +// Triggers correctly even when interleaved with other attributes. +/// A multiline +#[derive(Debug)] +/// doc comment: +/// only the last line triggers the lint +//~^ doc_comments_missing_terminal_punctuation +enum Exceptions { + /// Question marks are fine? + QuestionMark, + /// Exclamation marks are fine! + ExclamationMark, + /// Ellipses are ok too… + Ellipsis, + /// HTML content is however not checked: + /// Raw HTML is allowed as well + RawHtml, + /// The raw HTML exception also unfortunately results in ignoring autolinks too: + /// + MarkdownAutolink, + /// | Exception | Note | + /// | -------------- | ----- | + /// | Markdown table | A-ok | + MarkdownTable, + /// ``` + /// // Code blocks are no issues. + /// ``` + CodeBlock, +} + +// Check the lint can be expected on a whole enum at once. +#[expect(clippy::doc_comments_missing_terminal_punctuation)] +enum Char { + /// U+0000 + Null, + /// U+0001 + StartOfHeading, +} + +// Check the lint can be expected on a single variant without affecting others. +enum Char2 { + #[expect(clippy::doc_comments_missing_terminal_punctuation)] + /// U+0000 + Null, + /// U+0001 + //~^ doc_comments_missing_terminal_punctuation + StartOfHeading, +} + +mod module { + //! Works on + //! inner attributes too + //~^ doc_comments_missing_terminal_punctuation +} + +enum Trailers { + /// (Sometimes the last sentence is in parentheses, and that's ok.) + ParensPassing, + /// (But sometimes it is missing a period) + //~^ doc_comments_missing_terminal_punctuation + ParensFailing, + /// **Sometimes the last sentence is in bold, and that's ok.** + DoubleStarPassing, + /// **But sometimes it is missing a period** + //~^ doc_comments_missing_terminal_punctuation + DoubleStarFailing, + /// _Sometimes the last sentence is in italics, and that's ok._ + UnderscorePassing, + /// _But sometimes it is missing a period_ + //~^ doc_comments_missing_terminal_punctuation + UnderscoreFailing, +} + +/// Doc comments can end with an [inline link](#anchor) +//~^ doc_comments_missing_terminal_punctuation +struct InlineLink; + +/// Some doc comments contain [link reference definitions][spec] +//~^ doc_comments_missing_terminal_punctuation +/// +/// [spec]: https://spec.commonmark.org/0.31.2/#link-reference-definitions +struct LinkRefDefinition; + +// List items do not always need to end with a period. +enum UnorderedLists { + /// - A list item + Dash, + /// + A list item + Plus, + /// * A list item + Star, +} + +enum OrderedLists { + /// 1. A list item + Dot, + /// 42) A list item + Paren, +} + +/// Doc comments with trailing blank lines are supported +//~^ doc_comments_missing_terminal_punctuation +/// +struct TrailingBlankLine; + +/// The first paragraph is not checked +/// +/// Other sentences are not either +/// Only the last sentence is +//~^ doc_comments_missing_terminal_punctuation +struct OnlyLastSentence; + +/// Sometimes a doc attribute is used for concatenation: +/// ``` +#[doc = ""] +/// ``` +struct DocAttribute; + +#[expect(clippy::empty_docs)] +/// +struct EmptyDocComment; diff --git a/tests/ui/doc/doc_comments_missing_terminal_punctuation.stderr b/tests/ui/doc/doc_comments_missing_terminal_punctuation.stderr new file mode 100644 index 0000000000000..b3fccdd186c71 --- /dev/null +++ b/tests/ui/doc/doc_comments_missing_terminal_punctuation.stderr @@ -0,0 +1,83 @@ +error: doc comments should end with a terminal punctuation mark + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:5:1 + | +LL | /// Returns the Answer to the Ultimate Question of Life, the Universe, and Everything + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: end the doc comment with some punctuation: `/// Returns the Answer to the Ultimate Question of Life, the Universe, and Everything.` + | + = note: `-D clippy::doc-comments-missing-terminal-punctuation` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::doc_comments_missing_terminal_punctuation)]` + +error: doc comments should end with a terminal punctuation mark + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:11:1 + | +LL | /// The `Option` type + | ^^^^^^^^^^^^^^^^^^^^^ help: end the doc comment with some punctuation: `/// The `Option` type.` + +error: doc comments should end with a terminal punctuation mark + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:16:5 + | +LL | /// No value + | ^^^^^^^^^^^^ help: end the doc comment with some punctuation: `/// No value.` + +error: doc comments should end with a terminal punctuation mark + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:27:1 + | +LL | /// only the last line triggers the lint + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: end the doc comment with some punctuation: `/// only the last line triggers the lint.` + +error: doc comments should end with a terminal punctuation mark + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:66:5 + | +LL | /// U+0001 + | ^^^^^^^^^^ help: end the doc comment with some punctuation: `/// U+0001.` + +error: doc comments should end with a terminal punctuation mark + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:73:5 + | +LL | //! inner attributes too + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: end the doc comment with some punctuation: `//! inner attributes too.` + +error: doc comments should end with a terminal punctuation mark + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:80:5 + | +LL | /// (But sometimes it is missing a period) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: end the doc comment with some punctuation: `/// (But sometimes it is missing a period.)` + +error: doc comments should end with a terminal punctuation mark + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:85:5 + | +LL | /// **But sometimes it is missing a period** + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: end the doc comment with some punctuation: `/// **But sometimes it is missing a period.**` + +error: doc comments should end with a terminal punctuation mark + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:90:5 + | +LL | /// _But sometimes it is missing a period_ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: end the doc comment with some punctuation: `/// _But sometimes it is missing a period._` + +error: doc comments should end with a terminal punctuation mark + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:95:1 + | +LL | /// Doc comments can end with an [inline link](#anchor) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: end the doc comment with some punctuation: `/// Doc comments can end with an [inline link](#anchor).` + +error: doc comments should end with a terminal punctuation mark + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:99:1 + | +LL | /// Some doc comments contain [link reference definitions][spec] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: end the doc comment with some punctuation: `/// Some doc comments contain [link reference definitions][spec].` + +error: doc comments should end with a terminal punctuation mark + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:122:1 + | +LL | /// Doc comments with trailing blank lines are supported + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: end the doc comment with some punctuation: `/// Doc comments with trailing blank lines are supported.` + +error: doc comments should end with a terminal punctuation mark + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:130:1 + | +LL | /// Only the last sentence is + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: end the doc comment with some punctuation: `/// Only the last sentence is.` + +error: aborting due to 13 previous errors + diff --git a/tests/ui/doc/doc_comments_missing_terminal_punctuation_unfixable.rs b/tests/ui/doc/doc_comments_missing_terminal_punctuation_unfixable.rs new file mode 100644 index 0000000000000..8db089250ccec --- /dev/null +++ b/tests/ui/doc/doc_comments_missing_terminal_punctuation_unfixable.rs @@ -0,0 +1,12 @@ +#![feature(custom_inner_attributes)] +#![rustfmt::skip] +#![warn(clippy::doc_comments_missing_terminal_punctuation)] +// Only line doc comments are provided with suggestions. +//@no-rustfix + +/** +//~^ doc_comments_missing_terminal_punctuation + * Block doc comments work + * + */ +struct BlockDocComment; diff --git a/tests/ui/doc/doc_comments_missing_terminal_punctuation_unfixable.stderr b/tests/ui/doc/doc_comments_missing_terminal_punctuation_unfixable.stderr new file mode 100644 index 0000000000000..f2b2601f30795 --- /dev/null +++ b/tests/ui/doc/doc_comments_missing_terminal_punctuation_unfixable.stderr @@ -0,0 +1,15 @@ +error: doc comments should end with a terminal punctuation mark + --> tests/ui/doc/doc_comments_missing_terminal_punctuation_unfixable.rs:7:1 + | +LL | / /** +LL | | +LL | | * Block doc comments work +LL | | * +LL | | */ + | |___^ + | + = note: `-D clippy::doc-comments-missing-terminal-punctuation` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::doc_comments_missing_terminal_punctuation)]` + +error: aborting due to 1 previous error + From abdedec18b2e4fb923077cc269d2dd4140c2c047 Mon Sep 17 00:00:00 2001 From: Michael Howell Date: Fri, 26 Sep 2025 13:16:46 -0700 Subject: [PATCH 02/60] doc_comments_missing_terminal_punctuation: real md parser --- ...c_comments_missing_terminal_punctuation.rs | 235 ++++-------------- clippy_lints/src/doc/mod.rs | 10 +- ...omments_missing_terminal_punctuation.fixed | 19 +- ...c_comments_missing_terminal_punctuation.rs | 15 +- ...mments_missing_terminal_punctuation.stderr | 66 +++-- ..._missing_terminal_punctuation_unfixable.rs | 11 +- ...sing_terminal_punctuation_unfixable.stderr | 10 +- 7 files changed, 134 insertions(+), 232 deletions(-) diff --git a/clippy_lints/src/doc/doc_comments_missing_terminal_punctuation.rs b/clippy_lints/src/doc/doc_comments_missing_terminal_punctuation.rs index 47a721ce76a17..cb8022caa1fd3 100644 --- a/clippy_lints/src/doc/doc_comments_missing_terminal_punctuation.rs +++ b/clippy_lints/src/doc/doc_comments_missing_terminal_punctuation.rs @@ -1,54 +1,29 @@ -use rustc_ast::ast::{AttrKind, AttrStyle, Attribute}; use rustc_errors::Applicability; -use rustc_lint::EarlyContext; +use rustc_lint::LateContext; +use rustc_resolve::rustdoc::main_body_opts; -use super::DOC_COMMENTS_MISSING_TERMINAL_PUNCTUATION; +use rustc_resolve::rustdoc::pulldown_cmark::{Event, Options, Parser, Tag, TagEnd}; + +use super::{DOC_COMMENTS_MISSING_TERMINAL_PUNCTUATION, Fragments}; const MSG: &str = "doc comments should end with a terminal punctuation mark"; const PUNCTUATION_SUGGESTION: char = '.'; -pub fn check(cx: &EarlyContext<'_>, attrs: &[Attribute]) { - let mut doc_comment_attrs = attrs.iter().enumerate().filter(|(_, a)| is_doc_comment(a)); - - let Some((i, mut last_doc_attr)) = doc_comment_attrs.next_back() else { - return; - }; - - // Check that the next attribute is not a `#[doc]` attribute. - if let Some(next_attr) = attrs.get(i + 1) - && is_doc_attr(next_attr) - { - return; - } - - // Find the last, non-blank, non-refdef line of multiline doc comments: this is enough to check that - // the doc comment ends with proper punctuation. - while is_doc_comment_trailer(last_doc_attr) { - if let Some(doc_attr) = doc_comment_attrs.next_back() { - (_, last_doc_attr) = doc_attr; - } else { - // The doc comment looks (functionally) empty. - return; - } - } - - if let Some(doc_string) = is_missing_punctuation(last_doc_attr) { - let span = last_doc_attr.span; - - if is_line_doc_comment(last_doc_attr) { - let suggestion = generate_suggestion(last_doc_attr, doc_string); - +pub fn check(cx: &LateContext<'_>, doc: &str, fragments: Fragments<'_>) { + if let Some(offset) = is_missing_punctuation(doc) { + if let Some(span) = fragments.span(cx, offset..offset) { clippy_utils::diagnostics::span_lint_and_sugg( cx, DOC_COMMENTS_MISSING_TERMINAL_PUNCTUATION, span, MSG, "end the doc comment with some punctuation", - suggestion, + PUNCTUATION_SUGGESTION.to_string(), Applicability::MaybeIncorrect, ); } else { - // Seems more difficult to preserve the formatting of block doc comments, so we do not provide + let span = fragments.fragments.last().unwrap().span; + // Seems more difficult to preserve the formatting of `#[doc]` attrs, so we do not provide // suggestions for them; they are much rarer anyway. clippy_utils::diagnostics::span_lint(cx, DOC_COMMENTS_MISSING_TERMINAL_PUNCTUATION, span, MSG); } @@ -56,156 +31,56 @@ pub fn check(cx: &EarlyContext<'_>, attrs: &[Attribute]) { } #[must_use] -fn is_missing_punctuation(attr: &Attribute) -> Option<&str> { +/// If punctuation is missing, returns the docstring and the offset +/// where new punctuation should be inserted. +fn is_missing_punctuation(doc_string: &str) -> Option { const TERMINAL_PUNCTUATION_MARKS: &[char] = &['.', '?', '!', '…']; - const EXCEPTIONS: &[char] = &[ - '>', // Raw HTML or (unfortunately) Markdown autolinks. - '|', // Markdown tables. - ]; - - let doc_string = get_doc_string(attr)?; - // Doc comments could have some trailing whitespace, but that is not this lint's job. - let trimmed = doc_string.trim_end(); - - // Doc comments are also allowed to end with fenced code blocks. - if trimmed.ends_with(TERMINAL_PUNCTUATION_MARKS) || trimmed.ends_with(EXCEPTIONS) || trimmed.ends_with("```") { - return None; - } - - // Ignore single-line list items: they may not require any terminal punctuation. - if looks_like_list_item(trimmed) { - return None; - } - - if let Some(stripped) = strip_sentence_trailers(trimmed) - && stripped.ends_with(TERMINAL_PUNCTUATION_MARKS) + let mut no_report_depth = 0; + let mut text_offset = None; + for (event, offset) in + Parser::new_ext(doc_string, main_body_opts() - Options::ENABLE_SMART_PUNCTUATION).into_offset_iter() { - return None; - } - - Some(doc_string) -} - -#[must_use] -fn generate_suggestion(doc_attr: &Attribute, doc_string: &str) -> String { - let doc_comment_prefix = match doc_attr.style { - AttrStyle::Outer => "///", - AttrStyle::Inner => "//!", - }; - - let mut original_line = format!("{doc_comment_prefix}{doc_string}"); - - if let Some(stripped) = strip_sentence_trailers(doc_string) { - // Insert the punctuation mark just before the sentence trailer. - original_line.insert(doc_comment_prefix.len() + stripped.len(), PUNCTUATION_SUGGESTION); - } else { - original_line.push(PUNCTUATION_SUGGESTION); - } - - original_line -} - -/// Strips closing parentheses and Markdown emphasis delimiters. -#[must_use] -fn strip_sentence_trailers(string: &str) -> Option<&str> { - // The std has a few occurrences of doc comments ending with a sentence in parentheses. - const TRAILERS: &[char] = &[')', '*', '_']; - - if let Some(stripped) = string.strip_suffix("**") { - return Some(stripped); - } - - if let Some(stripped) = string.strip_suffix("__") { - return Some(stripped); - } - - // Markdown inline links should not be mistaken for sentences in parentheses. - if looks_like_inline_link(string) { - return None; - } - - string.strip_suffix(TRAILERS) -} - -/// Returns whether the doc comment looks like a Markdown reference definition or a blank line. -#[must_use] -fn is_doc_comment_trailer(attr: &Attribute) -> bool { - let Some(doc_string) = get_doc_string(attr) else { - return false; - }; - - super::looks_like_refdef(doc_string, 0..doc_string.len()).is_some() || doc_string.trim_end().is_empty() -} - -/// Returns whether the string looks like it ends with a Markdown inline link. -#[must_use] -fn looks_like_inline_link(string: &str) -> bool { - let Some(sub) = string.strip_suffix(')') else { - return false; - }; - let Some((sub, _)) = sub.rsplit_once('(') else { - return false; - }; - - // Check whether there is closing bracket just before the opening parenthesis. - sub.ends_with(']') -} - -/// Returns whether the string looks like a Markdown list item. -#[must_use] -fn looks_like_list_item(string: &str) -> bool { - const BULLET_LIST_MARKERS: &[char] = &['-', '+', '*']; - const ORDERED_LIST_MARKER_SYMBOL: &[char] = &['.', ')']; - - let trimmed = string.trim_start(); - - if let Some(sub) = trimmed.strip_prefix(BULLET_LIST_MARKERS) - && sub.starts_with(char::is_whitespace) - { - return true; - } - - let mut stripped = trimmed; - while let Some(sub) = stripped.strip_prefix(|c| char::is_digit(c, 10)) { - stripped = sub; - } - if let Some(sub) = stripped.strip_prefix(ORDERED_LIST_MARKER_SYMBOL) - && sub.starts_with(char::is_whitespace) - { - return true; + match event { + Event::Start( + Tag::CodeBlock(..) + | Tag::FootnoteDefinition(_) + | Tag::Heading { .. } + | Tag::HtmlBlock + | Tag::List(..) + | Tag::Table(_), + ) => { + no_report_depth += 1; + }, + Event::End( + TagEnd::CodeBlock + | TagEnd::FootnoteDefinition + | TagEnd::Heading(_) + | TagEnd::HtmlBlock + | TagEnd::List(_) + | TagEnd::Table, + ) => { + no_report_depth -= 1; + }, + Event::InlineHtml(_) | Event::Start(Tag::Image { .. }) | Event::End(TagEnd::Image) => { + text_offset = None; + }, + Event::Text(..) | Event::Start(Tag::Link { .. }) | Event::End(TagEnd::Link) + if no_report_depth == 0 && !offset.is_empty() => + { + text_offset = Some(offset); + }, + _ => {}, + } } - false -} - -#[must_use] -fn is_doc_attr(attr: &Attribute) -> bool { - if let AttrKind::Normal(normal_attr) = &attr.kind - && let Some(segment) = &normal_attr.item.path.segments.first() - && segment.ident.name == clippy_utils::sym::doc + let text_offset = text_offset?; + if doc_string[..text_offset.end] + .trim_end_matches(|c: char| c.is_whitespace() || c == ')' || c == ']' || c == '}') + .ends_with(TERMINAL_PUNCTUATION_MARKS) { - true - } else { - false - } -} - -#[must_use] -fn get_doc_string(attr: &Attribute) -> Option<&str> { - if let AttrKind::DocComment(_, symbol) = &attr.kind { - Some(symbol.as_str()) - } else { None + } else { + Some(text_offset.end) } } - -#[must_use] -fn is_doc_comment(attr: &Attribute) -> bool { - matches!(attr.kind, AttrKind::DocComment(_, _)) -} - -#[must_use] -fn is_line_doc_comment(attr: &Attribute) -> bool { - matches!(attr.kind, AttrKind::DocComment(rustc_ast::token::CommentKind::Line, _)) -} diff --git a/clippy_lints/src/doc/mod.rs b/clippy_lints/src/doc/mod.rs index 97a0fb7143396..a0911fccd5914 100644 --- a/clippy_lints/src/doc/mod.rs +++ b/clippy_lints/src/doc/mod.rs @@ -733,7 +733,6 @@ impl_lint_pass!(Documentation => [ impl EarlyLintPass for Documentation { fn check_attributes(&mut self, cx: &EarlyContext<'_>, attrs: &[rustc_ast::Attribute]) { include_in_doc_without_cfg::check(cx, attrs); - doc_comments_missing_terminal_punctuation::check(cx, attrs); } } @@ -900,6 +899,15 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet, attrs: &[ }, ); + doc_comments_missing_terminal_punctuation::check( + cx, + &doc, + Fragments { + doc: &doc, + fragments: &fragments, + }, + ); + // NOTE: check_doc uses it own cb function, // to avoid causing duplicated diagnostics for the broken link checker. let mut full_fake_broken_link_callback = |bl: BrokenLink<'_>| -> Option<(CowStr<'_>, CowStr<'_>)> { diff --git a/tests/ui/doc/doc_comments_missing_terminal_punctuation.fixed b/tests/ui/doc/doc_comments_missing_terminal_punctuation.fixed index 555b16d7022bf..6a5a30954585e 100644 --- a/tests/ui/doc/doc_comments_missing_terminal_punctuation.fixed +++ b/tests/ui/doc/doc_comments_missing_terminal_punctuation.fixed @@ -36,8 +36,9 @@ enum Exceptions { /// HTML content is however not checked: /// Raw HTML is allowed as well RawHtml, - /// The raw HTML exception also unfortunately results in ignoring autolinks too: - /// + /// The raw HTML exception actually does the right thing to autolinks: + /// . + //~^ doc_comments_missing_terminal_punctuation MarkdownAutolink, /// | Exception | Note | /// | -------------- | ----- | @@ -77,7 +78,7 @@ mod module { enum Trailers { /// (Sometimes the last sentence is in parentheses, and that's ok.) ParensPassing, - /// (But sometimes it is missing a period.) + /// (But sometimes it is missing a period). //~^ doc_comments_missing_terminal_punctuation ParensFailing, /// **Sometimes the last sentence is in bold, and that's ok.** @@ -131,12 +132,16 @@ struct TrailingBlankLine; //~^ doc_comments_missing_terminal_punctuation struct OnlyLastSentence; -/// Sometimes a doc attribute is used for concatenation: -/// ``` -#[doc = ""] /// ``` -struct DocAttribute; +struct IncompleteBlockCode; #[expect(clippy::empty_docs)] /// struct EmptyDocComment; + +/** + * Block doc comments work. + * + */ +//~^^^ doc_comments_missing_terminal_punctuation +struct BlockDocComment; diff --git a/tests/ui/doc/doc_comments_missing_terminal_punctuation.rs b/tests/ui/doc/doc_comments_missing_terminal_punctuation.rs index f2c6f0d610e91..09d54b134e9d4 100644 --- a/tests/ui/doc/doc_comments_missing_terminal_punctuation.rs +++ b/tests/ui/doc/doc_comments_missing_terminal_punctuation.rs @@ -36,8 +36,9 @@ enum Exceptions { /// HTML content is however not checked: /// Raw HTML is allowed as well RawHtml, - /// The raw HTML exception also unfortunately results in ignoring autolinks too: + /// The raw HTML exception actually does the right thing to autolinks: /// + //~^ doc_comments_missing_terminal_punctuation MarkdownAutolink, /// | Exception | Note | /// | -------------- | ----- | @@ -131,12 +132,16 @@ struct TrailingBlankLine; //~^ doc_comments_missing_terminal_punctuation struct OnlyLastSentence; -/// Sometimes a doc attribute is used for concatenation: -/// ``` -#[doc = ""] /// ``` -struct DocAttribute; +struct IncompleteBlockCode; #[expect(clippy::empty_docs)] /// struct EmptyDocComment; + +/** + * Block doc comments work + * + */ +//~^^^ doc_comments_missing_terminal_punctuation +struct BlockDocComment; diff --git a/tests/ui/doc/doc_comments_missing_terminal_punctuation.stderr b/tests/ui/doc/doc_comments_missing_terminal_punctuation.stderr index b3fccdd186c71..e4f3d30cf89db 100644 --- a/tests/ui/doc/doc_comments_missing_terminal_punctuation.stderr +++ b/tests/ui/doc/doc_comments_missing_terminal_punctuation.stderr @@ -1,83 +1,95 @@ error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:5:1 + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:5:86 | LL | /// Returns the Answer to the Ultimate Question of Life, the Universe, and Everything - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: end the doc comment with some punctuation: `/// Returns the Answer to the Ultimate Question of Life, the Universe, and Everything.` + | ^ help: end the doc comment with some punctuation: `.` | = note: `-D clippy::doc-comments-missing-terminal-punctuation` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::doc_comments_missing_terminal_punctuation)]` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:11:1 + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:11:22 | LL | /// The `Option` type - | ^^^^^^^^^^^^^^^^^^^^^ help: end the doc comment with some punctuation: `/// The `Option` type.` + | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:16:5 + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:16:17 | LL | /// No value - | ^^^^^^^^^^^^ help: end the doc comment with some punctuation: `/// No value.` + | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:27:1 + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:27:41 | LL | /// only the last line triggers the lint - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: end the doc comment with some punctuation: `/// only the last line triggers the lint.` + | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:66:5 + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:40:56 + | +LL | /// + | ^ help: end the doc comment with some punctuation: `.` + +error: doc comments should end with a terminal punctuation mark + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:67:15 | LL | /// U+0001 - | ^^^^^^^^^^ help: end the doc comment with some punctuation: `/// U+0001.` + | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:73:5 + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:74:29 | LL | //! inner attributes too - | ^^^^^^^^^^^^^^^^^^^^^^^^ help: end the doc comment with some punctuation: `//! inner attributes too.` + | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:80:5 + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:81:47 | LL | /// (But sometimes it is missing a period) - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: end the doc comment with some punctuation: `/// (But sometimes it is missing a period.)` + | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:85:5 + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:86:47 | LL | /// **But sometimes it is missing a period** - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: end the doc comment with some punctuation: `/// **But sometimes it is missing a period.**` + | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:90:5 + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:91:46 | LL | /// _But sometimes it is missing a period_ - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: end the doc comment with some punctuation: `/// _But sometimes it is missing a period._` + | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:95:1 + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:96:56 | LL | /// Doc comments can end with an [inline link](#anchor) - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: end the doc comment with some punctuation: `/// Doc comments can end with an [inline link](#anchor).` + | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:99:1 + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:100:65 | LL | /// Some doc comments contain [link reference definitions][spec] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: end the doc comment with some punctuation: `/// Some doc comments contain [link reference definitions][spec].` + | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:122:1 + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:123:57 | LL | /// Doc comments with trailing blank lines are supported - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: end the doc comment with some punctuation: `/// Doc comments with trailing blank lines are supported.` + | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:130:1 + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:131:30 | LL | /// Only the last sentence is - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: end the doc comment with some punctuation: `/// Only the last sentence is.` + | ^ help: end the doc comment with some punctuation: `.` + +error: doc comments should end with a terminal punctuation mark + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:143:27 + | +LL | * Block doc comments work + | ^ help: end the doc comment with some punctuation: `.` -error: aborting due to 13 previous errors +error: aborting due to 15 previous errors diff --git a/tests/ui/doc/doc_comments_missing_terminal_punctuation_unfixable.rs b/tests/ui/doc/doc_comments_missing_terminal_punctuation_unfixable.rs index 8db089250ccec..44d3dd693a69e 100644 --- a/tests/ui/doc/doc_comments_missing_terminal_punctuation_unfixable.rs +++ b/tests/ui/doc/doc_comments_missing_terminal_punctuation_unfixable.rs @@ -4,9 +4,10 @@ // Only line doc comments are provided with suggestions. //@no-rustfix -/** +/// Sometimes a doc attribute is used for concatenation +/// ``` +#[doc = ""] +/// ``` //~^ doc_comments_missing_terminal_punctuation - * Block doc comments work - * - */ -struct BlockDocComment; +struct DocAttribute; + diff --git a/tests/ui/doc/doc_comments_missing_terminal_punctuation_unfixable.stderr b/tests/ui/doc/doc_comments_missing_terminal_punctuation_unfixable.stderr index f2b2601f30795..0732934542968 100644 --- a/tests/ui/doc/doc_comments_missing_terminal_punctuation_unfixable.stderr +++ b/tests/ui/doc/doc_comments_missing_terminal_punctuation_unfixable.stderr @@ -1,12 +1,8 @@ error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation_unfixable.rs:7:1 + --> tests/ui/doc/doc_comments_missing_terminal_punctuation_unfixable.rs:10:1 | -LL | / /** -LL | | -LL | | * Block doc comments work -LL | | * -LL | | */ - | |___^ +LL | /// ``` + | ^^^^^^^ | = note: `-D clippy::doc-comments-missing-terminal-punctuation` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::doc_comments_missing_terminal_punctuation)]` From 7678f3b65aaccd1818c0320fc14642a46186cecd Mon Sep 17 00:00:00 2001 From: AudaciousAxiom <179637270+AudaciousAxiom@users.noreply.github.com> Date: Sat, 27 Sep 2025 12:30:26 +0200 Subject: [PATCH 03/60] doc_comments_missing_terminal_punctuation: exclude doc attributes to avoid FP --- ...c_comments_missing_terminal_punctuation.rs | 30 ++++++++----------- ...omments_missing_terminal_punctuation.fixed | 6 ++++ ...c_comments_missing_terminal_punctuation.rs | 6 ++++ ..._missing_terminal_punctuation_unfixable.rs | 13 -------- ...sing_terminal_punctuation_unfixable.stderr | 11 ------- 5 files changed, 25 insertions(+), 41 deletions(-) delete mode 100644 tests/ui/doc/doc_comments_missing_terminal_punctuation_unfixable.rs delete mode 100644 tests/ui/doc/doc_comments_missing_terminal_punctuation_unfixable.stderr diff --git a/clippy_lints/src/doc/doc_comments_missing_terminal_punctuation.rs b/clippy_lints/src/doc/doc_comments_missing_terminal_punctuation.rs index cb8022caa1fd3..ec628c2bbab4c 100644 --- a/clippy_lints/src/doc/doc_comments_missing_terminal_punctuation.rs +++ b/clippy_lints/src/doc/doc_comments_missing_terminal_punctuation.rs @@ -10,23 +10,19 @@ const MSG: &str = "doc comments should end with a terminal punctuation mark"; const PUNCTUATION_SUGGESTION: char = '.'; pub fn check(cx: &LateContext<'_>, doc: &str, fragments: Fragments<'_>) { - if let Some(offset) = is_missing_punctuation(doc) { - if let Some(span) = fragments.span(cx, offset..offset) { - clippy_utils::diagnostics::span_lint_and_sugg( - cx, - DOC_COMMENTS_MISSING_TERMINAL_PUNCTUATION, - span, - MSG, - "end the doc comment with some punctuation", - PUNCTUATION_SUGGESTION.to_string(), - Applicability::MaybeIncorrect, - ); - } else { - let span = fragments.fragments.last().unwrap().span; - // Seems more difficult to preserve the formatting of `#[doc]` attrs, so we do not provide - // suggestions for them; they are much rarer anyway. - clippy_utils::diagnostics::span_lint(cx, DOC_COMMENTS_MISSING_TERMINAL_PUNCTUATION, span, MSG); - } + // This ignores `#[doc]` attributes, which we do not handle. + if let Some(offset) = is_missing_punctuation(doc) + && let Some(span) = fragments.span(cx, offset..offset) + { + clippy_utils::diagnostics::span_lint_and_sugg( + cx, + DOC_COMMENTS_MISSING_TERMINAL_PUNCTUATION, + span, + MSG, + "end the doc comment with some punctuation", + PUNCTUATION_SUGGESTION.to_string(), + Applicability::MaybeIncorrect, + ); } } diff --git a/tests/ui/doc/doc_comments_missing_terminal_punctuation.fixed b/tests/ui/doc/doc_comments_missing_terminal_punctuation.fixed index 6a5a30954585e..ef2f5867418b0 100644 --- a/tests/ui/doc/doc_comments_missing_terminal_punctuation.fixed +++ b/tests/ui/doc/doc_comments_missing_terminal_punctuation.fixed @@ -145,3 +145,9 @@ struct EmptyDocComment; */ //~^^^ doc_comments_missing_terminal_punctuation struct BlockDocComment; + +/// Sometimes a doc attribute is used for concatenation +/// ``` +#[doc = ""] +/// ``` +struct DocAttribute; diff --git a/tests/ui/doc/doc_comments_missing_terminal_punctuation.rs b/tests/ui/doc/doc_comments_missing_terminal_punctuation.rs index 09d54b134e9d4..40a989c159c45 100644 --- a/tests/ui/doc/doc_comments_missing_terminal_punctuation.rs +++ b/tests/ui/doc/doc_comments_missing_terminal_punctuation.rs @@ -145,3 +145,9 @@ struct EmptyDocComment; */ //~^^^ doc_comments_missing_terminal_punctuation struct BlockDocComment; + +/// Sometimes a doc attribute is used for concatenation +/// ``` +#[doc = ""] +/// ``` +struct DocAttribute; diff --git a/tests/ui/doc/doc_comments_missing_terminal_punctuation_unfixable.rs b/tests/ui/doc/doc_comments_missing_terminal_punctuation_unfixable.rs deleted file mode 100644 index 44d3dd693a69e..0000000000000 --- a/tests/ui/doc/doc_comments_missing_terminal_punctuation_unfixable.rs +++ /dev/null @@ -1,13 +0,0 @@ -#![feature(custom_inner_attributes)] -#![rustfmt::skip] -#![warn(clippy::doc_comments_missing_terminal_punctuation)] -// Only line doc comments are provided with suggestions. -//@no-rustfix - -/// Sometimes a doc attribute is used for concatenation -/// ``` -#[doc = ""] -/// ``` -//~^ doc_comments_missing_terminal_punctuation -struct DocAttribute; - diff --git a/tests/ui/doc/doc_comments_missing_terminal_punctuation_unfixable.stderr b/tests/ui/doc/doc_comments_missing_terminal_punctuation_unfixable.stderr deleted file mode 100644 index 0732934542968..0000000000000 --- a/tests/ui/doc/doc_comments_missing_terminal_punctuation_unfixable.stderr +++ /dev/null @@ -1,11 +0,0 @@ -error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation_unfixable.rs:10:1 - | -LL | /// ``` - | ^^^^^^^ - | - = note: `-D clippy::doc-comments-missing-terminal-punctuation` implied by `-D warnings` - = help: to override `-D warnings` add `#[allow(clippy::doc_comments_missing_terminal_punctuation)]` - -error: aborting due to 1 previous error - From e34a90b26e23e2e50d11e6f4b29302427d172587 Mon Sep 17 00:00:00 2001 From: AudaciousAxiom <179637270+AudaciousAxiom@users.noreply.github.com> Date: Sat, 27 Sep 2025 12:11:51 +0200 Subject: [PATCH 04/60] doc_comments_missing_terminal_punctuation: exclude tables and code blocks --- ...c_comments_missing_terminal_punctuation.rs | 11 +++++----- ...omments_missing_terminal_punctuation.fixed | 6 ++++++ ...c_comments_missing_terminal_punctuation.rs | 6 ++++++ ...mments_missing_terminal_punctuation.stderr | 20 +++++++++---------- 4 files changed, 27 insertions(+), 16 deletions(-) diff --git a/clippy_lints/src/doc/doc_comments_missing_terminal_punctuation.rs b/clippy_lints/src/doc/doc_comments_missing_terminal_punctuation.rs index ec628c2bbab4c..d56f7500cb691 100644 --- a/clippy_lints/src/doc/doc_comments_missing_terminal_punctuation.rs +++ b/clippy_lints/src/doc/doc_comments_missing_terminal_punctuation.rs @@ -48,15 +48,14 @@ fn is_missing_punctuation(doc_string: &str) -> Option { ) => { no_report_depth += 1; }, + Event::End(TagEnd::FootnoteDefinition) => { + no_report_depth -= 1; + }, Event::End( - TagEnd::CodeBlock - | TagEnd::FootnoteDefinition - | TagEnd::Heading(_) - | TagEnd::HtmlBlock - | TagEnd::List(_) - | TagEnd::Table, + TagEnd::CodeBlock | TagEnd::Heading(_) | TagEnd::HtmlBlock | TagEnd::List(_) | TagEnd::Table, ) => { no_report_depth -= 1; + text_offset = None; }, Event::InlineHtml(_) | Event::Start(Tag::Image { .. }) | Event::End(TagEnd::Image) => { text_offset = None; diff --git a/tests/ui/doc/doc_comments_missing_terminal_punctuation.fixed b/tests/ui/doc/doc_comments_missing_terminal_punctuation.fixed index ef2f5867418b0..8ab134614ba79 100644 --- a/tests/ui/doc/doc_comments_missing_terminal_punctuation.fixed +++ b/tests/ui/doc/doc_comments_missing_terminal_punctuation.fixed @@ -40,10 +40,14 @@ enum Exceptions { /// . //~^ doc_comments_missing_terminal_punctuation MarkdownAutolink, + /// This table introduction ends with a colon: + /// /// | Exception | Note | /// | -------------- | ----- | /// | Markdown table | A-ok | MarkdownTable, + /// Here is a snippet + /// /// ``` /// // Code blocks are no issues. /// ``` @@ -105,6 +109,8 @@ struct LinkRefDefinition; // List items do not always need to end with a period. enum UnorderedLists { + /// This list has an introductory sentence: + /// /// - A list item Dash, /// + A list item diff --git a/tests/ui/doc/doc_comments_missing_terminal_punctuation.rs b/tests/ui/doc/doc_comments_missing_terminal_punctuation.rs index 40a989c159c45..fa238ce8920c2 100644 --- a/tests/ui/doc/doc_comments_missing_terminal_punctuation.rs +++ b/tests/ui/doc/doc_comments_missing_terminal_punctuation.rs @@ -40,10 +40,14 @@ enum Exceptions { /// //~^ doc_comments_missing_terminal_punctuation MarkdownAutolink, + /// This table introduction ends with a colon: + /// /// | Exception | Note | /// | -------------- | ----- | /// | Markdown table | A-ok | MarkdownTable, + /// Here is a snippet + /// /// ``` /// // Code blocks are no issues. /// ``` @@ -105,6 +109,8 @@ struct LinkRefDefinition; // List items do not always need to end with a period. enum UnorderedLists { + /// This list has an introductory sentence: + /// /// - A list item Dash, /// + A list item diff --git a/tests/ui/doc/doc_comments_missing_terminal_punctuation.stderr b/tests/ui/doc/doc_comments_missing_terminal_punctuation.stderr index e4f3d30cf89db..ca4cdf66f24b0 100644 --- a/tests/ui/doc/doc_comments_missing_terminal_punctuation.stderr +++ b/tests/ui/doc/doc_comments_missing_terminal_punctuation.stderr @@ -32,61 +32,61 @@ LL | /// | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:67:15 + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:71:15 | LL | /// U+0001 | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:74:29 + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:78:29 | LL | //! inner attributes too | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:81:47 + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:85:47 | LL | /// (But sometimes it is missing a period) | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:86:47 + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:90:47 | LL | /// **But sometimes it is missing a period** | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:91:46 + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:95:46 | LL | /// _But sometimes it is missing a period_ | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:96:56 + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:100:56 | LL | /// Doc comments can end with an [inline link](#anchor) | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:100:65 + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:104:65 | LL | /// Some doc comments contain [link reference definitions][spec] | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:123:57 + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:129:57 | LL | /// Doc comments with trailing blank lines are supported | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:131:30 + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:137:30 | LL | /// Only the last sentence is | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:143:27 + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:149:27 | LL | * Block doc comments work | ^ help: end the doc comment with some punctuation: `.` From 36df5d67219460f19b832238f228080c4ee1e82e Mon Sep 17 00:00:00 2001 From: AudaciousAxiom <179637270+AudaciousAxiom@users.noreply.github.com> Date: Sat, 27 Sep 2025 14:33:04 +0200 Subject: [PATCH 05/60] doc_comments_missing_terminal_punctuation: handle code spans properly --- .../doc/doc_comments_missing_terminal_punctuation.rs | 2 +- .../doc_comments_missing_terminal_punctuation.fixed | 4 ++++ .../doc/doc_comments_missing_terminal_punctuation.rs | 4 ++++ .../doc_comments_missing_terminal_punctuation.stderr | 10 ++++++++-- 4 files changed, 17 insertions(+), 3 deletions(-) diff --git a/clippy_lints/src/doc/doc_comments_missing_terminal_punctuation.rs b/clippy_lints/src/doc/doc_comments_missing_terminal_punctuation.rs index d56f7500cb691..705bc3fc95d64 100644 --- a/clippy_lints/src/doc/doc_comments_missing_terminal_punctuation.rs +++ b/clippy_lints/src/doc/doc_comments_missing_terminal_punctuation.rs @@ -60,7 +60,7 @@ fn is_missing_punctuation(doc_string: &str) -> Option { Event::InlineHtml(_) | Event::Start(Tag::Image { .. }) | Event::End(TagEnd::Image) => { text_offset = None; }, - Event::Text(..) | Event::Start(Tag::Link { .. }) | Event::End(TagEnd::Link) + Event::Code(..) | Event::Text(..) | Event::Start(Tag::Link { .. }) | Event::End(TagEnd::Link) if no_report_depth == 0 && !offset.is_empty() => { text_offset = Some(offset); diff --git a/tests/ui/doc/doc_comments_missing_terminal_punctuation.fixed b/tests/ui/doc/doc_comments_missing_terminal_punctuation.fixed index 8ab134614ba79..d925e895cb994 100644 --- a/tests/ui/doc/doc_comments_missing_terminal_punctuation.fixed +++ b/tests/ui/doc/doc_comments_missing_terminal_punctuation.fixed @@ -141,6 +141,10 @@ struct OnlyLastSentence; /// ``` struct IncompleteBlockCode; +/// This ends with a code `span`. +//~^ doc_comments_missing_terminal_punctuation +struct CodeSpan; + #[expect(clippy::empty_docs)] /// struct EmptyDocComment; diff --git a/tests/ui/doc/doc_comments_missing_terminal_punctuation.rs b/tests/ui/doc/doc_comments_missing_terminal_punctuation.rs index fa238ce8920c2..2d1c39e48a3d2 100644 --- a/tests/ui/doc/doc_comments_missing_terminal_punctuation.rs +++ b/tests/ui/doc/doc_comments_missing_terminal_punctuation.rs @@ -141,6 +141,10 @@ struct OnlyLastSentence; /// ``` struct IncompleteBlockCode; +/// This ends with a code `span` +//~^ doc_comments_missing_terminal_punctuation +struct CodeSpan; + #[expect(clippy::empty_docs)] /// struct EmptyDocComment; diff --git a/tests/ui/doc/doc_comments_missing_terminal_punctuation.stderr b/tests/ui/doc/doc_comments_missing_terminal_punctuation.stderr index ca4cdf66f24b0..d1782ce8d33a1 100644 --- a/tests/ui/doc/doc_comments_missing_terminal_punctuation.stderr +++ b/tests/ui/doc/doc_comments_missing_terminal_punctuation.stderr @@ -86,10 +86,16 @@ LL | /// Only the last sentence is | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:149:27 + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:144:33 + | +LL | /// This ends with a code `span` + | ^ help: end the doc comment with some punctuation: `.` + +error: doc comments should end with a terminal punctuation mark + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:153:27 | LL | * Block doc comments work | ^ help: end the doc comment with some punctuation: `.` -error: aborting due to 15 previous errors +error: aborting due to 16 previous errors From d35076edcb9243c8f895c5dc3a6026bfbce58f8a Mon Sep 17 00:00:00 2001 From: AudaciousAxiom <179637270+AudaciousAxiom@users.noreply.github.com> Date: Tue, 30 Sep 2025 18:12:41 +0200 Subject: [PATCH 06/60] doc_comments_missing_terminal_punctuation: remove the parens special treatment --- ...c_comments_missing_terminal_punctuation.rs | 11 ++++--- ...omments_missing_terminal_punctuation.fixed | 7 +++-- ...c_comments_missing_terminal_punctuation.rs | 7 +++-- ...mments_missing_terminal_punctuation.stderr | 30 +++++++++++-------- 4 files changed, 31 insertions(+), 24 deletions(-) diff --git a/clippy_lints/src/doc/doc_comments_missing_terminal_punctuation.rs b/clippy_lints/src/doc/doc_comments_missing_terminal_punctuation.rs index 705bc3fc95d64..d26da1c1e5c08 100644 --- a/clippy_lints/src/doc/doc_comments_missing_terminal_punctuation.rs +++ b/clippy_lints/src/doc/doc_comments_missing_terminal_punctuation.rs @@ -27,8 +27,7 @@ pub fn check(cx: &LateContext<'_>, doc: &str, fragments: Fragments<'_>) { } #[must_use] -/// If punctuation is missing, returns the docstring and the offset -/// where new punctuation should be inserted. +/// If punctuation is missing, returns the offset where new punctuation should be inserted. fn is_missing_punctuation(doc_string: &str) -> Option { const TERMINAL_PUNCTUATION_MARKS: &[char] = &['.', '?', '!', '…']; @@ -63,19 +62,19 @@ fn is_missing_punctuation(doc_string: &str) -> Option { Event::Code(..) | Event::Text(..) | Event::Start(Tag::Link { .. }) | Event::End(TagEnd::Link) if no_report_depth == 0 && !offset.is_empty() => { - text_offset = Some(offset); + text_offset = Some(offset.end); }, _ => {}, } } let text_offset = text_offset?; - if doc_string[..text_offset.end] - .trim_end_matches(|c: char| c.is_whitespace() || c == ')' || c == ']' || c == '}') + if doc_string[..text_offset] + .trim_end() .ends_with(TERMINAL_PUNCTUATION_MARKS) { None } else { - Some(text_offset.end) + Some(text_offset) } } diff --git a/tests/ui/doc/doc_comments_missing_terminal_punctuation.fixed b/tests/ui/doc/doc_comments_missing_terminal_punctuation.fixed index d925e895cb994..b17914250fa66 100644 --- a/tests/ui/doc/doc_comments_missing_terminal_punctuation.fixed +++ b/tests/ui/doc/doc_comments_missing_terminal_punctuation.fixed @@ -80,9 +80,10 @@ mod module { } enum Trailers { - /// (Sometimes the last sentence is in parentheses, and that's ok.) - ParensPassing, - /// (But sometimes it is missing a period). + /// Sometimes the doc comment ends with parentheses (like this). + //~^ doc_comments_missing_terminal_punctuation + EndsWithParens, + /// (Sometimes the last sentence is in parentheses, but there is no special treatment of this.). //~^ doc_comments_missing_terminal_punctuation ParensFailing, /// **Sometimes the last sentence is in bold, and that's ok.** diff --git a/tests/ui/doc/doc_comments_missing_terminal_punctuation.rs b/tests/ui/doc/doc_comments_missing_terminal_punctuation.rs index 2d1c39e48a3d2..8c599b1916562 100644 --- a/tests/ui/doc/doc_comments_missing_terminal_punctuation.rs +++ b/tests/ui/doc/doc_comments_missing_terminal_punctuation.rs @@ -80,9 +80,10 @@ mod module { } enum Trailers { - /// (Sometimes the last sentence is in parentheses, and that's ok.) - ParensPassing, - /// (But sometimes it is missing a period) + /// Sometimes the doc comment ends with parentheses (like this) + //~^ doc_comments_missing_terminal_punctuation + EndsWithParens, + /// (Sometimes the last sentence is in parentheses, but there is no special treatment of this.) //~^ doc_comments_missing_terminal_punctuation ParensFailing, /// **Sometimes the last sentence is in bold, and that's ok.** diff --git a/tests/ui/doc/doc_comments_missing_terminal_punctuation.stderr b/tests/ui/doc/doc_comments_missing_terminal_punctuation.stderr index d1782ce8d33a1..4b4105155e878 100644 --- a/tests/ui/doc/doc_comments_missing_terminal_punctuation.stderr +++ b/tests/ui/doc/doc_comments_missing_terminal_punctuation.stderr @@ -44,58 +44,64 @@ LL | //! inner attributes too | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:85:47 + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:83:68 | -LL | /// (But sometimes it is missing a period) - | ^ help: end the doc comment with some punctuation: `.` +LL | /// Sometimes the doc comment ends with parentheses (like this) + | ^ help: end the doc comment with some punctuation: `.` + +error: doc comments should end with a terminal punctuation mark + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:86:100 + | +LL | /// (Sometimes the last sentence is in parentheses, but there is no special treatment of this.) + | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:90:47 + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:91:47 | LL | /// **But sometimes it is missing a period** | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:95:46 + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:96:46 | LL | /// _But sometimes it is missing a period_ | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:100:56 + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:101:56 | LL | /// Doc comments can end with an [inline link](#anchor) | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:104:65 + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:105:65 | LL | /// Some doc comments contain [link reference definitions][spec] | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:129:57 + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:130:57 | LL | /// Doc comments with trailing blank lines are supported | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:137:30 + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:138:30 | LL | /// Only the last sentence is | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:144:33 + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:145:33 | LL | /// This ends with a code `span` | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:153:27 + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:154:27 | LL | * Block doc comments work | ^ help: end the doc comment with some punctuation: `.` -error: aborting due to 16 previous errors +error: aborting due to 17 previous errors From f24631329204d822021d78f169164778de64b0f3 Mon Sep 17 00:00:00 2001 From: AudaciousAxiom <179637270+AudaciousAxiom@users.noreply.github.com> Date: Thu, 2 Oct 2025 19:20:59 +0200 Subject: [PATCH 07/60] doc_comments_missing_terminal_punctuation: accept punctuation in quotes --- ...c_comments_missing_terminal_punctuation.rs | 10 +++++++++- ...omments_missing_terminal_punctuation.fixed | 5 +++++ ...c_comments_missing_terminal_punctuation.rs | 5 +++++ ...mments_missing_terminal_punctuation.stderr | 20 ++++++++++++------- 4 files changed, 32 insertions(+), 8 deletions(-) diff --git a/clippy_lints/src/doc/doc_comments_missing_terminal_punctuation.rs b/clippy_lints/src/doc/doc_comments_missing_terminal_punctuation.rs index d26da1c1e5c08..5c73b747e826e 100644 --- a/clippy_lints/src/doc/doc_comments_missing_terminal_punctuation.rs +++ b/clippy_lints/src/doc/doc_comments_missing_terminal_punctuation.rs @@ -59,11 +59,19 @@ fn is_missing_punctuation(doc_string: &str) -> Option { Event::InlineHtml(_) | Event::Start(Tag::Image { .. }) | Event::End(TagEnd::Image) => { text_offset = None; }, - Event::Code(..) | Event::Text(..) | Event::Start(Tag::Link { .. }) | Event::End(TagEnd::Link) + Event::Code(..) | Event::Start(Tag::Link { .. }) | Event::End(TagEnd::Link) if no_report_depth == 0 && !offset.is_empty() => { text_offset = Some(offset.end); }, + Event::Text(..) if no_report_depth == 0 && !offset.is_empty() => { + // American-style quotes require punctuation to be placed inside closing quotation marks. + if doc_string[..offset.end].trim_end().ends_with('"') { + text_offset = Some(offset.end - 1); + } else { + text_offset = Some(offset.end); + } + }, _ => {}, } } diff --git a/tests/ui/doc/doc_comments_missing_terminal_punctuation.fixed b/tests/ui/doc/doc_comments_missing_terminal_punctuation.fixed index b17914250fa66..ebc9c17ea043e 100644 --- a/tests/ui/doc/doc_comments_missing_terminal_punctuation.fixed +++ b/tests/ui/doc/doc_comments_missing_terminal_punctuation.fixed @@ -96,6 +96,11 @@ enum Trailers { /// _But sometimes it is missing a period._ //~^ doc_comments_missing_terminal_punctuation UnderscoreFailing, + /// This comment ends with "a quote." + QuotePassing, + /// This comment ends with "a quote." + //~^ doc_comments_missing_terminal_punctuation + QuoteFailing, } /// Doc comments can end with an [inline link](#anchor). diff --git a/tests/ui/doc/doc_comments_missing_terminal_punctuation.rs b/tests/ui/doc/doc_comments_missing_terminal_punctuation.rs index 8c599b1916562..3677dc4ffb3e3 100644 --- a/tests/ui/doc/doc_comments_missing_terminal_punctuation.rs +++ b/tests/ui/doc/doc_comments_missing_terminal_punctuation.rs @@ -96,6 +96,11 @@ enum Trailers { /// _But sometimes it is missing a period_ //~^ doc_comments_missing_terminal_punctuation UnderscoreFailing, + /// This comment ends with "a quote." + QuotePassing, + /// This comment ends with "a quote" + //~^ doc_comments_missing_terminal_punctuation + QuoteFailing, } /// Doc comments can end with an [inline link](#anchor) diff --git a/tests/ui/doc/doc_comments_missing_terminal_punctuation.stderr b/tests/ui/doc/doc_comments_missing_terminal_punctuation.stderr index 4b4105155e878..2783f8e055b65 100644 --- a/tests/ui/doc/doc_comments_missing_terminal_punctuation.stderr +++ b/tests/ui/doc/doc_comments_missing_terminal_punctuation.stderr @@ -68,40 +68,46 @@ LL | /// _But sometimes it is missing a period_ | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:101:56 + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:101:40 + | +LL | /// This comment ends with "a quote" + | ^ help: end the doc comment with some punctuation: `.` + +error: doc comments should end with a terminal punctuation mark + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:106:56 | LL | /// Doc comments can end with an [inline link](#anchor) | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:105:65 + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:110:65 | LL | /// Some doc comments contain [link reference definitions][spec] | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:130:57 + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:135:57 | LL | /// Doc comments with trailing blank lines are supported | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:138:30 + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:143:30 | LL | /// Only the last sentence is | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:145:33 + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:150:33 | LL | /// This ends with a code `span` | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:154:27 + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:159:27 | LL | * Block doc comments work | ^ help: end the doc comment with some punctuation: `.` -error: aborting due to 17 previous errors +error: aborting due to 18 previous errors From 2ec26719fe462c25cee88115c057d963c08a2b3a Mon Sep 17 00:00:00 2001 From: AudaciousAxiom <179637270+AudaciousAxiom@users.noreply.github.com> Date: Thu, 2 Oct 2025 22:31:01 +0200 Subject: [PATCH 08/60] doc_comments_missing_terminal_punctuation: treat some trailers as unfixable --- ...c_comments_missing_terminal_punctuation.rs | 91 ++++++++++++------- ...omments_missing_terminal_punctuation.fixed | 17 ++-- ...c_comments_missing_terminal_punctuation.rs | 17 ++-- ...mments_missing_terminal_punctuation.stderr | 36 ++------ ..._missing_terminal_punctuation_unfixable.rs | 13 +++ ...sing_terminal_punctuation_unfixable.stderr | 20 ++++ 6 files changed, 116 insertions(+), 78 deletions(-) create mode 100644 tests/ui/doc/doc_comments_missing_terminal_punctuation_unfixable.rs create mode 100644 tests/ui/doc/doc_comments_missing_terminal_punctuation_unfixable.stderr diff --git a/clippy_lints/src/doc/doc_comments_missing_terminal_punctuation.rs b/clippy_lints/src/doc/doc_comments_missing_terminal_punctuation.rs index 5c73b747e826e..47b73999665b8 100644 --- a/clippy_lints/src/doc/doc_comments_missing_terminal_punctuation.rs +++ b/clippy_lints/src/doc/doc_comments_missing_terminal_punctuation.rs @@ -10,29 +10,45 @@ const MSG: &str = "doc comments should end with a terminal punctuation mark"; const PUNCTUATION_SUGGESTION: char = '.'; pub fn check(cx: &LateContext<'_>, doc: &str, fragments: Fragments<'_>) { - // This ignores `#[doc]` attributes, which we do not handle. - if let Some(offset) = is_missing_punctuation(doc) - && let Some(span) = fragments.span(cx, offset..offset) - { - clippy_utils::diagnostics::span_lint_and_sugg( - cx, - DOC_COMMENTS_MISSING_TERMINAL_PUNCTUATION, - span, - MSG, - "end the doc comment with some punctuation", - PUNCTUATION_SUGGESTION.to_string(), - Applicability::MaybeIncorrect, - ); + match is_missing_punctuation(doc) { + IsMissingPunctuation::Fixable(offset) => { + // This ignores `#[doc]` attributes, which we do not handle. + if let Some(span) = fragments.span(cx, offset..offset) { + clippy_utils::diagnostics::span_lint_and_sugg( + cx, + DOC_COMMENTS_MISSING_TERMINAL_PUNCTUATION, + span, + MSG, + "end the doc comment with some punctuation", + PUNCTUATION_SUGGESTION.to_string(), + Applicability::MaybeIncorrect, + ); + } + }, + IsMissingPunctuation::Unfixable(offset) => { + // This ignores `#[doc]` attributes, which we do not handle. + if let Some(span) = fragments.span(cx, offset..offset) { + clippy_utils::diagnostics::span_lint_and_help( + cx, + DOC_COMMENTS_MISSING_TERMINAL_PUNCTUATION, + span, + MSG, + None, + "end the doc comment with some punctuation", + ); + } + }, + IsMissingPunctuation::No => {}, } } #[must_use] /// If punctuation is missing, returns the offset where new punctuation should be inserted. -fn is_missing_punctuation(doc_string: &str) -> Option { +fn is_missing_punctuation(doc_string: &str) -> IsMissingPunctuation { const TERMINAL_PUNCTUATION_MARKS: &[char] = &['.', '?', '!', '…']; let mut no_report_depth = 0; - let mut text_offset = None; + let mut missing_punctuation = IsMissingPunctuation::No; for (event, offset) in Parser::new_ext(doc_string, main_body_opts() - Options::ENABLE_SMART_PUNCTUATION).into_offset_iter() { @@ -54,35 +70,48 @@ fn is_missing_punctuation(doc_string: &str) -> Option { TagEnd::CodeBlock | TagEnd::Heading(_) | TagEnd::HtmlBlock | TagEnd::List(_) | TagEnd::Table, ) => { no_report_depth -= 1; - text_offset = None; + missing_punctuation = IsMissingPunctuation::No; }, Event::InlineHtml(_) | Event::Start(Tag::Image { .. }) | Event::End(TagEnd::Image) => { - text_offset = None; + missing_punctuation = IsMissingPunctuation::No; }, Event::Code(..) | Event::Start(Tag::Link { .. }) | Event::End(TagEnd::Link) if no_report_depth == 0 && !offset.is_empty() => { - text_offset = Some(offset.end); + if doc_string[..offset.end] + .trim_end() + .ends_with(TERMINAL_PUNCTUATION_MARKS) + { + missing_punctuation = IsMissingPunctuation::No; + } else { + missing_punctuation = IsMissingPunctuation::Fixable(offset.end); + } }, Event::Text(..) if no_report_depth == 0 && !offset.is_empty() => { - // American-style quotes require punctuation to be placed inside closing quotation marks. - if doc_string[..offset.end].trim_end().ends_with('"') { - text_offset = Some(offset.end - 1); + let trimmed = doc_string[..offset.end].trim_end(); + if trimmed.ends_with(TERMINAL_PUNCTUATION_MARKS) { + missing_punctuation = IsMissingPunctuation::No; + } else if let Some(t) = trimmed.strip_suffix(|c| c == ')' || c == '"') { + if t.ends_with(TERMINAL_PUNCTUATION_MARKS) { + // Avoid false positives. + missing_punctuation = IsMissingPunctuation::No; + } else { + missing_punctuation = IsMissingPunctuation::Unfixable(offset.end); + } } else { - text_offset = Some(offset.end); + missing_punctuation = IsMissingPunctuation::Fixable(offset.end); } }, _ => {}, } } - let text_offset = text_offset?; - if doc_string[..text_offset] - .trim_end() - .ends_with(TERMINAL_PUNCTUATION_MARKS) - { - None - } else { - Some(text_offset) - } + missing_punctuation +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +enum IsMissingPunctuation { + Fixable(usize), + Unfixable(usize), + No, } diff --git a/tests/ui/doc/doc_comments_missing_terminal_punctuation.fixed b/tests/ui/doc/doc_comments_missing_terminal_punctuation.fixed index ebc9c17ea043e..a1f1da33d806a 100644 --- a/tests/ui/doc/doc_comments_missing_terminal_punctuation.fixed +++ b/tests/ui/doc/doc_comments_missing_terminal_punctuation.fixed @@ -80,12 +80,10 @@ mod module { } enum Trailers { - /// Sometimes the doc comment ends with parentheses (like this). - //~^ doc_comments_missing_terminal_punctuation - EndsWithParens, - /// (Sometimes the last sentence is in parentheses, but there is no special treatment of this.). - //~^ doc_comments_missing_terminal_punctuation - ParensFailing, + /// Sometimes the last sentence ends with parentheses (and that's ok). + ParensPassing, + /// (Sometimes the last sentence is in parentheses.) + SentenceInParensPassing, /// **Sometimes the last sentence is in bold, and that's ok.** DoubleStarPassing, /// **But sometimes it is missing a period.** @@ -97,10 +95,9 @@ enum Trailers { //~^ doc_comments_missing_terminal_punctuation UnderscoreFailing, /// This comment ends with "a quote." - QuotePassing, - /// This comment ends with "a quote." - //~^ doc_comments_missing_terminal_punctuation - QuoteFailing, + AmericanStyleQuotePassing, + /// This comment ends with "a quote". + BritishStyleQuotePassing, } /// Doc comments can end with an [inline link](#anchor). diff --git a/tests/ui/doc/doc_comments_missing_terminal_punctuation.rs b/tests/ui/doc/doc_comments_missing_terminal_punctuation.rs index 3677dc4ffb3e3..85c1b31dcd5b4 100644 --- a/tests/ui/doc/doc_comments_missing_terminal_punctuation.rs +++ b/tests/ui/doc/doc_comments_missing_terminal_punctuation.rs @@ -80,12 +80,10 @@ mod module { } enum Trailers { - /// Sometimes the doc comment ends with parentheses (like this) - //~^ doc_comments_missing_terminal_punctuation - EndsWithParens, - /// (Sometimes the last sentence is in parentheses, but there is no special treatment of this.) - //~^ doc_comments_missing_terminal_punctuation - ParensFailing, + /// Sometimes the last sentence ends with parentheses (and that's ok). + ParensPassing, + /// (Sometimes the last sentence is in parentheses.) + SentenceInParensPassing, /// **Sometimes the last sentence is in bold, and that's ok.** DoubleStarPassing, /// **But sometimes it is missing a period** @@ -97,10 +95,9 @@ enum Trailers { //~^ doc_comments_missing_terminal_punctuation UnderscoreFailing, /// This comment ends with "a quote." - QuotePassing, - /// This comment ends with "a quote" - //~^ doc_comments_missing_terminal_punctuation - QuoteFailing, + AmericanStyleQuotePassing, + /// This comment ends with "a quote". + BritishStyleQuotePassing, } /// Doc comments can end with an [inline link](#anchor) diff --git a/tests/ui/doc/doc_comments_missing_terminal_punctuation.stderr b/tests/ui/doc/doc_comments_missing_terminal_punctuation.stderr index 2783f8e055b65..bd36e7ace9290 100644 --- a/tests/ui/doc/doc_comments_missing_terminal_punctuation.stderr +++ b/tests/ui/doc/doc_comments_missing_terminal_punctuation.stderr @@ -44,70 +44,52 @@ LL | //! inner attributes too | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:83:68 - | -LL | /// Sometimes the doc comment ends with parentheses (like this) - | ^ help: end the doc comment with some punctuation: `.` - -error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:86:100 - | -LL | /// (Sometimes the last sentence is in parentheses, but there is no special treatment of this.) - | ^ help: end the doc comment with some punctuation: `.` - -error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:91:47 + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:89:47 | LL | /// **But sometimes it is missing a period** | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:96:46 + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:94:46 | LL | /// _But sometimes it is missing a period_ | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:101:40 - | -LL | /// This comment ends with "a quote" - | ^ help: end the doc comment with some punctuation: `.` - -error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:106:56 + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:103:56 | LL | /// Doc comments can end with an [inline link](#anchor) | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:110:65 + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:107:65 | LL | /// Some doc comments contain [link reference definitions][spec] | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:135:57 + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:132:57 | LL | /// Doc comments with trailing blank lines are supported | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:143:30 + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:140:30 | LL | /// Only the last sentence is | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:150:33 + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:147:33 | LL | /// This ends with a code `span` | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:159:27 + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:156:27 | LL | * Block doc comments work | ^ help: end the doc comment with some punctuation: `.` -error: aborting due to 18 previous errors +error: aborting due to 15 previous errors diff --git a/tests/ui/doc/doc_comments_missing_terminal_punctuation_unfixable.rs b/tests/ui/doc/doc_comments_missing_terminal_punctuation_unfixable.rs new file mode 100644 index 0000000000000..1c52441eb656d --- /dev/null +++ b/tests/ui/doc/doc_comments_missing_terminal_punctuation_unfixable.rs @@ -0,0 +1,13 @@ +#![feature(custom_inner_attributes)] +#![rustfmt::skip] +#![warn(clippy::doc_comments_missing_terminal_punctuation)] +//@no-rustfix + +enum UnfixableTrailers { + /// Sometimes the doc comment ends with parentheses (like this) + //~^ doc_comments_missing_terminal_punctuation + EndsWithParens, + /// This comment ends with "a quote" + //~^ doc_comments_missing_terminal_punctuation + QuoteFailing, +} diff --git a/tests/ui/doc/doc_comments_missing_terminal_punctuation_unfixable.stderr b/tests/ui/doc/doc_comments_missing_terminal_punctuation_unfixable.stderr new file mode 100644 index 0000000000000..72e10b0bff9f3 --- /dev/null +++ b/tests/ui/doc/doc_comments_missing_terminal_punctuation_unfixable.stderr @@ -0,0 +1,20 @@ +error: doc comments should end with a terminal punctuation mark + --> tests/ui/doc/doc_comments_missing_terminal_punctuation_unfixable.rs:7:68 + | +LL | /// Sometimes the doc comment ends with parentheses (like this) + | ^ + | + = help: end the doc comment with some punctuation + = note: `-D clippy::doc-comments-missing-terminal-punctuation` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::doc_comments_missing_terminal_punctuation)]` + +error: doc comments should end with a terminal punctuation mark + --> tests/ui/doc/doc_comments_missing_terminal_punctuation_unfixable.rs:10:41 + | +LL | /// This comment ends with "a quote" + | ^ + | + = help: end the doc comment with some punctuation + +error: aborting due to 2 previous errors + From 53fd302ad1ca19d3a8294e02b96050df101270c9 Mon Sep 17 00:00:00 2001 From: AudaciousAxiom <179637270+AudaciousAxiom@users.noreply.github.com> Date: Sun, 5 Oct 2025 11:19:46 +0200 Subject: [PATCH 09/60] doc_comments_missing_terminal_punctuation: short-circuit to avoid parsing --- .../src/doc/doc_comments_missing_terminal_punctuation.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/clippy_lints/src/doc/doc_comments_missing_terminal_punctuation.rs b/clippy_lints/src/doc/doc_comments_missing_terminal_punctuation.rs index 47b73999665b8..7b3f438e90674 100644 --- a/clippy_lints/src/doc/doc_comments_missing_terminal_punctuation.rs +++ b/clippy_lints/src/doc/doc_comments_missing_terminal_punctuation.rs @@ -47,6 +47,11 @@ pub fn check(cx: &LateContext<'_>, doc: &str, fragments: Fragments<'_>) { fn is_missing_punctuation(doc_string: &str) -> IsMissingPunctuation { const TERMINAL_PUNCTUATION_MARKS: &[char] = &['.', '?', '!', '…']; + // Short-circuit in simple, common cases to avoid Markdown parsing. + if doc_string.trim_end().ends_with(TERMINAL_PUNCTUATION_MARKS) { + return IsMissingPunctuation::No; + } + let mut no_report_depth = 0; let mut missing_punctuation = IsMissingPunctuation::No; for (event, offset) in From 76fb0ce3b821c50057328e8897ac6705443f1195 Mon Sep 17 00:00:00 2001 From: AudaciousAxiom <179637270+AudaciousAxiom@users.noreply.github.com> Date: Sat, 18 Oct 2025 10:45:55 +0200 Subject: [PATCH 10/60] doc_comments_missing_terminal_punctuation: check every paragraph --- ...c_comments_missing_terminal_punctuation.rs | 98 ++++++++++--------- clippy_lints/src/doc/mod.rs | 13 ++- ...omments_missing_terminal_punctuation.fixed | 16 ++- ...c_comments_missing_terminal_punctuation.rs | 14 ++- ...mments_missing_terminal_punctuation.stderr | 44 ++++++--- 5 files changed, 112 insertions(+), 73 deletions(-) diff --git a/clippy_lints/src/doc/doc_comments_missing_terminal_punctuation.rs b/clippy_lints/src/doc/doc_comments_missing_terminal_punctuation.rs index 7b3f438e90674..1a69bfaa12371 100644 --- a/clippy_lints/src/doc/doc_comments_missing_terminal_punctuation.rs +++ b/clippy_lints/src/doc/doc_comments_missing_terminal_punctuation.rs @@ -10,50 +10,50 @@ const MSG: &str = "doc comments should end with a terminal punctuation mark"; const PUNCTUATION_SUGGESTION: char = '.'; pub fn check(cx: &LateContext<'_>, doc: &str, fragments: Fragments<'_>) { - match is_missing_punctuation(doc) { - IsMissingPunctuation::Fixable(offset) => { - // This ignores `#[doc]` attributes, which we do not handle. - if let Some(span) = fragments.span(cx, offset..offset) { - clippy_utils::diagnostics::span_lint_and_sugg( - cx, - DOC_COMMENTS_MISSING_TERMINAL_PUNCTUATION, - span, - MSG, - "end the doc comment with some punctuation", - PUNCTUATION_SUGGESTION.to_string(), - Applicability::MaybeIncorrect, - ); - } - }, - IsMissingPunctuation::Unfixable(offset) => { - // This ignores `#[doc]` attributes, which we do not handle. - if let Some(span) = fragments.span(cx, offset..offset) { - clippy_utils::diagnostics::span_lint_and_help( - cx, - DOC_COMMENTS_MISSING_TERMINAL_PUNCTUATION, - span, - MSG, - None, - "end the doc comment with some punctuation", - ); - } - }, - IsMissingPunctuation::No => {}, + for missing_punctuation in is_missing_punctuation(doc) { + match missing_punctuation { + MissingPunctuation::Fixable(offset) => { + // This ignores `#[doc]` attributes, which we do not handle. + if let Some(span) = fragments.span(cx, offset..offset) { + clippy_utils::diagnostics::span_lint_and_sugg( + cx, + DOC_COMMENTS_MISSING_TERMINAL_PUNCTUATION, + span, + MSG, + "end the doc comment with some punctuation", + PUNCTUATION_SUGGESTION.to_string(), + Applicability::MaybeIncorrect, + ); + } + }, + MissingPunctuation::Unfixable(offset) => { + // This ignores `#[doc]` attributes, which we do not handle. + if let Some(span) = fragments.span(cx, offset..offset) { + clippy_utils::diagnostics::span_lint_and_help( + cx, + DOC_COMMENTS_MISSING_TERMINAL_PUNCTUATION, + span, + MSG, + None, + "end the doc comment with some punctuation", + ); + } + }, + } } } #[must_use] /// If punctuation is missing, returns the offset where new punctuation should be inserted. -fn is_missing_punctuation(doc_string: &str) -> IsMissingPunctuation { - const TERMINAL_PUNCTUATION_MARKS: &[char] = &['.', '?', '!', '…']; - - // Short-circuit in simple, common cases to avoid Markdown parsing. - if doc_string.trim_end().ends_with(TERMINAL_PUNCTUATION_MARKS) { - return IsMissingPunctuation::No; - } +fn is_missing_punctuation(doc_string: &str) -> Vec { + // The colon is not exactly a terminal punctuation mark, but this is required for paragraphs that + // introduce a table or a list for example. + const TERMINAL_PUNCTUATION_MARKS: &[char] = &['.', '?', '!', '…', ':']; let mut no_report_depth = 0; - let mut missing_punctuation = IsMissingPunctuation::No; + let mut missing_punctuation = Vec::new(); + let mut current_paragraph = None; + for (event, offset) in Parser::new_ext(doc_string, main_body_opts() - Options::ENABLE_SMART_PUNCTUATION).into_offset_iter() { @@ -75,10 +75,15 @@ fn is_missing_punctuation(doc_string: &str) -> IsMissingPunctuation { TagEnd::CodeBlock | TagEnd::Heading(_) | TagEnd::HtmlBlock | TagEnd::List(_) | TagEnd::Table, ) => { no_report_depth -= 1; - missing_punctuation = IsMissingPunctuation::No; + current_paragraph = None; }, Event::InlineHtml(_) | Event::Start(Tag::Image { .. }) | Event::End(TagEnd::Image) => { - missing_punctuation = IsMissingPunctuation::No; + current_paragraph = None; + }, + Event::End(TagEnd::Paragraph) => { + if let Some(mp) = current_paragraph { + missing_punctuation.push(mp); + } }, Event::Code(..) | Event::Start(Tag::Link { .. }) | Event::End(TagEnd::Link) if no_report_depth == 0 && !offset.is_empty() => @@ -87,24 +92,24 @@ fn is_missing_punctuation(doc_string: &str) -> IsMissingPunctuation { .trim_end() .ends_with(TERMINAL_PUNCTUATION_MARKS) { - missing_punctuation = IsMissingPunctuation::No; + current_paragraph = None; } else { - missing_punctuation = IsMissingPunctuation::Fixable(offset.end); + current_paragraph = Some(MissingPunctuation::Fixable(offset.end)); } }, Event::Text(..) if no_report_depth == 0 && !offset.is_empty() => { let trimmed = doc_string[..offset.end].trim_end(); if trimmed.ends_with(TERMINAL_PUNCTUATION_MARKS) { - missing_punctuation = IsMissingPunctuation::No; + current_paragraph = None; } else if let Some(t) = trimmed.strip_suffix(|c| c == ')' || c == '"') { if t.ends_with(TERMINAL_PUNCTUATION_MARKS) { // Avoid false positives. - missing_punctuation = IsMissingPunctuation::No; + current_paragraph = None; } else { - missing_punctuation = IsMissingPunctuation::Unfixable(offset.end); + current_paragraph = Some(MissingPunctuation::Unfixable(offset.end)); } } else { - missing_punctuation = IsMissingPunctuation::Fixable(offset.end); + current_paragraph = Some(MissingPunctuation::Fixable(offset.end)); } }, _ => {}, @@ -115,8 +120,7 @@ fn is_missing_punctuation(doc_string: &str) -> IsMissingPunctuation { } #[derive(Debug, Copy, Clone, PartialEq, Eq)] -enum IsMissingPunctuation { +enum MissingPunctuation { Fixable(usize), Unfixable(usize), - No, } diff --git a/clippy_lints/src/doc/mod.rs b/clippy_lints/src/doc/mod.rs index a0911fccd5914..90120553fff56 100644 --- a/clippy_lints/src/doc/mod.rs +++ b/clippy_lints/src/doc/mod.rs @@ -673,19 +673,24 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does - /// Checks for doc comments that do not end with a period or another punctuation mark. + /// Checks for doc comments whose paragraphs do not end with a period or another punctuation mark. /// Various Markdowns constructs are taken into account to avoid false positives. /// /// ### Why is this bad? - /// A project may wish to enforce consistent doc comments by making sure they end with a punctuation mark. + /// A project may wish to enforce consistent doc comments by making sure paragraphs end with a + /// punctuation mark. /// /// ### Example /// ```no_run - /// /// Returns the Answer to the Ultimate Question of Life, the Universe, and Everything + /// /// Returns a random number + /// /// + /// /// It was chosen by a fair dice roll /// ``` /// Use instead: /// ```no_run - /// /// Returns the Answer to the Ultimate Question of Life, the Universe, and Everything. + /// /// Returns a random number. + /// /// + /// /// It was chosen by a fair dice roll. /// ``` #[clippy::version = "1.92.0"] pub DOC_COMMENTS_MISSING_TERMINAL_PUNCTUATION, diff --git a/tests/ui/doc/doc_comments_missing_terminal_punctuation.fixed b/tests/ui/doc/doc_comments_missing_terminal_punctuation.fixed index a1f1da33d806a..6961d3d0eae1e 100644 --- a/tests/ui/doc/doc_comments_missing_terminal_punctuation.fixed +++ b/tests/ui/doc/doc_comments_missing_terminal_punctuation.fixed @@ -46,7 +46,8 @@ enum Exceptions { /// | -------------- | ----- | /// | Markdown table | A-ok | MarkdownTable, - /// Here is a snippet + /// Here is a snippet. + //~^ doc_comments_missing_terminal_punctuation /// /// ``` /// // Code blocks are no issues. @@ -134,12 +135,17 @@ enum OrderedLists { /// struct TrailingBlankLine; -/// The first paragraph is not checked +/// This doc comment has multiple paragraph. +/// This first paragraph is missing punctuation. +//~^ doc_comments_missing_terminal_punctuation +/// +/// The second one as well +/// And it has multiple sentences. +//~^ doc_comments_missing_terminal_punctuation /// -/// Other sentences are not either -/// Only the last sentence is. +/// Same for this third and last one. //~^ doc_comments_missing_terminal_punctuation -struct OnlyLastSentence; +struct MultiParagraphDocComment; /// ``` struct IncompleteBlockCode; diff --git a/tests/ui/doc/doc_comments_missing_terminal_punctuation.rs b/tests/ui/doc/doc_comments_missing_terminal_punctuation.rs index 85c1b31dcd5b4..1ab0b96ec719e 100644 --- a/tests/ui/doc/doc_comments_missing_terminal_punctuation.rs +++ b/tests/ui/doc/doc_comments_missing_terminal_punctuation.rs @@ -47,6 +47,7 @@ enum Exceptions { /// | Markdown table | A-ok | MarkdownTable, /// Here is a snippet + //~^ doc_comments_missing_terminal_punctuation /// /// ``` /// // Code blocks are no issues. @@ -134,12 +135,17 @@ enum OrderedLists { /// struct TrailingBlankLine; -/// The first paragraph is not checked +/// This doc comment has multiple paragraph. +/// This first paragraph is missing punctuation +//~^ doc_comments_missing_terminal_punctuation +/// +/// The second one as well +/// And it has multiple sentences +//~^ doc_comments_missing_terminal_punctuation /// -/// Other sentences are not either -/// Only the last sentence is +/// Same for this third and last one //~^ doc_comments_missing_terminal_punctuation -struct OnlyLastSentence; +struct MultiParagraphDocComment; /// ``` struct IncompleteBlockCode; diff --git a/tests/ui/doc/doc_comments_missing_terminal_punctuation.stderr b/tests/ui/doc/doc_comments_missing_terminal_punctuation.stderr index bd36e7ace9290..f0617c6e9b25d 100644 --- a/tests/ui/doc/doc_comments_missing_terminal_punctuation.stderr +++ b/tests/ui/doc/doc_comments_missing_terminal_punctuation.stderr @@ -32,64 +32,82 @@ LL | /// | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:71:15 + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:49:26 + | +LL | /// Here is a snippet + | ^ help: end the doc comment with some punctuation: `.` + +error: doc comments should end with a terminal punctuation mark + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:72:15 | LL | /// U+0001 | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:78:29 + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:79:29 | LL | //! inner attributes too | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:89:47 + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:90:47 | LL | /// **But sometimes it is missing a period** | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:94:46 + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:95:46 | LL | /// _But sometimes it is missing a period_ | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:103:56 + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:104:56 | LL | /// Doc comments can end with an [inline link](#anchor) | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:107:65 + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:108:65 | LL | /// Some doc comments contain [link reference definitions][spec] | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:132:57 + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:133:57 | LL | /// Doc comments with trailing blank lines are supported | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:140:30 + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:139:48 + | +LL | /// This first paragraph is missing punctuation + | ^ help: end the doc comment with some punctuation: `.` + +error: doc comments should end with a terminal punctuation mark + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:143:34 + | +LL | /// And it has multiple sentences + | ^ help: end the doc comment with some punctuation: `.` + +error: doc comments should end with a terminal punctuation mark + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:146:37 | -LL | /// Only the last sentence is - | ^ help: end the doc comment with some punctuation: `.` +LL | /// Same for this third and last one + | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:147:33 + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:153:33 | LL | /// This ends with a code `span` | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:156:27 + --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:162:27 | LL | * Block doc comments work | ^ help: end the doc comment with some punctuation: `.` -error: aborting due to 15 previous errors +error: aborting due to 18 previous errors From 52aa4a5abe06cf4173fd83003b2f9f3b9d00374d Mon Sep 17 00:00:00 2001 From: AudaciousAxiom <179637270+AudaciousAxiom@users.noreply.github.com> Date: Sun, 19 Oct 2025 16:52:21 +0200 Subject: [PATCH 11/60] doc_paragraphs_missing_punctuation: rename the lint --- CHANGELOG.md | 2 +- clippy_lints/src/declared_lints.rs | 2 +- ... => doc_paragraphs_missing_punctuation.rs} | 6 +-- clippy_lints/src/doc/mod.rs | 8 ++-- ... doc_paragraphs_missing_punctuation.fixed} | 42 +++++++++---------- ... => doc_paragraphs_missing_punctuation.rs} | 42 +++++++++---------- ...doc_paragraphs_missing_punctuation.stderr} | 40 +++++++++--------- ...ragraphs_missing_punctuation_unfixable.rs} | 6 +-- ...aphs_missing_punctuation_unfixable.stderr} | 8 ++-- 9 files changed, 78 insertions(+), 78 deletions(-) rename clippy_lints/src/doc/{doc_comments_missing_terminal_punctuation.rs => doc_paragraphs_missing_punctuation.rs} (95%) rename tests/ui/doc/{doc_comments_missing_terminal_punctuation.fixed => doc_paragraphs_missing_punctuation.fixed} (76%) rename tests/ui/doc/{doc_comments_missing_terminal_punctuation.rs => doc_paragraphs_missing_punctuation.rs} (76%) rename tests/ui/doc/{doc_comments_missing_terminal_punctuation.stderr => doc_paragraphs_missing_punctuation.stderr} (72%) rename tests/ui/doc/{doc_comments_missing_terminal_punctuation_unfixable.rs => doc_paragraphs_missing_punctuation_unfixable.rs} (60%) rename tests/ui/doc/{doc_comments_missing_terminal_punctuation_unfixable.stderr => doc_paragraphs_missing_punctuation_unfixable.stderr} (60%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42d9b2ea60f29..df4346cef8b69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6224,7 +6224,6 @@ Released 2018-09-13 [`diverging_sub_expression`]: https://rust-lang.github.io/rust-clippy/master/index.html#diverging_sub_expression [`doc_broken_link`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_broken_link [`doc_comment_double_space_linebreaks`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_comment_double_space_linebreaks -[`doc_comments_missing_terminal_punctuation`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_comments_missing_terminal_punctuation [`doc_include_without_cfg`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_include_without_cfg [`doc_lazy_continuation`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_lazy_continuation [`doc_link_code`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_link_code @@ -6232,6 +6231,7 @@ Released 2018-09-13 [`doc_markdown`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_markdown [`doc_nested_refdefs`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_nested_refdefs [`doc_overindented_list_items`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_overindented_list_items +[`doc_paragraphs_missing_punctuation`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_paragraphs_missing_punctuation [`doc_suspicious_footnotes`]: https://rust-lang.github.io/rust-clippy/master/index.html#doc_suspicious_footnotes [`double_comparisons`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_comparisons [`double_ended_iterator_last`]: https://rust-lang.github.io/rust-clippy/master/index.html#double_ended_iterator_last diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index fbeafef2e4554..e9ce87c2dd8d3 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -110,7 +110,6 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::disallowed_script_idents::DISALLOWED_SCRIPT_IDENTS_INFO, crate::disallowed_types::DISALLOWED_TYPES_INFO, crate::doc::DOC_BROKEN_LINK_INFO, - crate::doc::DOC_COMMENTS_MISSING_TERMINAL_PUNCTUATION_INFO, crate::doc::DOC_COMMENT_DOUBLE_SPACE_LINEBREAKS_INFO, crate::doc::DOC_INCLUDE_WITHOUT_CFG_INFO, crate::doc::DOC_LAZY_CONTINUATION_INFO, @@ -119,6 +118,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::doc::DOC_MARKDOWN_INFO, crate::doc::DOC_NESTED_REFDEFS_INFO, crate::doc::DOC_OVERINDENTED_LIST_ITEMS_INFO, + crate::doc::DOC_PARAGRAPHS_MISSING_PUNCTUATION_INFO, crate::doc::DOC_SUSPICIOUS_FOOTNOTES_INFO, crate::doc::EMPTY_DOCS_INFO, crate::doc::MISSING_ERRORS_DOC_INFO, diff --git a/clippy_lints/src/doc/doc_comments_missing_terminal_punctuation.rs b/clippy_lints/src/doc/doc_paragraphs_missing_punctuation.rs similarity index 95% rename from clippy_lints/src/doc/doc_comments_missing_terminal_punctuation.rs rename to clippy_lints/src/doc/doc_paragraphs_missing_punctuation.rs index 1a69bfaa12371..7e8acadbd6fa9 100644 --- a/clippy_lints/src/doc/doc_comments_missing_terminal_punctuation.rs +++ b/clippy_lints/src/doc/doc_paragraphs_missing_punctuation.rs @@ -4,7 +4,7 @@ use rustc_resolve::rustdoc::main_body_opts; use rustc_resolve::rustdoc::pulldown_cmark::{Event, Options, Parser, Tag, TagEnd}; -use super::{DOC_COMMENTS_MISSING_TERMINAL_PUNCTUATION, Fragments}; +use super::{DOC_PARAGRAPHS_MISSING_PUNCTUATION, Fragments}; const MSG: &str = "doc comments should end with a terminal punctuation mark"; const PUNCTUATION_SUGGESTION: char = '.'; @@ -17,7 +17,7 @@ pub fn check(cx: &LateContext<'_>, doc: &str, fragments: Fragments<'_>) { if let Some(span) = fragments.span(cx, offset..offset) { clippy_utils::diagnostics::span_lint_and_sugg( cx, - DOC_COMMENTS_MISSING_TERMINAL_PUNCTUATION, + DOC_PARAGRAPHS_MISSING_PUNCTUATION, span, MSG, "end the doc comment with some punctuation", @@ -31,7 +31,7 @@ pub fn check(cx: &LateContext<'_>, doc: &str, fragments: Fragments<'_>) { if let Some(span) = fragments.span(cx, offset..offset) { clippy_utils::diagnostics::span_lint_and_help( cx, - DOC_COMMENTS_MISSING_TERMINAL_PUNCTUATION, + DOC_PARAGRAPHS_MISSING_PUNCTUATION, span, MSG, None, diff --git a/clippy_lints/src/doc/mod.rs b/clippy_lints/src/doc/mod.rs index 90120553fff56..45c11f8a82de5 100644 --- a/clippy_lints/src/doc/mod.rs +++ b/clippy_lints/src/doc/mod.rs @@ -28,7 +28,7 @@ use url::Url; mod broken_link; mod doc_comment_double_space_linebreaks; -mod doc_comments_missing_terminal_punctuation; +mod doc_paragraphs_missing_punctuation; mod doc_suspicious_footnotes; mod include_in_doc_without_cfg; mod lazy_continuation; @@ -693,7 +693,7 @@ declare_clippy_lint! { /// /// It was chosen by a fair dice roll. /// ``` #[clippy::version = "1.92.0"] - pub DOC_COMMENTS_MISSING_TERMINAL_PUNCTUATION, + pub DOC_PARAGRAPHS_MISSING_PUNCTUATION, nursery, "missing terminal punctuation in doc comments" } @@ -732,7 +732,7 @@ impl_lint_pass!(Documentation => [ DOC_INCLUDE_WITHOUT_CFG, DOC_COMMENT_DOUBLE_SPACE_LINEBREAKS, DOC_SUSPICIOUS_FOOTNOTES, - DOC_COMMENTS_MISSING_TERMINAL_PUNCTUATION, + DOC_PARAGRAPHS_MISSING_PUNCTUATION, ]); impl EarlyLintPass for Documentation { @@ -904,7 +904,7 @@ fn check_attrs(cx: &LateContext<'_>, valid_idents: &FxHashSet, attrs: &[ }, ); - doc_comments_missing_terminal_punctuation::check( + doc_paragraphs_missing_punctuation::check( cx, &doc, Fragments { diff --git a/tests/ui/doc/doc_comments_missing_terminal_punctuation.fixed b/tests/ui/doc/doc_paragraphs_missing_punctuation.fixed similarity index 76% rename from tests/ui/doc/doc_comments_missing_terminal_punctuation.fixed rename to tests/ui/doc/doc_paragraphs_missing_punctuation.fixed index 6961d3d0eae1e..95d65039440b5 100644 --- a/tests/ui/doc/doc_comments_missing_terminal_punctuation.fixed +++ b/tests/ui/doc/doc_paragraphs_missing_punctuation.fixed @@ -1,20 +1,20 @@ #![feature(custom_inner_attributes)] #![rustfmt::skip] -#![warn(clippy::doc_comments_missing_terminal_punctuation)] +#![warn(clippy::doc_paragraphs_missing_punctuation)] /// Returns the Answer to the Ultimate Question of Life, the Universe, and Everything. -//~^ doc_comments_missing_terminal_punctuation +//~^ doc_paragraphs_missing_punctuation fn answer() -> i32 { 42 } /// The `Option` type. -//~^ doc_comments_missing_terminal_punctuation +//~^ doc_paragraphs_missing_punctuation // Triggers even in the presence of another attribute. #[derive(Debug)] enum MyOption { /// No value. - //~^ doc_comments_missing_terminal_punctuation + //~^ doc_paragraphs_missing_punctuation None, /// Some value of type `T`. Some(T), @@ -25,7 +25,7 @@ enum MyOption { #[derive(Debug)] /// doc comment: /// only the last line triggers the lint. -//~^ doc_comments_missing_terminal_punctuation +//~^ doc_paragraphs_missing_punctuation enum Exceptions { /// Question marks are fine? QuestionMark, @@ -38,7 +38,7 @@ enum Exceptions { RawHtml, /// The raw HTML exception actually does the right thing to autolinks: /// . - //~^ doc_comments_missing_terminal_punctuation + //~^ doc_paragraphs_missing_punctuation MarkdownAutolink, /// This table introduction ends with a colon: /// @@ -47,7 +47,7 @@ enum Exceptions { /// | Markdown table | A-ok | MarkdownTable, /// Here is a snippet. - //~^ doc_comments_missing_terminal_punctuation + //~^ doc_paragraphs_missing_punctuation /// /// ``` /// // Code blocks are no issues. @@ -56,7 +56,7 @@ enum Exceptions { } // Check the lint can be expected on a whole enum at once. -#[expect(clippy::doc_comments_missing_terminal_punctuation)] +#[expect(clippy::doc_paragraphs_missing_punctuation)] enum Char { /// U+0000 Null, @@ -66,18 +66,18 @@ enum Char { // Check the lint can be expected on a single variant without affecting others. enum Char2 { - #[expect(clippy::doc_comments_missing_terminal_punctuation)] + #[expect(clippy::doc_paragraphs_missing_punctuation)] /// U+0000 Null, /// U+0001. - //~^ doc_comments_missing_terminal_punctuation + //~^ doc_paragraphs_missing_punctuation StartOfHeading, } mod module { //! Works on //! inner attributes too. - //~^ doc_comments_missing_terminal_punctuation + //~^ doc_paragraphs_missing_punctuation } enum Trailers { @@ -88,12 +88,12 @@ enum Trailers { /// **Sometimes the last sentence is in bold, and that's ok.** DoubleStarPassing, /// **But sometimes it is missing a period.** - //~^ doc_comments_missing_terminal_punctuation + //~^ doc_paragraphs_missing_punctuation DoubleStarFailing, /// _Sometimes the last sentence is in italics, and that's ok._ UnderscorePassing, /// _But sometimes it is missing a period._ - //~^ doc_comments_missing_terminal_punctuation + //~^ doc_paragraphs_missing_punctuation UnderscoreFailing, /// This comment ends with "a quote." AmericanStyleQuotePassing, @@ -102,11 +102,11 @@ enum Trailers { } /// Doc comments can end with an [inline link](#anchor). -//~^ doc_comments_missing_terminal_punctuation +//~^ doc_paragraphs_missing_punctuation struct InlineLink; /// Some doc comments contain [link reference definitions][spec]. -//~^ doc_comments_missing_terminal_punctuation +//~^ doc_paragraphs_missing_punctuation /// /// [spec]: https://spec.commonmark.org/0.31.2/#link-reference-definitions struct LinkRefDefinition; @@ -131,27 +131,27 @@ enum OrderedLists { } /// Doc comments with trailing blank lines are supported. -//~^ doc_comments_missing_terminal_punctuation +//~^ doc_paragraphs_missing_punctuation /// struct TrailingBlankLine; /// This doc comment has multiple paragraph. /// This first paragraph is missing punctuation. -//~^ doc_comments_missing_terminal_punctuation +//~^ doc_paragraphs_missing_punctuation /// /// The second one as well /// And it has multiple sentences. -//~^ doc_comments_missing_terminal_punctuation +//~^ doc_paragraphs_missing_punctuation /// /// Same for this third and last one. -//~^ doc_comments_missing_terminal_punctuation +//~^ doc_paragraphs_missing_punctuation struct MultiParagraphDocComment; /// ``` struct IncompleteBlockCode; /// This ends with a code `span`. -//~^ doc_comments_missing_terminal_punctuation +//~^ doc_paragraphs_missing_punctuation struct CodeSpan; #[expect(clippy::empty_docs)] @@ -162,7 +162,7 @@ struct EmptyDocComment; * Block doc comments work. * */ -//~^^^ doc_comments_missing_terminal_punctuation +//~^^^ doc_paragraphs_missing_punctuation struct BlockDocComment; /// Sometimes a doc attribute is used for concatenation diff --git a/tests/ui/doc/doc_comments_missing_terminal_punctuation.rs b/tests/ui/doc/doc_paragraphs_missing_punctuation.rs similarity index 76% rename from tests/ui/doc/doc_comments_missing_terminal_punctuation.rs rename to tests/ui/doc/doc_paragraphs_missing_punctuation.rs index 1ab0b96ec719e..35b74d7d13b9e 100644 --- a/tests/ui/doc/doc_comments_missing_terminal_punctuation.rs +++ b/tests/ui/doc/doc_paragraphs_missing_punctuation.rs @@ -1,20 +1,20 @@ #![feature(custom_inner_attributes)] #![rustfmt::skip] -#![warn(clippy::doc_comments_missing_terminal_punctuation)] +#![warn(clippy::doc_paragraphs_missing_punctuation)] /// Returns the Answer to the Ultimate Question of Life, the Universe, and Everything -//~^ doc_comments_missing_terminal_punctuation +//~^ doc_paragraphs_missing_punctuation fn answer() -> i32 { 42 } /// The `Option` type -//~^ doc_comments_missing_terminal_punctuation +//~^ doc_paragraphs_missing_punctuation // Triggers even in the presence of another attribute. #[derive(Debug)] enum MyOption { /// No value - //~^ doc_comments_missing_terminal_punctuation + //~^ doc_paragraphs_missing_punctuation None, /// Some value of type `T`. Some(T), @@ -25,7 +25,7 @@ enum MyOption { #[derive(Debug)] /// doc comment: /// only the last line triggers the lint -//~^ doc_comments_missing_terminal_punctuation +//~^ doc_paragraphs_missing_punctuation enum Exceptions { /// Question marks are fine? QuestionMark, @@ -38,7 +38,7 @@ enum Exceptions { RawHtml, /// The raw HTML exception actually does the right thing to autolinks: /// - //~^ doc_comments_missing_terminal_punctuation + //~^ doc_paragraphs_missing_punctuation MarkdownAutolink, /// This table introduction ends with a colon: /// @@ -47,7 +47,7 @@ enum Exceptions { /// | Markdown table | A-ok | MarkdownTable, /// Here is a snippet - //~^ doc_comments_missing_terminal_punctuation + //~^ doc_paragraphs_missing_punctuation /// /// ``` /// // Code blocks are no issues. @@ -56,7 +56,7 @@ enum Exceptions { } // Check the lint can be expected on a whole enum at once. -#[expect(clippy::doc_comments_missing_terminal_punctuation)] +#[expect(clippy::doc_paragraphs_missing_punctuation)] enum Char { /// U+0000 Null, @@ -66,18 +66,18 @@ enum Char { // Check the lint can be expected on a single variant without affecting others. enum Char2 { - #[expect(clippy::doc_comments_missing_terminal_punctuation)] + #[expect(clippy::doc_paragraphs_missing_punctuation)] /// U+0000 Null, /// U+0001 - //~^ doc_comments_missing_terminal_punctuation + //~^ doc_paragraphs_missing_punctuation StartOfHeading, } mod module { //! Works on //! inner attributes too - //~^ doc_comments_missing_terminal_punctuation + //~^ doc_paragraphs_missing_punctuation } enum Trailers { @@ -88,12 +88,12 @@ enum Trailers { /// **Sometimes the last sentence is in bold, and that's ok.** DoubleStarPassing, /// **But sometimes it is missing a period** - //~^ doc_comments_missing_terminal_punctuation + //~^ doc_paragraphs_missing_punctuation DoubleStarFailing, /// _Sometimes the last sentence is in italics, and that's ok._ UnderscorePassing, /// _But sometimes it is missing a period_ - //~^ doc_comments_missing_terminal_punctuation + //~^ doc_paragraphs_missing_punctuation UnderscoreFailing, /// This comment ends with "a quote." AmericanStyleQuotePassing, @@ -102,11 +102,11 @@ enum Trailers { } /// Doc comments can end with an [inline link](#anchor) -//~^ doc_comments_missing_terminal_punctuation +//~^ doc_paragraphs_missing_punctuation struct InlineLink; /// Some doc comments contain [link reference definitions][spec] -//~^ doc_comments_missing_terminal_punctuation +//~^ doc_paragraphs_missing_punctuation /// /// [spec]: https://spec.commonmark.org/0.31.2/#link-reference-definitions struct LinkRefDefinition; @@ -131,27 +131,27 @@ enum OrderedLists { } /// Doc comments with trailing blank lines are supported -//~^ doc_comments_missing_terminal_punctuation +//~^ doc_paragraphs_missing_punctuation /// struct TrailingBlankLine; /// This doc comment has multiple paragraph. /// This first paragraph is missing punctuation -//~^ doc_comments_missing_terminal_punctuation +//~^ doc_paragraphs_missing_punctuation /// /// The second one as well /// And it has multiple sentences -//~^ doc_comments_missing_terminal_punctuation +//~^ doc_paragraphs_missing_punctuation /// /// Same for this third and last one -//~^ doc_comments_missing_terminal_punctuation +//~^ doc_paragraphs_missing_punctuation struct MultiParagraphDocComment; /// ``` struct IncompleteBlockCode; /// This ends with a code `span` -//~^ doc_comments_missing_terminal_punctuation +//~^ doc_paragraphs_missing_punctuation struct CodeSpan; #[expect(clippy::empty_docs)] @@ -162,7 +162,7 @@ struct EmptyDocComment; * Block doc comments work * */ -//~^^^ doc_comments_missing_terminal_punctuation +//~^^^ doc_paragraphs_missing_punctuation struct BlockDocComment; /// Sometimes a doc attribute is used for concatenation diff --git a/tests/ui/doc/doc_comments_missing_terminal_punctuation.stderr b/tests/ui/doc/doc_paragraphs_missing_punctuation.stderr similarity index 72% rename from tests/ui/doc/doc_comments_missing_terminal_punctuation.stderr rename to tests/ui/doc/doc_paragraphs_missing_punctuation.stderr index f0617c6e9b25d..75c5edb06f99b 100644 --- a/tests/ui/doc/doc_comments_missing_terminal_punctuation.stderr +++ b/tests/ui/doc/doc_paragraphs_missing_punctuation.stderr @@ -1,110 +1,110 @@ error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:5:86 + --> tests/ui/doc/doc_paragraphs_missing_punctuation.rs:5:86 | LL | /// Returns the Answer to the Ultimate Question of Life, the Universe, and Everything | ^ help: end the doc comment with some punctuation: `.` | - = note: `-D clippy::doc-comments-missing-terminal-punctuation` implied by `-D warnings` - = help: to override `-D warnings` add `#[allow(clippy::doc_comments_missing_terminal_punctuation)]` + = note: `-D clippy::doc-paragraphs-missing-punctuation` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::doc_paragraphs_missing_punctuation)]` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:11:22 + --> tests/ui/doc/doc_paragraphs_missing_punctuation.rs:11:22 | LL | /// The `Option` type | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:16:17 + --> tests/ui/doc/doc_paragraphs_missing_punctuation.rs:16:17 | LL | /// No value | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:27:41 + --> tests/ui/doc/doc_paragraphs_missing_punctuation.rs:27:41 | LL | /// only the last line triggers the lint | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:40:56 + --> tests/ui/doc/doc_paragraphs_missing_punctuation.rs:40:56 | LL | /// | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:49:26 + --> tests/ui/doc/doc_paragraphs_missing_punctuation.rs:49:26 | LL | /// Here is a snippet | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:72:15 + --> tests/ui/doc/doc_paragraphs_missing_punctuation.rs:72:15 | LL | /// U+0001 | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:79:29 + --> tests/ui/doc/doc_paragraphs_missing_punctuation.rs:79:29 | LL | //! inner attributes too | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:90:47 + --> tests/ui/doc/doc_paragraphs_missing_punctuation.rs:90:47 | LL | /// **But sometimes it is missing a period** | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:95:46 + --> tests/ui/doc/doc_paragraphs_missing_punctuation.rs:95:46 | LL | /// _But sometimes it is missing a period_ | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:104:56 + --> tests/ui/doc/doc_paragraphs_missing_punctuation.rs:104:56 | LL | /// Doc comments can end with an [inline link](#anchor) | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:108:65 + --> tests/ui/doc/doc_paragraphs_missing_punctuation.rs:108:65 | LL | /// Some doc comments contain [link reference definitions][spec] | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:133:57 + --> tests/ui/doc/doc_paragraphs_missing_punctuation.rs:133:57 | LL | /// Doc comments with trailing blank lines are supported | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:139:48 + --> tests/ui/doc/doc_paragraphs_missing_punctuation.rs:139:48 | LL | /// This first paragraph is missing punctuation | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:143:34 + --> tests/ui/doc/doc_paragraphs_missing_punctuation.rs:143:34 | LL | /// And it has multiple sentences | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:146:37 + --> tests/ui/doc/doc_paragraphs_missing_punctuation.rs:146:37 | LL | /// Same for this third and last one | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:153:33 + --> tests/ui/doc/doc_paragraphs_missing_punctuation.rs:153:33 | LL | /// This ends with a code `span` | ^ help: end the doc comment with some punctuation: `.` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation.rs:162:27 + --> tests/ui/doc/doc_paragraphs_missing_punctuation.rs:162:27 | LL | * Block doc comments work | ^ help: end the doc comment with some punctuation: `.` diff --git a/tests/ui/doc/doc_comments_missing_terminal_punctuation_unfixable.rs b/tests/ui/doc/doc_paragraphs_missing_punctuation_unfixable.rs similarity index 60% rename from tests/ui/doc/doc_comments_missing_terminal_punctuation_unfixable.rs rename to tests/ui/doc/doc_paragraphs_missing_punctuation_unfixable.rs index 1c52441eb656d..3873f1d1edcfa 100644 --- a/tests/ui/doc/doc_comments_missing_terminal_punctuation_unfixable.rs +++ b/tests/ui/doc/doc_paragraphs_missing_punctuation_unfixable.rs @@ -1,13 +1,13 @@ #![feature(custom_inner_attributes)] #![rustfmt::skip] -#![warn(clippy::doc_comments_missing_terminal_punctuation)] +#![warn(clippy::doc_paragraphs_missing_punctuation)] //@no-rustfix enum UnfixableTrailers { /// Sometimes the doc comment ends with parentheses (like this) - //~^ doc_comments_missing_terminal_punctuation + //~^ doc_paragraphs_missing_punctuation EndsWithParens, /// This comment ends with "a quote" - //~^ doc_comments_missing_terminal_punctuation + //~^ doc_paragraphs_missing_punctuation QuoteFailing, } diff --git a/tests/ui/doc/doc_comments_missing_terminal_punctuation_unfixable.stderr b/tests/ui/doc/doc_paragraphs_missing_punctuation_unfixable.stderr similarity index 60% rename from tests/ui/doc/doc_comments_missing_terminal_punctuation_unfixable.stderr rename to tests/ui/doc/doc_paragraphs_missing_punctuation_unfixable.stderr index 72e10b0bff9f3..72cce4b82594e 100644 --- a/tests/ui/doc/doc_comments_missing_terminal_punctuation_unfixable.stderr +++ b/tests/ui/doc/doc_paragraphs_missing_punctuation_unfixable.stderr @@ -1,15 +1,15 @@ error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation_unfixable.rs:7:68 + --> tests/ui/doc/doc_paragraphs_missing_punctuation_unfixable.rs:7:68 | LL | /// Sometimes the doc comment ends with parentheses (like this) | ^ | = help: end the doc comment with some punctuation - = note: `-D clippy::doc-comments-missing-terminal-punctuation` implied by `-D warnings` - = help: to override `-D warnings` add `#[allow(clippy::doc_comments_missing_terminal_punctuation)]` + = note: `-D clippy::doc-paragraphs-missing-punctuation` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::doc_paragraphs_missing_punctuation)]` error: doc comments should end with a terminal punctuation mark - --> tests/ui/doc/doc_comments_missing_terminal_punctuation_unfixable.rs:10:41 + --> tests/ui/doc/doc_paragraphs_missing_punctuation_unfixable.rs:10:41 | LL | /// This comment ends with "a quote" | ^ From 2f9342e8ff97499b4035c19b58d4e6764076aa00 Mon Sep 17 00:00:00 2001 From: AudaciousAxiom <179637270+AudaciousAxiom@users.noreply.github.com> Date: Sun, 19 Oct 2025 17:08:33 +0200 Subject: [PATCH 12/60] doc_paragraphs_missing_punctuation: update the lint message --- .../doc/doc_paragraphs_missing_punctuation.rs | 2 +- .../doc_paragraphs_missing_punctuation.stderr | 36 +++++++++---------- ...raphs_missing_punctuation_unfixable.stderr | 4 +-- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/clippy_lints/src/doc/doc_paragraphs_missing_punctuation.rs b/clippy_lints/src/doc/doc_paragraphs_missing_punctuation.rs index 7e8acadbd6fa9..a5d6416211035 100644 --- a/clippy_lints/src/doc/doc_paragraphs_missing_punctuation.rs +++ b/clippy_lints/src/doc/doc_paragraphs_missing_punctuation.rs @@ -6,7 +6,7 @@ use rustc_resolve::rustdoc::pulldown_cmark::{Event, Options, Parser, Tag, TagEnd use super::{DOC_PARAGRAPHS_MISSING_PUNCTUATION, Fragments}; -const MSG: &str = "doc comments should end with a terminal punctuation mark"; +const MSG: &str = "doc paragraphs should end with a terminal punctuation mark"; const PUNCTUATION_SUGGESTION: char = '.'; pub fn check(cx: &LateContext<'_>, doc: &str, fragments: Fragments<'_>) { diff --git a/tests/ui/doc/doc_paragraphs_missing_punctuation.stderr b/tests/ui/doc/doc_paragraphs_missing_punctuation.stderr index 75c5edb06f99b..81805eeecfdae 100644 --- a/tests/ui/doc/doc_paragraphs_missing_punctuation.stderr +++ b/tests/ui/doc/doc_paragraphs_missing_punctuation.stderr @@ -1,4 +1,4 @@ -error: doc comments should end with a terminal punctuation mark +error: doc paragraphs should end with a terminal punctuation mark --> tests/ui/doc/doc_paragraphs_missing_punctuation.rs:5:86 | LL | /// Returns the Answer to the Ultimate Question of Life, the Universe, and Everything @@ -7,103 +7,103 @@ LL | /// Returns the Answer to the Ultimate Question of Life, the Universe, and = note: `-D clippy::doc-paragraphs-missing-punctuation` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::doc_paragraphs_missing_punctuation)]` -error: doc comments should end with a terminal punctuation mark +error: doc paragraphs should end with a terminal punctuation mark --> tests/ui/doc/doc_paragraphs_missing_punctuation.rs:11:22 | LL | /// The `Option` type | ^ help: end the doc comment with some punctuation: `.` -error: doc comments should end with a terminal punctuation mark +error: doc paragraphs should end with a terminal punctuation mark --> tests/ui/doc/doc_paragraphs_missing_punctuation.rs:16:17 | LL | /// No value | ^ help: end the doc comment with some punctuation: `.` -error: doc comments should end with a terminal punctuation mark +error: doc paragraphs should end with a terminal punctuation mark --> tests/ui/doc/doc_paragraphs_missing_punctuation.rs:27:41 | LL | /// only the last line triggers the lint | ^ help: end the doc comment with some punctuation: `.` -error: doc comments should end with a terminal punctuation mark +error: doc paragraphs should end with a terminal punctuation mark --> tests/ui/doc/doc_paragraphs_missing_punctuation.rs:40:56 | LL | /// | ^ help: end the doc comment with some punctuation: `.` -error: doc comments should end with a terminal punctuation mark +error: doc paragraphs should end with a terminal punctuation mark --> tests/ui/doc/doc_paragraphs_missing_punctuation.rs:49:26 | LL | /// Here is a snippet | ^ help: end the doc comment with some punctuation: `.` -error: doc comments should end with a terminal punctuation mark +error: doc paragraphs should end with a terminal punctuation mark --> tests/ui/doc/doc_paragraphs_missing_punctuation.rs:72:15 | LL | /// U+0001 | ^ help: end the doc comment with some punctuation: `.` -error: doc comments should end with a terminal punctuation mark +error: doc paragraphs should end with a terminal punctuation mark --> tests/ui/doc/doc_paragraphs_missing_punctuation.rs:79:29 | LL | //! inner attributes too | ^ help: end the doc comment with some punctuation: `.` -error: doc comments should end with a terminal punctuation mark +error: doc paragraphs should end with a terminal punctuation mark --> tests/ui/doc/doc_paragraphs_missing_punctuation.rs:90:47 | LL | /// **But sometimes it is missing a period** | ^ help: end the doc comment with some punctuation: `.` -error: doc comments should end with a terminal punctuation mark +error: doc paragraphs should end with a terminal punctuation mark --> tests/ui/doc/doc_paragraphs_missing_punctuation.rs:95:46 | LL | /// _But sometimes it is missing a period_ | ^ help: end the doc comment with some punctuation: `.` -error: doc comments should end with a terminal punctuation mark +error: doc paragraphs should end with a terminal punctuation mark --> tests/ui/doc/doc_paragraphs_missing_punctuation.rs:104:56 | LL | /// Doc comments can end with an [inline link](#anchor) | ^ help: end the doc comment with some punctuation: `.` -error: doc comments should end with a terminal punctuation mark +error: doc paragraphs should end with a terminal punctuation mark --> tests/ui/doc/doc_paragraphs_missing_punctuation.rs:108:65 | LL | /// Some doc comments contain [link reference definitions][spec] | ^ help: end the doc comment with some punctuation: `.` -error: doc comments should end with a terminal punctuation mark +error: doc paragraphs should end with a terminal punctuation mark --> tests/ui/doc/doc_paragraphs_missing_punctuation.rs:133:57 | LL | /// Doc comments with trailing blank lines are supported | ^ help: end the doc comment with some punctuation: `.` -error: doc comments should end with a terminal punctuation mark +error: doc paragraphs should end with a terminal punctuation mark --> tests/ui/doc/doc_paragraphs_missing_punctuation.rs:139:48 | LL | /// This first paragraph is missing punctuation | ^ help: end the doc comment with some punctuation: `.` -error: doc comments should end with a terminal punctuation mark +error: doc paragraphs should end with a terminal punctuation mark --> tests/ui/doc/doc_paragraphs_missing_punctuation.rs:143:34 | LL | /// And it has multiple sentences | ^ help: end the doc comment with some punctuation: `.` -error: doc comments should end with a terminal punctuation mark +error: doc paragraphs should end with a terminal punctuation mark --> tests/ui/doc/doc_paragraphs_missing_punctuation.rs:146:37 | LL | /// Same for this third and last one | ^ help: end the doc comment with some punctuation: `.` -error: doc comments should end with a terminal punctuation mark +error: doc paragraphs should end with a terminal punctuation mark --> tests/ui/doc/doc_paragraphs_missing_punctuation.rs:153:33 | LL | /// This ends with a code `span` | ^ help: end the doc comment with some punctuation: `.` -error: doc comments should end with a terminal punctuation mark +error: doc paragraphs should end with a terminal punctuation mark --> tests/ui/doc/doc_paragraphs_missing_punctuation.rs:162:27 | LL | * Block doc comments work diff --git a/tests/ui/doc/doc_paragraphs_missing_punctuation_unfixable.stderr b/tests/ui/doc/doc_paragraphs_missing_punctuation_unfixable.stderr index 72cce4b82594e..10f41ddc83b1c 100644 --- a/tests/ui/doc/doc_paragraphs_missing_punctuation_unfixable.stderr +++ b/tests/ui/doc/doc_paragraphs_missing_punctuation_unfixable.stderr @@ -1,4 +1,4 @@ -error: doc comments should end with a terminal punctuation mark +error: doc paragraphs should end with a terminal punctuation mark --> tests/ui/doc/doc_paragraphs_missing_punctuation_unfixable.rs:7:68 | LL | /// Sometimes the doc comment ends with parentheses (like this) @@ -8,7 +8,7 @@ LL | /// Sometimes the doc comment ends with parentheses (like this) = note: `-D clippy::doc-paragraphs-missing-punctuation` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::doc_paragraphs_missing_punctuation)]` -error: doc comments should end with a terminal punctuation mark +error: doc paragraphs should end with a terminal punctuation mark --> tests/ui/doc/doc_paragraphs_missing_punctuation_unfixable.rs:10:41 | LL | /// This comment ends with "a quote" From 5107f34f295463188e75cb23de095a955b48091e Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Thu, 30 Oct 2025 09:58:59 +0100 Subject: [PATCH 13/60] refactor(byte_char_slices): clean-up --- clippy_lints/src/byte_char_slices.rs | 51 +++++++++++++--------------- tests/ui/byte_char_slices.fixed | 1 - tests/ui/byte_char_slices.rs | 1 - tests/ui/byte_char_slices.stderr | 10 +++--- 4 files changed, 28 insertions(+), 35 deletions(-) diff --git a/clippy_lints/src/byte_char_slices.rs b/clippy_lints/src/byte_char_slices.rs index d88c0711b3971..fc9931439e93b 100644 --- a/clippy_lints/src/byte_char_slices.rs +++ b/clippy_lints/src/byte_char_slices.rs @@ -31,8 +31,8 @@ declare_lint_pass!(ByteCharSlice => [BYTE_CHAR_SLICES]); impl EarlyLintPass for ByteCharSlice { fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { - if let Some(slice) = is_byte_char_slices(expr) - && !expr.span.from_expansion() + if !expr.span.from_expansion() + && let Some(slice) = is_byte_char_slices(expr) { span_lint_and_sugg( cx, @@ -47,33 +47,28 @@ impl EarlyLintPass for ByteCharSlice { } } +/// Checks whether the slice is that of byte chars, and if so, builds a byte-string out of it fn is_byte_char_slices(expr: &Expr) -> Option { - if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, expr) = &expr.kind { - match &expr.kind { - ExprKind::Array(members) => { - if members.is_empty() { - return None; - } - - members - .iter() - .map(|member| match &member.kind { - ExprKind::Lit(Lit { - kind: LitKind::Byte, - symbol, - .. - }) => Some(symbol.as_str()), - _ => None, - }) - .map(|maybe_quote| match maybe_quote { - Some("\"") => Some("\\\""), - Some("\\'") => Some("'"), - other => other, - }) - .collect::>() - }, - _ => None, - } + if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, expr) = &expr.kind + && let ExprKind::Array(members) = &expr.kind + && !members.is_empty() + { + members + .iter() + .map(|member| match &member.kind { + ExprKind::Lit(Lit { + kind: LitKind::Byte, + symbol, + .. + }) => Some(symbol.as_str()), + _ => None, + }) + .map(|maybe_quote| match maybe_quote { + Some("\"") => Some("\\\""), + Some("\\'") => Some("'"), + other => other, + }) + .collect::>() } else { None } diff --git a/tests/ui/byte_char_slices.fixed b/tests/ui/byte_char_slices.fixed index b0c1b1f034b4d..87934d6362f75 100644 --- a/tests/ui/byte_char_slices.fixed +++ b/tests/ui/byte_char_slices.fixed @@ -1,4 +1,3 @@ -#![allow(unused)] #![warn(clippy::byte_char_slices)] fn main() { diff --git a/tests/ui/byte_char_slices.rs b/tests/ui/byte_char_slices.rs index 0d6953dda97e7..0de7cf66fda89 100644 --- a/tests/ui/byte_char_slices.rs +++ b/tests/ui/byte_char_slices.rs @@ -1,4 +1,3 @@ -#![allow(unused)] #![warn(clippy::byte_char_slices)] fn main() { diff --git a/tests/ui/byte_char_slices.stderr b/tests/ui/byte_char_slices.stderr index 2556aa9c0f76b..c1b7e4ca2f17d 100644 --- a/tests/ui/byte_char_slices.stderr +++ b/tests/ui/byte_char_slices.stderr @@ -1,5 +1,5 @@ error: can be more succinctly written as a byte str - --> tests/ui/byte_char_slices.rs:5:15 + --> tests/ui/byte_char_slices.rs:4:15 | LL | let bad = &[b'a', b'b', b'c']; | ^^^^^^^^^^^^^^^^^^^ help: try: `b"abc"` @@ -8,25 +8,25 @@ LL | let bad = &[b'a', b'b', b'c']; = help: to override `-D warnings` add `#[allow(clippy::byte_char_slices)]` error: can be more succinctly written as a byte str - --> tests/ui/byte_char_slices.rs:7:18 + --> tests/ui/byte_char_slices.rs:6:18 | LL | let quotes = &[b'"', b'H', b'i']; | ^^^^^^^^^^^^^^^^^^^ help: try: `b"\"Hi"` error: can be more succinctly written as a byte str - --> tests/ui/byte_char_slices.rs:9:18 + --> tests/ui/byte_char_slices.rs:8:18 | LL | let quotes = &[b'\'', b'S', b'u', b'p']; | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `b"'Sup"` error: can be more succinctly written as a byte str - --> tests/ui/byte_char_slices.rs:11:19 + --> tests/ui/byte_char_slices.rs:10:19 | LL | let escapes = &[b'\x42', b'E', b's', b'c']; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `b"\x42Esc"` error: useless use of `vec!` - --> tests/ui/byte_char_slices.rs:15:16 + --> tests/ui/byte_char_slices.rs:14:16 | LL | let good = vec![b'a', b'a']; | ^^^^^^^^^^^^^^^^ help: you can use an array directly: `[b'a', b'a']` From d6936cbd8cf17088858d8e8390f6250492f528cb Mon Sep 17 00:00:00 2001 From: AudaciousAxiom <179637270+AudaciousAxiom@users.noreply.github.com> Date: Fri, 7 Nov 2025 18:03:23 +0100 Subject: [PATCH 14/60] doc_paragraphs_missing_punctuation: update lint metadata --- clippy_lints/src/doc/mod.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/doc/mod.rs b/clippy_lints/src/doc/mod.rs index 45c11f8a82de5..2756446962a6a 100644 --- a/clippy_lints/src/doc/mod.rs +++ b/clippy_lints/src/doc/mod.rs @@ -692,9 +692,9 @@ declare_clippy_lint! { /// /// /// /// It was chosen by a fair dice roll. /// ``` - #[clippy::version = "1.92.0"] + #[clippy::version = "1.93.0"] pub DOC_PARAGRAPHS_MISSING_PUNCTUATION, - nursery, + restriction, "missing terminal punctuation in doc comments" } From a0015b8a92a7841897af2b4b734f016c3c08fc9d Mon Sep 17 00:00:00 2001 From: AudaciousAxiom <179637270+AudaciousAxiom@users.noreply.github.com> Date: Sat, 8 Nov 2025 10:50:29 +0100 Subject: [PATCH 15/60] doc_paragraphs_missing_punctuation: update the lint suggestion help --- .../doc/doc_paragraphs_missing_punctuation.rs | 4 +-- .../doc_paragraphs_missing_punctuation.stderr | 36 +++++++++---------- ...raphs_missing_punctuation_unfixable.stderr | 4 +-- 3 files changed, 22 insertions(+), 22 deletions(-) diff --git a/clippy_lints/src/doc/doc_paragraphs_missing_punctuation.rs b/clippy_lints/src/doc/doc_paragraphs_missing_punctuation.rs index a5d6416211035..a8f7346376728 100644 --- a/clippy_lints/src/doc/doc_paragraphs_missing_punctuation.rs +++ b/clippy_lints/src/doc/doc_paragraphs_missing_punctuation.rs @@ -20,7 +20,7 @@ pub fn check(cx: &LateContext<'_>, doc: &str, fragments: Fragments<'_>) { DOC_PARAGRAPHS_MISSING_PUNCTUATION, span, MSG, - "end the doc comment with some punctuation", + "end the paragraph with some punctuation", PUNCTUATION_SUGGESTION.to_string(), Applicability::MaybeIncorrect, ); @@ -35,7 +35,7 @@ pub fn check(cx: &LateContext<'_>, doc: &str, fragments: Fragments<'_>) { span, MSG, None, - "end the doc comment with some punctuation", + "end the paragraph with some punctuation", ); } }, diff --git a/tests/ui/doc/doc_paragraphs_missing_punctuation.stderr b/tests/ui/doc/doc_paragraphs_missing_punctuation.stderr index 81805eeecfdae..49aa4e8aeb888 100644 --- a/tests/ui/doc/doc_paragraphs_missing_punctuation.stderr +++ b/tests/ui/doc/doc_paragraphs_missing_punctuation.stderr @@ -2,7 +2,7 @@ error: doc paragraphs should end with a terminal punctuation mark --> tests/ui/doc/doc_paragraphs_missing_punctuation.rs:5:86 | LL | /// Returns the Answer to the Ultimate Question of Life, the Universe, and Everything - | ^ help: end the doc comment with some punctuation: `.` + | ^ help: end the paragraph with some punctuation: `.` | = note: `-D clippy::doc-paragraphs-missing-punctuation` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::doc_paragraphs_missing_punctuation)]` @@ -11,103 +11,103 @@ error: doc paragraphs should end with a terminal punctuation mark --> tests/ui/doc/doc_paragraphs_missing_punctuation.rs:11:22 | LL | /// The `Option` type - | ^ help: end the doc comment with some punctuation: `.` + | ^ help: end the paragraph with some punctuation: `.` error: doc paragraphs should end with a terminal punctuation mark --> tests/ui/doc/doc_paragraphs_missing_punctuation.rs:16:17 | LL | /// No value - | ^ help: end the doc comment with some punctuation: `.` + | ^ help: end the paragraph with some punctuation: `.` error: doc paragraphs should end with a terminal punctuation mark --> tests/ui/doc/doc_paragraphs_missing_punctuation.rs:27:41 | LL | /// only the last line triggers the lint - | ^ help: end the doc comment with some punctuation: `.` + | ^ help: end the paragraph with some punctuation: `.` error: doc paragraphs should end with a terminal punctuation mark --> tests/ui/doc/doc_paragraphs_missing_punctuation.rs:40:56 | LL | /// - | ^ help: end the doc comment with some punctuation: `.` + | ^ help: end the paragraph with some punctuation: `.` error: doc paragraphs should end with a terminal punctuation mark --> tests/ui/doc/doc_paragraphs_missing_punctuation.rs:49:26 | LL | /// Here is a snippet - | ^ help: end the doc comment with some punctuation: `.` + | ^ help: end the paragraph with some punctuation: `.` error: doc paragraphs should end with a terminal punctuation mark --> tests/ui/doc/doc_paragraphs_missing_punctuation.rs:72:15 | LL | /// U+0001 - | ^ help: end the doc comment with some punctuation: `.` + | ^ help: end the paragraph with some punctuation: `.` error: doc paragraphs should end with a terminal punctuation mark --> tests/ui/doc/doc_paragraphs_missing_punctuation.rs:79:29 | LL | //! inner attributes too - | ^ help: end the doc comment with some punctuation: `.` + | ^ help: end the paragraph with some punctuation: `.` error: doc paragraphs should end with a terminal punctuation mark --> tests/ui/doc/doc_paragraphs_missing_punctuation.rs:90:47 | LL | /// **But sometimes it is missing a period** - | ^ help: end the doc comment with some punctuation: `.` + | ^ help: end the paragraph with some punctuation: `.` error: doc paragraphs should end with a terminal punctuation mark --> tests/ui/doc/doc_paragraphs_missing_punctuation.rs:95:46 | LL | /// _But sometimes it is missing a period_ - | ^ help: end the doc comment with some punctuation: `.` + | ^ help: end the paragraph with some punctuation: `.` error: doc paragraphs should end with a terminal punctuation mark --> tests/ui/doc/doc_paragraphs_missing_punctuation.rs:104:56 | LL | /// Doc comments can end with an [inline link](#anchor) - | ^ help: end the doc comment with some punctuation: `.` + | ^ help: end the paragraph with some punctuation: `.` error: doc paragraphs should end with a terminal punctuation mark --> tests/ui/doc/doc_paragraphs_missing_punctuation.rs:108:65 | LL | /// Some doc comments contain [link reference definitions][spec] - | ^ help: end the doc comment with some punctuation: `.` + | ^ help: end the paragraph with some punctuation: `.` error: doc paragraphs should end with a terminal punctuation mark --> tests/ui/doc/doc_paragraphs_missing_punctuation.rs:133:57 | LL | /// Doc comments with trailing blank lines are supported - | ^ help: end the doc comment with some punctuation: `.` + | ^ help: end the paragraph with some punctuation: `.` error: doc paragraphs should end with a terminal punctuation mark --> tests/ui/doc/doc_paragraphs_missing_punctuation.rs:139:48 | LL | /// This first paragraph is missing punctuation - | ^ help: end the doc comment with some punctuation: `.` + | ^ help: end the paragraph with some punctuation: `.` error: doc paragraphs should end with a terminal punctuation mark --> tests/ui/doc/doc_paragraphs_missing_punctuation.rs:143:34 | LL | /// And it has multiple sentences - | ^ help: end the doc comment with some punctuation: `.` + | ^ help: end the paragraph with some punctuation: `.` error: doc paragraphs should end with a terminal punctuation mark --> tests/ui/doc/doc_paragraphs_missing_punctuation.rs:146:37 | LL | /// Same for this third and last one - | ^ help: end the doc comment with some punctuation: `.` + | ^ help: end the paragraph with some punctuation: `.` error: doc paragraphs should end with a terminal punctuation mark --> tests/ui/doc/doc_paragraphs_missing_punctuation.rs:153:33 | LL | /// This ends with a code `span` - | ^ help: end the doc comment with some punctuation: `.` + | ^ help: end the paragraph with some punctuation: `.` error: doc paragraphs should end with a terminal punctuation mark --> tests/ui/doc/doc_paragraphs_missing_punctuation.rs:162:27 | LL | * Block doc comments work - | ^ help: end the doc comment with some punctuation: `.` + | ^ help: end the paragraph with some punctuation: `.` error: aborting due to 18 previous errors diff --git a/tests/ui/doc/doc_paragraphs_missing_punctuation_unfixable.stderr b/tests/ui/doc/doc_paragraphs_missing_punctuation_unfixable.stderr index 10f41ddc83b1c..e8587eace2d6c 100644 --- a/tests/ui/doc/doc_paragraphs_missing_punctuation_unfixable.stderr +++ b/tests/ui/doc/doc_paragraphs_missing_punctuation_unfixable.stderr @@ -4,7 +4,7 @@ error: doc paragraphs should end with a terminal punctuation mark LL | /// Sometimes the doc comment ends with parentheses (like this) | ^ | - = help: end the doc comment with some punctuation + = help: end the paragraph with some punctuation = note: `-D clippy::doc-paragraphs-missing-punctuation` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::doc_paragraphs_missing_punctuation)]` @@ -14,7 +14,7 @@ error: doc paragraphs should end with a terminal punctuation mark LL | /// This comment ends with "a quote" | ^ | - = help: end the doc comment with some punctuation + = help: end the paragraph with some punctuation error: aborting due to 2 previous errors From aed57a9e5be3a020a84ec3eaf8707ad60b24a494 Mon Sep 17 00:00:00 2001 From: AudaciousAxiom <179637270+AudaciousAxiom@users.noreply.github.com> Date: Sat, 8 Nov 2025 10:51:58 +0100 Subject: [PATCH 16/60] doc_paragraphs_missing_punctuation: add a missing period in another test --- tests/ui/blanket_clippy_restriction_lints.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ui/blanket_clippy_restriction_lints.rs b/tests/ui/blanket_clippy_restriction_lints.rs index de699309b16d1..1cd45685609f0 100644 --- a/tests/ui/blanket_clippy_restriction_lints.rs +++ b/tests/ui/blanket_clippy_restriction_lints.rs @@ -3,7 +3,7 @@ #![warn(clippy::blanket_clippy_restriction_lints)] -//! Test that the whole restriction group is not enabled +//! Test that the whole restriction group is not enabled. #![warn(clippy::restriction)] //~^ blanket_clippy_restriction_lints #![deny(clippy::restriction)] From a200679d9630a61cef926b072b0f0980c1d24a31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Thu, 13 Nov 2025 10:29:26 +0100 Subject: [PATCH 17/60] Revert "Rollup merge of #146627 - madsmtm:jemalloc-simplify, r=jdonszelmann" This reverts commit 5dc3c19417d0fbfd979c5a1ecd8bebe2d8d9110e, reversing changes made to 11339a0ef5ed586bb7ea4f85a9b7287880caac3a. --- Cargo.toml | 3 +-- src/driver.rs | 36 +++++++++++++++++++++++++++++++++--- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f0f9c05e4330d..fee885d8fa7e9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,7 +31,6 @@ tempfile = { version = "3.20", optional = true } termize = "0.2" color-print = "0.3.4" anstream = "0.6.18" -tikv-jemalloc-sys = { version = "0.6.1", optional = true, features = ['override_allocator_on_supported_platforms'] } [dev-dependencies] cargo_metadata = "0.18.1" @@ -57,7 +56,7 @@ rustc_tools_util = { path = "rustc_tools_util", version = "0.4.2" } [features] integration = ["dep:tempfile"] internal = ["dep:clippy_lints_internal", "dep:tempfile"] -jemalloc = ["dep:tikv-jemalloc-sys"] +jemalloc = [] [package.metadata.rust-analyzer] # This package uses #[feature(rustc_private)] diff --git a/src/driver.rs b/src/driver.rs index a8db5dddf22fc..abc706b7772f1 100644 --- a/src/driver.rs +++ b/src/driver.rs @@ -13,10 +13,10 @@ extern crate rustc_interface; extern crate rustc_session; extern crate rustc_span; -/// See docs in https://github.com/rust-lang/rust/blob/HEAD/compiler/rustc/src/main.rs -/// and https://github.com/rust-lang/rust/pull/146627 for why we need this `use` statement. +// See docs in https://github.com/rust-lang/rust/blob/HEAD/compiler/rustc/src/main.rs +// about jemalloc. #[cfg(feature = "jemalloc")] -use tikv_jemalloc_sys as _; +extern crate tikv_jemalloc_sys as jemalloc_sys; use clippy_utils::sym; use declare_clippy_lint::LintListBuilder; @@ -189,6 +189,36 @@ const BUG_REPORT_URL: &str = "https://github.com/rust-lang/rust-clippy/issues/ne #[expect(clippy::too_many_lines)] pub fn main() { + // See docs in https://github.com/rust-lang/rust/blob/HEAD/compiler/rustc/src/main.rs + // about jemalloc. + #[cfg(feature = "jemalloc")] + { + use std::os::raw::{c_int, c_void}; + + #[used] + static _F1: unsafe extern "C" fn(usize, usize) -> *mut c_void = jemalloc_sys::calloc; + #[used] + static _F2: unsafe extern "C" fn(*mut *mut c_void, usize, usize) -> c_int = jemalloc_sys::posix_memalign; + #[used] + static _F3: unsafe extern "C" fn(usize, usize) -> *mut c_void = jemalloc_sys::aligned_alloc; + #[used] + static _F4: unsafe extern "C" fn(usize) -> *mut c_void = jemalloc_sys::malloc; + #[used] + static _F5: unsafe extern "C" fn(*mut c_void, usize) -> *mut c_void = jemalloc_sys::realloc; + #[used] + static _F6: unsafe extern "C" fn(*mut c_void) = jemalloc_sys::free; + + #[cfg(target_os = "macos")] + { + unsafe extern "C" { + fn _rjem_je_zone_register(); + } + + #[used] + static _F7: unsafe extern "C" fn() = _rjem_je_zone_register; + } + } + let early_dcx = EarlyDiagCtxt::new(ErrorOutputType::default()); rustc_driver::init_rustc_env_logger(&early_dcx); From 2d8211a7047d436d2b9e45c78e23528aa0875bfd Mon Sep 17 00:00:00 2001 From: lukaslueg Date: Thu, 18 Sep 2025 11:41:00 +0200 Subject: [PATCH 18/60] Add `large-error-ignored` config-knob --- CHANGELOG.md | 1 + book/src/lint_configuration.md | 11 +++++++ clippy_config/src/conf.rs | 4 +++ clippy_lints/src/functions/mod.rs | 30 +++++++++++++++++-- clippy_lints/src/functions/result.rs | 30 +++++++++++++++---- tests/ui-toml/result_large_err/clippy.toml | 1 + .../result_large_err/result_large_err.rs | 20 +++++++++++++ .../result_large_err/result_large_err.stderr | 2 +- .../toml_unknown_key/conf_unknown_key.stderr | 3 ++ 9 files changed, 93 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6cb2755be0eec..138e92982af9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7121,6 +7121,7 @@ Released 2018-09-13 [`future-size-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#future-size-threshold [`ignore-interior-mutability`]: https://doc.rust-lang.org/clippy/lint_configuration.html#ignore-interior-mutability [`inherent-impl-lint-scope`]: https://doc.rust-lang.org/clippy/lint_configuration.html#inherent-impl-lint-scope +[`large-error-ignored`]: https://doc.rust-lang.org/clippy/lint_configuration.html#large-error-ignored [`large-error-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#large-error-threshold [`lint-commented-code`]: https://doc.rust-lang.org/clippy/lint_configuration.html#lint-commented-code [`literal-representation-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#literal-representation-threshold diff --git a/book/src/lint_configuration.md b/book/src/lint_configuration.md index 6569bdabf115a..2e185fb3a086e 100644 --- a/book/src/lint_configuration.md +++ b/book/src/lint_configuration.md @@ -681,6 +681,17 @@ Sets the scope ("crate", "file", or "module") in which duplicate inherent `impl` * [`multiple_inherent_impl`](https://rust-lang.github.io/rust-clippy/master/index.html#multiple_inherent_impl) +## `large-error-ignored` +A list of paths to types that should be ignored as overly large `Err`-variants in a +`Result` returned from a function + +**Default Value:** `[]` + +--- +**Affected lints:** +* [`result_large_err`](https://rust-lang.github.io/rust-clippy/master/index.html#result_large_err) + + ## `large-error-threshold` The maximum size of the `Err`-variant in a `Result` returned from a function diff --git a/clippy_config/src/conf.rs b/clippy_config/src/conf.rs index 2a042e6c3d853..4a949732af032 100644 --- a/clippy_config/src/conf.rs +++ b/clippy_config/src/conf.rs @@ -666,6 +666,10 @@ define_Conf! { /// Sets the scope ("crate", "file", or "module") in which duplicate inherent `impl` blocks for the same type are linted. #[lints(multiple_inherent_impl)] inherent_impl_lint_scope: InherentImplLintScope = InherentImplLintScope::Crate, + /// A list of paths to types that should be ignored as overly large `Err`-variants in a + /// `Result` returned from a function + #[lints(result_large_err)] + large_error_ignored: Vec = Vec::default(), /// The maximum size of the `Err`-variant in a `Result` returned from a function #[lints(result_large_err)] large_error_threshold: u64 = 128, diff --git a/clippy_lints/src/functions/mod.rs b/clippy_lints/src/functions/mod.rs index 5a40af4219426..bdc366f6878a0 100644 --- a/clippy_lints/src/functions/mod.rs +++ b/clippy_lints/src/functions/mod.rs @@ -485,6 +485,7 @@ pub struct Functions { too_many_arguments_threshold: u64, too_many_lines_threshold: u64, large_error_threshold: u64, + large_error_ignored: DefIdSet, avoid_breaking_exported_api: bool, /// A set of resolved `def_id` of traits that are configured to allow /// function params renaming. @@ -498,6 +499,11 @@ impl Functions { too_many_arguments_threshold: conf.too_many_arguments_threshold, too_many_lines_threshold: conf.too_many_lines_threshold, large_error_threshold: conf.large_error_threshold, + large_error_ignored: conf + .large_error_ignored + .iter() + .flat_map(|ignored_ty| lookup_path_str(tcx, PathNS::Type, ignored_ty)) + .collect(), avoid_breaking_exported_api: conf.avoid_breaking_exported_api, trait_ids: conf .allow_renamed_params_for @@ -554,12 +560,24 @@ impl<'tcx> LateLintPass<'tcx> for Functions { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) { must_use::check_item(cx, item); - result::check_item(cx, item, self.large_error_threshold, self.msrv); + result::check_item( + cx, + item, + self.large_error_threshold, + &self.large_error_ignored, + self.msrv, + ); } fn check_impl_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::ImplItem<'_>) { must_use::check_impl_item(cx, item); - result::check_impl_item(cx, item, self.large_error_threshold, self.msrv); + result::check_impl_item( + cx, + item, + self.large_error_threshold, + &self.large_error_ignored, + self.msrv, + ); impl_trait_in_params::check_impl_item(cx, item); renamed_function_params::check_impl_item(cx, item, &self.trait_ids); } @@ -568,7 +586,13 @@ impl<'tcx> LateLintPass<'tcx> for Functions { too_many_arguments::check_trait_item(cx, item, self.too_many_arguments_threshold); not_unsafe_ptr_arg_deref::check_trait_item(cx, item); must_use::check_trait_item(cx, item); - result::check_trait_item(cx, item, self.large_error_threshold, self.msrv); + result::check_trait_item( + cx, + item, + self.large_error_threshold, + &self.large_error_ignored, + self.msrv, + ); 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/result.rs b/clippy_lints/src/functions/result.rs index fb80cc1a63a30..04e15a1d8a0e9 100644 --- a/clippy_lints/src/functions/result.rs +++ b/clippy_lints/src/functions/result.rs @@ -4,6 +4,7 @@ use rustc_errors::Diag; use rustc_hir as hir; use rustc_lint::{LateContext, LintContext}; use rustc_middle::ty::{self, Ty}; +use rustc_span::def_id::DefIdSet; use rustc_span::{Span, sym}; use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_then}; @@ -35,7 +36,13 @@ fn result_err_ty<'tcx>( } } -pub(super) fn check_item<'tcx>(cx: &LateContext<'tcx>, item: &hir::Item<'tcx>, large_err_threshold: u64, msrv: Msrv) { +pub(super) fn check_item<'tcx>( + cx: &LateContext<'tcx>, + item: &hir::Item<'tcx>, + large_err_threshold: u64, + large_err_ignored: &DefIdSet, + msrv: Msrv, +) { if let hir::ItemKind::Fn { ref sig, .. } = item.kind && let Some((hir_ty, err_ty)) = result_err_ty(cx, sig.decl, item.owner_id.def_id, item.span) { @@ -43,7 +50,7 @@ pub(super) fn check_item<'tcx>(cx: &LateContext<'tcx>, item: &hir::Item<'tcx>, l let fn_header_span = item.span.with_hi(sig.decl.output.span().hi()); check_result_unit_err(cx, err_ty, fn_header_span, msrv); } - check_result_large_err(cx, err_ty, hir_ty.span, large_err_threshold); + check_result_large_err(cx, err_ty, hir_ty.span, large_err_threshold, large_err_ignored); } } @@ -51,6 +58,7 @@ pub(super) fn check_impl_item<'tcx>( cx: &LateContext<'tcx>, item: &hir::ImplItem<'tcx>, large_err_threshold: u64, + large_err_ignored: &DefIdSet, msrv: Msrv, ) { // Don't lint if method is a trait's implementation, we can't do anything about those @@ -62,7 +70,7 @@ pub(super) fn check_impl_item<'tcx>( let fn_header_span = item.span.with_hi(sig.decl.output.span().hi()); check_result_unit_err(cx, err_ty, fn_header_span, msrv); } - check_result_large_err(cx, err_ty, hir_ty.span, large_err_threshold); + check_result_large_err(cx, err_ty, hir_ty.span, large_err_threshold, large_err_ignored); } } @@ -70,6 +78,7 @@ pub(super) fn check_trait_item<'tcx>( cx: &LateContext<'tcx>, item: &hir::TraitItem<'tcx>, large_err_threshold: u64, + large_err_ignored: &DefIdSet, msrv: Msrv, ) { if let hir::TraitItemKind::Fn(ref sig, _) = item.kind { @@ -78,7 +87,7 @@ pub(super) fn check_trait_item<'tcx>( if cx.effective_visibilities.is_exported(item.owner_id.def_id) { check_result_unit_err(cx, err_ty, fn_header_span, msrv); } - check_result_large_err(cx, err_ty, hir_ty.span, large_err_threshold); + check_result_large_err(cx, err_ty, hir_ty.span, large_err_threshold, large_err_ignored); } } } @@ -96,7 +105,18 @@ fn check_result_unit_err(cx: &LateContext<'_>, err_ty: Ty<'_>, fn_header_span: S } } -fn check_result_large_err<'tcx>(cx: &LateContext<'tcx>, err_ty: Ty<'tcx>, hir_ty_span: Span, large_err_threshold: u64) { +fn check_result_large_err<'tcx>( + cx: &LateContext<'tcx>, + err_ty: Ty<'tcx>, + hir_ty_span: Span, + large_err_threshold: u64, + large_err_ignored: &DefIdSet, +) { + if let ty::Adt(adt, _) = err_ty.kind() + && large_err_ignored.contains(&adt.did()) + { + return; + } if let ty::Adt(adt, subst) = err_ty.kind() && let Some(local_def_id) = adt.did().as_local() && let hir::Node::Item(item) = cx.tcx.hir_node_by_def_id(local_def_id) diff --git a/tests/ui-toml/result_large_err/clippy.toml b/tests/ui-toml/result_large_err/clippy.toml index df505ed9672a6..80eeac0f9870d 100644 --- a/tests/ui-toml/result_large_err/clippy.toml +++ b/tests/ui-toml/result_large_err/clippy.toml @@ -1 +1,2 @@ large-error-threshold = 512 +large-error-ignored = ["result_large_err::IgnoredError", "result_large_err::IgnoredErrorEnum"] diff --git a/tests/ui-toml/result_large_err/result_large_err.rs b/tests/ui-toml/result_large_err/result_large_err.rs index dea4d61a96bfb..170f37db7593e 100644 --- a/tests/ui-toml/result_large_err/result_large_err.rs +++ b/tests/ui-toml/result_large_err/result_large_err.rs @@ -1,4 +1,6 @@ +//@compile-flags: --crate-name result_large_err #![warn(clippy::result_large_err)] +#![allow(clippy::large_enum_variant)] fn f() -> Result<(), [u8; 511]> { todo!() @@ -7,4 +9,22 @@ fn f2() -> Result<(), [u8; 512]> { //~^ ERROR: the `Err`-variant returned from this function is very large todo!() } + +struct IgnoredError { + inner: [u8; 512], +} + +fn f3() -> Result<(), IgnoredError> { + todo!() +} + +enum IgnoredErrorEnum { + V1, + V2 { inner: [u8; 512] }, +} + +fn f4() -> Result<(), IgnoredErrorEnum> { + todo!() +} + fn main() {} diff --git a/tests/ui-toml/result_large_err/result_large_err.stderr b/tests/ui-toml/result_large_err/result_large_err.stderr index 656ce7ab7f2fa..7e5954f885b82 100644 --- a/tests/ui-toml/result_large_err/result_large_err.stderr +++ b/tests/ui-toml/result_large_err/result_large_err.stderr @@ -1,5 +1,5 @@ error: the `Err`-variant returned from this function is very large - --> tests/ui-toml/result_large_err/result_large_err.rs:6:12 + --> tests/ui-toml/result_large_err/result_large_err.rs:8:12 | LL | fn f2() -> Result<(), [u8; 512]> { | ^^^^^^^^^^^^^^^^^^^^^ the `Err`-variant is at least 512 bytes diff --git a/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr b/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr index 2d9503c5ac53a..d5040f4a39bfd 100644 --- a/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr +++ b/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr @@ -50,6 +50,7 @@ error: error reading Clippy's configuration file: unknown field `foobar`, expect future-size-threshold ignore-interior-mutability inherent-impl-lint-scope + large-error-ignored large-error-threshold lint-commented-code literal-representation-threshold @@ -147,6 +148,7 @@ error: error reading Clippy's configuration file: unknown field `barfoo`, expect future-size-threshold ignore-interior-mutability inherent-impl-lint-scope + large-error-ignored large-error-threshold lint-commented-code literal-representation-threshold @@ -244,6 +246,7 @@ error: error reading Clippy's configuration file: unknown field `allow_mixed_uni future-size-threshold ignore-interior-mutability inherent-impl-lint-scope + large-error-ignored large-error-threshold lint-commented-code literal-representation-threshold From 757bad7206ead2559965bf9f31fff1e5b40f3e2e Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Wed, 13 Aug 2025 21:16:29 +0200 Subject: [PATCH 19/60] fix `equatable_if_let`: FP in const context --- clippy_lints/src/equatable_if_let.rs | 12 ++++++++++- tests/ui/equatable_if_let.fixed | 21 +++++++++++++++++++ tests/ui/equatable_if_let.rs | 21 +++++++++++++++++++ tests/ui/equatable_if_let.stderr | 14 ++++++++++++- tests/ui/equatable_if_let_const_cmp.fixed | 24 ++++++++++++++++++++++ tests/ui/equatable_if_let_const_cmp.rs | 24 ++++++++++++++++++++++ tests/ui/equatable_if_let_const_cmp.stderr | 17 +++++++++++++++ 7 files changed, 131 insertions(+), 2 deletions(-) create mode 100644 tests/ui/equatable_if_let_const_cmp.fixed create mode 100644 tests/ui/equatable_if_let_const_cmp.rs create mode 100644 tests/ui/equatable_if_let_const_cmp.stderr diff --git a/clippy_lints/src/equatable_if_let.rs b/clippy_lints/src/equatable_if_let.rs index c3fc09343dbfe..d872d5628b2cd 100644 --- a/clippy_lints/src/equatable_if_let.rs +++ b/clippy_lints/src/equatable_if_let.rs @@ -1,4 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::is_in_const_context; use clippy_utils::source::snippet_with_context; use clippy_utils::ty::implements_trait; use rustc_errors::Applicability; @@ -110,7 +111,16 @@ impl<'tcx> LateLintPass<'tcx> for PatternEquality { let pat_ty = cx.typeck_results().pat_ty(let_expr.pat); let mut applicability = Applicability::MachineApplicable; - if is_structural_partial_eq(cx, exp_ty, pat_ty) && !contains_type_mismatch(cx, let_expr.pat) { + if is_structural_partial_eq(cx, exp_ty, pat_ty) + && !contains_type_mismatch(cx, let_expr.pat) + // Calls to trait methods (`PartialEq::eq` in this case) aren't stable yet. We could _technically_ + // try looking at whether: + // 1) features `const_trait_impl` and `const_cmp` are enabled + // 2) implementation of `PartialEq for ExpTy` has `fn eq` that is `const` + // + // but that didn't quite work out (see #15482), so we just reject outright in this case + && !is_in_const_context(cx) + { let pat_str = match let_expr.pat.kind { PatKind::Struct(..) => format!( "({})", diff --git a/tests/ui/equatable_if_let.fixed b/tests/ui/equatable_if_let.fixed index 58fbad64a78da..867ba1904bdae 100644 --- a/tests/ui/equatable_if_let.fixed +++ b/tests/ui/equatable_if_let.fixed @@ -139,3 +139,24 @@ mod issue8710 { } } } + +// PartialEq is not stable in consts yet +fn issue15376() { + enum NonConstEq { + A, + B, + } + impl PartialEq for NonConstEq { + fn eq(&self, _other: &Self) -> bool { + true + } + } + + const N: NonConstEq = NonConstEq::A; + + // `impl PartialEq` is not const, suggest `matches!` + const _: u32 = if matches!(N, NonConstEq::A) { 0 } else { 1 }; + //~^ ERROR: this pattern matching can be expressed using `matches!` + const _: u32 = if matches!(Some(N), Some(NonConstEq::A)) { 0 } else { 1 }; + //~^ ERROR: this pattern matching can be expressed using `matches!` +} diff --git a/tests/ui/equatable_if_let.rs b/tests/ui/equatable_if_let.rs index cca97c76b5094..6690ca24339d5 100644 --- a/tests/ui/equatable_if_let.rs +++ b/tests/ui/equatable_if_let.rs @@ -139,3 +139,24 @@ mod issue8710 { } } } + +// PartialEq is not stable in consts yet +fn issue15376() { + enum NonConstEq { + A, + B, + } + impl PartialEq for NonConstEq { + fn eq(&self, _other: &Self) -> bool { + true + } + } + + const N: NonConstEq = NonConstEq::A; + + // `impl PartialEq` is not const, suggest `matches!` + const _: u32 = if let NonConstEq::A = N { 0 } else { 1 }; + //~^ ERROR: this pattern matching can be expressed using `matches!` + const _: u32 = if let Some(NonConstEq::A) = Some(N) { 0 } else { 1 }; + //~^ ERROR: this pattern matching can be expressed using `matches!` +} diff --git a/tests/ui/equatable_if_let.stderr b/tests/ui/equatable_if_let.stderr index dd1832ad68b28..75242e1c527e2 100644 --- a/tests/ui/equatable_if_let.stderr +++ b/tests/ui/equatable_if_let.stderr @@ -103,5 +103,17 @@ error: this pattern matching can be expressed using `matches!` LL | if let Some(MyEnum::B) = get_enum() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `matches!(get_enum(), Some(MyEnum::B))` -error: aborting due to 17 previous errors +error: this pattern matching can be expressed using `matches!` + --> tests/ui/equatable_if_let.rs:158:23 + | +LL | const _: u32 = if let NonConstEq::A = N { 0 } else { 1 }; + | ^^^^^^^^^^^^^^^^^^^^^ help: try: `matches!(N, NonConstEq::A)` + +error: this pattern matching can be expressed using `matches!` + --> tests/ui/equatable_if_let.rs:160:23 + | +LL | const _: u32 = if let Some(NonConstEq::A) = Some(N) { 0 } else { 1 }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `matches!(Some(N), Some(NonConstEq::A))` + +error: aborting due to 19 previous errors diff --git a/tests/ui/equatable_if_let_const_cmp.fixed b/tests/ui/equatable_if_let_const_cmp.fixed new file mode 100644 index 0000000000000..51dab25ed6d83 --- /dev/null +++ b/tests/ui/equatable_if_let_const_cmp.fixed @@ -0,0 +1,24 @@ +#![warn(clippy::equatable_if_let)] +#![allow(clippy::eq_op)] +#![feature(const_trait_impl, const_cmp)] + +fn issue15376() { + enum ConstEq { + A, + B, + } + impl const PartialEq for ConstEq { + fn eq(&self, _other: &Self) -> bool { + true + } + } + + const C: ConstEq = ConstEq::A; + + // `impl PartialEq` is const... but we still suggest `matches!` for now + // TODO: detect this and suggest `=` + const _: u32 = if matches!(C, ConstEq::A) { 0 } else { 1 }; + //~^ ERROR: this pattern matching can be expressed using `matches!` + const _: u32 = if matches!(Some(C), Some(ConstEq::A)) { 0 } else { 1 }; + //~^ ERROR: this pattern matching can be expressed using `matches!` +} diff --git a/tests/ui/equatable_if_let_const_cmp.rs b/tests/ui/equatable_if_let_const_cmp.rs new file mode 100644 index 0000000000000..b402e05c53de9 --- /dev/null +++ b/tests/ui/equatable_if_let_const_cmp.rs @@ -0,0 +1,24 @@ +#![warn(clippy::equatable_if_let)] +#![allow(clippy::eq_op)] +#![feature(const_trait_impl, const_cmp)] + +fn issue15376() { + enum ConstEq { + A, + B, + } + impl const PartialEq for ConstEq { + fn eq(&self, _other: &Self) -> bool { + true + } + } + + const C: ConstEq = ConstEq::A; + + // `impl PartialEq` is const... but we still suggest `matches!` for now + // TODO: detect this and suggest `=` + const _: u32 = if let ConstEq::A = C { 0 } else { 1 }; + //~^ ERROR: this pattern matching can be expressed using `matches!` + const _: u32 = if let Some(ConstEq::A) = Some(C) { 0 } else { 1 }; + //~^ ERROR: this pattern matching can be expressed using `matches!` +} diff --git a/tests/ui/equatable_if_let_const_cmp.stderr b/tests/ui/equatable_if_let_const_cmp.stderr new file mode 100644 index 0000000000000..ec72e42d64305 --- /dev/null +++ b/tests/ui/equatable_if_let_const_cmp.stderr @@ -0,0 +1,17 @@ +error: this pattern matching can be expressed using `matches!` + --> tests/ui/equatable_if_let_const_cmp.rs:20:23 + | +LL | const _: u32 = if let ConstEq::A = C { 0 } else { 1 }; + | ^^^^^^^^^^^^^^^^^^ help: try: `matches!(C, ConstEq::A)` + | + = note: `-D clippy::equatable-if-let` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::equatable_if_let)]` + +error: this pattern matching can be expressed using `matches!` + --> tests/ui/equatable_if_let_const_cmp.rs:22:23 + | +LL | const _: u32 = if let Some(ConstEq::A) = Some(C) { 0 } else { 1 }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `matches!(Some(C), Some(ConstEq::A))` + +error: aborting due to 2 previous errors + From 386451c16a55966ee19fc217ab2bd82e09125c1b Mon Sep 17 00:00:00 2001 From: Yotam Ofek Date: Sun, 12 Oct 2025 20:46:14 +0300 Subject: [PATCH 20/60] Fix `sliced_string_as_bytes` FP with a `RangeFull` --- clippy_lints/src/methods/sliced_string_as_bytes.rs | 10 ++++++++-- tests/ui/sliced_string_as_bytes.fixed | 6 ++++++ tests/ui/sliced_string_as_bytes.rs | 6 ++++++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/methods/sliced_string_as_bytes.rs b/clippy_lints/src/methods/sliced_string_as_bytes.rs index 4aff194923a6c..fb124f3605b94 100644 --- a/clippy_lints/src/methods/sliced_string_as_bytes.rs +++ b/clippy_lints/src/methods/sliced_string_as_bytes.rs @@ -1,15 +1,21 @@ use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::higher; use clippy_utils::res::MaybeDef; use clippy_utils::source::snippet_with_applicability; use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind, LangItem, is_range_literal}; +use rustc_hir::{Expr, ExprKind, LangItem}; use rustc_lint::LateContext; use super::SLICED_STRING_AS_BYTES; +/// Checks if `index` is any type of range except `RangeFull` (i.e. `..`) +fn is_bounded_range_literal(cx: &LateContext<'_>, index: &Expr<'_>) -> bool { + higher::Range::hir(cx, index).is_some_and(|range| Option::or(range.start, range.end).is_some()) +} + pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>) { if let ExprKind::Index(indexed, index, _) = recv.kind - && is_range_literal(index) + && is_bounded_range_literal(cx, index) && let ty = cx.typeck_results().expr_ty(indexed).peel_refs() && (ty.is_str() || ty.is_lang_item(cx, LangItem::String)) { diff --git a/tests/ui/sliced_string_as_bytes.fixed b/tests/ui/sliced_string_as_bytes.fixed index 16c0daff78fdc..b5576188b83f6 100644 --- a/tests/ui/sliced_string_as_bytes.fixed +++ b/tests/ui/sliced_string_as_bytes.fixed @@ -32,6 +32,12 @@ fn main() { let bytes = &"consectetur adipiscing".as_bytes()[..=5]; //~^ sliced_string_as_bytes + // this lint is a perf lint meant to catch utf-8 alignment checks. + // while the slicing here *is* redundant, it's more like a needless borrow, and shouldn't affect + // perf + let bytes = s[..].as_bytes(); + let bytes = string[..].as_bytes(); + let f = Foo; let bytes = f[0..4].as_bytes(); } diff --git a/tests/ui/sliced_string_as_bytes.rs b/tests/ui/sliced_string_as_bytes.rs index 67985ae5b9842..58b8d92902942 100644 --- a/tests/ui/sliced_string_as_bytes.rs +++ b/tests/ui/sliced_string_as_bytes.rs @@ -32,6 +32,12 @@ fn main() { let bytes = "consectetur adipiscing"[..=5].as_bytes(); //~^ sliced_string_as_bytes + // this lint is a perf lint meant to catch utf-8 alignment checks. + // while the slicing here *is* redundant, it's more like a needless borrow, and shouldn't affect + // perf + let bytes = s[..].as_bytes(); + let bytes = string[..].as_bytes(); + let f = Foo; let bytes = f[0..4].as_bytes(); } From f279a0bef78d20df26514b264a6284400f6e456a Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Sat, 25 Oct 2025 21:24:29 +0200 Subject: [PATCH 21/60] rename `tests/ui/useless_vec{,_unfixable}.rs` will improve the diff for the next commit --- tests/ui/{useless_vec.rs => useless_vec_unfixable.rs} | 0 tests/ui/{useless_vec.stderr => useless_vec_unfixable.stderr} | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename tests/ui/{useless_vec.rs => useless_vec_unfixable.rs} (100%) rename tests/ui/{useless_vec.stderr => useless_vec_unfixable.stderr} (92%) diff --git a/tests/ui/useless_vec.rs b/tests/ui/useless_vec_unfixable.rs similarity index 100% rename from tests/ui/useless_vec.rs rename to tests/ui/useless_vec_unfixable.rs diff --git a/tests/ui/useless_vec.stderr b/tests/ui/useless_vec_unfixable.stderr similarity index 92% rename from tests/ui/useless_vec.stderr rename to tests/ui/useless_vec_unfixable.stderr index e47364fb06d3b..41a172bd58d1b 100644 --- a/tests/ui/useless_vec.stderr +++ b/tests/ui/useless_vec_unfixable.stderr @@ -1,5 +1,5 @@ error: useless use of `vec!` - --> tests/ui/useless_vec.rs:8:26 + --> tests/ui/useless_vec_unfixable.rs:8:26 | LL | let _some_variable = vec![ | __________________________^ From 91779ae78f152399714360513a7162c1acde62c1 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Sat, 25 Oct 2025 16:20:37 +0200 Subject: [PATCH 22/60] rename lint files to match lint name --- clippy_lints/src/declared_lints.rs | 2 +- clippy_lints/src/lib.rs | 4 +- clippy_lints/src/ptr/ptr_arg.rs | 2 +- clippy_lints/src/{vec.rs => useless_vec.rs} | 0 tests/ui/{vec.fixed => useless_vec.fixed} | 0 tests/ui/{vec.rs => useless_vec.rs} | 0 tests/ui/{vec.stderr => useless_vec.stderr} | 44 ++++++++++----------- 7 files changed, 26 insertions(+), 26 deletions(-) rename clippy_lints/src/{vec.rs => useless_vec.rs} (100%) rename tests/ui/{vec.fixed => useless_vec.fixed} (100%) rename tests/ui/{vec.rs => useless_vec.rs} (100%) rename tests/ui/{vec.stderr => useless_vec.stderr} (82%) diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index a754eea311651..b251f27a4dcf7 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -777,7 +777,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[ crate::use_self::USE_SELF_INFO, crate::useless_concat::USELESS_CONCAT_INFO, crate::useless_conversion::USELESS_CONVERSION_INFO, - crate::vec::USELESS_VEC_INFO, + crate::useless_vec::USELESS_VEC_INFO, crate::vec_init_then_push::VEC_INIT_THEN_PUSH_INFO, crate::visibility::NEEDLESS_PUB_SELF_INFO, crate::visibility::PUB_WITHOUT_SHORTHAND_INFO, diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 4542105d3277a..230d83dacc959 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -386,7 +386,7 @@ mod upper_case_acronyms; mod use_self; mod useless_concat; mod useless_conversion; -mod vec; +mod useless_vec; mod vec_init_then_push; mod visibility; mod volatile_composites; @@ -592,7 +592,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co Box::new(move |_| Box::new(transmute::Transmute::new(conf))), Box::new(move |_| Box::new(cognitive_complexity::CognitiveComplexity::new(conf))), Box::new(move |_| Box::new(escape::BoxedLocal::new(conf))), - Box::new(move |_| Box::new(vec::UselessVec::new(conf))), + Box::new(move |_| Box::new(useless_vec::UselessVec::new(conf))), Box::new(move |_| Box::new(panic_unimplemented::PanicUnimplemented::new(conf))), Box::new(|_| Box::new(strings::StringLitAsBytes)), Box::new(|_| Box::new(derive::Derive)), diff --git a/clippy_lints/src/ptr/ptr_arg.rs b/clippy_lints/src/ptr/ptr_arg.rs index fd9230f00a8bd..332acb746a7a8 100644 --- a/clippy_lints/src/ptr/ptr_arg.rs +++ b/clippy_lints/src/ptr/ptr_arg.rs @@ -23,7 +23,7 @@ use rustc_trait_selection::infer::InferCtxtExt as _; use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _; use std::{fmt, iter}; -use crate::vec::is_allowed_vec_method; +use crate::useless_vec::is_allowed_vec_method; pub(super) fn check_body<'tcx>( cx: &LateContext<'tcx>, diff --git a/clippy_lints/src/vec.rs b/clippy_lints/src/useless_vec.rs similarity index 100% rename from clippy_lints/src/vec.rs rename to clippy_lints/src/useless_vec.rs diff --git a/tests/ui/vec.fixed b/tests/ui/useless_vec.fixed similarity index 100% rename from tests/ui/vec.fixed rename to tests/ui/useless_vec.fixed diff --git a/tests/ui/vec.rs b/tests/ui/useless_vec.rs similarity index 100% rename from tests/ui/vec.rs rename to tests/ui/useless_vec.rs diff --git a/tests/ui/vec.stderr b/tests/ui/useless_vec.stderr similarity index 82% rename from tests/ui/vec.stderr rename to tests/ui/useless_vec.stderr index d16c8a8944a24..25be94072e2d8 100644 --- a/tests/ui/vec.stderr +++ b/tests/ui/useless_vec.stderr @@ -1,5 +1,5 @@ error: useless use of `vec!` - --> tests/ui/vec.rs:30:14 + --> tests/ui/useless_vec.rs:30:14 | LL | on_slice(&vec![]); | ^^^^^^^ help: you can use a slice directly: `&[]` @@ -8,127 +8,127 @@ LL | on_slice(&vec![]); = help: to override `-D warnings` add `#[allow(clippy::useless_vec)]` error: useless use of `vec!` - --> tests/ui/vec.rs:33:18 + --> tests/ui/useless_vec.rs:33:18 | LL | on_mut_slice(&mut vec![]); | ^^^^^^^^^^^ help: you can use a slice directly: `&mut []` error: useless use of `vec!` - --> tests/ui/vec.rs:36:14 + --> tests/ui/useless_vec.rs:36:14 | LL | on_slice(&vec![1, 2]); | ^^^^^^^^^^^ help: you can use a slice directly: `&[1, 2]` error: useless use of `vec!` - --> tests/ui/vec.rs:39:18 + --> tests/ui/useless_vec.rs:39:18 | LL | on_mut_slice(&mut vec![1, 2]); | ^^^^^^^^^^^^^^^ help: you can use a slice directly: `&mut [1, 2]` error: useless use of `vec!` - --> tests/ui/vec.rs:42:14 + --> tests/ui/useless_vec.rs:42:14 | LL | on_slice(&vec![1, 2]); | ^^^^^^^^^^^ help: you can use a slice directly: `&[1, 2]` error: useless use of `vec!` - --> tests/ui/vec.rs:45:18 + --> tests/ui/useless_vec.rs:45:18 | LL | on_mut_slice(&mut vec![1, 2]); | ^^^^^^^^^^^^^^^ help: you can use a slice directly: `&mut [1, 2]` error: useless use of `vec!` - --> tests/ui/vec.rs:48:14 + --> tests/ui/useless_vec.rs:48:14 | LL | on_slice(&vec!(1, 2)); | ^^^^^^^^^^^ help: you can use a slice directly: `&[1, 2]` error: useless use of `vec!` - --> tests/ui/vec.rs:51:18 + --> tests/ui/useless_vec.rs:51:18 | LL | on_mut_slice(&mut vec![1, 2]); | ^^^^^^^^^^^^^^^ help: you can use a slice directly: `&mut [1, 2]` error: useless use of `vec!` - --> tests/ui/vec.rs:54:14 + --> tests/ui/useless_vec.rs:54:14 | LL | on_slice(&vec![1; 2]); | ^^^^^^^^^^^ help: you can use a slice directly: `&[1; 2]` error: useless use of `vec!` - --> tests/ui/vec.rs:57:18 + --> tests/ui/useless_vec.rs:57:18 | LL | on_mut_slice(&mut vec![1; 2]); | ^^^^^^^^^^^^^^^ help: you can use a slice directly: `&mut [1; 2]` error: useless use of `vec!` - --> tests/ui/vec.rs:84:19 + --> tests/ui/useless_vec.rs:84:19 | LL | let _x: i32 = vec![1, 2, 3].iter().sum(); | ^^^^^^^^^^^^^ help: you can use an array directly: `[1, 2, 3]` error: useless use of `vec!` - --> tests/ui/vec.rs:88:17 + --> tests/ui/useless_vec.rs:88:17 | LL | let mut x = vec![1, 2, 3]; | ^^^^^^^^^^^^^ help: you can use an array directly: `[1, 2, 3]` error: useless use of `vec!` - --> tests/ui/vec.rs:95:22 + --> tests/ui/useless_vec.rs:95:22 | LL | let _x: &[i32] = &vec![1, 2, 3]; | ^^^^^^^^^^^^^^ help: you can use a slice directly: `&[1, 2, 3]` error: useless use of `vec!` - --> tests/ui/vec.rs:98:14 + --> tests/ui/useless_vec.rs:98:14 | LL | for _ in vec![1, 2, 3] {} | ^^^^^^^^^^^^^ help: you can use an array directly: `[1, 2, 3]` error: useless use of `vec!` - --> tests/ui/vec.rs:138:20 + --> tests/ui/useless_vec.rs:138:20 | LL | for _string in vec![repro!(true), repro!(null)] { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: you can use an array directly: `[repro!(true), repro!(null)]` error: useless use of `vec!` - --> tests/ui/vec.rs:156:18 + --> tests/ui/useless_vec.rs:156:18 | LL | in_macro!(1, vec![1, 2], vec![1; 2]); | ^^^^^^^^^^ help: you can use an array directly: `[1, 2]` error: useless use of `vec!` - --> tests/ui/vec.rs:156:30 + --> tests/ui/useless_vec.rs:156:30 | LL | in_macro!(1, vec![1, 2], vec![1; 2]); | ^^^^^^^^^^ help: you can use an array directly: `[1; 2]` error: useless use of `vec!` - --> tests/ui/vec.rs:177:14 + --> tests/ui/useless_vec.rs:177:14 | LL | for a in vec![1, 2, 3] { | ^^^^^^^^^^^^^ help: you can use an array directly: `[1, 2, 3]` error: useless use of `vec!` - --> tests/ui/vec.rs:182:14 + --> tests/ui/useless_vec.rs:182:14 | LL | for a in vec![String::new(), String::new()] { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: you can use an array directly: `[String::new(), String::new()]` error: useless use of `vec!` - --> tests/ui/vec.rs:215:33 + --> tests/ui/useless_vec.rs:215:33 | LL | this_macro_doesnt_need_vec!(vec![1]); | ^^^^^^^ help: you can use an array directly: `[1]` error: useless use of `vec!` - --> tests/ui/vec.rs:242:14 + --> tests/ui/useless_vec.rs:242:14 | LL | for a in &(vec![1, 2]) {} | ^^^^^^^^^^^^^ help: you can use a slice directly: `&[1, 2]` error: useless use of `vec!` - --> tests/ui/vec.rs:250:13 + --> tests/ui/useless_vec.rs:250:13 | LL | let v = &vec![]; | ^^^^^^^ help: you can use a slice directly: `&[]` From bbb251a25f3fdf362e800e60f27a0e7783269d7c Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Sat, 25 Oct 2025 16:37:22 +0200 Subject: [PATCH 23/60] clean-up - move `is_allowed_vec_method` (a stripped-down version of it, anyway, as the function doesn't make sense as is out of context) to utils, as it's shared between `useless_vec` and `ptr_arg` - add another test for non-standard macro brace case - rm unneeded `allow`s - rm duplicated tests - add comments to some tests --- clippy_lints/src/ptr/ptr_arg.rs | 6 +-- clippy_lints/src/useless_vec.rs | 35 +++++++------- clippy_utils/src/lib.rs | 3 ++ tests/ui/useless_vec.fixed | 38 ++++++++------- tests/ui/useless_vec.rs | 38 ++++++++------- tests/ui/useless_vec.stderr | 66 +++++++++++---------------- tests/ui/useless_vec_unfixable.rs | 1 - tests/ui/useless_vec_unfixable.stderr | 2 +- 8 files changed, 85 insertions(+), 104 deletions(-) diff --git a/clippy_lints/src/ptr/ptr_arg.rs b/clippy_lints/src/ptr/ptr_arg.rs index 332acb746a7a8..4bfff64b1bd48 100644 --- a/clippy_lints/src/ptr/ptr_arg.rs +++ b/clippy_lints/src/ptr/ptr_arg.rs @@ -2,7 +2,7 @@ use super::PTR_ARG; use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::res::MaybeResPath; use clippy_utils::source::SpanRangeExt; -use clippy_utils::{get_expr_use_or_unification_node, is_lint_allowed, sym}; +use clippy_utils::{VEC_METHODS_SHADOWING_SLICE_METHODS, get_expr_use_or_unification_node, is_lint_allowed, sym}; use hir::LifetimeKind; use rustc_abi::ExternAbi; use rustc_errors::Applicability; @@ -23,8 +23,6 @@ use rustc_trait_selection::infer::InferCtxtExt as _; use rustc_trait_selection::traits::query::evaluate_obligation::InferCtxtExt as _; use std::{fmt, iter}; -use crate::useless_vec::is_allowed_vec_method; - pub(super) fn check_body<'tcx>( cx: &LateContext<'tcx>, body: &Body<'tcx>, @@ -383,7 +381,7 @@ fn check_ptr_arg_usage<'tcx>(cx: &LateContext<'tcx>, body: &Body<'tcx>, args: &[ // Some methods exist on both `[T]` and `Vec`, such as `len`, where the receiver type // doesn't coerce to a slice and our adjusted type check below isn't enough, // but it would still be valid to call with a slice - if is_allowed_vec_method(use_expr) { + if VEC_METHODS_SHADOWING_SLICE_METHODS.contains(&name) { return; } } diff --git a/clippy_lints/src/useless_vec.rs b/clippy_lints/src/useless_vec.rs index b87db836869d6..28c339ce2b7d3 100644 --- a/clippy_lints/src/useless_vec.rs +++ b/clippy_lints/src/useless_vec.rs @@ -10,7 +10,7 @@ use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::source::SpanRangeExt; use clippy_utils::ty::is_copy; use clippy_utils::visitors::for_each_local_use_after_expr; -use clippy_utils::{get_parent_expr, higher, is_in_test, span_contains_comment, sym}; +use clippy_utils::{VEC_METHODS_SHADOWING_SLICE_METHODS, get_parent_expr, higher, is_in_test, span_contains_comment}; use rustc_errors::Applicability; use rustc_hir::{BorrowKind, Expr, ExprKind, HirId, LetStmt, Mutability, Node, Pat, PatKind}; use rustc_lint::{LateContext, LateLintPass}; @@ -123,8 +123,16 @@ impl UselessVec { // allow indexing into a vec and some set of allowed method calls that exist on slices, too if let Some(parent) = get_parent_expr(cx, expr) && (adjusts_to_slice(cx, expr) - || matches!(parent.kind, ExprKind::Index(..)) - || is_allowed_vec_method(parent)) + || match parent.kind { + ExprKind::Index(..) => true, + ExprKind::MethodCall(path, _, [], _) => { + // If the given expression is a method call to a `Vec` method that also exists on + // slices, it means that this expression does not actually require a `Vec` and could + // just work with an array. + VEC_METHODS_SHADOWING_SLICE_METHODS.contains(&path.ident.name) + }, + _ => false, + }) { ControlFlow::Continue(()) } else { @@ -144,8 +152,9 @@ impl UselessVec { VecToArray::Impossible }, // search for `for _ in vec![...]` - Node::Expr(Expr { span, .. }) - if span.is_desugaring(DesugaringKind::ForLoop) && self.msrv.meets(cx, msrvs::ARRAY_INTO_ITERATOR) => + Node::Expr(expr) + if expr.span.is_desugaring(DesugaringKind::ForLoop) + && self.msrv.meets(cx, msrvs::ARRAY_INTO_ITERATOR) => { VecToArray::Possible }, @@ -276,9 +285,8 @@ impl SuggestedType { assert!(args_span.is_none_or(|s| !s.from_expansion())); assert!(len_span.is_none_or(|s| !s.from_expansion())); - let maybe_args = args_span - .map(|sp| sp.get_source_text(cx).expect("spans are always crate-local")) - .map_or(String::new(), |x| x.to_owned()); + let maybe_args = args_span.map(|sp| sp.get_source_text(cx).expect("spans are always crate-local")); + let maybe_args = maybe_args.as_deref().unwrap_or_default(); let maybe_len = len_span .map(|sp| sp.get_source_text(cx).expect("spans are always crate-local")) .map(|st| format!("; {st}")) @@ -301,17 +309,6 @@ fn adjusts_to_slice(cx: &LateContext<'_>, e: &Expr<'_>) -> bool { matches!(cx.typeck_results().expr_ty_adjusted(e).kind(), ty::Ref(_, ty, _) if ty.is_slice()) } -/// Checks if the given expression is a method call to a `Vec` method -/// that also exists on slices. If this returns true, it means that -/// this expression does not actually require a `Vec` and could just work with an array. -pub fn is_allowed_vec_method(e: &Expr<'_>) -> bool { - if let ExprKind::MethodCall(path, _, [], _) = e.kind { - matches!(path.ident.name, sym::as_ptr | sym::is_empty | sym::len) - } else { - false - } -} - fn suggest_type(expr: &Expr<'_>) -> SuggestedType { if let ExprKind::AddrOf(BorrowKind::Ref, mutability, _) = expr.kind { // `expr` is `&vec![_]`, so suggest `&[_]` (or `&mut[_]` resp.) diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 46c5af058ccc0..3124ada0e94ae 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -136,6 +136,9 @@ use crate::res::{MaybeDef, MaybeQPath, MaybeResPath}; 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; +/// Methods on `Vec` that also exists on slices. +pub const VEC_METHODS_SHADOWING_SLICE_METHODS: [Symbol; 3] = [sym::as_ptr, sym::is_empty, sym::len]; + #[macro_export] macro_rules! extract_msrv_attr { () => { diff --git a/tests/ui/useless_vec.fixed b/tests/ui/useless_vec.fixed index 55742459c92cf..3cea4862611d8 100644 --- a/tests/ui/useless_vec.fixed +++ b/tests/ui/useless_vec.fixed @@ -1,5 +1,4 @@ #![warn(clippy::useless_vec)] -#![allow(clippy::nonstandard_macro_braces, clippy::uninlined_format_args, unused)] use std::rc::Rc; @@ -39,17 +38,14 @@ fn main() { on_mut_slice(&mut [1, 2]); //~^ useless_vec - on_slice(&[1, 2]); - //~^ useless_vec - on_slice(&[1, 2]); - on_mut_slice(&mut [1, 2]); - //~^ useless_vec #[rustfmt::skip] - on_slice(&[1, 2]); - //~^ useless_vec - on_slice(&[1, 2]); - on_mut_slice(&mut [1, 2]); - //~^ useless_vec + #[allow(clippy::nonstandard_macro_braces)] // not an `expect` as it will only lint _before_ the fix + { + on_slice(&[1, 2]); + //~^ useless_vec + on_mut_slice(&mut [1, 2]); + //~^ useless_vec + }; on_slice(&[1; 2]); //~^ useless_vec @@ -75,22 +71,24 @@ fn main() { on_vec(&vec![1; 201]); // Ok, size of `vec` higher than `too_large_for_stack` on_mut_vec(&mut vec![1; 201]); // Ok, size of `vec` higher than `too_large_for_stack` - // Ok + // Ok, size of `vec` higher than `too_large_for_stack` for a in vec![1; 201] { - println!("{:?}", a); + println!("{a:?}"); } // https://github.com/rust-lang/rust-clippy/issues/2262#issuecomment-783979246 let _x: i32 = [1, 2, 3].iter().sum(); //~^ useless_vec - // Do lint - let mut x = [1, 2, 3]; - //~^ useless_vec - x.fill(123); - dbg!(x[0]); - dbg!(x.len()); - dbg!(x.iter().sum::()); + // Do lint, only used as slice + { + let mut x = [1, 2, 3]; + //~^ useless_vec + x.fill(123); + dbg!(x[0]); + dbg!(x.len()); + dbg!(x.iter().sum::()); + } let _x: &[i32] = &[1, 2, 3]; //~^ useless_vec diff --git a/tests/ui/useless_vec.rs b/tests/ui/useless_vec.rs index fbf7131323c3b..2b5d71ae7fa40 100644 --- a/tests/ui/useless_vec.rs +++ b/tests/ui/useless_vec.rs @@ -1,5 +1,4 @@ #![warn(clippy::useless_vec)] -#![allow(clippy::nonstandard_macro_braces, clippy::uninlined_format_args, unused)] use std::rc::Rc; @@ -39,17 +38,14 @@ fn main() { on_mut_slice(&mut vec![1, 2]); //~^ useless_vec - on_slice(&vec![1, 2]); - //~^ useless_vec - on_slice(&[1, 2]); - on_mut_slice(&mut vec![1, 2]); - //~^ useless_vec #[rustfmt::skip] - on_slice(&vec!(1, 2)); - //~^ useless_vec - on_slice(&[1, 2]); - on_mut_slice(&mut vec![1, 2]); - //~^ useless_vec + #[allow(clippy::nonstandard_macro_braces)] // not an `expect` as it will only lint _before_ the fix + { + on_slice(&vec!(1, 2)); + //~^ useless_vec + on_mut_slice(&mut vec!(1, 2)); + //~^ useless_vec + }; on_slice(&vec![1; 2]); //~^ useless_vec @@ -75,22 +71,24 @@ fn main() { on_vec(&vec![1; 201]); // Ok, size of `vec` higher than `too_large_for_stack` on_mut_vec(&mut vec![1; 201]); // Ok, size of `vec` higher than `too_large_for_stack` - // Ok + // Ok, size of `vec` higher than `too_large_for_stack` for a in vec![1; 201] { - println!("{:?}", a); + println!("{a:?}"); } // https://github.com/rust-lang/rust-clippy/issues/2262#issuecomment-783979246 let _x: i32 = vec![1, 2, 3].iter().sum(); //~^ useless_vec - // Do lint - let mut x = vec![1, 2, 3]; - //~^ useless_vec - x.fill(123); - dbg!(x[0]); - dbg!(x.len()); - dbg!(x.iter().sum::()); + // Do lint, only used as slice + { + let mut x = vec![1, 2, 3]; + //~^ useless_vec + x.fill(123); + dbg!(x[0]); + dbg!(x.len()); + dbg!(x.iter().sum::()); + } let _x: &[i32] = &vec![1, 2, 3]; //~^ useless_vec diff --git a/tests/ui/useless_vec.stderr b/tests/ui/useless_vec.stderr index 25be94072e2d8..65120d8b338fc 100644 --- a/tests/ui/useless_vec.stderr +++ b/tests/ui/useless_vec.stderr @@ -1,5 +1,5 @@ error: useless use of `vec!` - --> tests/ui/useless_vec.rs:30:14 + --> tests/ui/useless_vec.rs:29:14 | LL | on_slice(&vec![]); | ^^^^^^^ help: you can use a slice directly: `&[]` @@ -8,130 +8,118 @@ LL | on_slice(&vec![]); = help: to override `-D warnings` add `#[allow(clippy::useless_vec)]` error: useless use of `vec!` - --> tests/ui/useless_vec.rs:33:18 + --> tests/ui/useless_vec.rs:32:18 | LL | on_mut_slice(&mut vec![]); | ^^^^^^^^^^^ help: you can use a slice directly: `&mut []` error: useless use of `vec!` - --> tests/ui/useless_vec.rs:36:14 + --> tests/ui/useless_vec.rs:35:14 | LL | on_slice(&vec![1, 2]); | ^^^^^^^^^^^ help: you can use a slice directly: `&[1, 2]` error: useless use of `vec!` - --> tests/ui/useless_vec.rs:39:18 + --> tests/ui/useless_vec.rs:38:18 | LL | on_mut_slice(&mut vec![1, 2]); | ^^^^^^^^^^^^^^^ help: you can use a slice directly: `&mut [1, 2]` error: useless use of `vec!` - --> tests/ui/useless_vec.rs:42:14 + --> tests/ui/useless_vec.rs:44:18 | -LL | on_slice(&vec![1, 2]); - | ^^^^^^^^^^^ help: you can use a slice directly: `&[1, 2]` - -error: useless use of `vec!` - --> tests/ui/useless_vec.rs:45:18 - | -LL | on_mut_slice(&mut vec![1, 2]); - | ^^^^^^^^^^^^^^^ help: you can use a slice directly: `&mut [1, 2]` +LL | on_slice(&vec!(1, 2)); + | ^^^^^^^^^^^ help: you can use a slice directly: `&[1, 2]` error: useless use of `vec!` - --> tests/ui/useless_vec.rs:48:14 + --> tests/ui/useless_vec.rs:46:22 | -LL | on_slice(&vec!(1, 2)); - | ^^^^^^^^^^^ help: you can use a slice directly: `&[1, 2]` - -error: useless use of `vec!` - --> tests/ui/useless_vec.rs:51:18 - | -LL | on_mut_slice(&mut vec![1, 2]); - | ^^^^^^^^^^^^^^^ help: you can use a slice directly: `&mut [1, 2]` +LL | on_mut_slice(&mut vec!(1, 2)); + | ^^^^^^^^^^^^^^^ help: you can use a slice directly: `&mut [1, 2]` error: useless use of `vec!` - --> tests/ui/useless_vec.rs:54:14 + --> tests/ui/useless_vec.rs:50:14 | LL | on_slice(&vec![1; 2]); | ^^^^^^^^^^^ help: you can use a slice directly: `&[1; 2]` error: useless use of `vec!` - --> tests/ui/useless_vec.rs:57:18 + --> tests/ui/useless_vec.rs:53:18 | LL | on_mut_slice(&mut vec![1; 2]); | ^^^^^^^^^^^^^^^ help: you can use a slice directly: `&mut [1; 2]` error: useless use of `vec!` - --> tests/ui/useless_vec.rs:84:19 + --> tests/ui/useless_vec.rs:80:19 | LL | let _x: i32 = vec![1, 2, 3].iter().sum(); | ^^^^^^^^^^^^^ help: you can use an array directly: `[1, 2, 3]` error: useless use of `vec!` - --> tests/ui/useless_vec.rs:88:17 + --> tests/ui/useless_vec.rs:85:21 | -LL | let mut x = vec![1, 2, 3]; - | ^^^^^^^^^^^^^ help: you can use an array directly: `[1, 2, 3]` +LL | let mut x = vec![1, 2, 3]; + | ^^^^^^^^^^^^^ help: you can use an array directly: `[1, 2, 3]` error: useless use of `vec!` - --> tests/ui/useless_vec.rs:95:22 + --> tests/ui/useless_vec.rs:93:22 | LL | let _x: &[i32] = &vec![1, 2, 3]; | ^^^^^^^^^^^^^^ help: you can use a slice directly: `&[1, 2, 3]` error: useless use of `vec!` - --> tests/ui/useless_vec.rs:98:14 + --> tests/ui/useless_vec.rs:96:14 | LL | for _ in vec![1, 2, 3] {} | ^^^^^^^^^^^^^ help: you can use an array directly: `[1, 2, 3]` error: useless use of `vec!` - --> tests/ui/useless_vec.rs:138:20 + --> tests/ui/useless_vec.rs:136:20 | LL | for _string in vec![repro!(true), repro!(null)] { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: you can use an array directly: `[repro!(true), repro!(null)]` error: useless use of `vec!` - --> tests/ui/useless_vec.rs:156:18 + --> tests/ui/useless_vec.rs:154:18 | LL | in_macro!(1, vec![1, 2], vec![1; 2]); | ^^^^^^^^^^ help: you can use an array directly: `[1, 2]` error: useless use of `vec!` - --> tests/ui/useless_vec.rs:156:30 + --> tests/ui/useless_vec.rs:154:30 | LL | in_macro!(1, vec![1, 2], vec![1; 2]); | ^^^^^^^^^^ help: you can use an array directly: `[1; 2]` error: useless use of `vec!` - --> tests/ui/useless_vec.rs:177:14 + --> tests/ui/useless_vec.rs:175:14 | LL | for a in vec![1, 2, 3] { | ^^^^^^^^^^^^^ help: you can use an array directly: `[1, 2, 3]` error: useless use of `vec!` - --> tests/ui/useless_vec.rs:182:14 + --> tests/ui/useless_vec.rs:180:14 | LL | for a in vec![String::new(), String::new()] { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: you can use an array directly: `[String::new(), String::new()]` error: useless use of `vec!` - --> tests/ui/useless_vec.rs:215:33 + --> tests/ui/useless_vec.rs:213:33 | LL | this_macro_doesnt_need_vec!(vec![1]); | ^^^^^^^ help: you can use an array directly: `[1]` error: useless use of `vec!` - --> tests/ui/useless_vec.rs:242:14 + --> tests/ui/useless_vec.rs:240:14 | LL | for a in &(vec![1, 2]) {} | ^^^^^^^^^^^^^ help: you can use a slice directly: `&[1, 2]` error: useless use of `vec!` - --> tests/ui/useless_vec.rs:250:13 + --> tests/ui/useless_vec.rs:248:13 | LL | let v = &vec![]; | ^^^^^^^ help: you can use a slice directly: `&[]` -error: aborting due to 22 previous errors +error: aborting due to 20 previous errors diff --git a/tests/ui/useless_vec_unfixable.rs b/tests/ui/useless_vec_unfixable.rs index 880809f81d7ae..7f45f4df5ee6e 100644 --- a/tests/ui/useless_vec_unfixable.rs +++ b/tests/ui/useless_vec_unfixable.rs @@ -1,5 +1,4 @@ //@no-rustfix: no suggestions - #![warn(clippy::useless_vec)] // Regression test for . diff --git a/tests/ui/useless_vec_unfixable.stderr b/tests/ui/useless_vec_unfixable.stderr index 41a172bd58d1b..980194ac71910 100644 --- a/tests/ui/useless_vec_unfixable.stderr +++ b/tests/ui/useless_vec_unfixable.stderr @@ -1,5 +1,5 @@ error: useless use of `vec!` - --> tests/ui/useless_vec_unfixable.rs:8:26 + --> tests/ui/useless_vec_unfixable.rs:7:26 | LL | let _some_variable = vec![ | __________________________^ From e1344da2feb8c5ec12a0f830523bf9b30215d644 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Sat, 15 Nov 2025 08:19:33 +0100 Subject: [PATCH 24/60] Handle the suggestion-less case inside `print_unchecked_duration_subtration_sugg` This doesn't change any functionality, but will make it easier to switch to a uniform message in the next commit. Also a nice simplification imo --- clippy_lints/src/time_subtraction.rs | 59 ++++++++++------------------ 1 file changed, 21 insertions(+), 38 deletions(-) diff --git a/clippy_lints/src/time_subtraction.rs b/clippy_lints/src/time_subtraction.rs index dbd4ec77fd5f9..a48c6be4dc554 100644 --- a/clippy_lints/src/time_subtraction.rs +++ b/clippy_lints/src/time_subtraction.rs @@ -1,5 +1,5 @@ use clippy_config::Conf; -use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg}; +use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; use clippy_utils::sugg::Sugg; @@ -109,36 +109,16 @@ impl LateLintPass<'_> for UncheckedTimeSubtraction { && !expr.span.from_expansion() && self.msrv.meets(cx, msrvs::TRY_FROM) { - // For chained subtraction like (instant - dur1) - dur2, avoid suggestions - if is_chained_time_subtraction(cx, lhs) { - span_lint( - cx, - UNCHECKED_TIME_SUBTRACTION, - expr.span, - "unchecked subtraction of a 'Duration' from an 'Instant'", - ); - } else { - // instant - duration - print_unchecked_duration_subtraction_sugg(cx, lhs, rhs, expr); - } + print_unchecked_duration_subtraction_sugg(cx, lhs, rhs, expr); } - } else if lhs_ty.is_diag_item(cx, sym::Duration) + } + // duration - duration + else if lhs_ty.is_diag_item(cx, sym::Duration) && rhs_ty.is_diag_item(cx, sym::Duration) && !expr.span.from_expansion() && self.msrv.meets(cx, msrvs::TRY_FROM) { - // For chained subtraction like (dur1 - dur2) - dur3, avoid suggestions - if is_chained_time_subtraction(cx, lhs) { - span_lint( - cx, - UNCHECKED_TIME_SUBTRACTION, - expr.span, - "unchecked subtraction between 'Duration' values", - ); - } else { - // duration - duration - print_unchecked_duration_subtraction_sugg(cx, lhs, rhs, expr); - } + print_unchecked_duration_subtraction_sugg(cx, lhs, rhs, expr); } } } @@ -200,17 +180,20 @@ fn print_unchecked_duration_subtraction_sugg( "unchecked subtraction between 'Duration' values" }; - let mut applicability = Applicability::MachineApplicable; - let left_sugg = Sugg::hir_with_applicability(cx, left_expr, "", &mut applicability); - let right_sugg = Sugg::hir_with_applicability(cx, right_expr, "", &mut applicability); + span_lint_and_then(cx, UNCHECKED_TIME_SUBTRACTION, expr.span, lint_msg, |diag| { + // For chained subtraction, like `(dur1 - dur2) - dur3` or `(instant - dur1) - dur2`, + // avoid suggestions + if !is_chained_time_subtraction(cx, left_expr) { + let mut applicability = Applicability::MachineApplicable; + let left_sugg = Sugg::hir_with_applicability(cx, left_expr, "", &mut applicability); + let right_sugg = Sugg::hir_with_applicability(cx, right_expr, "", &mut applicability); - span_lint_and_sugg( - cx, - UNCHECKED_TIME_SUBTRACTION, - expr.span, - lint_msg, - "try", - format!("{}.checked_sub({}).unwrap()", left_sugg.maybe_paren(), right_sugg), - applicability, - ); + diag.span_suggestion( + expr.span, + "try", + format!("{}.checked_sub({}).unwrap()", left_sugg.maybe_paren(), right_sugg), + applicability, + ); + } + }); } From 32f5ae101d68ce49980854995e51bf698eba3403 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Sat, 15 Nov 2025 08:12:02 +0100 Subject: [PATCH 25/60] Give the same message for `Instant - Duration` and `Duration - Duration` Diff best seen with whitespace ignored --- clippy_lints/src/time_subtraction.rs | 45 +++++++++---------- tests/ui/unchecked_time_subtraction.stderr | 16 +++---- ...nchecked_time_subtraction_unfixable.stderr | 8 ++-- 3 files changed, 33 insertions(+), 36 deletions(-) diff --git a/clippy_lints/src/time_subtraction.rs b/clippy_lints/src/time_subtraction.rs index a48c6be4dc554..e0fdca97dbeee 100644 --- a/clippy_lints/src/time_subtraction.rs +++ b/clippy_lints/src/time_subtraction.rs @@ -171,29 +171,26 @@ fn print_unchecked_duration_subtraction_sugg( right_expr: &Expr<'_>, expr: &Expr<'_>, ) { - let typeck = cx.typeck_results(); - let left_ty = typeck.expr_ty(left_expr); - - let lint_msg = if left_ty.is_diag_item(cx, sym::Instant) { - "unchecked subtraction of a 'Duration' from an 'Instant'" - } else { - "unchecked subtraction between 'Duration' values" - }; - - span_lint_and_then(cx, UNCHECKED_TIME_SUBTRACTION, expr.span, lint_msg, |diag| { - // For chained subtraction, like `(dur1 - dur2) - dur3` or `(instant - dur1) - dur2`, - // avoid suggestions - if !is_chained_time_subtraction(cx, left_expr) { - let mut applicability = Applicability::MachineApplicable; - let left_sugg = Sugg::hir_with_applicability(cx, left_expr, "", &mut applicability); - let right_sugg = Sugg::hir_with_applicability(cx, right_expr, "", &mut applicability); + span_lint_and_then( + cx, + UNCHECKED_TIME_SUBTRACTION, + expr.span, + "unchecked subtraction of a `Duration`", + |diag| { + // For chained subtraction, like `(dur1 - dur2) - dur3` or `(instant - dur1) - dur2`, + // avoid suggestions + if !is_chained_time_subtraction(cx, left_expr) { + let mut applicability = Applicability::MachineApplicable; + let left_sugg = Sugg::hir_with_applicability(cx, left_expr, "", &mut applicability); + let right_sugg = Sugg::hir_with_applicability(cx, right_expr, "", &mut applicability); - diag.span_suggestion( - expr.span, - "try", - format!("{}.checked_sub({}).unwrap()", left_sugg.maybe_paren(), right_sugg), - applicability, - ); - } - }); + diag.span_suggestion( + expr.span, + "try", + format!("{}.checked_sub({}).unwrap()", left_sugg.maybe_paren(), right_sugg), + applicability, + ); + } + }, + ); } diff --git a/tests/ui/unchecked_time_subtraction.stderr b/tests/ui/unchecked_time_subtraction.stderr index 7a39712269cf7..c129497447fc0 100644 --- a/tests/ui/unchecked_time_subtraction.stderr +++ b/tests/ui/unchecked_time_subtraction.stderr @@ -1,4 +1,4 @@ -error: unchecked subtraction of a 'Duration' from an 'Instant' +error: unchecked subtraction of a `Duration` --> tests/ui/unchecked_time_subtraction.rs:9:13 | LL | let _ = _first - second; @@ -7,43 +7,43 @@ LL | let _ = _first - second; = note: `-D clippy::unchecked-time-subtraction` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::unchecked_time_subtraction)]` -error: unchecked subtraction of a 'Duration' from an 'Instant' +error: unchecked subtraction of a `Duration` --> tests/ui/unchecked_time_subtraction.rs:12:13 | LL | let _ = Instant::now() - Duration::from_secs(5); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Instant::now().checked_sub(Duration::from_secs(5)).unwrap()` -error: unchecked subtraction of a 'Duration' from an 'Instant' +error: unchecked subtraction of a `Duration` --> tests/ui/unchecked_time_subtraction.rs:15:13 | LL | let _ = _first - Duration::from_secs(5); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `_first.checked_sub(Duration::from_secs(5)).unwrap()` -error: unchecked subtraction of a 'Duration' from an 'Instant' +error: unchecked subtraction of a `Duration` --> tests/ui/unchecked_time_subtraction.rs:18:13 | LL | let _ = Instant::now() - second; | ^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Instant::now().checked_sub(second).unwrap()` -error: unchecked subtraction between 'Duration' values +error: unchecked subtraction of a `Duration` --> tests/ui/unchecked_time_subtraction.rs:25:13 | LL | let _ = dur1 - dur2; | ^^^^^^^^^^^ help: try: `dur1.checked_sub(dur2).unwrap()` -error: unchecked subtraction between 'Duration' values +error: unchecked subtraction of a `Duration` --> tests/ui/unchecked_time_subtraction.rs:28:13 | LL | let _ = Duration::from_secs(10) - Duration::from_secs(5); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Duration::from_secs(10).checked_sub(Duration::from_secs(5)).unwrap()` -error: unchecked subtraction between 'Duration' values +error: unchecked subtraction of a `Duration` --> tests/ui/unchecked_time_subtraction.rs:31:13 | LL | let _ = second - dur1; | ^^^^^^^^^^^^^ help: try: `second.checked_sub(dur1).unwrap()` -error: unchecked subtraction between 'Duration' values +error: unchecked subtraction of a `Duration` --> tests/ui/unchecked_time_subtraction.rs:35:13 | LL | let _ = 2 * dur1 - dur2; diff --git a/tests/ui/unchecked_time_subtraction_unfixable.stderr b/tests/ui/unchecked_time_subtraction_unfixable.stderr index c25c112b06ce0..017e5b1c7c11e 100644 --- a/tests/ui/unchecked_time_subtraction_unfixable.stderr +++ b/tests/ui/unchecked_time_subtraction_unfixable.stderr @@ -1,4 +1,4 @@ -error: unchecked subtraction between 'Duration' values +error: unchecked subtraction of a `Duration` --> tests/ui/unchecked_time_subtraction_unfixable.rs:12:13 | LL | let _ = dur1 - dur2 - dur3; @@ -7,19 +7,19 @@ LL | let _ = dur1 - dur2 - dur3; = note: `-D clippy::unchecked-time-subtraction` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::unchecked_time_subtraction)]` -error: unchecked subtraction between 'Duration' values +error: unchecked subtraction of a `Duration` --> tests/ui/unchecked_time_subtraction_unfixable.rs:12:13 | LL | let _ = dur1 - dur2 - dur3; | ^^^^^^^^^^^ help: try: `dur1.checked_sub(dur2).unwrap()` -error: unchecked subtraction of a 'Duration' from an 'Instant' +error: unchecked subtraction of a `Duration` --> tests/ui/unchecked_time_subtraction_unfixable.rs:19:13 | LL | let _ = instant1 - dur2 - dur3; | ^^^^^^^^^^^^^^^^^^^^^^ -error: unchecked subtraction of a 'Duration' from an 'Instant' +error: unchecked subtraction of a `Duration` --> tests/ui/unchecked_time_subtraction_unfixable.rs:19:13 | LL | let _ = instant1 - dur2 - dur3; From aa4869f27a5376695093cc4f541da786f9c4edb1 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Fri, 24 Oct 2025 20:29:29 +0200 Subject: [PATCH 26/60] clean-up --- clippy_lints/src/unwrap.rs | 93 ++++++++------- .../ui/checked_unwrap/complex_conditionals.rs | 28 +---- .../complex_conditionals.stderr | 56 ++++----- .../complex_conditionals_nested.rs | 11 +- .../complex_conditionals_nested.stderr | 19 +-- tests/ui/checked_unwrap/if_let_chains.rs | 2 +- tests/ui/checked_unwrap/if_let_chains.stderr | 7 +- .../ui/checked_unwrap/simple_conditionals.rs | 38 +----- .../checked_unwrap/simple_conditionals.stderr | 109 +++++++----------- 9 files changed, 140 insertions(+), 223 deletions(-) diff --git a/clippy_lints/src/unwrap.rs b/clippy_lints/src/unwrap.rs index 99201a1ca215f..0a96ee0920895 100644 --- a/clippy_lints/src/unwrap.rs +++ b/clippy_lints/src/unwrap.rs @@ -156,39 +156,52 @@ fn collect_unwrap_info<'tcx>( } } - match expr.kind { - ExprKind::Binary(op, left, right) - if matches!( - (invert, op.node), - (false, BinOpKind::And | BinOpKind::BitAnd) | (true, BinOpKind::Or | BinOpKind::BitOr) - ) => - { - let mut unwrap_info = collect_unwrap_info(cx, if_expr, left, branch, invert, false); - unwrap_info.extend(collect_unwrap_info(cx, if_expr, right, branch, invert, false)); - unwrap_info - }, - ExprKind::Unary(UnOp::Not, expr) => collect_unwrap_info(cx, if_expr, expr, branch, !invert, false), - ExprKind::MethodCall(method_name, receiver, [], _) - if let Some(local_id) = receiver.res_local_id() - && let ty = cx.typeck_results().expr_ty(receiver) - && let name = method_name.ident.name - && let Some((kind, unwrappable)) = option_or_result_call(cx, ty, name) => - { - let safe_to_unwrap = unwrappable != invert; - - vec![UnwrapInfo { - local_id, - if_expr, - check: expr, - check_name: name, - branch, - safe_to_unwrap, - kind, - is_entire_condition, - }] - }, - _ => vec![], + fn inner<'tcx>( + cx: &LateContext<'tcx>, + if_expr: &'tcx Expr<'_>, + expr: &'tcx Expr<'_>, + branch: &'tcx Expr<'_>, + invert: bool, + is_entire_condition: bool, + out: &mut Vec>, + ) { + match expr.kind { + ExprKind::Binary(op, left, right) + if matches!( + (invert, op.node), + (false, BinOpKind::And | BinOpKind::BitAnd) | (true, BinOpKind::Or | BinOpKind::BitOr) + ) => + { + inner(cx, if_expr, left, branch, invert, false, out); + inner(cx, if_expr, right, branch, invert, false, out); + }, + ExprKind::Unary(UnOp::Not, expr) => inner(cx, if_expr, expr, branch, !invert, false, out), + ExprKind::MethodCall(method_name, receiver, [], _) + if let Some(local_id) = receiver.res_local_id() + && let ty = cx.typeck_results().expr_ty(receiver) + && let name = method_name.ident.name + && let Some((kind, unwrappable)) = option_or_result_call(cx, ty, name) => + { + let safe_to_unwrap = unwrappable != invert; + + out.push(UnwrapInfo { + local_id, + if_expr, + check: expr, + check_name: name, + branch, + safe_to_unwrap, + kind, + is_entire_condition, + }); + }, + _ => {}, + } } + + let mut out = vec![]; + inner(cx, if_expr, expr, branch, invert, is_entire_condition, &mut out); + out } /// A HIR visitor delegate that checks if a local variable of type `Option` or `Result` is mutated, @@ -321,21 +334,14 @@ impl<'tcx> Visitor<'tcx> for UnwrappableVariablesVisitor<'_, 'tcx> { && let Some(id) = self_arg.res_local_id() && matches!(method_name.ident.name, sym::unwrap | sym::expect | sym::unwrap_err) && let call_to_unwrap = matches!(method_name.ident.name, sym::unwrap | sym::expect) - && let Some(unwrappable) = self.unwrappables.iter() - .find(|u| u.local_id == id) + && let Some(unwrappable) = self.unwrappables.iter().find(|u| u.local_id == id) // Span contexts should not differ with the conditional branch && let span_ctxt = expr.span.ctxt() && unwrappable.branch.span.ctxt() == span_ctxt && unwrappable.check.span.ctxt() == span_ctxt { if call_to_unwrap == unwrappable.safe_to_unwrap { - let is_entire_condition = unwrappable.is_entire_condition; let unwrappable_variable_name = self.cx.tcx.hir_name(unwrappable.local_id); - let suggested_pattern = if call_to_unwrap { - unwrappable.kind.success_variant_pattern() - } else { - unwrappable.kind.error_variant_pattern() - }; span_lint_hir_and_then( self.cx, @@ -347,12 +353,17 @@ impl<'tcx> Visitor<'tcx> for UnwrappableVariablesVisitor<'_, 'tcx> { method_name.ident.name, unwrappable.check_name, ), |diag| { - if is_entire_condition { + if unwrappable.is_entire_condition { diag.span_suggestion( unwrappable.check.span.with_lo(unwrappable.if_expr.span.lo()), "try", format!( "if let {suggested_pattern} = {borrow_prefix}{unwrappable_variable_name}", + suggested_pattern = if call_to_unwrap { + unwrappable.kind.success_variant_pattern() + } else { + unwrappable.kind.error_variant_pattern() + }, borrow_prefix = match as_ref_kind { Some(AsRefKind::AsRef) => "&", Some(AsRefKind::AsMut) => "&mut ", diff --git a/tests/ui/checked_unwrap/complex_conditionals.rs b/tests/ui/checked_unwrap/complex_conditionals.rs index 7d0bcc547a42b..d1db2e67e269d 100644 --- a/tests/ui/checked_unwrap/complex_conditionals.rs +++ b/tests/ui/checked_unwrap/complex_conditionals.rs @@ -1,27 +1,19 @@ -#![deny(clippy::panicking_unwrap, clippy::unnecessary_unwrap)] -#![allow( - clippy::if_same_then_else, - clippy::branches_sharing_code, - clippy::unnecessary_literal_unwrap -)] +#![warn(clippy::panicking_unwrap, clippy::unnecessary_unwrap)] +#![expect(clippy::branches_sharing_code, clippy::unnecessary_literal_unwrap)] fn test_complex_conditions() { let x: Result<(), ()> = Ok(()); let y: Result<(), ()> = Ok(()); if x.is_ok() && y.is_err() { - // unnecessary x.unwrap(); //~^ unnecessary_unwrap - // will panic x.unwrap_err(); //~^ panicking_unwrap - // will panic y.unwrap(); //~^ panicking_unwrap - // unnecessary y.unwrap_err(); //~^ unnecessary_unwrap } else { @@ -37,45 +29,35 @@ fn test_complex_conditions() { x.unwrap(); y.unwrap(); } else { - // will panic x.unwrap(); //~^ panicking_unwrap - // unnecessary x.unwrap_err(); //~^ unnecessary_unwrap - // will panic y.unwrap(); //~^ panicking_unwrap - // unnecessary y.unwrap_err(); //~^ unnecessary_unwrap } let z: Result<(), ()> = Ok(()); if x.is_ok() && !(y.is_ok() || z.is_err()) { - // unnecessary x.unwrap(); //~^ unnecessary_unwrap - // will panic x.unwrap_err(); //~^ panicking_unwrap - // will panic y.unwrap(); //~^ panicking_unwrap - // unnecessary y.unwrap_err(); //~^ unnecessary_unwrap - // unnecessary z.unwrap(); //~^ unnecessary_unwrap - // will panic z.unwrap_err(); //~^ panicking_unwrap } @@ -85,27 +67,21 @@ fn test_complex_conditions() { y.unwrap(); z.unwrap(); } else { - // will panic x.unwrap(); //~^ panicking_unwrap - // unnecessary x.unwrap_err(); //~^ unnecessary_unwrap - // unnecessary y.unwrap(); //~^ unnecessary_unwrap - // will panic y.unwrap_err(); //~^ panicking_unwrap - // will panic z.unwrap(); //~^ panicking_unwrap - // unnecessary z.unwrap_err(); //~^ unnecessary_unwrap } diff --git a/tests/ui/checked_unwrap/complex_conditionals.stderr b/tests/ui/checked_unwrap/complex_conditionals.stderr index d3905850c9702..e154e3c35dc9f 100644 --- a/tests/ui/checked_unwrap/complex_conditionals.stderr +++ b/tests/ui/checked_unwrap/complex_conditionals.stderr @@ -1,21 +1,17 @@ error: called `unwrap` on `x` after checking its variant with `is_ok` - --> tests/ui/checked_unwrap/complex_conditionals.rs:13:9 + --> tests/ui/checked_unwrap/complex_conditionals.rs:8:9 | LL | if x.is_ok() && y.is_err() { | --------- the check is happening here -LL | // unnecessary LL | x.unwrap(); | ^^^^^^^^^^ | = help: try using `if let` or `match` -note: the lint level is defined here - --> tests/ui/checked_unwrap/complex_conditionals.rs:1:35 - | -LL | #![deny(clippy::panicking_unwrap, clippy::unnecessary_unwrap)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: `-D clippy::unnecessary-unwrap` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::unnecessary_unwrap)]` error: this call to `unwrap_err()` will always panic - --> tests/ui/checked_unwrap/complex_conditionals.rs:17:9 + --> tests/ui/checked_unwrap/complex_conditionals.rs:11:9 | LL | if x.is_ok() && y.is_err() { | --------- because of this check @@ -23,14 +19,11 @@ LL | if x.is_ok() && y.is_err() { LL | x.unwrap_err(); | ^^^^^^^^^^^^^^ | -note: the lint level is defined here - --> tests/ui/checked_unwrap/complex_conditionals.rs:1:9 - | -LL | #![deny(clippy::panicking_unwrap, clippy::unnecessary_unwrap)] - | ^^^^^^^^^^^^^^^^^^^^^^^^ + = note: `-D clippy::panicking-unwrap` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::panicking_unwrap)]` error: this call to `unwrap()` will always panic - --> tests/ui/checked_unwrap/complex_conditionals.rs:21:9 + --> tests/ui/checked_unwrap/complex_conditionals.rs:14:9 | LL | if x.is_ok() && y.is_err() { | ---------- because of this check @@ -39,7 +32,7 @@ LL | y.unwrap(); | ^^^^^^^^^^ error: called `unwrap_err` on `y` after checking its variant with `is_err` - --> tests/ui/checked_unwrap/complex_conditionals.rs:25:9 + --> tests/ui/checked_unwrap/complex_conditionals.rs:17:9 | LL | if x.is_ok() && y.is_err() { | ---------- the check is happening here @@ -50,7 +43,7 @@ LL | y.unwrap_err(); = help: try using `if let` or `match` error: this call to `unwrap()` will always panic - --> tests/ui/checked_unwrap/complex_conditionals.rs:41:9 + --> tests/ui/checked_unwrap/complex_conditionals.rs:32:9 | LL | if x.is_ok() || y.is_ok() { | --------- because of this check @@ -59,7 +52,7 @@ LL | x.unwrap(); | ^^^^^^^^^^ error: called `unwrap_err` on `x` after checking its variant with `is_ok` - --> tests/ui/checked_unwrap/complex_conditionals.rs:45:9 + --> tests/ui/checked_unwrap/complex_conditionals.rs:35:9 | LL | if x.is_ok() || y.is_ok() { | --------- the check is happening here @@ -70,7 +63,7 @@ LL | x.unwrap_err(); = help: try using `if let` or `match` error: this call to `unwrap()` will always panic - --> tests/ui/checked_unwrap/complex_conditionals.rs:49:9 + --> tests/ui/checked_unwrap/complex_conditionals.rs:38:9 | LL | if x.is_ok() || y.is_ok() { | --------- because of this check @@ -79,7 +72,7 @@ LL | y.unwrap(); | ^^^^^^^^^^ error: called `unwrap_err` on `y` after checking its variant with `is_ok` - --> tests/ui/checked_unwrap/complex_conditionals.rs:53:9 + --> tests/ui/checked_unwrap/complex_conditionals.rs:41:9 | LL | if x.is_ok() || y.is_ok() { | --------- the check is happening here @@ -90,18 +83,17 @@ LL | y.unwrap_err(); = help: try using `if let` or `match` error: called `unwrap` on `x` after checking its variant with `is_ok` - --> tests/ui/checked_unwrap/complex_conditionals.rs:59:9 + --> tests/ui/checked_unwrap/complex_conditionals.rs:46:9 | LL | if x.is_ok() && !(y.is_ok() || z.is_err()) { | --------- the check is happening here -LL | // unnecessary LL | x.unwrap(); | ^^^^^^^^^^ | = help: try using `if let` or `match` error: this call to `unwrap_err()` will always panic - --> tests/ui/checked_unwrap/complex_conditionals.rs:63:9 + --> tests/ui/checked_unwrap/complex_conditionals.rs:49:9 | LL | if x.is_ok() && !(y.is_ok() || z.is_err()) { | --------- because of this check @@ -110,7 +102,7 @@ LL | x.unwrap_err(); | ^^^^^^^^^^^^^^ error: this call to `unwrap()` will always panic - --> tests/ui/checked_unwrap/complex_conditionals.rs:67:9 + --> tests/ui/checked_unwrap/complex_conditionals.rs:52:9 | LL | if x.is_ok() && !(y.is_ok() || z.is_err()) { | --------- because of this check @@ -119,7 +111,7 @@ LL | y.unwrap(); | ^^^^^^^^^^ error: called `unwrap_err` on `y` after checking its variant with `is_ok` - --> tests/ui/checked_unwrap/complex_conditionals.rs:71:9 + --> tests/ui/checked_unwrap/complex_conditionals.rs:55:9 | LL | if x.is_ok() && !(y.is_ok() || z.is_err()) { | --------- the check is happening here @@ -130,7 +122,7 @@ LL | y.unwrap_err(); = help: try using `if let` or `match` error: called `unwrap` on `z` after checking its variant with `is_err` - --> tests/ui/checked_unwrap/complex_conditionals.rs:75:9 + --> tests/ui/checked_unwrap/complex_conditionals.rs:58:9 | LL | if x.is_ok() && !(y.is_ok() || z.is_err()) { | ---------- the check is happening here @@ -141,7 +133,7 @@ LL | z.unwrap(); = help: try using `if let` or `match` error: this call to `unwrap_err()` will always panic - --> tests/ui/checked_unwrap/complex_conditionals.rs:79:9 + --> tests/ui/checked_unwrap/complex_conditionals.rs:61:9 | LL | if x.is_ok() && !(y.is_ok() || z.is_err()) { | ---------- because of this check @@ -150,7 +142,7 @@ LL | z.unwrap_err(); | ^^^^^^^^^^^^^^ error: this call to `unwrap()` will always panic - --> tests/ui/checked_unwrap/complex_conditionals.rs:89:9 + --> tests/ui/checked_unwrap/complex_conditionals.rs:70:9 | LL | if x.is_ok() || !(y.is_ok() && z.is_err()) { | --------- because of this check @@ -159,7 +151,7 @@ LL | x.unwrap(); | ^^^^^^^^^^ error: called `unwrap_err` on `x` after checking its variant with `is_ok` - --> tests/ui/checked_unwrap/complex_conditionals.rs:93:9 + --> tests/ui/checked_unwrap/complex_conditionals.rs:73:9 | LL | if x.is_ok() || !(y.is_ok() && z.is_err()) { | --------- the check is happening here @@ -170,7 +162,7 @@ LL | x.unwrap_err(); = help: try using `if let` or `match` error: called `unwrap` on `y` after checking its variant with `is_ok` - --> tests/ui/checked_unwrap/complex_conditionals.rs:97:9 + --> tests/ui/checked_unwrap/complex_conditionals.rs:76:9 | LL | if x.is_ok() || !(y.is_ok() && z.is_err()) { | --------- the check is happening here @@ -181,7 +173,7 @@ LL | y.unwrap(); = help: try using `if let` or `match` error: this call to `unwrap_err()` will always panic - --> tests/ui/checked_unwrap/complex_conditionals.rs:101:9 + --> tests/ui/checked_unwrap/complex_conditionals.rs:79:9 | LL | if x.is_ok() || !(y.is_ok() && z.is_err()) { | --------- because of this check @@ -190,7 +182,7 @@ LL | y.unwrap_err(); | ^^^^^^^^^^^^^^ error: this call to `unwrap()` will always panic - --> tests/ui/checked_unwrap/complex_conditionals.rs:105:9 + --> tests/ui/checked_unwrap/complex_conditionals.rs:82:9 | LL | if x.is_ok() || !(y.is_ok() && z.is_err()) { | ---------- because of this check @@ -199,7 +191,7 @@ LL | z.unwrap(); | ^^^^^^^^^^ error: called `unwrap_err` on `z` after checking its variant with `is_err` - --> tests/ui/checked_unwrap/complex_conditionals.rs:109:9 + --> tests/ui/checked_unwrap/complex_conditionals.rs:85:9 | LL | if x.is_ok() || !(y.is_ok() && z.is_err()) { | ---------- the check is happening here diff --git a/tests/ui/checked_unwrap/complex_conditionals_nested.rs b/tests/ui/checked_unwrap/complex_conditionals_nested.rs index 7635f848cb349..6789e7c262b33 100644 --- a/tests/ui/checked_unwrap/complex_conditionals_nested.rs +++ b/tests/ui/checked_unwrap/complex_conditionals_nested.rs @@ -1,19 +1,14 @@ -#![deny(clippy::panicking_unwrap, clippy::unnecessary_unwrap)] -#![allow( - clippy::if_same_then_else, - clippy::branches_sharing_code, - clippy::unnecessary_literal_unwrap -)] //@no-rustfix: has placeholders +#![warn(clippy::panicking_unwrap, clippy::unnecessary_unwrap)] +#![expect(clippy::branches_sharing_code, clippy::unnecessary_literal_unwrap)] + fn test_nested() { fn nested() { let x = Some(()); if x.is_some() { - // unnecessary x.unwrap(); //~^ unnecessary_unwrap } else { - // will panic x.unwrap(); //~^ panicking_unwrap } diff --git a/tests/ui/checked_unwrap/complex_conditionals_nested.stderr b/tests/ui/checked_unwrap/complex_conditionals_nested.stderr index 329be4d366210..7e4ef049f4a59 100644 --- a/tests/ui/checked_unwrap/complex_conditionals_nested.stderr +++ b/tests/ui/checked_unwrap/complex_conditionals_nested.stderr @@ -1,20 +1,16 @@ error: called `unwrap` on `x` after checking its variant with `is_some` - --> tests/ui/checked_unwrap/complex_conditionals_nested.rs:13:13 + --> tests/ui/checked_unwrap/complex_conditionals_nested.rs:9:13 | LL | if x.is_some() { | -------------- help: try: `if let Some() = x` -LL | // unnecessary LL | x.unwrap(); | ^^^^^^^^^^ | -note: the lint level is defined here - --> tests/ui/checked_unwrap/complex_conditionals_nested.rs:1:35 - | -LL | #![deny(clippy::panicking_unwrap, clippy::unnecessary_unwrap)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: `-D clippy::unnecessary-unwrap` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::unnecessary_unwrap)]` error: this call to `unwrap()` will always panic - --> tests/ui/checked_unwrap/complex_conditionals_nested.rs:17:13 + --> tests/ui/checked_unwrap/complex_conditionals_nested.rs:12:13 | LL | if x.is_some() { | ----------- because of this check @@ -22,11 +18,8 @@ LL | if x.is_some() { LL | x.unwrap(); | ^^^^^^^^^^ | -note: the lint level is defined here - --> tests/ui/checked_unwrap/complex_conditionals_nested.rs:1:9 - | -LL | #![deny(clippy::panicking_unwrap, clippy::unnecessary_unwrap)] - | ^^^^^^^^^^^^^^^^^^^^^^^^ + = note: `-D clippy::panicking-unwrap` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::panicking_unwrap)]` error: aborting due to 2 previous errors diff --git a/tests/ui/checked_unwrap/if_let_chains.rs b/tests/ui/checked_unwrap/if_let_chains.rs index cfa7715965cdb..5c20ebb800244 100644 --- a/tests/ui/checked_unwrap/if_let_chains.rs +++ b/tests/ui/checked_unwrap/if_let_chains.rs @@ -1,5 +1,5 @@ //@require-annotations-for-level: ERROR -#![deny(clippy::unnecessary_unwrap)] +#![warn(clippy::unnecessary_unwrap)] #[clippy::msrv = "1.85"] fn if_let_chains_unsupported(a: Option, b: Option) { diff --git a/tests/ui/checked_unwrap/if_let_chains.stderr b/tests/ui/checked_unwrap/if_let_chains.stderr index 8a4137de37a3c..801b074fc277c 100644 --- a/tests/ui/checked_unwrap/if_let_chains.stderr +++ b/tests/ui/checked_unwrap/if_let_chains.stderr @@ -8,11 +8,8 @@ LL | println!("the value of a is {}", a.unwrap()); | ^^^^^^^^^^ | = help: try using `match` -note: the lint level is defined here - --> tests/ui/checked_unwrap/if_let_chains.rs:2:9 - | -LL | #![deny(clippy::unnecessary_unwrap)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: `-D clippy::unnecessary-unwrap` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::unnecessary_unwrap)]` error: called `unwrap` on `a` after checking its variant with `is_none` --> tests/ui/checked_unwrap/if_let_chains.rs:20:42 diff --git a/tests/ui/checked_unwrap/simple_conditionals.rs b/tests/ui/checked_unwrap/simple_conditionals.rs index bba264080b406..b762693071597 100644 --- a/tests/ui/checked_unwrap/simple_conditionals.rs +++ b/tests/ui/checked_unwrap/simple_conditionals.rs @@ -1,6 +1,6 @@ //@no-rustfix: has placeholders -#![deny(clippy::panicking_unwrap, clippy::unnecessary_unwrap)] -#![allow( +#![warn(clippy::panicking_unwrap, clippy::unnecessary_unwrap)] +#![expect( clippy::if_same_then_else, clippy::branches_sharing_code, clippy::unnecessary_literal_unwrap @@ -9,7 +9,6 @@ macro_rules! m { ($a:expr) => { if $a.is_some() { - // unnecessary $a.unwrap(); //~^ unnecessary_unwrap } @@ -43,90 +42,71 @@ macro_rules! checks_some { fn main() { let x = Some(()); if x.is_some() { - // unnecessary x.unwrap(); //~^ unnecessary_unwrap - // unnecessary x.expect("an error message"); //~^ unnecessary_unwrap } else { - // will panic x.unwrap(); //~^ panicking_unwrap - // will panic x.expect("an error message"); //~^ panicking_unwrap } if x.is_none() { - // will panic x.unwrap(); //~^ panicking_unwrap } else { - // unnecessary x.unwrap(); //~^ unnecessary_unwrap } m!(x); - // ok checks_in_param!(x.is_some(), x.unwrap()); - // ok checks_unwrap!(x, x.unwrap()); - // ok checks_some!(x.is_some(), x); let mut x: Result<(), ()> = Ok(()); if x.is_ok() { - // unnecessary x.unwrap(); //~^ unnecessary_unwrap - // unnecessary x.expect("an error message"); //~^ unnecessary_unwrap - // will panic x.unwrap_err(); //~^ panicking_unwrap } else { - // will panic x.unwrap(); //~^ panicking_unwrap - // will panic x.expect("an error message"); //~^ panicking_unwrap - // unnecessary x.unwrap_err(); //~^ unnecessary_unwrap } if x.is_err() { - // will panic x.unwrap(); //~^ panicking_unwrap - // unnecessary x.unwrap_err(); //~^ unnecessary_unwrap } else { - // unnecessary x.unwrap(); //~^ unnecessary_unwrap - // will panic x.unwrap_err(); //~^ panicking_unwrap } if x.is_ok() { x = Err(()); - // not unnecessary because of mutation of x + // not unnecessary because of mutation of `x` // it will always panic but the lint is not smart enough to see this (it only // checks if conditions). x.unwrap(); } else { x = Ok(()); - // not unnecessary because of mutation of x + // not unnecessary because of mutation of `x` // it will always panic but the lint is not smart enough to see this (it // only checks if conditions). x.unwrap_err(); @@ -175,13 +155,11 @@ fn issue11371() { //~^ panicking_unwrap } - // This should not lint. Statics are, at the time of writing, not linted on anyway, - // but if at some point they are supported by this lint, it should correctly see that - // `X` is being mutated and not suggest `if let Some(..) = X {}` + // This should not lint and suggest `if let Some(..) = X {}`, as `X` is being mutated static mut X: Option = Some(123); unsafe { + #[expect(static_mut_refs)] if X.is_some() { - //~^ ERROR: creating a shared reference X = None; X.unwrap(); } @@ -299,17 +277,13 @@ fn check_expect() { let x = Some(()); if x.is_some() { #[expect(clippy::unnecessary_unwrap)] - // unnecessary x.unwrap(); #[expect(clippy::unnecessary_unwrap)] - // unnecessary x.expect("an error message"); } else { #[expect(clippy::panicking_unwrap)] - // will panic x.unwrap(); #[expect(clippy::panicking_unwrap)] - // will panic x.expect("an error message"); } } diff --git a/tests/ui/checked_unwrap/simple_conditionals.stderr b/tests/ui/checked_unwrap/simple_conditionals.stderr index 2007a85954137..ad234ee1d96a5 100644 --- a/tests/ui/checked_unwrap/simple_conditionals.stderr +++ b/tests/ui/checked_unwrap/simple_conditionals.stderr @@ -1,20 +1,16 @@ error: called `unwrap` on `x` after checking its variant with `is_some` - --> tests/ui/checked_unwrap/simple_conditionals.rs:47:9 + --> tests/ui/checked_unwrap/simple_conditionals.rs:45:9 | LL | if x.is_some() { | -------------- help: try: `if let Some() = x` -LL | // unnecessary LL | x.unwrap(); | ^^^^^^^^^^ | -note: the lint level is defined here - --> tests/ui/checked_unwrap/simple_conditionals.rs:2:35 - | -LL | #![deny(clippy::panicking_unwrap, clippy::unnecessary_unwrap)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: `-D clippy::unnecessary-unwrap` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::unnecessary_unwrap)]` error: called `expect` on `x` after checking its variant with `is_some` - --> tests/ui/checked_unwrap/simple_conditionals.rs:51:9 + --> tests/ui/checked_unwrap/simple_conditionals.rs:48:9 | LL | if x.is_some() { | -------------- help: try: `if let Some() = x` @@ -23,7 +19,7 @@ LL | x.expect("an error message"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: this call to `unwrap()` will always panic - --> tests/ui/checked_unwrap/simple_conditionals.rs:55:9 + --> tests/ui/checked_unwrap/simple_conditionals.rs:51:9 | LL | if x.is_some() { | ----------- because of this check @@ -31,14 +27,11 @@ LL | if x.is_some() { LL | x.unwrap(); | ^^^^^^^^^^ | -note: the lint level is defined here - --> tests/ui/checked_unwrap/simple_conditionals.rs:2:9 - | -LL | #![deny(clippy::panicking_unwrap, clippy::unnecessary_unwrap)] - | ^^^^^^^^^^^^^^^^^^^^^^^^ + = note: `-D clippy::panicking-unwrap` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::panicking_unwrap)]` error: this call to `expect()` will always panic - --> tests/ui/checked_unwrap/simple_conditionals.rs:59:9 + --> tests/ui/checked_unwrap/simple_conditionals.rs:54:9 | LL | if x.is_some() { | ----------- because of this check @@ -47,16 +40,15 @@ LL | x.expect("an error message"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: this call to `unwrap()` will always panic - --> tests/ui/checked_unwrap/simple_conditionals.rs:64:9 + --> tests/ui/checked_unwrap/simple_conditionals.rs:58:9 | LL | if x.is_none() { | ----------- because of this check -LL | // will panic LL | x.unwrap(); | ^^^^^^^^^^ error: called `unwrap` on `x` after checking its variant with `is_none` - --> tests/ui/checked_unwrap/simple_conditionals.rs:68:9 + --> tests/ui/checked_unwrap/simple_conditionals.rs:61:9 | LL | if x.is_none() { | -------------- help: try: `if let Some() = x` @@ -65,11 +57,10 @@ LL | x.unwrap(); | ^^^^^^^^^^ error: called `unwrap` on `x` after checking its variant with `is_some` - --> tests/ui/checked_unwrap/simple_conditionals.rs:13:13 + --> tests/ui/checked_unwrap/simple_conditionals.rs:12:13 | LL | if $a.is_some() { | --------------- help: try: `if let Some() = x` -LL | // unnecessary LL | $a.unwrap(); | ^^^^^^^^^^^ ... @@ -79,16 +70,15 @@ LL | m!(x); = note: this error originates in the macro `m` (in Nightly builds, run with -Z macro-backtrace for more info) error: called `unwrap` on `x` after checking its variant with `is_ok` - --> tests/ui/checked_unwrap/simple_conditionals.rs:81:9 + --> tests/ui/checked_unwrap/simple_conditionals.rs:70:9 | LL | if x.is_ok() { | ------------ help: try: `if let Ok() = x` -LL | // unnecessary LL | x.unwrap(); | ^^^^^^^^^^ error: called `expect` on `x` after checking its variant with `is_ok` - --> tests/ui/checked_unwrap/simple_conditionals.rs:85:9 + --> tests/ui/checked_unwrap/simple_conditionals.rs:73:9 | LL | if x.is_ok() { | ------------ help: try: `if let Ok() = x` @@ -97,7 +87,7 @@ LL | x.expect("an error message"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: this call to `unwrap_err()` will always panic - --> tests/ui/checked_unwrap/simple_conditionals.rs:89:9 + --> tests/ui/checked_unwrap/simple_conditionals.rs:76:9 | LL | if x.is_ok() { | --------- because of this check @@ -106,7 +96,7 @@ LL | x.unwrap_err(); | ^^^^^^^^^^^^^^ error: this call to `unwrap()` will always panic - --> tests/ui/checked_unwrap/simple_conditionals.rs:93:9 + --> tests/ui/checked_unwrap/simple_conditionals.rs:79:9 | LL | if x.is_ok() { | --------- because of this check @@ -115,7 +105,7 @@ LL | x.unwrap(); | ^^^^^^^^^^ error: this call to `expect()` will always panic - --> tests/ui/checked_unwrap/simple_conditionals.rs:97:9 + --> tests/ui/checked_unwrap/simple_conditionals.rs:82:9 | LL | if x.is_ok() { | --------- because of this check @@ -124,7 +114,7 @@ LL | x.expect("an error message"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: called `unwrap_err` on `x` after checking its variant with `is_ok` - --> tests/ui/checked_unwrap/simple_conditionals.rs:101:9 + --> tests/ui/checked_unwrap/simple_conditionals.rs:85:9 | LL | if x.is_ok() { | ------------ help: try: `if let Err() = x` @@ -133,16 +123,15 @@ LL | x.unwrap_err(); | ^^^^^^^^^^^^^^ error: this call to `unwrap()` will always panic - --> tests/ui/checked_unwrap/simple_conditionals.rs:106:9 + --> tests/ui/checked_unwrap/simple_conditionals.rs:89:9 | LL | if x.is_err() { | ---------- because of this check -LL | // will panic LL | x.unwrap(); | ^^^^^^^^^^ error: called `unwrap_err` on `x` after checking its variant with `is_err` - --> tests/ui/checked_unwrap/simple_conditionals.rs:110:9 + --> tests/ui/checked_unwrap/simple_conditionals.rs:92:9 | LL | if x.is_err() { | ------------- help: try: `if let Err() = x` @@ -151,7 +140,7 @@ LL | x.unwrap_err(); | ^^^^^^^^^^^^^^ error: called `unwrap` on `x` after checking its variant with `is_err` - --> tests/ui/checked_unwrap/simple_conditionals.rs:114:9 + --> tests/ui/checked_unwrap/simple_conditionals.rs:95:9 | LL | if x.is_err() { | ------------- help: try: `if let Ok() = x` @@ -160,7 +149,7 @@ LL | x.unwrap(); | ^^^^^^^^^^ error: this call to `unwrap_err()` will always panic - --> tests/ui/checked_unwrap/simple_conditionals.rs:118:9 + --> tests/ui/checked_unwrap/simple_conditionals.rs:98:9 | LL | if x.is_err() { | ---------- because of this check @@ -169,7 +158,7 @@ LL | x.unwrap_err(); | ^^^^^^^^^^^^^^ error: called `unwrap` on `option` after checking its variant with `is_some` - --> tests/ui/checked_unwrap/simple_conditionals.rs:143:9 + --> tests/ui/checked_unwrap/simple_conditionals.rs:123:9 | LL | if option.is_some() { | ------------------- help: try: `if let Some() = &option` @@ -177,7 +166,7 @@ LL | option.as_ref().unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^ error: this call to `unwrap()` will always panic - --> tests/ui/checked_unwrap/simple_conditionals.rs:146:9 + --> tests/ui/checked_unwrap/simple_conditionals.rs:126:9 | LL | if option.is_some() { | ---------------- because of this check @@ -186,7 +175,7 @@ LL | option.as_ref().unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^ error: called `unwrap` on `result` after checking its variant with `is_ok` - --> tests/ui/checked_unwrap/simple_conditionals.rs:153:9 + --> tests/ui/checked_unwrap/simple_conditionals.rs:133:9 | LL | if result.is_ok() { | ----------------- help: try: `if let Ok() = &result` @@ -194,7 +183,7 @@ LL | result.as_ref().unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^ error: this call to `unwrap()` will always panic - --> tests/ui/checked_unwrap/simple_conditionals.rs:156:9 + --> tests/ui/checked_unwrap/simple_conditionals.rs:136:9 | LL | if result.is_ok() { | -------------- because of this check @@ -203,7 +192,7 @@ LL | result.as_ref().unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^ error: called `unwrap` on `option` after checking its variant with `is_some` - --> tests/ui/checked_unwrap/simple_conditionals.rs:162:9 + --> tests/ui/checked_unwrap/simple_conditionals.rs:142:9 | LL | if option.is_some() { | ------------------- help: try: `if let Some() = &mut option` @@ -211,7 +200,7 @@ LL | option.as_mut().unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^ error: this call to `unwrap()` will always panic - --> tests/ui/checked_unwrap/simple_conditionals.rs:165:9 + --> tests/ui/checked_unwrap/simple_conditionals.rs:145:9 | LL | if option.is_some() { | ---------------- because of this check @@ -220,7 +209,7 @@ LL | option.as_mut().unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^ error: called `unwrap` on `result` after checking its variant with `is_ok` - --> tests/ui/checked_unwrap/simple_conditionals.rs:171:9 + --> tests/ui/checked_unwrap/simple_conditionals.rs:151:9 | LL | if result.is_ok() { | ----------------- help: try: `if let Ok() = &mut result` @@ -228,7 +217,7 @@ LL | result.as_mut().unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^ error: this call to `unwrap()` will always panic - --> tests/ui/checked_unwrap/simple_conditionals.rs:174:9 + --> tests/ui/checked_unwrap/simple_conditionals.rs:154:9 | LL | if result.is_ok() { | -------------- because of this check @@ -237,7 +226,7 @@ LL | result.as_mut().unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^ error: called `unwrap` on `option` after checking its variant with `is_some` - --> tests/ui/checked_unwrap/simple_conditionals.rs:205:17 + --> tests/ui/checked_unwrap/simple_conditionals.rs:183:17 | LL | if option.is_some() { | ------------------- help: try: `if let Some() = &option` @@ -245,7 +234,7 @@ LL | let _ = option.as_ref().unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^ error: this call to `unwrap()` will always panic - --> tests/ui/checked_unwrap/simple_conditionals.rs:208:17 + --> tests/ui/checked_unwrap/simple_conditionals.rs:186:17 | LL | if option.is_some() { | ---------------- because of this check @@ -254,7 +243,7 @@ LL | let _ = option.as_ref().unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^ error: called `unwrap` on `result` after checking its variant with `is_ok` - --> tests/ui/checked_unwrap/simple_conditionals.rs:216:9 + --> tests/ui/checked_unwrap/simple_conditionals.rs:194:9 | LL | if result.is_ok() { | ----------------- help: try: `if let Ok() = &result` @@ -263,7 +252,7 @@ LL | result.as_ref().unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^ error: this call to `unwrap()` will always panic - --> tests/ui/checked_unwrap/simple_conditionals.rs:220:9 + --> tests/ui/checked_unwrap/simple_conditionals.rs:198:9 | LL | if result.is_ok() { | -------------- because of this check @@ -272,7 +261,7 @@ LL | result.as_ref().unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^ error: called `unwrap` on `x` after checking its variant with `is_some` - --> tests/ui/checked_unwrap/simple_conditionals.rs:246:17 + --> tests/ui/checked_unwrap/simple_conditionals.rs:224:17 | LL | if x.is_some() { | -------------- help: try: `if let Some() = x` @@ -280,7 +269,7 @@ LL | _ = x.unwrap(); | ^^^^^^^^^^ error: this call to `unwrap()` will always panic - --> tests/ui/checked_unwrap/simple_conditionals.rs:249:17 + --> tests/ui/checked_unwrap/simple_conditionals.rs:227:17 | LL | if x.is_some() { | ----------- because of this check @@ -289,7 +278,7 @@ LL | _ = x.unwrap(); | ^^^^^^^^^^ error: called `unwrap` on `r` after checking its variant with `is_ok` - --> tests/ui/checked_unwrap/simple_conditionals.rs:255:17 + --> tests/ui/checked_unwrap/simple_conditionals.rs:233:17 | LL | if r.is_ok() { | ------------ help: try: `if let Ok() = &r` @@ -297,7 +286,7 @@ LL | _ = r.as_ref().unwrap(); | ^^^^^^^^^^^^^^^^^^^ error: this call to `unwrap()` will always panic - --> tests/ui/checked_unwrap/simple_conditionals.rs:258:17 + --> tests/ui/checked_unwrap/simple_conditionals.rs:236:17 | LL | if r.is_ok() { | --------- because of this check @@ -306,7 +295,7 @@ LL | _ = r.as_ref().unwrap(); | ^^^^^^^^^^^^^^^^^^^ error: called `unwrap` on `x` after checking its variant with `is_some` - --> tests/ui/checked_unwrap/simple_conditionals.rs:267:17 + --> tests/ui/checked_unwrap/simple_conditionals.rs:245:17 | LL | if x.is_some() { | -------------- help: try: `if let Some() = x` @@ -314,7 +303,7 @@ LL | _ = x.unwrap(); | ^^^^^^^^^^ error: this call to `unwrap()` will always panic - --> tests/ui/checked_unwrap/simple_conditionals.rs:270:17 + --> tests/ui/checked_unwrap/simple_conditionals.rs:248:17 | LL | if x.is_some() { | ----------- because of this check @@ -323,7 +312,7 @@ LL | _ = x.unwrap(); | ^^^^^^^^^^ error: called `unwrap` on `option` after checking its variant with `is_some` - --> tests/ui/checked_unwrap/simple_conditionals.rs:280:26 + --> tests/ui/checked_unwrap/simple_conditionals.rs:258:26 | LL | if option.is_some() { | ------------------- help: try: `if let Some() = option` @@ -331,7 +320,7 @@ LL | println!("{:?}", option.unwrap()); | ^^^^^^^^^^^^^^^ error: this call to `unwrap()` will always panic - --> tests/ui/checked_unwrap/simple_conditionals.rs:283:26 + --> tests/ui/checked_unwrap/simple_conditionals.rs:261:26 | LL | if option.is_some() { | ---------------- because of this check @@ -340,7 +329,7 @@ LL | println!("{:?}", option.unwrap()); | ^^^^^^^^^^^^^^^ error: called `unwrap` on `result` after checking its variant with `is_ok` - --> tests/ui/checked_unwrap/simple_conditionals.rs:290:26 + --> tests/ui/checked_unwrap/simple_conditionals.rs:268:26 | LL | if result.is_ok() { | ----------------- help: try: `if let Ok() = result` @@ -348,7 +337,7 @@ LL | println!("{:?}", result.unwrap()); | ^^^^^^^^^^^^^^^ error: this call to `unwrap()` will always panic - --> tests/ui/checked_unwrap/simple_conditionals.rs:293:26 + --> tests/ui/checked_unwrap/simple_conditionals.rs:271:26 | LL | if result.is_ok() { | -------------- because of this check @@ -356,15 +345,5 @@ LL | if result.is_ok() { LL | println!("{:?}", result.unwrap()); | ^^^^^^^^^^^^^^^ -error: creating a shared reference to mutable static - --> tests/ui/checked_unwrap/simple_conditionals.rs:183:12 - | -LL | if X.is_some() { - | ^^^^^^^^^^^ shared reference to mutable static - | - = note: for more information, see - = note: shared references to mutable statics are dangerous; it's undefined behavior if the static is mutated or if a mutable reference is created for it while the shared reference lives - = note: `#[deny(static_mut_refs)]` (part of `#[deny(rust_2024_compatibility)]`) on by default - -error: aborting due to 40 previous errors +error: aborting due to 39 previous errors From f13d246318e5548841fafde6cc6857d308d1098f Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Fri, 24 Oct 2025 21:30:18 +0200 Subject: [PATCH 27/60] feat({unnecessary,panicking}_unwrap): lint field accesses --- clippy_lints/src/unwrap.rs | 130 +++++++++++++-- .../ui/checked_unwrap/simple_conditionals.rs | 85 +++++++++- .../checked_unwrap/simple_conditionals.stderr | 150 +++++++++++++----- 3 files changed, 310 insertions(+), 55 deletions(-) diff --git a/clippy_lints/src/unwrap.rs b/clippy_lints/src/unwrap.rs index 0a96ee0920895..a5ef7f8e5559a 100644 --- a/clippy_lints/src/unwrap.rs +++ b/clippy_lints/src/unwrap.rs @@ -1,15 +1,20 @@ +use std::borrow::Cow; + use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::msrvs::Msrv; use clippy_utils::res::{MaybeDef, MaybeResPath}; +use clippy_utils::source::snippet; use clippy_utils::usage::is_potentially_local_place; use clippy_utils::{can_use_if_let_chains, higher, sym}; +use rustc_abi::FieldIdx; use rustc_errors::Applicability; use rustc_hir::intravisit::{FnKind, Visitor, walk_expr, walk_fn}; use rustc_hir::{BinOpKind, Body, Expr, ExprKind, FnDecl, HirId, Node, UnOp}; -use rustc_hir_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceWithHirId}; -use rustc_lint::{LateContext, LateLintPass}; +use rustc_hir_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, Place, PlaceWithHirId}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::hir::nested_filter; +use rustc_middle::hir::place::ProjectionKind; use rustc_middle::mir::FakeReadCause; use rustc_middle::ty::{self, Ty, TyCtxt}; use rustc_session::impl_lint_pass; @@ -114,11 +119,89 @@ impl UnwrappableKind { } } +#[derive(Copy, Clone, Debug, Eq)] +enum Local { + /// `x.opt` + WithFieldAccess { + local_id: HirId, + field_idx: FieldIdx, + /// The span of the whole expression + span: Span, + }, + /// `x` + Pure { local_id: HirId }, +} + +/// Identical to derived impl, but ignores `span` on [`Local::WithFieldAccess`] +impl PartialEq for Local { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + ( + Self::WithFieldAccess { + local_id: self_local_id, + field_idx: self_field_idx, + .. + }, + Self::WithFieldAccess { + local_id: other_local_id, + field_idx: other_field_idx, + .. + }, + ) => self_local_id == other_local_id && self_field_idx == other_field_idx, + ( + Self::Pure { + local_id: self_local_id, + }, + Self::Pure { + local_id: other_local_id, + }, + ) => self_local_id == other_local_id, + _ => false, + } + } +} + +impl Local { + fn snippet(self, cx: &LateContext<'_>) -> Cow<'static, str> { + match self { + Self::WithFieldAccess { span, .. } => snippet(cx.sess(), span, "_"), + Self::Pure { local_id } => cx.tcx.hir_name(local_id).to_string().into(), + } + } + + fn is_potentially_local_place(self, place: &Place<'_>) -> bool { + match self { + Self::WithFieldAccess { + local_id, field_idx, .. + } => { + if is_potentially_local_place(local_id, place) + // If there were projections other than the field projection, err on the side of caution and say + // that they _might_ have mutated the field + // + // The reason we use `<=` and not `==` is that, if there were no projections, then the whole local + // was mutated, which means that our field was mutated as well + && place.projections.len() <= 1 + && place.projections.last().is_none_or(|proj| match proj.kind { + ProjectionKind::Field(f_idx, _) => f_idx == field_idx, + // If this is a projection we don't expect, it _might_ be mutating something + _ => false, + }) + { + true + } else { + false + } + }, + Self::Pure { local_id } => is_potentially_local_place(local_id, place), + } + } +} + /// Contains information about whether a variable can be unwrapped. #[derive(Copy, Clone, Debug)] struct UnwrapInfo<'tcx> { /// The variable that is checked - local_id: HirId, + local: Local, /// The if itself if_expr: &'tcx Expr<'tcx>, /// The check, like `x.is_ok()` @@ -177,7 +260,7 @@ fn collect_unwrap_info<'tcx>( }, ExprKind::Unary(UnOp::Not, expr) => inner(cx, if_expr, expr, branch, !invert, false, out), ExprKind::MethodCall(method_name, receiver, [], _) - if let Some(local_id) = receiver.res_local_id() + if let Some(local) = extract_local(cx, receiver) && let ty = cx.typeck_results().expr_ty(receiver) && let name = method_name.ident.name && let Some((kind, unwrappable)) = option_or_result_call(cx, ty, name) => @@ -185,7 +268,7 @@ fn collect_unwrap_info<'tcx>( let safe_to_unwrap = unwrappable != invert; out.push(UnwrapInfo { - local_id, + local, if_expr, check: expr, check_name: name, @@ -204,6 +287,25 @@ fn collect_unwrap_info<'tcx>( out } +/// Extracts either a local used by itself ([`Local::Pure`]), or a field access to a local +/// ([`Local::WithFieldAccess`]) +fn extract_local(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option { + if let Some(local_id) = expr.res_local_id() { + Some(Local::Pure { local_id }) + } else if let ExprKind::Field(recv, _) = expr.kind + && let Some(local_id) = recv.res_local_id() + && let Some(field_idx) = cx.typeck_results().opt_field_index(expr.hir_id) + { + Some(Local::WithFieldAccess { + local_id, + field_idx, + span: expr.span, + }) + } else { + None + } +} + /// A HIR visitor delegate that checks if a local variable of type `Option` or `Result` is mutated, /// *except* for if `.as_mut()` is called. /// The reason for why we allow that one specifically is that `.as_mut()` cannot change @@ -213,7 +315,7 @@ fn collect_unwrap_info<'tcx>( /// (And also `.as_mut()` is a somewhat common method that is still worth linting on.) struct MutationVisitor<'tcx> { is_mutated: bool, - local_id: HirId, + local: Local, tcx: TyCtxt<'tcx>, } @@ -237,7 +339,7 @@ fn is_as_mut_use(tcx: TyCtxt<'_>, expr_id: HirId) -> bool { impl<'tcx> Delegate<'tcx> for MutationVisitor<'tcx> { fn borrow(&mut self, cat: &PlaceWithHirId<'tcx>, diag_expr_id: HirId, bk: ty::BorrowKind) { if let ty::BorrowKind::Mutable = bk - && is_potentially_local_place(self.local_id, &cat.place) + && self.local.is_potentially_local_place(&cat.place) && !is_as_mut_use(self.tcx, diag_expr_id) { self.is_mutated = true; @@ -245,7 +347,7 @@ impl<'tcx> Delegate<'tcx> for MutationVisitor<'tcx> { } fn mutate(&mut self, cat: &PlaceWithHirId<'tcx>, _: HirId) { - if is_potentially_local_place(self.local_id, &cat.place) { + if self.local.is_potentially_local_place(&cat.place) { self.is_mutated = true; } } @@ -269,7 +371,7 @@ impl<'tcx> UnwrappableVariablesVisitor<'_, 'tcx> { for unwrap_info in collect_unwrap_info(self.cx, if_expr, cond, branch, else_branch, true) { let mut delegate = MutationVisitor { is_mutated: false, - local_id: unwrap_info.local_id, + local: unwrap_info.local, tcx: self.cx.tcx, }; @@ -331,17 +433,17 @@ impl<'tcx> Visitor<'tcx> for UnwrappableVariablesVisitor<'_, 'tcx> { // find `unwrap[_err]()` or `expect("...")` calls: if let ExprKind::MethodCall(method_name, self_arg, ..) = expr.kind && let (self_arg, as_ref_kind) = consume_option_as_ref(self_arg) - && let Some(id) = self_arg.res_local_id() + && let Some(local) = extract_local(self.cx, self_arg) && matches!(method_name.ident.name, sym::unwrap | sym::expect | sym::unwrap_err) && let call_to_unwrap = matches!(method_name.ident.name, sym::unwrap | sym::expect) - && let Some(unwrappable) = self.unwrappables.iter().find(|u| u.local_id == id) + && let Some(unwrappable) = self.unwrappables.iter().find(|u| u.local == local) // Span contexts should not differ with the conditional branch && let span_ctxt = expr.span.ctxt() && unwrappable.branch.span.ctxt() == span_ctxt && unwrappable.check.span.ctxt() == span_ctxt { if call_to_unwrap == unwrappable.safe_to_unwrap { - let unwrappable_variable_name = self.cx.tcx.hir_name(unwrappable.local_id); + let unwrappable_variable_str = unwrappable.local.snippet(self.cx); span_lint_hir_and_then( self.cx, @@ -349,7 +451,7 @@ impl<'tcx> Visitor<'tcx> for UnwrappableVariablesVisitor<'_, 'tcx> { expr.hir_id, expr.span, format!( - "called `{}` on `{unwrappable_variable_name}` after checking its variant with `{}`", + "called `{}` on `{unwrappable_variable_str}` after checking its variant with `{}`", method_name.ident.name, unwrappable.check_name, ), |diag| { @@ -358,7 +460,7 @@ impl<'tcx> Visitor<'tcx> for UnwrappableVariablesVisitor<'_, 'tcx> { unwrappable.check.span.with_lo(unwrappable.if_expr.span.lo()), "try", format!( - "if let {suggested_pattern} = {borrow_prefix}{unwrappable_variable_name}", + "if let {suggested_pattern} = {borrow_prefix}{unwrappable_variable_str}", suggested_pattern = if call_to_unwrap { unwrappable.kind.success_variant_pattern() } else { diff --git a/tests/ui/checked_unwrap/simple_conditionals.rs b/tests/ui/checked_unwrap/simple_conditionals.rs index b762693071597..f5b11f4813f88 100644 --- a/tests/ui/checked_unwrap/simple_conditionals.rs +++ b/tests/ui/checked_unwrap/simple_conditionals.rs @@ -3,7 +3,8 @@ #![expect( clippy::if_same_then_else, clippy::branches_sharing_code, - clippy::unnecessary_literal_unwrap + clippy::unnecessary_literal_unwrap, + clippy::self_assignment )] macro_rules! m { @@ -287,3 +288,85 @@ fn check_expect() { x.expect("an error message"); } } + +fn issue15321() { + struct Soption { + option: Option, + other: bool, + } + let mut sopt = Soption { + option: Some(true), + other: true, + }; + // Lint: nothing was mutated + let _res = if sopt.option.is_some() { + sopt.option.unwrap() + //~^ unnecessary_unwrap + } else { + sopt.option.unwrap() + //~^ panicking_unwrap + }; + // Lint: an unrelated field was mutated + let _res = if sopt.option.is_some() { + sopt.other = false; + sopt.option.unwrap() + //~^ unnecessary_unwrap + } else { + sopt.other = false; + sopt.option.unwrap() + //~^ panicking_unwrap + }; + // No lint: the whole local was mutated + let _res = if sopt.option.is_some() { + sopt = sopt; + sopt.option.unwrap() + } else { + sopt.option = None; + sopt.option.unwrap() + }; + // No lint: the field we're looking at was mutated + let _res = if sopt.option.is_some() { + sopt = sopt; + sopt.option.unwrap() + } else { + sopt.option = None; + sopt.option.unwrap() + }; + + struct Toption(Option, bool); + let mut topt = Toption(Some(true), true); + // Lint: nothing was mutated + let _res = if topt.0.is_some() { + topt.0.unwrap() + //~^ unnecessary_unwrap + } else { + topt.0.unwrap() + //~^ panicking_unwrap + }; + // Lint: an unrelated field was mutated + let _res = if topt.0.is_some() { + topt.1 = false; + topt.0.unwrap() + //~^ unnecessary_unwrap + } else { + topt.1 = false; + topt.0.unwrap() + //~^ panicking_unwrap + }; + // No lint: the whole local was mutated + let _res = if topt.0.is_some() { + topt = topt; + topt.0.unwrap() + } else { + topt = topt; + topt.0.unwrap() + }; + // No lint: the field we're looking at was mutated + let _res = if topt.0.is_some() { + topt.0 = None; + topt.0.unwrap() + } else { + topt.0 = None; + topt.0.unwrap() + }; +} diff --git a/tests/ui/checked_unwrap/simple_conditionals.stderr b/tests/ui/checked_unwrap/simple_conditionals.stderr index ad234ee1d96a5..ac9dbd8ac57a4 100644 --- a/tests/ui/checked_unwrap/simple_conditionals.stderr +++ b/tests/ui/checked_unwrap/simple_conditionals.stderr @@ -1,5 +1,5 @@ error: called `unwrap` on `x` after checking its variant with `is_some` - --> tests/ui/checked_unwrap/simple_conditionals.rs:45:9 + --> tests/ui/checked_unwrap/simple_conditionals.rs:46:9 | LL | if x.is_some() { | -------------- help: try: `if let Some() = x` @@ -10,7 +10,7 @@ LL | x.unwrap(); = help: to override `-D warnings` add `#[allow(clippy::unnecessary_unwrap)]` error: called `expect` on `x` after checking its variant with `is_some` - --> tests/ui/checked_unwrap/simple_conditionals.rs:48:9 + --> tests/ui/checked_unwrap/simple_conditionals.rs:49:9 | LL | if x.is_some() { | -------------- help: try: `if let Some() = x` @@ -19,7 +19,7 @@ LL | x.expect("an error message"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: this call to `unwrap()` will always panic - --> tests/ui/checked_unwrap/simple_conditionals.rs:51:9 + --> tests/ui/checked_unwrap/simple_conditionals.rs:52:9 | LL | if x.is_some() { | ----------- because of this check @@ -31,7 +31,7 @@ LL | x.unwrap(); = help: to override `-D warnings` add `#[allow(clippy::panicking_unwrap)]` error: this call to `expect()` will always panic - --> tests/ui/checked_unwrap/simple_conditionals.rs:54:9 + --> tests/ui/checked_unwrap/simple_conditionals.rs:55:9 | LL | if x.is_some() { | ----------- because of this check @@ -40,7 +40,7 @@ LL | x.expect("an error message"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: this call to `unwrap()` will always panic - --> tests/ui/checked_unwrap/simple_conditionals.rs:58:9 + --> tests/ui/checked_unwrap/simple_conditionals.rs:59:9 | LL | if x.is_none() { | ----------- because of this check @@ -48,7 +48,7 @@ LL | x.unwrap(); | ^^^^^^^^^^ error: called `unwrap` on `x` after checking its variant with `is_none` - --> tests/ui/checked_unwrap/simple_conditionals.rs:61:9 + --> tests/ui/checked_unwrap/simple_conditionals.rs:62:9 | LL | if x.is_none() { | -------------- help: try: `if let Some() = x` @@ -57,7 +57,7 @@ LL | x.unwrap(); | ^^^^^^^^^^ error: called `unwrap` on `x` after checking its variant with `is_some` - --> tests/ui/checked_unwrap/simple_conditionals.rs:12:13 + --> tests/ui/checked_unwrap/simple_conditionals.rs:13:13 | LL | if $a.is_some() { | --------------- help: try: `if let Some() = x` @@ -70,7 +70,7 @@ LL | m!(x); = note: this error originates in the macro `m` (in Nightly builds, run with -Z macro-backtrace for more info) error: called `unwrap` on `x` after checking its variant with `is_ok` - --> tests/ui/checked_unwrap/simple_conditionals.rs:70:9 + --> tests/ui/checked_unwrap/simple_conditionals.rs:71:9 | LL | if x.is_ok() { | ------------ help: try: `if let Ok() = x` @@ -78,7 +78,7 @@ LL | x.unwrap(); | ^^^^^^^^^^ error: called `expect` on `x` after checking its variant with `is_ok` - --> tests/ui/checked_unwrap/simple_conditionals.rs:73:9 + --> tests/ui/checked_unwrap/simple_conditionals.rs:74:9 | LL | if x.is_ok() { | ------------ help: try: `if let Ok() = x` @@ -87,7 +87,7 @@ LL | x.expect("an error message"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: this call to `unwrap_err()` will always panic - --> tests/ui/checked_unwrap/simple_conditionals.rs:76:9 + --> tests/ui/checked_unwrap/simple_conditionals.rs:77:9 | LL | if x.is_ok() { | --------- because of this check @@ -96,7 +96,7 @@ LL | x.unwrap_err(); | ^^^^^^^^^^^^^^ error: this call to `unwrap()` will always panic - --> tests/ui/checked_unwrap/simple_conditionals.rs:79:9 + --> tests/ui/checked_unwrap/simple_conditionals.rs:80:9 | LL | if x.is_ok() { | --------- because of this check @@ -105,7 +105,7 @@ LL | x.unwrap(); | ^^^^^^^^^^ error: this call to `expect()` will always panic - --> tests/ui/checked_unwrap/simple_conditionals.rs:82:9 + --> tests/ui/checked_unwrap/simple_conditionals.rs:83:9 | LL | if x.is_ok() { | --------- because of this check @@ -114,7 +114,7 @@ LL | x.expect("an error message"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: called `unwrap_err` on `x` after checking its variant with `is_ok` - --> tests/ui/checked_unwrap/simple_conditionals.rs:85:9 + --> tests/ui/checked_unwrap/simple_conditionals.rs:86:9 | LL | if x.is_ok() { | ------------ help: try: `if let Err() = x` @@ -123,7 +123,7 @@ LL | x.unwrap_err(); | ^^^^^^^^^^^^^^ error: this call to `unwrap()` will always panic - --> tests/ui/checked_unwrap/simple_conditionals.rs:89:9 + --> tests/ui/checked_unwrap/simple_conditionals.rs:90:9 | LL | if x.is_err() { | ---------- because of this check @@ -131,7 +131,7 @@ LL | x.unwrap(); | ^^^^^^^^^^ error: called `unwrap_err` on `x` after checking its variant with `is_err` - --> tests/ui/checked_unwrap/simple_conditionals.rs:92:9 + --> tests/ui/checked_unwrap/simple_conditionals.rs:93:9 | LL | if x.is_err() { | ------------- help: try: `if let Err() = x` @@ -140,7 +140,7 @@ LL | x.unwrap_err(); | ^^^^^^^^^^^^^^ error: called `unwrap` on `x` after checking its variant with `is_err` - --> tests/ui/checked_unwrap/simple_conditionals.rs:95:9 + --> tests/ui/checked_unwrap/simple_conditionals.rs:96:9 | LL | if x.is_err() { | ------------- help: try: `if let Ok() = x` @@ -149,7 +149,7 @@ LL | x.unwrap(); | ^^^^^^^^^^ error: this call to `unwrap_err()` will always panic - --> tests/ui/checked_unwrap/simple_conditionals.rs:98:9 + --> tests/ui/checked_unwrap/simple_conditionals.rs:99:9 | LL | if x.is_err() { | ---------- because of this check @@ -158,7 +158,7 @@ LL | x.unwrap_err(); | ^^^^^^^^^^^^^^ error: called `unwrap` on `option` after checking its variant with `is_some` - --> tests/ui/checked_unwrap/simple_conditionals.rs:123:9 + --> tests/ui/checked_unwrap/simple_conditionals.rs:124:9 | LL | if option.is_some() { | ------------------- help: try: `if let Some() = &option` @@ -166,7 +166,7 @@ LL | option.as_ref().unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^ error: this call to `unwrap()` will always panic - --> tests/ui/checked_unwrap/simple_conditionals.rs:126:9 + --> tests/ui/checked_unwrap/simple_conditionals.rs:127:9 | LL | if option.is_some() { | ---------------- because of this check @@ -175,7 +175,7 @@ LL | option.as_ref().unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^ error: called `unwrap` on `result` after checking its variant with `is_ok` - --> tests/ui/checked_unwrap/simple_conditionals.rs:133:9 + --> tests/ui/checked_unwrap/simple_conditionals.rs:134:9 | LL | if result.is_ok() { | ----------------- help: try: `if let Ok() = &result` @@ -183,7 +183,7 @@ LL | result.as_ref().unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^ error: this call to `unwrap()` will always panic - --> tests/ui/checked_unwrap/simple_conditionals.rs:136:9 + --> tests/ui/checked_unwrap/simple_conditionals.rs:137:9 | LL | if result.is_ok() { | -------------- because of this check @@ -192,7 +192,7 @@ LL | result.as_ref().unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^ error: called `unwrap` on `option` after checking its variant with `is_some` - --> tests/ui/checked_unwrap/simple_conditionals.rs:142:9 + --> tests/ui/checked_unwrap/simple_conditionals.rs:143:9 | LL | if option.is_some() { | ------------------- help: try: `if let Some() = &mut option` @@ -200,7 +200,7 @@ LL | option.as_mut().unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^ error: this call to `unwrap()` will always panic - --> tests/ui/checked_unwrap/simple_conditionals.rs:145:9 + --> tests/ui/checked_unwrap/simple_conditionals.rs:146:9 | LL | if option.is_some() { | ---------------- because of this check @@ -209,7 +209,7 @@ LL | option.as_mut().unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^ error: called `unwrap` on `result` after checking its variant with `is_ok` - --> tests/ui/checked_unwrap/simple_conditionals.rs:151:9 + --> tests/ui/checked_unwrap/simple_conditionals.rs:152:9 | LL | if result.is_ok() { | ----------------- help: try: `if let Ok() = &mut result` @@ -217,7 +217,7 @@ LL | result.as_mut().unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^ error: this call to `unwrap()` will always panic - --> tests/ui/checked_unwrap/simple_conditionals.rs:154:9 + --> tests/ui/checked_unwrap/simple_conditionals.rs:155:9 | LL | if result.is_ok() { | -------------- because of this check @@ -226,7 +226,7 @@ LL | result.as_mut().unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^ error: called `unwrap` on `option` after checking its variant with `is_some` - --> tests/ui/checked_unwrap/simple_conditionals.rs:183:17 + --> tests/ui/checked_unwrap/simple_conditionals.rs:184:17 | LL | if option.is_some() { | ------------------- help: try: `if let Some() = &option` @@ -234,7 +234,7 @@ LL | let _ = option.as_ref().unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^ error: this call to `unwrap()` will always panic - --> tests/ui/checked_unwrap/simple_conditionals.rs:186:17 + --> tests/ui/checked_unwrap/simple_conditionals.rs:187:17 | LL | if option.is_some() { | ---------------- because of this check @@ -243,7 +243,7 @@ LL | let _ = option.as_ref().unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^ error: called `unwrap` on `result` after checking its variant with `is_ok` - --> tests/ui/checked_unwrap/simple_conditionals.rs:194:9 + --> tests/ui/checked_unwrap/simple_conditionals.rs:195:9 | LL | if result.is_ok() { | ----------------- help: try: `if let Ok() = &result` @@ -252,7 +252,7 @@ LL | result.as_ref().unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^ error: this call to `unwrap()` will always panic - --> tests/ui/checked_unwrap/simple_conditionals.rs:198:9 + --> tests/ui/checked_unwrap/simple_conditionals.rs:199:9 | LL | if result.is_ok() { | -------------- because of this check @@ -261,7 +261,7 @@ LL | result.as_ref().unwrap(); | ^^^^^^^^^^^^^^^^^^^^^^^^ error: called `unwrap` on `x` after checking its variant with `is_some` - --> tests/ui/checked_unwrap/simple_conditionals.rs:224:17 + --> tests/ui/checked_unwrap/simple_conditionals.rs:225:17 | LL | if x.is_some() { | -------------- help: try: `if let Some() = x` @@ -269,7 +269,7 @@ LL | _ = x.unwrap(); | ^^^^^^^^^^ error: this call to `unwrap()` will always panic - --> tests/ui/checked_unwrap/simple_conditionals.rs:227:17 + --> tests/ui/checked_unwrap/simple_conditionals.rs:228:17 | LL | if x.is_some() { | ----------- because of this check @@ -278,7 +278,7 @@ LL | _ = x.unwrap(); | ^^^^^^^^^^ error: called `unwrap` on `r` after checking its variant with `is_ok` - --> tests/ui/checked_unwrap/simple_conditionals.rs:233:17 + --> tests/ui/checked_unwrap/simple_conditionals.rs:234:17 | LL | if r.is_ok() { | ------------ help: try: `if let Ok() = &r` @@ -286,7 +286,7 @@ LL | _ = r.as_ref().unwrap(); | ^^^^^^^^^^^^^^^^^^^ error: this call to `unwrap()` will always panic - --> tests/ui/checked_unwrap/simple_conditionals.rs:236:17 + --> tests/ui/checked_unwrap/simple_conditionals.rs:237:17 | LL | if r.is_ok() { | --------- because of this check @@ -295,7 +295,7 @@ LL | _ = r.as_ref().unwrap(); | ^^^^^^^^^^^^^^^^^^^ error: called `unwrap` on `x` after checking its variant with `is_some` - --> tests/ui/checked_unwrap/simple_conditionals.rs:245:17 + --> tests/ui/checked_unwrap/simple_conditionals.rs:246:17 | LL | if x.is_some() { | -------------- help: try: `if let Some() = x` @@ -303,7 +303,7 @@ LL | _ = x.unwrap(); | ^^^^^^^^^^ error: this call to `unwrap()` will always panic - --> tests/ui/checked_unwrap/simple_conditionals.rs:248:17 + --> tests/ui/checked_unwrap/simple_conditionals.rs:249:17 | LL | if x.is_some() { | ----------- because of this check @@ -312,7 +312,7 @@ LL | _ = x.unwrap(); | ^^^^^^^^^^ error: called `unwrap` on `option` after checking its variant with `is_some` - --> tests/ui/checked_unwrap/simple_conditionals.rs:258:26 + --> tests/ui/checked_unwrap/simple_conditionals.rs:259:26 | LL | if option.is_some() { | ------------------- help: try: `if let Some() = option` @@ -320,7 +320,7 @@ LL | println!("{:?}", option.unwrap()); | ^^^^^^^^^^^^^^^ error: this call to `unwrap()` will always panic - --> tests/ui/checked_unwrap/simple_conditionals.rs:261:26 + --> tests/ui/checked_unwrap/simple_conditionals.rs:262:26 | LL | if option.is_some() { | ---------------- because of this check @@ -329,7 +329,7 @@ LL | println!("{:?}", option.unwrap()); | ^^^^^^^^^^^^^^^ error: called `unwrap` on `result` after checking its variant with `is_ok` - --> tests/ui/checked_unwrap/simple_conditionals.rs:268:26 + --> tests/ui/checked_unwrap/simple_conditionals.rs:269:26 | LL | if result.is_ok() { | ----------------- help: try: `if let Ok() = result` @@ -337,7 +337,7 @@ LL | println!("{:?}", result.unwrap()); | ^^^^^^^^^^^^^^^ error: this call to `unwrap()` will always panic - --> tests/ui/checked_unwrap/simple_conditionals.rs:271:26 + --> tests/ui/checked_unwrap/simple_conditionals.rs:272:26 | LL | if result.is_ok() { | -------------- because of this check @@ -345,5 +345,75 @@ LL | if result.is_ok() { LL | println!("{:?}", result.unwrap()); | ^^^^^^^^^^^^^^^ -error: aborting due to 39 previous errors +error: called `unwrap` on `sopt.option` after checking its variant with `is_some` + --> tests/ui/checked_unwrap/simple_conditionals.rs:303:9 + | +LL | let _res = if sopt.option.is_some() { + | ------------------------ help: try: `if let Some() = sopt.option` +LL | sopt.option.unwrap() + | ^^^^^^^^^^^^^^^^^^^^ + +error: this call to `unwrap()` will always panic + --> tests/ui/checked_unwrap/simple_conditionals.rs:306:9 + | +LL | let _res = if sopt.option.is_some() { + | --------------------- because of this check +... +LL | sopt.option.unwrap() + | ^^^^^^^^^^^^^^^^^^^^ + +error: called `unwrap` on `sopt.option` after checking its variant with `is_some` + --> tests/ui/checked_unwrap/simple_conditionals.rs:312:9 + | +LL | let _res = if sopt.option.is_some() { + | ------------------------ help: try: `if let Some() = sopt.option` +LL | sopt.other = false; +LL | sopt.option.unwrap() + | ^^^^^^^^^^^^^^^^^^^^ + +error: this call to `unwrap()` will always panic + --> tests/ui/checked_unwrap/simple_conditionals.rs:316:9 + | +LL | let _res = if sopt.option.is_some() { + | --------------------- because of this check +... +LL | sopt.option.unwrap() + | ^^^^^^^^^^^^^^^^^^^^ + +error: called `unwrap` on `topt.0` after checking its variant with `is_some` + --> tests/ui/checked_unwrap/simple_conditionals.rs:340:9 + | +LL | let _res = if topt.0.is_some() { + | ------------------- help: try: `if let Some() = topt.0` +LL | topt.0.unwrap() + | ^^^^^^^^^^^^^^^ + +error: this call to `unwrap()` will always panic + --> tests/ui/checked_unwrap/simple_conditionals.rs:343:9 + | +LL | let _res = if topt.0.is_some() { + | ---------------- because of this check +... +LL | topt.0.unwrap() + | ^^^^^^^^^^^^^^^ + +error: called `unwrap` on `topt.0` after checking its variant with `is_some` + --> tests/ui/checked_unwrap/simple_conditionals.rs:349:9 + | +LL | let _res = if topt.0.is_some() { + | ------------------- help: try: `if let Some() = topt.0` +LL | topt.1 = false; +LL | topt.0.unwrap() + | ^^^^^^^^^^^^^^^ + +error: this call to `unwrap()` will always panic + --> tests/ui/checked_unwrap/simple_conditionals.rs:353:9 + | +LL | let _res = if topt.0.is_some() { + | ---------------- because of this check +... +LL | topt.0.unwrap() + | ^^^^^^^^^^^^^^^ + +error: aborting due to 47 previous errors From 0384c20e1a60603ff7d3b48d404a694678a674b3 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Sat, 25 Oct 2025 13:08:51 +0200 Subject: [PATCH 28/60] lint nested field accesses as well --- clippy_lints/src/unwrap.rs | 96 ++++++++++--------- .../ui/checked_unwrap/simple_conditionals.rs | 67 +++++++++++++ .../checked_unwrap/simple_conditionals.stderr | 55 ++++++++++- 3 files changed, 174 insertions(+), 44 deletions(-) diff --git a/clippy_lints/src/unwrap.rs b/clippy_lints/src/unwrap.rs index a5ef7f8e5559a..8ed3df8731b3f 100644 --- a/clippy_lints/src/unwrap.rs +++ b/clippy_lints/src/unwrap.rs @@ -1,4 +1,5 @@ use std::borrow::Cow; +use std::iter; use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_hir_and_then; @@ -119,12 +120,15 @@ impl UnwrappableKind { } } -#[derive(Copy, Clone, Debug, Eq)] +#[derive(Clone, Debug, Eq)] enum Local { - /// `x.opt` + /// `x.field1.field2.field3` WithFieldAccess { local_id: HirId, - field_idx: FieldIdx, + /// The indices of the field accessed. + /// + /// Stored last-to-first, e.g. for the example above: `[field3, field2, field1]` + field_indices: Vec, /// The span of the whole expression span: Span, }, @@ -139,15 +143,15 @@ impl PartialEq for Local { ( Self::WithFieldAccess { local_id: self_local_id, - field_idx: self_field_idx, + field_indices: self_field_indices, .. }, Self::WithFieldAccess { local_id: other_local_id, - field_idx: other_field_idx, + field_indices: other_field_indices, .. }, - ) => self_local_id == other_local_id && self_field_idx == other_field_idx, + ) => self_local_id == other_local_id && self_field_indices == other_field_indices, ( Self::Pure { local_id: self_local_id, @@ -162,43 +166,42 @@ impl PartialEq for Local { } impl Local { - fn snippet(self, cx: &LateContext<'_>) -> Cow<'static, str> { - match self { + fn snippet(&self, cx: &LateContext<'_>) -> Cow<'static, str> { + match *self { Self::WithFieldAccess { span, .. } => snippet(cx.sess(), span, "_"), Self::Pure { local_id } => cx.tcx.hir_name(local_id).to_string().into(), } } - fn is_potentially_local_place(self, place: &Place<'_>) -> bool { + fn is_potentially_local_place(&self, place: &Place<'_>) -> bool { match self { Self::WithFieldAccess { - local_id, field_idx, .. + local_id, + field_indices, + .. } => { - if is_potentially_local_place(local_id, place) - // If there were projections other than the field projection, err on the side of caution and say - // that they _might_ have mutated the field + is_potentially_local_place(*local_id, place) + // If there were projections other than field projections, err on the side of caution and say that they + // _might_ be mutating something. // - // The reason we use `<=` and not `==` is that, if there were no projections, then the whole local - // was mutated, which means that our field was mutated as well - && place.projections.len() <= 1 - && place.projections.last().is_none_or(|proj| match proj.kind { - ProjectionKind::Field(f_idx, _) => f_idx == field_idx, - // If this is a projection we don't expect, it _might_ be mutating something - _ => false, + // The reason we use `<=` and not `==` is that a mutation of `struct` or `struct.field1` should count as + // mutation of the child fields such as `struct.field1.field2` + && place.projections.len() <= field_indices.len() + && iter::zip(&place.projections, field_indices.iter().copied().rev()).all(|(proj, field_idx)| { + match proj.kind { + ProjectionKind::Field(f_idx, _) => f_idx == field_idx, + // If this is a projection we don't expect, it _might_ be mutating something + _ => false, + } }) - { - true - } else { - false - } }, - Self::Pure { local_id } => is_potentially_local_place(local_id, place), + Self::Pure { local_id } => is_potentially_local_place(*local_id, place), } } } /// Contains information about whether a variable can be unwrapped. -#[derive(Copy, Clone, Debug)] +#[derive(Clone, Debug)] struct UnwrapInfo<'tcx> { /// The variable that is checked local: Local, @@ -287,20 +290,27 @@ fn collect_unwrap_info<'tcx>( out } -/// Extracts either a local used by itself ([`Local::Pure`]), or a field access to a local -/// ([`Local::WithFieldAccess`]) -fn extract_local(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option { - if let Some(local_id) = expr.res_local_id() { - Some(Local::Pure { local_id }) - } else if let ExprKind::Field(recv, _) = expr.kind - && let Some(local_id) = recv.res_local_id() +/// Extracts either a local used by itself ([`Local::Pure`]), or (one or more levels of) field +/// access to a local ([`Local::WithFieldAccess`]) +fn extract_local(cx: &LateContext<'_>, mut expr: &Expr<'_>) -> Option { + let span = expr.span; + let mut field_indices = vec![]; + while let ExprKind::Field(recv, _) = expr.kind && let Some(field_idx) = cx.typeck_results().opt_field_index(expr.hir_id) { - Some(Local::WithFieldAccess { - local_id, - field_idx, - span: expr.span, - }) + field_indices.push(field_idx); + expr = recv; + } + if let Some(local_id) = expr.res_local_id() { + if field_indices.is_empty() { + Some(Local::Pure { local_id }) + } else { + Some(Local::WithFieldAccess { + local_id, + field_indices, + span, + }) + } } else { None } @@ -313,9 +323,9 @@ fn extract_local(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option { /// `is_some` + `unwrap` is equivalent to `if let Some(..) = ..`, which it would not be if /// the option is changed to None between `is_some` and `unwrap`, ditto for `Result`. /// (And also `.as_mut()` is a somewhat common method that is still worth linting on.) -struct MutationVisitor<'tcx> { +struct MutationVisitor<'tcx, 'lcl> { is_mutated: bool, - local: Local, + local: &'lcl Local, tcx: TyCtxt<'tcx>, } @@ -336,7 +346,7 @@ fn is_as_mut_use(tcx: TyCtxt<'_>, expr_id: HirId) -> bool { } } -impl<'tcx> Delegate<'tcx> for MutationVisitor<'tcx> { +impl<'tcx> Delegate<'tcx> for MutationVisitor<'tcx, '_> { fn borrow(&mut self, cat: &PlaceWithHirId<'tcx>, diag_expr_id: HirId, bk: ty::BorrowKind) { if let ty::BorrowKind::Mutable = bk && self.local.is_potentially_local_place(&cat.place) @@ -371,7 +381,7 @@ impl<'tcx> UnwrappableVariablesVisitor<'_, 'tcx> { for unwrap_info in collect_unwrap_info(self.cx, if_expr, cond, branch, else_branch, true) { let mut delegate = MutationVisitor { is_mutated: false, - local: unwrap_info.local, + local: &unwrap_info.local, tcx: self.cx.tcx, }; diff --git a/tests/ui/checked_unwrap/simple_conditionals.rs b/tests/ui/checked_unwrap/simple_conditionals.rs index f5b11f4813f88..190f201c62272 100644 --- a/tests/ui/checked_unwrap/simple_conditionals.rs +++ b/tests/ui/checked_unwrap/simple_conditionals.rs @@ -369,4 +369,71 @@ fn issue15321() { topt.0 = None; topt.0.unwrap() }; + + // Nested field accesses get linted as well + struct Soption2 { + other: bool, + option: Soption, + } + let mut sopt2 = Soption2 { + other: true, + option: Soption { + option: Some(true), + other: true, + }, + }; + // Lint: no fields were mutated + let _res = if sopt2.option.option.is_some() { + sopt2.option.option.unwrap() + //~^ unnecessary_unwrap + } else { + sopt2.option.option.unwrap() + //~^ panicking_unwrap + }; + // Lint: an unrelated outer field was mutated -- don't get confused by `Soption2.other` having the + // same `FieldIdx` of 1 as `Soption.option` + let _res = if sopt2.option.option.is_some() { + sopt2.other = false; + sopt2.option.option.unwrap() + //~^ unnecessary_unwrap + } else { + sopt2.other = false; + sopt2.option.option.unwrap() + //~^ panicking_unwrap + }; + // Lint: an unrelated inner field was mutated + let _res = if sopt2.option.option.is_some() { + sopt2.option.other = false; + sopt2.option.option.unwrap() + //~^ unnecessary_unwrap + } else { + sopt2.option.other = false; + sopt2.option.option.unwrap() + //~^ panicking_unwrap + }; + // Don't lint: the whole local was mutated + let _res = if sopt2.option.option.is_some() { + sopt2 = sopt2; + sopt2.option.option.unwrap() + } else { + sopt2 = sopt2; + sopt2.option.option.unwrap() + }; + // Don't lint: a parent field of the field we're looking at was mutated, and with that the + // field we're looking at + let _res = if sopt2.option.option.is_some() { + sopt2.option = sopt; + sopt2.option.option.unwrap() + } else { + sopt2.option = sopt; + sopt2.option.option.unwrap() + }; + // Don't lint: the field we're looking at was mutated directly + let _res = if sopt2.option.option.is_some() { + sopt2.option.option = None; + sopt2.option.option.unwrap() + } else { + sopt2.option.option = None; + sopt2.option.option.unwrap() + }; } diff --git a/tests/ui/checked_unwrap/simple_conditionals.stderr b/tests/ui/checked_unwrap/simple_conditionals.stderr index ac9dbd8ac57a4..08f9bdc979b1b 100644 --- a/tests/ui/checked_unwrap/simple_conditionals.stderr +++ b/tests/ui/checked_unwrap/simple_conditionals.stderr @@ -415,5 +415,58 @@ LL | let _res = if topt.0.is_some() { LL | topt.0.unwrap() | ^^^^^^^^^^^^^^^ -error: aborting due to 47 previous errors +error: called `unwrap` on `sopt2.option.option` after checking its variant with `is_some` + --> tests/ui/checked_unwrap/simple_conditionals.rs:387:9 + | +LL | let _res = if sopt2.option.option.is_some() { + | -------------------------------- help: try: `if let Some() = sopt2.option.option` +LL | sopt2.option.option.unwrap() + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: this call to `unwrap()` will always panic + --> tests/ui/checked_unwrap/simple_conditionals.rs:390:9 + | +LL | let _res = if sopt2.option.option.is_some() { + | ----------------------------- because of this check +... +LL | sopt2.option.option.unwrap() + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: called `unwrap` on `sopt2.option.option` after checking its variant with `is_some` + --> tests/ui/checked_unwrap/simple_conditionals.rs:397:9 + | +LL | let _res = if sopt2.option.option.is_some() { + | -------------------------------- help: try: `if let Some() = sopt2.option.option` +LL | sopt2.other = false; +LL | sopt2.option.option.unwrap() + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: this call to `unwrap()` will always panic + --> tests/ui/checked_unwrap/simple_conditionals.rs:401:9 + | +LL | let _res = if sopt2.option.option.is_some() { + | ----------------------------- because of this check +... +LL | sopt2.option.option.unwrap() + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: called `unwrap` on `sopt2.option.option` after checking its variant with `is_some` + --> tests/ui/checked_unwrap/simple_conditionals.rs:407:9 + | +LL | let _res = if sopt2.option.option.is_some() { + | -------------------------------- help: try: `if let Some() = sopt2.option.option` +LL | sopt2.option.other = false; +LL | sopt2.option.option.unwrap() + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: this call to `unwrap()` will always panic + --> tests/ui/checked_unwrap/simple_conditionals.rs:411:9 + | +LL | let _res = if sopt2.option.option.is_some() { + | ----------------------------- because of this check +... +LL | sopt2.option.option.unwrap() + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 53 previous errors From 215cfedf9a7536b8690029a8ef9da2311d4f9975 Mon Sep 17 00:00:00 2001 From: Ada Alakbarova Date: Sat, 15 Nov 2025 11:22:41 +0100 Subject: [PATCH 29/60] add tests for partial moves --- .../ui/checked_unwrap/simple_conditionals.rs | 35 ++++++++++ .../checked_unwrap/simple_conditionals.stderr | 66 ++++++++++++++----- 2 files changed, 86 insertions(+), 15 deletions(-) diff --git a/tests/ui/checked_unwrap/simple_conditionals.rs b/tests/ui/checked_unwrap/simple_conditionals.rs index 190f201c62272..c6476a7507a1d 100644 --- a/tests/ui/checked_unwrap/simple_conditionals.rs +++ b/tests/ui/checked_unwrap/simple_conditionals.rs @@ -289,6 +289,24 @@ fn check_expect() { } } +fn partial_moves() { + fn borrow_option(_: &Option<()>) {} + + let x = Some(()); + // Using `if let Some(o) = x` won't work here, as `borrow_option` will try to borrow a moved value + if x.is_some() { + borrow_option(&x); + x.unwrap(); + //~^ unnecessary_unwrap + } + // This is fine though, as `if let Some(o) = &x` won't move `x` + if x.is_some() { + borrow_option(&x); + x.as_ref().unwrap(); + //~^ unnecessary_unwrap + } +} + fn issue15321() { struct Soption { option: Option, @@ -436,4 +454,21 @@ fn issue15321() { sopt2.option.option = None; sopt2.option.option.unwrap() }; + + // Partial moves + fn borrow_toption(_: &Toption) {} + + // Using `if let Some(o) = topt.0` won't work here, as `borrow_toption` will try to borrow a + // partially moved value + if topt.0.is_some() { + borrow_toption(&topt); + topt.0.unwrap(); + //~^ unnecessary_unwrap + } + // This is fine though, as `if let Some(o) = &topt.0` won't (partially) move `topt` + if topt.0.is_some() { + borrow_toption(&topt); + topt.0.as_ref().unwrap(); + //~^ unnecessary_unwrap + } } diff --git a/tests/ui/checked_unwrap/simple_conditionals.stderr b/tests/ui/checked_unwrap/simple_conditionals.stderr index 08f9bdc979b1b..be979baa9fe4a 100644 --- a/tests/ui/checked_unwrap/simple_conditionals.stderr +++ b/tests/ui/checked_unwrap/simple_conditionals.stderr @@ -345,8 +345,26 @@ LL | if result.is_ok() { LL | println!("{:?}", result.unwrap()); | ^^^^^^^^^^^^^^^ +error: called `unwrap` on `x` after checking its variant with `is_some` + --> tests/ui/checked_unwrap/simple_conditionals.rs:299:9 + | +LL | if x.is_some() { + | -------------- help: try: `if let Some() = x` +LL | borrow_option(&x); +LL | x.unwrap(); + | ^^^^^^^^^^ + +error: called `unwrap` on `x` after checking its variant with `is_some` + --> tests/ui/checked_unwrap/simple_conditionals.rs:305:9 + | +LL | if x.is_some() { + | -------------- help: try: `if let Some() = &x` +LL | borrow_option(&x); +LL | x.as_ref().unwrap(); + | ^^^^^^^^^^^^^^^^^^^ + error: called `unwrap` on `sopt.option` after checking its variant with `is_some` - --> tests/ui/checked_unwrap/simple_conditionals.rs:303:9 + --> tests/ui/checked_unwrap/simple_conditionals.rs:321:9 | LL | let _res = if sopt.option.is_some() { | ------------------------ help: try: `if let Some() = sopt.option` @@ -354,7 +372,7 @@ LL | sopt.option.unwrap() | ^^^^^^^^^^^^^^^^^^^^ error: this call to `unwrap()` will always panic - --> tests/ui/checked_unwrap/simple_conditionals.rs:306:9 + --> tests/ui/checked_unwrap/simple_conditionals.rs:324:9 | LL | let _res = if sopt.option.is_some() { | --------------------- because of this check @@ -363,7 +381,7 @@ LL | sopt.option.unwrap() | ^^^^^^^^^^^^^^^^^^^^ error: called `unwrap` on `sopt.option` after checking its variant with `is_some` - --> tests/ui/checked_unwrap/simple_conditionals.rs:312:9 + --> tests/ui/checked_unwrap/simple_conditionals.rs:330:9 | LL | let _res = if sopt.option.is_some() { | ------------------------ help: try: `if let Some() = sopt.option` @@ -372,7 +390,7 @@ LL | sopt.option.unwrap() | ^^^^^^^^^^^^^^^^^^^^ error: this call to `unwrap()` will always panic - --> tests/ui/checked_unwrap/simple_conditionals.rs:316:9 + --> tests/ui/checked_unwrap/simple_conditionals.rs:334:9 | LL | let _res = if sopt.option.is_some() { | --------------------- because of this check @@ -381,7 +399,7 @@ LL | sopt.option.unwrap() | ^^^^^^^^^^^^^^^^^^^^ error: called `unwrap` on `topt.0` after checking its variant with `is_some` - --> tests/ui/checked_unwrap/simple_conditionals.rs:340:9 + --> tests/ui/checked_unwrap/simple_conditionals.rs:358:9 | LL | let _res = if topt.0.is_some() { | ------------------- help: try: `if let Some() = topt.0` @@ -389,7 +407,7 @@ LL | topt.0.unwrap() | ^^^^^^^^^^^^^^^ error: this call to `unwrap()` will always panic - --> tests/ui/checked_unwrap/simple_conditionals.rs:343:9 + --> tests/ui/checked_unwrap/simple_conditionals.rs:361:9 | LL | let _res = if topt.0.is_some() { | ---------------- because of this check @@ -398,7 +416,7 @@ LL | topt.0.unwrap() | ^^^^^^^^^^^^^^^ error: called `unwrap` on `topt.0` after checking its variant with `is_some` - --> tests/ui/checked_unwrap/simple_conditionals.rs:349:9 + --> tests/ui/checked_unwrap/simple_conditionals.rs:367:9 | LL | let _res = if topt.0.is_some() { | ------------------- help: try: `if let Some() = topt.0` @@ -407,7 +425,7 @@ LL | topt.0.unwrap() | ^^^^^^^^^^^^^^^ error: this call to `unwrap()` will always panic - --> tests/ui/checked_unwrap/simple_conditionals.rs:353:9 + --> tests/ui/checked_unwrap/simple_conditionals.rs:371:9 | LL | let _res = if topt.0.is_some() { | ---------------- because of this check @@ -416,7 +434,7 @@ LL | topt.0.unwrap() | ^^^^^^^^^^^^^^^ error: called `unwrap` on `sopt2.option.option` after checking its variant with `is_some` - --> tests/ui/checked_unwrap/simple_conditionals.rs:387:9 + --> tests/ui/checked_unwrap/simple_conditionals.rs:405:9 | LL | let _res = if sopt2.option.option.is_some() { | -------------------------------- help: try: `if let Some() = sopt2.option.option` @@ -424,7 +442,7 @@ LL | sopt2.option.option.unwrap() | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: this call to `unwrap()` will always panic - --> tests/ui/checked_unwrap/simple_conditionals.rs:390:9 + --> tests/ui/checked_unwrap/simple_conditionals.rs:408:9 | LL | let _res = if sopt2.option.option.is_some() { | ----------------------------- because of this check @@ -433,7 +451,7 @@ LL | sopt2.option.option.unwrap() | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: called `unwrap` on `sopt2.option.option` after checking its variant with `is_some` - --> tests/ui/checked_unwrap/simple_conditionals.rs:397:9 + --> tests/ui/checked_unwrap/simple_conditionals.rs:415:9 | LL | let _res = if sopt2.option.option.is_some() { | -------------------------------- help: try: `if let Some() = sopt2.option.option` @@ -442,7 +460,7 @@ LL | sopt2.option.option.unwrap() | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: this call to `unwrap()` will always panic - --> tests/ui/checked_unwrap/simple_conditionals.rs:401:9 + --> tests/ui/checked_unwrap/simple_conditionals.rs:419:9 | LL | let _res = if sopt2.option.option.is_some() { | ----------------------------- because of this check @@ -451,7 +469,7 @@ LL | sopt2.option.option.unwrap() | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: called `unwrap` on `sopt2.option.option` after checking its variant with `is_some` - --> tests/ui/checked_unwrap/simple_conditionals.rs:407:9 + --> tests/ui/checked_unwrap/simple_conditionals.rs:425:9 | LL | let _res = if sopt2.option.option.is_some() { | -------------------------------- help: try: `if let Some() = sopt2.option.option` @@ -460,7 +478,7 @@ LL | sopt2.option.option.unwrap() | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: this call to `unwrap()` will always panic - --> tests/ui/checked_unwrap/simple_conditionals.rs:411:9 + --> tests/ui/checked_unwrap/simple_conditionals.rs:429:9 | LL | let _res = if sopt2.option.option.is_some() { | ----------------------------- because of this check @@ -468,5 +486,23 @@ LL | let _res = if sopt2.option.option.is_some() { LL | sopt2.option.option.unwrap() | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 53 previous errors +error: called `unwrap` on `topt.0` after checking its variant with `is_some` + --> tests/ui/checked_unwrap/simple_conditionals.rs:465:9 + | +LL | if topt.0.is_some() { + | ------------------- help: try: `if let Some() = topt.0` +LL | borrow_toption(&topt); +LL | topt.0.unwrap(); + | ^^^^^^^^^^^^^^^ + +error: called `unwrap` on `topt.0` after checking its variant with `is_some` + --> tests/ui/checked_unwrap/simple_conditionals.rs:471:9 + | +LL | if topt.0.is_some() { + | ------------------- help: try: `if let Some() = &topt.0` +LL | borrow_toption(&topt); +LL | topt.0.as_ref().unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 57 previous errors From 1e43c23a5dda05a8769234afd899639ed3d230b9 Mon Sep 17 00:00:00 2001 From: Weihang Lo Date: Sat, 15 Nov 2025 08:07:16 -0500 Subject: [PATCH 30/60] refactor(span): rename source_len to normalized_source_len This is a preparation for introducing a unnormalized source length field --- clippy_config/src/conf.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_config/src/conf.rs b/clippy_config/src/conf.rs index 2a042e6c3d853..8cdd99ac44a8e 100644 --- a/clippy_config/src/conf.rs +++ b/clippy_config/src/conf.rs @@ -108,7 +108,7 @@ struct ConfError { impl ConfError { fn from_toml(file: &SourceFile, error: &toml::de::Error) -> Self { - let span = error.span().unwrap_or(0..file.source_len.0 as usize); + let span = error.span().unwrap_or(0..file.normalized_source_len.0 as usize); Self::spanned(file, error.message(), None, span) } From 44731205b820176e8dbd13fa4eb28b1c90712e34 Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Sat, 15 Nov 2025 01:16:30 +0100 Subject: [PATCH 31/60] Merge commit '62589a21d351f63d77dcd173c31107e9f1d06328' into clippy-subtree-update --- .github/ISSUE_TEMPLATE/bug_report.yml | 6 +- .github/workflows/clippy_mq.yml | 2 - clippy_dev/src/new_lint.rs | 15 +- clippy_lints/src/booleans.rs | 13 +- clippy_lints/src/if_then_some_else_none.rs | 10 +- clippy_lints/src/incompatible_msrv.rs | 66 +- clippy_lints/src/inherent_impl.rs | 5 +- clippy_lints/src/let_if_seq.rs | 144 ++-- clippy_lints/src/lib.rs | 781 +++++++++--------- clippy_lints/src/loops/manual_find.rs | 5 +- clippy_lints/src/loops/mod.rs | 11 +- .../src/loops/unused_enumerate_index.rs | 95 ++- .../src/loops/while_let_on_iterator.rs | 7 +- clippy_lints/src/manual_is_power_of_two.rs | 8 +- clippy_lints/src/manual_option_as_slice.rs | 9 +- clippy_lints/src/match_result_ok.rs | 7 +- clippy_lints/src/matches/manual_filter.rs | 13 +- clippy_lints/src/matches/manual_ok_err.rs | 11 +- clippy_lints/src/matches/manual_utils.rs | 43 +- clippy_lints/src/matches/match_as_ref.rs | 14 +- .../src/matches/match_single_binding.rs | 61 +- .../matches/significant_drop_in_scrutinee.rs | 4 +- clippy_lints/src/matches/single_match.rs | 5 +- clippy_lints/src/mem_replace.rs | 12 +- clippy_lints/src/methods/err_expect.rs | 13 +- .../iter_on_single_or_empty_collections.rs | 15 +- .../src/methods/iter_overeager_cloned.rs | 3 +- clippy_lints/src/methods/mod.rs | 36 +- clippy_lints/src/methods/needless_collect.rs | 33 +- clippy_lints/src/methods/ok_expect.rs | 31 +- .../src/methods/option_map_or_none.rs | 5 +- .../src/methods/result_map_or_else_none.rs | 6 +- .../src/methods/unnecessary_filter_map.rs | 5 +- .../src/methods/unnecessary_literal_unwrap.rs | 32 +- .../src/methods/unused_enumerate_index.rs | 138 ---- .../src/missing_asserts_for_indexing.rs | 57 +- clippy_lints/src/missing_inline.rs | 29 +- clippy_lints/src/module_style.rs | 10 +- .../src/needless_arbitrary_self_type.rs | 108 ++- clippy_lints/src/non_canonical_impls.rs | 5 +- clippy_lints/src/non_copy_const.rs | 6 +- .../src/operators/arithmetic_side_effects.rs | 240 +++--- clippy_lints/src/option_if_let_else.rs | 27 +- clippy_lints/src/ptr/cmp_null.rs | 49 ++ clippy_lints/src/ptr/mod.rs | 202 +++++ clippy_lints/src/ptr/mut_from_ref.rs | 75 ++ clippy_lints/src/{ptr.rs => ptr/ptr_arg.rs} | 489 ++--------- clippy_lints/src/ptr/ptr_eq.rs | 87 ++ clippy_lints/src/question_mark.rs | 2 +- clippy_lints/src/replace_box.rs | 99 ++- clippy_lints/src/returns/let_and_return.rs | 4 +- .../src/significant_drop_tightening.rs | 4 +- clippy_lints/src/single_range_in_vec_init.rs | 19 +- clippy_lints/src/transmute/mod.rs | 7 +- clippy_lints/src/types/rc_buffer.rs | 138 +--- .../src/undocumented_unsafe_blocks.rs | 24 +- .../src/unnecessary_map_on_constructor.rs | 52 +- clippy_lints/src/unnested_or_patterns.rs | 55 +- clippy_lints/src/utils/author.rs | 4 +- clippy_lints/src/utils/dump_hir.rs | 4 +- clippy_lints/src/write.rs | 734 ---------------- clippy_lints/src/write/empty_string.rs | 38 + clippy_lints/src/write/literal.rs | 285 +++++++ clippy_lints/src/write/mod.rs | 354 ++++++++ clippy_lints/src/write/use_debug.rs | 18 + clippy_lints/src/write/with_newline.rs | 78 ++ clippy_utils/README.md | 2 +- clippy_utils/src/ast_utils/mod.rs | 4 +- clippy_utils/src/attrs.rs | 212 +++-- clippy_utils/src/consts.rs | 3 +- clippy_utils/src/hir_utils.rs | 6 +- clippy_utils/src/lib.rs | 44 +- clippy_utils/src/macros.rs | 4 +- clippy_utils/src/numeric_literal.rs | 14 +- clippy_utils/src/qualify_min_const_fn.rs | 3 +- clippy_utils/src/sym.rs | 2 + rust-toolchain.toml | 2 +- tests/ui/arithmetic_side_effects.rs | 56 ++ tests/ui/arithmetic_side_effects.stderr | 282 ++++--- tests/ui/cmp_null.fixed | 3 +- tests/ui/cmp_null.rs | 3 +- tests/ui/cmp_null.stderr | 12 +- tests/ui/fn_to_numeric_cast.32bit.stderr | 46 +- tests/ui/incompatible_msrv.rs | 7 + tests/ui/let_and_return.edition2021.fixed | 10 + tests/ui/let_and_return.edition2024.fixed | 10 + tests/ui/let_and_return.rs | 10 + tests/ui/let_if_seq.rs | 31 + tests/ui/let_if_seq.stderr | 26 +- tests/ui/match_single_binding.fixed | 79 ++ tests/ui/match_single_binding.rs | 81 ++ tests/ui/match_single_binding.stderr | 158 +++- tests/ui/missing_asserts_for_indexing.fixed | 18 +- tests/ui/missing_asserts_for_indexing.rs | 14 + tests/ui/missing_asserts_for_indexing.stderr | 56 +- tests/ui/missing_inline_executable.rs | 3 +- tests/ui/missing_inline_executable.stderr | 11 + tests/ui/missing_inline_test_crate.rs | 10 + tests/ui/multiple_inherent_impl_cfg.rs | 6 +- tests/ui/multiple_inherent_impl_cfg.stderr | 42 +- tests/ui/needless_arbitrary_self_type.stderr | 63 +- ...dless_arbitrary_self_type_unfixable.stderr | 7 +- tests/ui/needless_collect.fixed | 4 + tests/ui/needless_collect.rs | 4 + tests/ui/needless_collect.stderr | 40 +- tests/ui/nonminimal_bool_methods.fixed | 5 + tests/ui/nonminimal_bool_methods.rs | 5 + tests/ui/nonminimal_bool_methods.stderr | 8 +- tests/ui/ok_expect.fixed | 51 ++ tests/ui/ok_expect.rs | 18 + tests/ui/ok_expect.stderr | 86 +- tests/ui/rc_buffer.fixed | 6 +- tests/ui/rc_buffer.rs | 6 +- tests/ui/rc_buffer.stderr | 109 ++- tests/ui/rc_buffer_arc.fixed | 6 +- tests/ui/rc_buffer_arc.rs | 6 +- tests/ui/rc_buffer_arc.stderr | 109 ++- tests/ui/replace_box.fixed | 71 ++ tests/ui/replace_box.rs | 71 ++ tests/ui/replace_box.stderr | 50 +- tests/ui/single_range_in_vec_init.1.fixed | 84 ++ tests/ui/single_range_in_vec_init.2.fixed | 84 ++ tests/ui/single_range_in_vec_init.rs | 18 +- tests/ui/single_range_in_vec_init.stderr | 34 +- .../ui/unnecessary_map_on_constructor.stderr | 16 +- tests/ui/unnecessary_mut_passed.fixed | 2 +- tests/ui/unnecessary_mut_passed.rs | 2 +- tests/ui/unused_enumerate_index.stderr | 32 +- tests/ui/write_literal.fixed | 49 ++ tests/ui/write_literal.rs | 53 ++ tests/ui/write_literal.stderr | 163 +++- tests/ui/write_literal_2.rs | 65 -- tests/ui/write_literal_2.stderr | 165 ---- tests/ui/write_literal_unfixable.rs | 16 + tests/ui/write_literal_unfixable.stderr | 17 + util/gh-pages/index_template.html | 76 +- util/gh-pages/script.js | 520 ++++-------- util/gh-pages/style.css | 14 +- 138 files changed, 4877 insertions(+), 3495 deletions(-) delete mode 100644 clippy_lints/src/methods/unused_enumerate_index.rs create mode 100644 clippy_lints/src/ptr/cmp_null.rs create mode 100644 clippy_lints/src/ptr/mod.rs create mode 100644 clippy_lints/src/ptr/mut_from_ref.rs rename clippy_lints/src/{ptr.rs => ptr/ptr_arg.rs} (50%) create mode 100644 clippy_lints/src/ptr/ptr_eq.rs delete mode 100644 clippy_lints/src/write.rs create mode 100644 clippy_lints/src/write/empty_string.rs create mode 100644 clippy_lints/src/write/literal.rs create mode 100644 clippy_lints/src/write/mod.rs create mode 100644 clippy_lints/src/write/use_debug.rs create mode 100644 clippy_lints/src/write/with_newline.rs create mode 100644 tests/ui/missing_inline_executable.stderr create mode 100644 tests/ui/missing_inline_test_crate.rs create mode 100644 tests/ui/ok_expect.fixed create mode 100644 tests/ui/single_range_in_vec_init.1.fixed create mode 100644 tests/ui/single_range_in_vec_init.2.fixed delete mode 100644 tests/ui/write_literal_2.rs delete mode 100644 tests/ui/write_literal_2.stderr create mode 100644 tests/ui/write_literal_unfixable.rs create mode 100644 tests/ui/write_literal_unfixable.stderr diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index b6f70a7f18300..91aaf1f3644e0 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -20,15 +20,15 @@ body: label: Reproducer description: Please provide the code and steps to reproduce the bug value: | - I tried this code: + Code: ```rust ``` - I expected to see this happen: + Current output: - Instead, this happened: + Desired output: - type: textarea id: version attributes: diff --git a/.github/workflows/clippy_mq.yml b/.github/workflows/clippy_mq.yml index 9d099137449ef..ce15a861bb072 100644 --- a/.github/workflows/clippy_mq.yml +++ b/.github/workflows/clippy_mq.yml @@ -25,8 +25,6 @@ jobs: host: i686-unknown-linux-gnu - os: windows-latest host: x86_64-pc-windows-msvc - - os: macos-13 - host: x86_64-apple-darwin - os: macos-latest host: aarch64-apple-darwin diff --git a/clippy_dev/src/new_lint.rs b/clippy_dev/src/new_lint.rs index a180db6ad0629..0b6d702d77218 100644 --- a/clippy_dev/src/new_lint.rs +++ b/clippy_dev/src/new_lint.rs @@ -157,18 +157,19 @@ fn add_lint(lint: &LintData<'_>, enable_msrv: bool) -> io::Result<()> { let path = "clippy_lints/src/lib.rs"; let mut lib_rs = fs::read_to_string(path).context("reading")?; - let comment_start = lib_rs.find("// add lints here,").expect("Couldn't find comment"); - let ctor_arg = if lint.pass == Pass::Late { "_" } else { "" }; - let lint_pass = lint.pass; + let (comment, ctor_arg) = if lint.pass == Pass::Late { + ("// add late passes here", "_") + } else { + ("// add early passes here", "") + }; + let comment_start = lib_rs.find(comment).expect("Couldn't find comment"); let module_name = lint.name; let camel_name = to_camel_case(lint.name); let new_lint = if enable_msrv { - format!( - "store.register_{lint_pass}_pass(move |{ctor_arg}| Box::new({module_name}::{camel_name}::new(conf)));\n ", - ) + format!("Box::new(move |{ctor_arg}| Box::new({module_name}::{camel_name}::new(conf))),\n ",) } else { - format!("store.register_{lint_pass}_pass(|{ctor_arg}| Box::new({module_name}::{camel_name}));\n ",) + format!("Box::new(|{ctor_arg}| Box::new({module_name}::{camel_name})),\n ",) }; lib_rs.insert_str(comment_start, &new_lint); diff --git a/clippy_lints/src/booleans.rs b/clippy_lints/src/booleans.rs index f3985603c4d2e..902ba70577b91 100644 --- a/clippy_lints/src/booleans.rs +++ b/clippy_lints/src/booleans.rs @@ -3,7 +3,7 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then}; use clippy_utils::higher::has_let_expr; use clippy_utils::msrvs::{self, Msrv}; use clippy_utils::res::MaybeDef; -use clippy_utils::source::SpanRangeExt; +use clippy_utils::source::{SpanRangeExt, snippet_with_context}; use clippy_utils::sugg::Sugg; use clippy_utils::ty::implements_trait; use clippy_utils::{eq_expr_value, sym}; @@ -415,19 +415,20 @@ fn simplify_not(cx: &LateContext<'_>, curr_msrv: Msrv, expr: &Expr<'_>) -> Optio BinOpKind::Ge => Some(" < "), _ => None, } - .and_then(|op| { - let lhs_snippet = lhs.span.get_source_text(cx)?; - let rhs_snippet = rhs.span.get_source_text(cx)?; + .map(|op| { + let mut app = Applicability::MachineApplicable; + let (lhs_snippet, _) = snippet_with_context(cx, lhs.span, SyntaxContext::root(), "", &mut app); + let (rhs_snippet, _) = snippet_with_context(cx, rhs.span, SyntaxContext::root(), "", &mut app); if !(lhs_snippet.starts_with('(') && lhs_snippet.ends_with(')')) && let (ExprKind::Cast(..), BinOpKind::Ge) = (&lhs.kind, binop.node) { // e.g. `(a as u64) < b`. Without the parens the `<` is // interpreted as a start of generic arguments for `u64` - return Some(format!("({lhs_snippet}){op}{rhs_snippet}")); + return format!("({lhs_snippet}){op}{rhs_snippet}"); } - Some(format!("{lhs_snippet}{op}{rhs_snippet}")) + format!("{lhs_snippet}{op}{rhs_snippet}") }) }, ExprKind::MethodCall(path, receiver, args, _) => { diff --git a/clippy_lints/src/if_then_some_else_none.rs b/clippy_lints/src/if_then_some_else_none.rs index dfc8411baa005..7f3ef58c93d1e 100644 --- a/clippy_lints/src/if_then_some_else_none.rs +++ b/clippy_lints/src/if_then_some_else_none.rs @@ -2,14 +2,13 @@ use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::eager_or_lazy::switch_to_eager_eval; use clippy_utils::msrvs::{self, Msrv}; -use clippy_utils::res::{MaybeDef, MaybeQPath}; use clippy_utils::source::{snippet_with_applicability, snippet_with_context, walk_span_to_context}; use clippy_utils::sugg::Sugg; use clippy_utils::{ - contains_return, expr_adjustment_requires_coercion, higher, is_else_clause, is_in_const_context, peel_blocks, sym, + as_some_expr, contains_return, expr_adjustment_requires_coercion, higher, is_else_clause, is_in_const_context, + is_none_expr, peel_blocks, sym, }; use rustc_errors::Applicability; -use rustc_hir::LangItem::{OptionNone, OptionSome}; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::impl_lint_pass; @@ -70,11 +69,10 @@ impl<'tcx> LateLintPass<'tcx> for IfThenSomeElseNone { }) = higher::If::hir(expr) && let ExprKind::Block(then_block, _) = then.kind && let Some(then_expr) = then_block.expr - && let ExprKind::Call(then_call, [then_arg]) = then_expr.kind + && let Some(then_arg) = as_some_expr(cx, then_expr) && !expr.span.from_expansion() && !then_expr.span.from_expansion() - && then_call.res(cx).ctor_parent(cx).is_lang_item(cx, OptionSome) - && peel_blocks(els).res(cx).ctor_parent(cx).is_lang_item(cx, OptionNone) + && is_none_expr(cx, peel_blocks(els)) && !is_else_clause(cx.tcx, expr) && !is_in_const_context(cx) && self.msrv.meets(cx, msrvs::BOOL_THEN) diff --git a/clippy_lints/src/incompatible_msrv.rs b/clippy_lints/src/incompatible_msrv.rs index 716334656926d..c3bc9048c23a8 100644 --- a/clippy_lints/src/incompatible_msrv.rs +++ b/clippy_lints/src/incompatible_msrv.rs @@ -1,14 +1,14 @@ use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::msrvs::Msrv; -use clippy_utils::{is_in_const_context, is_in_test}; +use clippy_utils::{is_in_const_context, is_in_test, sym}; use rustc_data_structures::fx::FxHashMap; use rustc_hir::{self as hir, AmbigArg, Expr, ExprKind, HirId, RustcVersion, StabilityLevel, StableSince}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{self, TyCtxt}; use rustc_session::impl_lint_pass; use rustc_span::def_id::{CrateNum, DefId}; -use rustc_span::{ExpnKind, Span, sym}; +use rustc_span::{ExpnKind, Span}; declare_clippy_lint! { /// ### What it does @@ -77,11 +77,36 @@ enum Availability { Since(RustcVersion), } +/// All known std crates containing a stability attribute. +struct StdCrates([Option; 6]); +impl StdCrates { + fn new(tcx: TyCtxt<'_>) -> Self { + let mut res = Self([None; _]); + for &krate in tcx.crates(()) { + // FIXME(@Jarcho): We should have an internal lint to detect when this list is out of date. + match tcx.crate_name(krate) { + sym::alloc => res.0[0] = Some(krate), + sym::core => res.0[1] = Some(krate), + sym::core_arch => res.0[2] = Some(krate), + sym::proc_macro => res.0[3] = Some(krate), + sym::std => res.0[4] = Some(krate), + sym::std_detect => res.0[5] = Some(krate), + _ => {}, + } + } + res + } + + fn contains(&self, krate: CrateNum) -> bool { + self.0.contains(&Some(krate)) + } +} + pub struct IncompatibleMsrv { msrv: Msrv, availability_cache: FxHashMap<(DefId, bool), Availability>, check_in_tests: bool, - core_crate: Option, + std_crates: StdCrates, // The most recently called path. Used to skip checking the path after it's // been checked when visiting the call expression. @@ -96,11 +121,7 @@ impl IncompatibleMsrv { msrv: conf.msrv, availability_cache: FxHashMap::default(), check_in_tests: conf.check_incompatible_msrv_in_tests, - core_crate: tcx - .crates(()) - .iter() - .find(|krate| tcx.crate_name(**krate) == sym::core) - .copied(), + std_crates: StdCrates::new(tcx), called_path: None, } } @@ -152,21 +173,24 @@ impl IncompatibleMsrv { node: HirId, span: Span, ) { - if def_id.is_local() { - // We don't check local items since their MSRV is supposed to always be valid. + if !self.std_crates.contains(def_id.krate) { + // No stability attributes to lookup for these items. return; } - let expn_data = span.ctxt().outer_expn_data(); - if let ExpnKind::AstPass(_) | ExpnKind::Desugaring(_) = expn_data.kind { - // Desugared expressions get to cheat and stability is ignored. - // Intentionally not using `.from_expansion()`, since we do still care about macro expansions - return; - } - // Functions coming from `core` while expanding a macro such as `assert*!()` get to cheat too: the - // macros may have existed prior to the checked MSRV, but their expansion with a recent compiler - // might use recent functions or methods. Compiling with an older compiler would not use those. - if Some(def_id.krate) == self.core_crate && expn_data.macro_def_id.map(|did| did.krate) == self.core_crate { - return; + // Use `from_expansion` to fast-path the common case. + if span.from_expansion() { + let expn = span.ctxt().outer_expn_data(); + match expn.kind { + // FIXME(@Jarcho): Check that the actual desugaring or std macro is supported by the + // current MSRV. Note that nested expansions need to be handled as well. + ExpnKind::AstPass(_) | ExpnKind::Desugaring(_) => return, + ExpnKind::Macro(..) if expn.macro_def_id.is_some_and(|did| self.std_crates.contains(did.krate)) => { + return; + }, + // All other expansions share the target's MSRV. + // FIXME(@Jarcho): What should we do about version dependant macros from external crates? + _ => {}, + } } if (self.check_in_tests || !is_in_test(cx.tcx, node)) diff --git a/clippy_lints/src/inherent_impl.rs b/clippy_lints/src/inherent_impl.rs index a08efbc52d457..f59c7615d7458 100644 --- a/clippy_lints/src/inherent_impl.rs +++ b/clippy_lints/src/inherent_impl.rs @@ -1,7 +1,7 @@ use clippy_config::Conf; use clippy_config::types::InherentImplLintScope; use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::fulfill_or_allowed; +use clippy_utils::{fulfill_or_allowed, is_cfg_test, is_in_cfg_test}; use rustc_data_structures::fx::FxHashMap; use rustc_hir::def_id::{LocalDefId, LocalModDefId}; use rustc_hir::{Item, ItemKind, Node}; @@ -100,7 +100,8 @@ impl<'tcx> LateLintPass<'tcx> for MultipleInherentImpl { }, InherentImplLintScope::Crate => Criterion::Crate, }; - match type_map.entry((impl_ty, criterion)) { + let is_test = is_cfg_test(cx.tcx, hir_id) || is_in_cfg_test(cx.tcx, hir_id); + match type_map.entry((impl_ty, criterion, is_test)) { Entry::Vacant(e) => { // Store the id for the first impl block of this type. The span is retrieved lazily. e.insert(IdOrSpan::Id(impl_id)); diff --git a/clippy_lints/src/let_if_seq.rs b/clippy_lints/src/let_if_seq.rs index 2dbf55a8540ba..dc7a916614be3 100644 --- a/clippy_lints/src/let_if_seq.rs +++ b/clippy_lints/src/let_if_seq.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::res::MaybeResPath; -use clippy_utils::source::snippet; +use clippy_utils::source::snippet_with_context; use clippy_utils::visitors::is_local_used; use rustc_errors::Applicability; use rustc_hir as hir; @@ -59,80 +59,88 @@ declare_lint_pass!(LetIfSeq => [USELESS_LET_IF_SEQ]); impl<'tcx> LateLintPass<'tcx> for LetIfSeq { fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx hir::Block<'_>) { for [stmt, next] in block.stmts.array_windows::<2>() { - if let hir::StmtKind::Let(local) = stmt.kind - && let hir::PatKind::Binding(mode, canonical_id, ident, None) = local.pat.kind - && let hir::StmtKind::Expr(if_) = next.kind - && let hir::ExprKind::If(cond, then, else_) = if_.kind - && !is_local_used(cx, cond, canonical_id) - && let hir::ExprKind::Block(then, _) = then.kind - && let Some(value) = check_assign(cx, canonical_id, then) - && !is_local_used(cx, value, canonical_id) - { - let span = stmt.span.to(if_.span); + if let hir::StmtKind::Expr(if_) = next.kind { + check_block_inner(cx, stmt, if_); + } + } - let has_interior_mutability = !cx - .typeck_results() - .node_type(canonical_id) - .is_freeze(cx.tcx, cx.typing_env()); - if has_interior_mutability { - return; - } + if let Some(expr) = block.expr + && let Some(stmt) = block.stmts.last() + { + check_block_inner(cx, stmt, expr); + } + } +} + +fn check_block_inner<'tcx>(cx: &LateContext<'tcx>, stmt: &'tcx hir::Stmt<'tcx>, if_: &'tcx hir::Expr<'tcx>) { + if let hir::StmtKind::Let(local) = stmt.kind + && let hir::PatKind::Binding(mode, canonical_id, ident, None) = local.pat.kind + && let hir::ExprKind::If(cond, then, else_) = if_.kind + && !is_local_used(cx, cond, canonical_id) + && let hir::ExprKind::Block(then, _) = then.kind + && let Some(value) = check_assign(cx, canonical_id, then) + && !is_local_used(cx, value, canonical_id) + { + let span = stmt.span.to(if_.span); - let (default_multi_stmts, default) = if let Some(else_) = else_ { - if let hir::ExprKind::Block(else_, _) = else_.kind { - if let Some(default) = check_assign(cx, canonical_id, else_) { - (else_.stmts.len() > 1, default) - } else if let Some(default) = local.init { - (true, default) - } else { - continue; - } - } else { - continue; - } + let has_interior_mutability = !cx + .typeck_results() + .node_type(canonical_id) + .is_freeze(cx.tcx, cx.typing_env()); + if has_interior_mutability { + return; + } + + let (default_multi_stmts, default) = if let Some(else_) = else_ { + if let hir::ExprKind::Block(else_, _) = else_.kind { + if let Some(default) = check_assign(cx, canonical_id, else_) { + (else_.stmts.len() > 1, default) } else if let Some(default) = local.init { - (false, default) + (true, default) } else { - continue; - }; + return; + } + } else { + return; + } + } else if let Some(default) = local.init { + (false, default) + } else { + return; + }; - let mutability = match mode { - BindingMode(_, Mutability::Mut) => " ", - _ => "", - }; + let mutability = match mode { + BindingMode(_, Mutability::Mut) => " ", + _ => "", + }; - // FIXME: this should not suggest `mut` if we can detect that the variable is not - // use mutably after the `if` + // FIXME: this should not suggest `mut` if we can detect that the variable is not + // use mutably after the `if` - let sug = format!( - "let {mutability}{name} = if {cond} {{{then} {value} }} else {{{else} {default} }};", - name=ident.name, - cond=snippet(cx, cond.span, "_"), - then=if then.stmts.len() > 1 { " ..;" } else { "" }, - else=if default_multi_stmts { " ..;" } else { "" }, - value=snippet(cx, value.span, ""), - default=snippet(cx, default.span, ""), - ); - span_lint_hir_and_then( - cx, - USELESS_LET_IF_SEQ, - local.hir_id, - span, - "`if _ { .. } else { .. }` is an expression", - |diag| { - diag.span_suggestion( - span, - "it is more idiomatic to write", - sug, - Applicability::HasPlaceholders, - ); - if !mutability.is_empty() { - diag.note("you might not need `mut` at all"); - } - }, - ); - } - } + let mut applicability = Applicability::HasPlaceholders; + let (cond_snip, _) = snippet_with_context(cx, cond.span, if_.span.ctxt(), "_", &mut applicability); + let (value_snip, _) = snippet_with_context(cx, value.span, if_.span.ctxt(), "", &mut applicability); + let (default_snip, _) = + snippet_with_context(cx, default.span, if_.span.ctxt(), "", &mut applicability); + let sug = format!( + "let {mutability}{name} = if {cond_snip} {{{then} {value_snip} }} else {{{else} {default_snip} }};", + name=ident.name, + then=if then.stmts.len() > 1 { " ..;" } else { "" }, + else=if default_multi_stmts { " ..;" } else { "" }, + ); + span_lint_hir_and_then( + cx, + USELESS_LET_IF_SEQ, + local.hir_id, + span, + "`if _ { .. } else { .. }` is an expression", + |diag| { + diag.span_suggestion(span, "it is more idiomatic to write", sug, applicability); + if !mutability.is_empty() { + diag.note("you might not need `mut` at all"); + } + }, + ); } } diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 99cafc7fc6d89..4542105d3277a 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -401,7 +401,9 @@ mod zombie_processes; use clippy_config::{Conf, get_configuration_metadata, sanitize_explanation}; use clippy_utils::macros::FormatArgsStorage; use rustc_data_structures::fx::FxHashSet; -use rustc_lint::Lint; +use rustc_data_structures::sync; +use rustc_lint::{EarlyLintPass, LateLintPass, Lint}; +use rustc_middle::ty::TyCtxt; use utils::attr_collector::{AttrCollector, AttrStorage}; pub fn explain(name: &str) -> i32 { @@ -443,381 +445,410 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co // level (i.e `#![cfg_attr(...)]`) will still be expanded even when using a pre-expansion pass. store.register_pre_expansion_pass(move || Box::new(attrs::EarlyAttributes::new(conf))); - store.register_early_pass(move || Box::new(attrs::PostExpansionEarlyAttributes::new(conf))); - let format_args_storage = FormatArgsStorage::default(); - let format_args = format_args_storage.clone(); - store.register_early_pass(move || { - Box::new(utils::format_args_collector::FormatArgsCollector::new( - format_args.clone(), - )) - }); - let attr_storage = AttrStorage::default(); - let attrs = attr_storage.clone(); - store.register_early_pass(move || Box::new(AttrCollector::new(attrs.clone()))); - store.register_late_pass(move |_| Box::new(operators::arithmetic_side_effects::ArithmeticSideEffects::new(conf))); - store.register_late_pass(|_| Box::new(utils::dump_hir::DumpHir)); - store.register_late_pass(|_| Box::new(utils::author::Author)); - 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(move |_| Box::new(booleans::NonminimalBool::new(conf))); - store.register_late_pass(|_| Box::new(enum_clike::UnportableVariant)); - store.register_late_pass(move |_| Box::new(float_literal::FloatLiteral::new(conf))); - store.register_late_pass(|_| Box::new(ptr::Ptr)); - store.register_late_pass(|_| Box::new(needless_bool::NeedlessBool)); - store.register_late_pass(|_| Box::new(bool_comparison::BoolComparison)); - store.register_late_pass(|_| Box::new(needless_for_each::NeedlessForEach)); - store.register_late_pass(|_| Box::new(misc::LintPass)); - store.register_late_pass(|_| Box::new(eta_reduction::EtaReduction)); - store.register_late_pass(|_| Box::new(mut_mut::MutMut::default())); - store.register_late_pass(|_| Box::new(unnecessary_mut_passed::UnnecessaryMutPassed)); - store.register_late_pass(|_| Box::>::default()); - store.register_late_pass(move |_| Box::new(len_zero::LenZero::new(conf))); - store.register_late_pass(move |_| Box::new(attrs::Attributes::new(conf))); - store.register_late_pass(|_| Box::new(blocks_in_conditions::BlocksInConditions)); - store.register_late_pass(|_| Box::new(unicode::Unicode)); - store.register_late_pass(|_| Box::new(uninit_vec::UninitVec)); - store.register_late_pass(|_| Box::new(unit_return_expecting_ord::UnitReturnExpectingOrd)); - store.register_late_pass(|_| Box::new(strings::StringAdd)); - store.register_late_pass(|_| Box::new(implicit_return::ImplicitReturn)); - store.register_late_pass(move |_| Box::new(implicit_saturating_sub::ImplicitSaturatingSub::new(conf))); - store.register_late_pass(|_| Box::new(default_numeric_fallback::DefaultNumericFallback)); - store.register_late_pass(move |_| { - Box::new(inconsistent_struct_constructor::InconsistentStructConstructor::new( - conf, - )) - }); - store.register_late_pass(|_| Box::new(non_octal_unix_permissions::NonOctalUnixPermissions)); - store.register_early_pass(|| Box::new(unnecessary_self_imports::UnnecessarySelfImports)); - store.register_late_pass(move |_| Box::new(approx_const::ApproxConstant::new(conf))); - let format_args = format_args_storage.clone(); - store.register_late_pass(move |_| Box::new(methods::Methods::new(conf, format_args.clone()))); - store.register_late_pass(move |_| Box::new(matches::Matches::new(conf))); - store.register_late_pass(move |_| Box::new(manual_non_exhaustive::ManualNonExhaustive::new(conf))); - store.register_late_pass(move |_| Box::new(manual_strip::ManualStrip::new(conf))); - store.register_early_pass(move || Box::new(redundant_static_lifetimes::RedundantStaticLifetimes::new(conf))); - store.register_early_pass(move || Box::new(redundant_field_names::RedundantFieldNames::new(conf))); - store.register_late_pass(move |_| Box::new(checked_conversions::CheckedConversions::new(conf))); - store.register_late_pass(move |_| Box::new(mem_replace::MemReplace::new(conf))); - store.register_late_pass(move |_| Box::new(ranges::Ranges::new(conf))); - store.register_late_pass(move |_| Box::new(from_over_into::FromOverInto::new(conf))); - store.register_late_pass(move |_| Box::new(use_self::UseSelf::new(conf))); - store.register_late_pass(move |_| Box::new(missing_const_for_fn::MissingConstForFn::new(conf))); - store.register_late_pass(move |_| Box::new(needless_question_mark::NeedlessQuestionMark)); - store.register_late_pass(move |_| Box::new(casts::Casts::new(conf))); - store.register_early_pass(move || Box::new(unnested_or_patterns::UnnestedOrPatterns::new(conf))); - store.register_late_pass(|_| Box::new(size_of_in_element_count::SizeOfInElementCount)); - store.register_late_pass(|_| Box::new(same_name_method::SameNameMethod)); - store.register_late_pass(move |_| Box::new(index_refutable_slice::IndexRefutableSlice::new(conf))); - store.register_late_pass(|_| Box::::default()); - let format_args = format_args_storage.clone(); - store.register_late_pass(move |_| Box::new(unit_types::UnitTypes::new(format_args.clone()))); - store.register_late_pass(move |_| Box::new(loops::Loops::new(conf))); - store.register_late_pass(|_| Box::::default()); - store.register_late_pass(move |_| Box::new(lifetimes::Lifetimes::new(conf))); - store.register_late_pass(|_| Box::new(entry::HashMapPass)); - store.register_late_pass(|_| Box::new(minmax::MinMaxPass)); - store.register_late_pass(|_| Box::new(zero_div_zero::ZeroDiv)); - store.register_late_pass(|_| Box::new(mutex_atomic::Mutex)); - store.register_late_pass(|_| Box::new(needless_update::NeedlessUpdate)); - store.register_late_pass(|_| Box::new(needless_borrowed_ref::NeedlessBorrowedRef)); - store.register_late_pass(|_| Box::new(borrow_deref_ref::BorrowDerefRef)); - store.register_late_pass(|_| Box::::default()); - store.register_late_pass(|_| Box::new(temporary_assignment::TemporaryAssignment)); - store.register_late_pass(move |_| Box::new(transmute::Transmute::new(conf))); - store.register_late_pass(move |_| Box::new(cognitive_complexity::CognitiveComplexity::new(conf))); - store.register_late_pass(move |_| Box::new(escape::BoxedLocal::new(conf))); - store.register_late_pass(move |_| Box::new(vec::UselessVec::new(conf))); - store.register_late_pass(move |_| Box::new(panic_unimplemented::PanicUnimplemented::new(conf))); - store.register_late_pass(|_| Box::new(strings::StringLitAsBytes)); - store.register_late_pass(|_| Box::new(derive::Derive)); - store.register_late_pass(move |_| Box::new(derivable_impls::DerivableImpls::new(conf))); - store.register_late_pass(|_| Box::new(drop_forget_ref::DropForgetRef)); - store.register_late_pass(|_| Box::new(empty_enums::EmptyEnums)); - store.register_late_pass(|_| Box::::default()); - store.register_late_pass(move |tcx| Box::new(ifs::CopyAndPaste::new(tcx, conf))); - store.register_late_pass(|_| Box::new(copy_iterator::CopyIterator)); - let format_args = format_args_storage.clone(); - store.register_late_pass(move |_| Box::new(format::UselessFormat::new(format_args.clone()))); - store.register_late_pass(|_| Box::new(swap::Swap)); - store.register_late_pass(|_| Box::new(panicking_overflow_checks::PanickingOverflowChecks)); - store.register_late_pass(|_| Box::::default()); - store.register_late_pass(move |_| Box::new(disallowed_names::DisallowedNames::new(conf))); - store.register_early_pass(|| Box::new(functions::EarlyFunctions)); - store.register_late_pass(move |tcx| Box::new(functions::Functions::new(tcx, conf))); - store.register_late_pass(move |_| Box::new(doc::Documentation::new(conf))); - store.register_early_pass(move || Box::new(doc::Documentation::new(conf))); - store.register_late_pass(|_| Box::new(neg_multiply::NegMultiply)); - store.register_late_pass(|_| Box::new(let_if_seq::LetIfSeq)); - store.register_late_pass(|_| Box::new(mixed_read_write_in_expression::EvalOrderDependence)); - store.register_late_pass(move |_| Box::new(missing_doc::MissingDoc::new(conf))); - store.register_late_pass(|_| Box::new(missing_inline::MissingInline)); - store.register_late_pass(move |_| Box::new(exhaustive_items::ExhaustiveItems)); - store.register_late_pass(|_| Box::new(unused_result_ok::UnusedResultOk)); - store.register_late_pass(|_| Box::new(match_result_ok::MatchResultOk)); - store.register_late_pass(|_| Box::new(partialeq_ne_impl::PartialEqNeImpl)); - store.register_late_pass(|_| Box::new(unused_io_amount::UnusedIoAmount)); - store.register_late_pass(move |_| Box::new(large_enum_variant::LargeEnumVariant::new(conf))); - let format_args = format_args_storage.clone(); - store.register_late_pass(move |_| Box::new(explicit_write::ExplicitWrite::new(format_args.clone()))); - store.register_late_pass(|_| Box::new(needless_pass_by_value::NeedlessPassByValue)); - store.register_late_pass(move |tcx| Box::new(pass_by_ref_or_value::PassByRefOrValue::new(tcx, conf))); - store.register_late_pass(|_| Box::new(ref_option_ref::RefOptionRef)); - store.register_late_pass(|_| Box::new(infinite_iter::InfiniteIter)); - store.register_late_pass(|_| Box::new(inline_fn_without_body::InlineFnWithoutBody)); - store.register_late_pass(|_| Box::::default()); - store.register_late_pass(|_| Box::new(implicit_hasher::ImplicitHasher)); - store.register_late_pass(|_| Box::new(fallible_impl_from::FallibleImplFrom)); - store.register_late_pass(move |_| Box::new(question_mark::QuestionMark::new(conf))); - store.register_late_pass(|_| Box::new(question_mark_used::QuestionMarkUsed)); - store.register_early_pass(|| Box::new(suspicious_operation_groupings::SuspiciousOperationGroupings)); - store.register_late_pass(|_| Box::new(suspicious_trait_impl::SuspiciousImpl)); - store.register_late_pass(|_| Box::new(map_unit_fn::MapUnit)); - store.register_late_pass(move |_| Box::new(inherent_impl::MultipleInherentImpl::new(conf))); - store.register_late_pass(|_| Box::new(neg_cmp_op_on_partial_ord::NoNegCompOpForPartialOrd)); - store.register_late_pass(move |_| Box::new(unwrap::Unwrap::new(conf))); - store.register_late_pass(move |_| Box::new(indexing_slicing::IndexingSlicing::new(conf))); - store.register_late_pass(move |tcx| Box::new(non_copy_const::NonCopyConst::new(tcx, conf))); - store.register_late_pass(|_| Box::new(redundant_clone::RedundantClone)); - store.register_late_pass(|_| Box::new(slow_vector_initialization::SlowVectorInit)); - store.register_late_pass(move |_| Box::new(unnecessary_wraps::UnnecessaryWraps::new(conf))); - store.register_late_pass(|_| Box::new(assertions_on_constants::AssertionsOnConstants::new(conf))); - store.register_late_pass(|_| Box::new(assertions_on_result_states::AssertionsOnResultStates)); - store.register_late_pass(|_| Box::new(inherent_to_string::InherentToString)); - store.register_late_pass(move |_| Box::new(trait_bounds::TraitBounds::new(conf))); - store.register_late_pass(|_| Box::new(comparison_chain::ComparisonChain)); - store.register_late_pass(move |tcx| Box::new(mut_key::MutableKeyType::new(tcx, conf))); - store.register_late_pass(|_| Box::new(reference::DerefAddrOf)); - store.register_early_pass(|| Box::new(double_parens::DoubleParens)); - let format_args = format_args_storage.clone(); - store.register_late_pass(move |_| Box::new(format_impl::FormatImpl::new(format_args.clone()))); - store.register_early_pass(|| Box::new(unsafe_removed_from_name::UnsafeNameRemoval)); - store.register_early_pass(|| Box::new(else_if_without_else::ElseIfWithoutElse)); - store.register_early_pass(|| Box::new(int_plus_one::IntPlusOne)); - store.register_early_pass(|| Box::new(formatting::Formatting)); - store.register_early_pass(|| Box::new(misc_early::MiscEarlyLints)); - store.register_late_pass(|_| Box::new(redundant_closure_call::RedundantClosureCall)); - store.register_early_pass(|| Box::new(unused_unit::UnusedUnit)); - store.register_late_pass(|_| Box::new(unused_unit::UnusedUnit)); - store.register_late_pass(|_| Box::new(returns::Return)); - store.register_late_pass(move |_| Box::new(collapsible_if::CollapsibleIf::new(conf))); - store.register_late_pass(|_| Box::new(items_after_statements::ItemsAfterStatements)); - store.register_early_pass(|| Box::new(precedence::Precedence)); - store.register_late_pass(|_| Box::new(needless_parens_on_range_literals::NeedlessParensOnRangeLiterals)); - store.register_late_pass(|_| Box::new(needless_continue::NeedlessContinue)); - store.register_early_pass(|| Box::new(redundant_else::RedundantElse)); - store.register_late_pass(|_| Box::new(create_dir::CreateDir)); - store.register_early_pass(|| Box::new(needless_arbitrary_self_type::NeedlessArbitrarySelfType)); - store.register_early_pass(move || Box::new(literal_representation::LiteralDigitGrouping::new(conf))); - store.register_early_pass(move || Box::new(literal_representation::DecimalLiteralRepresentation::new(conf))); - store.register_late_pass(move |_| Box::new(item_name_repetitions::ItemNameRepetitions::new(conf))); - store.register_early_pass(|| Box::new(tabs_in_doc_comments::TabsInDocComments)); - store.register_late_pass(move |_| Box::new(upper_case_acronyms::UpperCaseAcronyms::new(conf))); - store.register_late_pass(|_| Box::::default()); - store.register_late_pass(move |_| Box::new(unused_self::UnusedSelf::new(conf))); - store.register_late_pass(|_| Box::new(mutable_debug_assertion::DebugAssertWithMutCall)); - store.register_late_pass(|_| Box::new(exit::Exit)); - store.register_late_pass(move |_| Box::new(to_digit_is_some::ToDigitIsSome::new(conf))); - store.register_late_pass(move |_| Box::new(large_stack_arrays::LargeStackArrays::new(conf))); - store.register_late_pass(move |_| Box::new(large_const_arrays::LargeConstArrays::new(conf))); - store.register_late_pass(|_| Box::new(floating_point_arithmetic::FloatingPointArithmetic)); - store.register_late_pass(|_| Box::new(as_conversions::AsConversions)); - store.register_late_pass(|_| Box::new(let_underscore::LetUnderscore)); - store.register_early_pass(|| Box::::default()); - store.register_late_pass(move |_| Box::new(excessive_bools::ExcessiveBools::new(conf))); - store.register_early_pass(|| Box::new(option_env_unwrap::OptionEnvUnwrap)); - store.register_late_pass(move |_| Box::new(wildcard_imports::WildcardImports::new(conf))); - store.register_late_pass(|_| Box::::default()); - store.register_late_pass(|_| Box::>::default()); - store.register_late_pass(|_| Box::new(option_if_let_else::OptionIfLetElse)); - store.register_late_pass(|_| Box::new(future_not_send::FutureNotSend)); - store.register_late_pass(move |_| Box::new(large_futures::LargeFuture::new(conf))); - store.register_late_pass(|_| Box::new(if_let_mutex::IfLetMutex)); - store.register_late_pass(|_| Box::new(if_not_else::IfNotElse)); - store.register_late_pass(|_| Box::new(equatable_if_let::PatternEquality)); - store.register_late_pass(|_| Box::new(manual_async_fn::ManualAsyncFn)); - store.register_late_pass(|_| Box::new(panic_in_result_fn::PanicInResultFn)); - store.register_early_pass(move || Box::new(non_expressive_names::NonExpressiveNames::new(conf))); - store.register_early_pass(move || Box::new(nonstandard_macro_braces::MacroBraces::new(conf))); - store.register_late_pass(|_| Box::::default()); - store.register_late_pass(|_| Box::new(pattern_type_mismatch::PatternTypeMismatch)); - store.register_late_pass(|_| Box::::default()); - store.register_late_pass(|_| Box::new(semicolon_if_nothing_returned::SemicolonIfNothingReturned)); - store.register_late_pass(|_| Box::new(async_yields_async::AsyncYieldsAsync)); - let attrs = attr_storage.clone(); - store.register_late_pass(move |tcx| Box::new(disallowed_macros::DisallowedMacros::new(tcx, conf, attrs.clone()))); - store.register_late_pass(move |tcx| Box::new(disallowed_methods::DisallowedMethods::new(tcx, conf))); - store.register_early_pass(|| Box::new(asm_syntax::InlineAsmX86AttSyntax)); - store.register_early_pass(|| Box::new(asm_syntax::InlineAsmX86IntelSyntax)); - store.register_late_pass(|_| Box::new(empty_drop::EmptyDrop)); - store.register_late_pass(|_| Box::new(strings::StrToString)); - store.register_late_pass(|_| Box::new(zero_sized_map_values::ZeroSizedMapValues)); - store.register_late_pass(|_| Box::::default()); - store.register_late_pass(|_| Box::new(redundant_slicing::RedundantSlicing)); - store.register_late_pass(|_| Box::new(from_str_radix_10::FromStrRadix10)); - store.register_late_pass(move |_| Box::new(if_then_some_else_none::IfThenSomeElseNone::new(conf))); - store.register_late_pass(|_| Box::new(bool_assert_comparison::BoolAssertComparison)); - store.register_early_pass(move || Box::new(module_style::ModStyle::default())); - store.register_late_pass(|_| Box::::default()); - store.register_late_pass(move |tcx| Box::new(disallowed_types::DisallowedTypes::new(tcx, conf))); - store.register_late_pass(move |tcx| Box::new(missing_enforced_import_rename::ImportRename::new(tcx, conf))); - store.register_early_pass(move || Box::new(disallowed_script_idents::DisallowedScriptIdents::new(conf))); - store.register_late_pass(|_| Box::new(strlen_on_c_strings::StrlenOnCStrings)); - store.register_late_pass(move |_| Box::new(self_named_constructors::SelfNamedConstructors)); - store.register_late_pass(move |_| Box::new(iter_not_returning_iterator::IterNotReturningIterator)); - store.register_late_pass(move |_| Box::new(manual_assert::ManualAssert)); - store.register_late_pass(move |_| Box::new(non_send_fields_in_send_ty::NonSendFieldInSendTy::new(conf))); - store.register_late_pass(move |_| Box::new(undocumented_unsafe_blocks::UndocumentedUnsafeBlocks::new(conf))); - let format_args = format_args_storage.clone(); - store.register_late_pass(move |tcx| Box::new(format_args::FormatArgs::new(tcx, conf, format_args.clone()))); - store.register_late_pass(|_| Box::new(trailing_empty_array::TrailingEmptyArray)); - store.register_early_pass(|| Box::new(octal_escapes::OctalEscapes)); - store.register_late_pass(|_| Box::new(needless_late_init::NeedlessLateInit)); - store.register_late_pass(|_| Box::new(return_self_not_must_use::ReturnSelfNotMustUse)); - store.register_late_pass(|_| Box::new(init_numbered_fields::NumberedFields)); - store.register_early_pass(|| Box::new(single_char_lifetime_names::SingleCharLifetimeNames)); - store.register_late_pass(move |_| Box::new(manual_bits::ManualBits::new(conf))); - store.register_late_pass(|_| Box::new(default_union_representation::DefaultUnionRepresentation)); - store.register_late_pass(|_| Box::::default()); - store.register_late_pass(move |_| Box::new(dbg_macro::DbgMacro::new(conf))); - let format_args = format_args_storage.clone(); - store.register_late_pass(move |_| Box::new(write::Write::new(conf, format_args.clone()))); - store.register_late_pass(move |_| Box::new(cargo::Cargo::new(conf))); - store.register_early_pass(|| Box::new(crate_in_macro_def::CrateInMacroDef)); - store.register_late_pass(|_| Box::new(empty_with_brackets::EmptyWithBrackets::default())); - store.register_late_pass(|_| Box::new(unnecessary_owned_empty_strings::UnnecessaryOwnedEmptyStrings)); - store.register_early_pass(|| Box::new(pub_use::PubUse)); - store.register_late_pass(|_| Box::new(format_push_string::FormatPushString)); - store.register_late_pass(move |_| Box::new(large_include_file::LargeIncludeFile::new(conf))); - store.register_early_pass(move || Box::new(large_include_file::LargeIncludeFile::new(conf))); - store.register_late_pass(|_| Box::new(strings::TrimSplitWhitespace)); - store.register_late_pass(|_| Box::new(rc_clone_in_vec_init::RcCloneInVecInit)); - store.register_early_pass(|| Box::::default()); - store.register_early_pass(|| Box::new(unused_rounding::UnusedRounding)); - store.register_early_pass(move || Box::new(almost_complete_range::AlmostCompleteRange::new(conf))); - store.register_late_pass(|_| Box::new(swap_ptr_to_ref::SwapPtrToRef)); - store.register_late_pass(|_| Box::new(mismatching_type_param_order::TypeParamMismatch)); - store.register_late_pass(|_| Box::new(read_zero_byte_vec::ReadZeroByteVec)); - store.register_late_pass(|_| Box::new(default_instead_of_iter_empty::DefaultIterEmpty)); - store.register_late_pass(move |_| Box::new(manual_rem_euclid::ManualRemEuclid::new(conf))); - store.register_late_pass(move |_| Box::new(manual_retain::ManualRetain::new(conf))); - store.register_late_pass(move |_| Box::new(manual_rotate::ManualRotate)); - store.register_late_pass(move |_| Box::new(operators::Operators::new(conf))); - store.register_late_pass(move |_| Box::new(std_instead_of_core::StdReexports::new(conf))); - store.register_late_pass(move |_| Box::new(time_subtraction::UncheckedTimeSubtraction::new(conf))); - store.register_late_pass(|_| Box::new(partialeq_to_none::PartialeqToNone)); - store.register_late_pass(move |_| Box::new(manual_abs_diff::ManualAbsDiff::new(conf))); - store.register_late_pass(move |_| Box::new(manual_clamp::ManualClamp::new(conf))); - store.register_late_pass(|_| Box::new(manual_string_new::ManualStringNew)); - store.register_late_pass(|_| Box::new(unused_peekable::UnusedPeekable)); - store.register_early_pass(|| Box::new(multi_assignments::MultiAssignments)); - store.register_late_pass(|_| Box::new(bool_to_int_with_if::BoolToIntWithIf)); - store.register_late_pass(|_| Box::new(box_default::BoxDefault)); - store.register_late_pass(|_| Box::new(implicit_saturating_add::ImplicitSaturatingAdd)); - store.register_early_pass(|| Box::new(partial_pub_fields::PartialPubFields)); - store.register_late_pass(|_| Box::new(missing_trait_methods::MissingTraitMethods)); - store.register_late_pass(|_| Box::new(from_raw_with_void_ptr::FromRawWithVoidPtr)); - store.register_late_pass(|_| Box::new(suspicious_xor_used_as_pow::ConfusingXorAndPow)); - store.register_late_pass(move |_| Box::new(manual_is_ascii_check::ManualIsAsciiCheck::new(conf))); - store.register_late_pass(move |_| Box::new(semicolon_block::SemicolonBlock::new(conf))); - store.register_late_pass(|_| Box::new(permissions_set_readonly_false::PermissionsSetReadonlyFalse)); - store.register_late_pass(|_| Box::new(size_of_ref::SizeOfRef)); - store.register_late_pass(|_| Box::new(multiple_unsafe_ops_per_block::MultipleUnsafeOpsPerBlock)); - store.register_late_pass(move |_| Box::new(extra_unused_type_parameters::ExtraUnusedTypeParameters::new(conf))); - store.register_late_pass(|_| Box::new(no_mangle_with_rust_abi::NoMangleWithRustAbi)); - store.register_late_pass(|_| Box::new(collection_is_never_read::CollectionIsNeverRead)); - store.register_late_pass(|_| Box::new(missing_assert_message::MissingAssertMessage)); - store.register_late_pass(|_| Box::new(needless_maybe_sized::NeedlessMaybeSized)); - store.register_late_pass(|_| Box::new(redundant_async_block::RedundantAsyncBlock)); - store.register_early_pass(|| Box::new(let_with_type_underscore::UnderscoreTyped)); - store.register_late_pass(move |_| Box::new(manual_main_separator_str::ManualMainSeparatorStr::new(conf))); - store.register_late_pass(|_| Box::new(unnecessary_struct_initialization::UnnecessaryStruct)); - store.register_late_pass(move |_| Box::new(unnecessary_box_returns::UnnecessaryBoxReturns::new(conf))); - store.register_late_pass(|_| Box::new(tests_outside_test_module::TestsOutsideTestModule)); - store.register_late_pass(|_| Box::new(manual_slice_size_calculation::ManualSliceSizeCalculation::new(conf))); - store.register_early_pass(move || Box::new(excessive_nesting::ExcessiveNesting::new(conf))); - store.register_late_pass(|_| Box::new(items_after_test_module::ItemsAfterTestModule)); - store.register_early_pass(|| Box::new(ref_patterns::RefPatterns)); - store.register_late_pass(|_| Box::new(default_constructed_unit_structs::DefaultConstructedUnitStructs)); - store.register_early_pass(|| Box::new(needless_else::NeedlessElse)); - store.register_late_pass(|_| Box::new(missing_fields_in_debug::MissingFieldsInDebug)); - store.register_late_pass(|_| Box::new(endian_bytes::EndianBytes)); - store.register_late_pass(|_| Box::new(redundant_type_annotations::RedundantTypeAnnotations)); - store.register_late_pass(|_| Box::new(arc_with_non_send_sync::ArcWithNonSendSync)); - store.register_late_pass(|_| Box::new(needless_ifs::NeedlessIfs)); - store.register_late_pass(move |_| Box::new(min_ident_chars::MinIdentChars::new(conf))); - store.register_late_pass(move |_| Box::new(large_stack_frames::LargeStackFrames::new(conf))); - store.register_late_pass(|_| Box::new(single_range_in_vec_init::SingleRangeInVecInit)); - store.register_late_pass(move |_| Box::new(needless_pass_by_ref_mut::NeedlessPassByRefMut::new(conf))); - store.register_late_pass(|tcx| Box::new(non_canonical_impls::NonCanonicalImpls::new(tcx))); - store.register_late_pass(move |_| Box::new(single_call_fn::SingleCallFn::new(conf))); - store.register_early_pass(move || Box::new(raw_strings::RawStrings::new(conf))); - store.register_late_pass(move |_| Box::new(legacy_numeric_constants::LegacyNumericConstants::new(conf))); - store.register_late_pass(|_| Box::new(manual_range_patterns::ManualRangePatterns)); - store.register_early_pass(|| Box::new(visibility::Visibility)); - store.register_late_pass(move |_| Box::new(tuple_array_conversions::TupleArrayConversions::new(conf))); - store.register_late_pass(move |_| Box::new(manual_float_methods::ManualFloatMethods::new(conf))); - store.register_late_pass(|_| Box::new(four_forward_slashes::FourForwardSlashes)); - store.register_late_pass(|_| Box::new(error_impl_error::ErrorImplError)); - store.register_late_pass(move |_| Box::new(absolute_paths::AbsolutePaths::new(conf))); - store.register_late_pass(|_| Box::new(redundant_locals::RedundantLocals)); - store.register_late_pass(|_| Box::new(ignored_unit_patterns::IgnoredUnitPatterns)); - store.register_late_pass(|_| Box::::default()); - store.register_late_pass(|_| Box::new(implied_bounds_in_impls::ImpliedBoundsInImpls)); - store.register_late_pass(|_| Box::new(missing_asserts_for_indexing::MissingAssertsForIndexing)); - store.register_late_pass(|_| Box::new(unnecessary_map_on_constructor::UnnecessaryMapOnConstructor)); - store.register_late_pass(move |_| { - Box::new(needless_borrows_for_generic_args::NeedlessBorrowsForGenericArgs::new( - conf, - )) - }); - store.register_late_pass(move |_| Box::new(manual_hash_one::ManualHashOne::new(conf))); - store.register_late_pass(|_| Box::new(iter_without_into_iter::IterWithoutIntoIter)); - store.register_late_pass(|_| Box::>::default()); - store.register_late_pass(|_| Box::new(iter_over_hash_type::IterOverHashType)); - store.register_late_pass(|_| Box::new(impl_hash_with_borrow_str_and_bytes::ImplHashWithBorrowStrBytes)); - store.register_late_pass(move |_| Box::new(repeat_vec_with_capacity::RepeatVecWithCapacity::new(conf))); - store.register_late_pass(|_| Box::new(uninhabited_references::UninhabitedReferences)); - store.register_late_pass(|_| Box::new(ineffective_open_options::IneffectiveOpenOptions)); - store.register_late_pass(|_| Box::::default()); - store.register_late_pass(move |_| Box::new(pub_underscore_fields::PubUnderscoreFields::new(conf))); - store.register_late_pass(move |_| Box::new(missing_const_for_thread_local::MissingConstForThreadLocal::new(conf))); - store.register_late_pass(move |tcx| Box::new(incompatible_msrv::IncompatibleMsrv::new(tcx, conf))); - store.register_late_pass(|_| Box::new(to_string_trait_impl::ToStringTraitImpl)); - store.register_early_pass(|| Box::new(multiple_bound_locations::MultipleBoundLocations)); - store.register_late_pass(move |_| Box::new(assigning_clones::AssigningClones::new(conf))); - store.register_late_pass(|_| Box::new(zero_repeat_side_effects::ZeroRepeatSideEffects)); - store.register_late_pass(move |_| Box::new(macro_metavars_in_unsafe::ExprMetavarsInUnsafe::new(conf))); - store.register_late_pass(move |_| Box::new(string_patterns::StringPatterns::new(conf))); - store.register_early_pass(|| Box::new(field_scoped_visibility_modifiers::FieldScopedVisibilityModifiers)); - store.register_late_pass(|_| Box::new(set_contains_or_insert::SetContainsOrInsert)); - store.register_early_pass(|| Box::new(byte_char_slices::ByteCharSlice)); - store.register_early_pass(|| Box::new(cfg_not_test::CfgNotTest)); - store.register_late_pass(|_| Box::new(zombie_processes::ZombieProcesses)); - store.register_late_pass(|_| Box::new(pointers_in_nomem_asm_block::PointersInNomemAsmBlock)); - store.register_late_pass(move |_| Box::new(manual_is_power_of_two::ManualIsPowerOfTwo::new(conf))); - store.register_late_pass(|_| Box::new(non_zero_suggestions::NonZeroSuggestions)); - store.register_late_pass(|_| Box::new(literal_string_with_formatting_args::LiteralStringWithFormattingArg)); - store.register_late_pass(move |_| Box::new(unused_trait_names::UnusedTraitNames::new(conf))); - store.register_late_pass(|_| Box::new(manual_ignore_case_cmp::ManualIgnoreCaseCmp)); - store.register_late_pass(|_| Box::new(unnecessary_literal_bound::UnnecessaryLiteralBound)); - store.register_early_pass(|| Box::new(empty_line_after::EmptyLineAfter::new())); - store.register_late_pass(move |_| Box::new(arbitrary_source_item_ordering::ArbitrarySourceItemOrdering::new(conf))); - store.register_late_pass(|_| Box::new(useless_concat::UselessConcat)); - store.register_late_pass(|_| Box::new(unneeded_struct_pattern::UnneededStructPattern)); - store.register_late_pass(|_| Box::::default()); - store.register_late_pass(move |_| Box::new(non_std_lazy_statics::NonStdLazyStatic::new(conf))); - store.register_late_pass(|_| Box::new(manual_option_as_slice::ManualOptionAsSlice::new(conf))); - store.register_late_pass(|_| Box::new(single_option_map::SingleOptionMap)); - store.register_late_pass(move |_| Box::new(redundant_test_prefix::RedundantTestPrefix)); - store.register_late_pass(|_| Box::new(cloned_ref_to_slice_refs::ClonedRefToSliceRefs::new(conf))); - store.register_late_pass(|_| Box::new(infallible_try_from::InfallibleTryFrom)); - store.register_late_pass(|_| Box::new(coerce_container_to_any::CoerceContainerToAny)); - store.register_late_pass(|_| Box::new(toplevel_ref_arg::ToplevelRefArg)); - store.register_late_pass(|_| Box::new(volatile_composites::VolatileComposites)); - store.register_late_pass(|_| Box::new(replace_box::ReplaceBox)); - // add lints here, do not remove this comment, it's used in `new_lint` + let early_lints: [Box Box + sync::DynSend + sync::DynSync>; _] = [ + { + let format_args = format_args_storage.clone(); + Box::new(move || { + Box::new(utils::format_args_collector::FormatArgsCollector::new( + format_args.clone(), + )) + }) + }, + { + let attrs = attr_storage.clone(); + Box::new(move || Box::new(AttrCollector::new(attrs.clone()))) + }, + Box::new(move || Box::new(attrs::PostExpansionEarlyAttributes::new(conf))), + Box::new(|| Box::new(unnecessary_self_imports::UnnecessarySelfImports)), + Box::new(move || Box::new(redundant_static_lifetimes::RedundantStaticLifetimes::new(conf))), + Box::new(move || Box::new(redundant_field_names::RedundantFieldNames::new(conf))), + Box::new(move || Box::new(unnested_or_patterns::UnnestedOrPatterns::new(conf))), + Box::new(|| Box::new(functions::EarlyFunctions)), + Box::new(move || Box::new(doc::Documentation::new(conf))), + Box::new(|| Box::new(suspicious_operation_groupings::SuspiciousOperationGroupings)), + Box::new(|| Box::new(double_parens::DoubleParens)), + Box::new(|| Box::new(unsafe_removed_from_name::UnsafeNameRemoval)), + Box::new(|| Box::new(else_if_without_else::ElseIfWithoutElse)), + Box::new(|| Box::new(int_plus_one::IntPlusOne)), + Box::new(|| Box::new(formatting::Formatting)), + Box::new(|| Box::new(misc_early::MiscEarlyLints)), + Box::new(|| Box::new(unused_unit::UnusedUnit)), + Box::new(|| Box::new(precedence::Precedence)), + Box::new(|| Box::new(redundant_else::RedundantElse)), + Box::new(|| Box::new(needless_arbitrary_self_type::NeedlessArbitrarySelfType)), + Box::new(move || Box::new(literal_representation::LiteralDigitGrouping::new(conf))), + Box::new(move || Box::new(literal_representation::DecimalLiteralRepresentation::new(conf))), + Box::new(|| Box::new(tabs_in_doc_comments::TabsInDocComments)), + Box::new(|| Box::::default()), + Box::new(|| Box::new(option_env_unwrap::OptionEnvUnwrap)), + Box::new(move || Box::new(non_expressive_names::NonExpressiveNames::new(conf))), + Box::new(move || Box::new(nonstandard_macro_braces::MacroBraces::new(conf))), + Box::new(|| Box::new(asm_syntax::InlineAsmX86AttSyntax)), + Box::new(|| Box::new(asm_syntax::InlineAsmX86IntelSyntax)), + Box::new(move || Box::new(module_style::ModStyle::default())), + Box::new(move || Box::new(disallowed_script_idents::DisallowedScriptIdents::new(conf))), + Box::new(|| Box::new(octal_escapes::OctalEscapes)), + Box::new(|| Box::new(single_char_lifetime_names::SingleCharLifetimeNames)), + Box::new(|| Box::new(crate_in_macro_def::CrateInMacroDef)), + Box::new(|| Box::new(pub_use::PubUse)), + Box::new(move || Box::new(large_include_file::LargeIncludeFile::new(conf))), + Box::new(|| Box::::default()), + Box::new(|| Box::new(unused_rounding::UnusedRounding)), + Box::new(move || Box::new(almost_complete_range::AlmostCompleteRange::new(conf))), + Box::new(|| Box::new(multi_assignments::MultiAssignments)), + Box::new(|| Box::new(partial_pub_fields::PartialPubFields)), + Box::new(|| Box::new(let_with_type_underscore::UnderscoreTyped)), + Box::new(move || Box::new(excessive_nesting::ExcessiveNesting::new(conf))), + Box::new(|| Box::new(ref_patterns::RefPatterns)), + Box::new(|| Box::new(needless_else::NeedlessElse)), + Box::new(move || Box::new(raw_strings::RawStrings::new(conf))), + Box::new(|| Box::new(visibility::Visibility)), + Box::new(|| Box::new(multiple_bound_locations::MultipleBoundLocations)), + Box::new(|| Box::new(field_scoped_visibility_modifiers::FieldScopedVisibilityModifiers)), + Box::new(|| Box::new(byte_char_slices::ByteCharSlice)), + Box::new(|| Box::new(cfg_not_test::CfgNotTest)), + Box::new(|| Box::new(empty_line_after::EmptyLineAfter::new())), + // add early passes here, used by `cargo dev new_lint` + ]; + store.early_passes.extend(early_lints); + + #[expect(clippy::type_complexity)] + let late_lints: [Box< + dyn for<'tcx> Fn(TyCtxt<'tcx>) -> Box + 'tcx> + sync::DynSend + sync::DynSync, + >; _] = [ + Box::new(move |_| Box::new(operators::arithmetic_side_effects::ArithmeticSideEffects::new(conf))), + Box::new(|_| Box::new(utils::dump_hir::DumpHir)), + Box::new(|_| Box::new(utils::author::Author)), + Box::new(move |tcx| Box::new(await_holding_invalid::AwaitHolding::new(tcx, conf))), + Box::new(|_| Box::new(serde_api::SerdeApi)), + Box::new(move |_| Box::new(types::Types::new(conf))), + Box::new(move |_| Box::new(booleans::NonminimalBool::new(conf))), + Box::new(|_| Box::new(enum_clike::UnportableVariant)), + Box::new(move |_| Box::new(float_literal::FloatLiteral::new(conf))), + Box::new(|_| Box::new(ptr::Ptr)), + Box::new(|_| Box::new(needless_bool::NeedlessBool)), + Box::new(|_| Box::new(bool_comparison::BoolComparison)), + Box::new(|_| Box::new(needless_for_each::NeedlessForEach)), + Box::new(|_| Box::new(misc::LintPass)), + Box::new(|_| Box::new(eta_reduction::EtaReduction)), + Box::new(|_| Box::new(mut_mut::MutMut::default())), + Box::new(|_| Box::new(unnecessary_mut_passed::UnnecessaryMutPassed)), + Box::new(|_| Box::>::default()), + Box::new(move |_| Box::new(len_zero::LenZero::new(conf))), + Box::new(move |_| Box::new(attrs::Attributes::new(conf))), + Box::new(|_| Box::new(blocks_in_conditions::BlocksInConditions)), + Box::new(|_| Box::new(unicode::Unicode)), + Box::new(|_| Box::new(uninit_vec::UninitVec)), + Box::new(|_| Box::new(unit_return_expecting_ord::UnitReturnExpectingOrd)), + Box::new(|_| Box::new(strings::StringAdd)), + Box::new(|_| Box::new(implicit_return::ImplicitReturn)), + Box::new(move |_| Box::new(implicit_saturating_sub::ImplicitSaturatingSub::new(conf))), + Box::new(|_| Box::new(default_numeric_fallback::DefaultNumericFallback)), + Box::new(|_| Box::new(non_octal_unix_permissions::NonOctalUnixPermissions)), + Box::new(move |_| Box::new(approx_const::ApproxConstant::new(conf))), + Box::new(move |_| Box::new(matches::Matches::new(conf))), + Box::new(move |_| Box::new(manual_non_exhaustive::ManualNonExhaustive::new(conf))), + Box::new(move |_| Box::new(manual_strip::ManualStrip::new(conf))), + Box::new(move |_| Box::new(checked_conversions::CheckedConversions::new(conf))), + Box::new(move |_| Box::new(mem_replace::MemReplace::new(conf))), + Box::new(move |_| Box::new(ranges::Ranges::new(conf))), + Box::new(move |_| Box::new(from_over_into::FromOverInto::new(conf))), + Box::new(move |_| Box::new(use_self::UseSelf::new(conf))), + Box::new(move |_| Box::new(missing_const_for_fn::MissingConstForFn::new(conf))), + Box::new(move |_| Box::new(needless_question_mark::NeedlessQuestionMark)), + Box::new(move |_| Box::new(casts::Casts::new(conf))), + Box::new(|_| Box::new(size_of_in_element_count::SizeOfInElementCount)), + Box::new(|_| Box::new(same_name_method::SameNameMethod)), + Box::new(move |_| Box::new(index_refutable_slice::IndexRefutableSlice::new(conf))), + Box::new(|_| Box::::default()), + Box::new(move |_| { + Box::new(inconsistent_struct_constructor::InconsistentStructConstructor::new( + conf, + )) + }), + { + let format_args = format_args_storage.clone(); + Box::new(move |_| Box::new(methods::Methods::new(conf, format_args.clone()))) + }, + { + let format_args = format_args_storage.clone(); + Box::new(move |_| Box::new(unit_types::UnitTypes::new(format_args.clone()))) + }, + Box::new(move |_| Box::new(loops::Loops::new(conf))), + Box::new(|_| Box::::default()), + Box::new(move |_| Box::new(lifetimes::Lifetimes::new(conf))), + Box::new(|_| Box::new(entry::HashMapPass)), + Box::new(|_| Box::new(minmax::MinMaxPass)), + Box::new(|_| Box::new(zero_div_zero::ZeroDiv)), + Box::new(|_| Box::new(mutex_atomic::Mutex)), + Box::new(|_| Box::new(needless_update::NeedlessUpdate)), + Box::new(|_| Box::new(needless_borrowed_ref::NeedlessBorrowedRef)), + Box::new(|_| Box::new(borrow_deref_ref::BorrowDerefRef)), + Box::new(|_| Box::::default()), + Box::new(|_| Box::new(temporary_assignment::TemporaryAssignment)), + Box::new(move |_| Box::new(transmute::Transmute::new(conf))), + Box::new(move |_| Box::new(cognitive_complexity::CognitiveComplexity::new(conf))), + Box::new(move |_| Box::new(escape::BoxedLocal::new(conf))), + Box::new(move |_| Box::new(vec::UselessVec::new(conf))), + Box::new(move |_| Box::new(panic_unimplemented::PanicUnimplemented::new(conf))), + Box::new(|_| Box::new(strings::StringLitAsBytes)), + Box::new(|_| Box::new(derive::Derive)), + Box::new(move |_| Box::new(derivable_impls::DerivableImpls::new(conf))), + Box::new(|_| Box::new(drop_forget_ref::DropForgetRef)), + Box::new(|_| Box::new(empty_enums::EmptyEnums)), + Box::new(|_| Box::::default()), + Box::new(move |tcx| Box::new(ifs::CopyAndPaste::new(tcx, conf))), + Box::new(|_| Box::new(copy_iterator::CopyIterator)), + { + let format_args = format_args_storage.clone(); + Box::new(move |_| Box::new(format::UselessFormat::new(format_args.clone()))) + }, + Box::new(|_| Box::new(swap::Swap)), + Box::new(|_| Box::new(panicking_overflow_checks::PanickingOverflowChecks)), + Box::new(|_| Box::::default()), + Box::new(move |_| Box::new(disallowed_names::DisallowedNames::new(conf))), + Box::new(move |tcx| Box::new(functions::Functions::new(tcx, conf))), + Box::new(move |_| Box::new(doc::Documentation::new(conf))), + Box::new(|_| Box::new(neg_multiply::NegMultiply)), + Box::new(|_| Box::new(let_if_seq::LetIfSeq)), + Box::new(|_| Box::new(mixed_read_write_in_expression::EvalOrderDependence)), + Box::new(move |_| Box::new(missing_doc::MissingDoc::new(conf))), + Box::new(|_| Box::new(missing_inline::MissingInline)), + Box::new(move |_| Box::new(exhaustive_items::ExhaustiveItems)), + Box::new(|_| Box::new(unused_result_ok::UnusedResultOk)), + Box::new(|_| Box::new(match_result_ok::MatchResultOk)), + Box::new(|_| Box::new(partialeq_ne_impl::PartialEqNeImpl)), + Box::new(|_| Box::new(unused_io_amount::UnusedIoAmount)), + Box::new(move |_| Box::new(large_enum_variant::LargeEnumVariant::new(conf))), + { + let format_args = format_args_storage.clone(); + Box::new(move |_| Box::new(explicit_write::ExplicitWrite::new(format_args.clone()))) + }, + Box::new(|_| Box::new(needless_pass_by_value::NeedlessPassByValue)), + Box::new(move |tcx| Box::new(pass_by_ref_or_value::PassByRefOrValue::new(tcx, conf))), + Box::new(|_| Box::new(ref_option_ref::RefOptionRef)), + Box::new(|_| Box::new(infinite_iter::InfiniteIter)), + Box::new(|_| Box::new(inline_fn_without_body::InlineFnWithoutBody)), + Box::new(|_| Box::::default()), + Box::new(|_| Box::new(implicit_hasher::ImplicitHasher)), + Box::new(|_| Box::new(fallible_impl_from::FallibleImplFrom)), + Box::new(move |_| Box::new(question_mark::QuestionMark::new(conf))), + Box::new(|_| Box::new(question_mark_used::QuestionMarkUsed)), + Box::new(|_| Box::new(suspicious_trait_impl::SuspiciousImpl)), + Box::new(|_| Box::new(map_unit_fn::MapUnit)), + Box::new(move |_| Box::new(inherent_impl::MultipleInherentImpl::new(conf))), + Box::new(|_| Box::new(neg_cmp_op_on_partial_ord::NoNegCompOpForPartialOrd)), + Box::new(move |_| Box::new(unwrap::Unwrap::new(conf))), + Box::new(move |_| Box::new(indexing_slicing::IndexingSlicing::new(conf))), + Box::new(move |tcx| Box::new(non_copy_const::NonCopyConst::new(tcx, conf))), + Box::new(|_| Box::new(redundant_clone::RedundantClone)), + Box::new(|_| Box::new(slow_vector_initialization::SlowVectorInit)), + Box::new(move |_| Box::new(unnecessary_wraps::UnnecessaryWraps::new(conf))), + Box::new(|_| Box::new(assertions_on_constants::AssertionsOnConstants::new(conf))), + Box::new(|_| Box::new(assertions_on_result_states::AssertionsOnResultStates)), + Box::new(|_| Box::new(inherent_to_string::InherentToString)), + Box::new(move |_| Box::new(trait_bounds::TraitBounds::new(conf))), + Box::new(|_| Box::new(comparison_chain::ComparisonChain)), + Box::new(move |tcx| Box::new(mut_key::MutableKeyType::new(tcx, conf))), + Box::new(|_| Box::new(reference::DerefAddrOf)), + { + let format_args = format_args_storage.clone(); + Box::new(move |_| Box::new(format_impl::FormatImpl::new(format_args.clone()))) + }, + Box::new(|_| Box::new(redundant_closure_call::RedundantClosureCall)), + Box::new(|_| Box::new(unused_unit::UnusedUnit)), + Box::new(|_| Box::new(returns::Return)), + Box::new(move |_| Box::new(collapsible_if::CollapsibleIf::new(conf))), + Box::new(|_| Box::new(items_after_statements::ItemsAfterStatements)), + Box::new(|_| Box::new(needless_parens_on_range_literals::NeedlessParensOnRangeLiterals)), + Box::new(|_| Box::new(needless_continue::NeedlessContinue)), + Box::new(|_| Box::new(create_dir::CreateDir)), + Box::new(move |_| Box::new(item_name_repetitions::ItemNameRepetitions::new(conf))), + Box::new(move |_| Box::new(upper_case_acronyms::UpperCaseAcronyms::new(conf))), + Box::new(|_| Box::::default()), + Box::new(move |_| Box::new(unused_self::UnusedSelf::new(conf))), + Box::new(|_| Box::new(mutable_debug_assertion::DebugAssertWithMutCall)), + Box::new(|_| Box::new(exit::Exit)), + Box::new(move |_| Box::new(to_digit_is_some::ToDigitIsSome::new(conf))), + Box::new(move |_| Box::new(large_stack_arrays::LargeStackArrays::new(conf))), + Box::new(move |_| Box::new(large_const_arrays::LargeConstArrays::new(conf))), + Box::new(|_| Box::new(floating_point_arithmetic::FloatingPointArithmetic)), + Box::new(|_| Box::new(as_conversions::AsConversions)), + Box::new(|_| Box::new(let_underscore::LetUnderscore)), + Box::new(move |_| Box::new(excessive_bools::ExcessiveBools::new(conf))), + Box::new(move |_| Box::new(wildcard_imports::WildcardImports::new(conf))), + Box::new(|_| Box::::default()), + Box::new(|_| Box::>::default()), + Box::new(|_| Box::new(option_if_let_else::OptionIfLetElse)), + Box::new(|_| Box::new(future_not_send::FutureNotSend)), + Box::new(move |_| Box::new(large_futures::LargeFuture::new(conf))), + Box::new(|_| Box::new(if_let_mutex::IfLetMutex)), + Box::new(|_| Box::new(if_not_else::IfNotElse)), + Box::new(|_| Box::new(equatable_if_let::PatternEquality)), + Box::new(|_| Box::new(manual_async_fn::ManualAsyncFn)), + Box::new(|_| Box::new(panic_in_result_fn::PanicInResultFn)), + Box::new(|_| Box::::default()), + Box::new(|_| Box::new(pattern_type_mismatch::PatternTypeMismatch)), + Box::new(|_| Box::::default()), + Box::new(|_| Box::new(semicolon_if_nothing_returned::SemicolonIfNothingReturned)), + Box::new(|_| Box::new(async_yields_async::AsyncYieldsAsync)), + { + let attrs = attr_storage.clone(); + Box::new(move |tcx| Box::new(disallowed_macros::DisallowedMacros::new(tcx, conf, attrs.clone()))) + }, + Box::new(move |tcx| Box::new(disallowed_methods::DisallowedMethods::new(tcx, conf))), + Box::new(|_| Box::new(empty_drop::EmptyDrop)), + Box::new(|_| Box::new(strings::StrToString)), + Box::new(|_| Box::new(zero_sized_map_values::ZeroSizedMapValues)), + Box::new(|_| Box::::default()), + Box::new(|_| Box::new(redundant_slicing::RedundantSlicing)), + Box::new(|_| Box::new(from_str_radix_10::FromStrRadix10)), + Box::new(move |_| Box::new(if_then_some_else_none::IfThenSomeElseNone::new(conf))), + Box::new(|_| Box::new(bool_assert_comparison::BoolAssertComparison)), + Box::new(|_| Box::::default()), + Box::new(move |tcx| Box::new(disallowed_types::DisallowedTypes::new(tcx, conf))), + Box::new(move |tcx| Box::new(missing_enforced_import_rename::ImportRename::new(tcx, conf))), + Box::new(|_| Box::new(strlen_on_c_strings::StrlenOnCStrings)), + Box::new(move |_| Box::new(self_named_constructors::SelfNamedConstructors)), + Box::new(move |_| Box::new(iter_not_returning_iterator::IterNotReturningIterator)), + Box::new(move |_| Box::new(manual_assert::ManualAssert)), + Box::new(move |_| Box::new(non_send_fields_in_send_ty::NonSendFieldInSendTy::new(conf))), + Box::new(move |_| Box::new(undocumented_unsafe_blocks::UndocumentedUnsafeBlocks::new(conf))), + { + let format_args = format_args_storage.clone(); + Box::new(move |tcx| Box::new(format_args::FormatArgs::new(tcx, conf, format_args.clone()))) + }, + Box::new(|_| Box::new(trailing_empty_array::TrailingEmptyArray)), + Box::new(|_| Box::new(needless_late_init::NeedlessLateInit)), + Box::new(|_| Box::new(return_self_not_must_use::ReturnSelfNotMustUse)), + Box::new(|_| Box::new(init_numbered_fields::NumberedFields)), + Box::new(move |_| Box::new(manual_bits::ManualBits::new(conf))), + Box::new(|_| Box::new(default_union_representation::DefaultUnionRepresentation)), + Box::new(|_| Box::::default()), + Box::new(move |_| Box::new(dbg_macro::DbgMacro::new(conf))), + { + let format_args = format_args_storage.clone(); + Box::new(move |_| Box::new(write::Write::new(conf, format_args.clone()))) + }, + Box::new(move |_| Box::new(cargo::Cargo::new(conf))), + Box::new(|_| Box::new(empty_with_brackets::EmptyWithBrackets::default())), + Box::new(|_| Box::new(unnecessary_owned_empty_strings::UnnecessaryOwnedEmptyStrings)), + Box::new(|_| Box::new(format_push_string::FormatPushString)), + Box::new(move |_| Box::new(large_include_file::LargeIncludeFile::new(conf))), + Box::new(|_| Box::new(strings::TrimSplitWhitespace)), + Box::new(|_| Box::new(rc_clone_in_vec_init::RcCloneInVecInit)), + Box::new(|_| Box::new(swap_ptr_to_ref::SwapPtrToRef)), + Box::new(|_| Box::new(mismatching_type_param_order::TypeParamMismatch)), + Box::new(|_| Box::new(read_zero_byte_vec::ReadZeroByteVec)), + Box::new(|_| Box::new(default_instead_of_iter_empty::DefaultIterEmpty)), + Box::new(move |_| Box::new(manual_rem_euclid::ManualRemEuclid::new(conf))), + Box::new(move |_| Box::new(manual_retain::ManualRetain::new(conf))), + Box::new(move |_| Box::new(manual_rotate::ManualRotate)), + Box::new(move |_| Box::new(operators::Operators::new(conf))), + Box::new(move |_| Box::new(std_instead_of_core::StdReexports::new(conf))), + Box::new(move |_| Box::new(time_subtraction::UncheckedTimeSubtraction::new(conf))), + Box::new(|_| Box::new(partialeq_to_none::PartialeqToNone)), + Box::new(move |_| Box::new(manual_abs_diff::ManualAbsDiff::new(conf))), + Box::new(move |_| Box::new(manual_clamp::ManualClamp::new(conf))), + Box::new(|_| Box::new(manual_string_new::ManualStringNew)), + Box::new(|_| Box::new(unused_peekable::UnusedPeekable)), + Box::new(|_| Box::new(bool_to_int_with_if::BoolToIntWithIf)), + Box::new(|_| Box::new(box_default::BoxDefault)), + Box::new(|_| Box::new(implicit_saturating_add::ImplicitSaturatingAdd)), + Box::new(|_| Box::new(missing_trait_methods::MissingTraitMethods)), + Box::new(|_| Box::new(from_raw_with_void_ptr::FromRawWithVoidPtr)), + Box::new(|_| Box::new(suspicious_xor_used_as_pow::ConfusingXorAndPow)), + Box::new(move |_| Box::new(manual_is_ascii_check::ManualIsAsciiCheck::new(conf))), + Box::new(move |_| Box::new(semicolon_block::SemicolonBlock::new(conf))), + Box::new(|_| Box::new(permissions_set_readonly_false::PermissionsSetReadonlyFalse)), + Box::new(|_| Box::new(size_of_ref::SizeOfRef)), + Box::new(|_| Box::new(multiple_unsafe_ops_per_block::MultipleUnsafeOpsPerBlock)), + Box::new(move |_| Box::new(extra_unused_type_parameters::ExtraUnusedTypeParameters::new(conf))), + Box::new(|_| Box::new(no_mangle_with_rust_abi::NoMangleWithRustAbi)), + Box::new(|_| Box::new(collection_is_never_read::CollectionIsNeverRead)), + Box::new(|_| Box::new(missing_assert_message::MissingAssertMessage)), + Box::new(|_| Box::new(needless_maybe_sized::NeedlessMaybeSized)), + Box::new(|_| Box::new(redundant_async_block::RedundantAsyncBlock)), + Box::new(move |_| Box::new(manual_main_separator_str::ManualMainSeparatorStr::new(conf))), + Box::new(|_| Box::new(unnecessary_struct_initialization::UnnecessaryStruct)), + Box::new(move |_| Box::new(unnecessary_box_returns::UnnecessaryBoxReturns::new(conf))), + Box::new(|_| Box::new(tests_outside_test_module::TestsOutsideTestModule)), + Box::new(|_| Box::new(manual_slice_size_calculation::ManualSliceSizeCalculation::new(conf))), + Box::new(|_| Box::new(items_after_test_module::ItemsAfterTestModule)), + Box::new(|_| Box::new(default_constructed_unit_structs::DefaultConstructedUnitStructs)), + Box::new(|_| Box::new(missing_fields_in_debug::MissingFieldsInDebug)), + Box::new(|_| Box::new(endian_bytes::EndianBytes)), + Box::new(|_| Box::new(redundant_type_annotations::RedundantTypeAnnotations)), + Box::new(|_| Box::new(arc_with_non_send_sync::ArcWithNonSendSync)), + Box::new(|_| Box::new(needless_ifs::NeedlessIfs)), + Box::new(move |_| Box::new(min_ident_chars::MinIdentChars::new(conf))), + Box::new(move |_| Box::new(large_stack_frames::LargeStackFrames::new(conf))), + Box::new(|_| Box::new(single_range_in_vec_init::SingleRangeInVecInit)), + Box::new(move |_| Box::new(needless_pass_by_ref_mut::NeedlessPassByRefMut::new(conf))), + Box::new(|tcx| Box::new(non_canonical_impls::NonCanonicalImpls::new(tcx))), + Box::new(move |_| Box::new(single_call_fn::SingleCallFn::new(conf))), + Box::new(move |_| Box::new(legacy_numeric_constants::LegacyNumericConstants::new(conf))), + Box::new(|_| Box::new(manual_range_patterns::ManualRangePatterns)), + Box::new(move |_| Box::new(tuple_array_conversions::TupleArrayConversions::new(conf))), + Box::new(move |_| Box::new(manual_float_methods::ManualFloatMethods::new(conf))), + Box::new(|_| Box::new(four_forward_slashes::FourForwardSlashes)), + Box::new(|_| Box::new(error_impl_error::ErrorImplError)), + Box::new(move |_| Box::new(absolute_paths::AbsolutePaths::new(conf))), + Box::new(|_| Box::new(redundant_locals::RedundantLocals)), + Box::new(|_| Box::new(ignored_unit_patterns::IgnoredUnitPatterns)), + Box::new(|_| Box::::default()), + Box::new(|_| Box::new(implied_bounds_in_impls::ImpliedBoundsInImpls)), + Box::new(|_| Box::new(missing_asserts_for_indexing::MissingAssertsForIndexing)), + Box::new(|_| Box::new(unnecessary_map_on_constructor::UnnecessaryMapOnConstructor)), + Box::new(move |_| { + Box::new(needless_borrows_for_generic_args::NeedlessBorrowsForGenericArgs::new( + conf, + )) + }), + Box::new(move |_| Box::new(manual_hash_one::ManualHashOne::new(conf))), + Box::new(|_| Box::new(iter_without_into_iter::IterWithoutIntoIter)), + Box::new(|_| Box::>::default()), + Box::new(|_| Box::new(iter_over_hash_type::IterOverHashType)), + Box::new(|_| Box::new(impl_hash_with_borrow_str_and_bytes::ImplHashWithBorrowStrBytes)), + Box::new(move |_| Box::new(repeat_vec_with_capacity::RepeatVecWithCapacity::new(conf))), + Box::new(|_| Box::new(uninhabited_references::UninhabitedReferences)), + Box::new(|_| Box::new(ineffective_open_options::IneffectiveOpenOptions)), + Box::new(|_| Box::::default()), + Box::new(move |_| Box::new(pub_underscore_fields::PubUnderscoreFields::new(conf))), + Box::new(move |_| Box::new(missing_const_for_thread_local::MissingConstForThreadLocal::new(conf))), + Box::new(move |tcx| Box::new(incompatible_msrv::IncompatibleMsrv::new(tcx, conf))), + Box::new(|_| Box::new(to_string_trait_impl::ToStringTraitImpl)), + Box::new(move |_| Box::new(assigning_clones::AssigningClones::new(conf))), + Box::new(|_| Box::new(zero_repeat_side_effects::ZeroRepeatSideEffects)), + Box::new(move |_| Box::new(macro_metavars_in_unsafe::ExprMetavarsInUnsafe::new(conf))), + Box::new(move |_| Box::new(string_patterns::StringPatterns::new(conf))), + Box::new(|_| Box::new(set_contains_or_insert::SetContainsOrInsert)), + Box::new(|_| Box::new(zombie_processes::ZombieProcesses)), + Box::new(|_| Box::new(pointers_in_nomem_asm_block::PointersInNomemAsmBlock)), + Box::new(move |_| Box::new(manual_is_power_of_two::ManualIsPowerOfTwo::new(conf))), + Box::new(|_| Box::new(non_zero_suggestions::NonZeroSuggestions)), + Box::new(|_| Box::new(literal_string_with_formatting_args::LiteralStringWithFormattingArg)), + Box::new(move |_| Box::new(unused_trait_names::UnusedTraitNames::new(conf))), + Box::new(|_| Box::new(manual_ignore_case_cmp::ManualIgnoreCaseCmp)), + Box::new(|_| Box::new(unnecessary_literal_bound::UnnecessaryLiteralBound)), + Box::new(move |_| Box::new(arbitrary_source_item_ordering::ArbitrarySourceItemOrdering::new(conf))), + Box::new(|_| Box::new(useless_concat::UselessConcat)), + Box::new(|_| Box::new(unneeded_struct_pattern::UnneededStructPattern)), + Box::new(|_| Box::::default()), + Box::new(move |_| Box::new(non_std_lazy_statics::NonStdLazyStatic::new(conf))), + Box::new(|_| Box::new(manual_option_as_slice::ManualOptionAsSlice::new(conf))), + Box::new(|_| Box::new(single_option_map::SingleOptionMap)), + Box::new(move |_| Box::new(redundant_test_prefix::RedundantTestPrefix)), + Box::new(|_| Box::new(cloned_ref_to_slice_refs::ClonedRefToSliceRefs::new(conf))), + Box::new(|_| Box::new(infallible_try_from::InfallibleTryFrom)), + Box::new(|_| Box::new(coerce_container_to_any::CoerceContainerToAny)), + Box::new(|_| Box::new(toplevel_ref_arg::ToplevelRefArg)), + Box::new(|_| Box::new(volatile_composites::VolatileComposites)), + Box::new(|_| Box::::default()), + // add late passes here, used by `cargo dev new_lint` + ]; + store.late_passes.extend(late_lints); } diff --git a/clippy_lints/src/loops/manual_find.rs b/clippy_lints/src/loops/manual_find.rs index 3455a47ba078d..d94dcfab23c73 100644 --- a/clippy_lints/src/loops/manual_find.rs +++ b/clippy_lints/src/loops/manual_find.rs @@ -5,7 +5,7 @@ use clippy_utils::res::{MaybeDef, MaybeQPath, MaybeResPath}; use clippy_utils::source::snippet_with_applicability; use clippy_utils::ty::implements_trait; use clippy_utils::usage::contains_return_break_continue_macro; -use clippy_utils::{higher, peel_blocks_with_stmt}; +use clippy_utils::{as_some_expr, higher, peel_blocks_with_stmt}; use rustc_errors::Applicability; use rustc_hir::lang_items::LangItem; use rustc_hir::{BindingMode, Block, Expr, ExprKind, HirId, Node, Pat, PatKind, Stmt, StmtKind}; @@ -33,8 +33,7 @@ pub(super) fn check<'tcx>( && let [stmt] = block.stmts && let StmtKind::Semi(semi) = stmt.kind && let ExprKind::Ret(Some(ret_value)) = semi.kind - && let ExprKind::Call(ctor, [inner_ret]) = ret_value.kind - && ctor.res(cx).ctor_parent(cx).is_lang_item(cx, LangItem::OptionSome) + && let Some(inner_ret) = as_some_expr(cx, ret_value) && inner_ret.res_local_id() == Some(binding_id) && !contains_return_break_continue_macro(cond) && let Some((last_stmt, last_ret)) = last_stmt_and_ret(cx, expr) diff --git a/clippy_lints/src/loops/mod.rs b/clippy_lints/src/loops/mod.rs index a064a5910ef98..21198c3c8bc20 100644 --- a/clippy_lints/src/loops/mod.rs +++ b/clippy_lints/src/loops/mod.rs @@ -880,6 +880,15 @@ impl<'tcx> LateLintPass<'tcx> for Loops { missing_spin_loop::check(cx, condition, body); manual_while_let_some::check(cx, condition, body, span); } + + if let ExprKind::MethodCall(path, recv, [arg], _) = expr.kind + && matches!( + path.ident.name, + sym::all | sym::any | sym::filter_map | sym::find_map | sym::flat_map | sym::for_each | sym::map + ) + { + unused_enumerate_index::check_method(cx, expr, recv, arg); + } } } @@ -908,7 +917,7 @@ impl Loops { same_item_push::check(cx, pat, arg, body, expr, self.msrv); manual_flatten::check(cx, pat, arg, body, span, self.msrv); manual_find::check(cx, pat, arg, body, span, expr); - unused_enumerate_index::check(cx, pat, arg, body); + unused_enumerate_index::check(cx, arg, pat, None, body); char_indices_as_byte_indices::check(cx, pat, arg, body); } diff --git a/clippy_lints/src/loops/unused_enumerate_index.rs b/clippy_lints/src/loops/unused_enumerate_index.rs index b893b0baad490..82ded453616d4 100644 --- a/clippy_lints/src/loops/unused_enumerate_index.rs +++ b/clippy_lints/src/loops/unused_enumerate_index.rs @@ -1,42 +1,87 @@ use super::UNUSED_ENUMERATE_INDEX; -use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::res::MaybeDef; -use clippy_utils::source::snippet; -use clippy_utils::{pat_is_wild, sugg}; +use clippy_utils::diagnostics::span_lint_hir_and_then; +use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; +use clippy_utils::source::{SpanRangeExt, walk_span_to_context}; +use clippy_utils::{expr_or_init, pat_is_wild}; use rustc_errors::Applicability; -use rustc_hir::def::DefKind; -use rustc_hir::{Expr, ExprKind, Pat, PatKind}; +use rustc_hir::{Expr, ExprKind, Pat, PatKind, TyKind}; use rustc_lint::LateContext; -use rustc_span::sym; +use rustc_span::{Span, SyntaxContext, sym}; -/// Checks for the `UNUSED_ENUMERATE_INDEX` lint. -/// -/// The lint is also partially implemented in `clippy_lints/src/methods/unused_enumerate_index.rs`. -pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'tcx>, arg: &Expr<'_>, body: &'tcx Expr<'tcx>) { - if let PatKind::Tuple([index, elem], _) = pat.kind - && let ExprKind::MethodCall(_method, self_arg, [], _) = arg.kind - && let ty = cx.typeck_results().expr_ty(arg) - && pat_is_wild(cx, &index.kind, body) - && ty.is_diag_item(cx, sym::Enumerate) - && let Some((DefKind::AssocFn, call_id)) = cx.typeck_results().type_dependent_def(arg.hir_id) - && cx.tcx.is_diagnostic_item(sym::enumerate_method, call_id) +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + iter_expr: &'tcx Expr<'tcx>, + pat: &Pat<'tcx>, + ty_spans: Option<(Span, Span)>, + body: &'tcx Expr<'tcx>, +) { + if let PatKind::Tuple([idx_pat, inner_pat], _) = pat.kind + && cx.typeck_results().expr_ty(iter_expr).is_diag_item(cx, sym::Enumerate) + && pat_is_wild(cx, &idx_pat.kind, body) + && let enumerate_call = expr_or_init(cx, iter_expr) + && let ExprKind::MethodCall(_, _, [], enumerate_span) = enumerate_call.kind + && let Some(enumerate_id) = cx.typeck_results().type_dependent_def_id(enumerate_call.hir_id) + && cx.tcx.is_diagnostic_item(sym::enumerate_method, enumerate_id) + && !enumerate_call.span.from_expansion() + && !pat.span.from_expansion() + && !idx_pat.span.from_expansion() + && !inner_pat.span.from_expansion() + && let Some(enumerate_range) = enumerate_span.map_range(cx, |_, text, range| { + text.get(..range.start)? + .ends_with('.') + .then_some(range.start - 1..range.end) + }) { - span_lint_and_then( + let enumerate_span = Span::new(enumerate_range.start, enumerate_range.end, SyntaxContext::root(), None); + span_lint_hir_and_then( cx, UNUSED_ENUMERATE_INDEX, - arg.span, + enumerate_call.hir_id, + enumerate_span, "you seem to use `.enumerate()` and immediately discard the index", |diag| { - let base_iter = sugg::Sugg::hir(cx, self_arg, "base iter"); + let mut spans = Vec::with_capacity(5); + spans.push((enumerate_span, String::new())); + spans.push((pat.span.with_hi(inner_pat.span.lo()), String::new())); + spans.push((pat.span.with_lo(inner_pat.span.hi()), String::new())); + if let Some((outer, inner)) = ty_spans { + spans.push((outer.with_hi(inner.lo()), String::new())); + spans.push((outer.with_lo(inner.hi()), String::new())); + } diag.multipart_suggestion( "remove the `.enumerate()` call", - vec![ - (pat.span, snippet(cx, elem.span, "..").into_owned()), - (arg.span, base_iter.to_string()), - ], + spans, Applicability::MachineApplicable, ); }, ); } } + +pub(super) fn check_method<'tcx>( + cx: &LateContext<'tcx>, + e: &'tcx Expr<'tcx>, + recv: &'tcx Expr<'tcx>, + arg: &'tcx Expr<'tcx>, +) { + if let ExprKind::Closure(closure) = arg.kind + && let body = cx.tcx.hir_body(closure.body) + && let [param] = body.params + && cx.ty_based_def(e).opt_parent(cx).is_diag_item(cx, sym::Iterator) + && let [input] = closure.fn_decl.inputs + && !arg.span.from_expansion() + && !input.span.from_expansion() + && !recv.span.from_expansion() + && !param.span.from_expansion() + { + let ty_spans = if let TyKind::Tup([_, inner]) = input.kind { + let Some(inner) = walk_span_to_context(inner.span, SyntaxContext::root()) else { + return; + }; + Some((input.span, inner)) + } else { + None + }; + check(cx, recv, param.pat, ty_spans, body.value); + } +} diff --git a/clippy_lints/src/loops/while_let_on_iterator.rs b/clippy_lints/src/loops/while_let_on_iterator.rs index 3ea6ba341bedb..2545f81f1afad 100644 --- a/clippy_lints/src/loops/while_let_on_iterator.rs +++ b/clippy_lints/src/loops/while_let_on_iterator.rs @@ -5,11 +5,11 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; use clippy_utils::source::snippet_with_applicability; use clippy_utils::visitors::is_res_used; -use clippy_utils::{get_enclosing_loop_or_multi_call_closure, higher, is_refutable}; +use clippy_utils::{as_some_pattern, get_enclosing_loop_or_multi_call_closure, higher, is_refutable}; use rustc_errors::Applicability; use rustc_hir::def::Res; use rustc_hir::intravisit::{Visitor, walk_expr}; -use rustc_hir::{Closure, Expr, ExprKind, HirId, LangItem, LetStmt, Mutability, PatKind, UnOp}; +use rustc_hir::{Closure, Expr, ExprKind, HirId, LetStmt, Mutability, UnOp}; use rustc_lint::LateContext; use rustc_middle::hir::nested_filter::OnlyBodies; use rustc_middle::ty::adjustment::Adjust; @@ -19,8 +19,7 @@ use rustc_span::symbol::sym; pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { if let Some(higher::WhileLet { if_then, let_pat, let_expr, label, .. }) = higher::WhileLet::hir(expr) // check for `Some(..)` pattern - && let PatKind::TupleStruct(ref pat_path, some_pat, _) = let_pat.kind - && cx.qpath_res(pat_path, let_pat.hir_id).ctor_parent(cx).is_lang_item(cx, LangItem::OptionSome) + && let Some(some_pat) = as_some_pattern(cx, let_pat) // check for call to `Iterator::next` && let ExprKind::MethodCall(method_name, iter_expr, [], _) = let_expr.kind && method_name.ident.name == sym::next diff --git a/clippy_lints/src/manual_is_power_of_two.rs b/clippy_lints/src/manual_is_power_of_two.rs index 4439a28763a2f..25db719c8214e 100644 --- a/clippy_lints/src/manual_is_power_of_two.rs +++ b/clippy_lints/src/manual_is_power_of_two.rs @@ -70,12 +70,12 @@ impl<'tcx> LateLintPass<'tcx> for ManualIsPowerOfTwo { if !expr.span.from_expansion() && let Some((lhs, rhs)) = unexpanded_binop_operands(expr, BinOpKind::Eq) { - if let Some(a) = count_ones_receiver(cx, lhs) - && is_integer_literal(rhs, 1) + if is_integer_literal(rhs, 1) + && let Some(a) = count_ones_receiver(cx, lhs) { self.build_sugg(cx, expr, a); - } else if let Some(a) = count_ones_receiver(cx, rhs) - && is_integer_literal(lhs, 1) + } else if is_integer_literal(lhs, 1) + && let Some(a) = count_ones_receiver(cx, rhs) { self.build_sugg(cx, expr, a); } else if is_integer_literal(rhs, 0) diff --git a/clippy_lints/src/manual_option_as_slice.rs b/clippy_lints/src/manual_option_as_slice.rs index dce0d105f4c5b..5cf90eecaa978 100644 --- a/clippy_lints/src/manual_option_as_slice.rs +++ b/clippy_lints/src/manual_option_as_slice.rs @@ -3,10 +3,9 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::msrvs::Msrv; use clippy_utils::res::{MaybeDef, MaybeQPath, MaybeResPath}; use clippy_utils::source::snippet_with_context; -use clippy_utils::{is_none_pattern, msrvs, peel_hir_expr_refs, sym}; +use clippy_utils::{as_some_pattern, is_none_pattern, msrvs, peel_hir_expr_refs, sym}; use rustc_errors::Applicability; -use rustc_hir::def::{DefKind, Res}; -use rustc_hir::{Arm, Expr, ExprKind, LangItem, Pat, PatKind, QPath, is_range_literal}; +use rustc_hir::{Arm, Expr, ExprKind, Pat, PatKind, QPath, is_range_literal}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::impl_lint_pass; use rustc_span::{Span, Symbol}; @@ -154,10 +153,8 @@ fn check_as_ref(cx: &LateContext<'_>, expr: &Expr<'_>, span: Span, msrv: Msrv) { } fn extract_ident_from_some_pat(cx: &LateContext<'_>, pat: &Pat<'_>) -> Option { - if let PatKind::TupleStruct(QPath::Resolved(None, path), [binding], _) = pat.kind - && let Res::Def(DefKind::Ctor(..), def_id) = path.res + if let Some([binding]) = as_some_pattern(cx, pat) && let PatKind::Binding(_mode, _hir_id, ident, _inner_pat) = binding.kind - && clippy_utils::is_lang_item_or_ctor(cx, def_id, LangItem::OptionSome) { Some(ident.name) } else { diff --git a/clippy_lints/src/match_result_ok.rs b/clippy_lints/src/match_result_ok.rs index fb83f7cf65ddb..1ebbd209ae52d 100644 --- a/clippy_lints/src/match_result_ok.rs +++ b/clippy_lints/src/match_result_ok.rs @@ -1,9 +1,9 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::res::MaybeDef; use clippy_utils::source::snippet_with_context; -use clippy_utils::{higher, sym}; +use clippy_utils::{as_some_pattern, higher, sym}; use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind, LangItem, PatKind}; +use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; @@ -55,10 +55,9 @@ impl<'tcx> LateLintPass<'tcx> for MatchResultOk { }; if let ExprKind::MethodCall(ok_path, recv, [], ..) = let_expr.kind //check is expr.ok() has type Result.ok(, _) - && let PatKind::TupleStruct(ref pat_path, [ok_pat], _) = let_pat.kind //get operation && ok_path.ident.name == sym::ok && cx.typeck_results().expr_ty(recv).is_diag_item(cx, sym::Result) - && cx.qpath_res(pat_path, let_pat.hir_id).ctor_parent(cx).is_lang_item(cx, LangItem::OptionSome) + && let Some([ok_pat]) = as_some_pattern(cx, let_pat) //get operation && let ctxt = expr.span.ctxt() && let_expr.span.ctxt() == ctxt && let_pat.span.ctxt() == ctxt diff --git a/clippy_lints/src/matches/manual_filter.rs b/clippy_lints/src/matches/manual_filter.rs index d7224052ebc58..da68f8421c168 100644 --- a/clippy_lints/src/matches/manual_filter.rs +++ b/clippy_lints/src/matches/manual_filter.rs @@ -1,8 +1,9 @@ +use clippy_utils::as_some_expr; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::res::{MaybeDef, MaybeQPath, MaybeResPath}; use clippy_utils::visitors::contains_unsafe_block; -use rustc_hir::LangItem::{OptionNone, OptionSome}; +use rustc_hir::LangItem::OptionNone; use rustc_hir::{Arm, Expr, ExprKind, HirId, Pat, PatKind}; use rustc_lint::LateContext; use rustc_span::{SyntaxContext, sym}; @@ -52,21 +53,19 @@ fn peels_blocks_incl_unsafe<'a>(expr: &'a Expr<'a>) -> &'a Expr<'a> { peels_blocks_incl_unsafe_opt(expr).unwrap_or(expr) } -// function called for each expression: +/// Checks whether resolves to `Some(target)` +// NOTE: called for each expression: // Some(x) => if { // // } else { // // } -// Returns true if resolves to `Some(x)`, `false` otherwise fn is_some_expr(cx: &LateContext<'_>, target: HirId, ctxt: SyntaxContext, expr: &Expr<'_>) -> bool { if let Some(inner_expr) = peels_blocks_incl_unsafe_opt(expr) // there can be not statements in the block as they would be removed when switching to `.filter` - && let ExprKind::Call(callee, [arg]) = inner_expr.kind + && let Some(arg) = as_some_expr(cx, inner_expr) { - return ctxt == expr.span.ctxt() - && callee.res(cx).ctor_parent(cx).is_lang_item(cx, OptionSome) - && arg.res_local_id() == Some(target); + return ctxt == expr.span.ctxt() && arg.res_local_id() == Some(target); } false } diff --git a/clippy_lints/src/matches/manual_ok_err.rs b/clippy_lints/src/matches/manual_ok_err.rs index 9ced6c9d452ba..c35c3d1f62e6b 100644 --- a/clippy_lints/src/matches/manual_ok_err.rs +++ b/clippy_lints/src/matches/manual_ok_err.rs @@ -1,12 +1,12 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::res::{MaybeDef, MaybeQPath}; +use clippy_utils::res::MaybeDef; use clippy_utils::source::{indent_of, reindent_multiline}; use clippy_utils::sugg::Sugg; use clippy_utils::ty::{option_arg_ty, peel_and_count_ty_refs}; -use clippy_utils::{get_parent_expr, peel_blocks, span_contains_comment}; +use clippy_utils::{as_some_expr, get_parent_expr, is_none_expr, peel_blocks, span_contains_comment}; use rustc_ast::{BindingMode, Mutability}; use rustc_errors::Applicability; -use rustc_hir::LangItem::{OptionNone, OptionSome, ResultErr}; +use rustc_hir::LangItem::ResultErr; use rustc_hir::def::{DefKind, Res}; use rustc_hir::{Arm, Expr, ExprKind, Pat, PatExpr, PatExprKind, PatKind, Path, QPath}; use rustc_lint::{LateContext, LintContext}; @@ -106,8 +106,7 @@ fn is_ok_or_err<'hir>(cx: &LateContext<'_>, pat: &Pat<'hir>) -> Option<(bool, &' /// Check if `expr` contains `Some(ident)`, possibly as a block fn is_some_ident<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, ident: &Ident, ty: Ty<'tcx>) -> bool { - if let ExprKind::Call(body_callee, [body_arg]) = peel_blocks(expr).kind - && body_callee.res(cx).ctor_parent(cx).is_lang_item(cx, OptionSome) + if let Some(body_arg) = as_some_expr(cx, peel_blocks(expr)) && cx.typeck_results().expr_ty(body_arg) == ty && let ExprKind::Path(QPath::Resolved( _, @@ -124,7 +123,7 @@ fn is_some_ident<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, ident: &Ident, t /// Check if `expr` is `None`, possibly as a block fn is_none(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { - peel_blocks(expr).res(cx).ctor_parent(cx).is_lang_item(cx, OptionNone) + is_none_expr(cx, peel_blocks(expr)) } /// Suggest replacing `expr` by `scrutinee.METHOD()`, where `METHOD` is either `ok` or diff --git a/clippy_lints/src/matches/manual_utils.rs b/clippy_lints/src/matches/manual_utils.rs index 19b3572bd3ae7..6a755fac45fe3 100644 --- a/clippy_lints/src/matches/manual_utils.rs +++ b/clippy_lints/src/matches/manual_utils.rs @@ -1,18 +1,17 @@ use crate::map_unit_fn::OPTION_MAP_UNIT_FN; use crate::matches::MATCH_AS_REF; -use clippy_utils::res::{MaybeDef, MaybeQPath, MaybeResPath}; +use clippy_utils::res::{MaybeDef, MaybeResPath}; use clippy_utils::source::{snippet_with_applicability, snippet_with_context}; use clippy_utils::sugg::Sugg; use clippy_utils::ty::{is_copy, is_unsafe_fn, peel_and_count_ty_refs}; use clippy_utils::{ - CaptureKind, can_move_expr_to_closure, expr_requires_coercion, is_else_clause, is_lint_allowed, peel_blocks, - peel_hir_expr_refs, peel_hir_expr_while, + CaptureKind, as_some_pattern, can_move_expr_to_closure, expr_requires_coercion, is_else_clause, is_lint_allowed, + is_none_expr, is_none_pattern, peel_blocks, peel_hir_expr_refs, peel_hir_expr_while, }; use rustc_ast::util::parser::ExprPrecedence; use rustc_errors::Applicability; -use rustc_hir::LangItem::{OptionNone, OptionSome}; use rustc_hir::def::Res; -use rustc_hir::{BindingMode, Expr, ExprKind, HirId, Mutability, Pat, PatExpr, PatExprKind, PatKind, Path, QPath}; +use rustc_hir::{BindingMode, Expr, ExprKind, HirId, Mutability, Pat, PatKind, Path, QPath}; use rustc_lint::LateContext; use rustc_span::{SyntaxContext, sym}; @@ -44,16 +43,16 @@ where try_parse_pattern(cx, then_pat, expr_ctxt), else_pat.map_or(Some(OptionPat::Wild), |p| try_parse_pattern(cx, p, expr_ctxt)), ) { - (Some(OptionPat::Wild), Some(OptionPat::Some { pattern, ref_count })) if is_none_expr(cx, then_body) => { + (Some(OptionPat::Wild), Some(OptionPat::Some { pattern, ref_count })) if is_none_arm_body(cx, then_body) => { (else_body, pattern, ref_count, true) }, - (Some(OptionPat::None), Some(OptionPat::Some { pattern, ref_count })) if is_none_expr(cx, then_body) => { + (Some(OptionPat::None), Some(OptionPat::Some { pattern, ref_count })) if is_none_arm_body(cx, then_body) => { (else_body, pattern, ref_count, false) }, - (Some(OptionPat::Some { pattern, ref_count }), Some(OptionPat::Wild)) if is_none_expr(cx, else_body) => { + (Some(OptionPat::Some { pattern, ref_count }), Some(OptionPat::Wild)) if is_none_arm_body(cx, else_body) => { (then_body, pattern, ref_count, true) }, - (Some(OptionPat::Some { pattern, ref_count }), Some(OptionPat::None)) if is_none_expr(cx, else_body) => { + (Some(OptionPat::Some { pattern, ref_count }), Some(OptionPat::None)) if is_none_arm_body(cx, else_body) => { (then_body, pattern, ref_count, false) }, _ => return None, @@ -255,23 +254,9 @@ pub(super) fn try_parse_pattern<'tcx>( match pat.kind { PatKind::Wild => Some(OptionPat::Wild), PatKind::Ref(pat, _, _) => f(cx, pat, ref_count + 1, ctxt), - PatKind::Expr(PatExpr { - kind: PatExprKind::Path(qpath), - hir_id, - .. - }) if cx - .qpath_res(qpath, *hir_id) - .ctor_parent(cx) - .is_lang_item(cx, OptionNone) => - { - Some(OptionPat::None) - }, - PatKind::TupleStruct(ref qpath, [pattern], _) - if cx - .qpath_res(qpath, pat.hir_id) - .ctor_parent(cx) - .is_lang_item(cx, OptionSome) - && pat.span.ctxt() == ctxt => + _ if is_none_pattern(cx, pat) => Some(OptionPat::None), + _ if let Some([pattern]) = as_some_pattern(cx, pat) + && pat.span.ctxt() == ctxt => { Some(OptionPat::Some { pattern, ref_count }) }, @@ -281,7 +266,7 @@ pub(super) fn try_parse_pattern<'tcx>( f(cx, pat, 0, ctxt) } -// Checks for the `None` value. -fn is_none_expr(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { - peel_blocks(expr).res(cx).ctor_parent(cx).is_lang_item(cx, OptionNone) +/// Checks for the `None` value, possibly in a block. +fn is_none_arm_body(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + is_none_expr(cx, peel_blocks(expr)) } diff --git a/clippy_lints/src/matches/match_as_ref.rs b/clippy_lints/src/matches/match_as_ref.rs index 2ca656edc66eb..795355f25f9e0 100644 --- a/clippy_lints/src/matches/match_as_ref.rs +++ b/clippy_lints/src/matches/match_as_ref.rs @@ -1,10 +1,9 @@ use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::res::{MaybeDef, MaybeQPath}; use clippy_utils::sugg::Sugg; use clippy_utils::ty::option_arg_ty; -use clippy_utils::{is_none_arm, peel_blocks}; +use clippy_utils::{as_some_expr, as_some_pattern, is_none_arm, peel_blocks}; use rustc_errors::Applicability; -use rustc_hir::{Arm, BindingMode, ByRef, Expr, ExprKind, LangItem, Mutability, PatKind, QPath}; +use rustc_hir::{Arm, BindingMode, ByRef, Expr, ExprKind, Mutability, PatKind, QPath}; use rustc_lint::LateContext; use rustc_middle::ty; @@ -82,14 +81,9 @@ pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: // Checks if arm has the form `Some(ref v) => Some(v)` (checks for `ref` and `ref mut`) fn as_ref_some_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> Option { - if let PatKind::TupleStruct(ref qpath, [first_pat, ..], _) = arm.pat.kind - && cx - .qpath_res(qpath, arm.pat.hir_id) - .ctor_parent(cx) - .is_lang_item(cx, LangItem::OptionSome) + if let Some([first_pat, ..]) = as_some_pattern(cx, arm.pat) && let PatKind::Binding(BindingMode(ByRef::Yes(_, mutabl), _), .., ident, _) = first_pat.kind - && let ExprKind::Call(e, [arg]) = peel_blocks(arm.body).kind - && e.res(cx).ctor_parent(cx).is_lang_item(cx, LangItem::OptionSome) + && let Some(arg) = as_some_expr(cx, peel_blocks(arm.body)) && let ExprKind::Path(QPath::Resolved(_, path2)) = arg.kind && path2.segments.len() == 1 && ident.name == path2.segments[0].ident.name diff --git a/clippy_lints/src/matches/match_single_binding.rs b/clippy_lints/src/matches/match_single_binding.rs index 82d5310663ee0..e40e21c490f3c 100644 --- a/clippy_lints/src/matches/match_single_binding.rs +++ b/clippy_lints/src/matches/match_single_binding.rs @@ -8,7 +8,7 @@ use rustc_data_structures::fx::FxHashSet; use rustc_errors::Applicability; use rustc_hir::def::Res; use rustc_hir::intravisit::{Visitor, walk_block, walk_expr, walk_path, walk_stmt}; -use rustc_hir::{Arm, Block, Expr, ExprKind, HirId, Node, PatKind, Path, Stmt, StmtKind}; +use rustc_hir::{Arm, Block, Expr, ExprKind, HirId, Item, ItemKind, Node, PatKind, Path, Stmt, StmtKind}; use rustc_lint::LateContext; use rustc_span::{Span, Symbol}; @@ -307,26 +307,6 @@ fn expr_in_nested_block(cx: &LateContext<'_>, match_expr: &Expr<'_>) -> bool { false } -fn expr_must_have_curlies(cx: &LateContext<'_>, match_expr: &Expr<'_>) -> bool { - let parent = cx.tcx.parent_hir_node(match_expr.hir_id); - if let Node::Expr(Expr { - kind: ExprKind::Closure(..) | ExprKind::Binary(..), - .. - }) - | Node::AnonConst(..) = parent - { - return true; - } - - if let Node::Arm(arm) = &cx.tcx.parent_hir_node(match_expr.hir_id) - && let ExprKind::Match(..) = arm.body.kind - { - return true; - } - - false -} - fn indent_of_nth_line(snippet: &str, nth: usize) -> Option { snippet .lines() @@ -379,14 +359,47 @@ fn sugg_with_curlies<'a>( let mut indent = " ".repeat(indent_of(cx, ex.span).unwrap_or(0)); let (mut cbrace_start, mut cbrace_end) = (String::new(), String::new()); - if !expr_in_nested_block(cx, match_expr) - && ((needs_var_binding && is_var_binding_used_later) || expr_must_have_curlies(cx, match_expr)) - { + let mut add_curlies = || { cbrace_end = format!("\n{indent}}}"); // Fix body indent due to the closure indent = " ".repeat(indent_of(cx, bind_names).unwrap_or(0)); cbrace_start = format!("{{\n{indent}"); snippet_body = reindent_snippet_if_in_block(&snippet_body, !assignment_str.is_empty()); + }; + + if !expr_in_nested_block(cx, match_expr) { + let mut parent = cx.tcx.parent_hir_node(match_expr.hir_id); + if let Node::Expr(Expr { + kind: ExprKind::Assign(..), + hir_id, + .. + }) = parent + { + parent = cx.tcx.parent_hir_node(*hir_id); + } + if let Node::Stmt(stmt) = parent { + parent = cx.tcx.parent_hir_node(stmt.hir_id); + } + + match parent { + Node::Block(..) + | Node::Expr(Expr { + kind: ExprKind::Block(..) | ExprKind::ConstBlock(..), + .. + }) => { + if needs_var_binding && is_var_binding_used_later { + add_curlies(); + } + }, + Node::Expr(..) + | Node::AnonConst(..) + | Node::Item(Item { + kind: ItemKind::Const(..), + .. + }) => add_curlies(), + Node::Arm(arm) if let ExprKind::Match(..) = arm.body.kind => add_curlies(), + _ => {}, + } } format!("{cbrace_start}{scrutinee};\n{indent}{assignment_str}{snippet_body}{cbrace_end}") diff --git a/clippy_lints/src/matches/significant_drop_in_scrutinee.rs b/clippy_lints/src/matches/significant_drop_in_scrutinee.rs index 81fecc87256c4..bac35e7f8c700 100644 --- a/clippy_lints/src/matches/significant_drop_in_scrutinee.rs +++ b/clippy_lints/src/matches/significant_drop_in_scrutinee.rs @@ -4,7 +4,7 @@ use crate::FxHashSet; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::{first_line_of_span, indent_of, snippet}; use clippy_utils::ty::{for_each_top_level_late_bound_region, is_copy}; -use clippy_utils::{get_attr, is_lint_allowed, sym}; +use clippy_utils::{get_builtin_attr, is_lint_allowed, sym}; use itertools::Itertools; use rustc_ast::Mutability; use rustc_data_structures::fx::FxIndexSet; @@ -183,7 +183,7 @@ impl<'a, 'tcx> SigDropChecker<'a, 'tcx> { fn has_sig_drop_attr_impl(&mut self, ty: Ty<'tcx>) -> bool { if let Some(adt) = ty.ty_adt_def() - && get_attr( + && get_builtin_attr( self.cx.sess(), self.cx.tcx.get_all_attrs(adt.did()), sym::has_significant_drop, diff --git a/clippy_lints/src/matches/single_match.rs b/clippy_lints/src/matches/single_match.rs index 57a91cf846b83..8642c7e349b1a 100644 --- a/clippy_lints/src/matches/single_match.rs +++ b/clippy_lints/src/matches/single_match.rs @@ -373,7 +373,10 @@ impl<'a> PatState<'a> { }, // Patterns for things which can only contain a single sub-pattern. - PatKind::Binding(_, _, _, Some(pat)) | PatKind::Ref(pat, _, _) | PatKind::Box(pat) | PatKind::Deref(pat) => { + PatKind::Binding(_, _, _, Some(pat)) + | PatKind::Ref(pat, _, _) + | PatKind::Box(pat) + | PatKind::Deref(pat) => { self.add_pat(cx, pat) }, PatKind::Tuple([sub_pat], pos) diff --git a/clippy_lints/src/mem_replace.rs b/clippy_lints/src/mem_replace.rs index ac3cbaec55f30..0f32f89666a0f 100644 --- a/clippy_lints/src/mem_replace.rs +++ b/clippy_lints/src/mem_replace.rs @@ -1,13 +1,14 @@ use clippy_config::Conf; use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg, span_lint_and_then}; use clippy_utils::msrvs::{self, Msrv}; -use clippy_utils::res::{MaybeDef, MaybeQPath}; +use clippy_utils::res::MaybeDef; use clippy_utils::source::{snippet_with_applicability, snippet_with_context}; use clippy_utils::sugg::Sugg; use clippy_utils::ty::is_non_aggregate_primitive_type; -use clippy_utils::{is_default_equivalent, is_expr_used_or_unified, peel_ref_operators, std_or_core}; +use clippy_utils::{ + as_some_expr, is_default_equivalent, is_expr_used_or_unified, is_none_expr, peel_ref_operators, std_or_core, +}; use rustc_errors::Applicability; -use rustc_hir::LangItem::{OptionNone, OptionSome}; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::impl_lint_pass; @@ -128,7 +129,7 @@ impl_lint_pass!(MemReplace => [MEM_REPLACE_OPTION_WITH_NONE, MEM_REPLACE_OPTION_WITH_SOME, MEM_REPLACE_WITH_UNINIT, MEM_REPLACE_WITH_DEFAULT]); fn check_replace_option_with_none(cx: &LateContext<'_>, src: &Expr<'_>, dest: &Expr<'_>, expr_span: Span) -> bool { - if src.res(cx).ctor_parent(cx).is_lang_item(cx, OptionNone) { + if is_none_expr(cx, src) { // Since this is a late pass (already type-checked), // and we already know that the second argument is an // `Option`, we do not need to check the first @@ -161,8 +162,7 @@ fn check_replace_option_with_some( expr_span: Span, msrv: Msrv, ) -> bool { - if let ExprKind::Call(src_func, [src_arg]) = src.kind - && src_func.res(cx).ctor_parent(cx).is_lang_item(cx, OptionSome) + if let Some(src_arg) = as_some_expr(cx, src) && msrv.meets(cx, msrvs::OPTION_REPLACE) { // We do not have to check for a `const` context here, because `core::mem::replace()` and diff --git a/clippy_lints/src/methods/err_expect.rs b/clippy_lints/src/methods/err_expect.rs index 6e9aebcf18ae4..4353f6302c4b9 100644 --- a/clippy_lints/src/methods/err_expect.rs +++ b/clippy_lints/src/methods/err_expect.rs @@ -1,7 +1,6 @@ use super::ERR_EXPECT; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::msrvs::{self, Msrv}; -use clippy_utils::res::MaybeDef; use clippy_utils::ty::has_debug_impl; use rustc_errors::Applicability; use rustc_lint::LateContext; @@ -17,12 +16,10 @@ pub(super) fn check( err_span: Span, msrv: Msrv, ) { - if cx.typeck_results().expr_ty(recv).is_diag_item(cx, sym::Result) - // Grabs the `Result` type - && let result_type = cx.typeck_results().expr_ty(recv) - // Tests if the T type in a `Result` is not None - && let Some(data_type) = get_data_type(cx, result_type) - // Tests if the T type in a `Result` implements debug + let result_ty = cx.typeck_results().expr_ty(recv); + // Grabs the `Result` type + if let Some(data_type) = get_data_type(cx, result_ty) + // Tests if the T type in a `Result` implements Debug && has_debug_impl(cx, data_type) && msrv.meets(cx, msrvs::EXPECT_ERR) { @@ -41,7 +38,7 @@ pub(super) fn check( /// Given a `Result` type, return its data (`T`). fn get_data_type<'a>(cx: &LateContext<'_>, ty: Ty<'a>) -> Option> { match ty.kind() { - ty::Adt(_, args) if ty.is_diag_item(cx, sym::Result) => args.types().next(), + ty::Adt(adt, args) if cx.tcx.is_diagnostic_item(sym::Result, adt.did()) => args.types().next(), _ => None, } } diff --git a/clippy_lints/src/methods/iter_on_single_or_empty_collections.rs b/clippy_lints/src/methods/iter_on_single_or_empty_collections.rs index 8183c30f8c56b..cdef98be14af3 100644 --- a/clippy_lints/src/methods/iter_on_single_or_empty_collections.rs +++ b/clippy_lints/src/methods/iter_on_single_or_empty_collections.rs @@ -1,13 +1,11 @@ use std::iter::once; use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::res::{MaybeDef, MaybeQPath}; use clippy_utils::source::snippet; use clippy_utils::ty::{ExprFnSig, expr_sig, ty_sig}; -use clippy_utils::{get_expr_use_or_unification_node, std_or_core, sym}; +use clippy_utils::{as_some_expr, get_expr_use_or_unification_node, is_none_expr, std_or_core, sym}; use rustc_errors::Applicability; -use rustc_hir::LangItem::{OptionNone, OptionSome}; use rustc_hir::hir_id::HirId; use rustc_hir::{Expr, ExprKind, Node}; use rustc_lint::LateContext; @@ -68,15 +66,8 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, method let item = match recv.kind { ExprKind::Array([]) => None, ExprKind::Array([e]) => Some(e), - ExprKind::Path(ref p) - if cx - .qpath_res(p, recv.hir_id) - .ctor_parent(cx) - .is_lang_item(cx, OptionNone) => - { - None - }, - ExprKind::Call(f, [arg]) if f.res(cx).ctor_parent(cx).is_lang_item(cx, OptionSome) => Some(arg), + _ if is_none_expr(cx, recv) => None, + _ if let Some(arg) = as_some_expr(cx, recv) => Some(arg), _ => return, }; let iter_type = match method_name { diff --git a/clippy_lints/src/methods/iter_overeager_cloned.rs b/clippy_lints/src/methods/iter_overeager_cloned.rs index 1c26648e26eb1..e3bcca64e9235 100644 --- a/clippy_lints/src/methods/iter_overeager_cloned.rs +++ b/clippy_lints/src/methods/iter_overeager_cloned.rs @@ -81,7 +81,8 @@ pub(super) fn check<'tcx>( } match it.kind { - PatKind::Binding(BindingMode(_, Mutability::Mut), _, _, _) | PatKind::Ref(_, _, Mutability::Mut) => { + PatKind::Binding(BindingMode(_, Mutability::Mut), _, _, _) + | PatKind::Ref(_, _, Mutability::Mut) => { to_be_discarded = true; false }, diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 20dfce914838f..c22b0a548e3df 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -138,7 +138,6 @@ mod unnecessary_option_map_or_else; mod unnecessary_result_map_or_else; mod unnecessary_sort_by; mod unnecessary_to_owned; -mod unused_enumerate_index; mod unwrap_expect_used; mod useless_asref; mod useless_nonzero_new_unchecked; @@ -1084,7 +1083,7 @@ declare_clippy_lint! { /// /// ### Why is this bad? /// In versions of the compiler before Rust 1.82.0, this bypasses the specialized - /// implementation of`ToString` and instead goes through the more expensive string + /// implementation of `ToString` and instead goes through the more expensive string /// formatting facilities. /// /// ### Example @@ -5026,7 +5025,6 @@ impl Methods { zst_offset::check(cx, expr, recv); }, (sym::all, [arg]) => { - unused_enumerate_index::check(cx, expr, recv, arg); needless_character_iteration::check(cx, expr, recv, arg, true); match method_call(recv) { Some((sym::cloned, recv2, [], _, _)) => { @@ -5056,7 +5054,6 @@ impl Methods { } }, (sym::any, [arg]) => { - unused_enumerate_index::check(cx, expr, recv, arg); needless_character_iteration::check(cx, expr, recv, arg, false); match method_call(recv) { Some((sym::cloned, recv2, [], _, _)) => iter_overeager_cloned::check( @@ -5170,7 +5167,7 @@ impl Methods { }, (sym::expect, [_]) => { match method_call(recv) { - Some((sym::ok, recv, [], _, _)) => ok_expect::check(cx, expr, recv), + Some((sym::ok, recv_inner, [], _, _)) => ok_expect::check(cx, expr, recv, recv_inner), Some((sym::err, recv, [], err_span, _)) => { err_expect::check(cx, expr, recv, span, err_span, self.msrv); }, @@ -5216,7 +5213,6 @@ impl Methods { } }, (sym::filter_map, [arg]) => { - unused_enumerate_index::check(cx, expr, recv, arg); unnecessary_filter_map::check(cx, expr, arg, call_span, unnecessary_filter_map::Kind::FilterMap); filter_map_bool_then::check(cx, expr, arg, call_span); filter_map_identity::check(cx, expr, arg, span); @@ -5231,11 +5227,9 @@ impl Methods { ); }, (sym::find_map, [arg]) => { - unused_enumerate_index::check(cx, expr, recv, arg); unnecessary_filter_map::check(cx, expr, arg, call_span, unnecessary_filter_map::Kind::FindMap); }, (sym::flat_map, [arg]) => { - unused_enumerate_index::check(cx, expr, recv, arg); flat_map_identity::check(cx, expr, arg, span); flat_map_option::check(cx, expr, arg, span); lines_filter_map_ok::check_filter_or_flat_map( @@ -5263,20 +5257,17 @@ impl Methods { manual_try_fold::check(cx, expr, init, acc, call_span, self.msrv); unnecessary_fold::check(cx, expr, init, acc, span); }, - (sym::for_each, [arg]) => { - unused_enumerate_index::check(cx, expr, recv, arg); - match method_call(recv) { - Some((sym::inspect, _, [_], span2, _)) => inspect_for_each::check(cx, expr, span2), - Some((sym::cloned, recv2, [], _, _)) => iter_overeager_cloned::check( - cx, - expr, - recv, - recv2, - iter_overeager_cloned::Op::NeedlessMove(arg), - false, - ), - _ => {}, - } + (sym::for_each, [arg]) => match method_call(recv) { + Some((sym::inspect, _, [_], span2, _)) => inspect_for_each::check(cx, expr, span2), + Some((sym::cloned, recv2, [], _, _)) => iter_overeager_cloned::check( + cx, + expr, + recv, + recv2, + iter_overeager_cloned::Op::NeedlessMove(arg), + false, + ), + _ => {}, }, (sym::get, [arg]) => { get_first::check(cx, expr, recv, arg); @@ -5337,7 +5328,6 @@ impl Methods { }, (name @ (sym::map | sym::map_err), [m_arg]) => { if name == sym::map { - unused_enumerate_index::check(cx, expr, recv, m_arg); map_clone::check(cx, expr, recv, m_arg, self.msrv); map_with_unused_argument_over_ranges::check(cx, expr, recv, m_arg, self.msrv, span); manual_is_variant_and::check_map(cx, expr); diff --git a/clippy_lints/src/methods/needless_collect.rs b/clippy_lints/src/methods/needless_collect.rs index 4f005103d23f8..055fdcabdd210 100644 --- a/clippy_lints/src/methods/needless_collect.rs +++ b/clippy_lints/src/methods/needless_collect.rs @@ -38,11 +38,14 @@ pub(super) fn check<'tcx>( Node::Expr(parent) => { check_collect_into_intoiterator(cx, parent, collect_expr, call_span, iter_expr); + let sugg: String; + let mut app; + if let ExprKind::MethodCall(name, _, args @ ([] | [_]), _) = parent.kind { - let mut app = Applicability::MachineApplicable; + app = Applicability::MachineApplicable; let collect_ty = cx.typeck_results().expr_ty(collect_expr); - let sugg: String = match name.ident.name { + sugg = match name.ident.name { sym::len => { if let Some(adt) = collect_ty.ty_adt_def() && matches!( @@ -78,17 +81,23 @@ pub(super) fn check<'tcx>( }, _ => return, }; - - span_lint_and_sugg( - cx, - NEEDLESS_COLLECT, - call_span.with_hi(parent.span.hi()), - NEEDLESS_COLLECT_MSG, - "replace with", - sugg, - app, - ); + } else if let ExprKind::Index(_, index, _) = parent.kind { + app = Applicability::MaybeIncorrect; + let snip = snippet_with_applicability(cx, index.span, "_", &mut app); + sugg = format!("nth({snip}).unwrap()"); + } else { + return; } + + span_lint_and_sugg( + cx, + NEEDLESS_COLLECT, + call_span.with_hi(parent.span.hi()), + NEEDLESS_COLLECT_MSG, + "replace with", + sugg, + app, + ); }, Node::LetStmt(l) => { if let PatKind::Binding(BindingMode::NONE | BindingMode::MUT, id, _, None) = l.pat.kind diff --git a/clippy_lints/src/methods/ok_expect.rs b/clippy_lints/src/methods/ok_expect.rs index c9c1f4865b813..5f1cae130daed 100644 --- a/clippy_lints/src/methods/ok_expect.rs +++ b/clippy_lints/src/methods/ok_expect.rs @@ -1,28 +1,35 @@ -use clippy_utils::diagnostics::span_lint_and_help; -use clippy_utils::res::MaybeDef; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::ty::has_debug_impl; +use rustc_errors::Applicability; use rustc_hir as hir; -use rustc_lint::LateContext; +use rustc_lint::{LateContext, LintContext}; use rustc_middle::ty::{self, Ty}; use rustc_span::sym; use super::OK_EXPECT; /// lint use of `ok().expect()` for `Result`s -pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>) { - if cx.typeck_results().expr_ty(recv).is_diag_item(cx, sym::Result) - // lint if the caller of `ok()` is a `Result` - && let result_type = cx.typeck_results().expr_ty(recv) - && let Some(error_type) = get_error_type(cx, result_type) +pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr<'_>, recv_inner: &hir::Expr<'_>) { + let result_ty = cx.typeck_results().expr_ty(recv_inner); + // lint if the caller of `ok()` is a `Result` + if let Some(error_type) = get_error_type(cx, result_ty) && has_debug_impl(cx, error_type) + && let Some(span) = recv.span.trim_start(recv_inner.span) { - span_lint_and_help( + span_lint_and_then( cx, OK_EXPECT, expr.span, "called `ok().expect()` on a `Result` value", - None, - "you can call `expect()` directly on the `Result`", + |diag| { + let span = cx.sess().source_map().span_extend_while_whitespace(span); + diag.span_suggestion_verbose( + span, + "call `expect()` directly on the `Result`", + String::new(), + Applicability::MachineApplicable, + ); + }, ); } } @@ -30,7 +37,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr /// Given a `Result` type, return its error type (`E`). fn get_error_type<'a>(cx: &LateContext<'_>, ty: Ty<'a>) -> Option> { match ty.kind() { - ty::Adt(_, args) if ty.is_diag_item(cx, sym::Result) => args.types().nth(1), + ty::Adt(adt, args) if cx.tcx.is_diagnostic_item(sym::Result, adt.did()) => args.types().nth(1), _ => None, } } diff --git a/clippy_lints/src/methods/option_map_or_none.rs b/clippy_lints/src/methods/option_map_or_none.rs index 342ffea51d656..817388915f189 100644 --- a/clippy_lints/src/methods/option_map_or_none.rs +++ b/clippy_lints/src/methods/option_map_or_none.rs @@ -1,9 +1,10 @@ use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::is_none_expr; use clippy_utils::res::{MaybeDef, MaybeQPath}; use clippy_utils::source::snippet; use rustc_errors::Applicability; use rustc_hir as hir; -use rustc_hir::LangItem::{OptionNone, OptionSome}; +use rustc_hir::LangItem::OptionSome; use rustc_lint::LateContext; use rustc_span::symbol::sym; @@ -48,7 +49,7 @@ pub(super) fn check<'tcx>( return; } - if !def_arg.res(cx).ctor_parent(cx).is_lang_item(cx, OptionNone) { + if !is_none_expr(cx, def_arg) { // nothing to lint! return; } diff --git a/clippy_lints/src/methods/result_map_or_else_none.rs b/clippy_lints/src/methods/result_map_or_else_none.rs index e2946c22a46b5..d5477b9be4c1e 100644 --- a/clippy_lints/src/methods/result_map_or_else_none.rs +++ b/clippy_lints/src/methods/result_map_or_else_none.rs @@ -1,10 +1,10 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::peel_blocks; use clippy_utils::res::{MaybeDef, MaybeQPath}; use clippy_utils::source::snippet; +use clippy_utils::{is_none_expr, peel_blocks}; use rustc_errors::Applicability; use rustc_hir as hir; -use rustc_hir::LangItem::{OptionNone, OptionSome}; +use rustc_hir::LangItem::OptionSome; use rustc_lint::LateContext; use rustc_span::symbol::sym; @@ -25,7 +25,7 @@ pub(super) fn check<'tcx>( && let hir::ExprKind::Closure(&hir::Closure { body, .. }) = def_arg.kind && let body = cx.tcx.hir_body(body) // And finally we check that we return a `None` in the "else case". - && peel_blocks(body.value).res(cx).ctor_parent(cx).is_lang_item(cx, OptionNone) + && is_none_expr(cx, peel_blocks(body.value)) { let msg = "called `map_or_else(|_| None, Some)` on a `Result` value"; let self_snippet = snippet(cx, recv.span, ".."); diff --git a/clippy_lints/src/methods/unnecessary_filter_map.rs b/clippy_lints/src/methods/unnecessary_filter_map.rs index 7f729ac7ca94e..72f1c42da2ee3 100644 --- a/clippy_lints/src/methods/unnecessary_filter_map.rs +++ b/clippy_lints/src/methods/unnecessary_filter_map.rs @@ -1,10 +1,10 @@ use super::utils::clone_or_copy_needed; use clippy_utils::diagnostics::span_lint; use clippy_utils::res::{MaybeDef, MaybeQPath, MaybeResPath, MaybeTypeckRes}; -use clippy_utils::sym; use clippy_utils::ty::{is_copy, option_arg_ty}; use clippy_utils::usage::mutated_variables; use clippy_utils::visitors::{Descend, for_each_expr_without_closures}; +use clippy_utils::{as_some_expr, sym}; use core::ops::ControlFlow; use rustc_hir as hir; use rustc_hir::LangItem::{OptionNone, OptionSome}; @@ -47,8 +47,7 @@ pub(super) fn check<'tcx>( let sugg = if !found_filtering { // Check if the closure is .filter_map(|x| Some(x)) if kind.is_filter_map() - && let hir::ExprKind::Call(expr, [arg]) = body.value.kind - && expr.res(cx).ctor_parent(cx).is_lang_item(cx, OptionSome) + && let Some(arg) = as_some_expr(cx, body.value) && let hir::ExprKind::Path(_) = arg.kind { span_lint( diff --git a/clippy_lints/src/methods/unnecessary_literal_unwrap.rs b/clippy_lints/src/methods/unnecessary_literal_unwrap.rs index 410e973f855b6..da6f03931e24b 100644 --- a/clippy_lints/src/methods/unnecessary_literal_unwrap.rs +++ b/clippy_lints/src/methods/unnecessary_literal_unwrap.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::res::{MaybeDef, MaybeQPath}; -use clippy_utils::{last_path_segment, sym}; +use clippy_utils::{is_none_expr, last_path_segment, sym}; use rustc_errors::Applicability; use rustc_hir::{self as hir, AmbigArg}; use rustc_lint::LateContext; @@ -23,6 +23,7 @@ fn get_ty_from_args<'a>(args: Option<&'a [hir::GenericArg<'a>]>, index: usize) - } } +#[expect(clippy::too_many_lines)] pub(super) fn check( cx: &LateContext<'_>, expr: &hir::Expr<'_>, @@ -38,23 +39,24 @@ pub(super) fn check( } let (constructor, call_args, ty) = if let hir::ExprKind::Call(call, call_args) = init.kind { - let Some((qpath, hir_id)) = call.opt_qpath() else { - return; - }; - - let args = last_path_segment(qpath).args.map(|args| args.args); - let res = cx.qpath_res(qpath, hir_id); - - if res.ctor_parent(cx).is_lang_item(cx, hir::LangItem::OptionSome) { - (sym::Some, call_args, get_ty_from_args(args, 0)) - } else if res.ctor_parent(cx).is_lang_item(cx, hir::LangItem::ResultOk) { - (sym::Ok, call_args, get_ty_from_args(args, 0)) - } else if res.ctor_parent(cx).is_lang_item(cx, hir::LangItem::ResultErr) { - (sym::Err, call_args, get_ty_from_args(args, 1)) + if let Some((qpath, hir_id)) = call.opt_qpath() + && let args = last_path_segment(qpath).args.map(|args| args.args) + && let Some(did) = cx.qpath_res(qpath, hir_id).ctor_parent(cx).opt_def_id() + { + let lang_items = cx.tcx.lang_items(); + if Some(did) == lang_items.option_some_variant() { + (sym::Some, call_args, get_ty_from_args(args, 0)) + } else if Some(did) == lang_items.result_ok_variant() { + (sym::Ok, call_args, get_ty_from_args(args, 0)) + } else if Some(did) == lang_items.result_err_variant() { + (sym::Err, call_args, get_ty_from_args(args, 1)) + } else { + return; + } } else { return; } - } else if init.res(cx).ctor_parent(cx).is_lang_item(cx, hir::LangItem::OptionNone) { + } else if is_none_expr(cx, init) { let call_args: &[hir::Expr<'_>] = &[]; (sym::None, call_args, None) } else { diff --git a/clippy_lints/src/methods/unused_enumerate_index.rs b/clippy_lints/src/methods/unused_enumerate_index.rs deleted file mode 100644 index a7d9b2e0fab01..0000000000000 --- a/clippy_lints/src/methods/unused_enumerate_index.rs +++ /dev/null @@ -1,138 +0,0 @@ -use clippy_utils::diagnostics::span_lint_hir_and_then; -use clippy_utils::res::{MaybeDef, MaybeTypeckRes}; -use clippy_utils::source::{SpanRangeExt, snippet}; -use clippy_utils::{expr_or_init, pat_is_wild}; -use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind, FnDecl, PatKind, TyKind}; -use rustc_lint::LateContext; -use rustc_span::{Span, sym}; - -use crate::loops::UNUSED_ENUMERATE_INDEX; - -/// Check for the `UNUSED_ENUMERATE_INDEX` lint outside of loops. -/// -/// The lint is declared in `clippy_lints/src/loops/mod.rs`. There, the following pattern is -/// checked: -/// ```ignore -/// for (_, x) in some_iter.enumerate() { -/// // Index is ignored -/// } -/// ``` -/// -/// This `check` function checks for chained method calls constructs where we can detect that the -/// index is unused. Currently, this checks only for the following patterns: -/// ```ignore -/// some_iter.enumerate().map_function(|(_, x)| ..) -/// let x = some_iter.enumerate(); -/// x.map_function(|(_, x)| ..) -/// ``` -/// where `map_function` is one of `all`, `any`, `filter_map`, `find_map`, `flat_map`, `for_each` or -/// `map`. -/// -/// # Preconditions -/// This function must be called not on the `enumerate` call expression itself, but on any of the -/// map functions listed above. It will ensure that `recv` is a `std::iter::Enumerate` instance and -/// that the method call is one of the `std::iter::Iterator` trait. -/// -/// * `call_expr`: The map function call expression -/// * `recv`: The receiver of the call -/// * `closure_arg`: The argument to the map function call containing the closure/function to apply -pub(super) fn check(cx: &LateContext<'_>, call_expr: &Expr<'_>, recv: &Expr<'_>, closure_arg: &Expr<'_>) { - let recv_ty = cx.typeck_results().expr_ty(recv); - // If we call a method on a `std::iter::Enumerate` instance - if recv_ty.is_diag_item(cx, sym::Enumerate) - // If we are calling a method of the `Iterator` trait - && cx.ty_based_def(call_expr).opt_parent(cx).is_diag_item(cx, sym::Iterator) - // And the map argument is a closure - && let ExprKind::Closure(closure) = closure_arg.kind - && let closure_body = cx.tcx.hir_body(closure.body) - // And that closure has one argument ... - && let [closure_param] = closure_body.params - // .. which is a tuple of 2 elements - && let PatKind::Tuple([index, elem], ..) = closure_param.pat.kind - // And that the first element (the index) is either `_` or unused in the body - && pat_is_wild(cx, &index.kind, closure_body) - // Try to find the initializer for `recv`. This is needed in case `recv` is a local_binding. In the - // first example below, `expr_or_init` would return `recv`. - // ``` - // iter.enumerate().map(|(_, x)| x) - // ^^^^^^^^^^^^^^^^ `recv`, a call to `std::iter::Iterator::enumerate` - // - // let binding = iter.enumerate(); - // ^^^^^^^^^^^^^^^^ `recv_init_expr` - // binding.map(|(_, x)| x) - // ^^^^^^^ `recv`, not a call to `std::iter::Iterator::enumerate` - // ``` - && let recv_init_expr = expr_or_init(cx, recv) - // Make sure the initializer is a method call. It may be that the `Enumerate` comes from something - // that we cannot control. - // This would for instance happen with: - // ``` - // external_lib::some_function_returning_enumerate().map(|(_, x)| x) - // ``` - && let ExprKind::MethodCall(_, enumerate_recv, _, enumerate_span) = recv_init_expr.kind - && let Some(enumerate_defid) = cx.typeck_results().type_dependent_def_id(recv_init_expr.hir_id) - // Make sure the method call is `std::iter::Iterator::enumerate`. - && cx.tcx.is_diagnostic_item(sym::enumerate_method, enumerate_defid) - { - // Check if the tuple type was explicit. It may be the type system _needs_ the type of the element - // that would be explicitly in the closure. - let new_closure_param = match find_elem_explicit_type_span(closure.fn_decl) { - // We have an explicit type. Get its snippet, that of the binding name, and do `binding: ty`. - // Fallback to `..` if we fail getting either snippet. - Some(ty_span) => elem - .span - .get_source_text(cx) - .and_then(|binding_name| { - ty_span - .get_source_text(cx) - .map(|ty_name| format!("{binding_name}: {ty_name}")) - }) - .unwrap_or_else(|| "..".to_string()), - // Otherwise, we have no explicit type. We can replace with the binding name of the element. - None => snippet(cx, elem.span, "..").into_owned(), - }; - - // Suggest removing the tuple from the closure and the preceding call to `enumerate`, whose span we - // can get from the `MethodCall`. - span_lint_hir_and_then( - cx, - UNUSED_ENUMERATE_INDEX, - recv_init_expr.hir_id, - enumerate_span, - "you seem to use `.enumerate()` and immediately discard the index", - |diag| { - diag.multipart_suggestion( - "remove the `.enumerate()` call", - vec![ - (closure_param.span, new_closure_param), - ( - enumerate_span.with_lo(enumerate_recv.span.source_callsite().hi()), - String::new(), - ), - ], - Applicability::MachineApplicable, - ); - }, - ); - } -} - -/// Find the span of the explicit type of the element. -/// -/// # Returns -/// If the tuple argument: -/// * Has no explicit type, returns `None` -/// * Has an explicit tuple type with an implicit element type (`(usize, _)`), returns `None` -/// * Has an explicit tuple type with an explicit element type (`(_, i32)`), returns the span for -/// the element type. -fn find_elem_explicit_type_span(fn_decl: &FnDecl<'_>) -> Option { - if let [tuple_ty] = fn_decl.inputs - && let TyKind::Tup([_idx_ty, elem_ty]) = tuple_ty.kind - && !matches!(elem_ty.kind, TyKind::Err(..) | TyKind::Infer(())) - { - Some(elem_ty.span) - } else { - None - } -} diff --git a/clippy_lints/src/missing_asserts_for_indexing.rs b/clippy_lints/src/missing_asserts_for_indexing.rs index 35d06780bcb82..808adb7e71cee 100644 --- a/clippy_lints/src/missing_asserts_for_indexing.rs +++ b/clippy_lints/src/missing_asserts_for_indexing.rs @@ -3,10 +3,11 @@ use std::ops::ControlFlow; use clippy_utils::comparisons::{Rel, normalize_comparison}; use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::macros::{find_assert_eq_args, first_node_macro_backtrace}; +use clippy_utils::higher::{If, Range}; +use clippy_utils::macros::{find_assert_eq_args, first_node_macro_backtrace, root_macro_call}; use clippy_utils::source::snippet; use clippy_utils::visitors::for_each_expr_without_closures; -use clippy_utils::{eq_expr_value, hash_expr, higher}; +use clippy_utils::{eq_expr_value, hash_expr}; use rustc_ast::{BinOpKind, LitKind, RangeLimits}; use rustc_data_structures::packed::Pu128; use rustc_data_structures::unhash::UnindexMap; @@ -15,7 +16,7 @@ use rustc_hir::{Block, Body, Expr, ExprKind, UnOp}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; use rustc_span::source_map::Spanned; -use rustc_span::{Span, sym}; +use rustc_span::{Span, Symbol, sym}; declare_clippy_lint! { /// ### What it does @@ -134,15 +135,15 @@ fn len_comparison<'hir>( fn assert_len_expr<'hir>( cx: &LateContext<'_>, expr: &'hir Expr<'hir>, -) -> Option<(LengthComparison, usize, &'hir Expr<'hir>)> { - let (cmp, asserted_len, slice_len) = if let Some(higher::If { cond, then, .. }) = higher::If::hir(expr) +) -> Option<(LengthComparison, usize, &'hir Expr<'hir>, Symbol)> { + let ((cmp, asserted_len, slice_len), macro_call) = if let Some(If { cond, then, .. }) = If::hir(expr) && let ExprKind::Unary(UnOp::Not, condition) = &cond.kind && let ExprKind::Binary(bin_op, left, right) = &condition.kind // check if `then` block has a never type expression && let ExprKind::Block(Block { expr: Some(then_expr), .. }, _) = then.kind && cx.typeck_results().expr_ty(then_expr).is_never() { - len_comparison(bin_op.node, left, right)? + (len_comparison(bin_op.node, left, right)?, sym::assert_macro) } else if let Some((macro_call, bin_op)) = first_node_macro_backtrace(cx, expr).find_map(|macro_call| { match cx.tcx.get_diagnostic_name(macro_call.def_id) { Some(sym::assert_eq_macro) => Some((macro_call, BinOpKind::Eq)), @@ -151,7 +152,12 @@ fn assert_len_expr<'hir>( } }) && let Some((left, right, _)) = find_assert_eq_args(cx, expr, macro_call.expn) { - len_comparison(bin_op, left, right)? + ( + len_comparison(bin_op, left, right)?, + root_macro_call(expr.span) + .and_then(|macro_call| cx.tcx.get_diagnostic_name(macro_call.def_id)) + .unwrap_or(sym::assert_macro), + ) } else { return None; }; @@ -160,7 +166,7 @@ fn assert_len_expr<'hir>( && cx.typeck_results().expr_ty_adjusted(recv).peel_refs().is_slice() && method.ident.name == sym::len { - Some((cmp, asserted_len, recv)) + Some((cmp, asserted_len, recv, macro_call)) } else { None } @@ -174,6 +180,7 @@ enum IndexEntry<'hir> { comparison: LengthComparison, assert_span: Span, slice: &'hir Expr<'hir>, + macro_call: Symbol, }, /// `assert!` with indexing /// @@ -187,6 +194,7 @@ enum IndexEntry<'hir> { slice: &'hir Expr<'hir>, indexes: Vec, comparison: LengthComparison, + macro_call: Symbol, }, /// Indexing without an `assert!` IndexWithoutAssert { @@ -225,9 +233,9 @@ fn upper_index_expr(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option { && let LitKind::Int(Pu128(index), _) = lit.node { Some(index as usize) - } else if let Some(higher::Range { + } else if let Some(Range { end: Some(end), limits, .. - }) = higher::Range::hir(cx, expr) + }) = Range::hir(cx, expr) && let ExprKind::Lit(lit) = &end.kind && let LitKind::Int(Pu128(index @ 1..), _) = lit.node { @@ -258,6 +266,7 @@ fn check_index<'hir>(cx: &LateContext<'_>, expr: &'hir Expr<'hir>, map: &mut Uni comparison, assert_span, slice, + macro_call, } => { if slice.span.lo() > assert_span.lo() { *entry = IndexEntry::AssertWithIndex { @@ -268,6 +277,7 @@ fn check_index<'hir>(cx: &LateContext<'_>, expr: &'hir Expr<'hir>, map: &mut Uni slice, indexes: vec![expr.span], comparison: *comparison, + macro_call: *macro_call, }; } }, @@ -303,7 +313,7 @@ fn check_index<'hir>(cx: &LateContext<'_>, expr: &'hir Expr<'hir>, map: &mut Uni /// Checks if the expression is an `assert!` expression and adds it to `asserts` fn check_assert<'hir>(cx: &LateContext<'_>, expr: &'hir Expr<'hir>, map: &mut UnindexMap>>) { - if let Some((comparison, asserted_len, slice)) = assert_len_expr(cx, expr) { + if let Some((comparison, asserted_len, slice, macro_call)) = assert_len_expr(cx, expr) { let hash = hash_expr(cx, slice); let indexes = map.entry(hash).or_default(); @@ -326,6 +336,7 @@ fn check_assert<'hir>(cx: &LateContext<'_>, expr: &'hir Expr<'hir>, map: &mut Un assert_span: expr.span.source_callsite(), comparison, asserted_len, + macro_call, }; } } else { @@ -334,6 +345,7 @@ fn check_assert<'hir>(cx: &LateContext<'_>, expr: &'hir Expr<'hir>, map: &mut Un comparison, assert_span: expr.span.source_callsite(), slice, + macro_call, }); } } @@ -362,6 +374,7 @@ fn report_indexes(cx: &LateContext<'_>, map: &UnindexMap comparison, assert_span, slice, + macro_call, } if indexes.len() > 1 && !is_first_highest => { // if we have found an `assert!`, let's also check that it's actually right // and if it covers the highest index and if not, suggest the correct length @@ -382,11 +395,23 @@ fn report_indexes(cx: &LateContext<'_>, map: &UnindexMap snippet(cx, slice.span, "..") )), // `highest_index` here is rather a length, so we need to add 1 to it - LengthComparison::LengthEqualInt if asserted_len < highest_index + 1 => Some(format!( - "assert!({}.len() == {})", - snippet(cx, slice.span, ".."), - highest_index + 1 - )), + LengthComparison::LengthEqualInt if asserted_len < highest_index + 1 => match macro_call { + sym::assert_eq_macro => Some(format!( + "assert_eq!({}.len(), {})", + snippet(cx, slice.span, ".."), + highest_index + 1 + )), + sym::debug_assert_eq_macro => Some(format!( + "debug_assert_eq!({}.len(), {})", + snippet(cx, slice.span, ".."), + highest_index + 1 + )), + _ => Some(format!( + "assert!({}.len() == {})", + snippet(cx, slice.span, ".."), + highest_index + 1 + )), + }, _ => None, }; diff --git a/clippy_lints/src/missing_inline.rs b/clippy_lints/src/missing_inline.rs index bccc72c2a516c..c308f2a345852 100644 --- a/clippy_lints/src/missing_inline.rs +++ b/clippy_lints/src/missing_inline.rs @@ -4,6 +4,7 @@ use rustc_hir::def_id::DefId; use rustc_hir::{self as hir, Attribute, find_attr}; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::ty::AssocContainer; +use rustc_session::config::CrateType; use rustc_session::declare_lint_pass; use rustc_span::Span; @@ -81,20 +82,20 @@ fn check_missing_inline_attrs( } } -fn is_executable_or_proc_macro(cx: &LateContext<'_>) -> bool { - use rustc_session::config::CrateType; - - cx.tcx - .crate_types() - .iter() - .any(|t: &CrateType| matches!(t, CrateType::Executable | CrateType::ProcMacro)) -} - declare_lint_pass!(MissingInline => [MISSING_INLINE_IN_PUBLIC_ITEMS]); impl<'tcx> LateLintPass<'tcx> for MissingInline { fn check_item(&mut self, cx: &LateContext<'tcx>, it: &'tcx hir::Item<'_>) { - if it.span.in_external_macro(cx.sess().source_map()) || is_executable_or_proc_macro(cx) { + if it.span.in_external_macro(cx.sess().source_map()) { + return; + } + + if cx + .tcx + .crate_types() + .iter() + .any(|t: &CrateType| matches!(t, CrateType::ProcMacro)) + { return; } @@ -149,7 +150,13 @@ impl<'tcx> LateLintPass<'tcx> for MissingInline { } fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx hir::ImplItem<'_>) { - if impl_item.span.in_external_macro(cx.sess().source_map()) || is_executable_or_proc_macro(cx) { + if impl_item.span.in_external_macro(cx.sess().source_map()) + || cx + .tcx + .crate_types() + .iter() + .any(|t: &CrateType| matches!(t, CrateType::ProcMacro)) + { return; } diff --git a/clippy_lints/src/module_style.rs b/clippy_lints/src/module_style.rs index f132b90ac4f2e..9096d6f1c7b32 100644 --- a/clippy_lints/src/module_style.rs +++ b/clippy_lints/src/module_style.rs @@ -4,7 +4,7 @@ use rustc_lint::{EarlyContext, EarlyLintPass, Level, LintContext}; use rustc_session::impl_lint_pass; use rustc_span::def_id::LOCAL_CRATE; use rustc_span::{FileName, SourceFile, Span, SyntaxContext, sym}; -use std::path::{Path, PathBuf}; +use std::path::{Component, Path, PathBuf}; use std::sync::Arc; declare_clippy_lint! { @@ -150,7 +150,13 @@ fn check_self_named_module(cx: &EarlyContext<'_>, path: &Path, file: &SourceFile /// Using `mod.rs` in integration tests is a [common pattern](https://doc.rust-lang.org/book/ch11-03-test-organization.html#submodules-in-integration-test) /// for code-sharing between tests. fn check_mod_module(cx: &EarlyContext<'_>, path: &Path, file: &SourceFile) { - if path.ends_with("mod.rs") && !path.starts_with("tests") { + if path.ends_with("mod.rs") + && !path + .components() + .filter_map(|c| if let Component::Normal(d) = c { Some(d) } else { None }) + .take_while(|&c| c != "src") + .any(|c| c == "tests") + { span_lint_and_then( cx, MOD_MODULE_FILES, diff --git a/clippy_lints/src/needless_arbitrary_self_type.rs b/clippy_lints/src/needless_arbitrary_self_type.rs index 5f7fde30f03f6..691d9035d02c5 100644 --- a/clippy_lints/src/needless_arbitrary_self_type.rs +++ b/clippy_lints/src/needless_arbitrary_self_type.rs @@ -1,10 +1,9 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::snippet_with_applicability; -use rustc_ast::ast::{BindingMode, ByRef, Lifetime, Mutability, Param, PatKind, Path, TyKind}; +use rustc_ast::ast::{BindingMode, ByRef, Lifetime, Param, PatKind, TyKind}; use rustc_errors::Applicability; use rustc_lint::{EarlyContext, EarlyLintPass}; use rustc_session::declare_lint_pass; -use rustc_span::Span; use rustc_span::symbol::kw; declare_clippy_lint! { @@ -65,52 +64,6 @@ enum Mode { Value, } -fn check_param_inner(cx: &EarlyContext<'_>, path: &Path, span: Span, binding_mode: &Mode, mutbl: Mutability) { - if let [segment] = &path.segments[..] - && segment.ident.name == kw::SelfUpper - { - // In case we have a named lifetime, we check if the name comes from expansion. - // If it does, at this point we know the rest of the parameter was written by the user, - // so let them decide what the name of the lifetime should be. - // See #6089 for more details. - let mut applicability = Applicability::MachineApplicable; - let self_param = match (binding_mode, mutbl) { - (Mode::Ref(None), Mutability::Mut) => "&mut self".to_string(), - (Mode::Ref(Some(lifetime)), Mutability::Mut) => { - if lifetime.ident.span.from_expansion() { - applicability = Applicability::HasPlaceholders; - "&'_ mut self".to_string() - } else { - let lt_name = snippet_with_applicability(cx, lifetime.ident.span, "..", &mut applicability); - format!("&{lt_name} mut self") - } - }, - (Mode::Ref(None), Mutability::Not) => "&self".to_string(), - (Mode::Ref(Some(lifetime)), Mutability::Not) => { - if lifetime.ident.span.from_expansion() { - applicability = Applicability::HasPlaceholders; - "&'_ self".to_string() - } else { - let lt_name = snippet_with_applicability(cx, lifetime.ident.span, "..", &mut applicability); - format!("&{lt_name} self") - } - }, - (Mode::Value, Mutability::Mut) => "mut self".to_string(), - (Mode::Value, Mutability::Not) => "self".to_string(), - }; - - span_lint_and_sugg( - cx, - NEEDLESS_ARBITRARY_SELF_TYPE, - span, - "the type of the `self` parameter does not need to be arbitrary", - "consider to change this parameter to", - self_param, - applicability, - ); - } -} - impl EarlyLintPass for NeedlessArbitrarySelfType { fn check_param(&mut self, cx: &EarlyContext<'_>, p: &Param) { // Bail out if the parameter it's not a receiver or was not written by the user @@ -118,20 +71,55 @@ impl EarlyLintPass for NeedlessArbitrarySelfType { return; } - match &p.ty.kind { - TyKind::Path(None, path) => { - if let PatKind::Ident(BindingMode(ByRef::No, mutbl), _, _) = p.pat.kind { - check_param_inner(cx, path, p.span.to(p.ty.span), &Mode::Value, mutbl); - } + let (path, binding_mode, mutbl) = match &p.ty.kind { + TyKind::Path(None, path) if let PatKind::Ident(BindingMode(ByRef::No, mutbl), _, _) = p.pat.kind => { + (path, Mode::Value, mutbl) }, - TyKind::Ref(lifetime, mut_ty) => { + TyKind::Ref(lifetime, mut_ty) if let TyKind::Path(None, path) = &mut_ty.ty.kind - && let PatKind::Ident(BindingMode::NONE, _, _) = p.pat.kind - { - check_param_inner(cx, path, p.span.to(p.ty.span), &Mode::Ref(*lifetime), mut_ty.mutbl); - } + && let PatKind::Ident(BindingMode::NONE, _, _) = p.pat.kind => + { + (path, Mode::Ref(*lifetime), mut_ty.mutbl) }, - _ => {}, + _ => return, + }; + + let span = p.span.to(p.ty.span); + if let [segment] = &path.segments[..] + && segment.ident.name == kw::SelfUpper + { + span_lint_and_then( + cx, + NEEDLESS_ARBITRARY_SELF_TYPE, + span, + "the type of the `self` parameter does not need to be arbitrary", + |diag| { + let mut applicability = Applicability::MachineApplicable; + let add = match binding_mode { + Mode::Value => String::new(), + Mode::Ref(None) => mutbl.ref_prefix_str().to_string(), + Mode::Ref(Some(lifetime)) => { + // In case we have a named lifetime, we check if the name comes from expansion. + // If it does, at this point we know the rest of the parameter was written by the user, + // so let them decide what the name of the lifetime should be. + // See #6089 for more details. + let lt_name = if lifetime.ident.span.from_expansion() { + applicability = Applicability::HasPlaceholders; + "'_".into() + } else { + snippet_with_applicability(cx, lifetime.ident.span, "'_", &mut applicability) + }; + format!("&{lt_name} {mut_}", mut_ = mutbl.prefix_str()) + }, + }; + + let mut sugg = vec![(p.ty.span.with_lo(p.span.hi()), String::new())]; + if !add.is_empty() { + sugg.push((p.span.shrink_to_lo(), add)); + } + diag.multipart_suggestion_verbose("remove the type", sugg, applicability); + }, + ); } } } diff --git a/clippy_lints/src/non_canonical_impls.rs b/clippy_lints/src/non_canonical_impls.rs index e11f775018ed8..e66c088617cb5 100644 --- a/clippy_lints/src/non_canonical_impls.rs +++ b/clippy_lints/src/non_canonical_impls.rs @@ -354,10 +354,7 @@ fn self_cmp_call<'tcx>( needs_fully_qualified: &mut bool, ) -> bool { match cmp_expr.kind { - ExprKind::Call(path, [_, _]) => path - .res(typeck) - .opt_def_id() - .is_some_and(|def_id| cx.tcx.is_diagnostic_item(sym::ord_cmp_method, def_id)), + ExprKind::Call(path, [_, _]) => path.res(typeck).is_diag_item(cx, sym::ord_cmp_method), ExprKind::MethodCall(_, recv, [_], ..) => { let ExprKind::Path(path) = recv.kind else { return false; diff --git a/clippy_lints/src/non_copy_const.rs b/clippy_lints/src/non_copy_const.rs index 3c3e5fea49703..9b0008a29c6b4 100644 --- a/clippy_lints/src/non_copy_const.rs +++ b/clippy_lints/src/non_copy_const.rs @@ -706,7 +706,7 @@ impl<'tcx> LateLintPass<'tcx> for NonCopyConst<'tcx> { IsFreeze::Maybe => match cx.tcx.const_eval_poly(item.owner_id.to_def_id()) { Ok(val) if let Ok(is_freeze) = self.is_value_freeze(cx.tcx, cx.typing_env(), ty, val) => !is_freeze, // FIXME: we just assume mgca rhs's are freeze - _ => const_item_rhs_to_expr(cx.tcx, ct_rhs).map_or(false, |e| !self.is_init_expr_freeze( + _ => const_item_rhs_to_expr(cx.tcx, ct_rhs).is_some_and(|e| !self.is_init_expr_freeze( cx.tcx, cx.typing_env(), cx.tcx.typeck(item.owner_id), @@ -749,7 +749,7 @@ impl<'tcx> LateLintPass<'tcx> for NonCopyConst<'tcx> { !is_freeze }, // FIXME: we just assume mgca rhs's are freeze - _ => const_item_rhs_to_expr(cx.tcx, ct_rhs).map_or(false, |e| { + _ => const_item_rhs_to_expr(cx.tcx, ct_rhs).is_some_and(|e| { !self.is_init_expr_freeze( cx.tcx, cx.typing_env(), @@ -806,7 +806,7 @@ impl<'tcx> LateLintPass<'tcx> for NonCopyConst<'tcx> { IsFreeze::Maybe => match cx.tcx.const_eval_poly(item.owner_id.to_def_id()) { Ok(val) if let Ok(is_freeze) = self.is_value_freeze(cx.tcx, cx.typing_env(), ty, val) => !is_freeze, // FIXME: we just assume mgca rhs's are freeze - _ => const_item_rhs_to_expr(cx.tcx, ct_rhs).map_or(false, |e| { + _ => const_item_rhs_to_expr(cx.tcx, ct_rhs).is_some_and(|e| { !self.is_init_expr_freeze( cx.tcx, cx.typing_env(), diff --git a/clippy_lints/src/operators/arithmetic_side_effects.rs b/clippy_lints/src/operators/arithmetic_side_effects.rs index 0a6499e095832..91a069559f7b4 100644 --- a/clippy_lints/src/operators/arithmetic_side_effects.rs +++ b/clippy_lints/src/operators/arithmetic_side_effects.rs @@ -1,4 +1,5 @@ use super::ARITHMETIC_SIDE_EFFECTS; +use crate::clippy_utils::res::MaybeQPath as _; use clippy_config::Conf; use clippy_utils::consts::{ConstEvalCtxt, Constant}; use clippy_utils::diagnostics::span_lint; @@ -6,7 +7,7 @@ use clippy_utils::res::MaybeDef; use clippy_utils::{expr_or_init, is_from_proc_macro, is_lint_allowed, peel_hir_expr_refs, peel_hir_expr_unary, sym}; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::{self, Ty}; +use rustc_middle::ty::{self, Ty, UintTy}; use rustc_session::impl_lint_pass; use rustc_span::{Span, Symbol}; use {rustc_ast as ast, rustc_hir as hir}; @@ -88,74 +89,16 @@ impl ArithmeticSideEffects { self.allowed_unary.contains(ty_string_elem) } - fn is_non_zero_u(cx: &LateContext<'_>, ty: Ty<'_>) -> bool { - if let ty::Adt(adt, substs) = ty.kind() - && cx.tcx.is_diagnostic_item(sym::NonZero, adt.did()) - && let int_type = substs.type_at(0) - && matches!(int_type.kind(), ty::Uint(_)) - { - true - } else { - false - } - } - - /// Verifies built-in types that have specific allowed operations - fn has_specific_allowed_type_and_operation<'tcx>( - cx: &LateContext<'tcx>, - lhs_ty: Ty<'tcx>, - op: hir::BinOpKind, - rhs_ty: Ty<'tcx>, - ) -> bool { - let is_div_or_rem = matches!(op, hir::BinOpKind::Div | hir::BinOpKind::Rem); - let is_sat_or_wrap = |ty: Ty<'_>| ty.is_diag_item(cx, sym::Saturating) || ty.is_diag_item(cx, sym::Wrapping); - - // If the RHS is `NonZero`, then division or module by zero will never occur. - if Self::is_non_zero_u(cx, rhs_ty) && is_div_or_rem { - return true; - } - - // `Saturation` and `Wrapping` can overflow if the RHS is zero in a division or module. - if is_sat_or_wrap(lhs_ty) { - return !is_div_or_rem; - } - - false - } - - // For example, 8i32 or &i64::MAX. - fn is_integral(ty: Ty<'_>) -> bool { - ty.peel_refs().is_integral() - } - // Common entry-point to avoid code duplication. fn issue_lint<'tcx>(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { if is_from_proc_macro(cx, expr) { return; } - let msg = "arithmetic operation that can potentially result in unexpected side-effects"; span_lint(cx, ARITHMETIC_SIDE_EFFECTS, expr.span, msg); self.expr_span = Some(expr.span); } - /// Returns the numeric value of a literal integer originated from `expr`, if any. - /// - /// Literal integers can be originated from adhoc declarations like `1`, associated constants - /// like `i32::MAX` or constant references like `N` from `const N: i32 = 1;`, - fn literal_integer(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option { - let actual = peel_hir_expr_unary(expr).0; - if let hir::ExprKind::Lit(lit) = actual.kind - && let ast::LitKind::Int(n, _) = lit.node - { - return Some(n.get()); - } - if let Some(Constant::Int(n)) = ConstEvalCtxt::new(cx).eval(expr) { - return Some(n); - } - None - } - /// Methods like `add_assign` are send to their `BinOps` references. fn manage_sugar_methods<'tcx>( &mut self, @@ -213,59 +156,53 @@ impl ArithmeticSideEffects { && let hir::ExprKind::MethodCall(method, receiver, [], _) = actual_lhs.kind && method.ident.name == sym::get && let receiver_ty = cx.typeck_results().expr_ty(receiver).peel_refs() - && Self::is_non_zero_u(cx, receiver_ty) - && let Some(1) = Self::literal_integer(cx, actual_rhs) + && is_non_zero_u(cx, receiver_ty) + && literal_integer(cx, actual_rhs) == Some(1) { return; } let lhs_ty = cx.typeck_results().expr_ty(actual_lhs).peel_refs(); let rhs_ty = cx.typeck_results().expr_ty_adjusted(actual_rhs).peel_refs(); - if self.has_allowed_binary(lhs_ty, rhs_ty) { - return; - } - if Self::has_specific_allowed_type_and_operation(cx, lhs_ty, op, rhs_ty) { + if self.has_allowed_binary(lhs_ty, rhs_ty) + | has_specific_allowed_type_and_operation(cx, lhs_ty, op, rhs_ty) + | is_safe_due_to_smaller_source_type(cx, op, (actual_lhs, lhs_ty), actual_rhs) + | is_safe_due_to_smaller_source_type(cx, op, (actual_rhs, rhs_ty), actual_lhs) + { return; } - - let has_valid_op = if Self::is_integral(lhs_ty) && Self::is_integral(rhs_ty) { + if is_integer(lhs_ty) && is_integer(rhs_ty) { if let hir::BinOpKind::Shl | hir::BinOpKind::Shr = op { // At least for integers, shifts are already handled by the CTFE return; } - match ( - Self::literal_integer(cx, actual_lhs), - Self::literal_integer(cx, actual_rhs), - ) { - (None, None) => false, + match (literal_integer(cx, actual_lhs), literal_integer(cx, actual_rhs)) { (None, Some(n)) => match (&op, n) { // Division and module are always valid if applied to non-zero integers - (hir::BinOpKind::Div | hir::BinOpKind::Rem, local_n) if local_n != 0 => true, - // Adding or subtracting zeros is always a no-op - (hir::BinOpKind::Add | hir::BinOpKind::Sub, 0) - // Multiplication by 1 or 0 will never overflow - | (hir::BinOpKind::Mul, 0 | 1) - => true, - _ => false, - }, - (Some(n), None) => match (&op, n) { + (hir::BinOpKind::Div | hir::BinOpKind::Rem, local_n) if local_n != 0 => return, // Adding or subtracting zeros is always a no-op (hir::BinOpKind::Add | hir::BinOpKind::Sub, 0) // Multiplication by 1 or 0 will never overflow | (hir::BinOpKind::Mul, 0 | 1) - => true, - _ => false, + => return, + _ => {}, }, - (Some(_), Some(_)) => { - matches!((lhs_ref_counter, rhs_ref_counter), (0, 0)) + (Some(n), None) + if matches!( + (&op, n), + // Adding or subtracting zeros is always a no-op + (hir::BinOpKind::Add | hir::BinOpKind::Sub, 0) + // Multiplication by 1 or 0 will never overflow + | (hir::BinOpKind::Mul, 0 | 1) + ) => + { + return; }, + (Some(_), Some(_)) if matches!((lhs_ref_counter, rhs_ref_counter), (0, 0)) => return, + _ => {}, } - } else { - false - }; - if !has_valid_op { - self.issue_lint(cx, expr); } + self.issue_lint(cx, expr); } /// There are some integer methods like `wrapping_div` that will panic depending on the @@ -285,7 +222,7 @@ impl ArithmeticSideEffects { return; } let instance_ty = cx.typeck_results().expr_ty_adjusted(receiver); - if !Self::is_integral(instance_ty) { + if !is_integer(instance_ty) { return; } self.manage_sugar_methods(cx, expr, receiver, ps, arg); @@ -293,7 +230,7 @@ impl ArithmeticSideEffects { return; } let (actual_arg, _) = peel_hir_expr_refs(arg); - match Self::literal_integer(cx, actual_arg) { + match literal_integer(cx, actual_arg) { None | Some(0) => self.issue_lint(cx, arg), Some(_) => {}, } @@ -317,7 +254,7 @@ impl ArithmeticSideEffects { return; } let actual_un_expr = peel_hir_expr_refs(un_expr).0; - if Self::literal_integer(cx, actual_un_expr).is_some() { + if literal_integer(cx, actual_un_expr).is_some() { return; } self.issue_lint(cx, expr); @@ -385,3 +322,120 @@ impl<'tcx> LateLintPass<'tcx> for ArithmeticSideEffects { } } } + +/// Detects a type-casting conversion and returns the type of the original expression. For +/// example, `let foo = u64::from(bar)`. +fn find_original_primitive_ty<'tcx>(cx: &LateContext<'tcx>, expr: &hir::Expr<'_>) -> Option> { + if let hir::ExprKind::Call(path, [arg]) = &expr.kind + && path.res(cx).opt_def_id().is_diag_item(&cx.tcx, sym::from_fn) + { + Some(cx.typeck_results().expr_ty(arg)) + } else { + None + } +} + +/// Verifies built-in types that have specific allowed operations +fn has_specific_allowed_type_and_operation<'tcx>( + cx: &LateContext<'tcx>, + lhs_ty: Ty<'tcx>, + op: hir::BinOpKind, + rhs_ty: Ty<'tcx>, +) -> bool { + let is_div_or_rem = matches!(op, hir::BinOpKind::Div | hir::BinOpKind::Rem); + let is_sat_or_wrap = |ty: Ty<'_>| matches!(ty.opt_diag_name(cx), Some(sym::Saturating | sym::Wrapping)); + + // If the RHS is `NonZero`, then division or module by zero will never occur. + if is_non_zero_u(cx, rhs_ty) && is_div_or_rem { + return true; + } + + // `Saturation` and `Wrapping` can overflow if the RHS is zero in a division or module. + if is_sat_or_wrap(lhs_ty) { + return !is_div_or_rem; + } + + false +} + +// For example, `i8` or `u128` and possible associated references like `&&u16`. +fn is_integer(ty: Ty<'_>) -> bool { + ty.peel_refs().is_integral() +} + +fn is_non_zero_u(cx: &LateContext<'_>, ty: Ty<'_>) -> bool { + if let ty::Adt(adt, substs) = ty.kind() + && cx.tcx.is_diagnostic_item(sym::NonZero, adt.did()) + && let int_type = substs.type_at(0) + && matches!(int_type.kind(), ty::Uint(_)) + { + true + } else { + false + } +} + +/// If one side is a literal it is possible to evaluate overflows as long as the other side has a +/// smaller type. `0` and `1` suffixes indicate different sides. +/// +/// For example, `1000u64 + u64::from(some_runtime_variable_of_type_u8)`. +fn is_safe_due_to_smaller_source_type( + cx: &LateContext<'_>, + op: hir::BinOpKind, + (expr0, ty0): (&hir::Expr<'_>, Ty<'_>), + expr1: &hir::Expr<'_>, +) -> bool { + let Some(num0) = literal_integer(cx, expr0) else { + return false; + }; + let Some(orig_ty1) = find_original_primitive_ty(cx, expr1) else { + return false; + }; + let Some(num1) = max_int_num(orig_ty1) else { + return false; + }; + let Some(rslt) = (match op { + hir::BinOpKind::Add => num0.checked_add(num1), + hir::BinOpKind::Mul => num0.checked_mul(num1), + _ => None, + }) else { + return false; + }; + match ty0.peel_refs().kind() { + ty::Uint(UintTy::U16) => u16::try_from(rslt).is_ok(), + ty::Uint(UintTy::U32) => u32::try_from(rslt).is_ok(), + ty::Uint(UintTy::U64) => u64::try_from(rslt).is_ok(), + ty::Uint(UintTy::U128) => true, + ty::Uint(UintTy::Usize) => usize::try_from(rslt).is_ok(), + _ => false, + } +} + +/// Returns the numeric value of a literal integer originated from `expr`, if any. +/// +/// Literal integers can be originated from adhoc declarations like `1`, associated constants +/// like `i32::MAX` or constant references like `N` from `const N: i32 = 1;`, +fn literal_integer(cx: &LateContext<'_>, expr: &hir::Expr<'_>) -> Option { + let actual = peel_hir_expr_unary(expr).0; + if let hir::ExprKind::Lit(lit) = actual.kind + && let ast::LitKind::Int(n, _) = lit.node + { + return Some(n.get()); + } + if let Some(Constant::Int(n)) = ConstEvalCtxt::new(cx).eval(expr) { + return Some(n); + } + None +} + +fn max_int_num(ty: Ty<'_>) -> Option { + match ty.peel_refs().kind() { + ty::Uint(UintTy::U8) => Some(u8::MAX.into()), + ty::Uint(UintTy::U16) => Some(u16::MAX.into()), + ty::Uint(UintTy::U32) => Some(u32::MAX.into()), + ty::Uint(UintTy::U64) => Some(u64::MAX.into()), + ty::Uint(UintTy::U128) => Some(u128::MAX), + ty::Uint(UintTy::Usize) => usize::MAX.try_into().ok(), + _ => None, + } +} diff --git a/clippy_lints/src/option_if_let_else.rs b/clippy_lints/src/option_if_let_else.rs index c32c74a8fe608..85cf483fce90d 100644 --- a/clippy_lints/src/option_if_let_else.rs +++ b/clippy_lints/src/option_if_let_else.rs @@ -6,16 +6,15 @@ use clippy_utils::sugg::Sugg; use clippy_utils::ty::is_copy; use clippy_utils::{ CaptureKind, can_move_expr_to_closure, eager_or_lazy, expr_requires_coercion, higher, is_else_clause, - is_in_const_context, peel_blocks, peel_hir_expr_while, + is_in_const_context, is_none_pattern, peel_blocks, peel_hir_expr_while, }; use rustc_data_structures::fx::FxHashSet; use rustc_errors::Applicability; -use rustc_hir::LangItem::{OptionNone, OptionSome, ResultErr, ResultOk}; +use rustc_hir::LangItem::ResultErr; use rustc_hir::def::Res; use rustc_hir::intravisit::{Visitor, walk_expr, walk_path}; use rustc_hir::{ - Arm, BindingMode, Expr, ExprKind, HirId, MatchSource, Mutability, Node, Pat, PatExpr, PatExprKind, PatKind, Path, - QPath, UnOp, + Arm, BindingMode, Expr, ExprKind, HirId, MatchSource, Mutability, Node, Pat, PatKind, Path, QPath, UnOp, }; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::hir::nested_filter; @@ -313,11 +312,14 @@ impl<'tcx> Visitor<'tcx> for ReferenceVisitor<'_, 'tcx> { } fn try_get_inner_pat_and_is_result<'tcx>(cx: &LateContext<'tcx>, pat: &Pat<'tcx>) -> Option<(&'tcx Pat<'tcx>, bool)> { - if let PatKind::TupleStruct(ref qpath, [inner_pat], ..) = pat.kind { - let res = cx.qpath_res(qpath, pat.hir_id); - if res.ctor_parent(cx).is_lang_item(cx, OptionSome) { + if let PatKind::TupleStruct(ref qpath, [inner_pat], ..) = pat.kind + && let res = cx.qpath_res(qpath, pat.hir_id) + && let Some(did) = res.ctor_parent(cx).opt_def_id() + { + let lang_items = cx.tcx.lang_items(); + if Some(did) == lang_items.option_some_variant() { return Some((inner_pat, false)); - } else if res.ctor_parent(cx).is_lang_item(cx, ResultOk) { + } else if Some(did) == lang_items.result_ok_variant() { return Some((inner_pat, true)); } } @@ -376,14 +378,7 @@ fn try_convert_match<'tcx>( fn is_none_or_err_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool { match arm.pat.kind { - PatKind::Expr(PatExpr { - kind: PatExprKind::Path(qpath), - hir_id, - .. - }) => cx - .qpath_res(qpath, *hir_id) - .ctor_parent(cx) - .is_lang_item(cx, OptionNone), + _ if is_none_pattern(cx, arm.pat) => true, PatKind::TupleStruct(ref qpath, [first_pat], _) => { cx.qpath_res(qpath, arm.pat.hir_id) .ctor_parent(cx) diff --git a/clippy_lints/src/ptr/cmp_null.rs b/clippy_lints/src/ptr/cmp_null.rs new file mode 100644 index 0000000000000..905b48e6d1d49 --- /dev/null +++ b/clippy_lints/src/ptr/cmp_null.rs @@ -0,0 +1,49 @@ +use super::CMP_NULL; +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::res::{MaybeDef, MaybeResPath}; +use clippy_utils::sugg::Sugg; +use clippy_utils::{is_lint_allowed, sym}; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Expr, ExprKind}; +use rustc_lint::LateContext; + +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'_>, + op: BinOpKind, + l: &Expr<'_>, + r: &Expr<'_>, +) -> bool { + let non_null_path_snippet = match ( + is_lint_allowed(cx, CMP_NULL, expr.hir_id), + is_null_path(cx, l), + is_null_path(cx, r), + ) { + (false, true, false) if let Some(sugg) = Sugg::hir_opt(cx, r) => sugg.maybe_paren(), + (false, false, true) if let Some(sugg) = Sugg::hir_opt(cx, l) => sugg.maybe_paren(), + _ => return false, + }; + let invert = if op == BinOpKind::Eq { "" } else { "!" }; + + span_lint_and_sugg( + cx, + CMP_NULL, + expr.span, + "comparing with null is better expressed by the `.is_null()` method", + "try", + format!("{invert}{non_null_path_snippet}.is_null()",), + Applicability::MachineApplicable, + ); + true +} + +fn is_null_path(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + if let ExprKind::Call(pathexp, []) = expr.kind { + matches!( + pathexp.basic_res().opt_diag_name(cx), + Some(sym::ptr_null | sym::ptr_null_mut) + ) + } else { + false + } +} diff --git a/clippy_lints/src/ptr/mod.rs b/clippy_lints/src/ptr/mod.rs new file mode 100644 index 0000000000000..6b2647e7b0a2f --- /dev/null +++ b/clippy_lints/src/ptr/mod.rs @@ -0,0 +1,202 @@ +use rustc_hir::{BinOpKind, Body, Expr, ExprKind, ImplItemKind, ItemKind, Node, TraitFn, TraitItem, TraitItemKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::declare_lint_pass; + +mod cmp_null; +mod mut_from_ref; +mod ptr_arg; +mod ptr_eq; + +declare_clippy_lint! { + /// ### What it does + /// This lint checks for function arguments of type `&String`, `&Vec`, + /// `&PathBuf`, and `Cow<_>`. It will also suggest you replace `.clone()` calls + /// with the appropriate `.to_owned()`/`to_string()` calls. + /// + /// ### Why is this bad? + /// Requiring the argument to be of the specific type + /// makes the function less useful for no benefit; slices in the form of `&[T]` + /// or `&str` usually suffice and can be obtained from other types, too. + /// + /// ### Known problems + /// There may be `fn(&Vec)`-typed references pointing to your function. + /// If you have them, you will get a compiler error after applying this lint's + /// suggestions. You then have the choice to undo your changes or change the + /// type of the reference. + /// + /// Note that if the function is part of your public interface, there may be + /// other crates referencing it, of which you may not be aware. Carefully + /// deprecate the function before applying the lint suggestions in this case. + /// + /// ### Example + /// ```ignore + /// fn foo(&Vec) { .. } + /// ``` + /// + /// Use instead: + /// ```ignore + /// fn foo(&[u32]) { .. } + /// ``` + #[clippy::version = "pre 1.29.0"] + pub PTR_ARG, + style, + "fn arguments of the type `&Vec<...>` or `&String`, suggesting to use `&[...]` or `&str` instead, respectively" +} + +declare_clippy_lint! { + /// ### What it does + /// This lint checks for equality comparisons with `ptr::null` + /// + /// ### Why is this bad? + /// It's easier and more readable to use the inherent + /// `.is_null()` + /// method instead + /// + /// ### Example + /// ```rust,ignore + /// use std::ptr; + /// + /// if x == ptr::null { + /// // .. + /// } + /// ``` + /// + /// Use instead: + /// ```rust,ignore + /// if x.is_null() { + /// // .. + /// } + /// ``` + #[clippy::version = "pre 1.29.0"] + pub CMP_NULL, + style, + "comparing a pointer to a null pointer, suggesting to use `.is_null()` instead" +} + +declare_clippy_lint! { + /// ### What it does + /// This lint checks for functions that take immutable references and return + /// mutable ones. This will not trigger if no unsafe code exists as there + /// are multiple safe functions which will do this transformation + /// + /// To be on the conservative side, if there's at least one mutable + /// reference with the output lifetime, this lint will not trigger. + /// + /// ### Why is this bad? + /// Creating a mutable reference which can be repeatably derived from an + /// immutable reference is unsound as it allows creating multiple live + /// mutable references to the same object. + /// + /// This [error](https://github.com/rust-lang/rust/issues/39465) actually + /// lead to an interim Rust release 1.15.1. + /// + /// ### Known problems + /// This pattern is used by memory allocators to allow allocating multiple + /// objects while returning mutable references to each one. So long as + /// different mutable references are returned each time such a function may + /// be safe. + /// + /// ### Example + /// ```ignore + /// fn foo(&Foo) -> &mut Bar { .. } + /// ``` + #[clippy::version = "pre 1.29.0"] + pub MUT_FROM_REF, + correctness, + "fns that create mutable refs from immutable ref args" +} + +declare_clippy_lint! { + /// ### What it does + /// Use `std::ptr::eq` when applicable + /// + /// ### Why is this bad? + /// `ptr::eq` can be used to compare `&T` references + /// (which coerce to `*const T` implicitly) by their address rather than + /// comparing the values they point to. + /// + /// ### Example + /// ```no_run + /// let a = &[1, 2, 3]; + /// let b = &[1, 2, 3]; + /// + /// assert!(a as *const _ as usize == b as *const _ as usize); + /// ``` + /// Use instead: + /// ```no_run + /// let a = &[1, 2, 3]; + /// let b = &[1, 2, 3]; + /// + /// assert!(std::ptr::eq(a, b)); + /// ``` + #[clippy::version = "1.49.0"] + pub PTR_EQ, + style, + "use `std::ptr::eq` when comparing raw pointers" +} + +declare_lint_pass!(Ptr => [PTR_ARG, CMP_NULL, MUT_FROM_REF, PTR_EQ]); + +impl<'tcx> LateLintPass<'tcx> for Ptr { + fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) { + if let TraitItemKind::Fn(sig, trait_method) = &item.kind { + if matches!(trait_method, TraitFn::Provided(_)) { + // Handled by `check_body`. + return; + } + + mut_from_ref::check(cx, sig, None); + ptr_arg::check_trait_item(cx, item.owner_id, sig); + } + } + + fn check_body(&mut self, cx: &LateContext<'tcx>, body: &Body<'tcx>) { + let mut parents = cx.tcx.hir_parent_iter(body.value.hir_id); + let (item_id, sig, is_trait_item) = match parents.next() { + Some((_, Node::Item(i))) => { + if let ItemKind::Fn { sig, .. } = &i.kind { + (i.owner_id, sig, false) + } else { + return; + } + }, + Some((_, Node::ImplItem(i))) => { + if !matches!(parents.next(), + Some((_, Node::Item(i))) if matches!(&i.kind, ItemKind::Impl(i) if i.of_trait.is_none()) + ) { + return; + } + if let ImplItemKind::Fn(sig, _) = &i.kind { + (i.owner_id, sig, false) + } else { + return; + } + }, + Some((_, Node::TraitItem(i))) => { + if let TraitItemKind::Fn(sig, _) = &i.kind { + (i.owner_id, sig, true) + } else { + return; + } + }, + _ => return, + }; + + mut_from_ref::check(cx, sig, Some(body)); + ptr_arg::check_body(cx, body, item_id, sig, is_trait_item); + } + + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if let ExprKind::Binary(op, l, r) = expr.kind + && (op.node == BinOpKind::Eq || op.node == BinOpKind::Ne) + { + #[expect( + clippy::collapsible_if, + reason = "the outer `if`s check the HIR, the inner ones run lints" + )] + if !cmp_null::check(cx, expr, op.node, l, r) { + ptr_eq::check(cx, op.node, l, r, expr.span); + } + } + } +} diff --git a/clippy_lints/src/ptr/mut_from_ref.rs b/clippy_lints/src/ptr/mut_from_ref.rs new file mode 100644 index 0000000000000..30d708f436b40 --- /dev/null +++ b/clippy_lints/src/ptr/mut_from_ref.rs @@ -0,0 +1,75 @@ +use super::MUT_FROM_REF; +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::visitors::contains_unsafe_block; +use rustc_errors::MultiSpan; +use rustc_hir::intravisit::Visitor; +use rustc_hir::{self as hir, Body, FnRetTy, FnSig, GenericArg, Lifetime, Mutability, TyKind}; +use rustc_lint::LateContext; +use rustc_span::Span; + +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, sig: &FnSig<'_>, body: Option<&Body<'tcx>>) { + let FnRetTy::Return(ty) = sig.decl.output else { return }; + for (out, mutability, out_span) in get_lifetimes(ty) { + if mutability != Some(Mutability::Mut) { + continue; + } + let out_region = cx.tcx.named_bound_var(out.hir_id); + // `None` if one of the types contains `&'a mut T` or `T<'a>`. + // Else, contains all the locations of `&'a T` types. + let args_immut_refs: Option> = sig + .decl + .inputs + .iter() + .flat_map(get_lifetimes) + .filter(|&(lt, _, _)| cx.tcx.named_bound_var(lt.hir_id) == out_region) + .map(|(_, mutability, span)| (mutability == Some(Mutability::Not)).then_some(span)) + .collect(); + if let Some(args_immut_refs) = args_immut_refs + && !args_immut_refs.is_empty() + && body.is_none_or(|body| sig.header.is_unsafe() || contains_unsafe_block(cx, body.value)) + { + span_lint_and_then( + cx, + MUT_FROM_REF, + out_span, + "mutable borrow from immutable input(s)", + |diag| { + let ms = MultiSpan::from_spans(args_immut_refs); + diag.span_note(ms, "immutable borrow here"); + }, + ); + } + } +} + +struct LifetimeVisitor<'tcx> { + result: Vec<(&'tcx Lifetime, Option, Span)>, +} + +impl<'tcx> Visitor<'tcx> for LifetimeVisitor<'tcx> { + fn visit_ty(&mut self, ty: &'tcx hir::Ty<'tcx, hir::AmbigArg>) { + if let TyKind::Ref(lt, ref m) = ty.kind { + self.result.push((lt, Some(m.mutbl), ty.span)); + } + hir::intravisit::walk_ty(self, ty); + } + + fn visit_generic_arg(&mut self, generic_arg: &'tcx GenericArg<'tcx>) { + if let GenericArg::Lifetime(lt) = generic_arg { + self.result.push((lt, None, generic_arg.span())); + } + hir::intravisit::walk_generic_arg(self, generic_arg); + } +} + +/// Visit `ty` and collect the all the lifetimes appearing in it, implicit or not. +/// +/// The second field of the vector's elements indicate if the lifetime is attached to a +/// shared reference, a mutable reference, or neither. +fn get_lifetimes<'tcx>(ty: &'tcx hir::Ty<'tcx>) -> Vec<(&'tcx Lifetime, Option, Span)> { + use hir::intravisit::VisitorExt as _; + + let mut visitor = LifetimeVisitor { result: Vec::new() }; + visitor.visit_ty_unambig(ty); + visitor.result +} diff --git a/clippy_lints/src/ptr.rs b/clippy_lints/src/ptr/ptr_arg.rs similarity index 50% rename from clippy_lints/src/ptr.rs rename to clippy_lints/src/ptr/ptr_arg.rs index 8446b6fbbea57..fd9230f00a8bd 100644 --- a/clippy_lints/src/ptr.rs +++ b/clippy_lints/src/ptr/ptr_arg.rs @@ -1,24 +1,22 @@ -use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then, span_lint_hir_and_then}; -use clippy_utils::res::{MaybeDef, MaybeResPath}; +use super::PTR_ARG; +use clippy_utils::diagnostics::span_lint_hir_and_then; +use clippy_utils::res::MaybeResPath; use clippy_utils::source::SpanRangeExt; -use clippy_utils::sugg::Sugg; -use clippy_utils::visitors::contains_unsafe_block; -use clippy_utils::{get_expr_use_or_unification_node, is_lint_allowed, std_or_core, sym}; +use clippy_utils::{get_expr_use_or_unification_node, is_lint_allowed, sym}; use hir::LifetimeKind; use rustc_abi::ExternAbi; -use rustc_errors::{Applicability, MultiSpan}; +use rustc_errors::Applicability; use rustc_hir::hir_id::{HirId, HirIdMap}; use rustc_hir::intravisit::{Visitor, walk_expr}; use rustc_hir::{ - self as hir, AnonConst, BinOpKind, BindingMode, Body, Expr, ExprKind, FnRetTy, FnSig, GenericArg, ImplItemKind, - ItemKind, Lifetime, Mutability, Node, Param, PatKind, QPath, TraitFn, TraitItem, TraitItemKind, TyKind, + self as hir, AnonConst, BindingMode, Body, Expr, ExprKind, FnSig, GenericArg, Lifetime, Mutability, Node, OwnerId, + Param, PatKind, QPath, TyKind, }; use rustc_infer::infer::TyCtxtInferExt; use rustc_infer::traits::{Obligation, ObligationCause}; -use rustc_lint::{LateContext, LateLintPass}; +use rustc_lint::LateContext; use rustc_middle::hir::nested_filter; use rustc_middle::ty::{self, Binder, ClauseKind, ExistentialPredicate, List, PredicateKind, Ty}; -use rustc_session::declare_lint_pass; use rustc_span::Span; use rustc_span::symbol::Symbol; use rustc_trait_selection::infer::InferCtxtExt as _; @@ -27,260 +25,65 @@ use std::{fmt, iter}; use crate::vec::is_allowed_vec_method; -declare_clippy_lint! { - /// ### What it does - /// This lint checks for function arguments of type `&String`, `&Vec`, - /// `&PathBuf`, and `Cow<_>`. It will also suggest you replace `.clone()` calls - /// with the appropriate `.to_owned()`/`to_string()` calls. - /// - /// ### Why is this bad? - /// Requiring the argument to be of the specific type - /// makes the function less useful for no benefit; slices in the form of `&[T]` - /// or `&str` usually suffice and can be obtained from other types, too. - /// - /// ### Known problems - /// There may be `fn(&Vec)`-typed references pointing to your function. - /// If you have them, you will get a compiler error after applying this lint's - /// suggestions. You then have the choice to undo your changes or change the - /// type of the reference. - /// - /// Note that if the function is part of your public interface, there may be - /// other crates referencing it, of which you may not be aware. Carefully - /// deprecate the function before applying the lint suggestions in this case. - /// - /// ### Example - /// ```ignore - /// fn foo(&Vec) { .. } - /// ``` - /// - /// Use instead: - /// ```ignore - /// fn foo(&[u32]) { .. } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub PTR_ARG, - style, - "fn arguments of the type `&Vec<...>` or `&String`, suggesting to use `&[...]` or `&str` instead, respectively" -} - -declare_clippy_lint! { - /// ### What it does - /// This lint checks for equality comparisons with `ptr::null` - /// - /// ### Why is this bad? - /// It's easier and more readable to use the inherent - /// `.is_null()` - /// method instead - /// - /// ### Example - /// ```rust,ignore - /// use std::ptr; - /// - /// if x == ptr::null { - /// // .. - /// } - /// ``` - /// - /// Use instead: - /// ```rust,ignore - /// if x.is_null() { - /// // .. - /// } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub CMP_NULL, - style, - "comparing a pointer to a null pointer, suggesting to use `.is_null()` instead" -} - -declare_clippy_lint! { - /// ### What it does - /// This lint checks for functions that take immutable references and return - /// mutable ones. This will not trigger if no unsafe code exists as there - /// are multiple safe functions which will do this transformation - /// - /// To be on the conservative side, if there's at least one mutable - /// reference with the output lifetime, this lint will not trigger. - /// - /// ### Why is this bad? - /// Creating a mutable reference which can be repeatably derived from an - /// immutable reference is unsound as it allows creating multiple live - /// mutable references to the same object. - /// - /// This [error](https://github.com/rust-lang/rust/issues/39465) actually - /// lead to an interim Rust release 1.15.1. - /// - /// ### Known problems - /// This pattern is used by memory allocators to allow allocating multiple - /// objects while returning mutable references to each one. So long as - /// different mutable references are returned each time such a function may - /// be safe. - /// - /// ### Example - /// ```ignore - /// fn foo(&Foo) -> &mut Bar { .. } - /// ``` - #[clippy::version = "pre 1.29.0"] - pub MUT_FROM_REF, - correctness, - "fns that create mutable refs from immutable ref args" -} - -declare_clippy_lint! { - /// ### What it does - /// Use `std::ptr::eq` when applicable - /// - /// ### Why is this bad? - /// `ptr::eq` can be used to compare `&T` references - /// (which coerce to `*const T` implicitly) by their address rather than - /// comparing the values they point to. - /// - /// ### Example - /// ```no_run - /// let a = &[1, 2, 3]; - /// let b = &[1, 2, 3]; - /// - /// assert!(a as *const _ as usize == b as *const _ as usize); - /// ``` - /// Use instead: - /// ```no_run - /// let a = &[1, 2, 3]; - /// let b = &[1, 2, 3]; - /// - /// assert!(std::ptr::eq(a, b)); - /// ``` - #[clippy::version = "1.49.0"] - pub PTR_EQ, - style, - "use `std::ptr::eq` when comparing raw pointers" -} - -declare_lint_pass!(Ptr => [PTR_ARG, CMP_NULL, MUT_FROM_REF, PTR_EQ]); - -impl<'tcx> LateLintPass<'tcx> for Ptr { - fn check_trait_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx TraitItem<'_>) { - if let TraitItemKind::Fn(sig, trait_method) = &item.kind { - if matches!(trait_method, TraitFn::Provided(_)) { - // Handled by check body. - return; - } - - check_mut_from_ref(cx, sig, None); - - if !matches!(sig.header.abi, ExternAbi::Rust) { - // Ignore `extern` functions with non-Rust calling conventions - return; - } - - for arg in check_fn_args( - cx, - cx.tcx.fn_sig(item.owner_id).instantiate_identity().skip_binder(), - sig.decl.inputs, - &[], - ) - .filter(|arg| arg.mutability() == Mutability::Not) - { - span_lint_hir_and_then(cx, PTR_ARG, arg.emission_id, arg.span, arg.build_msg(), |diag| { - diag.span_suggestion( - arg.span, - "change this to", - format!("{}{}", arg.ref_prefix, arg.deref_ty.display(cx)), - Applicability::Unspecified, - ); - }); - } - } +pub(super) fn check_body<'tcx>( + cx: &LateContext<'tcx>, + body: &Body<'tcx>, + item_id: OwnerId, + sig: &FnSig<'tcx>, + is_trait_item: bool, +) { + if !matches!(sig.header.abi, ExternAbi::Rust) { + // Ignore `extern` functions with non-Rust calling conventions + return; } - fn check_body(&mut self, cx: &LateContext<'tcx>, body: &Body<'tcx>) { - let mut parents = cx.tcx.hir_parent_iter(body.value.hir_id); - let (item_id, sig, is_trait_item) = match parents.next() { - Some((_, Node::Item(i))) => { - if let ItemKind::Fn { sig, .. } = &i.kind { - (i.owner_id, sig, false) - } else { - return; - } - }, - Some((_, Node::ImplItem(i))) => { - if !matches!(parents.next(), - Some((_, Node::Item(i))) if matches!(&i.kind, ItemKind::Impl(i) if i.of_trait.is_none()) - ) { - return; - } - if let ImplItemKind::Fn(sig, _) = &i.kind { - (i.owner_id, sig, false) - } else { - return; - } - }, - Some((_, Node::TraitItem(i))) => { - if let TraitItemKind::Fn(sig, _) = &i.kind { - (i.owner_id, sig, true) - } else { - return; - } - }, - _ => return, - }; - - check_mut_from_ref(cx, sig, Some(body)); - - if !matches!(sig.header.abi, ExternAbi::Rust) { - // Ignore `extern` functions with non-Rust calling conventions - return; - } - - let decl = sig.decl; - let sig = cx.tcx.fn_sig(item_id).instantiate_identity().skip_binder(); - let lint_args: Vec<_> = check_fn_args(cx, sig, decl.inputs, body.params) - .filter(|arg| !is_trait_item || arg.mutability() == Mutability::Not) - .collect(); - let results = check_ptr_arg_usage(cx, body, &lint_args); - - for (result, args) in iter::zip(&results, &lint_args).filter(|(r, _)| !r.skip) { - span_lint_hir_and_then(cx, PTR_ARG, args.emission_id, args.span, args.build_msg(), |diag| { - diag.multipart_suggestion( - "change this to", - iter::once((args.span, format!("{}{}", args.ref_prefix, args.deref_ty.display(cx)))) - .chain(result.replacements.iter().map(|r| { - ( - r.expr_span, - format!("{}{}", r.self_span.get_source_text(cx).unwrap(), r.replacement), - ) - })) - .collect(), - Applicability::Unspecified, - ); - }); - } + let decl = sig.decl; + let sig = cx.tcx.fn_sig(item_id).instantiate_identity().skip_binder(); + let lint_args: Vec<_> = check_fn_args(cx, sig, decl.inputs, body.params) + .filter(|arg| !is_trait_item || arg.mutability() == Mutability::Not) + .collect(); + let results = check_ptr_arg_usage(cx, body, &lint_args); + + for (result, args) in iter::zip(&results, &lint_args).filter(|(r, _)| !r.skip) { + span_lint_hir_and_then(cx, PTR_ARG, args.emission_id, args.span, args.build_msg(), |diag| { + diag.multipart_suggestion( + "change this to", + iter::once((args.span, format!("{}{}", args.ref_prefix, args.deref_ty.display(cx)))) + .chain(result.replacements.iter().map(|r| { + ( + r.expr_span, + format!("{}{}", r.self_span.get_source_text(cx).unwrap(), r.replacement), + ) + })) + .collect(), + Applicability::Unspecified, + ); + }); } +} - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - if let ExprKind::Binary(op, l, r) = expr.kind - && (op.node == BinOpKind::Eq || op.node == BinOpKind::Ne) - { - let non_null_path_snippet = match ( - is_lint_allowed(cx, CMP_NULL, expr.hir_id), - is_null_path(cx, l), - is_null_path(cx, r), - ) { - (false, true, false) if let Some(sugg) = Sugg::hir_opt(cx, r) => sugg.maybe_paren(), - (false, false, true) if let Some(sugg) = Sugg::hir_opt(cx, l) => sugg.maybe_paren(), - _ => return check_ptr_eq(cx, expr, op.node, l, r), - }; - let invert = if op.node == BinOpKind::Eq { "" } else { "!" }; +pub(super) fn check_trait_item<'tcx>(cx: &LateContext<'tcx>, item_id: OwnerId, sig: &FnSig<'tcx>) { + if !matches!(sig.header.abi, ExternAbi::Rust) { + // Ignore `extern` functions with non-Rust calling conventions + return; + } - span_lint_and_sugg( - cx, - CMP_NULL, - expr.span, - "comparing with null is better expressed by the `.is_null()` method", - "try", - format!("{invert}{non_null_path_snippet}.is_null()",), - Applicability::MachineApplicable, + for arg in check_fn_args( + cx, + cx.tcx.fn_sig(item_id).instantiate_identity().skip_binder(), + sig.decl.inputs, + &[], + ) + .filter(|arg| arg.mutability() == Mutability::Not) + { + span_lint_hir_and_then(cx, PTR_ARG, arg.emission_id, arg.span, arg.build_msg(), |diag| { + diag.span_suggestion( + arg.span, + "change this to", + format!("{}{}", arg.ref_prefix, arg.deref_ty.display(cx)), + Applicability::Unspecified, ); - } + }); } } @@ -393,10 +196,7 @@ fn check_fn_args<'cx, 'tcx: 'cx>( hir_tys: &'tcx [hir::Ty<'tcx>], params: &'tcx [Param<'tcx>], ) -> impl Iterator> + 'cx { - fn_sig - .inputs() - .iter() - .zip(hir_tys.iter()) + iter::zip(fn_sig.inputs(), hir_tys) .enumerate() .filter_map(move |(i, (ty, hir_ty))| { if let ty::Ref(_, ty, mutability) = *ty.kind() @@ -499,41 +299,6 @@ fn check_fn_args<'cx, 'tcx: 'cx>( }) } -fn check_mut_from_ref<'tcx>(cx: &LateContext<'tcx>, sig: &FnSig<'_>, body: Option<&Body<'tcx>>) { - let FnRetTy::Return(ty) = sig.decl.output else { return }; - for (out, mutability, out_span) in get_lifetimes(ty) { - if mutability != Some(Mutability::Mut) { - continue; - } - let out_region = cx.tcx.named_bound_var(out.hir_id); - // `None` if one of the types contains `&'a mut T` or `T<'a>`. - // Else, contains all the locations of `&'a T` types. - let args_immut_refs: Option> = sig - .decl - .inputs - .iter() - .flat_map(get_lifetimes) - .filter(|&(lt, _, _)| cx.tcx.named_bound_var(lt.hir_id) == out_region) - .map(|(_, mutability, span)| (mutability == Some(Mutability::Not)).then_some(span)) - .collect(); - if let Some(args_immut_refs) = args_immut_refs - && !args_immut_refs.is_empty() - && body.is_none_or(|body| sig.header.is_unsafe() || contains_unsafe_block(cx, body.value)) - { - span_lint_and_then( - cx, - MUT_FROM_REF, - out_span, - "mutable borrow from immutable input(s)", - |diag| { - let ms = MultiSpan::from_spans(args_immut_refs); - diag.span_note(ms, "immutable borrow here"); - }, - ); - } - } -} - #[expect(clippy::too_many_lines)] fn check_ptr_arg_usage<'tcx>(cx: &LateContext<'tcx>, body: &Body<'tcx>, args: &[PtrArg<'tcx>]) -> Vec { struct V<'cx, 'tcx> { @@ -658,11 +423,11 @@ fn check_ptr_arg_usage<'tcx>(cx: &LateContext<'tcx>, body: &Body<'tcx>, args: &[ match param.pat.kind { PatKind::Binding(BindingMode::NONE, id, ident, None) if !is_lint_allowed(cx, PTR_ARG, param.hir_id) - // Let's not lint for the current parameter. The user may still intend to mutate - // (or, if not mutate, then perhaps call a method that's not otherwise available - // for) the referenced value behind the parameter with the underscore being only - // temporary. - && !ident.name.as_str().starts_with('_') => + // Let's not lint for the current parameter. The user may still intend to mutate + // (or, if not mutate, then perhaps call a method that's not otherwise available + // for) the referenced value behind the parameter with the underscore being only + // temporary. + && !ident.name.as_str().starts_with('_') => { Some((id, i)) }, @@ -708,123 +473,3 @@ fn matches_preds<'tcx>( .must_apply_modulo_regions(), }) } - -struct LifetimeVisitor<'tcx> { - result: Vec<(&'tcx Lifetime, Option, Span)>, -} - -impl<'tcx> Visitor<'tcx> for LifetimeVisitor<'tcx> { - fn visit_ty(&mut self, ty: &'tcx hir::Ty<'tcx, hir::AmbigArg>) { - if let TyKind::Ref(lt, ref m) = ty.kind { - self.result.push((lt, Some(m.mutbl), ty.span)); - } - hir::intravisit::walk_ty(self, ty); - } - - fn visit_generic_arg(&mut self, generic_arg: &'tcx GenericArg<'tcx>) { - if let GenericArg::Lifetime(lt) = generic_arg { - self.result.push((lt, None, generic_arg.span())); - } - hir::intravisit::walk_generic_arg(self, generic_arg); - } -} - -/// Visit `ty` and collect the all the lifetimes appearing in it, implicit or not. -/// -/// The second field of the vector's elements indicate if the lifetime is attached to a -/// shared reference, a mutable reference, or neither. -fn get_lifetimes<'tcx>(ty: &'tcx hir::Ty<'tcx>) -> Vec<(&'tcx Lifetime, Option, Span)> { - use hir::intravisit::VisitorExt as _; - - let mut visitor = LifetimeVisitor { result: Vec::new() }; - visitor.visit_ty_unambig(ty); - visitor.result -} - -fn is_null_path(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { - if let ExprKind::Call(pathexp, []) = expr.kind { - matches!( - pathexp.basic_res().opt_diag_name(cx), - Some(sym::ptr_null | sym::ptr_null_mut) - ) - } else { - false - } -} - -fn check_ptr_eq<'tcx>( - cx: &LateContext<'tcx>, - expr: &'tcx Expr<'_>, - op: BinOpKind, - left: &'tcx Expr<'_>, - right: &'tcx Expr<'_>, -) { - if expr.span.from_expansion() { - return; - } - - // Remove one level of usize conversion if any - let (left, right, usize_peeled) = match (expr_as_cast_to_usize(cx, left), expr_as_cast_to_usize(cx, right)) { - (Some(lhs), Some(rhs)) => (lhs, rhs, true), - _ => (left, right, false), - }; - - // This lint concerns raw pointers - let (left_ty, right_ty) = (cx.typeck_results().expr_ty(left), cx.typeck_results().expr_ty(right)); - if !left_ty.is_raw_ptr() || !right_ty.is_raw_ptr() { - return; - } - - let ((left_var, left_casts_peeled), (right_var, right_casts_peeled)) = - (peel_raw_casts(cx, left, left_ty), peel_raw_casts(cx, right, right_ty)); - - if !(usize_peeled || left_casts_peeled || right_casts_peeled) { - return; - } - - let mut app = Applicability::MachineApplicable; - let left_snip = Sugg::hir_with_context(cx, left_var, expr.span.ctxt(), "_", &mut app); - let right_snip = Sugg::hir_with_context(cx, right_var, expr.span.ctxt(), "_", &mut app); - { - let Some(top_crate) = std_or_core(cx) else { return }; - let invert = if op == BinOpKind::Eq { "" } else { "!" }; - span_lint_and_sugg( - cx, - PTR_EQ, - expr.span, - format!("use `{top_crate}::ptr::eq` when comparing raw pointers"), - "try", - format!("{invert}{top_crate}::ptr::eq({left_snip}, {right_snip})"), - app, - ); - } -} - -// If the given expression is a cast to a usize, return the lhs of the cast -// E.g., `foo as *const _ as usize` returns `foo as *const _`. -fn expr_as_cast_to_usize<'tcx>(cx: &LateContext<'tcx>, cast_expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> { - if !cast_expr.span.from_expansion() - && cx.typeck_results().expr_ty(cast_expr) == cx.tcx.types.usize - && let ExprKind::Cast(expr, _) = cast_expr.kind - { - Some(expr) - } else { - None - } -} - -// Peel raw casts if the remaining expression can be coerced to it, and whether casts have been -// peeled or not. -fn peel_raw_casts<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, expr_ty: Ty<'tcx>) -> (&'tcx Expr<'tcx>, bool) { - if !expr.span.from_expansion() - && let ExprKind::Cast(inner, _) = expr.kind - && let ty::RawPtr(target_ty, _) = expr_ty.kind() - && let inner_ty = cx.typeck_results().expr_ty(inner) - && let ty::RawPtr(inner_target_ty, _) | ty::Ref(_, inner_target_ty, _) = inner_ty.kind() - && target_ty == inner_target_ty - { - (peel_raw_casts(cx, inner, inner_ty).0, true) - } else { - (expr, false) - } -} diff --git a/clippy_lints/src/ptr/ptr_eq.rs b/clippy_lints/src/ptr/ptr_eq.rs new file mode 100644 index 0000000000000..c982bb1ffbc5f --- /dev/null +++ b/clippy_lints/src/ptr/ptr_eq.rs @@ -0,0 +1,87 @@ +use super::PTR_EQ; +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::std_or_core; +use clippy_utils::sugg::Sugg; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Expr, ExprKind}; +use rustc_lint::LateContext; +use rustc_middle::ty::{self, Ty}; +use rustc_span::Span; + +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + op: BinOpKind, + left: &'tcx Expr<'_>, + right: &'tcx Expr<'_>, + span: Span, +) { + if span.from_expansion() { + return; + } + + // Remove one level of usize conversion if any + let (left, right, usize_peeled) = match (expr_as_cast_to_usize(cx, left), expr_as_cast_to_usize(cx, right)) { + (Some(lhs), Some(rhs)) => (lhs, rhs, true), + _ => (left, right, false), + }; + + // This lint concerns raw pointers + let (left_ty, right_ty) = (cx.typeck_results().expr_ty(left), cx.typeck_results().expr_ty(right)); + if !left_ty.is_raw_ptr() || !right_ty.is_raw_ptr() { + return; + } + + let ((left_var, left_casts_peeled), (right_var, right_casts_peeled)) = + (peel_raw_casts(cx, left, left_ty), peel_raw_casts(cx, right, right_ty)); + + if !(usize_peeled || left_casts_peeled || right_casts_peeled) { + return; + } + + let mut app = Applicability::MachineApplicable; + let ctxt = span.ctxt(); + let left_snip = Sugg::hir_with_context(cx, left_var, ctxt, "_", &mut app); + let right_snip = Sugg::hir_with_context(cx, right_var, ctxt, "_", &mut app); + { + let Some(top_crate) = std_or_core(cx) else { return }; + let invert = if op == BinOpKind::Eq { "" } else { "!" }; + span_lint_and_sugg( + cx, + PTR_EQ, + span, + format!("use `{top_crate}::ptr::eq` when comparing raw pointers"), + "try", + format!("{invert}{top_crate}::ptr::eq({left_snip}, {right_snip})"), + app, + ); + } +} + +// If the given expression is a cast to a usize, return the lhs of the cast +// E.g., `foo as *const _ as usize` returns `foo as *const _`. +fn expr_as_cast_to_usize<'tcx>(cx: &LateContext<'tcx>, cast_expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> { + if !cast_expr.span.from_expansion() + && cx.typeck_results().expr_ty(cast_expr) == cx.tcx.types.usize + && let ExprKind::Cast(expr, _) = cast_expr.kind + { + Some(expr) + } else { + None + } +} + +// Peel raw casts if the remaining expression can be coerced to it, and whether casts have been +// peeled or not. +fn peel_raw_casts<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, expr_ty: Ty<'tcx>) -> (&'tcx Expr<'tcx>, bool) { + if !expr.span.from_expansion() + && let ExprKind::Cast(inner, _) = expr.kind + && let ty::RawPtr(target_ty, _) = expr_ty.kind() + && let inner_ty = cx.typeck_results().expr_ty(inner) + && let ty::RawPtr(inner_target_ty, _) | ty::Ref(_, inner_target_ty, _) = inner_ty.kind() + && target_ty == inner_target_ty + { + (peel_raw_casts(cx, inner, inner_ty).0, true) + } else { + (expr, false) + } +} diff --git a/clippy_lints/src/question_mark.rs b/clippy_lints/src/question_mark.rs index 14675015c35e0..59d31f782bc33 100644 --- a/clippy_lints/src/question_mark.rs +++ b/clippy_lints/src/question_mark.rs @@ -150,7 +150,7 @@ fn check_let_some_else_return_none(cx: &LateContext<'_>, stmt: &Stmt<'_>) { let init_expr_str = Sugg::hir_with_applicability(cx, init_expr, "..", &mut applicability).maybe_paren(); // Take care when binding is `ref` let sugg = if let PatKind::Binding( - BindingMode(ByRef::Yes(_,ref_mutability), binding_mutability), + BindingMode(ByRef::Yes(_, ref_mutability), binding_mutability), _hir_id, ident, subpattern, diff --git a/clippy_lints/src/replace_box.rs b/clippy_lints/src/replace_box.rs index 4bbd1803a78d2..638f6dc1532bb 100644 --- a/clippy_lints/src/replace_box.rs +++ b/clippy_lints/src/replace_box.rs @@ -3,11 +3,17 @@ use clippy_utils::res::{MaybeDef, MaybeResPath}; use clippy_utils::sugg::Sugg; use clippy_utils::ty::implements_trait; use clippy_utils::{is_default_equivalent_call, local_is_initialized}; +use rustc_data_structures::fx::FxHashSet; +use rustc_data_structures::smallvec::SmallVec; use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind, LangItem, QPath}; +use rustc_hir::{Body, BodyId, Expr, ExprKind, HirId, LangItem, QPath}; +use rustc_hir_typeck::expr_use_visitor::{Delegate, ExprUseVisitor, PlaceBase, PlaceWithHirId}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_session::declare_lint_pass; -use rustc_span::sym; +use rustc_middle::hir::place::ProjectionKind; +use rustc_middle::mir::FakeReadCause; +use rustc_middle::ty; +use rustc_session::impl_lint_pass; +use rustc_span::{Symbol, sym}; declare_clippy_lint! { /// ### What it does @@ -33,17 +39,57 @@ declare_clippy_lint! { perf, "assigning a newly created box to `Box` is inefficient" } -declare_lint_pass!(ReplaceBox => [REPLACE_BOX]); + +#[derive(Default)] +pub struct ReplaceBox { + consumed_locals: FxHashSet, + loaded_bodies: SmallVec<[BodyId; 2]>, +} + +impl ReplaceBox { + fn get_consumed_locals(&mut self, cx: &LateContext<'_>) -> &FxHashSet { + if let Some(body_id) = cx.enclosing_body + && !self.loaded_bodies.contains(&body_id) + { + self.loaded_bodies.push(body_id); + ExprUseVisitor::for_clippy( + cx, + cx.tcx.hir_body_owner_def_id(body_id), + MovedVariablesCtxt { + consumed_locals: &mut self.consumed_locals, + }, + ) + .consume_body(cx.tcx.hir_body(body_id)) + .into_ok(); + } + + &self.consumed_locals + } +} + +impl_lint_pass!(ReplaceBox => [REPLACE_BOX]); impl LateLintPass<'_> for ReplaceBox { + fn check_body_post(&mut self, _: &LateContext<'_>, body: &Body<'_>) { + if self.loaded_bodies.first().is_some_and(|&x| x == body.id()) { + self.consumed_locals.clear(); + self.loaded_bodies.clear(); + } + } + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) { if let ExprKind::Assign(lhs, rhs, _) = &expr.kind && !lhs.span.from_expansion() && !rhs.span.from_expansion() && let lhs_ty = cx.typeck_results().expr_ty(lhs) + && let Some(inner_ty) = lhs_ty.boxed_ty() // No diagnostic for late-initialized locals && lhs.res_local_id().is_none_or(|local| local_is_initialized(cx, local)) - && let Some(inner_ty) = lhs_ty.boxed_ty() + // No diagnostic if this is a local that has been moved, or the field + // of a local that has been moved, or several chained field accesses of a local + && local_base(lhs).is_none_or(|(base_id, _)| { + !self.get_consumed_locals(cx).contains(&base_id) + }) { if let Some(default_trait_id) = cx.tcx.get_diagnostic_item(sym::Default) && implements_trait(cx, inner_ty, default_trait_id, &[]) @@ -109,3 +155,46 @@ fn get_box_new_payload<'tcx>(cx: &LateContext<'_>, expr: &Expr<'tcx>) -> Option< None } } + +struct MovedVariablesCtxt<'a> { + consumed_locals: &'a mut FxHashSet, +} + +impl<'tcx> Delegate<'tcx> for MovedVariablesCtxt<'_> { + fn consume(&mut self, cmt: &PlaceWithHirId<'tcx>, _: HirId) { + if let PlaceBase::Local(id) = cmt.place.base + && let mut projections = cmt + .place + .projections + .iter() + .filter(|x| matches!(x.kind, ProjectionKind::Deref)) + // Either no deref or multiple derefs + && (projections.next().is_none() || projections.next().is_some()) + { + self.consumed_locals.insert(id); + } + } + + fn use_cloned(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {} + + fn borrow(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId, _: ty::BorrowKind) {} + + fn mutate(&mut self, _: &PlaceWithHirId<'tcx>, _: HirId) {} + + fn fake_read(&mut self, _: &PlaceWithHirId<'tcx>, _: FakeReadCause, _: HirId) {} +} + +/// A local place followed by optional fields +type IdFields = (HirId, Vec); + +/// If `expr` is a local variable with optional field accesses, return it. +fn local_base(expr: &Expr<'_>) -> Option { + match expr.kind { + ExprKind::Path(qpath) => qpath.res_local_id().map(|id| (id, Vec::new())), + ExprKind::Field(expr, field) => local_base(expr).map(|(id, mut fields)| { + fields.push(field.name); + (id, fields) + }), + _ => None, + } +} diff --git a/clippy_lints/src/returns/let_and_return.rs b/clippy_lints/src/returns/let_and_return.rs index f54a26a77620a..0a00981e15bed 100644 --- a/clippy_lints/src/returns/let_and_return.rs +++ b/clippy_lints/src/returns/let_and_return.rs @@ -3,7 +3,7 @@ use clippy_utils::res::MaybeResPath; use clippy_utils::source::SpanRangeExt; use clippy_utils::sugg::has_enclosing_paren; use clippy_utils::visitors::for_each_expr; -use clippy_utils::{binary_expr_needs_parentheses, fn_def_id, span_contains_cfg}; +use clippy_utils::{binary_expr_needs_parentheses, fn_def_id, span_contains_non_whitespace}; use core::ops::ControlFlow; use rustc_errors::Applicability; use rustc_hir::{Block, Expr, PatKind, StmtKind}; @@ -27,7 +27,7 @@ pub(super) fn check_block<'tcx>(cx: &LateContext<'tcx>, block: &'tcx Block<'_>) && !initexpr.span.in_external_macro(cx.sess().source_map()) && !retexpr.span.in_external_macro(cx.sess().source_map()) && !local.span.from_expansion() - && !span_contains_cfg(cx, stmt.span.between(retexpr.span)) + && !span_contains_non_whitespace(cx, stmt.span.between(retexpr.span), true) { span_lint_hir_and_then( cx, diff --git a/clippy_lints/src/significant_drop_tightening.rs b/clippy_lints/src/significant_drop_tightening.rs index c4604fb1558df..fabb21f78b9ec 100644 --- a/clippy_lints/src/significant_drop_tightening.rs +++ b/clippy_lints/src/significant_drop_tightening.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::res::MaybeResPath; use clippy_utils::source::{indent_of, snippet}; -use clippy_utils::{expr_or_init, get_attr, peel_hir_expr_unary, sym}; +use clippy_utils::{expr_or_init, get_builtin_attr, peel_hir_expr_unary, sym}; use rustc_data_structures::fx::{FxHashMap, FxIndexMap}; use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; @@ -167,7 +167,7 @@ impl<'cx, 'others, 'tcx> AttrChecker<'cx, 'others, 'tcx> { fn has_sig_drop_attr_uncached(&mut self, ty: Ty<'tcx>, depth: usize) -> bool { if let Some(adt) = ty.ty_adt_def() { - let mut iter = get_attr( + let mut iter = get_builtin_attr( self.cx.sess(), self.cx.tcx.get_all_attrs(adt.did()), sym::has_significant_drop, diff --git a/clippy_lints/src/single_range_in_vec_init.rs b/clippy_lints/src/single_range_in_vec_init.rs index 412ca2fa4ed9a..92d1b112198fd 100644 --- a/clippy_lints/src/single_range_in_vec_init.rs +++ b/clippy_lints/src/single_range_in_vec_init.rs @@ -1,14 +1,15 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::higher::VecArgs; use clippy_utils::macros::root_macro_call_first_node; -use clippy_utils::source::SpanRangeExt; +use clippy_utils::source::{SpanRangeExt, snippet_with_context}; use clippy_utils::ty::implements_trait; use clippy_utils::{is_no_std_crate, sym}; use rustc_ast::{LitIntType, LitKind, UintTy}; use rustc_errors::Applicability; -use rustc_hir::{Expr, ExprKind, LangItem, StructTailExpr}; +use rustc_hir::{Expr, ExprKind, StructTailExpr}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; +use rustc_span::DesugaringKind; use std::fmt::{self, Display, Formatter}; declare_clippy_lint! { @@ -86,19 +87,21 @@ impl LateLintPass<'_> for SingleRangeInVecInit { return; }; - let ExprKind::Struct(&qpath, [start, end], StructTailExpr::None) = inner_expr.kind else { + let ExprKind::Struct(_, [start, end], StructTailExpr::None) = inner_expr.kind else { return; }; - if cx.tcx.qpath_is_lang_item(qpath, LangItem::Range) + if inner_expr.span.is_desugaring(DesugaringKind::RangeExpr) && let ty = cx.typeck_results().expr_ty(start.expr) && let Some(snippet) = span.get_source_text(cx) // `is_from_proc_macro` will skip any `vec![]`. Let's not! && snippet.starts_with(suggested_type.starts_with()) && snippet.ends_with(suggested_type.ends_with()) - && let Some(start_snippet) = start.span.get_source_text(cx) - && let Some(end_snippet) = end.span.get_source_text(cx) { + let mut applicability = Applicability::MachineApplicable; + let (start_snippet, _) = snippet_with_context(cx, start.expr.span, span.ctxt(), "..", &mut applicability); + let (end_snippet, _) = snippet_with_context(cx, end.expr.span, span.ctxt(), "..", &mut applicability); + let should_emit_every_value = if let Some(step_def_id) = cx.tcx.get_diagnostic_item(sym::range_step) && implements_trait(cx, ty, step_def_id, &[]) { @@ -129,7 +132,7 @@ impl LateLintPass<'_> for SingleRangeInVecInit { span, "if you wanted a `Vec` that contains the entire range, try", format!("({start_snippet}..{end_snippet}).collect::>()"), - Applicability::MaybeIncorrect, + applicability, ); } @@ -138,7 +141,7 @@ impl LateLintPass<'_> for SingleRangeInVecInit { inner_expr.span, format!("if you wanted {suggested_type} of len {end_snippet}, try"), format!("{start_snippet}; {end_snippet}"), - Applicability::MaybeIncorrect, + applicability, ); } }, diff --git a/clippy_lints/src/transmute/mod.rs b/clippy_lints/src/transmute/mod.rs index 5fda388259a61..d643f7aea497f 100644 --- a/clippy_lints/src/transmute/mod.rs +++ b/clippy_lints/src/transmute/mod.rs @@ -435,10 +435,11 @@ declare_clippy_lint! { /// to infer a technically correct yet unexpected type. /// /// ### Example - /// ```no_run + /// ``` /// # unsafe { + /// let mut x: i32 = 0; /// // Avoid "naked" calls to `transmute()`! - /// let x: i32 = std::mem::transmute([1u16, 2u16]); + /// x = std::mem::transmute([1u16, 2u16]); /// /// // `first_answers` is intended to transmute a slice of bool to a slice of u8. /// // But the programmer forgot to index the first element of the outer slice, @@ -449,7 +450,7 @@ declare_clippy_lint! { /// # } /// ``` /// Use instead: - /// ```no_run + /// ``` /// # unsafe { /// let x = std::mem::transmute::<[u16; 2], i32>([1u16, 2u16]); /// diff --git a/clippy_lints/src/types/rc_buffer.rs b/clippy_lints/src/types/rc_buffer.rs index 46d9febb187fc..43b38bb662dcc 100644 --- a/clippy_lints/src/types/rc_buffer.rs +++ b/clippy_lints/src/types/rc_buffer.rs @@ -1,115 +1,59 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::qpath_generic_tys; -use clippy_utils::res::{MaybeDef, MaybeResPath}; +use clippy_utils::res::MaybeResPath; use clippy_utils::source::snippet_with_applicability; use rustc_errors::Applicability; use rustc_hir::def_id::DefId; -use rustc_hir::{self as hir, QPath, TyKind}; +use rustc_hir::{QPath, Ty, TyKind}; use rustc_lint::LateContext; use rustc_span::symbol::sym; +use std::borrow::Cow; use super::RC_BUFFER; -pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, qpath: &QPath<'_>, def_id: DefId) -> bool { - let app = Applicability::Unspecified; - let name = cx.tcx.get_diagnostic_name(def_id); - if name == Some(sym::Rc) { - if let Some(alternate) = match_buffer_type(cx, qpath) { - #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] - span_lint_and_then( - cx, - RC_BUFFER, - hir_ty.span, - "usage of `Rc` when T is a buffer type", - |diag| { - diag.span_suggestion(hir_ty.span, "try", format!("Rc<{alternate}>"), app); - }, - ); - } else { - let Some(ty) = qpath_generic_tys(qpath).next() else { - return false; - }; - if !ty.basic_res().is_diag_item(cx, sym::Vec) { - return false; - } - let TyKind::Path(qpath) = &ty.kind else { return false }; - let inner_span = match qpath_generic_tys(qpath).next() { - Some(ty) => ty.span, - None => return false, - }; - span_lint_and_then( - cx, - RC_BUFFER, - hir_ty.span, - "usage of `Rc` when T is a buffer type", - |diag| { - let mut applicability = app; - diag.span_suggestion( - hir_ty.span, - "try", - format!( - "Rc<[{}]>", - snippet_with_applicability(cx, inner_span, "..", &mut applicability) - ), - app, - ); - }, - ); - return true; - } - } else if name == Some(sym::Arc) { - if let Some(alternate) = match_buffer_type(cx, qpath) { - #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] - span_lint_and_then( - cx, - RC_BUFFER, - hir_ty.span, - "usage of `Arc` when T is a buffer type", - |diag| { - diag.span_suggestion(hir_ty.span, "try", format!("Arc<{alternate}>"), app); - }, - ); - } else if let Some(ty) = qpath_generic_tys(qpath).next() { - if !ty.basic_res().is_diag_item(cx, sym::Vec) { - return false; - } - let TyKind::Path(qpath) = &ty.kind else { return false }; - let inner_span = match qpath_generic_tys(qpath).next() { - Some(ty) => ty.span, - None => return false, - }; - span_lint_and_then( - cx, - RC_BUFFER, - hir_ty.span, - "usage of `Arc` when T is a buffer type", - |diag| { - let mut applicability = app; - diag.span_suggestion( - hir_ty.span, - "try", - format!( - "Arc<[{}]>", - snippet_with_applicability(cx, inner_span, "..", &mut applicability) - ), - app, - ); - }, - ); - return true; - } +pub(super) fn check(cx: &LateContext<'_>, hir_ty: &Ty<'_>, qpath: &QPath<'_>, def_id: DefId) -> bool { + let mut app = Applicability::Unspecified; + let rc = match cx.tcx.get_diagnostic_name(def_id) { + Some(sym::Rc) => "Rc", + Some(sym::Arc) => "Arc", + _ => return false, + }; + if let Some(ty) = qpath_generic_tys(qpath).next() + && let Some(alternate) = match_buffer_type(cx, ty, &mut app) + { + span_lint_and_then( + cx, + RC_BUFFER, + hir_ty.span, + format!("usage of `{rc}` when `T` is a buffer type"), + |diag| { + diag.span_suggestion_verbose(ty.span, "try", alternate, app); + }, + ); + true + } else { + false } - - false } -fn match_buffer_type(cx: &LateContext<'_>, qpath: &QPath<'_>) -> Option<&'static str> { - let ty = qpath_generic_tys(qpath).next()?; +fn match_buffer_type( + cx: &LateContext<'_>, + ty: &Ty<'_>, + applicability: &mut Applicability, +) -> Option> { let id = ty.basic_res().opt_def_id()?; let path = match cx.tcx.get_diagnostic_name(id) { - Some(sym::OsString) => "std::ffi::OsStr", - Some(sym::PathBuf) => "std::path::Path", - _ if Some(id) == cx.tcx.lang_items().string() => "str", + Some(sym::OsString) => "std::ffi::OsStr".into(), + Some(sym::PathBuf) => "std::path::Path".into(), + Some(sym::Vec) => { + let TyKind::Path(vec_qpath) = &ty.kind else { + return None; + }; + let vec_generic_ty = qpath_generic_tys(vec_qpath).next()?; + let snippet = snippet_with_applicability(cx, vec_generic_ty.span, "_", applicability); + format!("[{snippet}]").into() + }, + _ if Some(id) == cx.tcx.lang_items().string() => "str".into(), _ => return None, }; Some(path) diff --git a/clippy_lints/src/undocumented_unsafe_blocks.rs b/clippy_lints/src/undocumented_unsafe_blocks.rs index 11d3f33331cb8..9d27a66a9ab83 100644 --- a/clippy_lints/src/undocumented_unsafe_blocks.rs +++ b/clippy_lints/src/undocumented_unsafe_blocks.rs @@ -215,7 +215,13 @@ impl<'tcx> LateLintPass<'tcx> for UndocumentedUnsafeBlocks { } } -fn check_has_safety_comment<'tcx>(cx: &LateContext<'tcx>, item: &hir::Item<'tcx>, (span, help_span): (Span, Span), is_doc: bool) { +#[expect(clippy::too_many_lines)] +fn check_has_safety_comment<'tcx>( + cx: &LateContext<'tcx>, + item: &hir::Item<'tcx>, + (span, help_span): (Span, Span), + is_doc: bool, +) { match &item.kind { ItemKind::Impl(Impl { of_trait: Some(of_trait), @@ -236,12 +242,14 @@ fn check_has_safety_comment<'tcx>(cx: &LateContext<'tcx>, item: &hir::Item<'tcx> ItemKind::Impl(_) => {}, // const and static items only need a safety comment if their body is an unsafe block, lint otherwise &ItemKind::Const(.., ct_rhs) => { - if !is_lint_allowed(cx, UNNECESSARY_SAFETY_COMMENT, ct_rhs.hir_id()) { + if !is_lint_allowed(cx, UNNECESSARY_SAFETY_COMMENT, ct_rhs.hir_id()) { let expr = const_item_rhs_to_expr(cx.tcx, ct_rhs); - if let Some(expr) = expr && !matches!( - expr.kind, hir::ExprKind::Block(block, _) - if block.rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) - ) { + if let Some(expr) = expr + && !matches!( + expr.kind, hir::ExprKind::Block(block, _) + if block.rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) + ) + { span_lint_and_then( cx, UNNECESSARY_SAFETY_COMMENT, @@ -256,8 +264,8 @@ fn check_has_safety_comment<'tcx>(cx: &LateContext<'tcx>, item: &hir::Item<'tcx> ); } } - } - &ItemKind::Static(.., body) => { + }, + &ItemKind::Static(.., body) => { if !is_lint_allowed(cx, UNNECESSARY_SAFETY_COMMENT, body.hir_id) { let body = cx.tcx.hir_body(body); if !matches!( diff --git a/clippy_lints/src/unnecessary_map_on_constructor.rs b/clippy_lints/src/unnecessary_map_on_constructor.rs index af9f291f5deb7..fba530d0dfcae 100644 --- a/clippy_lints/src/unnecessary_map_on_constructor.rs +++ b/clippy_lints/src/unnecessary_map_on_constructor.rs @@ -35,18 +35,17 @@ declare_lint_pass!(UnnecessaryMapOnConstructor => [UNNECESSARY_MAP_ON_CONSTRUCTO impl<'tcx> LateLintPass<'tcx> for UnnecessaryMapOnConstructor { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx rustc_hir::Expr<'tcx>) { - if expr.span.from_expansion() { - return; - } - if let hir::ExprKind::MethodCall(path, recv, [map_arg], ..) = expr.kind + if !expr.span.from_expansion() + && let hir::ExprKind::MethodCall(path, recv, [map_arg], ..) = expr.kind + && !map_arg.span.from_expansion() + && let hir::ExprKind::Path(fun) = map_arg.kind && let Some(sym::Option | sym::Result) = cx.typeck_results().expr_ty(recv).opt_diag_name(cx) { let (constructor_path, constructor_item) = if let hir::ExprKind::Call(constructor, [arg, ..]) = recv.kind && let hir::ExprKind::Path(constructor_path) = constructor.kind + && !constructor.span.from_expansion() + && !arg.span.from_expansion() { - if constructor.span.from_expansion() || arg.span.from_expansion() { - return; - } (constructor_path, arg) } else { return; @@ -67,29 +66,22 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryMapOnConstructor { _ => return, } - if let hir::ExprKind::Path(fun) = map_arg.kind { - if map_arg.span.from_expansion() { - return; - } - let mut applicability = Applicability::MachineApplicable; - let fun_snippet = snippet_with_applicability(cx, fun.span(), "_", &mut applicability); - let constructor_snippet = - snippet_with_applicability(cx, constructor_path.span(), "_", &mut applicability); - let constructor_arg_snippet = - snippet_with_applicability(cx, constructor_item.span, "_", &mut applicability); - span_lint_and_sugg( - cx, - UNNECESSARY_MAP_ON_CONSTRUCTOR, - expr.span, - format!( - "unnecessary {} on constructor {constructor_snippet}(_)", - path.ident.name - ), - "try", - format!("{constructor_snippet}({fun_snippet}({constructor_arg_snippet}))"), - applicability, - ); - } + let mut app = Applicability::MachineApplicable; + let fun_snippet = snippet_with_applicability(cx, fun.span(), "_", &mut app); + let constructor_snippet = snippet_with_applicability(cx, constructor_path.span(), "_", &mut app); + let constructor_arg_snippet = snippet_with_applicability(cx, constructor_item.span, "_", &mut app); + span_lint_and_sugg( + cx, + UNNECESSARY_MAP_ON_CONSTRUCTOR, + expr.span, + format!( + "unnecessary `{}` on constructor `{constructor_snippet}(_)`", + path.ident.name + ), + "try", + format!("{constructor_snippet}({fun_snippet}({constructor_arg_snippet}))"), + app, + ); } } } diff --git a/clippy_lints/src/unnested_or_patterns.rs b/clippy_lints/src/unnested_or_patterns.rs index bbb1b831888fd..975dd332ad064 100644 --- a/clippy_lints/src/unnested_or_patterns.rs +++ b/clippy_lints/src/unnested_or_patterns.rs @@ -10,6 +10,7 @@ use rustc_ast::mut_visit::*; use rustc_ast::{self as ast, DUMMY_NODE_ID, Mutability, Pat, PatKind, Pinnedness}; use rustc_ast_pretty::pprust; use rustc_data_structures::thin_vec::{ThinVec, thin_vec}; +use rustc_data_structures::thinvec::ExtractIf; use rustc_errors::Applicability; use rustc_lint::{EarlyContext, EarlyLintPass}; use rustc_session::impl_lint_pass; @@ -98,7 +99,7 @@ fn lint_unnested_or_patterns(cx: &EarlyContext<'_>, pat: &Pat) { return; } - let mut pat = Box::new(pat.clone()); + let mut pat = pat.clone(); // Nix all the paren patterns everywhere so that they aren't in our way. remove_all_parens(&mut pat); @@ -120,7 +121,7 @@ fn lint_unnested_or_patterns(cx: &EarlyContext<'_>, pat: &Pat) { } /// Remove all `(p)` patterns in `pat`. -fn remove_all_parens(pat: &mut Box) { +fn remove_all_parens(pat: &mut Pat) { #[derive(Default)] struct Visitor { /// If is not in the outer most pattern. This is needed to avoid removing the outermost @@ -143,7 +144,7 @@ fn remove_all_parens(pat: &mut Box) { } /// Insert parens where necessary according to Rust's precedence rules for patterns. -fn insert_necessary_parens(pat: &mut Box) { +fn insert_necessary_parens(pat: &mut Pat) { struct Visitor; impl MutVisitor for Visitor { fn visit_pat(&mut self, pat: &mut Pat) { @@ -152,7 +153,8 @@ fn insert_necessary_parens(pat: &mut Box) { let target = match &mut pat.kind { // `i @ a | b`, `box a | b`, and `& mut? a | b`. Ident(.., Some(p)) | Box(p) | Ref(p, _, _) if matches!(&p.kind, Or(ps) if ps.len() > 1) => p, - Ref(p, Pinnedness::Not, Mutability::Not) if matches!(p.kind, Ident(BindingMode::MUT, ..)) => p, // `&(mut x)` + // `&(mut x)` + Ref(p, Pinnedness::Not, Mutability::Not) if matches!(p.kind, Ident(BindingMode::MUT, ..)) => p, _ => return, }; target.kind = Paren(Box::new(take_pat(target))); @@ -163,7 +165,7 @@ fn insert_necessary_parens(pat: &mut Box) { /// Unnest or-patterns `p0 | ... | p1` in the pattern `pat`. /// For example, this would transform `Some(0) | FOO | Some(2)` into `Some(0 | 2) | FOO`. -fn unnest_or_patterns(pat: &mut Box) -> bool { +fn unnest_or_patterns(pat: &mut Pat) -> bool { struct Visitor { changed: bool, } @@ -385,15 +387,14 @@ fn take_pat(from: &mut Pat) -> Pat { /// in `tail_or` if there are any and return if there were. fn extend_with_tail_or(target: &mut Pat, tail_or: ThinVec) -> bool { fn extend(target: &mut Pat, mut tail_or: ThinVec) { - match target { - // On an existing or-pattern in the target, append to it. - Pat { kind: Or(ps), .. } => ps.append(&mut tail_or), - // Otherwise convert the target to an or-pattern. - target => { - let mut init_or = thin_vec![take_pat(target)]; - init_or.append(&mut tail_or); - target.kind = Or(init_or); - }, + // On an existing or-pattern in the target, append to it, + // otherwise convert the target to an or-pattern. + if let Or(ps) = &mut target.kind { + ps.append(&mut tail_or); + } else { + let mut init_or = thin_vec![take_pat(target)]; + init_or.append(&mut tail_or); + target.kind = Or(init_or); } } @@ -416,26 +417,14 @@ fn drain_matching( let mut tail_or = ThinVec::new(); let mut idx = 0; - // If `ThinVec` had the `drain_filter` method, this loop could be rewritten - // like so: - // - // for pat in alternatives.drain_filter(|p| { - // // Check if we should extract, but only if `idx >= start`. - // idx += 1; - // idx > start && predicate(&p.kind) - // }) { - // tail_or.push(extract(pat.into_inner().kind)); - // } - let mut i = 0; - while i < alternatives.len() { - idx += 1; + // FIXME: once `thin-vec` releases a new version, change this to `alternatives.extract_if()` + // See https://github.com/mozilla/thin-vec/issues/77 + for pat in ExtractIf::new(alternatives, |p| { // Check if we should extract, but only if `idx >= start`. - if idx > start && predicate(&alternatives[i].kind) { - let pat = alternatives.remove(i); - tail_or.push(extract(pat.kind)); - } else { - i += 1; - } + idx += 1; + idx > start && predicate(&p.kind) + }) { + tail_or.push(extract(pat.kind)); } tail_or diff --git a/clippy_lints/src/utils/author.rs b/clippy_lints/src/utils/author.rs index 57f889818be37..03cbb0311c6ca 100644 --- a/clippy_lints/src/utils/author.rs +++ b/clippy_lints/src/utils/author.rs @@ -1,5 +1,5 @@ use clippy_utils::res::MaybeQPath; -use clippy_utils::{get_attr, higher, sym}; +use clippy_utils::{get_builtin_attr, higher, sym}; use itertools::Itertools; use rustc_ast::LitIntType; use rustc_ast::ast::{LitFloatType, LitKind}; @@ -859,5 +859,5 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> { fn has_attr(cx: &LateContext<'_>, hir_id: HirId) -> bool { let attrs = cx.tcx.hir_attrs(hir_id); - get_attr(cx.sess(), attrs, sym::author).count() > 0 + get_builtin_attr(cx.sess(), attrs, sym::author).count() > 0 } diff --git a/clippy_lints/src/utils/dump_hir.rs b/clippy_lints/src/utils/dump_hir.rs index d6cf07fdaf3f8..b490866f0a11e 100644 --- a/clippy_lints/src/utils/dump_hir.rs +++ b/clippy_lints/src/utils/dump_hir.rs @@ -1,4 +1,4 @@ -use clippy_utils::{get_attr, sym}; +use clippy_utils::{get_builtin_attr, sym}; use hir::TraitItem; use rustc_hir as hir; use rustc_lint::{LateContext, LateLintPass, LintContext}; @@ -60,5 +60,5 @@ impl<'tcx> LateLintPass<'tcx> for DumpHir { fn has_attr(cx: &LateContext<'_>, hir_id: hir::HirId) -> bool { let attrs = cx.tcx.hir_attrs(hir_id); - get_attr(cx.sess(), attrs, sym::dump).count() > 0 + get_builtin_attr(cx.sess(), attrs, sym::dump).count() > 0 } diff --git a/clippy_lints/src/write.rs b/clippy_lints/src/write.rs deleted file mode 100644 index c39e4a4cc9562..0000000000000 --- a/clippy_lints/src/write.rs +++ /dev/null @@ -1,734 +0,0 @@ -use clippy_config::Conf; -use clippy_utils::diagnostics::{span_lint, span_lint_and_then}; -use clippy_utils::macros::{FormatArgsStorage, MacroCall, format_arg_removal_span, root_macro_call_first_node}; -use clippy_utils::source::{SpanRangeExt, expand_past_previous_comma}; -use clippy_utils::{is_in_test, sym}; -use rustc_ast::token::LitKind; -use rustc_ast::{ - FormatArgPosition, FormatArgPositionKind, FormatArgs, FormatArgsPiece, FormatCount, FormatOptions, - FormatPlaceholder, FormatTrait, -}; -use rustc_errors::Applicability; -use rustc_hir::{Expr, Impl, Item, ItemKind, OwnerId}; -use rustc_lint::{LateContext, LateLintPass, LintContext}; -use rustc_session::impl_lint_pass; -use rustc_span::{BytePos, Span}; - -declare_clippy_lint! { - /// ### What it does - /// This lint warns when you use `println!("")` to - /// print a newline. - /// - /// ### Why is this bad? - /// You should use `println!()`, which is simpler. - /// - /// ### Example - /// ```no_run - /// println!(""); - /// ``` - /// - /// Use instead: - /// ```no_run - /// println!(); - /// ``` - #[clippy::version = "pre 1.29.0"] - pub PRINTLN_EMPTY_STRING, - style, - "using `println!(\"\")` with an empty string" -} - -declare_clippy_lint! { - /// ### What it does - /// This lint warns when you use `print!()` with a format - /// string that ends in a newline. - /// - /// ### Why is this bad? - /// You should use `println!()` instead, which appends the - /// newline. - /// - /// ### Example - /// ```no_run - /// # let name = "World"; - /// print!("Hello {}!\n", name); - /// ``` - /// use println!() instead - /// ```no_run - /// # let name = "World"; - /// println!("Hello {}!", name); - /// ``` - #[clippy::version = "pre 1.29.0"] - pub PRINT_WITH_NEWLINE, - style, - "using `print!()` with a format string that ends in a single newline" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for printing on *stdout*. The purpose of this lint - /// is to catch debugging remnants. - /// - /// ### Why restrict this? - /// People often print on *stdout* while debugging an - /// application and might forget to remove those prints afterward. - /// - /// ### Known problems - /// Only catches `print!` and `println!` calls. - /// - /// ### Example - /// ```no_run - /// println!("Hello world!"); - /// ``` - #[clippy::version = "pre 1.29.0"] - pub PRINT_STDOUT, - restriction, - "printing on stdout" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for printing on *stderr*. The purpose of this lint - /// is to catch debugging remnants. - /// - /// ### Why restrict this? - /// People often print on *stderr* while debugging an - /// application and might forget to remove those prints afterward. - /// - /// ### Known problems - /// Only catches `eprint!` and `eprintln!` calls. - /// - /// ### Example - /// ```no_run - /// eprintln!("Hello world!"); - /// ``` - #[clippy::version = "1.50.0"] - pub PRINT_STDERR, - restriction, - "printing on stderr" -} - -declare_clippy_lint! { - /// ### What it does - /// Checks for usage of `Debug` formatting. The purpose of this - /// lint is to catch debugging remnants. - /// - /// ### Why restrict this? - /// The purpose of the `Debug` trait is to facilitate debugging Rust code, - /// and [no guarantees are made about its output][stability]. - /// It should not be used in user-facing output. - /// - /// ### Example - /// ```no_run - /// # let foo = "bar"; - /// println!("{:?}", foo); - /// ``` - /// - /// [stability]: https://doc.rust-lang.org/stable/std/fmt/trait.Debug.html#stability - #[clippy::version = "pre 1.29.0"] - pub USE_DEBUG, - restriction, - "use of `Debug`-based formatting" -} - -declare_clippy_lint! { - /// ### What it does - /// This lint warns about the use of literals as `print!`/`println!` args. - /// - /// ### Why is this bad? - /// Using literals as `println!` args is inefficient - /// (c.f., https://github.com/matthiaskrgr/rust-str-bench) and unnecessary - /// (i.e., just put the literal in the format string) - /// - /// ### Example - /// ```no_run - /// println!("{}", "foo"); - /// ``` - /// use the literal without formatting: - /// ```no_run - /// println!("foo"); - /// ``` - #[clippy::version = "pre 1.29.0"] - pub PRINT_LITERAL, - style, - "printing a literal with a format string" -} - -declare_clippy_lint! { - /// ### What it does - /// This lint warns when you use `writeln!(buf, "")` to - /// print a newline. - /// - /// ### Why is this bad? - /// You should use `writeln!(buf)`, which is simpler. - /// - /// ### Example - /// ```no_run - /// # use std::fmt::Write; - /// # let mut buf = String::new(); - /// writeln!(buf, ""); - /// ``` - /// - /// Use instead: - /// ```no_run - /// # use std::fmt::Write; - /// # let mut buf = String::new(); - /// writeln!(buf); - /// ``` - #[clippy::version = "pre 1.29.0"] - pub WRITELN_EMPTY_STRING, - style, - "using `writeln!(buf, \"\")` with an empty string" -} - -declare_clippy_lint! { - /// ### What it does - /// This lint warns when you use `write!()` with a format - /// string that - /// ends in a newline. - /// - /// ### Why is this bad? - /// You should use `writeln!()` instead, which appends the - /// newline. - /// - /// ### Example - /// ```no_run - /// # use std::fmt::Write; - /// # let mut buf = String::new(); - /// # let name = "World"; - /// write!(buf, "Hello {}!\n", name); - /// ``` - /// - /// Use instead: - /// ```no_run - /// # use std::fmt::Write; - /// # let mut buf = String::new(); - /// # let name = "World"; - /// writeln!(buf, "Hello {}!", name); - /// ``` - #[clippy::version = "pre 1.29.0"] - pub WRITE_WITH_NEWLINE, - style, - "using `write!()` with a format string that ends in a single newline" -} - -declare_clippy_lint! { - /// ### What it does - /// This lint warns about the use of literals as `write!`/`writeln!` args. - /// - /// ### Why is this bad? - /// Using literals as `writeln!` args is inefficient - /// (c.f., https://github.com/matthiaskrgr/rust-str-bench) and unnecessary - /// (i.e., just put the literal in the format string) - /// - /// ### Example - /// ```no_run - /// # use std::fmt::Write; - /// # let mut buf = String::new(); - /// writeln!(buf, "{}", "foo"); - /// ``` - /// - /// Use instead: - /// ```no_run - /// # use std::fmt::Write; - /// # let mut buf = String::new(); - /// writeln!(buf, "foo"); - /// ``` - #[clippy::version = "pre 1.29.0"] - pub WRITE_LITERAL, - style, - "writing a literal with a format string" -} - -pub struct Write { - format_args: FormatArgsStorage, - // The outermost `impl Debug` we're currently in. While we're in one, `USE_DEBUG` is deactivated - outermost_debug_impl: Option, - allow_print_in_tests: bool, -} - -impl Write { - pub fn new(conf: &'static Conf, format_args: FormatArgsStorage) -> Self { - Self { - format_args, - outermost_debug_impl: None, - allow_print_in_tests: conf.allow_print_in_tests, - } - } - - fn in_debug_impl(&self) -> bool { - self.outermost_debug_impl.is_some() - } -} - -impl_lint_pass!(Write => [ - PRINT_WITH_NEWLINE, - PRINTLN_EMPTY_STRING, - PRINT_STDOUT, - PRINT_STDERR, - USE_DEBUG, - PRINT_LITERAL, - WRITE_WITH_NEWLINE, - WRITELN_EMPTY_STRING, - WRITE_LITERAL, -]); - -impl<'tcx> LateLintPass<'tcx> for Write { - fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { - // Only check for `impl Debug`s if we're not already in one - if self.outermost_debug_impl.is_none() && is_debug_impl(cx, item) { - self.outermost_debug_impl = Some(item.owner_id); - } - } - - fn check_item_post(&mut self, _cx: &LateContext<'_>, item: &Item<'_>) { - // Only clear `self.outermost_debug_impl` if we're escaping the _outermost_ debug impl - if self.outermost_debug_impl == Some(item.owner_id) { - self.outermost_debug_impl = None; - } - } - - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - let Some(macro_call) = root_macro_call_first_node(cx, expr) else { - return; - }; - let Some(diag_name) = cx.tcx.get_diagnostic_name(macro_call.def_id) else { - return; - }; - let Some(name) = diag_name.as_str().strip_suffix("_macro") else { - return; - }; - - let is_build_script = cx - .sess() - .opts - .crate_name - .as_ref() - .is_some_and(|crate_name| crate_name == "build_script_build"); - - let allowed_in_tests = self.allow_print_in_tests && is_in_test(cx.tcx, expr.hir_id); - match diag_name { - sym::print_macro | sym::println_macro if !allowed_in_tests => { - if !is_build_script { - span_lint(cx, PRINT_STDOUT, macro_call.span, format!("use of `{name}!`")); - } - }, - sym::eprint_macro | sym::eprintln_macro if !allowed_in_tests => { - span_lint(cx, PRINT_STDERR, macro_call.span, format!("use of `{name}!`")); - }, - sym::write_macro | sym::writeln_macro => {}, - _ => return, - } - - if let Some(format_args) = self.format_args.get(cx, expr, macro_call.expn) { - // ignore `writeln!(w)` and `write!(v, some_macro!())` - if format_args.span.from_expansion() { - return; - } - - match diag_name { - sym::print_macro | sym::eprint_macro | sym::write_macro => { - check_newline(cx, format_args, ¯o_call, name); - }, - sym::println_macro | sym::eprintln_macro | sym::writeln_macro => { - check_empty_string(cx, format_args, ¯o_call, name); - }, - _ => {}, - } - - check_literal(cx, format_args, name); - - if !self.in_debug_impl() { - for piece in &format_args.template { - if let &FormatArgsPiece::Placeholder(FormatPlaceholder { - span: Some(span), - format_trait: FormatTrait::Debug, - .. - }) = piece - { - span_lint(cx, USE_DEBUG, span, "use of `Debug`-based formatting"); - } - } - } - } - } -} - -fn is_debug_impl(cx: &LateContext<'_>, item: &Item<'_>) -> bool { - if let ItemKind::Impl(Impl { - of_trait: Some(of_trait), - .. - }) = &item.kind - && let Some(trait_id) = of_trait.trait_ref.trait_def_id() - { - cx.tcx.is_diagnostic_item(sym::Debug, trait_id) - } else { - false - } -} - -fn check_newline(cx: &LateContext<'_>, format_args: &FormatArgs, macro_call: &MacroCall, name: &str) { - let Some(&FormatArgsPiece::Literal(last)) = format_args.template.last() else { - return; - }; - - let count_vertical_whitespace = || { - format_args - .template - .iter() - .filter_map(|piece| match piece { - FormatArgsPiece::Literal(literal) => Some(literal), - FormatArgsPiece::Placeholder(_) => None, - }) - .flat_map(|literal| literal.as_str().chars()) - .filter(|ch| matches!(ch, '\r' | '\n')) - .count() - }; - - if last.as_str().ends_with('\n') - // ignore format strings with other internal vertical whitespace - && count_vertical_whitespace() == 1 - { - let mut format_string_span = format_args.span; - - let lint = if name == "write" { - format_string_span = expand_past_previous_comma(cx, format_string_span); - - WRITE_WITH_NEWLINE - } else { - PRINT_WITH_NEWLINE - }; - - span_lint_and_then( - cx, - lint, - macro_call.span, - format!("using `{name}!()` with a format string that ends in a single newline"), - |diag| { - let name_span = cx.sess().source_map().span_until_char(macro_call.span, '!'); - let Some(format_snippet) = format_string_span.get_source_text(cx) else { - return; - }; - - if format_args.template.len() == 1 && last == sym::LF { - // print!("\n"), write!(f, "\n") - - diag.multipart_suggestion( - format!("use `{name}ln!` instead"), - vec![(name_span, format!("{name}ln")), (format_string_span, String::new())], - Applicability::MachineApplicable, - ); - } else if format_snippet.ends_with("\\n\"") { - // print!("...\n"), write!(f, "...\n") - - let hi = format_string_span.hi(); - let newline_span = format_string_span.with_lo(hi - BytePos(3)).with_hi(hi - BytePos(1)); - - diag.multipart_suggestion( - format!("use `{name}ln!` instead"), - vec![(name_span, format!("{name}ln")), (newline_span, String::new())], - Applicability::MachineApplicable, - ); - } - }, - ); - } -} - -fn check_empty_string(cx: &LateContext<'_>, format_args: &FormatArgs, macro_call: &MacroCall, name: &str) { - if let [FormatArgsPiece::Literal(sym::LF)] = &format_args.template[..] { - let mut span = format_args.span; - - let lint = if name == "writeln" { - span = expand_past_previous_comma(cx, span); - - WRITELN_EMPTY_STRING - } else { - PRINTLN_EMPTY_STRING - }; - - span_lint_and_then( - cx, - lint, - macro_call.span, - format!("empty string literal in `{name}!`"), - |diag| { - diag.span_suggestion( - span, - "remove the empty string", - String::new(), - Applicability::MachineApplicable, - ); - }, - ); - } -} - -fn check_literal(cx: &LateContext<'_>, format_args: &FormatArgs, name: &str) { - let arg_index = |argument: &FormatArgPosition| argument.index.unwrap_or_else(|pos| pos); - - let lint_name = if name.starts_with("write") { - WRITE_LITERAL - } else { - PRINT_LITERAL - }; - - let mut counts = vec![0u32; format_args.arguments.all_args().len()]; - for piece in &format_args.template { - if let FormatArgsPiece::Placeholder(placeholder) = piece { - counts[arg_index(&placeholder.argument)] += 1; - } - } - - let mut suggestion: Vec<(Span, String)> = vec![]; - // holds index of replaced positional arguments; used to decrement the index of the remaining - // positional arguments. - let mut replaced_position: Vec = vec![]; - let mut sug_span: Option = None; - - for piece in &format_args.template { - if let FormatArgsPiece::Placeholder(FormatPlaceholder { - argument, - span: Some(placeholder_span), - format_trait: FormatTrait::Display, - format_options, - }) = piece - && *format_options == FormatOptions::default() - && let index = arg_index(argument) - && counts[index] == 1 - && let Some(arg) = format_args.arguments.by_index(index) - && let rustc_ast::ExprKind::Lit(lit) = &arg.expr.kind - && !arg.expr.span.from_expansion() - && let Some(value_string) = arg.expr.span.get_source_text(cx) - { - let (replacement, replace_raw) = match lit.kind { - LitKind::Str | LitKind::StrRaw(_) => match extract_str_literal(&value_string) { - Some(extracted) => extracted, - None => return, - }, - LitKind::Char => ( - match lit.symbol { - sym::DOUBLE_QUOTE => "\\\"", - sym::BACKSLASH_SINGLE_QUOTE => "'", - _ => match value_string.strip_prefix('\'').and_then(|s| s.strip_suffix('\'')) { - Some(stripped) => stripped, - None => return, - }, - } - .to_string(), - false, - ), - LitKind::Bool => (lit.symbol.to_string(), false), - _ => continue, - }; - - let Some(format_string_snippet) = format_args.span.get_source_text(cx) else { - continue; - }; - let format_string_is_raw = format_string_snippet.starts_with('r'); - - let replacement = match (format_string_is_raw, replace_raw) { - (false, false) => Some(replacement), - (false, true) => Some(replacement.replace('\\', "\\\\").replace('"', "\\\"")), - (true, false) => match conservative_unescape(&replacement) { - Ok(unescaped) => Some(unescaped), - Err(UnescapeErr::Lint) => None, - Err(UnescapeErr::Ignore) => continue, - }, - (true, true) => { - if replacement.contains(['#', '"']) { - None - } else { - Some(replacement) - } - }, - }; - - sug_span = Some(sug_span.unwrap_or(arg.expr.span).to(arg.expr.span)); - - if let Some((_, index)) = format_arg_piece_span(piece) { - replaced_position.push(index); - } - - if let Some(replacement) = replacement - // `format!("{}", "a")`, `format!("{named}", named = "b") - // ~~~~~ ~~~~~~~~~~~~~ - && let Some(removal_span) = format_arg_removal_span(format_args, index) - { - let replacement = escape_braces(&replacement, !format_string_is_raw && !replace_raw); - suggestion.push((*placeholder_span, replacement)); - suggestion.push((removal_span, String::new())); - } - } - } - - // Decrement the index of the remaining by the number of replaced positional arguments - if !suggestion.is_empty() { - for piece in &format_args.template { - relocalize_format_args_indexes(piece, &mut suggestion, &replaced_position); - } - } - - if let Some(span) = sug_span { - span_lint_and_then(cx, lint_name, span, "literal with an empty format string", |diag| { - if !suggestion.is_empty() { - diag.multipart_suggestion("try", suggestion, Applicability::MachineApplicable); - } - }); - } -} - -/// Extract Span and its index from the given `piece` -fn format_arg_piece_span(piece: &FormatArgsPiece) -> Option<(Span, usize)> { - match piece { - FormatArgsPiece::Placeholder(FormatPlaceholder { - argument: FormatArgPosition { index: Ok(index), .. }, - span: Some(span), - .. - }) => Some((*span, *index)), - _ => None, - } -} - -/// Relocalizes the indexes of positional arguments in the format string -fn relocalize_format_args_indexes( - piece: &FormatArgsPiece, - suggestion: &mut Vec<(Span, String)>, - replaced_position: &[usize], -) { - if let FormatArgsPiece::Placeholder(FormatPlaceholder { - argument: - FormatArgPosition { - index: Ok(index), - // Only consider positional arguments - kind: FormatArgPositionKind::Number, - span: Some(span), - }, - format_options, - .. - }) = piece - { - if suggestion.iter().any(|(s, _)| s.overlaps(*span)) { - // If the span is already in the suggestion, we don't need to process it again - return; - } - - // lambda to get the decremented index based on the replaced positions - let decremented_index = |index: usize| -> usize { - let decrement = replaced_position.iter().filter(|&&i| i < index).count(); - index - decrement - }; - - suggestion.push((*span, decremented_index(*index).to_string())); - - // If there are format options, we need to handle them as well - if *format_options != FormatOptions::default() { - // lambda to process width and precision format counts and add them to the suggestion - let mut process_format_count = |count: &Option, formatter: &dyn Fn(usize) -> String| { - if let Some(FormatCount::Argument(FormatArgPosition { - index: Ok(format_arg_index), - kind: FormatArgPositionKind::Number, - span: Some(format_arg_span), - })) = count - { - suggestion.push((*format_arg_span, formatter(decremented_index(*format_arg_index)))); - } - }; - - process_format_count(&format_options.width, &|index: usize| format!("{index}$")); - process_format_count(&format_options.precision, &|index: usize| format!(".{index}$")); - } - } -} - -/// Removes the raw marker, `#`s and quotes from a str, and returns if the literal is raw -/// -/// `r#"a"#` -> (`a`, true) -/// -/// `"b"` -> (`b`, false) -fn extract_str_literal(literal: &str) -> Option<(String, bool)> { - let (literal, raw) = match literal.strip_prefix('r') { - Some(stripped) => (stripped.trim_matches('#'), true), - None => (literal, false), - }; - - Some((literal.strip_prefix('"')?.strip_suffix('"')?.to_string(), raw)) -} - -enum UnescapeErr { - /// Should still be linted, can be manually resolved by author, e.g. - /// - /// ```ignore - /// print!(r"{}", '"'); - /// ``` - Lint, - /// Should not be linted, e.g. - /// - /// ```ignore - /// print!(r"{}", '\r'); - /// ``` - Ignore, -} - -/// Unescape a normal string into a raw string -fn conservative_unescape(literal: &str) -> Result { - let mut unescaped = String::with_capacity(literal.len()); - let mut chars = literal.chars(); - let mut err = false; - - while let Some(ch) = chars.next() { - match ch { - '#' => err = true, - '\\' => match chars.next() { - Some('\\') => unescaped.push('\\'), - Some('"') => err = true, - _ => return Err(UnescapeErr::Ignore), - }, - _ => unescaped.push(ch), - } - } - - if err { Err(UnescapeErr::Lint) } else { Ok(unescaped) } -} - -/// Replaces `{` with `{{` and `}` with `}}`. If `preserve_unicode_escapes` is `true` the braces in -/// `\u{xxxx}` are left unmodified -#[expect(clippy::match_same_arms)] -fn escape_braces(literal: &str, preserve_unicode_escapes: bool) -> String { - #[derive(Clone, Copy)] - enum State { - Normal, - Backslash, - UnicodeEscape, - } - - let mut escaped = String::with_capacity(literal.len()); - let mut state = State::Normal; - - for ch in literal.chars() { - state = match (ch, state) { - // Escape braces outside of unicode escapes by doubling them up - ('{' | '}', State::Normal) => { - escaped.push(ch); - State::Normal - }, - // If `preserve_unicode_escapes` isn't enabled stay in `State::Normal`, otherwise: - // - // \u{aaaa} \\ \x01 - // ^ ^ ^ - ('\\', State::Normal) if preserve_unicode_escapes => State::Backslash, - // \u{aaaa} - // ^ - ('u', State::Backslash) => State::UnicodeEscape, - // \xAA \\ - // ^ ^ - (_, State::Backslash) => State::Normal, - // \u{aaaa} - // ^ - ('}', State::UnicodeEscape) => State::Normal, - _ => state, - }; - - escaped.push(ch); - } - - escaped -} diff --git a/clippy_lints/src/write/empty_string.rs b/clippy_lints/src/write/empty_string.rs new file mode 100644 index 0000000000000..e7eb99eb34ec3 --- /dev/null +++ b/clippy_lints/src/write/empty_string.rs @@ -0,0 +1,38 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::macros::MacroCall; +use clippy_utils::source::expand_past_previous_comma; +use clippy_utils::sym; +use rustc_ast::{FormatArgs, FormatArgsPiece}; +use rustc_errors::Applicability; +use rustc_lint::LateContext; + +use super::{PRINTLN_EMPTY_STRING, WRITELN_EMPTY_STRING}; + +pub(super) fn check(cx: &LateContext<'_>, format_args: &FormatArgs, macro_call: &MacroCall, name: &str) { + if let [FormatArgsPiece::Literal(sym::LF)] = &format_args.template[..] { + let mut span = format_args.span; + + let lint = if name == "writeln" { + span = expand_past_previous_comma(cx, span); + + WRITELN_EMPTY_STRING + } else { + PRINTLN_EMPTY_STRING + }; + + span_lint_and_then( + cx, + lint, + macro_call.span, + format!("empty string literal in `{name}!`"), + |diag| { + diag.span_suggestion( + span, + "remove the empty string", + String::new(), + Applicability::MachineApplicable, + ); + }, + ); + } +} diff --git a/clippy_lints/src/write/literal.rs b/clippy_lints/src/write/literal.rs new file mode 100644 index 0000000000000..699ac7ea7a5cd --- /dev/null +++ b/clippy_lints/src/write/literal.rs @@ -0,0 +1,285 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::macros::format_arg_removal_span; +use clippy_utils::source::SpanRangeExt; +use clippy_utils::sym; +use rustc_ast::token::LitKind; +use rustc_ast::{ + FormatArgPosition, FormatArgPositionKind, FormatArgs, FormatArgsPiece, FormatCount, FormatOptions, + FormatPlaceholder, FormatTrait, +}; +use rustc_errors::Applicability; +use rustc_lint::LateContext; +use rustc_span::Span; + +use super::{PRINT_LITERAL, WRITE_LITERAL}; + +pub(super) fn check(cx: &LateContext<'_>, format_args: &FormatArgs, name: &str) { + let arg_index = |argument: &FormatArgPosition| argument.index.unwrap_or_else(|pos| pos); + + let lint_name = if name.starts_with("write") { + WRITE_LITERAL + } else { + PRINT_LITERAL + }; + + let mut counts = vec![0u32; format_args.arguments.all_args().len()]; + for piece in &format_args.template { + if let FormatArgsPiece::Placeholder(placeholder) = piece { + counts[arg_index(&placeholder.argument)] += 1; + } + } + + let mut suggestion: Vec<(Span, String)> = vec![]; + // holds index of replaced positional arguments; used to decrement the index of the remaining + // positional arguments. + let mut replaced_position: Vec = vec![]; + let mut sug_span: Option = None; + + for piece in &format_args.template { + if let FormatArgsPiece::Placeholder(FormatPlaceholder { + argument, + span: Some(placeholder_span), + format_trait: FormatTrait::Display, + format_options, + }) = piece + && *format_options == FormatOptions::default() + && let index = arg_index(argument) + && counts[index] == 1 + && let Some(arg) = format_args.arguments.by_index(index) + && let rustc_ast::ExprKind::Lit(lit) = &arg.expr.kind + && !arg.expr.span.from_expansion() + && let Some(value_string) = arg.expr.span.get_source_text(cx) + { + let (replacement, replace_raw) = match lit.kind { + LitKind::Str | LitKind::StrRaw(_) => match extract_str_literal(&value_string) { + Some(extracted) => extracted, + None => return, + }, + LitKind::Char => ( + match lit.symbol { + sym::DOUBLE_QUOTE => "\\\"", + sym::BACKSLASH_SINGLE_QUOTE => "'", + _ => match value_string.strip_prefix('\'').and_then(|s| s.strip_suffix('\'')) { + Some(stripped) => stripped, + None => return, + }, + } + .to_string(), + false, + ), + LitKind::Bool => (lit.symbol.to_string(), false), + _ => continue, + }; + + let Some(format_string_snippet) = format_args.span.get_source_text(cx) else { + continue; + }; + let format_string_is_raw = format_string_snippet.starts_with('r'); + + let replacement = match (format_string_is_raw, replace_raw) { + (false, false) => Some(replacement), + (false, true) => Some(replacement.replace('\\', "\\\\").replace('"', "\\\"")), + (true, false) => match conservative_unescape(&replacement) { + Ok(unescaped) => Some(unescaped), + Err(UnescapeErr::Lint) => None, + Err(UnescapeErr::Ignore) => continue, + }, + (true, true) => { + if replacement.contains(['#', '"']) { + None + } else { + Some(replacement) + } + }, + }; + + sug_span = Some(sug_span.unwrap_or(arg.expr.span).to(arg.expr.span)); + + if let Some((_, index)) = format_arg_piece_span(piece) { + replaced_position.push(index); + } + + if let Some(replacement) = replacement + // `format!("{}", "a")`, `format!("{named}", named = "b") + // ~~~~~ ~~~~~~~~~~~~~ + && let Some(removal_span) = format_arg_removal_span(format_args, index) + { + let replacement = escape_braces(&replacement, !format_string_is_raw && !replace_raw); + suggestion.push((*placeholder_span, replacement)); + suggestion.push((removal_span, String::new())); + } + } + } + + // Decrement the index of the remaining by the number of replaced positional arguments + if !suggestion.is_empty() { + for piece in &format_args.template { + relocalize_format_args_indexes(piece, &mut suggestion, &replaced_position); + } + } + + if let Some(span) = sug_span { + span_lint_and_then(cx, lint_name, span, "literal with an empty format string", |diag| { + if !suggestion.is_empty() { + diag.multipart_suggestion("try", suggestion, Applicability::MachineApplicable); + } + }); + } +} + +/// Extract Span and its index from the given `piece` +fn format_arg_piece_span(piece: &FormatArgsPiece) -> Option<(Span, usize)> { + match piece { + FormatArgsPiece::Placeholder(FormatPlaceholder { + argument: FormatArgPosition { index: Ok(index), .. }, + span: Some(span), + .. + }) => Some((*span, *index)), + _ => None, + } +} + +/// Relocalizes the indexes of positional arguments in the format string +fn relocalize_format_args_indexes( + piece: &FormatArgsPiece, + suggestion: &mut Vec<(Span, String)>, + replaced_position: &[usize], +) { + if let FormatArgsPiece::Placeholder(FormatPlaceholder { + argument: + FormatArgPosition { + index: Ok(index), + // Only consider positional arguments + kind: FormatArgPositionKind::Number, + span: Some(span), + }, + format_options, + .. + }) = piece + { + if suggestion.iter().any(|(s, _)| s.overlaps(*span)) { + // If the span is already in the suggestion, we don't need to process it again + return; + } + + // lambda to get the decremented index based on the replaced positions + let decremented_index = |index: usize| -> usize { + let decrement = replaced_position.iter().filter(|&&i| i < index).count(); + index - decrement + }; + + suggestion.push((*span, decremented_index(*index).to_string())); + + // If there are format options, we need to handle them as well + if *format_options != FormatOptions::default() { + // lambda to process width and precision format counts and add them to the suggestion + let mut process_format_count = |count: &Option, formatter: &dyn Fn(usize) -> String| { + if let Some(FormatCount::Argument(FormatArgPosition { + index: Ok(format_arg_index), + kind: FormatArgPositionKind::Number, + span: Some(format_arg_span), + })) = count + { + suggestion.push((*format_arg_span, formatter(decremented_index(*format_arg_index)))); + } + }; + + process_format_count(&format_options.width, &|index: usize| format!("{index}$")); + process_format_count(&format_options.precision, &|index: usize| format!(".{index}$")); + } + } +} + +/// Removes the raw marker, `#`s and quotes from a str, and returns if the literal is raw +/// +/// `r#"a"#` -> (`a`, true) +/// +/// `"b"` -> (`b`, false) +fn extract_str_literal(literal: &str) -> Option<(String, bool)> { + let (literal, raw) = match literal.strip_prefix('r') { + Some(stripped) => (stripped.trim_matches('#'), true), + None => (literal, false), + }; + + Some((literal.strip_prefix('"')?.strip_suffix('"')?.to_string(), raw)) +} + +enum UnescapeErr { + /// Should still be linted, can be manually resolved by author, e.g. + /// + /// ```ignore + /// print!(r"{}", '"'); + /// ``` + Lint, + /// Should not be linted, e.g. + /// + /// ```ignore + /// print!(r"{}", '\r'); + /// ``` + Ignore, +} + +/// Unescape a normal string into a raw string +fn conservative_unescape(literal: &str) -> Result { + let mut unescaped = String::with_capacity(literal.len()); + let mut chars = literal.chars(); + let mut err = false; + + while let Some(ch) = chars.next() { + match ch { + '#' => err = true, + '\\' => match chars.next() { + Some('\\') => unescaped.push('\\'), + Some('"') => err = true, + _ => return Err(UnescapeErr::Ignore), + }, + _ => unescaped.push(ch), + } + } + + if err { Err(UnescapeErr::Lint) } else { Ok(unescaped) } +} + +/// Replaces `{` with `{{` and `}` with `}}`. If `preserve_unicode_escapes` is `true` the braces +/// in `\u{xxxx}` are left unmodified +#[expect(clippy::match_same_arms)] +fn escape_braces(literal: &str, preserve_unicode_escapes: bool) -> String { + #[derive(Clone, Copy)] + enum State { + Normal, + Backslash, + UnicodeEscape, + } + + let mut escaped = String::with_capacity(literal.len()); + let mut state = State::Normal; + + for ch in literal.chars() { + state = match (ch, state) { + // Escape braces outside of unicode escapes by doubling them up + ('{' | '}', State::Normal) => { + escaped.push(ch); + State::Normal + }, + // If `preserve_unicode_escapes` isn't enabled stay in `State::Normal`, otherwise: + // + // \u{aaaa} \\ \x01 + // ^ ^ ^ + ('\\', State::Normal) if preserve_unicode_escapes => State::Backslash, + // \u{aaaa} + // ^ + ('u', State::Backslash) => State::UnicodeEscape, + // \xAA \\ + // ^ ^ + (_, State::Backslash) => State::Normal, + // \u{aaaa} + // ^ + ('}', State::UnicodeEscape) => State::Normal, + _ => state, + }; + + escaped.push(ch); + } + + escaped +} diff --git a/clippy_lints/src/write/mod.rs b/clippy_lints/src/write/mod.rs new file mode 100644 index 0000000000000..c42c047745bbd --- /dev/null +++ b/clippy_lints/src/write/mod.rs @@ -0,0 +1,354 @@ +use clippy_config::Conf; +use clippy_utils::diagnostics::span_lint; +use clippy_utils::macros::{FormatArgsStorage, root_macro_call_first_node}; +use clippy_utils::{is_in_test, sym}; +use rustc_hir::{Expr, Impl, Item, ItemKind, OwnerId}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_session::impl_lint_pass; + +mod empty_string; +mod literal; +mod use_debug; +mod with_newline; + +declare_clippy_lint! { + /// ### What it does + /// This lint warns when you use `println!("")` to + /// print a newline. + /// + /// ### Why is this bad? + /// You should use `println!()`, which is simpler. + /// + /// ### Example + /// ```no_run + /// println!(""); + /// ``` + /// + /// Use instead: + /// ```no_run + /// println!(); + /// ``` + #[clippy::version = "pre 1.29.0"] + pub PRINTLN_EMPTY_STRING, + style, + "using `println!(\"\")` with an empty string" +} + +declare_clippy_lint! { + /// ### What it does + /// This lint warns when you use `print!()` with a format + /// string that ends in a newline. + /// + /// ### Why is this bad? + /// You should use `println!()` instead, which appends the + /// newline. + /// + /// ### Example + /// ```no_run + /// # let name = "World"; + /// print!("Hello {}!\n", name); + /// ``` + /// use println!() instead + /// ```no_run + /// # let name = "World"; + /// println!("Hello {}!", name); + /// ``` + #[clippy::version = "pre 1.29.0"] + pub PRINT_WITH_NEWLINE, + style, + "using `print!()` with a format string that ends in a single newline" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for printing on *stdout*. The purpose of this lint + /// is to catch debugging remnants. + /// + /// ### Why restrict this? + /// People often print on *stdout* while debugging an + /// application and might forget to remove those prints afterward. + /// + /// ### Known problems + /// Only catches `print!` and `println!` calls. + /// + /// ### Example + /// ```no_run + /// println!("Hello world!"); + /// ``` + #[clippy::version = "pre 1.29.0"] + pub PRINT_STDOUT, + restriction, + "printing on stdout" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for printing on *stderr*. The purpose of this lint + /// is to catch debugging remnants. + /// + /// ### Why restrict this? + /// People often print on *stderr* while debugging an + /// application and might forget to remove those prints afterward. + /// + /// ### Known problems + /// Only catches `eprint!` and `eprintln!` calls. + /// + /// ### Example + /// ```no_run + /// eprintln!("Hello world!"); + /// ``` + #[clippy::version = "1.50.0"] + pub PRINT_STDERR, + restriction, + "printing on stderr" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `Debug` formatting. The purpose of this + /// lint is to catch debugging remnants. + /// + /// ### Why restrict this? + /// The purpose of the `Debug` trait is to facilitate debugging Rust code, + /// and [no guarantees are made about its output][stability]. + /// It should not be used in user-facing output. + /// + /// ### Example + /// ```no_run + /// # let foo = "bar"; + /// println!("{:?}", foo); + /// ``` + /// + /// [stability]: https://doc.rust-lang.org/stable/std/fmt/trait.Debug.html#stability + #[clippy::version = "pre 1.29.0"] + pub USE_DEBUG, + restriction, + "use of `Debug`-based formatting" +} + +declare_clippy_lint! { + /// ### What it does + /// This lint warns about the use of literals as `print!`/`println!` args. + /// + /// ### Why is this bad? + /// Using literals as `println!` args is inefficient + /// (c.f., https://github.com/matthiaskrgr/rust-str-bench) and unnecessary + /// (i.e., just put the literal in the format string) + /// + /// ### Example + /// ```no_run + /// println!("{}", "foo"); + /// ``` + /// use the literal without formatting: + /// ```no_run + /// println!("foo"); + /// ``` + #[clippy::version = "pre 1.29.0"] + pub PRINT_LITERAL, + style, + "printing a literal with a format string" +} + +declare_clippy_lint! { + /// ### What it does + /// This lint warns when you use `writeln!(buf, "")` to + /// print a newline. + /// + /// ### Why is this bad? + /// You should use `writeln!(buf)`, which is simpler. + /// + /// ### Example + /// ```no_run + /// # use std::fmt::Write; + /// # let mut buf = String::new(); + /// writeln!(buf, ""); + /// ``` + /// + /// Use instead: + /// ```no_run + /// # use std::fmt::Write; + /// # let mut buf = String::new(); + /// writeln!(buf); + /// ``` + #[clippy::version = "pre 1.29.0"] + pub WRITELN_EMPTY_STRING, + style, + "using `writeln!(buf, \"\")` with an empty string" +} + +declare_clippy_lint! { + /// ### What it does + /// This lint warns when you use `write!()` with a format + /// string that + /// ends in a newline. + /// + /// ### Why is this bad? + /// You should use `writeln!()` instead, which appends the + /// newline. + /// + /// ### Example + /// ```no_run + /// # use std::fmt::Write; + /// # let mut buf = String::new(); + /// # let name = "World"; + /// write!(buf, "Hello {}!\n", name); + /// ``` + /// + /// Use instead: + /// ```no_run + /// # use std::fmt::Write; + /// # let mut buf = String::new(); + /// # let name = "World"; + /// writeln!(buf, "Hello {}!", name); + /// ``` + #[clippy::version = "pre 1.29.0"] + pub WRITE_WITH_NEWLINE, + style, + "using `write!()` with a format string that ends in a single newline" +} + +declare_clippy_lint! { + /// ### What it does + /// This lint warns about the use of literals as `write!`/`writeln!` args. + /// + /// ### Why is this bad? + /// Using literals as `writeln!` args is inefficient + /// (c.f., https://github.com/matthiaskrgr/rust-str-bench) and unnecessary + /// (i.e., just put the literal in the format string) + /// + /// ### Example + /// ```no_run + /// # use std::fmt::Write; + /// # let mut buf = String::new(); + /// writeln!(buf, "{}", "foo"); + /// ``` + /// + /// Use instead: + /// ```no_run + /// # use std::fmt::Write; + /// # let mut buf = String::new(); + /// writeln!(buf, "foo"); + /// ``` + #[clippy::version = "pre 1.29.0"] + pub WRITE_LITERAL, + style, + "writing a literal with a format string" +} + +pub struct Write { + format_args: FormatArgsStorage, + // The outermost `impl Debug` we're currently in. While we're in one, `USE_DEBUG` is deactivated + outermost_debug_impl: Option, + allow_print_in_tests: bool, +} + +impl Write { + pub fn new(conf: &'static Conf, format_args: FormatArgsStorage) -> Self { + Self { + format_args, + outermost_debug_impl: None, + allow_print_in_tests: conf.allow_print_in_tests, + } + } + + fn in_debug_impl(&self) -> bool { + self.outermost_debug_impl.is_some() + } +} + +impl_lint_pass!(Write => [ + PRINT_WITH_NEWLINE, + PRINTLN_EMPTY_STRING, + PRINT_STDOUT, + PRINT_STDERR, + USE_DEBUG, + PRINT_LITERAL, + WRITE_WITH_NEWLINE, + WRITELN_EMPTY_STRING, + WRITE_LITERAL, +]); + +impl<'tcx> LateLintPass<'tcx> for Write { + fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { + // Only check for `impl Debug`s if we're not already in one + if self.outermost_debug_impl.is_none() && is_debug_impl(cx, item) { + self.outermost_debug_impl = Some(item.owner_id); + } + } + + fn check_item_post(&mut self, _cx: &LateContext<'_>, item: &Item<'_>) { + // Only clear `self.outermost_debug_impl` if we're escaping the _outermost_ debug impl + if self.outermost_debug_impl == Some(item.owner_id) { + self.outermost_debug_impl = None; + } + } + + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + let Some(macro_call) = root_macro_call_first_node(cx, expr) else { + return; + }; + let Some(diag_name) = cx.tcx.get_diagnostic_name(macro_call.def_id) else { + return; + }; + let Some(name) = diag_name.as_str().strip_suffix("_macro") else { + return; + }; + + let is_build_script = cx + .sess() + .opts + .crate_name + .as_ref() + .is_some_and(|crate_name| crate_name == "build_script_build"); + + let allowed_in_tests = self.allow_print_in_tests && is_in_test(cx.tcx, expr.hir_id); + match diag_name { + sym::print_macro | sym::println_macro if !allowed_in_tests => { + if !is_build_script { + span_lint(cx, PRINT_STDOUT, macro_call.span, format!("use of `{name}!`")); + } + }, + sym::eprint_macro | sym::eprintln_macro if !allowed_in_tests => { + span_lint(cx, PRINT_STDERR, macro_call.span, format!("use of `{name}!`")); + }, + sym::write_macro | sym::writeln_macro => {}, + _ => return, + } + + if let Some(format_args) = self.format_args.get(cx, expr, macro_call.expn) { + // ignore `writeln!(w)` and `write!(v, some_macro!())` + if format_args.span.from_expansion() { + return; + } + + match diag_name { + sym::print_macro | sym::eprint_macro | sym::write_macro => { + with_newline::check(cx, format_args, ¯o_call, name); + }, + sym::println_macro | sym::eprintln_macro | sym::writeln_macro => { + empty_string::check(cx, format_args, ¯o_call, name); + }, + _ => {}, + } + + literal::check(cx, format_args, name); + + if !self.in_debug_impl() { + use_debug::check(cx, format_args); + } + } + } +} + +fn is_debug_impl(cx: &LateContext<'_>, item: &Item<'_>) -> bool { + if let ItemKind::Impl(Impl { + of_trait: Some(of_trait), + .. + }) = &item.kind + && let Some(trait_id) = of_trait.trait_ref.trait_def_id() + { + cx.tcx.is_diagnostic_item(sym::Debug, trait_id) + } else { + false + } +} diff --git a/clippy_lints/src/write/use_debug.rs b/clippy_lints/src/write/use_debug.rs new file mode 100644 index 0000000000000..75dddeb5d2a72 --- /dev/null +++ b/clippy_lints/src/write/use_debug.rs @@ -0,0 +1,18 @@ +use clippy_utils::diagnostics::span_lint; +use rustc_ast::{FormatArgs, FormatArgsPiece, FormatPlaceholder, FormatTrait}; +use rustc_lint::LateContext; + +use super::USE_DEBUG; + +pub(super) fn check(cx: &LateContext<'_>, format_args: &FormatArgs) { + for piece in &format_args.template { + if let &FormatArgsPiece::Placeholder(FormatPlaceholder { + span: Some(span), + format_trait: FormatTrait::Debug, + .. + }) = piece + { + span_lint(cx, USE_DEBUG, span, "use of `Debug`-based formatting"); + } + } +} diff --git a/clippy_lints/src/write/with_newline.rs b/clippy_lints/src/write/with_newline.rs new file mode 100644 index 0000000000000..e4b51da3cadcf --- /dev/null +++ b/clippy_lints/src/write/with_newline.rs @@ -0,0 +1,78 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::macros::MacroCall; +use clippy_utils::source::{SpanRangeExt, expand_past_previous_comma}; +use clippy_utils::sym; +use rustc_ast::{FormatArgs, FormatArgsPiece}; +use rustc_errors::Applicability; +use rustc_lint::{LateContext, LintContext}; +use rustc_span::BytePos; + +use super::{PRINT_WITH_NEWLINE, WRITE_WITH_NEWLINE}; + +pub(super) fn check(cx: &LateContext<'_>, format_args: &FormatArgs, macro_call: &MacroCall, name: &str) { + let Some(&FormatArgsPiece::Literal(last)) = format_args.template.last() else { + return; + }; + + let count_vertical_whitespace = || { + format_args + .template + .iter() + .filter_map(|piece| match piece { + FormatArgsPiece::Literal(literal) => Some(literal), + FormatArgsPiece::Placeholder(_) => None, + }) + .flat_map(|literal| literal.as_str().chars()) + .filter(|ch| matches!(ch, '\r' | '\n')) + .count() + }; + + if last.as_str().ends_with('\n') + // ignore format strings with other internal vertical whitespace + && count_vertical_whitespace() == 1 + { + let mut format_string_span = format_args.span; + + let lint = if name == "write" { + format_string_span = expand_past_previous_comma(cx, format_string_span); + + WRITE_WITH_NEWLINE + } else { + PRINT_WITH_NEWLINE + }; + + span_lint_and_then( + cx, + lint, + macro_call.span, + format!("using `{name}!()` with a format string that ends in a single newline"), + |diag| { + let name_span = cx.sess().source_map().span_until_char(macro_call.span, '!'); + let Some(format_snippet) = format_string_span.get_source_text(cx) else { + return; + }; + + if format_args.template.len() == 1 && last == sym::LF { + // print!("\n"), write!(f, "\n") + + diag.multipart_suggestion( + format!("use `{name}ln!` instead"), + vec![(name_span, format!("{name}ln")), (format_string_span, String::new())], + Applicability::MachineApplicable, + ); + } else if format_snippet.ends_with("\\n\"") { + // print!("...\n"), write!(f, "...\n") + + let hi = format_string_span.hi(); + let newline_span = format_string_span.with_lo(hi - BytePos(3)).with_hi(hi - BytePos(1)); + + diag.multipart_suggestion( + format!("use `{name}ln!` instead"), + vec![(name_span, format!("{name}ln")), (newline_span, String::new())], + Applicability::MachineApplicable, + ); + } + }, + ); + } +} diff --git a/clippy_utils/README.md b/clippy_utils/README.md index 45463b4fa1db8..6f976094fc2d0 100644 --- a/clippy_utils/README.md +++ b/clippy_utils/README.md @@ -8,7 +8,7 @@ This crate is only guaranteed to build with this `nightly` toolchain: ``` -nightly-2025-10-31 +nightly-2025-11-14 ``` diff --git a/clippy_utils/src/ast_utils/mod.rs b/clippy_utils/src/ast_utils/mod.rs index 208aa98f12f22..9c08f7b4d80fd 100644 --- a/clippy_utils/src/ast_utils/mod.rs +++ b/clippy_utils/src/ast_utils/mod.rs @@ -371,7 +371,7 @@ pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool { && eq_id(*li, *ri) && eq_generics(lg, rg) && eq_ty(lt, rt) - && both(lb.as_ref(), rb.as_ref(), |l, r| eq_const_item_rhs(l, r)) + && both(lb.as_ref(), rb.as_ref(), eq_const_item_rhs) }, ( Fn(box ast::Fn { @@ -625,7 +625,7 @@ pub fn eq_assoc_item_kind(l: &AssocItemKind, r: &AssocItemKind) -> bool { && eq_id(*li, *ri) && eq_generics(lg, rg) && eq_ty(lt, rt) - && both(lb.as_ref(), rb.as_ref(), |l, r| eq_const_item_rhs(l, r)) + && both(lb.as_ref(), rb.as_ref(), eq_const_item_rhs) }, ( Fn(box ast::Fn { diff --git a/clippy_utils/src/attrs.rs b/clippy_utils/src/attrs.rs index 2d42e76dcbc9b..671b266ba0086 100644 --- a/clippy_utils/src/attrs.rs +++ b/clippy_utils/src/attrs.rs @@ -1,3 +1,5 @@ +//! Utility functions for attributes, including Clippy's built-in ones + use crate::source::SpanRangeExt; use crate::{sym, tokenize_with_text}; use rustc_ast::attr; @@ -12,131 +14,59 @@ use rustc_session::Session; use rustc_span::{Span, Symbol}; use std::str::FromStr; -/// Deprecation status of attributes known by Clippy. -pub enum DeprecationStatus { - /// Attribute is deprecated - Deprecated, - /// Attribute is deprecated and was replaced by the named attribute - Replaced(&'static str), - None, -} - -#[rustfmt::skip] -pub const BUILTIN_ATTRIBUTES: &[(Symbol, DeprecationStatus)] = &[ - (sym::author, DeprecationStatus::None), - (sym::version, DeprecationStatus::None), - (sym::cognitive_complexity, DeprecationStatus::None), - (sym::cyclomatic_complexity, DeprecationStatus::Replaced("cognitive_complexity")), - (sym::dump, DeprecationStatus::None), - (sym::msrv, DeprecationStatus::None), - // The following attributes are for the 3rd party crate authors. - // See book/src/attribs.md - (sym::has_significant_drop, DeprecationStatus::None), - (sym::format_args, DeprecationStatus::None), -]; - -pub struct LimitStack { - stack: Vec, -} - -impl Drop for LimitStack { - fn drop(&mut self) { - assert_eq!(self.stack.len(), 1); - } -} - -impl LimitStack { - #[must_use] - pub fn new(limit: u64) -> Self { - Self { stack: vec![limit] } - } - pub fn limit(&self) -> u64 { - *self.stack.last().expect("there should always be a value in the stack") - } - pub fn push_attrs(&mut self, sess: &Session, attrs: &[impl AttributeExt], name: Symbol) { - let stack = &mut self.stack; - parse_attrs(sess, attrs, name, |val| stack.push(val)); - } - pub fn pop_attrs(&mut self, sess: &Session, attrs: &[impl AttributeExt], name: Symbol) { - let stack = &mut self.stack; - parse_attrs(sess, attrs, name, |val| assert_eq!(stack.pop(), Some(val))); - } -} - -pub fn get_attr<'a, A: AttributeExt + 'a>( +/// Given `attrs`, extract all the instances of a built-in Clippy attribute called `name` +pub fn get_builtin_attr<'a, A: AttributeExt + 'a>( sess: &'a Session, attrs: &'a [A], name: Symbol, ) -> impl Iterator { attrs.iter().filter(move |attr| { - let Some(attr_segments) = attr.ident_path() else { - return false; - }; + if let Some([clippy, segment2]) = attr.ident_path().as_deref() + && clippy.name == sym::clippy + { + let new_name = match segment2.name { + sym::cyclomatic_complexity => Some("cognitive_complexity"), + sym::author + | sym::version + | sym::cognitive_complexity + | sym::dump + | sym::msrv + // The following attributes are for the 3rd party crate authors. + // See book/src/attribs.md + | sym::has_significant_drop + | sym::format_args => None, + _ => { + sess.dcx().span_err(segment2.span, "usage of unknown attribute"); + return false; + }, + }; - if attr_segments.len() == 2 && attr_segments[0].name == sym::clippy { - BUILTIN_ATTRIBUTES - .iter() - .find_map(|(builtin_name, deprecation_status)| { - if attr_segments[1].name == *builtin_name { - Some(deprecation_status) - } else { - None - } - }) - .map_or_else( - || { - sess.dcx().span_err(attr_segments[1].span, "usage of unknown attribute"); - false - }, - |deprecation_status| { - let mut diag = sess - .dcx() - .struct_span_err(attr_segments[1].span, "usage of deprecated attribute"); - match *deprecation_status { - DeprecationStatus::Deprecated => { - diag.emit(); - false - }, - DeprecationStatus::Replaced(new_name) => { - diag.span_suggestion( - attr_segments[1].span, - "consider using", - new_name, - Applicability::MachineApplicable, - ); - diag.emit(); - false - }, - DeprecationStatus::None => { - diag.cancel(); - attr_segments[1].name == name - }, - } - }, - ) + match new_name { + Some(new_name) => { + sess.dcx() + .struct_span_err(segment2.span, "usage of deprecated attribute") + .with_span_suggestion( + segment2.span, + "consider using", + new_name, + Applicability::MachineApplicable, + ) + .emit(); + false + }, + None => segment2.name == name, + } } else { false } }) } -fn parse_attrs(sess: &Session, attrs: &[impl AttributeExt], name: Symbol, mut f: F) { - for attr in get_attr(sess, attrs, name) { - if let Some(value) = attr.value_str() { - if let Ok(value) = FromStr::from_str(value.as_str()) { - f(value); - } else { - sess.dcx().span_err(attr.span(), "not a number"); - } - } else { - sess.dcx().span_err(attr.span(), "bad clippy attribute"); - } - } -} - -pub fn get_unique_attr<'a, A: AttributeExt>(sess: &'a Session, attrs: &'a [A], name: Symbol) -> Option<&'a A> { +/// If `attrs` contain exactly one instance of a built-in Clippy attribute called `name`, +/// returns that attribute, and `None` otherwise +pub fn get_unique_builtin_attr<'a, A: AttributeExt>(sess: &'a Session, attrs: &'a [A], name: Symbol) -> Option<&'a A> { let mut unique_attr: Option<&A> = None; - for attr in get_attr(sess, attrs, name) { + for attr in get_builtin_attr(sess, attrs, name) { if let Some(duplicate) = unique_attr { sess.dcx() .struct_span_err(attr.span(), format!("`{name}` is defined multiple times")) @@ -149,13 +79,13 @@ pub fn get_unique_attr<'a, A: AttributeExt>(sess: &'a Session, attrs: &'a [A], n unique_attr } -/// Returns true if the attributes contain any of `proc_macro`, -/// `proc_macro_derive` or `proc_macro_attribute`, false otherwise +/// Checks whether `attrs` contain any of `proc_macro`, `proc_macro_derive` or +/// `proc_macro_attribute` pub fn is_proc_macro(attrs: &[impl AttributeExt]) -> bool { attrs.iter().any(AttributeExt::is_proc_macro_attr) } -/// Returns true if the attributes contain `#[doc(hidden)]` +/// Checks whether `attrs` contain `#[doc(hidden)]` pub fn is_doc_hidden(attrs: &[impl AttributeExt]) -> bool { attrs .iter() @@ -164,6 +94,7 @@ pub fn is_doc_hidden(attrs: &[impl AttributeExt]) -> bool { .any(|l| attr::list_contains_name(&l, sym::hidden)) } +/// Checks whether the given ADT, or any of its fields/variants, are marked as `#[non_exhaustive]` pub fn has_non_exhaustive_attr(tcx: TyCtxt<'_>, adt: AdtDef<'_>) -> bool { adt.is_variant_list_non_exhaustive() || find_attr!(tcx.get_all_attrs(adt.did()), AttributeKind::NonExhaustive(..)) @@ -176,7 +107,7 @@ pub fn has_non_exhaustive_attr(tcx: TyCtxt<'_>, adt: AdtDef<'_>) -> bool { .any(|field_def| find_attr!(tcx.get_all_attrs(field_def.did), AttributeKind::NonExhaustive(..))) } -/// Checks if the given span contains a `#[cfg(..)]` attribute +/// Checks whether the given span contains a `#[cfg(..)]` attribute pub fn span_contains_cfg(cx: &LateContext<'_>, s: Span) -> bool { s.check_source_text(cx, |src| { let mut iter = tokenize_with_text(src); @@ -198,3 +129,52 @@ pub fn span_contains_cfg(cx: &LateContext<'_>, s: Span) -> bool { false }) } + +/// Currently used to keep track of the current value of `#[clippy::cognitive_complexity(N)]` +pub struct LimitStack { + default: u64, + stack: Vec, +} + +impl Drop for LimitStack { + fn drop(&mut self) { + debug_assert_eq!(self.stack, Vec::::new()); // avoid `.is_empty()`, for a nicer error message + } +} + +#[expect(missing_docs, reason = "they're all trivial...")] +impl LimitStack { + #[must_use] + /// Initialize the stack starting with a default value, which usually comes from configuration + pub fn new(limit: u64) -> Self { + Self { + default: limit, + stack: vec![], + } + } + pub fn limit(&self) -> u64 { + self.stack.last().copied().unwrap_or(self.default) + } + pub fn push_attrs(&mut self, sess: &Session, attrs: &[impl AttributeExt], name: Symbol) { + let stack = &mut self.stack; + parse_attrs(sess, attrs, name, |val| stack.push(val)); + } + pub fn pop_attrs(&mut self, sess: &Session, attrs: &[impl AttributeExt], name: Symbol) { + let stack = &mut self.stack; + parse_attrs(sess, attrs, name, |val| debug_assert_eq!(stack.pop(), Some(val))); + } +} + +fn parse_attrs(sess: &Session, attrs: &[impl AttributeExt], name: Symbol, mut f: F) { + for attr in get_builtin_attr(sess, attrs, name) { + let Some(value) = attr.value_str() else { + sess.dcx().span_err(attr.span(), "bad clippy attribute"); + continue; + }; + let Ok(value) = u64::from_str(value.as_str()) else { + sess.dcx().span_err(attr.span(), "not a number"); + continue; + }; + f(value); + } +} diff --git a/clippy_utils/src/consts.rs b/clippy_utils/src/consts.rs index ac408a1b59e5a..7e3fa4f9909b3 100644 --- a/clippy_utils/src/consts.rs +++ b/clippy_utils/src/consts.rs @@ -1138,9 +1138,8 @@ pub fn const_item_rhs_to_expr<'tcx>(tcx: TyCtxt<'tcx>, ct_rhs: ConstItemRhs<'tcx match ct_rhs { ConstItemRhs::Body(body_id) => Some(tcx.hir_body(body_id).value), ConstItemRhs::TypeConst(const_arg) => match const_arg.kind { - ConstArgKind::Path(_) => None, ConstArgKind::Anon(anon) => Some(tcx.hir_body(anon.body).value), - ConstArgKind::Error(..) | ConstArgKind::Infer(..) => None, + ConstArgKind::Path(_) | ConstArgKind::Error(..) | ConstArgKind::Infer(..) => None, }, } } diff --git a/clippy_utils/src/hir_utils.rs b/clippy_utils/src/hir_utils.rs index 2433ca8b97f27..b286701fbed11 100644 --- a/clippy_utils/src/hir_utils.rs +++ b/clippy_utils/src/hir_utils.rs @@ -480,10 +480,8 @@ impl HirEqInterExpr<'_, '_, '_> { // Use explicit match for now since ConstArg is undergoing flux. (ConstArgKind::Path(..), ConstArgKind::Anon(..)) | (ConstArgKind::Anon(..), ConstArgKind::Path(..)) - | (ConstArgKind::Infer(..), _) - | (_, ConstArgKind::Infer(..)) - | (ConstArgKind::Error(..), _) - | (_, ConstArgKind::Error(..)) => false, + | (ConstArgKind::Infer(..) | ConstArgKind::Error(..), _) + | (_, ConstArgKind::Infer(..) | ConstArgKind::Error(..)) => false, } } diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index d98e3073b41d1..c9302b17eb7ec 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -50,6 +50,7 @@ extern crate rustc_span; extern crate rustc_trait_selection; pub mod ast_utils; +#[deny(missing_docs)] pub mod attrs; mod check_proc_macro; pub mod comparisons; @@ -131,7 +132,7 @@ use crate::ast_utils::unordered_over; use crate::consts::{ConstEvalCtxt, Constant}; use crate::higher::Range; use crate::msrvs::Msrv; -use crate::res::{MaybeDef, MaybeResPath}; +use crate::res::{MaybeDef, MaybeQPath, MaybeResPath}; 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; @@ -300,6 +301,22 @@ pub fn is_lang_item_or_ctor(cx: &LateContext<'_>, did: DefId, item: LangItem) -> cx.tcx.lang_items().get(item) == Some(did) } +/// Checks is `expr` is `None` +pub fn is_none_expr(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + expr.res(cx).ctor_parent(cx).is_lang_item(cx, OptionNone) +} + +/// If `expr` is `Some(inner)`, returns `inner` +pub fn as_some_expr<'tcx>(cx: &LateContext<'_>, expr: &'tcx Expr<'tcx>) -> Option<&'tcx Expr<'tcx>> { + if let ExprKind::Call(e, [arg]) = expr.kind + && e.res(cx).ctor_parent(cx).is_lang_item(cx, OptionSome) + { + Some(arg) + } else { + None + } +} + /// Checks if `expr` is an empty block or an empty tuple. pub fn is_unit_expr(expr: &Expr<'_>) -> bool { matches!( @@ -320,6 +337,25 @@ pub fn is_wild(pat: &Pat<'_>) -> bool { matches!(pat.kind, PatKind::Wild) } +/// If `pat` is: +/// - `Some(inner)`, returns `inner` +/// - it will _usually_ contain just one element, but could have two, given patterns like +/// `Some(inner, ..)` or `Some(.., inner)` +/// - `Some`, returns `[]` +/// - otherwise, returns `None` +pub fn as_some_pattern<'a, 'hir>(cx: &LateContext<'_>, pat: &'a Pat<'hir>) -> Option<&'a [Pat<'hir>]> { + if let PatKind::TupleStruct(ref qpath, inner, _) = pat.kind + && cx + .qpath_res(qpath, pat.hir_id) + .ctor_parent(cx) + .is_lang_item(cx, OptionSome) + { + Some(inner) + } else { + None + } +} + /// Checks if the `pat` is `None`. pub fn is_none_pattern(cx: &LateContext<'_>, pat: &Pat<'_>) -> bool { matches!(pat.kind, @@ -2782,11 +2818,7 @@ pub fn pat_and_expr_can_be_question_mark<'a, 'hir>( pat: &'a Pat<'hir>, else_body: &Expr<'_>, ) -> Option<&'a Pat<'hir>> { - if let PatKind::TupleStruct(pat_path, [inner_pat], _) = pat.kind - && cx - .qpath_res(&pat_path, pat.hir_id) - .ctor_parent(cx) - .is_lang_item(cx, OptionSome) + if let Some([inner_pat]) = as_some_pattern(cx, pat) && !is_refutable(cx, inner_pat) && let else_body = peel_blocks(else_body) && let ExprKind::Ret(Some(ret_val)) = else_body.kind diff --git a/clippy_utils/src/macros.rs b/clippy_utils/src/macros.rs index 7cd5a16f5b46d..4e06f010bd591 100644 --- a/clippy_utils/src/macros.rs +++ b/clippy_utils/src/macros.rs @@ -3,7 +3,7 @@ use std::sync::{Arc, OnceLock}; use crate::visitors::{Descend, for_each_expr_without_closures}; -use crate::{get_unique_attr, sym}; +use crate::{get_unique_builtin_attr, sym}; use arrayvec::ArrayVec; use rustc_ast::{FormatArgs, FormatArgument, FormatPlaceholder}; @@ -42,7 +42,7 @@ pub fn is_format_macro(cx: &LateContext<'_>, macro_def_id: DefId) -> bool { } else { // Allow users to tag any macro as being format!-like // TODO: consider deleting FORMAT_MACRO_DIAG_ITEMS and using just this method - get_unique_attr(cx.sess(), cx.tcx.get_all_attrs(macro_def_id), sym::format_args).is_some() + get_unique_builtin_attr(cx.sess(), cx.tcx.get_all_attrs(macro_def_id), sym::format_args).is_some() } } diff --git a/clippy_utils/src/numeric_literal.rs b/clippy_utils/src/numeric_literal.rs index bb2a628211000..b5fffab13b1da 100644 --- a/clippy_utils/src/numeric_literal.rs +++ b/clippy_utils/src/numeric_literal.rs @@ -1,11 +1,16 @@ use rustc_ast::ast::{LitFloatType, LitIntType, LitKind}; use std::iter; +/// Represents the base of a numeric literal, used for parsing and formatting. #[derive(Debug, PartialEq, Eq, Copy, Clone)] pub enum Radix { + /// A binary literal (e.g., `0b1010`) Binary, + /// An octal literal (e.g., `0o670`) Octal, + /// A decimal literal (e.g., `123`) Decimal, + /// A hexadecimal literal (e.g., `0xFF`) Hexadecimal, } @@ -46,6 +51,7 @@ pub struct NumericLiteral<'a> { } impl<'a> NumericLiteral<'a> { + /// Attempts to parse a `NumericLiteral` from the source string of an `ast::LitKind`. pub fn from_lit_kind(src: &'a str, lit_kind: &LitKind) -> Option> { let unsigned_src = src.strip_prefix('-').map_or(src, |s| s); if lit_kind.is_numeric() @@ -63,6 +69,7 @@ impl<'a> NumericLiteral<'a> { } } + /// Parses a raw numeric literal string into its structured `NumericLiteral` parts. #[must_use] pub fn new(lit: &'a str, suffix: Option<&'a str>, float: bool) -> Self { let unsigned_lit = lit.trim_start_matches('-'); @@ -102,11 +109,12 @@ impl<'a> NumericLiteral<'a> { } } + /// Checks if the literal's radix is `Radix::Decimal` pub fn is_decimal(&self) -> bool { self.radix == Radix::Decimal } - pub fn split_digit_parts(digits: &str, float: bool) -> (&str, Option<&str>, Option<(&str, &str)>) { + fn split_digit_parts(digits: &str, float: bool) -> (&str, Option<&str>, Option<(&str, &str)>) { let mut integer = digits; let mut fraction = None; let mut exponent = None; @@ -180,7 +188,7 @@ impl<'a> NumericLiteral<'a> { output } - pub fn group_digits(output: &mut String, input: &str, group_size: usize, partial_group_first: bool, pad: bool) { + fn group_digits(output: &mut String, input: &str, group_size: usize, partial_group_first: bool, zero_pad: bool) { debug_assert!(group_size > 0); let mut digits = input.chars().filter(|&c| c != '_'); @@ -196,7 +204,7 @@ impl<'a> NumericLiteral<'a> { if partial_group_first { first_group_size = (digits.clone().count() - 1) % group_size + 1; - if pad { + if zero_pad { for _ in 0..group_size - first_group_size { output.push('0'); } diff --git a/clippy_utils/src/qualify_min_const_fn.rs b/clippy_utils/src/qualify_min_const_fn.rs index 0cf1ad348953b..296da9fec8c0d 100644 --- a/clippy_utils/src/qualify_min_const_fn.rs +++ b/clippy_utils/src/qualify_min_const_fn.rs @@ -194,8 +194,7 @@ fn check_rvalue<'tcx>( )) } }, - Rvalue::NullaryOp(NullOp::OffsetOf(_) | NullOp::RuntimeChecks(_), _) - | Rvalue::ShallowInitBox(_, _) => Ok(()), + Rvalue::NullaryOp(NullOp::OffsetOf(_) | NullOp::RuntimeChecks(_), _) | Rvalue::ShallowInitBox(_, _) => Ok(()), Rvalue::UnaryOp(_, operand) => { let ty = operand.ty(body, cx.tcx); if ty.is_integral() || ty.is_bool() { diff --git a/clippy_utils/src/sym.rs b/clippy_utils/src/sym.rs index c2523540f007d..1d1537dd0e91f 100644 --- a/clippy_utils/src/sym.rs +++ b/clippy_utils/src/sym.rs @@ -126,6 +126,7 @@ generate! { copy_from_nonoverlapping, copy_to, copy_to_nonoverlapping, + core_arch, count_ones, create, create_new, @@ -330,6 +331,7 @@ generate! { splitn_mut, sqrt, starts_with, + std_detect, step_by, strlen, style, diff --git a/rust-toolchain.toml b/rust-toolchain.toml index d23fd74d9accd..f6809da98f2b1 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,6 +1,6 @@ [toolchain] # begin autogenerated nightly -channel = "nightly-2025-10-31" +channel = "nightly-2025-11-14" # end autogenerated nightly components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"] profile = "minimal" diff --git a/tests/ui/arithmetic_side_effects.rs b/tests/ui/arithmetic_side_effects.rs index 3245b2c983e19..b7ed596d811e5 100644 --- a/tests/ui/arithmetic_side_effects.rs +++ b/tests/ui/arithmetic_side_effects.rs @@ -17,6 +17,7 @@ extern crate proc_macro_derive; use core::num::{NonZero, Saturating, Wrapping}; +use core::time::Duration; const ONE: i32 = 1; const ZERO: i32 = 0; @@ -687,4 +688,59 @@ pub fn explicit_methods() { //~^ arithmetic_side_effects } +pub fn issue_15943(days: u8) -> Duration { + Duration::from_secs(86400 * u64::from(days)) +} + +pub fn type_conversion_add() { + let _ = u128::MAX + u128::from(1u8); + //~^ arithmetic_side_effects + let _ = 1u128 + u128::from(1u16); + let _ = 1u128 + u128::from(1u32); + let _ = 1u128 + u128::from(1u64); + + let _ = 1u64 + u64::from(1u8); + let _ = 1u64 + u64::from(1u16); + let _ = 1u64 + u64::from(1u32); + + let _ = 1u32 + u32::from(1u8); + let _ = 1u32 + u32::from(1u16); + + let _ = 1u16 + u16::from(1u8); +} + +pub fn type_conversion_mul() { + let _ = u128::MAX * u128::from(1u8); + //~^ arithmetic_side_effects + let _ = 1u128 * u128::from(1u16); + let _ = 1u128 * u128::from(1u32); + let _ = 1u128 * u128::from(1u64); + + let _ = 1u64 * u64::from(1u8); + let _ = 1u64 * u64::from(1u16); + let _ = 1u64 * u64::from(1u32); + + let _ = 1u32 * u32::from(1u8); + let _ = 1u32 * u32::from(1u16); + + let _ = 1u16 * u16::from(1u8); +} + +pub fn type_conversion_does_not_escape_its_context() { + struct Foo; + impl Foo { + fn from(n: u8) -> u64 { + u64::from(n) + } + } + let _ = Duration::from_secs(86400 * Foo::from(1)); + //~^ arithmetic_side_effects + + fn shift(x: u8) -> u64 { + 1 << u64::from(x) + } + let _ = Duration::from_secs(86400 * shift(1)); + //~^ arithmetic_side_effects +} + fn main() {} diff --git a/tests/ui/arithmetic_side_effects.stderr b/tests/ui/arithmetic_side_effects.stderr index 4150493ba94a3..22742a82601ab 100644 --- a/tests/ui/arithmetic_side_effects.stderr +++ b/tests/ui/arithmetic_side_effects.stderr @@ -1,5 +1,5 @@ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:166:13 + --> tests/ui/arithmetic_side_effects.rs:167:13 | LL | let _ = 1f16 + 1f16; | ^^^^^^^^^^^ @@ -8,766 +8,790 @@ LL | let _ = 1f16 + 1f16; = help: to override `-D warnings` add `#[allow(clippy::arithmetic_side_effects)]` error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:170:13 + --> tests/ui/arithmetic_side_effects.rs:171:13 | LL | let _ = 1f128 + 1f128; | ^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:175:13 + --> tests/ui/arithmetic_side_effects.rs:176:13 | LL | let _ = String::new() + &String::new(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:311:5 + --> tests/ui/arithmetic_side_effects.rs:312:5 | LL | _n += 1; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:313:5 + --> tests/ui/arithmetic_side_effects.rs:314:5 | LL | _n += &1; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:315:5 + --> tests/ui/arithmetic_side_effects.rs:316:5 | LL | _n -= 1; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:317:5 + --> tests/ui/arithmetic_side_effects.rs:318:5 | LL | _n -= &1; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:319:5 + --> tests/ui/arithmetic_side_effects.rs:320:5 | LL | _n /= 0; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:321:5 + --> tests/ui/arithmetic_side_effects.rs:322:5 | LL | _n /= &0; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:323:5 + --> tests/ui/arithmetic_side_effects.rs:324:5 | LL | _n %= 0; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:325:5 + --> tests/ui/arithmetic_side_effects.rs:326:5 | LL | _n %= &0; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:327:5 + --> tests/ui/arithmetic_side_effects.rs:328:5 | LL | _n *= 2; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:329:5 + --> tests/ui/arithmetic_side_effects.rs:330:5 | LL | _n *= &2; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:331:5 + --> tests/ui/arithmetic_side_effects.rs:332:5 | LL | _n += -1; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:333:5 + --> tests/ui/arithmetic_side_effects.rs:334:5 | LL | _n += &-1; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:335:5 + --> tests/ui/arithmetic_side_effects.rs:336:5 | LL | _n -= -1; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:337:5 + --> tests/ui/arithmetic_side_effects.rs:338:5 | LL | _n -= &-1; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:339:5 + --> tests/ui/arithmetic_side_effects.rs:340:5 | LL | _n /= -0; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:341:5 + --> tests/ui/arithmetic_side_effects.rs:342:5 | LL | _n /= &-0; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:343:5 + --> tests/ui/arithmetic_side_effects.rs:344:5 | LL | _n %= -0; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:345:5 + --> tests/ui/arithmetic_side_effects.rs:346:5 | LL | _n %= &-0; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:347:5 + --> tests/ui/arithmetic_side_effects.rs:348:5 | LL | _n *= -2; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:349:5 + --> tests/ui/arithmetic_side_effects.rs:350:5 | LL | _n *= &-2; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:351:5 + --> tests/ui/arithmetic_side_effects.rs:352:5 | LL | _custom += Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:353:5 + --> tests/ui/arithmetic_side_effects.rs:354:5 | LL | _custom += &Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:355:5 + --> tests/ui/arithmetic_side_effects.rs:356:5 | LL | _custom -= Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:357:5 + --> tests/ui/arithmetic_side_effects.rs:358:5 | LL | _custom -= &Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:359:5 + --> tests/ui/arithmetic_side_effects.rs:360:5 | LL | _custom /= Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:361:5 + --> tests/ui/arithmetic_side_effects.rs:362:5 | LL | _custom /= &Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:363:5 + --> tests/ui/arithmetic_side_effects.rs:364:5 | LL | _custom %= Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:365:5 + --> tests/ui/arithmetic_side_effects.rs:366:5 | LL | _custom %= &Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:367:5 + --> tests/ui/arithmetic_side_effects.rs:368:5 | LL | _custom *= Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:369:5 + --> tests/ui/arithmetic_side_effects.rs:370:5 | LL | _custom *= &Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:371:5 + --> tests/ui/arithmetic_side_effects.rs:372:5 | LL | _custom >>= Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:373:5 + --> tests/ui/arithmetic_side_effects.rs:374:5 | LL | _custom >>= &Custom; | ^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:375:5 + --> tests/ui/arithmetic_side_effects.rs:376:5 | LL | _custom <<= Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:377:5 + --> tests/ui/arithmetic_side_effects.rs:378:5 | LL | _custom <<= &Custom; | ^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:379:5 + --> tests/ui/arithmetic_side_effects.rs:380:5 | LL | _custom += -Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:381:5 + --> tests/ui/arithmetic_side_effects.rs:382:5 | LL | _custom += &-Custom; | ^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:383:5 + --> tests/ui/arithmetic_side_effects.rs:384:5 | LL | _custom -= -Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:385:5 + --> tests/ui/arithmetic_side_effects.rs:386:5 | LL | _custom -= &-Custom; | ^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:387:5 + --> tests/ui/arithmetic_side_effects.rs:388:5 | LL | _custom /= -Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:389:5 + --> tests/ui/arithmetic_side_effects.rs:390:5 | LL | _custom /= &-Custom; | ^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:391:5 + --> tests/ui/arithmetic_side_effects.rs:392:5 | LL | _custom %= -Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:393:5 + --> tests/ui/arithmetic_side_effects.rs:394:5 | LL | _custom %= &-Custom; | ^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:395:5 + --> tests/ui/arithmetic_side_effects.rs:396:5 | LL | _custom *= -Custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:397:5 + --> tests/ui/arithmetic_side_effects.rs:398:5 | LL | _custom *= &-Custom; | ^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:399:5 + --> tests/ui/arithmetic_side_effects.rs:400:5 | LL | _custom >>= -Custom; | ^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:401:5 + --> tests/ui/arithmetic_side_effects.rs:402:5 | LL | _custom >>= &-Custom; | ^^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:403:5 + --> tests/ui/arithmetic_side_effects.rs:404:5 | LL | _custom <<= -Custom; | ^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:405:5 + --> tests/ui/arithmetic_side_effects.rs:406:5 | LL | _custom <<= &-Custom; | ^^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:409:10 + --> tests/ui/arithmetic_side_effects.rs:410:10 | LL | _n = _n + 1; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:411:10 + --> tests/ui/arithmetic_side_effects.rs:412:10 | LL | _n = _n + &1; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:413:10 + --> tests/ui/arithmetic_side_effects.rs:414:10 | LL | _n = 1 + _n; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:415:10 + --> tests/ui/arithmetic_side_effects.rs:416:10 | LL | _n = &1 + _n; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:417:10 + --> tests/ui/arithmetic_side_effects.rs:418:10 | LL | _n = _n - 1; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:419:10 + --> tests/ui/arithmetic_side_effects.rs:420:10 | LL | _n = _n - &1; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:421:10 + --> tests/ui/arithmetic_side_effects.rs:422:10 | LL | _n = 1 - _n; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:423:10 + --> tests/ui/arithmetic_side_effects.rs:424:10 | LL | _n = &1 - _n; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:425:10 + --> tests/ui/arithmetic_side_effects.rs:426:10 | LL | _n = _n / 0; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:427:10 + --> tests/ui/arithmetic_side_effects.rs:428:10 | LL | _n = _n / &0; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:429:10 + --> tests/ui/arithmetic_side_effects.rs:430:10 | LL | _n = _n % 0; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:431:10 + --> tests/ui/arithmetic_side_effects.rs:432:10 | LL | _n = _n % &0; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:433:10 + --> tests/ui/arithmetic_side_effects.rs:434:10 | LL | _n = _n * 2; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:435:10 + --> tests/ui/arithmetic_side_effects.rs:436:10 | LL | _n = _n * &2; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:437:10 + --> tests/ui/arithmetic_side_effects.rs:438:10 | LL | _n = 2 * _n; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:439:10 + --> tests/ui/arithmetic_side_effects.rs:440:10 | LL | _n = &2 * _n; | ^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:441:10 + --> tests/ui/arithmetic_side_effects.rs:442:10 | LL | _n = 23 + &85; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:443:10 + --> tests/ui/arithmetic_side_effects.rs:444:10 | LL | _n = &23 + 85; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:445:10 + --> tests/ui/arithmetic_side_effects.rs:446:10 | LL | _n = &23 + &85; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:447:15 + --> tests/ui/arithmetic_side_effects.rs:448:15 | LL | _custom = _custom + _custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:449:15 + --> tests/ui/arithmetic_side_effects.rs:450:15 | LL | _custom = _custom + &_custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:451:15 + --> tests/ui/arithmetic_side_effects.rs:452:15 | LL | _custom = Custom + _custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:453:15 + --> tests/ui/arithmetic_side_effects.rs:454:15 | LL | _custom = &Custom + _custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:455:15 + --> tests/ui/arithmetic_side_effects.rs:456:15 | LL | _custom = _custom - Custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:457:15 + --> tests/ui/arithmetic_side_effects.rs:458:15 | LL | _custom = _custom - &Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:459:15 + --> tests/ui/arithmetic_side_effects.rs:460:15 | LL | _custom = Custom - _custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:461:15 + --> tests/ui/arithmetic_side_effects.rs:462:15 | LL | _custom = &Custom - _custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:463:15 + --> tests/ui/arithmetic_side_effects.rs:464:15 | LL | _custom = _custom / Custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:465:15 + --> tests/ui/arithmetic_side_effects.rs:466:15 | LL | _custom = _custom / &Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:467:15 + --> tests/ui/arithmetic_side_effects.rs:468:15 | LL | _custom = _custom % Custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:469:15 + --> tests/ui/arithmetic_side_effects.rs:470:15 | LL | _custom = _custom % &Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:471:15 + --> tests/ui/arithmetic_side_effects.rs:472:15 | LL | _custom = _custom * Custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:473:15 + --> tests/ui/arithmetic_side_effects.rs:474:15 | LL | _custom = _custom * &Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:475:15 + --> tests/ui/arithmetic_side_effects.rs:476:15 | LL | _custom = Custom * _custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:477:15 + --> tests/ui/arithmetic_side_effects.rs:478:15 | LL | _custom = &Custom * _custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:479:15 + --> tests/ui/arithmetic_side_effects.rs:480:15 | LL | _custom = Custom + &Custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:481:15 + --> tests/ui/arithmetic_side_effects.rs:482:15 | LL | _custom = &Custom + Custom; | ^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:483:15 + --> tests/ui/arithmetic_side_effects.rs:484:15 | LL | _custom = &Custom + &Custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:485:15 + --> tests/ui/arithmetic_side_effects.rs:486:15 | LL | _custom = _custom >> _custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:487:15 + --> tests/ui/arithmetic_side_effects.rs:488:15 | LL | _custom = _custom >> &_custom; | ^^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:489:15 + --> tests/ui/arithmetic_side_effects.rs:490:15 | LL | _custom = Custom << _custom; | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:491:15 + --> tests/ui/arithmetic_side_effects.rs:492:15 | LL | _custom = &Custom << _custom; | ^^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:495:23 + --> tests/ui/arithmetic_side_effects.rs:496:23 | LL | _n.saturating_div(0); | ^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:497:21 + --> tests/ui/arithmetic_side_effects.rs:498:21 | LL | _n.wrapping_div(0); | ^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:499:21 + --> tests/ui/arithmetic_side_effects.rs:500:21 | LL | _n.wrapping_rem(0); | ^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:501:28 + --> tests/ui/arithmetic_side_effects.rs:502:28 | LL | _n.wrapping_rem_euclid(0); | ^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:504:23 + --> tests/ui/arithmetic_side_effects.rs:505:23 | LL | _n.saturating_div(_n); | ^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:506:21 + --> tests/ui/arithmetic_side_effects.rs:507:21 | LL | _n.wrapping_div(_n); | ^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:508:21 + --> tests/ui/arithmetic_side_effects.rs:509:21 | LL | _n.wrapping_rem(_n); | ^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:510:28 + --> tests/ui/arithmetic_side_effects.rs:511:28 | LL | _n.wrapping_rem_euclid(_n); | ^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:513:23 + --> tests/ui/arithmetic_side_effects.rs:514:23 | LL | _n.saturating_div(*Box::new(_n)); | ^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:517:10 + --> tests/ui/arithmetic_side_effects.rs:518:10 | LL | _n = -_n; | ^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:519:10 + --> tests/ui/arithmetic_side_effects.rs:520:10 | LL | _n = -&_n; | ^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:521:15 + --> tests/ui/arithmetic_side_effects.rs:522:15 | LL | _custom = -_custom; | ^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:523:15 + --> tests/ui/arithmetic_side_effects.rs:524:15 | LL | _custom = -&_custom; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:525:9 + --> tests/ui/arithmetic_side_effects.rs:526:9 | LL | _ = -*Box::new(_n); | ^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:535:5 + --> tests/ui/arithmetic_side_effects.rs:536:5 | LL | 1 + i; | ^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:537:5 + --> tests/ui/arithmetic_side_effects.rs:538:5 | LL | i * 2; | ^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:539:5 + --> tests/ui/arithmetic_side_effects.rs:540:5 | LL | 1 % i / 2; | ^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:541:5 + --> tests/ui/arithmetic_side_effects.rs:542:5 | LL | i - 2 + 2 - i; | ^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:543:5 + --> tests/ui/arithmetic_side_effects.rs:544:5 | LL | -i; | ^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:555:5 + --> tests/ui/arithmetic_side_effects.rs:556:5 | LL | i += 1; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:557:5 + --> tests/ui/arithmetic_side_effects.rs:558:5 | LL | i -= 1; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:559:5 + --> tests/ui/arithmetic_side_effects.rs:560:5 | LL | i *= 2; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:562:5 + --> tests/ui/arithmetic_side_effects.rs:563:5 | LL | i /= 0; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:565:5 + --> tests/ui/arithmetic_side_effects.rs:566:5 | LL | i /= var1; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:567:5 + --> tests/ui/arithmetic_side_effects.rs:568:5 | LL | i /= var2; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:570:5 + --> tests/ui/arithmetic_side_effects.rs:571:5 | LL | i %= 0; | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:573:5 + --> tests/ui/arithmetic_side_effects.rs:574:5 | LL | i %= var1; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:575:5 + --> tests/ui/arithmetic_side_effects.rs:576:5 | LL | i %= var2; | ^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:586:5 + --> tests/ui/arithmetic_side_effects.rs:587:5 | LL | 10 / a | ^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:641:9 + --> tests/ui/arithmetic_side_effects.rs:642:9 | LL | x / maybe_zero | ^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:646:9 + --> tests/ui/arithmetic_side_effects.rs:647:9 | LL | x % maybe_zero | ^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:658:5 + --> tests/ui/arithmetic_side_effects.rs:659:5 | LL | one.add_assign(1); | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:663:5 + --> tests/ui/arithmetic_side_effects.rs:664:5 | LL | one.sub_assign(1); | ^^^^^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:684:5 + --> tests/ui/arithmetic_side_effects.rs:685:5 | LL | one.add(&one); | ^^^^^^^^^^^^^ error: arithmetic operation that can potentially result in unexpected side-effects - --> tests/ui/arithmetic_side_effects.rs:686:5 + --> tests/ui/arithmetic_side_effects.rs:687:5 | LL | Box::new(one).add(one); | ^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 128 previous errors +error: arithmetic operation that can potentially result in unexpected side-effects + --> tests/ui/arithmetic_side_effects.rs:696:13 + | +LL | let _ = u128::MAX + u128::from(1u8); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: arithmetic operation that can potentially result in unexpected side-effects + --> tests/ui/arithmetic_side_effects.rs:713:13 + | +LL | let _ = u128::MAX * u128::from(1u8); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: arithmetic operation that can potentially result in unexpected side-effects + --> tests/ui/arithmetic_side_effects.rs:736:33 + | +LL | let _ = Duration::from_secs(86400 * Foo::from(1)); + | ^^^^^^^^^^^^^^^^^^^^ + +error: arithmetic operation that can potentially result in unexpected side-effects + --> tests/ui/arithmetic_side_effects.rs:742:33 + | +LL | let _ = Duration::from_secs(86400 * shift(1)); + | ^^^^^^^^^^^^^^^^ + +error: aborting due to 132 previous errors diff --git a/tests/ui/cmp_null.fixed b/tests/ui/cmp_null.fixed index 04b8ec50160b3..c12279cf12e63 100644 --- a/tests/ui/cmp_null.fixed +++ b/tests/ui/cmp_null.fixed @@ -1,5 +1,4 @@ #![warn(clippy::cmp_null)] -#![allow(unused_mut)] use std::ptr; @@ -18,7 +17,7 @@ fn main() { } let mut y = 0; - let mut m: *mut usize = &mut y; + let m: *mut usize = &mut y; if m.is_null() { //~^ cmp_null diff --git a/tests/ui/cmp_null.rs b/tests/ui/cmp_null.rs index 6f7762e6ae831..2771a16e00c52 100644 --- a/tests/ui/cmp_null.rs +++ b/tests/ui/cmp_null.rs @@ -1,5 +1,4 @@ #![warn(clippy::cmp_null)] -#![allow(unused_mut)] use std::ptr; @@ -18,7 +17,7 @@ fn main() { } let mut y = 0; - let mut m: *mut usize = &mut y; + let m: *mut usize = &mut y; if m == ptr::null_mut() { //~^ cmp_null diff --git a/tests/ui/cmp_null.stderr b/tests/ui/cmp_null.stderr index 8a75b05011193..381747cb3c657 100644 --- a/tests/ui/cmp_null.stderr +++ b/tests/ui/cmp_null.stderr @@ -1,5 +1,5 @@ error: comparing with null is better expressed by the `.is_null()` method - --> tests/ui/cmp_null.rs:9:8 + --> tests/ui/cmp_null.rs:8:8 | LL | if p == ptr::null() { | ^^^^^^^^^^^^^^^^ help: try: `p.is_null()` @@ -8,31 +8,31 @@ LL | if p == ptr::null() { = help: to override `-D warnings` add `#[allow(clippy::cmp_null)]` error: comparing with null is better expressed by the `.is_null()` method - --> tests/ui/cmp_null.rs:14:8 + --> tests/ui/cmp_null.rs:13:8 | LL | if ptr::null() == p { | ^^^^^^^^^^^^^^^^ help: try: `p.is_null()` error: comparing with null is better expressed by the `.is_null()` method - --> tests/ui/cmp_null.rs:22:8 + --> tests/ui/cmp_null.rs:21:8 | LL | if m == ptr::null_mut() { | ^^^^^^^^^^^^^^^^^^^^ help: try: `m.is_null()` error: comparing with null is better expressed by the `.is_null()` method - --> tests/ui/cmp_null.rs:27:8 + --> tests/ui/cmp_null.rs:26:8 | LL | if ptr::null_mut() == m { | ^^^^^^^^^^^^^^^^^^^^ help: try: `m.is_null()` error: comparing with null is better expressed by the `.is_null()` method - --> tests/ui/cmp_null.rs:33:13 + --> tests/ui/cmp_null.rs:32:13 | LL | let _ = x as *const () == ptr::null(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `(x as *const ()).is_null()` error: comparing with null is better expressed by the `.is_null()` method - --> tests/ui/cmp_null.rs:39:19 + --> tests/ui/cmp_null.rs:38:19 | LL | debug_assert!(f != std::ptr::null_mut()); | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `!f.is_null()` diff --git a/tests/ui/fn_to_numeric_cast.32bit.stderr b/tests/ui/fn_to_numeric_cast.32bit.stderr index 2affd0b7d6e91..86c189cb44cbc 100644 --- a/tests/ui/fn_to_numeric_cast.32bit.stderr +++ b/tests/ui/fn_to_numeric_cast.32bit.stderr @@ -1,5 +1,5 @@ error: casting function pointer `foo` to `i8`, which truncates the value - --> tests/ui/fn_to_numeric_cast.rs:12:13 + --> tests/ui/fn_to_numeric_cast.rs:13:13 | LL | let _ = foo as i8; | ^^^^^^^^^ help: try: `foo as usize` @@ -8,13 +8,13 @@ LL | let _ = foo as i8; = help: to override `-D warnings` add `#[allow(clippy::fn_to_numeric_cast_with_truncation)]` error: casting function pointer `foo` to `i16`, which truncates the value - --> tests/ui/fn_to_numeric_cast.rs:14:13 + --> tests/ui/fn_to_numeric_cast.rs:15:13 | LL | let _ = foo as i16; | ^^^^^^^^^^ help: try: `foo as usize` error: casting function pointer `foo` to `i32` - --> tests/ui/fn_to_numeric_cast.rs:16:13 + --> tests/ui/fn_to_numeric_cast.rs:17:13 | LL | let _ = foo as i32; | ^^^^^^^^^^ help: try: `foo as usize` @@ -23,121 +23,121 @@ LL | let _ = foo as i32; = help: to override `-D warnings` add `#[allow(clippy::fn_to_numeric_cast)]` error: casting function pointer `foo` to `i64` - --> tests/ui/fn_to_numeric_cast.rs:19:13 + --> tests/ui/fn_to_numeric_cast.rs:20:13 | LL | let _ = foo as i64; | ^^^^^^^^^^ help: try: `foo as usize` error: casting function pointer `foo` to `i128` - --> tests/ui/fn_to_numeric_cast.rs:21:13 + --> tests/ui/fn_to_numeric_cast.rs:22:13 | LL | let _ = foo as i128; | ^^^^^^^^^^^ help: try: `foo as usize` error: casting function pointer `foo` to `isize` - --> tests/ui/fn_to_numeric_cast.rs:23:13 + --> tests/ui/fn_to_numeric_cast.rs:24:13 | LL | let _ = foo as isize; | ^^^^^^^^^^^^ help: try: `foo as usize` error: casting function pointer `foo` to `u8`, which truncates the value - --> tests/ui/fn_to_numeric_cast.rs:26:13 + --> tests/ui/fn_to_numeric_cast.rs:27:13 | LL | let _ = foo as u8; | ^^^^^^^^^ help: try: `foo as usize` error: casting function pointer `foo` to `u16`, which truncates the value - --> tests/ui/fn_to_numeric_cast.rs:28:13 + --> tests/ui/fn_to_numeric_cast.rs:29:13 | LL | let _ = foo as u16; | ^^^^^^^^^^ help: try: `foo as usize` error: casting function pointer `foo` to `u32` - --> tests/ui/fn_to_numeric_cast.rs:30:13 + --> tests/ui/fn_to_numeric_cast.rs:31:13 | LL | let _ = foo as u32; | ^^^^^^^^^^ help: try: `foo as usize` error: casting function pointer `foo` to `u64` - --> tests/ui/fn_to_numeric_cast.rs:33:13 + --> tests/ui/fn_to_numeric_cast.rs:34:13 | LL | let _ = foo as u64; | ^^^^^^^^^^ help: try: `foo as usize` error: casting function pointer `foo` to `u128` - --> tests/ui/fn_to_numeric_cast.rs:35:13 + --> tests/ui/fn_to_numeric_cast.rs:36:13 | LL | let _ = foo as u128; | ^^^^^^^^^^^ help: try: `foo as usize` error: casting function pointer `abc` to `i8`, which truncates the value - --> tests/ui/fn_to_numeric_cast.rs:49:13 + --> tests/ui/fn_to_numeric_cast.rs:50:13 | LL | let _ = abc as i8; | ^^^^^^^^^ help: try: `abc as usize` error: casting function pointer `abc` to `i16`, which truncates the value - --> tests/ui/fn_to_numeric_cast.rs:51:13 + --> tests/ui/fn_to_numeric_cast.rs:52:13 | LL | let _ = abc as i16; | ^^^^^^^^^^ help: try: `abc as usize` error: casting function pointer `abc` to `i32` - --> tests/ui/fn_to_numeric_cast.rs:53:13 + --> tests/ui/fn_to_numeric_cast.rs:54:13 | LL | let _ = abc as i32; | ^^^^^^^^^^ help: try: `abc as usize` error: casting function pointer `abc` to `i64` - --> tests/ui/fn_to_numeric_cast.rs:56:13 + --> tests/ui/fn_to_numeric_cast.rs:57:13 | LL | let _ = abc as i64; | ^^^^^^^^^^ help: try: `abc as usize` error: casting function pointer `abc` to `i128` - --> tests/ui/fn_to_numeric_cast.rs:58:13 + --> tests/ui/fn_to_numeric_cast.rs:59:13 | LL | let _ = abc as i128; | ^^^^^^^^^^^ help: try: `abc as usize` error: casting function pointer `abc` to `isize` - --> tests/ui/fn_to_numeric_cast.rs:60:13 + --> tests/ui/fn_to_numeric_cast.rs:61:13 | LL | let _ = abc as isize; | ^^^^^^^^^^^^ help: try: `abc as usize` error: casting function pointer `abc` to `u8`, which truncates the value - --> tests/ui/fn_to_numeric_cast.rs:63:13 + --> tests/ui/fn_to_numeric_cast.rs:64:13 | LL | let _ = abc as u8; | ^^^^^^^^^ help: try: `abc as usize` error: casting function pointer `abc` to `u16`, which truncates the value - --> tests/ui/fn_to_numeric_cast.rs:65:13 + --> tests/ui/fn_to_numeric_cast.rs:66:13 | LL | let _ = abc as u16; | ^^^^^^^^^^ help: try: `abc as usize` error: casting function pointer `abc` to `u32` - --> tests/ui/fn_to_numeric_cast.rs:67:13 + --> tests/ui/fn_to_numeric_cast.rs:68:13 | LL | let _ = abc as u32; | ^^^^^^^^^^ help: try: `abc as usize` error: casting function pointer `abc` to `u64` - --> tests/ui/fn_to_numeric_cast.rs:70:13 + --> tests/ui/fn_to_numeric_cast.rs:71:13 | LL | let _ = abc as u64; | ^^^^^^^^^^ help: try: `abc as usize` error: casting function pointer `abc` to `u128` - --> tests/ui/fn_to_numeric_cast.rs:72:13 + --> tests/ui/fn_to_numeric_cast.rs:73:13 | LL | let _ = abc as u128; | ^^^^^^^^^^^ help: try: `abc as usize` error: casting function pointer `f` to `i32` - --> tests/ui/fn_to_numeric_cast.rs:80:5 + --> tests/ui/fn_to_numeric_cast.rs:81:5 | LL | f as i32 | ^^^^^^^^ help: try: `f as usize` diff --git a/tests/ui/incompatible_msrv.rs b/tests/ui/incompatible_msrv.rs index 3069c8139abe3..e08828b46c369 100644 --- a/tests/ui/incompatible_msrv.rs +++ b/tests/ui/incompatible_msrv.rs @@ -178,4 +178,11 @@ const fn uncalled_len() { //~^ incompatible_msrv } +#[clippy::msrv = "1.0.0"] +fn vec_macro() { + let _: Vec = vec![]; + let _: Vec = vec![1; 3]; + let _: Vec = vec![1, 2]; +} + fn main() {} diff --git a/tests/ui/let_and_return.edition2021.fixed b/tests/ui/let_and_return.edition2021.fixed index 70d503018e0fe..e89e4476bf820 100644 --- a/tests/ui/let_and_return.edition2021.fixed +++ b/tests/ui/let_and_return.edition2021.fixed @@ -261,4 +261,14 @@ fn issue14164() -> Result { //~[edition2024]^ let_and_return } +fn issue15987() -> i32 { + macro_rules! sample { + ( $( $args:expr ),+ ) => {}; + } + + let r = 5; + sample!(r); + r +} + fn main() {} diff --git a/tests/ui/let_and_return.edition2024.fixed b/tests/ui/let_and_return.edition2024.fixed index 9990c3b712055..d2c76673ca03c 100644 --- a/tests/ui/let_and_return.edition2024.fixed +++ b/tests/ui/let_and_return.edition2024.fixed @@ -261,4 +261,14 @@ fn issue14164() -> Result { //~[edition2024]^ let_and_return } +fn issue15987() -> i32 { + macro_rules! sample { + ( $( $args:expr ),+ ) => {}; + } + + let r = 5; + sample!(r); + r +} + fn main() {} diff --git a/tests/ui/let_and_return.rs b/tests/ui/let_and_return.rs index 48c20cdd60dbe..1af5f8ba5c165 100644 --- a/tests/ui/let_and_return.rs +++ b/tests/ui/let_and_return.rs @@ -261,4 +261,14 @@ fn issue14164() -> Result { //~[edition2024]^ let_and_return } +fn issue15987() -> i32 { + macro_rules! sample { + ( $( $args:expr ),+ ) => {}; + } + + let r = 5; + sample!(r); + r +} + fn main() {} diff --git a/tests/ui/let_if_seq.rs b/tests/ui/let_if_seq.rs index 2db206212aa5d..69d6319fa8bf2 100644 --- a/tests/ui/let_if_seq.rs +++ b/tests/ui/let_if_seq.rs @@ -139,3 +139,34 @@ fn main() { } println!("{}", val.get()); } + +fn issue16062(bar: fn() -> bool) { + let foo; + //~^ useless_let_if_seq + if bar() { + foo = 42; + } else { + foo = 0; + } +} + +fn issue16064(bar: fn() -> bool) { + macro_rules! mac { + ($e:expr) => { + $e() + }; + ($base:expr, $lit:expr) => { + $lit * $base + 2 + }; + } + + let foo; + //~^ useless_let_if_seq + if mac!(bar) { + foo = mac!(10, 4); + } else { + foo = 0; + } + + let bar = 1; +} diff --git a/tests/ui/let_if_seq.stderr b/tests/ui/let_if_seq.stderr index f59d42bf4c8dc..b86bca6b384bc 100644 --- a/tests/ui/let_if_seq.stderr +++ b/tests/ui/let_if_seq.stderr @@ -52,5 +52,29 @@ LL | | } | = note: you might not need `mut` at all -error: aborting due to 4 previous errors +error: `if _ { .. } else { .. }` is an expression + --> tests/ui/let_if_seq.rs:144:5 + | +LL | / let foo; +LL | | +LL | | if bar() { +LL | | foo = 42; +LL | | } else { +LL | | foo = 0; +LL | | } + | |_____^ help: it is more idiomatic to write: `let foo = if bar() { 42 } else { 0 };` + +error: `if _ { .. } else { .. }` is an expression + --> tests/ui/let_if_seq.rs:163:5 + | +LL | / let foo; +LL | | +LL | | if mac!(bar) { +LL | | foo = mac!(10, 4); +LL | | } else { +LL | | foo = 0; +LL | | } + | |_____^ help: it is more idiomatic to write: `let foo = if mac!(bar) { mac!(10, 4) } else { 0 };` + +error: aborting due to 6 previous errors diff --git a/tests/ui/match_single_binding.fixed b/tests/ui/match_single_binding.fixed index 7e899a4766661..fa82a316d64d9 100644 --- a/tests/ui/match_single_binding.fixed +++ b/tests/ui/match_single_binding.fixed @@ -265,3 +265,82 @@ fn issue15269(a: usize, b: usize, c: usize) -> bool { a < b && b < c } + +#[allow( + irrefutable_let_patterns, + clippy::blocks_in_conditions, + clippy::unused_unit, + clippy::let_unit_value, + clippy::unit_arg, + clippy::unnecessary_operation +)] +fn issue15537(a: i32) -> ((), (), ()) { + let y = ( + { todo!() }, + { + { a }; + () + }, + (), + ); + + let y = [ + { todo!() }, + { + { a }; + () + }, + (), + ]; + + fn call(x: (), y: (), z: ()) {} + let y = call( + { todo!() }, + { + { a }; + () + }, + (), + ); + + struct Foo; + impl Foo { + fn method(&self, x: (), y: (), z: ()) {} + } + let x = Foo; + x.method( + { todo!() }, + { + { a }; + () + }, + (), + ); + + -{ + { a }; + 1 + }; + + _ = { a }; + 1; + + if let x = { + { a }; + 1 + } {} + + if { + { a }; + true + } { + todo!() + } + + [1, 2, 3][{ + { a }; + 1usize + }]; + + todo!() +} diff --git a/tests/ui/match_single_binding.rs b/tests/ui/match_single_binding.rs index 37a96f2287c85..6c1fae89e230d 100644 --- a/tests/ui/match_single_binding.rs +++ b/tests/ui/match_single_binding.rs @@ -342,3 +342,84 @@ fn issue15269(a: usize, b: usize, c: usize) -> bool { (a, b) => b < c, } } + +#[allow( + irrefutable_let_patterns, + clippy::blocks_in_conditions, + clippy::unused_unit, + clippy::let_unit_value, + clippy::unit_arg, + clippy::unnecessary_operation +)] +fn issue15537(a: i32) -> ((), (), ()) { + let y = ( + { todo!() }, + match { a } { + //~^ match_single_binding + _ => (), + }, + (), + ); + + let y = [ + { todo!() }, + match { a } { + //~^ match_single_binding + _ => (), + }, + (), + ]; + + fn call(x: (), y: (), z: ()) {} + let y = call( + { todo!() }, + match { a } { + //~^ match_single_binding + _ => (), + }, + (), + ); + + struct Foo; + impl Foo { + fn method(&self, x: (), y: (), z: ()) {} + } + let x = Foo; + x.method( + { todo!() }, + match { a } { + //~^ match_single_binding + _ => (), + }, + (), + ); + + -match { a } { + //~^ match_single_binding + _ => 1, + }; + + _ = match { a } { + //~^ match_single_binding + _ => 1, + }; + + if let x = match { a } { + //~^ match_single_binding + _ => 1, + } {} + + if match { a } { + //~^ match_single_binding + _ => true, + } { + todo!() + } + + [1, 2, 3][match { a } { + //~^ match_single_binding + _ => 1usize, + }]; + + todo!() +} diff --git a/tests/ui/match_single_binding.stderr b/tests/ui/match_single_binding.stderr index 82fc43aaa5eae..8a402dcca8470 100644 --- a/tests/ui/match_single_binding.stderr +++ b/tests/ui/match_single_binding.stderr @@ -525,5 +525,161 @@ LL | | (a, b) => b < c, LL | | } | |_________^ help: consider using the match body instead: `b < c` -error: aborting due to 37 previous errors +error: this match could be replaced by its scrutinee and body + --> tests/ui/match_single_binding.rs:357:9 + | +LL | / match { a } { +LL | | +LL | | _ => (), +LL | | }, + | |_________^ + | +help: consider using the scrutinee and body instead + | +LL ~ { +LL + { a }; +LL + () +LL ~ }, + | + +error: this match could be replaced by its scrutinee and body + --> tests/ui/match_single_binding.rs:366:9 + | +LL | / match { a } { +LL | | +LL | | _ => (), +LL | | }, + | |_________^ + | +help: consider using the scrutinee and body instead + | +LL ~ { +LL + { a }; +LL + () +LL ~ }, + | + +error: this match could be replaced by its scrutinee and body + --> tests/ui/match_single_binding.rs:376:9 + | +LL | / match { a } { +LL | | +LL | | _ => (), +LL | | }, + | |_________^ + | +help: consider using the scrutinee and body instead + | +LL ~ { +LL + { a }; +LL + () +LL ~ }, + | + +error: this match could be replaced by its scrutinee and body + --> tests/ui/match_single_binding.rs:390:9 + | +LL | / match { a } { +LL | | +LL | | _ => (), +LL | | }, + | |_________^ + | +help: consider using the scrutinee and body instead + | +LL ~ { +LL + { a }; +LL + () +LL ~ }, + | + +error: this match could be replaced by its scrutinee and body + --> tests/ui/match_single_binding.rs:397:6 + | +LL | -match { a } { + | ______^ +LL | | +LL | | _ => 1, +LL | | }; + | |_____^ + | +help: consider using the scrutinee and body instead + | +LL ~ -{ +LL + { a }; +LL + 1 +LL ~ }; + | + +error: this match could be replaced by its scrutinee and body + --> tests/ui/match_single_binding.rs:402:9 + | +LL | _ = match { a } { + | _________^ +LL | | +LL | | _ => 1, +LL | | }; + | |_____^ + | +help: consider using the scrutinee and body instead + | +LL ~ _ = { a }; +LL ~ 1; + | + +error: this match could be replaced by its scrutinee and body + --> tests/ui/match_single_binding.rs:407:16 + | +LL | if let x = match { a } { + | ________________^ +LL | | +LL | | _ => 1, +LL | | } {} + | |_____^ + | +help: consider using the scrutinee and body instead + | +LL ~ if let x = { +LL + { a }; +LL + 1 +LL ~ } {} + | + +error: this match could be replaced by its scrutinee and body + --> tests/ui/match_single_binding.rs:412:8 + | +LL | if match { a } { + | ________^ +LL | | +LL | | _ => true, +LL | | } { + | |_____^ + | +help: consider using the scrutinee and body instead + | +LL ~ if { +LL + { a }; +LL + true +LL ~ } { + | + +error: this match could be replaced by its scrutinee and body + --> tests/ui/match_single_binding.rs:419:15 + | +LL | [1, 2, 3][match { a } { + | _______________^ +LL | | +LL | | _ => 1usize, +LL | | }]; + | |_____^ + | +help: consider using the scrutinee and body instead + | +LL ~ [1, 2, 3][{ +LL + { a }; +LL + 1usize +LL ~ }]; + | + +error: aborting due to 46 previous errors diff --git a/tests/ui/missing_asserts_for_indexing.fixed b/tests/ui/missing_asserts_for_indexing.fixed index 9018f38100efd..50bc576dd1e23 100644 --- a/tests/ui/missing_asserts_for_indexing.fixed +++ b/tests/ui/missing_asserts_for_indexing.fixed @@ -150,9 +150,9 @@ fn highest_index_first(v1: &[u8]) { } fn issue14255(v1: &[u8], v2: &[u8], v3: &[u8], v4: &[u8]) { - assert!(v1.len() == 3); + assert_eq!(v1.len(), 3); assert_eq!(v2.len(), 4); - assert!(v3.len() == 3); + assert_eq!(v3.len(), 3); assert_eq!(4, v4.len()); let _ = v1[0] + v1[1] + v1[2]; @@ -166,4 +166,18 @@ fn issue14255(v1: &[u8], v2: &[u8], v3: &[u8], v4: &[u8]) { let _ = v4[0] + v4[1] + v4[2]; } +mod issue15988 { + fn assert_eq_len(v: &[i32]) { + assert_eq!(v.len(), 3); + let _ = v[0] + v[1] + v[2]; + //~^ missing_asserts_for_indexing + } + + fn debug_assert_eq_len(v: &[i32]) { + debug_assert_eq!(v.len(), 3); + let _ = v[0] + v[1] + v[2]; + //~^ missing_asserts_for_indexing + } +} + fn main() {} diff --git a/tests/ui/missing_asserts_for_indexing.rs b/tests/ui/missing_asserts_for_indexing.rs index 44c5eddf3d8b9..9e219a2af0732 100644 --- a/tests/ui/missing_asserts_for_indexing.rs +++ b/tests/ui/missing_asserts_for_indexing.rs @@ -166,4 +166,18 @@ fn issue14255(v1: &[u8], v2: &[u8], v3: &[u8], v4: &[u8]) { let _ = v4[0] + v4[1] + v4[2]; } +mod issue15988 { + fn assert_eq_len(v: &[i32]) { + assert_eq!(v.len(), 2); + let _ = v[0] + v[1] + v[2]; + //~^ missing_asserts_for_indexing + } + + fn debug_assert_eq_len(v: &[i32]) { + debug_assert_eq!(v.len(), 2); + let _ = v[0] + v[1] + v[2]; + //~^ missing_asserts_for_indexing + } +} + fn main() {} diff --git a/tests/ui/missing_asserts_for_indexing.stderr b/tests/ui/missing_asserts_for_indexing.stderr index b610de94b5308..b686eda7530a0 100644 --- a/tests/ui/missing_asserts_for_indexing.stderr +++ b/tests/ui/missing_asserts_for_indexing.stderr @@ -305,7 +305,7 @@ error: indexing into a slice multiple times with an `assert` that does not cover --> tests/ui/missing_asserts_for_indexing.rs:158:13 | LL | assert_eq!(v1.len(), 2); - | ----------------------- help: provide the highest index that is indexed with: `assert!(v1.len() == 3)` + | ----------------------- help: provide the highest index that is indexed with: `assert_eq!(v1.len(), 3)` ... LL | let _ = v1[0] + v1[1] + v1[2]; | ^^^^^^^^^^^^^^^^^^^^^ @@ -331,7 +331,7 @@ error: indexing into a slice multiple times with an `assert` that does not cover --> tests/ui/missing_asserts_for_indexing.rs:163:13 | LL | assert_eq!(2, v3.len()); - | ----------------------- help: provide the highest index that is indexed with: `assert!(v3.len() == 3)` + | ----------------------- help: provide the highest index that is indexed with: `assert_eq!(v3.len(), 3)` ... LL | let _ = v3[0] + v3[1] + v3[2]; | ^^^^^^^^^^^^^^^^^^^^^ @@ -353,5 +353,55 @@ LL | let _ = v3[0] + v3[1] + v3[2]; | ^^^^^ = note: asserting the length before indexing will elide bounds checks -error: aborting due to 13 previous errors +error: indexing into a slice multiple times with an `assert` that does not cover the highest index + --> tests/ui/missing_asserts_for_indexing.rs:172:17 + | +LL | assert_eq!(v.len(), 2); + | ---------------------- help: provide the highest index that is indexed with: `assert_eq!(v.len(), 3)` +LL | let _ = v[0] + v[1] + v[2]; + | ^^^^^^^^^^^^^^^^^^ + | +note: slice indexed here + --> tests/ui/missing_asserts_for_indexing.rs:172:17 + | +LL | let _ = v[0] + v[1] + v[2]; + | ^^^^ +note: slice indexed here + --> tests/ui/missing_asserts_for_indexing.rs:172:24 + | +LL | let _ = v[0] + v[1] + v[2]; + | ^^^^ +note: slice indexed here + --> tests/ui/missing_asserts_for_indexing.rs:172:31 + | +LL | let _ = v[0] + v[1] + v[2]; + | ^^^^ + = note: asserting the length before indexing will elide bounds checks + +error: indexing into a slice multiple times with an `assert` that does not cover the highest index + --> tests/ui/missing_asserts_for_indexing.rs:178:17 + | +LL | debug_assert_eq!(v.len(), 2); + | ---------------------------- help: provide the highest index that is indexed with: `debug_assert_eq!(v.len(), 3)` +LL | let _ = v[0] + v[1] + v[2]; + | ^^^^^^^^^^^^^^^^^^ + | +note: slice indexed here + --> tests/ui/missing_asserts_for_indexing.rs:178:17 + | +LL | let _ = v[0] + v[1] + v[2]; + | ^^^^ +note: slice indexed here + --> tests/ui/missing_asserts_for_indexing.rs:178:24 + | +LL | let _ = v[0] + v[1] + v[2]; + | ^^^^ +note: slice indexed here + --> tests/ui/missing_asserts_for_indexing.rs:178:31 + | +LL | let _ = v[0] + v[1] + v[2]; + | ^^^^ + = note: asserting the length before indexing will elide bounds checks + +error: aborting due to 15 previous errors diff --git a/tests/ui/missing_inline_executable.rs b/tests/ui/missing_inline_executable.rs index 444a7f1c964f0..2ab3e3b7825d5 100644 --- a/tests/ui/missing_inline_executable.rs +++ b/tests/ui/missing_inline_executable.rs @@ -1,7 +1,6 @@ -//@ check-pass - #![warn(clippy::missing_inline_in_public_items)] pub fn foo() {} +//~^ missing_inline_in_public_items fn main() {} diff --git a/tests/ui/missing_inline_executable.stderr b/tests/ui/missing_inline_executable.stderr new file mode 100644 index 0000000000000..3108e4e490659 --- /dev/null +++ b/tests/ui/missing_inline_executable.stderr @@ -0,0 +1,11 @@ +error: missing `#[inline]` for a function + --> tests/ui/missing_inline_executable.rs:3:1 + | +LL | pub fn foo() {} + | ^^^^^^^^^^^^^^^ + | + = note: `-D clippy::missing-inline-in-public-items` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::missing_inline_in_public_items)]` + +error: aborting due to 1 previous error + diff --git a/tests/ui/missing_inline_test_crate.rs b/tests/ui/missing_inline_test_crate.rs new file mode 100644 index 0000000000000..728292a0ee2b8 --- /dev/null +++ b/tests/ui/missing_inline_test_crate.rs @@ -0,0 +1,10 @@ +//@compile-flags: --test +//@check-pass +#![warn(clippy::missing_inline_in_public_items)] + +#[expect(clippy::missing_inline_in_public_items)] +pub fn foo() -> u32 { + 0 +} + +fn private_function() {} diff --git a/tests/ui/multiple_inherent_impl_cfg.rs b/tests/ui/multiple_inherent_impl_cfg.rs index 15c8b7c508781..4b973d762ed92 100644 --- a/tests/ui/multiple_inherent_impl_cfg.rs +++ b/tests/ui/multiple_inherent_impl_cfg.rs @@ -13,8 +13,7 @@ impl A {} //~^ multiple_inherent_impl #[cfg(test)] -impl A {} // false positive -//~^ multiple_inherent_impl +impl A {} #[cfg(test)] impl A {} @@ -25,8 +24,7 @@ struct B; impl B {} #[cfg(test)] -impl B {} // false positive -//~^ multiple_inherent_impl +impl B {} impl B {} //~^ multiple_inherent_impl diff --git a/tests/ui/multiple_inherent_impl_cfg.stderr b/tests/ui/multiple_inherent_impl_cfg.stderr index 9d408ce3dec38..991ceb0ff9677 100644 --- a/tests/ui/multiple_inherent_impl_cfg.stderr +++ b/tests/ui/multiple_inherent_impl_cfg.stderr @@ -16,76 +16,52 @@ LL | #![deny(clippy::multiple_inherent_impl)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: multiple implementations of this structure - --> tests/ui/multiple_inherent_impl_cfg.rs:16:1 - | -LL | impl A {} // false positive - | ^^^^^^^^^ - | -note: first implementation here - --> tests/ui/multiple_inherent_impl_cfg.rs:10:1 - | -LL | impl A {} - | ^^^^^^^^^ - -error: multiple implementations of this structure - --> tests/ui/multiple_inherent_impl_cfg.rs:20:1 + --> tests/ui/multiple_inherent_impl_cfg.rs:19:1 | LL | impl A {} | ^^^^^^^^^ | note: first implementation here - --> tests/ui/multiple_inherent_impl_cfg.rs:10:1 + --> tests/ui/multiple_inherent_impl_cfg.rs:16:1 | LL | impl A {} | ^^^^^^^^^ error: multiple implementations of this structure - --> tests/ui/multiple_inherent_impl_cfg.rs:28:1 - | -LL | impl B {} // false positive - | ^^^^^^^^^ - | -note: first implementation here - --> tests/ui/multiple_inherent_impl_cfg.rs:25:1 - | -LL | impl B {} - | ^^^^^^^^^ - -error: multiple implementations of this structure - --> tests/ui/multiple_inherent_impl_cfg.rs:31:1 + --> tests/ui/multiple_inherent_impl_cfg.rs:29:1 | LL | impl B {} | ^^^^^^^^^ | note: first implementation here - --> tests/ui/multiple_inherent_impl_cfg.rs:25:1 + --> tests/ui/multiple_inherent_impl_cfg.rs:24:1 | LL | impl B {} | ^^^^^^^^^ error: multiple implementations of this structure - --> tests/ui/multiple_inherent_impl_cfg.rs:35:1 + --> tests/ui/multiple_inherent_impl_cfg.rs:33:1 | LL | impl B {} | ^^^^^^^^^ | note: first implementation here - --> tests/ui/multiple_inherent_impl_cfg.rs:25:1 + --> tests/ui/multiple_inherent_impl_cfg.rs:27:1 | LL | impl B {} | ^^^^^^^^^ error: multiple implementations of this structure - --> tests/ui/multiple_inherent_impl_cfg.rs:45:1 + --> tests/ui/multiple_inherent_impl_cfg.rs:43:1 | LL | impl C {} | ^^^^^^^^^ | note: first implementation here - --> tests/ui/multiple_inherent_impl_cfg.rs:42:1 + --> tests/ui/multiple_inherent_impl_cfg.rs:40:1 | LL | impl C {} | ^^^^^^^^^ -error: aborting due to 7 previous errors +error: aborting due to 5 previous errors diff --git a/tests/ui/needless_arbitrary_self_type.stderr b/tests/ui/needless_arbitrary_self_type.stderr index b5c0aae8310f3..bb42e5ea63f51 100644 --- a/tests/ui/needless_arbitrary_self_type.stderr +++ b/tests/ui/needless_arbitrary_self_type.stderr @@ -2,52 +2,99 @@ error: the type of the `self` parameter does not need to be arbitrary --> tests/ui/needless_arbitrary_self_type.rs:10:16 | LL | pub fn bad(self: Self) { - | ^^^^^^^^^^ help: consider to change this parameter to: `self` + | ^^^^^^^^^^ | = note: `-D clippy::needless-arbitrary-self-type` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::needless_arbitrary_self_type)]` +help: remove the type + | +LL - pub fn bad(self: Self) { +LL + pub fn bad(self) { + | error: the type of the `self` parameter does not need to be arbitrary --> tests/ui/needless_arbitrary_self_type.rs:19:20 | LL | pub fn mut_bad(mut self: Self) { - | ^^^^^^^^^^^^^^ help: consider to change this parameter to: `mut self` + | ^^^^^^^^^^^^^^ + | +help: remove the type + | +LL - pub fn mut_bad(mut self: Self) { +LL + pub fn mut_bad(mut self) { + | error: the type of the `self` parameter does not need to be arbitrary --> tests/ui/needless_arbitrary_self_type.rs:28:20 | LL | pub fn ref_bad(self: &Self) { - | ^^^^^^^^^^^ help: consider to change this parameter to: `&self` + | ^^^^^^^^^^^ + | +help: remove the type + | +LL - pub fn ref_bad(self: &Self) { +LL + pub fn ref_bad(&self) { + | error: the type of the `self` parameter does not need to be arbitrary --> tests/ui/needless_arbitrary_self_type.rs:37:38 | LL | pub fn ref_bad_with_lifetime<'a>(self: &'a Self) { - | ^^^^^^^^^^^^^^ help: consider to change this parameter to: `&'a self` + | ^^^^^^^^^^^^^^ + | +help: remove the type + | +LL - pub fn ref_bad_with_lifetime<'a>(self: &'a Self) { +LL + pub fn ref_bad_with_lifetime<'a>(&'a self) { + | error: the type of the `self` parameter does not need to be arbitrary --> tests/ui/needless_arbitrary_self_type.rs:46:24 | LL | pub fn mut_ref_bad(self: &mut Self) { - | ^^^^^^^^^^^^^^^ help: consider to change this parameter to: `&mut self` + | ^^^^^^^^^^^^^^^ + | +help: remove the type + | +LL - pub fn mut_ref_bad(self: &mut Self) { +LL + pub fn mut_ref_bad(&mut self) { + | error: the type of the `self` parameter does not need to be arbitrary --> tests/ui/needless_arbitrary_self_type.rs:55:42 | LL | pub fn mut_ref_bad_with_lifetime<'a>(self: &'a mut Self) { - | ^^^^^^^^^^^^^^^^^^ help: consider to change this parameter to: `&'a mut self` + | ^^^^^^^^^^^^^^^^^^ + | +help: remove the type + | +LL - pub fn mut_ref_bad_with_lifetime<'a>(self: &'a mut Self) { +LL + pub fn mut_ref_bad_with_lifetime<'a>(&'a mut self) { + | error: the type of the `self` parameter does not need to be arbitrary --> tests/ui/needless_arbitrary_self_type.rs:74:11 | LL | fn f1(self: &'r#struct Self) {} - | ^^^^^^^^^^^^^^^^^^^^^ help: consider to change this parameter to: `&'r#struct self` + | ^^^^^^^^^^^^^^^^^^^^^ + | +help: remove the type + | +LL - fn f1(self: &'r#struct Self) {} +LL + fn f1(&'r#struct self) {} + | error: the type of the `self` parameter does not need to be arbitrary --> tests/ui/needless_arbitrary_self_type.rs:76:11 | LL | fn f2(self: &'r#struct mut Self) {} - | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider to change this parameter to: `&'r#struct mut self` + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: remove the type + | +LL - fn f2(self: &'r#struct mut Self) {} +LL + fn f2(&'r#struct mut self) {} + | error: aborting due to 8 previous errors diff --git a/tests/ui/needless_arbitrary_self_type_unfixable.stderr b/tests/ui/needless_arbitrary_self_type_unfixable.stderr index b50e005756290..4f8f001fc5e4d 100644 --- a/tests/ui/needless_arbitrary_self_type_unfixable.stderr +++ b/tests/ui/needless_arbitrary_self_type_unfixable.stderr @@ -2,10 +2,15 @@ error: the type of the `self` parameter does not need to be arbitrary --> tests/ui/needless_arbitrary_self_type_unfixable.rs:42:31 | LL | fn call_with_mut_self(self: &mut Self) {} - | ^^^^^^^^^^^^^^^ help: consider to change this parameter to: `&mut self` + | ^^^^^^^^^^^^^^^ | = note: `-D clippy::needless-arbitrary-self-type` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::needless_arbitrary_self_type)]` +help: remove the type + | +LL - fn call_with_mut_self(self: &mut Self) {} +LL + fn call_with_mut_self(&mut self) {} + | error: aborting due to 1 previous error diff --git a/tests/ui/needless_collect.fixed b/tests/ui/needless_collect.fixed index 842d77dbc8c50..ba1451bf9704f 100644 --- a/tests/ui/needless_collect.fixed +++ b/tests/ui/needless_collect.fixed @@ -20,6 +20,10 @@ fn main() { } sample.iter().cloned().any(|x| x == 1); //~^ needless_collect + + let _ = sample.iter().cloned().nth(1).unwrap(); + //~^ needless_collect + // #7164 HashMap's and BTreeMap's `len` usage should not be linted sample.iter().map(|x| (x, x)).collect::>().len(); sample.iter().map(|x| (x, x)).collect::>().len(); diff --git a/tests/ui/needless_collect.rs b/tests/ui/needless_collect.rs index 98d8d27321d24..e054cd01e6f59 100644 --- a/tests/ui/needless_collect.rs +++ b/tests/ui/needless_collect.rs @@ -20,6 +20,10 @@ fn main() { } sample.iter().cloned().collect::>().contains(&1); //~^ needless_collect + + let _ = sample.iter().cloned().collect::>()[1]; + //~^ needless_collect + // #7164 HashMap's and BTreeMap's `len` usage should not be linted sample.iter().map(|x| (x, x)).collect::>().len(); sample.iter().map(|x| (x, x)).collect::>().len(); diff --git a/tests/ui/needless_collect.stderr b/tests/ui/needless_collect.stderr index 00745eb2923c6..c77674dc55d4c 100644 --- a/tests/ui/needless_collect.stderr +++ b/tests/ui/needless_collect.stderr @@ -20,100 +20,106 @@ LL | sample.iter().cloned().collect::>().contains(&1); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `any(|x| x == 1)` error: avoid using `collect()` when not needed - --> tests/ui/needless_collect.rs:27:35 + --> tests/ui/needless_collect.rs:24:36 + | +LL | let _ = sample.iter().cloned().collect::>()[1]; + | ^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `nth(1).unwrap()` + +error: avoid using `collect()` when not needed + --> tests/ui/needless_collect.rs:31:35 | LL | sample.iter().map(|x| (x, x)).collect::>().is_empty(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `next().is_none()` error: avoid using `collect()` when not needed - --> tests/ui/needless_collect.rs:29:35 + --> tests/ui/needless_collect.rs:33:35 | LL | sample.iter().map(|x| (x, x)).collect::>().is_empty(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `next().is_none()` error: avoid using `collect()` when not needed - --> tests/ui/needless_collect.rs:37:19 + --> tests/ui/needless_collect.rs:41:19 | LL | sample.iter().collect::>().len(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `count()` error: avoid using `collect()` when not needed - --> tests/ui/needless_collect.rs:39:19 + --> tests/ui/needless_collect.rs:43:19 | LL | sample.iter().collect::>().is_empty(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `next().is_none()` error: avoid using `collect()` when not needed - --> tests/ui/needless_collect.rs:41:28 + --> tests/ui/needless_collect.rs:45:28 | LL | sample.iter().cloned().collect::>().contains(&1); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `any(|x| x == 1)` error: avoid using `collect()` when not needed - --> tests/ui/needless_collect.rs:43:19 + --> tests/ui/needless_collect.rs:47:19 | LL | sample.iter().collect::>().contains(&&1); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `any(|x| x == &1)` error: avoid using `collect()` when not needed - --> tests/ui/needless_collect.rs:47:19 + --> tests/ui/needless_collect.rs:51:19 | LL | sample.iter().collect::>().len(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `count()` error: avoid using `collect()` when not needed - --> tests/ui/needless_collect.rs:49:19 + --> tests/ui/needless_collect.rs:53:19 | LL | sample.iter().collect::>().is_empty(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `next().is_none()` error: avoid using `collect()` when not needed - --> tests/ui/needless_collect.rs:55:27 + --> tests/ui/needless_collect.rs:59:27 | LL | let _ = sample.iter().collect::>().is_empty(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `next().is_none()` error: avoid using `collect()` when not needed - --> tests/ui/needless_collect.rs:57:27 + --> tests/ui/needless_collect.rs:61:27 | LL | let _ = sample.iter().collect::>().contains(&&0); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `any(|x| x == &0)` error: avoid using `collect()` when not needed - --> tests/ui/needless_collect.rs:80:27 + --> tests/ui/needless_collect.rs:84:27 | LL | let _ = sample.iter().collect::>().is_empty(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `next().is_none()` error: avoid using `collect()` when not needed - --> tests/ui/needless_collect.rs:82:27 + --> tests/ui/needless_collect.rs:86:27 | LL | let _ = sample.iter().collect::>().contains(&&0); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `any(|x| x == &0)` error: avoid using `collect()` when not needed - --> tests/ui/needless_collect.rs:87:40 + --> tests/ui/needless_collect.rs:91:40 | LL | Vec::::new().extend((0..10).collect::>()); | ^^^^^^^^^^^^^^^^^^^^ help: remove this call error: avoid using `collect()` when not needed - --> tests/ui/needless_collect.rs:89:20 + --> tests/ui/needless_collect.rs:93:20 | LL | foo((0..10).collect::>()); | ^^^^^^^^^^^^^^^^^^^^ help: remove this call error: avoid using `collect()` when not needed - --> tests/ui/needless_collect.rs:91:49 + --> tests/ui/needless_collect.rs:95:49 | LL | bar((0..10).collect::>(), (0..10).collect::>()); | ^^^^^^^^^^^^^^^^^^^^ help: remove this call error: avoid using `collect()` when not needed - --> tests/ui/needless_collect.rs:93:37 + --> tests/ui/needless_collect.rs:97:37 | LL | baz((0..10), (), ('a'..='z').collect::>()) | ^^^^^^^^^^^^^^^^^^^^ help: remove this call -error: aborting due to 19 previous errors +error: aborting due to 20 previous errors diff --git a/tests/ui/nonminimal_bool_methods.fixed b/tests/ui/nonminimal_bool_methods.fixed index f50af147c60cf..0de944f9edcfe 100644 --- a/tests/ui/nonminimal_bool_methods.fixed +++ b/tests/ui/nonminimal_bool_methods.fixed @@ -242,4 +242,9 @@ fn issue_13436() { } } +fn issue16014() { + (vec![1, 2, 3] > vec![1, 2, 3, 3]); + //~^ nonminimal_bool +} + fn main() {} diff --git a/tests/ui/nonminimal_bool_methods.rs b/tests/ui/nonminimal_bool_methods.rs index 0ecd4775035bf..ac0bd6d8a4910 100644 --- a/tests/ui/nonminimal_bool_methods.rs +++ b/tests/ui/nonminimal_bool_methods.rs @@ -242,4 +242,9 @@ fn issue_13436() { } } +fn issue16014() { + !(vec![1, 2, 3] <= vec![1, 2, 3, 3]); + //~^ nonminimal_bool +} + fn main() {} diff --git a/tests/ui/nonminimal_bool_methods.stderr b/tests/ui/nonminimal_bool_methods.stderr index b5155b3b16968..568e880077279 100644 --- a/tests/ui/nonminimal_bool_methods.stderr +++ b/tests/ui/nonminimal_bool_methods.stderr @@ -247,5 +247,11 @@ error: this boolean expression can be simplified LL | _ = !opt.is_none_or(|x| x.is_err()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `opt.is_some_and(|x| x.is_ok())` -error: aborting due to 41 previous errors +error: this boolean expression can be simplified + --> tests/ui/nonminimal_bool_methods.rs:246:5 + | +LL | !(vec![1, 2, 3] <= vec![1, 2, 3, 3]); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `(vec![1, 2, 3] > vec![1, 2, 3, 3])` + +error: aborting due to 42 previous errors diff --git a/tests/ui/ok_expect.fixed b/tests/ui/ok_expect.fixed new file mode 100644 index 0000000000000..2a05b8805e4d5 --- /dev/null +++ b/tests/ui/ok_expect.fixed @@ -0,0 +1,51 @@ +#![allow(clippy::unnecessary_literal_unwrap)] + +use std::io; + +struct MyError(()); // doesn't implement Debug + +#[derive(Debug)] +struct MyErrorWithParam { + x: T, +} + +fn main() { + let res: Result = Ok(0); + let _ = res.unwrap(); + + res.expect("disaster!"); + //~^ ok_expect + + res.expect("longlonglonglonglonglonglonglonglonglonglonglonglonglong"); + //~^^ ok_expect + + let resres = res; + resres.expect("longlonglonglonglonglonglonglonglonglonglonglonglonglong"); + //~^^^ ok_expect + + // this one has a suboptimal suggestion, but oh well + std::process::Command::new("rustc") + .arg("-vV") + .output().expect("failed to get rustc version"); + //~^^^^^ ok_expect + + // the following should not warn, since `expect` isn't implemented unless + // the error type implements `Debug` + let res2: Result = Ok(0); + res2.ok().expect("oh noes!"); + let res3: Result> = Ok(0); + res3.expect("whoof"); + //~^ ok_expect + + let res4: Result = Ok(0); + res4.expect("argh"); + //~^ ok_expect + + let res5: io::Result = Ok(0); + res5.expect("oops"); + //~^ ok_expect + + let res6: Result = Ok(0); + res6.expect("meh"); + //~^ ok_expect +} diff --git a/tests/ui/ok_expect.rs b/tests/ui/ok_expect.rs index efb56f242a746..3761aa26f6e81 100644 --- a/tests/ui/ok_expect.rs +++ b/tests/ui/ok_expect.rs @@ -16,6 +16,24 @@ fn main() { res.ok().expect("disaster!"); //~^ ok_expect + res.ok() + .expect("longlonglonglonglonglonglonglonglonglonglonglonglonglong"); + //~^^ ok_expect + + let resres = res; + resres + .ok() + .expect("longlonglonglonglonglonglonglonglonglonglonglonglonglong"); + //~^^^ ok_expect + + // this one has a suboptimal suggestion, but oh well + std::process::Command::new("rustc") + .arg("-vV") + .output() + .ok() + .expect("failed to get rustc version"); + //~^^^^^ ok_expect + // the following should not warn, since `expect` isn't implemented unless // the error type implements `Debug` let res2: Result = Ok(0); diff --git a/tests/ui/ok_expect.stderr b/tests/ui/ok_expect.stderr index a9e3533d8ca1a..848a10e671dbf 100644 --- a/tests/ui/ok_expect.stderr +++ b/tests/ui/ok_expect.stderr @@ -4,41 +4,109 @@ error: called `ok().expect()` on a `Result` value LL | res.ok().expect("disaster!"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: you can call `expect()` directly on the `Result` = note: `-D clippy::ok-expect` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::ok_expect)]` +help: call `expect()` directly on the `Result` + | +LL - res.ok().expect("disaster!"); +LL + res.expect("disaster!"); + | + +error: called `ok().expect()` on a `Result` value + --> tests/ui/ok_expect.rs:19:5 + | +LL | / res.ok() +LL | | .expect("longlonglonglonglonglonglonglonglonglonglonglonglonglong"); + | |___________________________________________________________________________^ + | +help: call `expect()` directly on the `Result` + | +LL - res.ok() +LL - .expect("longlonglonglonglonglonglonglonglonglonglonglonglonglong"); +LL + res.expect("longlonglonglonglonglonglonglonglonglonglonglonglonglong"); + | error: called `ok().expect()` on a `Result` value --> tests/ui/ok_expect.rs:24:5 | +LL | / resres +LL | | .ok() +LL | | .expect("longlonglonglonglonglonglonglonglonglonglonglonglonglong"); + | |___________________________________________________________________________^ + | +help: call `expect()` directly on the `Result` + | +LL - resres +LL - .ok() +LL - .expect("longlonglonglonglonglonglonglonglonglonglonglonglonglong"); +LL + resres.expect("longlonglonglonglonglonglonglonglonglonglonglonglonglong"); + | + +error: called `ok().expect()` on a `Result` value + --> tests/ui/ok_expect.rs:30:5 + | +LL | / std::process::Command::new("rustc") +LL | | .arg("-vV") +LL | | .output() +LL | | .ok() +LL | | .expect("failed to get rustc version"); + | |______________________________________________^ + | +help: call `expect()` directly on the `Result` + | +LL - .output() +LL - .ok() +LL - .expect("failed to get rustc version"); +LL + .output().expect("failed to get rustc version"); + | + +error: called `ok().expect()` on a `Result` value + --> tests/ui/ok_expect.rs:42:5 + | LL | res3.ok().expect("whoof"); | ^^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: you can call `expect()` directly on the `Result` +help: call `expect()` directly on the `Result` + | +LL - res3.ok().expect("whoof"); +LL + res3.expect("whoof"); + | error: called `ok().expect()` on a `Result` value - --> tests/ui/ok_expect.rs:28:5 + --> tests/ui/ok_expect.rs:46:5 | LL | res4.ok().expect("argh"); | ^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: you can call `expect()` directly on the `Result` +help: call `expect()` directly on the `Result` + | +LL - res4.ok().expect("argh"); +LL + res4.expect("argh"); + | error: called `ok().expect()` on a `Result` value - --> tests/ui/ok_expect.rs:32:5 + --> tests/ui/ok_expect.rs:50:5 | LL | res5.ok().expect("oops"); | ^^^^^^^^^^^^^^^^^^^^^^^^ | - = help: you can call `expect()` directly on the `Result` +help: call `expect()` directly on the `Result` + | +LL - res5.ok().expect("oops"); +LL + res5.expect("oops"); + | error: called `ok().expect()` on a `Result` value - --> tests/ui/ok_expect.rs:36:5 + --> tests/ui/ok_expect.rs:54:5 | LL | res6.ok().expect("meh"); | ^^^^^^^^^^^^^^^^^^^^^^^ | - = help: you can call `expect()` directly on the `Result` +help: call `expect()` directly on the `Result` + | +LL - res6.ok().expect("meh"); +LL + res6.expect("meh"); + | -error: aborting due to 5 previous errors +error: aborting due to 8 previous errors diff --git a/tests/ui/rc_buffer.fixed b/tests/ui/rc_buffer.fixed index c71a4072b9623..a41f98c8fa35b 100644 --- a/tests/ui/rc_buffer.fixed +++ b/tests/ui/rc_buffer.fixed @@ -1,5 +1,4 @@ #![warn(clippy::rc_buffer)] -#![allow(dead_code, unused_imports)] use std::cell::RefCell; use std::ffi::OsString; @@ -32,4 +31,9 @@ fn func_bad4(_: Rc) {} // does not trigger lint fn func_good1(_: Rc>) {} +mod issue_15802 { + fn foo(_: std::rc::Rc<[u8]>) {} + //~^ rc_buffer +} + fn main() {} diff --git a/tests/ui/rc_buffer.rs b/tests/ui/rc_buffer.rs index 686c2644da170..879f606474727 100644 --- a/tests/ui/rc_buffer.rs +++ b/tests/ui/rc_buffer.rs @@ -1,5 +1,4 @@ #![warn(clippy::rc_buffer)] -#![allow(dead_code, unused_imports)] use std::cell::RefCell; use std::ffi::OsString; @@ -32,4 +31,9 @@ fn func_bad4(_: Rc) {} // does not trigger lint fn func_good1(_: Rc>) {} +mod issue_15802 { + fn foo(_: std::rc::Rc>) {} + //~^ rc_buffer +} + fn main() {} diff --git a/tests/ui/rc_buffer.stderr b/tests/ui/rc_buffer.stderr index 7500523ab4acc..e31e9c9c8fdf5 100644 --- a/tests/ui/rc_buffer.stderr +++ b/tests/ui/rc_buffer.stderr @@ -1,53 +1,112 @@ -error: usage of `Rc` when T is a buffer type - --> tests/ui/rc_buffer.rs:11:11 +error: usage of `Rc` when `T` is a buffer type + --> tests/ui/rc_buffer.rs:10:11 | LL | bad1: Rc, - | ^^^^^^^^^^ help: try: `Rc` + | ^^^^^^^^^^ | = note: `-D clippy::rc-buffer` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::rc_buffer)]` +help: try + | +LL - bad1: Rc, +LL + bad1: Rc, + | -error: usage of `Rc` when T is a buffer type - --> tests/ui/rc_buffer.rs:13:11 +error: usage of `Rc` when `T` is a buffer type + --> tests/ui/rc_buffer.rs:12:11 | LL | bad2: Rc, - | ^^^^^^^^^^^ help: try: `Rc` + | ^^^^^^^^^^^ + | +help: try + | +LL - bad2: Rc, +LL + bad2: Rc, + | -error: usage of `Rc` when T is a buffer type - --> tests/ui/rc_buffer.rs:15:11 +error: usage of `Rc` when `T` is a buffer type + --> tests/ui/rc_buffer.rs:14:11 | LL | bad3: Rc>, - | ^^^^^^^^^^^ help: try: `Rc<[u8]>` + | ^^^^^^^^^^^ + | +help: try + | +LL - bad3: Rc>, +LL + bad3: Rc<[u8]>, + | -error: usage of `Rc` when T is a buffer type - --> tests/ui/rc_buffer.rs:17:11 +error: usage of `Rc` when `T` is a buffer type + --> tests/ui/rc_buffer.rs:16:11 | LL | bad4: Rc, - | ^^^^^^^^^^^^ help: try: `Rc` + | ^^^^^^^^^^^^ + | +help: try + | +LL - bad4: Rc, +LL + bad4: Rc, + | -error: usage of `Rc` when T is a buffer type - --> tests/ui/rc_buffer.rs:24:17 +error: usage of `Rc` when `T` is a buffer type + --> tests/ui/rc_buffer.rs:23:17 | LL | fn func_bad1(_: Rc) {} - | ^^^^^^^^^^ help: try: `Rc` + | ^^^^^^^^^^ + | +help: try + | +LL - fn func_bad1(_: Rc) {} +LL + fn func_bad1(_: Rc) {} + | -error: usage of `Rc` when T is a buffer type - --> tests/ui/rc_buffer.rs:26:17 +error: usage of `Rc` when `T` is a buffer type + --> tests/ui/rc_buffer.rs:25:17 | LL | fn func_bad2(_: Rc) {} - | ^^^^^^^^^^^ help: try: `Rc` + | ^^^^^^^^^^^ + | +help: try + | +LL - fn func_bad2(_: Rc) {} +LL + fn func_bad2(_: Rc) {} + | -error: usage of `Rc` when T is a buffer type - --> tests/ui/rc_buffer.rs:28:17 +error: usage of `Rc` when `T` is a buffer type + --> tests/ui/rc_buffer.rs:27:17 | LL | fn func_bad3(_: Rc>) {} - | ^^^^^^^^^^^ help: try: `Rc<[u8]>` + | ^^^^^^^^^^^ + | +help: try + | +LL - fn func_bad3(_: Rc>) {} +LL + fn func_bad3(_: Rc<[u8]>) {} + | -error: usage of `Rc` when T is a buffer type - --> tests/ui/rc_buffer.rs:30:17 +error: usage of `Rc` when `T` is a buffer type + --> tests/ui/rc_buffer.rs:29:17 | LL | fn func_bad4(_: Rc) {} - | ^^^^^^^^^^^^ help: try: `Rc` + | ^^^^^^^^^^^^ + | +help: try + | +LL - fn func_bad4(_: Rc) {} +LL + fn func_bad4(_: Rc) {} + | + +error: usage of `Rc` when `T` is a buffer type + --> tests/ui/rc_buffer.rs:35:15 + | +LL | fn foo(_: std::rc::Rc>) {} + | ^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - fn foo(_: std::rc::Rc>) {} +LL + fn foo(_: std::rc::Rc<[u8]>) {} + | -error: aborting due to 8 previous errors +error: aborting due to 9 previous errors diff --git a/tests/ui/rc_buffer_arc.fixed b/tests/ui/rc_buffer_arc.fixed index 27059e3f2e1fd..36b989ec1b605 100644 --- a/tests/ui/rc_buffer_arc.fixed +++ b/tests/ui/rc_buffer_arc.fixed @@ -1,5 +1,4 @@ #![warn(clippy::rc_buffer)] -#![allow(dead_code, unused_imports)] use std::ffi::OsString; use std::path::PathBuf; @@ -31,4 +30,9 @@ fn func_bad4(_: Arc) {} // does not trigger lint fn func_good1(_: Arc>) {} +mod issue_15802 { + fn foo(_: std::sync::Arc<[u8]>) {} + //~^ rc_buffer +} + fn main() {} diff --git a/tests/ui/rc_buffer_arc.rs b/tests/ui/rc_buffer_arc.rs index 5261eae2f26ac..f8e78dc5c18f3 100644 --- a/tests/ui/rc_buffer_arc.rs +++ b/tests/ui/rc_buffer_arc.rs @@ -1,5 +1,4 @@ #![warn(clippy::rc_buffer)] -#![allow(dead_code, unused_imports)] use std::ffi::OsString; use std::path::PathBuf; @@ -31,4 +30,9 @@ fn func_bad4(_: Arc) {} // does not trigger lint fn func_good1(_: Arc>) {} +mod issue_15802 { + fn foo(_: std::sync::Arc>) {} + //~^ rc_buffer +} + fn main() {} diff --git a/tests/ui/rc_buffer_arc.stderr b/tests/ui/rc_buffer_arc.stderr index 786715463232b..043f7a15ec00c 100644 --- a/tests/ui/rc_buffer_arc.stderr +++ b/tests/ui/rc_buffer_arc.stderr @@ -1,53 +1,112 @@ -error: usage of `Arc` when T is a buffer type - --> tests/ui/rc_buffer_arc.rs:10:11 +error: usage of `Arc` when `T` is a buffer type + --> tests/ui/rc_buffer_arc.rs:9:11 | LL | bad1: Arc, - | ^^^^^^^^^^^ help: try: `Arc` + | ^^^^^^^^^^^ | = note: `-D clippy::rc-buffer` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::rc_buffer)]` +help: try + | +LL - bad1: Arc, +LL + bad1: Arc, + | -error: usage of `Arc` when T is a buffer type - --> tests/ui/rc_buffer_arc.rs:12:11 +error: usage of `Arc` when `T` is a buffer type + --> tests/ui/rc_buffer_arc.rs:11:11 | LL | bad2: Arc, - | ^^^^^^^^^^^^ help: try: `Arc` + | ^^^^^^^^^^^^ + | +help: try + | +LL - bad2: Arc, +LL + bad2: Arc, + | -error: usage of `Arc` when T is a buffer type - --> tests/ui/rc_buffer_arc.rs:14:11 +error: usage of `Arc` when `T` is a buffer type + --> tests/ui/rc_buffer_arc.rs:13:11 | LL | bad3: Arc>, - | ^^^^^^^^^^^^ help: try: `Arc<[u8]>` + | ^^^^^^^^^^^^ + | +help: try + | +LL - bad3: Arc>, +LL + bad3: Arc<[u8]>, + | -error: usage of `Arc` when T is a buffer type - --> tests/ui/rc_buffer_arc.rs:16:11 +error: usage of `Arc` when `T` is a buffer type + --> tests/ui/rc_buffer_arc.rs:15:11 | LL | bad4: Arc, - | ^^^^^^^^^^^^^ help: try: `Arc` + | ^^^^^^^^^^^^^ + | +help: try + | +LL - bad4: Arc, +LL + bad4: Arc, + | -error: usage of `Arc` when T is a buffer type - --> tests/ui/rc_buffer_arc.rs:23:17 +error: usage of `Arc` when `T` is a buffer type + --> tests/ui/rc_buffer_arc.rs:22:17 | LL | fn func_bad1(_: Arc) {} - | ^^^^^^^^^^^ help: try: `Arc` + | ^^^^^^^^^^^ + | +help: try + | +LL - fn func_bad1(_: Arc) {} +LL + fn func_bad1(_: Arc) {} + | -error: usage of `Arc` when T is a buffer type - --> tests/ui/rc_buffer_arc.rs:25:17 +error: usage of `Arc` when `T` is a buffer type + --> tests/ui/rc_buffer_arc.rs:24:17 | LL | fn func_bad2(_: Arc) {} - | ^^^^^^^^^^^^ help: try: `Arc` + | ^^^^^^^^^^^^ + | +help: try + | +LL - fn func_bad2(_: Arc) {} +LL + fn func_bad2(_: Arc) {} + | -error: usage of `Arc` when T is a buffer type - --> tests/ui/rc_buffer_arc.rs:27:17 +error: usage of `Arc` when `T` is a buffer type + --> tests/ui/rc_buffer_arc.rs:26:17 | LL | fn func_bad3(_: Arc>) {} - | ^^^^^^^^^^^^ help: try: `Arc<[u8]>` + | ^^^^^^^^^^^^ + | +help: try + | +LL - fn func_bad3(_: Arc>) {} +LL + fn func_bad3(_: Arc<[u8]>) {} + | -error: usage of `Arc` when T is a buffer type - --> tests/ui/rc_buffer_arc.rs:29:17 +error: usage of `Arc` when `T` is a buffer type + --> tests/ui/rc_buffer_arc.rs:28:17 | LL | fn func_bad4(_: Arc) {} - | ^^^^^^^^^^^^^ help: try: `Arc` + | ^^^^^^^^^^^^^ + | +help: try + | +LL - fn func_bad4(_: Arc) {} +LL + fn func_bad4(_: Arc) {} + | + +error: usage of `Arc` when `T` is a buffer type + --> tests/ui/rc_buffer_arc.rs:34:15 + | +LL | fn foo(_: std::sync::Arc>) {} + | ^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL - fn foo(_: std::sync::Arc>) {} +LL + fn foo(_: std::sync::Arc<[u8]>) {} + | -error: aborting due to 8 previous errors +error: aborting due to 9 previous errors diff --git a/tests/ui/replace_box.fixed b/tests/ui/replace_box.fixed index 58c8ed1691d77..e3fc7190a9c9c 100644 --- a/tests/ui/replace_box.fixed +++ b/tests/ui/replace_box.fixed @@ -70,3 +70,74 @@ fn main() { let bb: Box; bb = Default::default(); } + +fn issue15951() { + struct Foo { + inner: String, + } + + fn embedded_body() { + let mut x = Box::new(()); + let y = x; + x = Box::new(()); + + let mut x = Box::new(Foo { inner: String::new() }); + let y = x.inner; + *x = Foo { inner: String::new() }; + //~^ replace_box + } + + let mut x = Box::new(Foo { inner: String::new() }); + let in_closure = || { + *x = Foo { inner: String::new() }; + //~^ replace_box + }; +} + +static R: fn(&mut Box) = |x| **x = String::new(); +//~^ replace_box + +fn field() { + struct T { + content: String, + } + + impl T { + fn new() -> Self { + Self { content: String::new() } + } + } + + struct S { + b: Box, + } + + let mut s = S { b: Box::new(T::new()) }; + let _b = s.b; + s.b = Box::new(T::new()); + + // Interestingly, the lint and fix are valid here as `s.b` is not really moved + let mut s = S { b: Box::new(T::new()) }; + _ = s.b; + *s.b = T::new(); + //~^ replace_box + + let mut s = S { b: Box::new(T::new()) }; + *s.b = T::new(); + //~^ replace_box + + struct Q(Box); + let mut q = Q(Box::new(T::new())); + let _b = q.0; + q.0 = Box::new(T::new()); + + let mut q = Q(Box::new(T::new())); + _ = q.0; + *q.0 = T::new(); + //~^ replace_box + + // This one is a false negative, but it will need MIR analysis to work properly + let mut x = Box::new(String::new()); + x = Box::new(String::new()); + x; +} diff --git a/tests/ui/replace_box.rs b/tests/ui/replace_box.rs index e1fb223e4f211..1d5ca1b24994f 100644 --- a/tests/ui/replace_box.rs +++ b/tests/ui/replace_box.rs @@ -70,3 +70,74 @@ fn main() { let bb: Box; bb = Default::default(); } + +fn issue15951() { + struct Foo { + inner: String, + } + + fn embedded_body() { + let mut x = Box::new(()); + let y = x; + x = Box::new(()); + + let mut x = Box::new(Foo { inner: String::new() }); + let y = x.inner; + x = Box::new(Foo { inner: String::new() }); + //~^ replace_box + } + + let mut x = Box::new(Foo { inner: String::new() }); + let in_closure = || { + x = Box::new(Foo { inner: String::new() }); + //~^ replace_box + }; +} + +static R: fn(&mut Box) = |x| *x = Box::new(String::new()); +//~^ replace_box + +fn field() { + struct T { + content: String, + } + + impl T { + fn new() -> Self { + Self { content: String::new() } + } + } + + struct S { + b: Box, + } + + let mut s = S { b: Box::new(T::new()) }; + let _b = s.b; + s.b = Box::new(T::new()); + + // Interestingly, the lint and fix are valid here as `s.b` is not really moved + let mut s = S { b: Box::new(T::new()) }; + _ = s.b; + s.b = Box::new(T::new()); + //~^ replace_box + + let mut s = S { b: Box::new(T::new()) }; + s.b = Box::new(T::new()); + //~^ replace_box + + struct Q(Box); + let mut q = Q(Box::new(T::new())); + let _b = q.0; + q.0 = Box::new(T::new()); + + let mut q = Q(Box::new(T::new())); + _ = q.0; + q.0 = Box::new(T::new()); + //~^ replace_box + + // This one is a false negative, but it will need MIR analysis to work properly + let mut x = Box::new(String::new()); + x = Box::new(String::new()); + x; +} diff --git a/tests/ui/replace_box.stderr b/tests/ui/replace_box.stderr index 7d9c85da17314..4b7bd4a0eeaeb 100644 --- a/tests/ui/replace_box.stderr +++ b/tests/ui/replace_box.stderr @@ -48,5 +48,53 @@ LL | b = Box::new(mac!(three)); | = note: this creates a needless allocation -error: aborting due to 6 previous errors +error: creating a new box + --> tests/ui/replace_box.rs:86:9 + | +LL | x = Box::new(Foo { inner: String::new() }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace existing content with inner value instead: `*x = Foo { inner: String::new() }` + | + = note: this creates a needless allocation + +error: creating a new box + --> tests/ui/replace_box.rs:92:9 + | +LL | x = Box::new(Foo { inner: String::new() }); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace existing content with inner value instead: `*x = Foo { inner: String::new() }` + | + = note: this creates a needless allocation + +error: creating a new box + --> tests/ui/replace_box.rs:97:38 + | +LL | static R: fn(&mut Box) = |x| *x = Box::new(String::new()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace existing content with inner value instead: `**x = String::new()` + | + = note: this creates a needless allocation + +error: creating a new box + --> tests/ui/replace_box.rs:122:5 + | +LL | s.b = Box::new(T::new()); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: replace existing content with inner value instead: `*s.b = T::new()` + | + = note: this creates a needless allocation + +error: creating a new box + --> tests/ui/replace_box.rs:126:5 + | +LL | s.b = Box::new(T::new()); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: replace existing content with inner value instead: `*s.b = T::new()` + | + = note: this creates a needless allocation + +error: creating a new box + --> tests/ui/replace_box.rs:136:5 + | +LL | q.0 = Box::new(T::new()); + | ^^^^^^^^^^^^^^^^^^^^^^^^ help: replace existing content with inner value instead: `*q.0 = T::new()` + | + = note: this creates a needless allocation + +error: aborting due to 12 previous errors diff --git a/tests/ui/single_range_in_vec_init.1.fixed b/tests/ui/single_range_in_vec_init.1.fixed new file mode 100644 index 0000000000000..0af91907ad05f --- /dev/null +++ b/tests/ui/single_range_in_vec_init.1.fixed @@ -0,0 +1,84 @@ +//@aux-build:proc_macros.rs +#![allow(clippy::no_effect, clippy::unnecessary_operation, clippy::useless_vec, unused)] +#![warn(clippy::single_range_in_vec_init)] + +#[macro_use] +extern crate proc_macros; + +macro_rules! a { + () => { + vec![0..200]; + }; +} + +fn awa(start: T, end: T) { + [start..end]; +} + +fn awa_vec(start: T, end: T) { + vec![start..end]; +} + +fn main() { + // Lint + (0..200).collect::>(); + //~^ single_range_in_vec_init + (0..200).collect::>(); + //~^ single_range_in_vec_init + (0u8..200).collect::>(); + //~^ single_range_in_vec_init + (0usize..200).collect::>(); + //~^ single_range_in_vec_init + (0..200usize).collect::>(); + //~^ single_range_in_vec_init + (0u8..200).collect::>(); + //~^ single_range_in_vec_init + (0usize..200).collect::>(); + //~^ single_range_in_vec_init + (0..200usize).collect::>(); + //~^ single_range_in_vec_init + // Only suggest collect + (0..200isize).collect::>(); + //~^ single_range_in_vec_init + (0..200isize).collect::>(); + //~^ single_range_in_vec_init + // Do not lint + [0..200, 0..100]; + vec![0..200, 0..100]; + [0.0..200.0]; + vec![0.0..200.0]; + // `Copy` is not implemented for `Range`, so this doesn't matter + // FIXME: [0..200; 2]; + // FIXME: [vec!0..200; 2]; + + // Unfortunately skips any macros + a!(); + + // Skip external macros and procedural macros + external! { + [0..200]; + vec![0..200]; + } + with_span! { + span + [0..200]; + vec![0..200]; + } +} + +fn issue16042() { + use std::ops::Range; + + let input = vec![Range { start: 0, end: 5 }]; +} + +fn issue16044() { + macro_rules! as_i32 { + ($x:expr) => { + $x as i32 + }; + } + + let input = (0..as_i32!(10)).collect::>(); + //~^ single_range_in_vec_init +} diff --git a/tests/ui/single_range_in_vec_init.2.fixed b/tests/ui/single_range_in_vec_init.2.fixed new file mode 100644 index 0000000000000..fd6b91360aeb2 --- /dev/null +++ b/tests/ui/single_range_in_vec_init.2.fixed @@ -0,0 +1,84 @@ +//@aux-build:proc_macros.rs +#![allow(clippy::no_effect, clippy::unnecessary_operation, clippy::useless_vec, unused)] +#![warn(clippy::single_range_in_vec_init)] + +#[macro_use] +extern crate proc_macros; + +macro_rules! a { + () => { + vec![0..200]; + }; +} + +fn awa(start: T, end: T) { + [start..end]; +} + +fn awa_vec(start: T, end: T) { + vec![start..end]; +} + +fn main() { + // Lint + [0; 200]; + //~^ single_range_in_vec_init + vec![0; 200]; + //~^ single_range_in_vec_init + [0u8; 200]; + //~^ single_range_in_vec_init + [0usize; 200]; + //~^ single_range_in_vec_init + [0; 200usize]; + //~^ single_range_in_vec_init + vec![0u8; 200]; + //~^ single_range_in_vec_init + vec![0usize; 200]; + //~^ single_range_in_vec_init + vec![0; 200usize]; + //~^ single_range_in_vec_init + // Only suggest collect + (0..200isize).collect::>(); + //~^ single_range_in_vec_init + (0..200isize).collect::>(); + //~^ single_range_in_vec_init + // Do not lint + [0..200, 0..100]; + vec![0..200, 0..100]; + [0.0..200.0]; + vec![0.0..200.0]; + // `Copy` is not implemented for `Range`, so this doesn't matter + // FIXME: [0..200; 2]; + // FIXME: [vec!0..200; 2]; + + // Unfortunately skips any macros + a!(); + + // Skip external macros and procedural macros + external! { + [0..200]; + vec![0..200]; + } + with_span! { + span + [0..200]; + vec![0..200]; + } +} + +fn issue16042() { + use std::ops::Range; + + let input = vec![Range { start: 0, end: 5 }]; +} + +fn issue16044() { + macro_rules! as_i32 { + ($x:expr) => { + $x as i32 + }; + } + + let input = (0..as_i32!(10)).collect::>(); + //~^ single_range_in_vec_init +} diff --git a/tests/ui/single_range_in_vec_init.rs b/tests/ui/single_range_in_vec_init.rs index 0888019e101ce..1cc2b894c0348 100644 --- a/tests/ui/single_range_in_vec_init.rs +++ b/tests/ui/single_range_in_vec_init.rs @@ -1,5 +1,4 @@ //@aux-build:proc_macros.rs -//@no-rustfix: overlapping suggestions #![allow(clippy::no_effect, clippy::unnecessary_operation, clippy::useless_vec, unused)] #![warn(clippy::single_range_in_vec_init)] @@ -66,3 +65,20 @@ fn main() { vec![0..200]; } } + +fn issue16042() { + use std::ops::Range; + + let input = vec![Range { start: 0, end: 5 }]; +} + +fn issue16044() { + macro_rules! as_i32 { + ($x:expr) => { + $x as i32 + }; + } + + let input = vec![0..as_i32!(10)]; + //~^ single_range_in_vec_init +} diff --git a/tests/ui/single_range_in_vec_init.stderr b/tests/ui/single_range_in_vec_init.stderr index b21338e38a3cb..d93379777d39c 100644 --- a/tests/ui/single_range_in_vec_init.stderr +++ b/tests/ui/single_range_in_vec_init.stderr @@ -1,5 +1,5 @@ error: an array of `Range` that is only one element - --> tests/ui/single_range_in_vec_init.rs:25:5 + --> tests/ui/single_range_in_vec_init.rs:24:5 | LL | [0..200]; | ^^^^^^^^ @@ -18,7 +18,7 @@ LL + [0; 200]; | error: a `Vec` of `Range` that is only one element - --> tests/ui/single_range_in_vec_init.rs:27:5 + --> tests/ui/single_range_in_vec_init.rs:26:5 | LL | vec![0..200]; | ^^^^^^^^^^^^ @@ -35,7 +35,7 @@ LL + vec![0; 200]; | error: an array of `Range` that is only one element - --> tests/ui/single_range_in_vec_init.rs:29:5 + --> tests/ui/single_range_in_vec_init.rs:28:5 | LL | [0u8..200]; | ^^^^^^^^^^ @@ -52,7 +52,7 @@ LL + [0u8; 200]; | error: an array of `Range` that is only one element - --> tests/ui/single_range_in_vec_init.rs:31:5 + --> tests/ui/single_range_in_vec_init.rs:30:5 | LL | [0usize..200]; | ^^^^^^^^^^^^^ @@ -69,7 +69,7 @@ LL + [0usize; 200]; | error: an array of `Range` that is only one element - --> tests/ui/single_range_in_vec_init.rs:33:5 + --> tests/ui/single_range_in_vec_init.rs:32:5 | LL | [0..200usize]; | ^^^^^^^^^^^^^ @@ -86,7 +86,7 @@ LL + [0; 200usize]; | error: a `Vec` of `Range` that is only one element - --> tests/ui/single_range_in_vec_init.rs:35:5 + --> tests/ui/single_range_in_vec_init.rs:34:5 | LL | vec![0u8..200]; | ^^^^^^^^^^^^^^ @@ -103,7 +103,7 @@ LL + vec![0u8; 200]; | error: a `Vec` of `Range` that is only one element - --> tests/ui/single_range_in_vec_init.rs:37:5 + --> tests/ui/single_range_in_vec_init.rs:36:5 | LL | vec![0usize..200]; | ^^^^^^^^^^^^^^^^^ @@ -120,7 +120,7 @@ LL + vec![0usize; 200]; | error: a `Vec` of `Range` that is only one element - --> tests/ui/single_range_in_vec_init.rs:39:5 + --> tests/ui/single_range_in_vec_init.rs:38:5 | LL | vec![0..200usize]; | ^^^^^^^^^^^^^^^^^ @@ -137,7 +137,7 @@ LL + vec![0; 200usize]; | error: an array of `Range` that is only one element - --> tests/ui/single_range_in_vec_init.rs:42:5 + --> tests/ui/single_range_in_vec_init.rs:41:5 | LL | [0..200isize]; | ^^^^^^^^^^^^^ @@ -149,7 +149,7 @@ LL + (0..200isize).collect::>(); | error: a `Vec` of `Range` that is only one element - --> tests/ui/single_range_in_vec_init.rs:44:5 + --> tests/ui/single_range_in_vec_init.rs:43:5 | LL | vec![0..200isize]; | ^^^^^^^^^^^^^^^^^ @@ -160,5 +160,17 @@ LL - vec![0..200isize]; LL + (0..200isize).collect::>(); | -error: aborting due to 10 previous errors +error: a `Vec` of `Range` that is only one element + --> tests/ui/single_range_in_vec_init.rs:82:17 + | +LL | let input = vec![0..as_i32!(10)]; + | ^^^^^^^^^^^^^^^^^^^^ + | +help: if you wanted a `Vec` that contains the entire range, try + | +LL - let input = vec![0..as_i32!(10)]; +LL + let input = (0..as_i32!(10)).collect::>(); + | + +error: aborting due to 11 previous errors diff --git a/tests/ui/unnecessary_map_on_constructor.stderr b/tests/ui/unnecessary_map_on_constructor.stderr index f29bfec60f726..a191168208088 100644 --- a/tests/ui/unnecessary_map_on_constructor.stderr +++ b/tests/ui/unnecessary_map_on_constructor.stderr @@ -1,4 +1,4 @@ -error: unnecessary map on constructor Some(_) +error: unnecessary `map` on constructor `Some(_)` --> tests/ui/unnecessary_map_on_constructor.rs:32:13 | LL | let a = Some(x).map(fun); @@ -7,43 +7,43 @@ LL | let a = Some(x).map(fun); = note: `-D clippy::unnecessary-map-on-constructor` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::unnecessary_map_on_constructor)]` -error: unnecessary map on constructor Ok(_) +error: unnecessary `map` on constructor `Ok(_)` --> tests/ui/unnecessary_map_on_constructor.rs:34:27 | LL | let b: SimpleResult = Ok(x).map(fun); | ^^^^^^^^^^^^^^ help: try: `Ok(fun(x))` -error: unnecessary map_err on constructor Err(_) +error: unnecessary `map_err` on constructor `Err(_)` --> tests/ui/unnecessary_map_on_constructor.rs:36:27 | LL | let c: SimpleResult = Err(err).map_err(notfun); | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Err(notfun(err))` -error: unnecessary map on constructor Option::Some(_) +error: unnecessary `map` on constructor `Option::Some(_)` --> tests/ui/unnecessary_map_on_constructor.rs:39:13 | LL | let a = Option::Some(x).map(fun); | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Option::Some(fun(x))` -error: unnecessary map on constructor SimpleResult::Ok(_) +error: unnecessary `map` on constructor `SimpleResult::Ok(_)` --> tests/ui/unnecessary_map_on_constructor.rs:41:27 | LL | let b: SimpleResult = SimpleResult::Ok(x).map(fun); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `SimpleResult::Ok(fun(x))` -error: unnecessary map_err on constructor SimpleResult::Err(_) +error: unnecessary `map_err` on constructor `SimpleResult::Err(_)` --> tests/ui/unnecessary_map_on_constructor.rs:43:27 | LL | let c: SimpleResult = SimpleResult::Err(err).map_err(notfun); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `SimpleResult::Err(notfun(err))` -error: unnecessary map on constructor Ok(_) +error: unnecessary `map` on constructor `Ok(_)` --> tests/ui/unnecessary_map_on_constructor.rs:45:52 | LL | let b: std::result::Result = Ok(x).map(fun); | ^^^^^^^^^^^^^^ help: try: `Ok(fun(x))` -error: unnecessary map_err on constructor Err(_) +error: unnecessary `map_err` on constructor `Err(_)` --> tests/ui/unnecessary_map_on_constructor.rs:47:52 | LL | let c: std::result::Result = Err(err).map_err(notfun); diff --git a/tests/ui/unnecessary_mut_passed.fixed b/tests/ui/unnecessary_mut_passed.fixed index 63bbadb01dcb2..876b61d29519b 100644 --- a/tests/ui/unnecessary_mut_passed.fixed +++ b/tests/ui/unnecessary_mut_passed.fixed @@ -146,7 +146,7 @@ fn main() { my_struct.takes_raw_mut(a); } -// not supported currently +// These shouldn't be linted, see https://github.com/rust-lang/rust-clippy/pull/15962#issuecomment-3503704832 fn raw_ptrs(my_struct: MyStruct) { let mut n = 42; diff --git a/tests/ui/unnecessary_mut_passed.rs b/tests/ui/unnecessary_mut_passed.rs index b719ca1871b29..e92368bfffeb6 100644 --- a/tests/ui/unnecessary_mut_passed.rs +++ b/tests/ui/unnecessary_mut_passed.rs @@ -146,7 +146,7 @@ fn main() { my_struct.takes_raw_mut(a); } -// not supported currently +// These shouldn't be linted, see https://github.com/rust-lang/rust-clippy/pull/15962#issuecomment-3503704832 fn raw_ptrs(my_struct: MyStruct) { let mut n = 42; diff --git a/tests/ui/unused_enumerate_index.stderr b/tests/ui/unused_enumerate_index.stderr index 14d1d20a66e4b..c742cc8a85ba6 100644 --- a/tests/ui/unused_enumerate_index.stderr +++ b/tests/ui/unused_enumerate_index.stderr @@ -1,8 +1,8 @@ error: you seem to use `.enumerate()` and immediately discard the index - --> tests/ui/unused_enumerate_index.rs:12:19 + --> tests/ui/unused_enumerate_index.rs:12:27 | LL | for (_, x) in v.iter().enumerate() { - | ^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^ | = note: `-D clippy::unused-enumerate-index` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::unused_enumerate_index)]` @@ -13,10 +13,10 @@ LL + for x in v.iter() { | error: you seem to use `.enumerate()` and immediately discard the index - --> tests/ui/unused_enumerate_index.rs:60:19 + --> tests/ui/unused_enumerate_index.rs:60:24 | LL | for (_, x) in dummy.enumerate() { - | ^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^ | help: remove the `.enumerate()` call | @@ -25,10 +25,10 @@ LL + for x in dummy { | error: you seem to use `.enumerate()` and immediately discard the index - --> tests/ui/unused_enumerate_index.rs:65:39 + --> tests/ui/unused_enumerate_index.rs:65:38 | LL | let _ = vec![1, 2, 3].into_iter().enumerate().map(|(_, x)| println!("{x}")); - | ^^^^^^^^^^^ + | ^^^^^^^^^^^^ | help: remove the `.enumerate()` call | @@ -37,10 +37,10 @@ LL + let _ = vec![1, 2, 3].into_iter().map(|x| println!("{x}")); | error: you seem to use `.enumerate()` and immediately discard the index - --> tests/ui/unused_enumerate_index.rs:68:39 + --> tests/ui/unused_enumerate_index.rs:68:38 | LL | let p = vec![1, 2, 3].into_iter().enumerate(); - | ^^^^^^^^^^^ + | ^^^^^^^^^^^^ | help: remove the `.enumerate()` call | @@ -50,10 +50,10 @@ LL ~ p.map(|x| println!("{x}")); | error: you seem to use `.enumerate()` and immediately discard the index - --> tests/ui/unused_enumerate_index.rs:90:17 + --> tests/ui/unused_enumerate_index.rs:90:16 | LL | _ = mac2!().enumerate().map(|(_, _v)| {}); - | ^^^^^^^^^^^ + | ^^^^^^^^^^^^ | help: remove the `.enumerate()` call | @@ -62,10 +62,10 @@ LL + _ = mac2!().map(|_v| {}); | error: you seem to use `.enumerate()` and immediately discard the index - --> tests/ui/unused_enumerate_index.rs:99:39 + --> tests/ui/unused_enumerate_index.rs:99:38 | LL | let v = [1, 2, 3].iter().copied().enumerate(); - | ^^^^^^^^^^^ + | ^^^^^^^^^^^^ | help: remove the `.enumerate()` call | @@ -75,10 +75,10 @@ LL ~ let x = v.map(|x: i32| x).sum::(); | error: you seem to use `.enumerate()` and immediately discard the index - --> tests/ui/unused_enumerate_index.rs:105:39 + --> tests/ui/unused_enumerate_index.rs:105:38 | LL | let v = [1, 2, 3].iter().copied().enumerate(); - | ^^^^^^^^^^^ + | ^^^^^^^^^^^^ | help: remove the `.enumerate()` call | @@ -88,10 +88,10 @@ LL ~ let x = v.map(|x: i32| x).sum::(); | error: you seem to use `.enumerate()` and immediately discard the index - --> tests/ui/unused_enumerate_index.rs:110:39 + --> tests/ui/unused_enumerate_index.rs:110:38 | LL | let v = [1, 2, 3].iter().copied().enumerate(); - | ^^^^^^^^^^^ + | ^^^^^^^^^^^^ | help: remove the `.enumerate()` call | diff --git a/tests/ui/write_literal.fixed b/tests/ui/write_literal.fixed index 29352fd468ea4..ae29f3a574625 100644 --- a/tests/ui/write_literal.fixed +++ b/tests/ui/write_literal.fixed @@ -70,6 +70,55 @@ fn main() { //~^ write_literal } +fn escaping() { + let mut v = Vec::new(); + + writeln!(v, "{{hello}}"); + //~^ write_literal + + writeln!(v, r"{{hello}}"); + //~^ write_literal + + writeln!(v, "'"); + //~^ write_literal + + writeln!(v, "\""); + //~^ write_literal + + writeln!(v, r"'"); + //~^ write_literal + + writeln!( + v, + "some hello \ + //~^ write_literal + world!", + ); + writeln!( + v, + "some 1\ + 2 \\ 3", + //~^^^ write_literal + ); + writeln!(v, "\\"); + //~^ write_literal + + writeln!(v, r"\"); + //~^ write_literal + + writeln!(v, r#"\"#); + //~^ write_literal + + writeln!(v, "\\"); + //~^ write_literal + + writeln!(v, "\r"); + //~^ write_literal + + // should not lint + writeln!(v, r"{}", "\r"); +} + fn issue_13959() { let mut v = Vec::new(); writeln!(v, "\""); diff --git a/tests/ui/write_literal.rs b/tests/ui/write_literal.rs index 928727527592e..d930339e106cf 100644 --- a/tests/ui/write_literal.rs +++ b/tests/ui/write_literal.rs @@ -70,6 +70,59 @@ fn main() { //~^ write_literal } +fn escaping() { + let mut v = Vec::new(); + + writeln!(v, "{}", "{hello}"); + //~^ write_literal + + writeln!(v, r"{}", r"{hello}"); + //~^ write_literal + + writeln!(v, "{}", '\''); + //~^ write_literal + + writeln!(v, "{}", '"'); + //~^ write_literal + + writeln!(v, r"{}", '\''); + //~^ write_literal + + writeln!( + v, + "some {}", + "hello \ + //~^ write_literal + world!", + ); + writeln!( + v, + "some {}\ + {} \\ {}", + "1", + "2", + "3", + //~^^^ write_literal + ); + writeln!(v, "{}", "\\"); + //~^ write_literal + + writeln!(v, r"{}", "\\"); + //~^ write_literal + + writeln!(v, r#"{}"#, "\\"); + //~^ write_literal + + writeln!(v, "{}", r"\"); + //~^ write_literal + + writeln!(v, "{}", "\r"); + //~^ write_literal + + // should not lint + writeln!(v, r"{}", "\r"); +} + fn issue_13959() { let mut v = Vec::new(); writeln!(v, "{}", r#"""#); diff --git a/tests/ui/write_literal.stderr b/tests/ui/write_literal.stderr index ca37406c81141..374098fa2b14d 100644 --- a/tests/ui/write_literal.stderr +++ b/tests/ui/write_literal.stderr @@ -145,7 +145,156 @@ LL + writeln!(v, "hello {0} {1}, world {2}", 2, 3, 4); | error: literal with an empty format string - --> tests/ui/write_literal.rs:75:23 + --> tests/ui/write_literal.rs:76:23 + | +LL | writeln!(v, "{}", "{hello}"); + | ^^^^^^^^^ + | +help: try + | +LL - writeln!(v, "{}", "{hello}"); +LL + writeln!(v, "{{hello}}"); + | + +error: literal with an empty format string + --> tests/ui/write_literal.rs:79:24 + | +LL | writeln!(v, r"{}", r"{hello}"); + | ^^^^^^^^^^ + | +help: try + | +LL - writeln!(v, r"{}", r"{hello}"); +LL + writeln!(v, r"{{hello}}"); + | + +error: literal with an empty format string + --> tests/ui/write_literal.rs:82:23 + | +LL | writeln!(v, "{}", '\''); + | ^^^^ + | +help: try + | +LL - writeln!(v, "{}", '\''); +LL + writeln!(v, "'"); + | + +error: literal with an empty format string + --> tests/ui/write_literal.rs:85:23 + | +LL | writeln!(v, "{}", '"'); + | ^^^ + | +help: try + | +LL - writeln!(v, "{}", '"'); +LL + writeln!(v, "\""); + | + +error: literal with an empty format string + --> tests/ui/write_literal.rs:88:24 + | +LL | writeln!(v, r"{}", '\''); + | ^^^^ + | +help: try + | +LL - writeln!(v, r"{}", '\''); +LL + writeln!(v, r"'"); + | + +error: literal with an empty format string + --> tests/ui/write_literal.rs:94:9 + | +LL | / "hello \ +LL | | +LL | | world!", + | |_______________^ + | +help: try + | +LL ~ "some hello \ +LL + +LL ~ world!", + | + +error: literal with an empty format string + --> tests/ui/write_literal.rs:102:9 + | +LL | / "1", +LL | | "2", +LL | | "3", + | |___________^ + | +help: try + | +LL ~ "some 1\ +LL ~ 2 \\ 3", + | + +error: literal with an empty format string + --> tests/ui/write_literal.rs:107:23 + | +LL | writeln!(v, "{}", "\\"); + | ^^^^ + | +help: try + | +LL - writeln!(v, "{}", "\\"); +LL + writeln!(v, "\\"); + | + +error: literal with an empty format string + --> tests/ui/write_literal.rs:110:24 + | +LL | writeln!(v, r"{}", "\\"); + | ^^^^ + | +help: try + | +LL - writeln!(v, r"{}", "\\"); +LL + writeln!(v, r"\"); + | + +error: literal with an empty format string + --> tests/ui/write_literal.rs:113:26 + | +LL | writeln!(v, r#"{}"#, "\\"); + | ^^^^ + | +help: try + | +LL - writeln!(v, r#"{}"#, "\\"); +LL + writeln!(v, r#"\"#); + | + +error: literal with an empty format string + --> tests/ui/write_literal.rs:116:23 + | +LL | writeln!(v, "{}", r"\"); + | ^^^^ + | +help: try + | +LL - writeln!(v, "{}", r"\"); +LL + writeln!(v, "\\"); + | + +error: literal with an empty format string + --> tests/ui/write_literal.rs:119:23 + | +LL | writeln!(v, "{}", "\r"); + | ^^^^ + | +help: try + | +LL - writeln!(v, "{}", "\r"); +LL + writeln!(v, "\r"); + | + +error: literal with an empty format string + --> tests/ui/write_literal.rs:128:23 | LL | writeln!(v, "{}", r#"""#); | ^^^^^^ @@ -157,7 +306,7 @@ LL + writeln!(v, "\""); | error: literal with an empty format string - --> tests/ui/write_literal.rs:80:9 + --> tests/ui/write_literal.rs:133:9 | LL | / r#" LL | | @@ -182,7 +331,7 @@ LL ~ " | error: literal with an empty format string - --> tests/ui/write_literal.rs:94:55 + --> tests/ui/write_literal.rs:147:55 | LL | writeln!(v, "Hello {3} is {0:2$.1$}", 0.01, 2, 3, "x"); | ^^^ @@ -194,7 +343,7 @@ LL + writeln!(v, "Hello x is {0:2$.1$}", 0.01, 2, 3); | error: literal with an empty format string - --> tests/ui/write_literal.rs:96:52 + --> tests/ui/write_literal.rs:149:52 | LL | writeln!(v, "Hello {2} is {0:3$.1$}", 0.01, 2, "x", 3); | ^^^ @@ -206,7 +355,7 @@ LL + writeln!(v, "Hello x is {0:2$.1$}", 0.01, 2, 3); | error: literal with an empty format string - --> tests/ui/write_literal.rs:98:49 + --> tests/ui/write_literal.rs:151:49 | LL | writeln!(v, "Hello {1} is {0:3$.2$}", 0.01, "x", 2, 3); | ^^^ @@ -218,7 +367,7 @@ LL + writeln!(v, "Hello x is {0:2$.1$}", 0.01, 2, 3); | error: literal with an empty format string - --> tests/ui/write_literal.rs:100:43 + --> tests/ui/write_literal.rs:153:43 | LL | writeln!(v, "Hello {0} is {1:3$.2$}", "x", 0.01, 2, 3); | ^^^ @@ -229,5 +378,5 @@ LL - writeln!(v, "Hello {0} is {1:3$.2$}", "x", 0.01, 2, 3); LL + writeln!(v, "Hello x is {0:2$.1$}", 0.01, 2, 3); | -error: aborting due to 18 previous errors +error: aborting due to 30 previous errors diff --git a/tests/ui/write_literal_2.rs b/tests/ui/write_literal_2.rs deleted file mode 100644 index f896782aaf3b5..0000000000000 --- a/tests/ui/write_literal_2.rs +++ /dev/null @@ -1,65 +0,0 @@ -//@no-rustfix: overlapping suggestions -#![allow(unused_must_use)] -#![warn(clippy::write_literal)] - -use std::io::Write; - -fn main() { - let mut v = Vec::new(); - - writeln!(v, "{}", "{hello}"); - //~^ write_literal - - writeln!(v, r"{}", r"{hello}"); - //~^ write_literal - - writeln!(v, "{}", '\''); - //~^ write_literal - - writeln!(v, "{}", '"'); - //~^ write_literal - - writeln!(v, r"{}", '"'); - //~^ write_literal - - writeln!(v, r"{}", '\''); - //~^ write_literal - - writeln!( - v, - "some {}", - "hello \ - //~^ write_literal - world!", - ); - writeln!( - v, - "some {}\ - {} \\ {}", - "1", - "2", - "3", - //~^^^ write_literal - ); - writeln!(v, "{}", "\\"); - //~^ write_literal - - writeln!(v, r"{}", "\\"); - //~^ write_literal - - writeln!(v, r#"{}"#, "\\"); - //~^ write_literal - - writeln!(v, "{}", r"\"); - //~^ write_literal - - writeln!(v, "{}", "\r"); - //~^ write_literal - - // hard mode - writeln!(v, r#"{}{}"#, '#', '"'); - //~^ write_literal - - // should not lint - writeln!(v, r"{}", "\r"); -} diff --git a/tests/ui/write_literal_2.stderr b/tests/ui/write_literal_2.stderr deleted file mode 100644 index 29803d6a8b185..0000000000000 --- a/tests/ui/write_literal_2.stderr +++ /dev/null @@ -1,165 +0,0 @@ -error: literal with an empty format string - --> tests/ui/write_literal_2.rs:10:23 - | -LL | writeln!(v, "{}", "{hello}"); - | ^^^^^^^^^ - | - = note: `-D clippy::write-literal` implied by `-D warnings` - = help: to override `-D warnings` add `#[allow(clippy::write_literal)]` -help: try - | -LL - writeln!(v, "{}", "{hello}"); -LL + writeln!(v, "{{hello}}"); - | - -error: literal with an empty format string - --> tests/ui/write_literal_2.rs:13:24 - | -LL | writeln!(v, r"{}", r"{hello}"); - | ^^^^^^^^^^ - | -help: try - | -LL - writeln!(v, r"{}", r"{hello}"); -LL + writeln!(v, r"{{hello}}"); - | - -error: literal with an empty format string - --> tests/ui/write_literal_2.rs:16:23 - | -LL | writeln!(v, "{}", '\''); - | ^^^^ - | -help: try - | -LL - writeln!(v, "{}", '\''); -LL + writeln!(v, "'"); - | - -error: literal with an empty format string - --> tests/ui/write_literal_2.rs:19:23 - | -LL | writeln!(v, "{}", '"'); - | ^^^ - | -help: try - | -LL - writeln!(v, "{}", '"'); -LL + writeln!(v, "\""); - | - -error: literal with an empty format string - --> tests/ui/write_literal_2.rs:22:24 - | -LL | writeln!(v, r"{}", '"'); - | ^^^ - -error: literal with an empty format string - --> tests/ui/write_literal_2.rs:25:24 - | -LL | writeln!(v, r"{}", '\''); - | ^^^^ - | -help: try - | -LL - writeln!(v, r"{}", '\''); -LL + writeln!(v, r"'"); - | - -error: literal with an empty format string - --> tests/ui/write_literal_2.rs:31:9 - | -LL | / "hello \ -LL | | -LL | | world!", - | |_______________^ - | -help: try - | -LL ~ "some hello \ -LL + -LL ~ world!", - | - -error: literal with an empty format string - --> tests/ui/write_literal_2.rs:39:9 - | -LL | / "1", -LL | | "2", -LL | | "3", - | |___________^ - | -help: try - | -LL ~ "some 1\ -LL ~ 2 \\ 3", - | - -error: literal with an empty format string - --> tests/ui/write_literal_2.rs:44:23 - | -LL | writeln!(v, "{}", "\\"); - | ^^^^ - | -help: try - | -LL - writeln!(v, "{}", "\\"); -LL + writeln!(v, "\\"); - | - -error: literal with an empty format string - --> tests/ui/write_literal_2.rs:47:24 - | -LL | writeln!(v, r"{}", "\\"); - | ^^^^ - | -help: try - | -LL - writeln!(v, r"{}", "\\"); -LL + writeln!(v, r"\"); - | - -error: literal with an empty format string - --> tests/ui/write_literal_2.rs:50:26 - | -LL | writeln!(v, r#"{}"#, "\\"); - | ^^^^ - | -help: try - | -LL - writeln!(v, r#"{}"#, "\\"); -LL + writeln!(v, r#"\"#); - | - -error: literal with an empty format string - --> tests/ui/write_literal_2.rs:53:23 - | -LL | writeln!(v, "{}", r"\"); - | ^^^^ - | -help: try - | -LL - writeln!(v, "{}", r"\"); -LL + writeln!(v, "\\"); - | - -error: literal with an empty format string - --> tests/ui/write_literal_2.rs:56:23 - | -LL | writeln!(v, "{}", "\r"); - | ^^^^ - | -help: try - | -LL - writeln!(v, "{}", "\r"); -LL + writeln!(v, "\r"); - | - -error: literal with an empty format string - --> tests/ui/write_literal_2.rs:60:28 - | -LL | writeln!(v, r#"{}{}"#, '#', '"'); - | ^^^^^^^^ - -error: aborting due to 14 previous errors - diff --git a/tests/ui/write_literal_unfixable.rs b/tests/ui/write_literal_unfixable.rs new file mode 100644 index 0000000000000..3a5660180779d --- /dev/null +++ b/tests/ui/write_literal_unfixable.rs @@ -0,0 +1,16 @@ +//@no-rustfix +#![allow(unused_must_use)] +#![warn(clippy::write_literal)] + +use std::io::Write; + +fn escaping() { + let mut v = vec![]; + + writeln!(v, r"{}", '"'); + //~^ write_literal + + // hard mode + writeln!(v, r#"{}{}"#, '#', '"'); + //~^ write_literal +} diff --git a/tests/ui/write_literal_unfixable.stderr b/tests/ui/write_literal_unfixable.stderr new file mode 100644 index 0000000000000..0dd40e891893d --- /dev/null +++ b/tests/ui/write_literal_unfixable.stderr @@ -0,0 +1,17 @@ +error: literal with an empty format string + --> tests/ui/write_literal_unfixable.rs:10:24 + | +LL | writeln!(v, r"{}", '"'); + | ^^^ + | + = note: `-D clippy::write-literal` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::write_literal)]` + +error: literal with an empty format string + --> tests/ui/write_literal_unfixable.rs:14:28 + | +LL | writeln!(v, r#"{}{}"#, '#', '"'); + | ^^^^^^^^ + +error: aborting due to 2 previous errors + diff --git a/util/gh-pages/index_template.html b/util/gh-pages/index_template.html index d34ff0a597329..e443baff08086 100644 --- a/util/gh-pages/index_template.html +++ b/util/gh-pages/index_template.html @@ -28,7 +28,7 @@ {# #} {# #} {# #} -
{# #} +