From 919ed2ba03f6bf7b02df6eda53d1cbfc28161448 Mon Sep 17 00:00:00 2001 From: Cameron Steffen Date: Tue, 4 May 2021 16:22:43 -0500 Subject: [PATCH 01/58] Fix needless_quesiton_mark false positive --- clippy_lints/src/needless_question_mark.rs | 112 ++++----------------- tests/ui/needless_question_mark.fixed | 5 + tests/ui/needless_question_mark.rs | 5 + tests/ui/needless_question_mark.stderr | 2 +- 4 files changed, 30 insertions(+), 94 deletions(-) diff --git a/clippy_lints/src/needless_question_mark.rs b/clippy_lints/src/needless_question_mark.rs index 7b156a8c49d..5c9cce6aad4 100644 --- a/clippy_lints/src/needless_question_mark.rs +++ b/clippy_lints/src/needless_question_mark.rs @@ -1,14 +1,13 @@ use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::is_lang_ctor; use clippy_utils::source::snippet; -use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::{differing_macro_contexts, is_lang_ctor}; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::LangItem::{OptionSome, ResultOk}; use rustc_hir::{Body, Expr, ExprKind, LangItem, MatchSource, QPath}; use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::TyS; use rustc_session::{declare_lint_pass, declare_tool_lint}; -use rustc_span::sym; declare_clippy_lint! { /// **What it does:** @@ -63,12 +62,6 @@ declare_clippy_lint! { declare_lint_pass!(NeedlessQuestionMark => [NEEDLESS_QUESTION_MARK]); -#[derive(Debug)] -enum SomeOkCall<'a> { - SomeCall(&'a Expr<'a>, &'a Expr<'a>), - OkCall(&'a Expr<'a>, &'a Expr<'a>), -} - impl LateLintPass<'_> for NeedlessQuestionMark { /* * The question mark operator is compatible with both Result and Option, @@ -90,104 +83,37 @@ impl LateLintPass<'_> for NeedlessQuestionMark { */ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) { - let e = match &expr.kind { - ExprKind::Ret(Some(e)) => e, - _ => return, - }; - - if let Some(ok_some_call) = is_some_or_ok_call(cx, e) { - emit_lint(cx, &ok_some_call); + if let ExprKind::Ret(Some(e)) = expr.kind { + check(cx, e); } } fn check_body(&mut self, cx: &LateContext<'_>, body: &'_ Body<'_>) { - // Function / Closure block - let expr_opt = if let ExprKind::Block(block, _) = &body.value.kind { - block.expr - } else { - // Single line closure - Some(&body.value) - }; - - if_chain! { - if let Some(expr) = expr_opt; - if let Some(ok_some_call) = is_some_or_ok_call(cx, expr); - then { - emit_lint(cx, &ok_some_call); - } - }; + check(cx, body.value.peel_blocks()); } } -fn emit_lint(cx: &LateContext<'_>, expr: &SomeOkCall<'_>) { - let (entire_expr, inner_expr) = match expr { - SomeOkCall::OkCall(outer, inner) | SomeOkCall::SomeCall(outer, inner) => (outer, inner), +fn check(cx: &LateContext<'_>, expr: &Expr<'_>) { + let inner_expr = if_chain! { + if let ExprKind::Call(path, [arg]) = &expr.kind; + if let ExprKind::Path(ref qpath) = &path.kind; + if is_lang_ctor(cx, qpath, OptionSome) || is_lang_ctor(cx, qpath, ResultOk); + if let ExprKind::Match(inner_expr_with_q, _, MatchSource::TryDesugar) = &arg.kind; + if let ExprKind::Call(called, [inner_expr]) = &inner_expr_with_q.kind; + if let ExprKind::Path(QPath::LangItem(LangItem::TryIntoResult, _)) = &called.kind; + if expr.span.ctxt() == inner_expr.span.ctxt(); + let expr_ty = cx.typeck_results().expr_ty(expr); + let inner_ty = cx.typeck_results().expr_ty(inner_expr); + if TyS::same_type(expr_ty, inner_ty); + then { inner_expr } else { return; } }; - span_lint_and_sugg( cx, NEEDLESS_QUESTION_MARK, - entire_expr.span, + expr.span, "question mark operator is useless here", "try", format!("{}", snippet(cx, inner_expr.span, r#""...""#)), Applicability::MachineApplicable, ); } - -fn is_some_or_ok_call<'a>(cx: &'a LateContext<'_>, expr: &'a Expr<'_>) -> Option> { - if_chain! { - // Check outer expression matches CALL_IDENT(ARGUMENT) format - if let ExprKind::Call(path, args) = &expr.kind; - if let ExprKind::Path(ref qpath) = &path.kind; - if is_lang_ctor(cx, qpath, OptionSome) || is_lang_ctor(cx, qpath, ResultOk); - - // Extract inner expression from ARGUMENT - if let ExprKind::Match(inner_expr_with_q, _, MatchSource::TryDesugar) = &args[0].kind; - if let ExprKind::Call(called, args) = &inner_expr_with_q.kind; - if args.len() == 1; - - if let ExprKind::Path(QPath::LangItem(LangItem::TryIntoResult, _)) = &called.kind; - then { - // Extract inner expr type from match argument generated by - // question mark operator - let inner_expr = &args[0]; - - // if the inner expr is inside macro but the outer one is not, do not lint (#6921) - if differing_macro_contexts(expr.span, inner_expr.span) { - return None; - } - - let inner_ty = cx.typeck_results().expr_ty(inner_expr); - let outer_ty = cx.typeck_results().expr_ty(expr); - - // Check if outer and inner type are Option - let outer_is_some = is_type_diagnostic_item(cx, outer_ty, sym::option_type); - let inner_is_some = is_type_diagnostic_item(cx, inner_ty, sym::option_type); - - // Check for Option MSRV - if outer_is_some && inner_is_some { - return Some(SomeOkCall::SomeCall(expr, inner_expr)); - } - - // Check if outer and inner type are Result - let outer_is_result = is_type_diagnostic_item(cx, outer_ty, sym::result_type); - let inner_is_result = is_type_diagnostic_item(cx, inner_ty, sym::result_type); - - // Additional check: if the error type of the Result can be converted - // via the From trait, then don't match - let does_not_call_from = !has_implicit_error_from(cx, expr, inner_expr); - - // Must meet Result MSRV - if outer_is_result && inner_is_result && does_not_call_from { - return Some(SomeOkCall::OkCall(expr, inner_expr)); - } - } - } - - None -} - -fn has_implicit_error_from(cx: &LateContext<'_>, entire_expr: &Expr<'_>, inner_result_expr: &Expr<'_>) -> bool { - return cx.typeck_results().expr_ty(entire_expr) != cx.typeck_results().expr_ty(inner_result_expr); -} diff --git a/tests/ui/needless_question_mark.fixed b/tests/ui/needless_question_mark.fixed index 52ddd9d2dc8..f1fc81aa12b 100644 --- a/tests/ui/needless_question_mark.fixed +++ b/tests/ui/needless_question_mark.fixed @@ -94,6 +94,11 @@ where Ok(x?) } +// not quite needless +fn deref_ref(s: Option<&String>) -> Option<&str> { + Some(s?) +} + fn main() {} // #6921 if a macro wraps an expr in Some( ) and the ? is in the macro use, diff --git a/tests/ui/needless_question_mark.rs b/tests/ui/needless_question_mark.rs index 1ea4ba0d83f..44a0c5f61b5 100644 --- a/tests/ui/needless_question_mark.rs +++ b/tests/ui/needless_question_mark.rs @@ -94,6 +94,11 @@ where Ok(x?) } +// not quite needless +fn deref_ref(s: Option<&String>) -> Option<&str> { + Some(s?) +} + fn main() {} // #6921 if a macro wraps an expr in Some( ) and the ? is in the macro use, diff --git a/tests/ui/needless_question_mark.stderr b/tests/ui/needless_question_mark.stderr index afd68d91e51..fa698224530 100644 --- a/tests/ui/needless_question_mark.stderr +++ b/tests/ui/needless_question_mark.stderr @@ -67,7 +67,7 @@ LL | return Ok(t.magic?); | ^^^^^^^^^^^^ help: try: `t.magic` error: question mark operator is useless here - --> $DIR/needless_question_mark.rs:115:27 + --> $DIR/needless_question_mark.rs:120:27 | LL | || -> Option<_> { Some(Some($expr)?) }() | ^^^^^^^^^^^^^^^^^^ help: try: `Some($expr)` From 53ffe39b42af3f9a519866dc5428b552a1977e56 Mon Sep 17 00:00:00 2001 From: Andy Wang Date: Mon, 19 Apr 2021 23:27:02 +0100 Subject: [PATCH 02/58] Use local and remapped paths where appropriate --- clippy_lints/src/macro_use.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/clippy_lints/src/macro_use.rs b/clippy_lints/src/macro_use.rs index ec03daff87b..314bf11e2d6 100644 --- a/clippy_lints/src/macro_use.rs +++ b/clippy_lints/src/macro_use.rs @@ -47,7 +47,7 @@ pub struct MacroRefData { impl MacroRefData { pub fn new(name: String, callee: Span, cx: &LateContext<'_>) -> Self { - let mut path = cx.sess().source_map().span_to_filename(callee).to_string(); + let mut path = cx.sess().source_map().span_to_filename(callee).prefer_local().to_string(); // std lib paths are <::std::module::file type> // so remove brackets, space and type. @@ -96,8 +96,7 @@ impl MacroUseImports { let name = snippet(cx, cx.sess().source_map().span_until_char(call_site, '!'), "_"); if let Some(callee) = span.source_callee() { if !self.collected.contains(&call_site) { - self.mac_refs - .push(MacroRefData::new(name.to_string(), callee.def_site, cx)); + self.mac_refs.push(MacroRefData::new(name.to_string(), callee.def_site, cx)); self.collected.insert(call_site); } } @@ -175,7 +174,7 @@ impl<'tcx> LateLintPass<'tcx> for MacroUseImports { .push((*item).to_string()); check_dup.push((*item).to_string()); } - }, + } [root, rest @ ..] => { if rest.iter().all(|item| !check_dup.contains(&(*item).to_string())) { let filtered = rest @@ -199,7 +198,7 @@ impl<'tcx> LateLintPass<'tcx> for MacroUseImports { .push(rest.join("::")); check_dup.extend(rest.iter().map(ToString::to_string)); } - }, + } } } } From d6058820237893566670d828c900de61c8e8f8af Mon Sep 17 00:00:00 2001 From: flip1995 Date: Thu, 6 May 2021 11:51:22 +0200 Subject: [PATCH 03/58] Merge commit 'b71f3405606d49b9735606b479c3415a0ca9810f' into clippyup --- .gitignore | 1 + CONTRIBUTING.md | 2 +- Cargo.toml | 3 +- clippy_lints/Cargo.toml | 4 +- clippy_lints/src/comparison_chain.rs | 6 +- clippy_lints/src/default.rs | 3 +- clippy_lints/src/eval_order_dependence.rs | 30 +- clippy_lints/src/implicit_return.rs | 255 ++++--- clippy_lints/src/lib.rs | 84 ++- clippy_lints/src/loops/needless_collect.rs | 26 +- .../src/loops/while_immutable_condition.rs | 13 +- clippy_lints/src/matches.rs | 20 +- clippy_lints/src/methods/mod.rs | 21 - .../src/methods/single_char_pattern.rs | 24 +- .../src/methods/unnecessary_filter_map.rs | 40 +- clippy_lints/src/misc_early.rs | 569 ---------------- .../src/misc_early/builtin_type_shadow.rs | 19 + clippy_lints/src/misc_early/double_neg.rs | 23 + .../src/misc_early/mixed_case_hex_literals.rs | 34 + clippy_lints/src/misc_early/mod.rs | 348 ++++++++++ .../src/misc_early/redundant_pattern.rs | 31 + .../src/misc_early/unneeded_field_pattern.rs | 72 ++ .../misc_early/unneeded_wildcard_pattern.rs | 52 ++ .../misc_early/unseparated_literal_suffix.rs | 26 + .../src/misc_early/zero_prefixed_literal.rs | 29 + .../src/slow_vector_initialization.rs | 22 +- clippy_lints/src/unused_unit.rs | 4 +- clippy_lints/src/utils/conf.rs | 258 +++---- clippy_lints/src/utils/internal_lints.rs | 3 + .../internal_lints/metadata_collector.rs | 632 ++++++++++++++++++ clippy_lints/src/utils/mod.rs | 2 +- clippy_utils/Cargo.toml | 3 +- clippy_utils/src/diagnostics.rs | 8 + clippy_utils/src/hir_utils.rs | 93 ++- clippy_utils/src/lib.rs | 48 +- clippy_utils/src/paths.rs | 13 +- clippy_utils/src/source.rs | 53 +- clippy_utils/src/visitors.rs | 55 +- doc/adding_lints.md | 4 +- rust-toolchain | 2 +- src/driver.rs | 2 +- tests/dogfood.rs | 7 + .../bad_toml_type/conf_bad_type.stderr | 2 +- .../conf_deprecated_key.stderr | 2 +- ...-type-shadow.rs => builtin_type_shadow.rs} | 0 ...adow.stderr => builtin_type_shadow.stderr} | 4 +- tests/ui/comparison_chain.rs | 28 + tests/ui/copy_iterator.rs | 6 +- tests/ui/crashes/ice-7169.rs | 9 + tests/ui/crashes/ice-7169.stderr | 10 + tests/ui/eval_order_dependence.rs | 6 + tests/ui/eval_order_dependence.stderr | 24 +- tests/ui/implicit_return.fixed | 68 +- tests/ui/implicit_return.rs | 68 +- tests/ui/implicit_return.stderr | 55 +- tests/ui/infinite_loop.rs | 12 + tests/ui/needless_collect_indirect.rs | 34 +- tests/ui/needless_collect_indirect.stderr | 58 +- tests/ui/single_char_pattern.fixed | 2 + tests/ui/single_char_pattern.rs | 2 + tests/ui/single_char_pattern.stderr | 34 +- tests/ui/unnecessary_filter_map.rs | 4 + tests/ui/unused_unit.fixed | 7 + tests/ui/unused_unit.rs | 7 + util/lintlib.py | 2 +- 65 files changed, 2327 insertions(+), 1061 deletions(-) delete mode 100644 clippy_lints/src/misc_early.rs create mode 100644 clippy_lints/src/misc_early/builtin_type_shadow.rs create mode 100644 clippy_lints/src/misc_early/double_neg.rs create mode 100644 clippy_lints/src/misc_early/mixed_case_hex_literals.rs create mode 100644 clippy_lints/src/misc_early/mod.rs create mode 100644 clippy_lints/src/misc_early/redundant_pattern.rs create mode 100644 clippy_lints/src/misc_early/unneeded_field_pattern.rs create mode 100644 clippy_lints/src/misc_early/unneeded_wildcard_pattern.rs create mode 100644 clippy_lints/src/misc_early/unseparated_literal_suffix.rs create mode 100644 clippy_lints/src/misc_early/zero_prefixed_literal.rs create mode 100644 clippy_lints/src/utils/internal_lints/metadata_collector.rs rename tests/ui/{builtin-type-shadow.rs => builtin_type_shadow.rs} (100%) rename tests/ui/{builtin-type-shadow.stderr => builtin_type_shadow.stderr} (89%) create mode 100644 tests/ui/crashes/ice-7169.rs create mode 100644 tests/ui/crashes/ice-7169.stderr diff --git a/.gitignore b/.gitignore index 376528e3085..523bab18828 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,7 @@ out # gh pages docs util/gh-pages/lints.json +**/metadata_collection.json # rustfmt backups *.rs.bk diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 02d2b63c9e1..7265d1b8323 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -118,7 +118,7 @@ which `IntelliJ Rust` will be able to understand. Run `cargo dev ide_setup --repo-path ` where `` is a path to the rustc repo you just cloned. The command will add path-dependencies pointing towards rustc-crates inside the rustc repo to -Clippys `Cargo.toml`s and should allow rust-analyzer to understand most of the types that Clippy uses. +Clippys `Cargo.toml`s and should allow `IntelliJ Rust` to understand most of the types that Clippy uses. Just make sure to remove the dependencies again before finally making a pull request! [rustc_repo]: https://github.com/rust-lang/rust/ diff --git a/Cargo.toml b/Cargo.toml index cade44a0a9a..f010e609604 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "clippy" -version = "0.1.53" +version = "0.1.54" authors = ["The Rust Clippy Developers"] description = "A bunch of helpful lints to avoid common pitfalls in Rust" repository = "https://github.com/rust-lang/rust-clippy" @@ -52,6 +52,7 @@ rustc_tools_util = { version = "0.2.0", path = "rustc_tools_util" } deny-warnings = [] integration = ["tempfile"] internal-lints = ["clippy_lints/internal-lints"] +metadata-collector-lint = ["internal-lints", "clippy_lints/metadata-collector-lint"] [package.metadata.rust-analyzer] # This package uses #[feature(rustc_private)] diff --git a/clippy_lints/Cargo.toml b/clippy_lints/Cargo.toml index 05cdd9d064a..7ceb1da6a6e 100644 --- a/clippy_lints/Cargo.toml +++ b/clippy_lints/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "clippy_lints" # begin automatic update -version = "0.1.53" +version = "0.1.54" # end automatic update authors = ["The Rust Clippy Developers"] description = "A bunch of helpful lints to avoid common pitfalls in Rust" @@ -20,6 +20,7 @@ pulldown-cmark = { version = "0.8", default-features = false } quine-mc_cluskey = "0.2.2" regex-syntax = "0.6" serde = { version = "1.0", features = ["derive"] } +serde_json = { version = "1.0", optional = true } toml = "0.5.3" unicode-normalization = "0.1" semver = "0.11" @@ -32,6 +33,7 @@ url = { version = "2.1.0", features = ["serde"] } deny-warnings = [] # build clippy with internal lints enabled, off by default internal-lints = ["clippy_utils/internal-lints"] +metadata-collector-lint = ["serde_json", "clippy_utils/metadata-collector-lint"] [package.metadata.rust-analyzer] # This crate uses #[feature(rustc_private)] diff --git a/clippy_lints/src/comparison_chain.rs b/clippy_lints/src/comparison_chain.rs index 42e153909ce..2a61d58e653 100644 --- a/clippy_lints/src/comparison_chain.rs +++ b/clippy_lints/src/comparison_chain.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_help; use clippy_utils::ty::implements_trait; -use clippy_utils::{get_trait_def_id, if_sequence, is_else_clause, paths, SpanlessEq}; +use clippy_utils::{get_trait_def_id, if_sequence, in_constant, is_else_clause, paths, SpanlessEq}; use rustc_hir::{BinOpKind, Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; @@ -64,6 +64,10 @@ impl<'tcx> LateLintPass<'tcx> for ComparisonChain { return; } + if in_constant(cx, expr.hir_id) { + return; + } + // Check that there exists at least one explicit else condition let (conds, _) = if_sequence(expr); if conds.len() < 2 { diff --git a/clippy_lints/src/default.rs b/clippy_lints/src/default.rs index 710da8fe9e0..7a53d390bb4 100644 --- a/clippy_lints/src/default.rs +++ b/clippy_lints/src/default.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_sugg}; use clippy_utils::source::snippet_with_macro_callsite; -use clippy_utils::{any_parent_is_automatically_derived, contains_name, match_def_path, paths}; +use clippy_utils::{any_parent_is_automatically_derived, contains_name, in_macro, match_def_path, paths}; use if_chain::if_chain; use rustc_data_structures::fx::FxHashSet; use rustc_errors::Applicability; @@ -75,6 +75,7 @@ impl_lint_pass!(Default => [DEFAULT_TRAIT_ACCESS, FIELD_REASSIGN_WITH_DEFAULT]); impl LateLintPass<'_> for Default { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { if_chain! { + if !in_macro(expr.span); // Avoid cases already linted by `field_reassign_with_default` if !self.reassigned_linted.contains(&expr.span); if let ExprKind::Call(path, ..) = expr.kind; diff --git a/clippy_lints/src/eval_order_dependence.rs b/clippy_lints/src/eval_order_dependence.rs index 762f64fe37a..41acf55dd7d 100644 --- a/clippy_lints/src/eval_order_dependence.rs +++ b/clippy_lints/src/eval_order_dependence.rs @@ -1,5 +1,6 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_note}; use clippy_utils::{get_parent_expr, path_to_local, path_to_local_id}; +use if_chain::if_chain; use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor}; use rustc_hir::{BinOpKind, Block, Expr, ExprKind, Guard, HirId, Local, Node, Stmt, StmtKind}; use rustc_lint::{LateContext, LateLintPass}; @@ -70,20 +71,19 @@ declare_lint_pass!(EvalOrderDependence => [EVAL_ORDER_DEPENDENCE, DIVERGING_SUB_ impl<'tcx> LateLintPass<'tcx> for EvalOrderDependence { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { // Find a write to a local variable. - match expr.kind { - ExprKind::Assign(lhs, ..) | ExprKind::AssignOp(_, lhs, _) => { - if let Some(var) = path_to_local(lhs) { - let mut visitor = ReadVisitor { - cx, - var, - write_expr: expr, - last_expr: expr, - }; - check_for_unsequenced_reads(&mut visitor); - } - }, - _ => {}, - } + let var = if_chain! { + if let ExprKind::Assign(lhs, ..) | ExprKind::AssignOp(_, lhs, _) = expr.kind; + if let Some(var) = path_to_local(lhs); + if expr.span.desugaring_kind().is_none(); + then { var } else { return; } + }; + let mut visitor = ReadVisitor { + cx, + var, + write_expr: expr, + last_expr: expr, + }; + check_for_unsequenced_reads(&mut visitor); } fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) { match stmt.kind { @@ -305,7 +305,7 @@ impl<'a, 'tcx> Visitor<'tcx> for ReadVisitor<'a, 'tcx> { self.cx, EVAL_ORDER_DEPENDENCE, expr.span, - "unsequenced read of a variable", + &format!("unsequenced read of `{}`", self.cx.tcx.hir().name(self.var)), Some(self.write_expr.span), "whether read occurs before this write depends on evaluation order", ); diff --git a/clippy_lints/src/implicit_return.rs b/clippy_lints/src/implicit_return.rs index 6b379b0d59b..30174fa2100 100644 --- a/clippy_lints/src/implicit_return.rs +++ b/clippy_lints/src/implicit_return.rs @@ -1,13 +1,16 @@ -use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::match_panic_def_id; -use clippy_utils::source::snippet_opt; -use if_chain::if_chain; +use clippy_utils::{ + diagnostics::span_lint_and_sugg, + get_async_fn_body, is_async_fn, + source::{snippet_with_applicability, snippet_with_context, walk_span_to_context}, + visitors::visit_break_exprs, +}; use rustc_errors::Applicability; use rustc_hir::intravisit::FnKind; -use rustc_hir::{Body, Expr, ExprKind, FnDecl, HirId, MatchSource, StmtKind}; -use rustc_lint::{LateContext, LateLintPass}; +use rustc_hir::{Block, Body, Expr, ExprKind, FnDecl, FnRetTy, HirId}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::lint::in_external_macro; use rustc_session::{declare_lint_pass, declare_tool_lint}; -use rustc_span::source_map::Span; +use rustc_span::{Span, SyntaxContext}; declare_clippy_lint! { /// **What it does:** Checks for missing return statements at the end of a block. @@ -39,89 +42,160 @@ declare_clippy_lint! { declare_lint_pass!(ImplicitReturn => [IMPLICIT_RETURN]); -static LINT_BREAK: &str = "change `break` to `return` as shown"; -static LINT_RETURN: &str = "add `return` as shown"; - -fn lint(cx: &LateContext<'_>, outer_span: Span, inner_span: Span, msg: &str) { - let outer_span = outer_span.source_callsite(); - let inner_span = inner_span.source_callsite(); - - span_lint_and_then(cx, IMPLICIT_RETURN, outer_span, "missing `return` statement", |diag| { - if let Some(snippet) = snippet_opt(cx, inner_span) { - diag.span_suggestion( - outer_span, - msg, - format!("return {}", snippet), - Applicability::MachineApplicable, - ); - } - }); +fn lint_return(cx: &LateContext<'_>, span: Span) { + let mut app = Applicability::MachineApplicable; + let snip = snippet_with_applicability(cx, span, "..", &mut app); + span_lint_and_sugg( + cx, + IMPLICIT_RETURN, + span, + "missing `return` statement", + "add `return` as shown", + format!("return {}", snip), + app, + ); } -fn expr_match(cx: &LateContext<'_>, expr: &Expr<'_>) { +fn lint_break(cx: &LateContext<'_>, break_span: Span, expr_span: Span) { + let mut app = Applicability::MachineApplicable; + let snip = snippet_with_context(cx, expr_span, break_span.ctxt(), "..", &mut app).0; + span_lint_and_sugg( + cx, + IMPLICIT_RETURN, + break_span, + "missing `return` statement", + "change `break` to `return` as shown", + format!("return {}", snip), + app, + ) +} + +#[derive(Clone, Copy, PartialEq, Eq)] +enum LintLocation { + /// The lint was applied to a parent expression. + Parent, + /// The lint was applied to this expression, a child, or not applied. + Inner, +} +impl LintLocation { + fn still_parent(self, b: bool) -> Self { + if b { self } else { Self::Inner } + } + + fn is_parent(self) -> bool { + self == Self::Parent + } +} + +// Gets the call site if the span is in a child context. Otherwise returns `None`. +fn get_call_site(span: Span, ctxt: SyntaxContext) -> Option { + (span.ctxt() != ctxt).then(|| walk_span_to_context(span, ctxt).unwrap_or(span)) +} + +fn lint_implicit_returns( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'_>, + // The context of the function body. + ctxt: SyntaxContext, + // Whether the expression is from a macro expansion. + call_site_span: Option, +) -> LintLocation { match expr.kind { - // loops could be using `break` instead of `return` - ExprKind::Block(block, ..) | ExprKind::Loop(block, ..) => { - if let Some(expr) = &block.expr { - expr_match(cx, expr); + ExprKind::Block( + Block { + expr: Some(block_expr), .. + }, + _, + ) => lint_implicit_returns( + cx, + block_expr, + ctxt, + call_site_span.or_else(|| get_call_site(block_expr.span, ctxt)), + ) + .still_parent(call_site_span.is_some()), + + ExprKind::If(_, then_expr, Some(else_expr)) => { + // Both `then_expr` or `else_expr` are required to be blocks in the same context as the `if`. Don't + // bother checking. + let res = lint_implicit_returns(cx, then_expr, ctxt, call_site_span).still_parent(call_site_span.is_some()); + if res.is_parent() { + // The return was added as a parent of this if expression. + return res; } - // only needed in the case of `break` with `;` at the end - else if let Some(stmt) = block.stmts.last() { - if_chain! { - if let StmtKind::Semi(expr, ..) = &stmt.kind; - // make sure it's a break, otherwise we want to skip - if let ExprKind::Break(.., Some(break_expr)) = &expr.kind; - then { - lint(cx, expr.span, break_expr.span, LINT_BREAK); + lint_implicit_returns(cx, else_expr, ctxt, call_site_span).still_parent(call_site_span.is_some()) + }, + + ExprKind::Match(_, arms, _) => { + for arm in arms { + let res = lint_implicit_returns( + cx, + arm.body, + ctxt, + call_site_span.or_else(|| get_call_site(arm.body.span, ctxt)), + ) + .still_parent(call_site_span.is_some()); + if res.is_parent() { + // The return was added as a parent of this match expression. + return res; + } + } + LintLocation::Inner + }, + + ExprKind::Loop(block, ..) => { + let mut add_return = false; + visit_break_exprs(block, |break_expr, dest, sub_expr| { + if dest.target_id.ok() == Some(expr.hir_id) { + if call_site_span.is_none() && break_expr.span.ctxt() == ctxt { + lint_break(cx, break_expr.span, sub_expr.unwrap().span); + } else { + // the break expression is from a macro call, add a return to the loop + add_return = true; } } - } - }, - // use `return` instead of `break` - ExprKind::Break(.., break_expr) => { - if let Some(break_expr) = break_expr { - lint(cx, expr.span, break_expr.span, LINT_BREAK); - } - }, - ExprKind::If(.., if_expr, else_expr) => { - expr_match(cx, if_expr); - - if let Some(else_expr) = else_expr { - expr_match(cx, else_expr); - } - }, - ExprKind::Match(.., arms, source) => { - let check_all_arms = match source { - MatchSource::IfLetDesugar { - contains_else_clause: has_else, - } => has_else, - _ => true, - }; - - if check_all_arms { - for arm in arms { - expr_match(cx, arm.body); + }); + if add_return { + #[allow(clippy::option_if_let_else)] + if let Some(span) = call_site_span { + lint_return(cx, span); + LintLocation::Parent + } else { + lint_return(cx, expr.span); + LintLocation::Inner } } else { - expr_match(cx, arms.first().expect("`if let` doesn't have a single arm").body); + LintLocation::Inner } }, - // skip if it already has a return statement - ExprKind::Ret(..) => (), - // make sure it's not a call that panics - ExprKind::Call(expr, ..) => { - if_chain! { - if let ExprKind::Path(qpath) = &expr.kind; - if let Some(path_def_id) = cx.qpath_res(qpath, expr.hir_id).opt_def_id(); - if match_panic_def_id(cx, path_def_id); - then { } - else { - lint(cx, expr.span, expr.span, LINT_RETURN) - } + + // If expressions without an else clause, and blocks without a final expression can only be the final expression + // if they are divergent, or return the unit type. + ExprKind::If(_, _, None) | ExprKind::Block(Block { expr: None, .. }, _) | ExprKind::Ret(_) => { + LintLocation::Inner + }, + + // Any divergent expression doesn't need a return statement. + ExprKind::MethodCall(..) + | ExprKind::Call(..) + | ExprKind::Binary(..) + | ExprKind::Unary(..) + | ExprKind::Index(..) + if cx.typeck_results().expr_ty(expr).is_never() => + { + LintLocation::Inner + }, + + _ => + { + #[allow(clippy::option_if_let_else)] + if let Some(span) = call_site_span { + lint_return(cx, span); + LintLocation::Parent + } else { + lint_return(cx, expr.span); + LintLocation::Inner } }, - // everything else is missing `return` - _ => lint(cx, expr.span, expr.span, LINT_RETURN), } } @@ -129,19 +203,32 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitReturn { fn check_fn( &mut self, cx: &LateContext<'tcx>, - _: FnKind<'tcx>, - _: &'tcx FnDecl<'_>, + kind: FnKind<'tcx>, + decl: &'tcx FnDecl<'_>, body: &'tcx Body<'_>, span: Span, _: HirId, ) { - if span.from_expansion() { + if (!matches!(kind, FnKind::Closure) && matches!(decl.output, FnRetTy::DefaultReturn(_))) + || span.ctxt() != body.value.span.ctxt() + || in_external_macro(cx.sess(), span) + { return; } - let body = cx.tcx.hir().body(body.id()); - if cx.typeck_results().expr_ty(&body.value).is_unit() { + + let res_ty = cx.typeck_results().expr_ty(&body.value); + if res_ty.is_unit() || res_ty.is_never() { return; } - expr_match(cx, &body.value); + + let expr = if is_async_fn(kind) { + match get_async_fn_body(cx.tcx, body) { + Some(e) => e, + None => return, + } + } else { + &body.value + }; + lint_implicit_returns(cx, expr, expr.span.ctxt(), None); } } diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 8c74284fa46..725aa54157e 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -383,6 +383,7 @@ mod zero_sized_map_values; // end lints modules, do not remove this comment, it’s used in `update_lints` pub use crate::utils::conf::Conf; +use crate::utils::conf::TryConf; /// Register all pre expansion lints /// @@ -400,56 +401,40 @@ pub fn register_pre_expansion_lints(store: &mut rustc_lint::LintStore) { } #[doc(hidden)] -pub fn read_conf(args: &[rustc_ast::NestedMetaItem], sess: &Session) -> Conf { +pub fn read_conf(sess: &Session) -> Conf { use std::path::Path; - match utils::conf::file_from_args(args) { - Ok(file_name) => { - // if the user specified a file, it must exist, otherwise default to `clippy.toml` but - // do not require the file to exist - let file_name = match file_name { - Some(file_name) => file_name, - None => match utils::conf::lookup_conf_file() { - Ok(Some(path)) => path, - Ok(None) => return Conf::default(), - Err(error) => { - sess.struct_err(&format!("error finding Clippy's configuration file: {}", error)) - .emit(); - return Conf::default(); - }, - }, - }; - - let file_name = if file_name.is_relative() { - sess.local_crate_source_file - .as_deref() - .and_then(Path::parent) - .unwrap_or_else(|| Path::new("")) - .join(file_name) - } else { - file_name - }; - - let (conf, errors) = utils::conf::read(&file_name); - - // all conf errors are non-fatal, we just use the default conf in case of error - for error in errors { - sess.struct_err(&format!( - "error reading Clippy's configuration file `{}`: {}", - file_name.display(), - error - )) + let file_name = match utils::conf::lookup_conf_file() { + Ok(Some(path)) => path, + Ok(None) => return Conf::default(), + Err(error) => { + sess.struct_err(&format!("error finding Clippy's configuration file: {}", error)) .emit(); - } + return Conf::default(); + }, + }; - conf - }, - Err((err, span)) => { - sess.struct_span_err(span, err) - .span_note(span, "Clippy will use default configuration") - .emit(); - Conf::default() - }, + let file_name = if file_name.is_relative() { + sess.local_crate_source_file + .as_deref() + .and_then(Path::parent) + .unwrap_or_else(|| Path::new("")) + .join(file_name) + } else { + file_name + }; + + let TryConf { conf, errors } = utils::conf::read(&file_name); + // all conf errors are non-fatal, we just use the default conf in case of error + for error in errors { + sess.struct_err(&format!( + "error reading Clippy's configuration file `{}`: {}", + file_name.display(), + error + )) + .emit(); } + + conf } /// Register all lints and lint groups with the rustc plugin registry @@ -1020,6 +1005,13 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| box utils::internal_lints::MatchTypeOnDiagItem); store.register_late_pass(|| box utils::internal_lints::OuterExpnDataPass); } + #[cfg(feature = "metadata-collector-lint")] + { + if std::env::var("ENABLE_METADATA_COLLECTION").eq(&Ok("1".to_string())) { + store.register_late_pass(|| box utils::internal_lints::metadata_collector::MetadataCollector::default()); + } + } + store.register_late_pass(|| box utils::author::Author); store.register_late_pass(|| box await_holding_invalid::AwaitHolding); store.register_late_pass(|| box serde_api::SerdeApi); diff --git a/clippy_lints/src/loops/needless_collect.rs b/clippy_lints/src/loops/needless_collect.rs index 4d73aef76e8..9662a0b22a3 100644 --- a/clippy_lints/src/loops/needless_collect.rs +++ b/clippy_lints/src/loops/needless_collect.rs @@ -7,9 +7,10 @@ use clippy_utils::{is_trait_method, path_to_local_id, paths}; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::intravisit::{walk_block, walk_expr, NestedVisitorMap, Visitor}; -use rustc_hir::{Block, Expr, ExprKind, GenericArg, HirId, Local, Pat, PatKind, QPath, StmtKind}; +use rustc_hir::{Block, Expr, ExprKind, GenericArg, GenericArgs, HirId, Local, Pat, PatKind, QPath, StmtKind, Ty}; use rustc_lint::LateContext; use rustc_middle::hir::map::Map; + use rustc_span::symbol::{sym, Ident}; use rustc_span::{MultiSpan, Span}; @@ -26,7 +27,7 @@ fn check_needless_collect_direct_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateCont if chain_method.ident.name == sym!(collect) && is_trait_method(cx, &args[0], sym::Iterator); if let Some(generic_args) = chain_method.args; if let Some(GenericArg::Type(ref ty)) = generic_args.args.get(0); - let ty = cx.typeck_results().node_type(ty.hir_id); + if let Some(ty) = cx.typeck_results().node_type_opt(ty.hir_id); if is_type_diagnostic_item(cx, ty, sym::vec_type) || is_type_diagnostic_item(cx, ty, sym::vecdeque_type) || match_type(cx, ty, &paths::BTREEMAP) @@ -58,20 +59,33 @@ fn check_needless_collect_direct_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateCont } fn check_needless_collect_indirect_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) { + fn get_hir_id<'tcx>(ty: Option<&Ty<'tcx>>, method_args: Option<&GenericArgs<'tcx>>) -> Option { + if let Some(ty) = ty { + return Some(ty.hir_id); + } + + if let Some(generic_args) = method_args { + if let Some(GenericArg::Type(ref ty)) = generic_args.args.get(0) { + return Some(ty.hir_id); + } + } + + None + } if let ExprKind::Block(block, _) = expr.kind { for stmt in block.stmts { if_chain! { if let StmtKind::Local( Local { pat: Pat { hir_id: pat_id, kind: PatKind::Binding(_, _, ident, .. ), .. }, - init: Some(init_expr), .. } + init: Some(init_expr), ty, .. } ) = stmt.kind; if let ExprKind::MethodCall(method_name, collect_span, &[ref iter_source], ..) = init_expr.kind; if method_name.ident.name == sym!(collect) && is_trait_method(cx, init_expr, sym::Iterator); - if let Some(generic_args) = method_name.args; - if let Some(GenericArg::Type(ref ty)) = generic_args.args.get(0); - if let ty = cx.typeck_results().node_type(ty.hir_id); + if let Some(hir_id) = get_hir_id(*ty, method_name.args); + if let Some(ty) = cx.typeck_results().node_type_opt(hir_id); if is_type_diagnostic_item(cx, ty, sym::vec_type) || is_type_diagnostic_item(cx, ty, sym::vecdeque_type) || + is_type_diagnostic_item(cx, ty, sym::BinaryHeap) || match_type(cx, ty, &paths::LINKED_LIST); if let Some(iter_calls) = detect_iter_and_into_iters(block, *ident); if let [iter_call] = &*iter_calls; diff --git a/clippy_lints/src/loops/while_immutable_condition.rs b/clippy_lints/src/loops/while_immutable_condition.rs index de267cc77d2..55404b87ec9 100644 --- a/clippy_lints/src/loops/while_immutable_condition.rs +++ b/clippy_lints/src/loops/while_immutable_condition.rs @@ -28,11 +28,14 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, cond: &'tcx Expr<'_>, expr: &' return; } let used_in_condition = &var_visitor.ids; - let no_cond_variable_mutated = if let Some(used_mutably) = mutated_variables(expr, cx) { - used_in_condition.is_disjoint(&used_mutably) - } else { - return; - }; + let mutated_in_body = mutated_variables(expr, cx); + let mutated_in_condition = mutated_variables(cond, cx); + let no_cond_variable_mutated = + if let (Some(used_mutably_body), Some(used_mutably_cond)) = (mutated_in_body, mutated_in_condition) { + used_in_condition.is_disjoint(&used_mutably_body) && used_in_condition.is_disjoint(&used_mutably_cond) + } else { + return; + }; let mutable_static_in_cond = var_visitor.def_ids.iter().any(|(_, v)| *v); let mut has_break_or_return_visitor = HasBreakOrReturnVisitor { diff --git a/clippy_lints/src/matches.rs b/clippy_lints/src/matches.rs index 13b2a834b0a..a70e8b26087 100644 --- a/clippy_lints/src/matches.rs +++ b/clippy_lints/src/matches.rs @@ -1590,9 +1590,9 @@ fn is_none_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> bool { // Checks if arm has the form `Some(ref v) => Some(v)` (checks for `ref` and `ref mut`) fn is_ref_some_arm(cx: &LateContext<'_>, arm: &Arm<'_>) -> Option { if_chain! { - if let PatKind::TupleStruct(ref qpath, pats, _) = arm.pat.kind; + if let PatKind::TupleStruct(ref qpath, [first_pat, ..], _) = arm.pat.kind; if is_lang_ctor(cx, qpath, OptionSome); - if let PatKind::Binding(rb, .., ident, _) = pats[0].kind; + if let PatKind::Binding(rb, .., ident, _) = first_pat.kind; if rb == BindingAnnotation::Ref || rb == BindingAnnotation::RefMut; if let ExprKind::Call(e, args) = remove_blocks(arm.body).kind; if let ExprKind::Path(ref some_path) = e.kind; @@ -1712,6 +1712,7 @@ mod redundant_pattern_match { use clippy_utils::{is_lang_ctor, is_qpath_def_path, is_trait_method, paths}; use if_chain::if_chain; use rustc_ast::ast::LitKind; + use rustc_data_structures::fx::FxHashSet; use rustc_errors::Applicability; use rustc_hir::LangItem::{OptionNone, OptionSome, PollPending, PollReady, ResultErr, ResultOk}; use rustc_hir::{ @@ -1739,6 +1740,13 @@ mod redundant_pattern_match { /// deallocate memory. For these types, and composites containing them, changing the drop order /// won't result in any observable side effects. fn type_needs_ordered_drop(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { + type_needs_ordered_drop_inner(cx, ty, &mut FxHashSet::default()) + } + + fn type_needs_ordered_drop_inner(cx: &LateContext<'tcx>, ty: Ty<'tcx>, seen: &mut FxHashSet>) -> bool { + if !seen.insert(ty) { + return false; + } if !ty.needs_drop(cx.tcx, cx.param_env) { false } else if !cx @@ -1750,12 +1758,12 @@ mod redundant_pattern_match { // This type doesn't implement drop, so no side effects here. // Check if any component type has any. match ty.kind() { - ty::Tuple(_) => ty.tuple_fields().any(|ty| type_needs_ordered_drop(cx, ty)), - ty::Array(ty, _) => type_needs_ordered_drop(cx, ty), + ty::Tuple(_) => ty.tuple_fields().any(|ty| type_needs_ordered_drop_inner(cx, ty, seen)), + ty::Array(ty, _) => type_needs_ordered_drop_inner(cx, ty, seen), ty::Adt(adt, subs) => adt .all_fields() .map(|f| f.ty(cx.tcx, subs)) - .any(|ty| type_needs_ordered_drop(cx, ty)), + .any(|ty| type_needs_ordered_drop_inner(cx, ty, seen)), _ => true, } } @@ -1772,7 +1780,7 @@ mod redundant_pattern_match { { // Check all of the generic arguments. if let ty::Adt(_, subs) = ty.kind() { - subs.types().any(|ty| type_needs_ordered_drop(cx, ty)) + subs.types().any(|ty| type_needs_ordered_drop_inner(cx, ty, seen)) } else { true } diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index e15dbb899b3..0b1b6304def 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -2189,27 +2189,6 @@ const TRAIT_METHODS: [ShouldImplTraitCase; 30] = [ ShouldImplTraitCase::new("std::ops::Sub", "sub", 2, FN_HEADER, SelfKind::Value, OutType::Any, true), ]; -#[rustfmt::skip] -const PATTERN_METHODS: [(&str, usize); 17] = [ - ("contains", 1), - ("starts_with", 1), - ("ends_with", 1), - ("find", 1), - ("rfind", 1), - ("split", 1), - ("rsplit", 1), - ("split_terminator", 1), - ("rsplit_terminator", 1), - ("splitn", 2), - ("rsplitn", 2), - ("matches", 1), - ("rmatches", 1), - ("match_indices", 1), - ("rmatch_indices", 1), - ("trim_start_matches", 1), - ("trim_end_matches", 1), -]; - #[derive(Clone, Copy, PartialEq, Debug)] enum SelfKind { Value, diff --git a/clippy_lints/src/methods/single_char_pattern.rs b/clippy_lints/src/methods/single_char_pattern.rs index f4090c7c617..d313a3db479 100644 --- a/clippy_lints/src/methods/single_char_pattern.rs +++ b/clippy_lints/src/methods/single_char_pattern.rs @@ -9,9 +9,31 @@ use rustc_span::symbol::Symbol; use super::SINGLE_CHAR_PATTERN; +const PATTERN_METHODS: [(&str, usize); 19] = [ + ("contains", 1), + ("starts_with", 1), + ("ends_with", 1), + ("find", 1), + ("rfind", 1), + ("split", 1), + ("rsplit", 1), + ("split_terminator", 1), + ("rsplit_terminator", 1), + ("splitn", 2), + ("rsplitn", 2), + ("matches", 1), + ("rmatches", 1), + ("match_indices", 1), + ("rmatch_indices", 1), + ("strip_prefix", 1), + ("strip_suffix", 1), + ("trim_start_matches", 1), + ("trim_end_matches", 1), +]; + /// lint for length-1 `str`s for methods in `PATTERN_METHODS` pub(super) fn check(cx: &LateContext<'_>, _expr: &hir::Expr<'_>, method_name: Symbol, args: &[hir::Expr<'_>]) { - for &(method, pos) in &crate::methods::PATTERN_METHODS { + for &(method, pos) in &PATTERN_METHODS { if_chain! { if let ty::Ref(_, ty, _) = cx.typeck_results().expr_ty_adjusted(&args[0]).kind(); if *ty.kind() == ty::Str; diff --git a/clippy_lints/src/methods/unnecessary_filter_map.rs b/clippy_lints/src/methods/unnecessary_filter_map.rs index b61c4ffe9b3..8b66587bfd1 100644 --- a/clippy_lints/src/methods/unnecessary_filter_map.rs +++ b/clippy_lints/src/methods/unnecessary_filter_map.rs @@ -6,6 +6,7 @@ use rustc_hir::intravisit::{walk_expr, NestedVisitorMap, Visitor}; use rustc_hir::LangItem::{OptionNone, OptionSome}; use rustc_lint::LateContext; use rustc_middle::hir::map::Map; +use rustc_middle::ty::{self, TyS}; use rustc_span::sym; use super::UNNECESSARY_FILTER_MAP; @@ -28,25 +29,28 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, arg: &hir::Expr< found_mapping |= return_visitor.found_mapping; found_filtering |= return_visitor.found_filtering; - if !found_filtering { - span_lint( - cx, - UNNECESSARY_FILTER_MAP, - expr.span, - "this `.filter_map` can be written more simply using `.map`", - ); + let sugg = if !found_filtering { + "map" + } else if !found_mapping && !mutates_arg { + let in_ty = cx.typeck_results().node_type(body.params[0].hir_id); + match cx.typeck_results().expr_ty(&body.value).kind() { + ty::Adt(adt, subst) + if cx.tcx.is_diagnostic_item(sym::option_type, adt.did) + && TyS::same_type(in_ty, subst.type_at(0)) => + { + "filter" + }, + _ => return, + } + } else { return; - } - - if !found_mapping && !mutates_arg { - span_lint( - cx, - UNNECESSARY_FILTER_MAP, - expr.span, - "this `.filter_map` can be written more simply using `.filter`", - ); - return; - } + }; + span_lint( + cx, + UNNECESSARY_FILTER_MAP, + expr.span, + &format!("this `.filter_map` can be written more simply using `.{}`", sugg), + ); } } diff --git a/clippy_lints/src/misc_early.rs b/clippy_lints/src/misc_early.rs deleted file mode 100644 index 3c6a7071c24..00000000000 --- a/clippy_lints/src/misc_early.rs +++ /dev/null @@ -1,569 +0,0 @@ -use clippy_utils::diagnostics::{span_lint, span_lint_and_help, span_lint_and_sugg, span_lint_and_then}; -use clippy_utils::source::snippet_opt; -use rustc_ast::ast::{ - BindingMode, Expr, ExprKind, GenericParamKind, Generics, Lit, LitFloatType, LitIntType, LitKind, Mutability, - NodeId, Pat, PatKind, UnOp, -}; -use rustc_ast::visit::FnKind; -use rustc_data_structures::fx::FxHashMap; -use rustc_errors::Applicability; -use rustc_hir::PrimTy; -use rustc_lint::{EarlyContext, EarlyLintPass, LintContext}; -use rustc_middle::lint::in_external_macro; -use rustc_session::{declare_lint_pass, declare_tool_lint}; -use rustc_span::source_map::Span; - -declare_clippy_lint! { - /// **What it does:** Checks for structure field patterns bound to wildcards. - /// - /// **Why is this bad?** Using `..` instead is shorter and leaves the focus on - /// the fields that are actually bound. - /// - /// **Known problems:** None. - /// - /// **Example:** - /// ```rust - /// # struct Foo { - /// # a: i32, - /// # b: i32, - /// # c: i32, - /// # } - /// let f = Foo { a: 0, b: 0, c: 0 }; - /// - /// // Bad - /// match f { - /// Foo { a: _, b: 0, .. } => {}, - /// Foo { a: _, b: _, c: _ } => {}, - /// } - /// - /// // Good - /// match f { - /// Foo { b: 0, .. } => {}, - /// Foo { .. } => {}, - /// } - /// ``` - pub UNNEEDED_FIELD_PATTERN, - restriction, - "struct fields bound to a wildcard instead of using `..`" -} - -declare_clippy_lint! { - /// **What it does:** Checks for function arguments having the similar names - /// differing by an underscore. - /// - /// **Why is this bad?** It affects code readability. - /// - /// **Known problems:** None. - /// - /// **Example:** - /// ```rust - /// // Bad - /// fn foo(a: i32, _a: i32) {} - /// - /// // Good - /// fn bar(a: i32, _b: i32) {} - /// ``` - pub DUPLICATE_UNDERSCORE_ARGUMENT, - style, - "function arguments having names which only differ by an underscore" -} - -declare_clippy_lint! { - /// **What it does:** Detects expressions of the form `--x`. - /// - /// **Why is this bad?** It can mislead C/C++ programmers to think `x` was - /// decremented. - /// - /// **Known problems:** None. - /// - /// **Example:** - /// ```rust - /// let mut x = 3; - /// --x; - /// ``` - pub DOUBLE_NEG, - style, - "`--x`, which is a double negation of `x` and not a pre-decrement as in C/C++" -} - -declare_clippy_lint! { - /// **What it does:** Warns on hexadecimal literals with mixed-case letter - /// digits. - /// - /// **Why is this bad?** It looks confusing. - /// - /// **Known problems:** None. - /// - /// **Example:** - /// ```rust - /// // Bad - /// let y = 0x1a9BAcD; - /// - /// // Good - /// let y = 0x1A9BACD; - /// ``` - pub MIXED_CASE_HEX_LITERALS, - style, - "hex literals whose letter digits are not consistently upper- or lowercased" -} - -declare_clippy_lint! { - /// **What it does:** Warns if literal suffixes are not separated by an - /// underscore. - /// - /// **Why is this bad?** It is much less readable. - /// - /// **Known problems:** None. - /// - /// **Example:** - /// ```rust - /// // Bad - /// let y = 123832i32; - /// - /// // Good - /// let y = 123832_i32; - /// ``` - pub UNSEPARATED_LITERAL_SUFFIX, - pedantic, - "literals whose suffix is not separated by an underscore" -} - -declare_clippy_lint! { - /// **What it does:** Warns if an integral constant literal starts with `0`. - /// - /// **Why is this bad?** In some languages (including the infamous C language - /// and most of its - /// family), this marks an octal constant. In Rust however, this is a decimal - /// constant. This could - /// be confusing for both the writer and a reader of the constant. - /// - /// **Known problems:** None. - /// - /// **Example:** - /// - /// In Rust: - /// ```rust - /// fn main() { - /// let a = 0123; - /// println!("{}", a); - /// } - /// ``` - /// - /// prints `123`, while in C: - /// - /// ```c - /// #include - /// - /// int main() { - /// int a = 0123; - /// printf("%d\n", a); - /// } - /// ``` - /// - /// prints `83` (as `83 == 0o123` while `123 == 0o173`). - pub ZERO_PREFIXED_LITERAL, - complexity, - "integer literals starting with `0`" -} - -declare_clippy_lint! { - /// **What it does:** Warns if a generic shadows a built-in type. - /// - /// **Why is this bad?** This gives surprising type errors. - /// - /// **Known problems:** None. - /// - /// **Example:** - /// - /// ```ignore - /// impl Foo { - /// fn impl_func(&self) -> u32 { - /// 42 - /// } - /// } - /// ``` - pub BUILTIN_TYPE_SHADOW, - style, - "shadowing a builtin type" -} - -declare_clippy_lint! { - /// **What it does:** Checks for patterns in the form `name @ _`. - /// - /// **Why is this bad?** It's almost always more readable to just use direct - /// bindings. - /// - /// **Known problems:** None. - /// - /// **Example:** - /// ```rust - /// # let v = Some("abc"); - /// - /// // Bad - /// match v { - /// Some(x) => (), - /// y @ _ => (), - /// } - /// - /// // Good - /// match v { - /// Some(x) => (), - /// y => (), - /// } - /// ``` - pub REDUNDANT_PATTERN, - style, - "using `name @ _` in a pattern" -} - -declare_clippy_lint! { - /// **What it does:** Checks for tuple patterns with a wildcard - /// pattern (`_`) is next to a rest pattern (`..`). - /// - /// _NOTE_: While `_, ..` means there is at least one element left, `..` - /// means there are 0 or more elements left. This can make a difference - /// when refactoring, but shouldn't result in errors in the refactored code, - /// since the wildcard pattern isn't used anyway. - /// **Why is this bad?** The wildcard pattern is unneeded as the rest pattern - /// can match that element as well. - /// - /// **Known problems:** None. - /// - /// **Example:** - /// ```rust - /// # struct TupleStruct(u32, u32, u32); - /// # let t = TupleStruct(1, 2, 3); - /// // Bad - /// match t { - /// TupleStruct(0, .., _) => (), - /// _ => (), - /// } - /// - /// // Good - /// match t { - /// TupleStruct(0, ..) => (), - /// _ => (), - /// } - /// ``` - pub UNNEEDED_WILDCARD_PATTERN, - complexity, - "tuple patterns with a wildcard pattern (`_`) is next to a rest pattern (`..`)" -} - -declare_lint_pass!(MiscEarlyLints => [ - UNNEEDED_FIELD_PATTERN, - DUPLICATE_UNDERSCORE_ARGUMENT, - DOUBLE_NEG, - MIXED_CASE_HEX_LITERALS, - UNSEPARATED_LITERAL_SUFFIX, - ZERO_PREFIXED_LITERAL, - BUILTIN_TYPE_SHADOW, - REDUNDANT_PATTERN, - UNNEEDED_WILDCARD_PATTERN, -]); - -impl EarlyLintPass for MiscEarlyLints { - fn check_generics(&mut self, cx: &EarlyContext<'_>, gen: &Generics) { - for param in &gen.params { - if let GenericParamKind::Type { .. } = param.kind { - if let Some(prim_ty) = PrimTy::from_name(param.ident.name) { - span_lint( - cx, - BUILTIN_TYPE_SHADOW, - param.ident.span, - &format!("this generic shadows the built-in type `{}`", prim_ty.name()), - ); - } - } - } - } - - fn check_pat(&mut self, cx: &EarlyContext<'_>, pat: &Pat) { - if let PatKind::Struct(ref npat, ref pfields, _) = pat.kind { - let mut wilds = 0; - let type_name = npat - .segments - .last() - .expect("A path must have at least one segment") - .ident - .name; - - for field in pfields { - if let PatKind::Wild = field.pat.kind { - wilds += 1; - } - } - if !pfields.is_empty() && wilds == pfields.len() { - span_lint_and_help( - cx, - UNNEEDED_FIELD_PATTERN, - pat.span, - "all the struct fields are matched to a wildcard pattern, consider using `..`", - None, - &format!("try with `{} {{ .. }}` instead", type_name), - ); - return; - } - if wilds > 0 { - for field in pfields { - if let PatKind::Wild = field.pat.kind { - wilds -= 1; - if wilds > 0 { - span_lint( - cx, - UNNEEDED_FIELD_PATTERN, - field.span, - "you matched a field with a wildcard pattern, consider using `..` instead", - ); - } else { - let mut normal = vec![]; - - for field in pfields { - match field.pat.kind { - PatKind::Wild => {}, - _ => { - if let Ok(n) = cx.sess().source_map().span_to_snippet(field.span) { - normal.push(n); - } - }, - } - } - - span_lint_and_help( - cx, - UNNEEDED_FIELD_PATTERN, - field.span, - "you matched a field with a wildcard pattern, consider using `..` \ - instead", - None, - &format!("try with `{} {{ {}, .. }}`", type_name, normal[..].join(", ")), - ); - } - } - } - } - } - - if let PatKind::Ident(left, ident, Some(ref right)) = pat.kind { - let left_binding = match left { - BindingMode::ByRef(Mutability::Mut) => "ref mut ", - BindingMode::ByRef(Mutability::Not) => "ref ", - BindingMode::ByValue(..) => "", - }; - - if let PatKind::Wild = right.kind { - span_lint_and_sugg( - cx, - REDUNDANT_PATTERN, - pat.span, - &format!( - "the `{} @ _` pattern can be written as just `{}`", - ident.name, ident.name, - ), - "try", - format!("{}{}", left_binding, ident.name), - Applicability::MachineApplicable, - ); - } - } - - check_unneeded_wildcard_pattern(cx, pat); - } - - fn check_fn(&mut self, cx: &EarlyContext<'_>, fn_kind: FnKind<'_>, _: Span, _: NodeId) { - let mut registered_names: FxHashMap = FxHashMap::default(); - - for arg in &fn_kind.decl().inputs { - if let PatKind::Ident(_, ident, None) = arg.pat.kind { - let arg_name = ident.to_string(); - - if let Some(arg_name) = arg_name.strip_prefix('_') { - if let Some(correspondence) = registered_names.get(arg_name) { - span_lint( - cx, - DUPLICATE_UNDERSCORE_ARGUMENT, - *correspondence, - &format!( - "`{}` already exists, having another argument having almost the same \ - name makes code comprehension and documentation more difficult", - arg_name - ), - ); - } - } else { - registered_names.insert(arg_name, arg.pat.span); - } - } - } - } - - fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { - if in_external_macro(cx.sess(), expr.span) { - return; - } - match expr.kind { - ExprKind::Unary(UnOp::Neg, ref inner) => { - if let ExprKind::Unary(UnOp::Neg, _) = inner.kind { - span_lint( - cx, - DOUBLE_NEG, - expr.span, - "`--x` could be misinterpreted as pre-decrement by C programmers, is usually a no-op", - ); - } - }, - ExprKind::Lit(ref lit) => Self::check_lit(cx, lit), - _ => (), - } - } -} - -impl MiscEarlyLints { - fn check_lit(cx: &EarlyContext<'_>, lit: &Lit) { - // We test if first character in snippet is a number, because the snippet could be an expansion - // from a built-in macro like `line!()` or a proc-macro like `#[wasm_bindgen]`. - // Note that this check also covers special case that `line!()` is eagerly expanded by compiler. - // See for a regression. - // FIXME: Find a better way to detect those cases. - let lit_snip = match snippet_opt(cx, lit.span) { - Some(snip) if snip.chars().next().map_or(false, |c| c.is_digit(10)) => snip, - _ => return, - }; - - if let LitKind::Int(value, lit_int_type) = lit.kind { - let suffix = match lit_int_type { - LitIntType::Signed(ty) => ty.name_str(), - LitIntType::Unsigned(ty) => ty.name_str(), - LitIntType::Unsuffixed => "", - }; - - let maybe_last_sep_idx = if let Some(val) = lit_snip.len().checked_sub(suffix.len() + 1) { - val - } else { - return; // It's useless so shouldn't lint. - }; - // Do not lint when literal is unsuffixed. - if !suffix.is_empty() && lit_snip.as_bytes()[maybe_last_sep_idx] != b'_' { - span_lint_and_sugg( - cx, - UNSEPARATED_LITERAL_SUFFIX, - lit.span, - "integer type suffix should be separated by an underscore", - "add an underscore", - format!("{}_{}", &lit_snip[..=maybe_last_sep_idx], suffix), - Applicability::MachineApplicable, - ); - } - - if lit_snip.starts_with("0x") { - if maybe_last_sep_idx <= 2 { - // It's meaningless or causes range error. - return; - } - let mut seen = (false, false); - for ch in lit_snip.as_bytes()[2..=maybe_last_sep_idx].iter() { - match ch { - b'a'..=b'f' => seen.0 = true, - b'A'..=b'F' => seen.1 = true, - _ => {}, - } - if seen.0 && seen.1 { - span_lint( - cx, - MIXED_CASE_HEX_LITERALS, - lit.span, - "inconsistent casing in hexadecimal literal", - ); - break; - } - } - } else if lit_snip.starts_with("0b") || lit_snip.starts_with("0o") { - /* nothing to do */ - } else if value != 0 && lit_snip.starts_with('0') { - span_lint_and_then( - cx, - ZERO_PREFIXED_LITERAL, - lit.span, - "this is a decimal constant", - |diag| { - diag.span_suggestion( - lit.span, - "if you mean to use a decimal constant, remove the `0` to avoid confusion", - lit_snip.trim_start_matches(|c| c == '_' || c == '0').to_string(), - Applicability::MaybeIncorrect, - ); - diag.span_suggestion( - lit.span, - "if you mean to use an octal constant, use `0o`", - format!("0o{}", lit_snip.trim_start_matches(|c| c == '_' || c == '0')), - Applicability::MaybeIncorrect, - ); - }, - ); - } - } else if let LitKind::Float(_, LitFloatType::Suffixed(float_ty)) = lit.kind { - let suffix = float_ty.name_str(); - let maybe_last_sep_idx = if let Some(val) = lit_snip.len().checked_sub(suffix.len() + 1) { - val - } else { - return; // It's useless so shouldn't lint. - }; - if lit_snip.as_bytes()[maybe_last_sep_idx] != b'_' { - span_lint_and_sugg( - cx, - UNSEPARATED_LITERAL_SUFFIX, - lit.span, - "float type suffix should be separated by an underscore", - "add an underscore", - format!("{}_{}", &lit_snip[..=maybe_last_sep_idx], suffix), - Applicability::MachineApplicable, - ); - } - } - } -} - -fn check_unneeded_wildcard_pattern(cx: &EarlyContext<'_>, pat: &Pat) { - if let PatKind::TupleStruct(_, ref patterns) | PatKind::Tuple(ref patterns) = pat.kind { - fn span_lint(cx: &EarlyContext<'_>, span: Span, only_one: bool) { - span_lint_and_sugg( - cx, - UNNEEDED_WILDCARD_PATTERN, - span, - if only_one { - "this pattern is unneeded as the `..` pattern can match that element" - } else { - "these patterns are unneeded as the `..` pattern can match those elements" - }, - if only_one { "remove it" } else { "remove them" }, - "".to_string(), - Applicability::MachineApplicable, - ); - } - - if let Some(rest_index) = patterns.iter().position(|pat| pat.is_rest()) { - if let Some((left_index, left_pat)) = patterns[..rest_index] - .iter() - .rev() - .take_while(|pat| matches!(pat.kind, PatKind::Wild)) - .enumerate() - .last() - { - span_lint(cx, left_pat.span.until(patterns[rest_index].span), left_index == 0); - } - - if let Some((right_index, right_pat)) = patterns[rest_index + 1..] - .iter() - .take_while(|pat| matches!(pat.kind, PatKind::Wild)) - .enumerate() - .last() - { - span_lint( - cx, - patterns[rest_index].span.shrink_to_hi().to(right_pat.span), - right_index == 0, - ); - } - } - } -} diff --git a/clippy_lints/src/misc_early/builtin_type_shadow.rs b/clippy_lints/src/misc_early/builtin_type_shadow.rs new file mode 100644 index 00000000000..9f6b0bdc7a4 --- /dev/null +++ b/clippy_lints/src/misc_early/builtin_type_shadow.rs @@ -0,0 +1,19 @@ +use clippy_utils::diagnostics::span_lint; +use rustc_ast::ast::{GenericParam, GenericParamKind}; +use rustc_hir::PrimTy; +use rustc_lint::EarlyContext; + +use super::BUILTIN_TYPE_SHADOW; + +pub(super) fn check(cx: &EarlyContext<'_>, param: &GenericParam) { + if let GenericParamKind::Type { .. } = param.kind { + if let Some(prim_ty) = PrimTy::from_name(param.ident.name) { + span_lint( + cx, + BUILTIN_TYPE_SHADOW, + param.ident.span, + &format!("this generic shadows the built-in type `{}`", prim_ty.name()), + ); + } + } +} diff --git a/clippy_lints/src/misc_early/double_neg.rs b/clippy_lints/src/misc_early/double_neg.rs new file mode 100644 index 00000000000..6f65778e119 --- /dev/null +++ b/clippy_lints/src/misc_early/double_neg.rs @@ -0,0 +1,23 @@ +use super::MiscEarlyLints; +use clippy_utils::diagnostics::span_lint; +use rustc_ast::ast::{Expr, ExprKind, UnOp}; +use rustc_lint::EarlyContext; + +use super::DOUBLE_NEG; + +pub(super) fn check(cx: &EarlyContext<'_>, expr: &Expr) { + match expr.kind { + ExprKind::Unary(UnOp::Neg, ref inner) => { + if let ExprKind::Unary(UnOp::Neg, _) = inner.kind { + span_lint( + cx, + DOUBLE_NEG, + expr.span, + "`--x` could be misinterpreted as pre-decrement by C programmers, is usually a no-op", + ); + } + }, + ExprKind::Lit(ref lit) => MiscEarlyLints::check_lit(cx, lit), + _ => (), + } +} diff --git a/clippy_lints/src/misc_early/mixed_case_hex_literals.rs b/clippy_lints/src/misc_early/mixed_case_hex_literals.rs new file mode 100644 index 00000000000..80e24213100 --- /dev/null +++ b/clippy_lints/src/misc_early/mixed_case_hex_literals.rs @@ -0,0 +1,34 @@ +use clippy_utils::diagnostics::span_lint; +use rustc_ast::ast::Lit; +use rustc_lint::EarlyContext; + +use super::MIXED_CASE_HEX_LITERALS; + +pub(super) fn check(cx: &EarlyContext<'_>, lit: &Lit, suffix: &str, lit_snip: &str) { + let maybe_last_sep_idx = if let Some(val) = lit_snip.len().checked_sub(suffix.len() + 1) { + val + } else { + return; // It's useless so shouldn't lint. + }; + if maybe_last_sep_idx <= 2 { + // It's meaningless or causes range error. + return; + } + let mut seen = (false, false); + for ch in lit_snip.as_bytes()[2..=maybe_last_sep_idx].iter() { + match ch { + b'a'..=b'f' => seen.0 = true, + b'A'..=b'F' => seen.1 = true, + _ => {}, + } + if seen.0 && seen.1 { + span_lint( + cx, + MIXED_CASE_HEX_LITERALS, + lit.span, + "inconsistent casing in hexadecimal literal", + ); + break; + } + } +} diff --git a/clippy_lints/src/misc_early/mod.rs b/clippy_lints/src/misc_early/mod.rs new file mode 100644 index 00000000000..dd38316fa25 --- /dev/null +++ b/clippy_lints/src/misc_early/mod.rs @@ -0,0 +1,348 @@ +mod builtin_type_shadow; +mod double_neg; +mod mixed_case_hex_literals; +mod redundant_pattern; +mod unneeded_field_pattern; +mod unneeded_wildcard_pattern; +mod unseparated_literal_suffix; +mod zero_prefixed_literal; + +use clippy_utils::diagnostics::span_lint; +use clippy_utils::source::snippet_opt; +use rustc_ast::ast::{Expr, Generics, Lit, LitFloatType, LitIntType, LitKind, NodeId, Pat, PatKind}; +use rustc_ast::visit::FnKind; +use rustc_data_structures::fx::FxHashMap; +use rustc_lint::{EarlyContext, EarlyLintPass, LintContext}; +use rustc_middle::lint::in_external_macro; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::source_map::Span; + +declare_clippy_lint! { + /// **What it does:** Checks for structure field patterns bound to wildcards. + /// + /// **Why is this bad?** Using `..` instead is shorter and leaves the focus on + /// the fields that are actually bound. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # struct Foo { + /// # a: i32, + /// # b: i32, + /// # c: i32, + /// # } + /// let f = Foo { a: 0, b: 0, c: 0 }; + /// + /// // Bad + /// match f { + /// Foo { a: _, b: 0, .. } => {}, + /// Foo { a: _, b: _, c: _ } => {}, + /// } + /// + /// // Good + /// match f { + /// Foo { b: 0, .. } => {}, + /// Foo { .. } => {}, + /// } + /// ``` + pub UNNEEDED_FIELD_PATTERN, + restriction, + "struct fields bound to a wildcard instead of using `..`" +} + +declare_clippy_lint! { + /// **What it does:** Checks for function arguments having the similar names + /// differing by an underscore. + /// + /// **Why is this bad?** It affects code readability. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// // Bad + /// fn foo(a: i32, _a: i32) {} + /// + /// // Good + /// fn bar(a: i32, _b: i32) {} + /// ``` + pub DUPLICATE_UNDERSCORE_ARGUMENT, + style, + "function arguments having names which only differ by an underscore" +} + +declare_clippy_lint! { + /// **What it does:** Detects expressions of the form `--x`. + /// + /// **Why is this bad?** It can mislead C/C++ programmers to think `x` was + /// decremented. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// let mut x = 3; + /// --x; + /// ``` + pub DOUBLE_NEG, + style, + "`--x`, which is a double negation of `x` and not a pre-decrement as in C/C++" +} + +declare_clippy_lint! { + /// **What it does:** Warns on hexadecimal literals with mixed-case letter + /// digits. + /// + /// **Why is this bad?** It looks confusing. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// // Bad + /// let y = 0x1a9BAcD; + /// + /// // Good + /// let y = 0x1A9BACD; + /// ``` + pub MIXED_CASE_HEX_LITERALS, + style, + "hex literals whose letter digits are not consistently upper- or lowercased" +} + +declare_clippy_lint! { + /// **What it does:** Warns if literal suffixes are not separated by an + /// underscore. + /// + /// **Why is this bad?** It is much less readable. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// // Bad + /// let y = 123832i32; + /// + /// // Good + /// let y = 123832_i32; + /// ``` + pub UNSEPARATED_LITERAL_SUFFIX, + pedantic, + "literals whose suffix is not separated by an underscore" +} + +declare_clippy_lint! { + /// **What it does:** Warns if an integral constant literal starts with `0`. + /// + /// **Why is this bad?** In some languages (including the infamous C language + /// and most of its + /// family), this marks an octal constant. In Rust however, this is a decimal + /// constant. This could + /// be confusing for both the writer and a reader of the constant. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// In Rust: + /// ```rust + /// fn main() { + /// let a = 0123; + /// println!("{}", a); + /// } + /// ``` + /// + /// prints `123`, while in C: + /// + /// ```c + /// #include + /// + /// int main() { + /// int a = 0123; + /// printf("%d\n", a); + /// } + /// ``` + /// + /// prints `83` (as `83 == 0o123` while `123 == 0o173`). + pub ZERO_PREFIXED_LITERAL, + complexity, + "integer literals starting with `0`" +} + +declare_clippy_lint! { + /// **What it does:** Warns if a generic shadows a built-in type. + /// + /// **Why is this bad?** This gives surprising type errors. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// + /// ```ignore + /// impl Foo { + /// fn impl_func(&self) -> u32 { + /// 42 + /// } + /// } + /// ``` + pub BUILTIN_TYPE_SHADOW, + style, + "shadowing a builtin type" +} + +declare_clippy_lint! { + /// **What it does:** Checks for patterns in the form `name @ _`. + /// + /// **Why is this bad?** It's almost always more readable to just use direct + /// bindings. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # let v = Some("abc"); + /// + /// // Bad + /// match v { + /// Some(x) => (), + /// y @ _ => (), + /// } + /// + /// // Good + /// match v { + /// Some(x) => (), + /// y => (), + /// } + /// ``` + pub REDUNDANT_PATTERN, + style, + "using `name @ _` in a pattern" +} + +declare_clippy_lint! { + /// **What it does:** Checks for tuple patterns with a wildcard + /// pattern (`_`) is next to a rest pattern (`..`). + /// + /// _NOTE_: While `_, ..` means there is at least one element left, `..` + /// means there are 0 or more elements left. This can make a difference + /// when refactoring, but shouldn't result in errors in the refactored code, + /// since the wildcard pattern isn't used anyway. + /// **Why is this bad?** The wildcard pattern is unneeded as the rest pattern + /// can match that element as well. + /// + /// **Known problems:** None. + /// + /// **Example:** + /// ```rust + /// # struct TupleStruct(u32, u32, u32); + /// # let t = TupleStruct(1, 2, 3); + /// // Bad + /// match t { + /// TupleStruct(0, .., _) => (), + /// _ => (), + /// } + /// + /// // Good + /// match t { + /// TupleStruct(0, ..) => (), + /// _ => (), + /// } + /// ``` + pub UNNEEDED_WILDCARD_PATTERN, + complexity, + "tuple patterns with a wildcard pattern (`_`) is next to a rest pattern (`..`)" +} + +declare_lint_pass!(MiscEarlyLints => [ + UNNEEDED_FIELD_PATTERN, + DUPLICATE_UNDERSCORE_ARGUMENT, + DOUBLE_NEG, + MIXED_CASE_HEX_LITERALS, + UNSEPARATED_LITERAL_SUFFIX, + ZERO_PREFIXED_LITERAL, + BUILTIN_TYPE_SHADOW, + REDUNDANT_PATTERN, + UNNEEDED_WILDCARD_PATTERN, +]); + +impl EarlyLintPass for MiscEarlyLints { + fn check_generics(&mut self, cx: &EarlyContext<'_>, gen: &Generics) { + for param in &gen.params { + builtin_type_shadow::check(cx, param); + } + } + + fn check_pat(&mut self, cx: &EarlyContext<'_>, pat: &Pat) { + unneeded_field_pattern::check(cx, pat); + redundant_pattern::check(cx, pat); + unneeded_wildcard_pattern::check(cx, pat); + } + + fn check_fn(&mut self, cx: &EarlyContext<'_>, fn_kind: FnKind<'_>, _: Span, _: NodeId) { + let mut registered_names: FxHashMap = FxHashMap::default(); + + for arg in &fn_kind.decl().inputs { + if let PatKind::Ident(_, ident, None) = arg.pat.kind { + let arg_name = ident.to_string(); + + if let Some(arg_name) = arg_name.strip_prefix('_') { + if let Some(correspondence) = registered_names.get(arg_name) { + span_lint( + cx, + DUPLICATE_UNDERSCORE_ARGUMENT, + *correspondence, + &format!( + "`{}` already exists, having another argument having almost the same \ + name makes code comprehension and documentation more difficult", + arg_name + ), + ); + } + } else { + registered_names.insert(arg_name, arg.pat.span); + } + } + } + } + + fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) { + if in_external_macro(cx.sess(), expr.span) { + return; + } + double_neg::check(cx, expr) + } +} + +impl MiscEarlyLints { + fn check_lit(cx: &EarlyContext<'_>, lit: &Lit) { + // We test if first character in snippet is a number, because the snippet could be an expansion + // from a built-in macro like `line!()` or a proc-macro like `#[wasm_bindgen]`. + // Note that this check also covers special case that `line!()` is eagerly expanded by compiler. + // See for a regression. + // FIXME: Find a better way to detect those cases. + let lit_snip = match snippet_opt(cx, lit.span) { + Some(snip) if snip.chars().next().map_or(false, |c| c.is_digit(10)) => snip, + _ => return, + }; + + if let LitKind::Int(value, lit_int_type) = lit.kind { + let suffix = match lit_int_type { + LitIntType::Signed(ty) => ty.name_str(), + LitIntType::Unsigned(ty) => ty.name_str(), + LitIntType::Unsuffixed => "", + }; + unseparated_literal_suffix::check(cx, lit, &lit_snip, suffix, "integer"); + if lit_snip.starts_with("0x") { + mixed_case_hex_literals::check(cx, lit, suffix, &lit_snip) + } else if lit_snip.starts_with("0b") || lit_snip.starts_with("0o") { + /* nothing to do */ + } else if value != 0 && lit_snip.starts_with('0') { + zero_prefixed_literal::check(cx, lit, &lit_snip) + } + } else if let LitKind::Float(_, LitFloatType::Suffixed(float_ty)) = lit.kind { + let suffix = float_ty.name_str(); + unseparated_literal_suffix::check(cx, lit, &lit_snip, suffix, "float") + } + } +} diff --git a/clippy_lints/src/misc_early/redundant_pattern.rs b/clippy_lints/src/misc_early/redundant_pattern.rs new file mode 100644 index 00000000000..525dbf7757c --- /dev/null +++ b/clippy_lints/src/misc_early/redundant_pattern.rs @@ -0,0 +1,31 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use rustc_ast::ast::{BindingMode, Mutability, Pat, PatKind}; +use rustc_errors::Applicability; +use rustc_lint::EarlyContext; + +use super::REDUNDANT_PATTERN; + +pub(super) fn check(cx: &EarlyContext<'_>, pat: &Pat) { + if let PatKind::Ident(left, ident, Some(ref right)) = pat.kind { + let left_binding = match left { + BindingMode::ByRef(Mutability::Mut) => "ref mut ", + BindingMode::ByRef(Mutability::Not) => "ref ", + BindingMode::ByValue(..) => "", + }; + + if let PatKind::Wild = right.kind { + span_lint_and_sugg( + cx, + REDUNDANT_PATTERN, + pat.span, + &format!( + "the `{} @ _` pattern can be written as just `{}`", + ident.name, ident.name, + ), + "try", + format!("{}{}", left_binding, ident.name), + Applicability::MachineApplicable, + ); + } + } +} diff --git a/clippy_lints/src/misc_early/unneeded_field_pattern.rs b/clippy_lints/src/misc_early/unneeded_field_pattern.rs new file mode 100644 index 00000000000..329a0009a3e --- /dev/null +++ b/clippy_lints/src/misc_early/unneeded_field_pattern.rs @@ -0,0 +1,72 @@ +use clippy_utils::diagnostics::{span_lint, span_lint_and_help}; +use rustc_ast::ast::{Pat, PatKind}; +use rustc_lint::{EarlyContext, LintContext}; + +use super::UNNEEDED_FIELD_PATTERN; + +pub(super) fn check(cx: &EarlyContext<'_>, pat: &Pat) { + if let PatKind::Struct(ref npat, ref pfields, _) = pat.kind { + let mut wilds = 0; + let type_name = npat + .segments + .last() + .expect("A path must have at least one segment") + .ident + .name; + + for field in pfields { + if let PatKind::Wild = field.pat.kind { + wilds += 1; + } + } + if !pfields.is_empty() && wilds == pfields.len() { + span_lint_and_help( + cx, + UNNEEDED_FIELD_PATTERN, + pat.span, + "all the struct fields are matched to a wildcard pattern, consider using `..`", + None, + &format!("try with `{} {{ .. }}` instead", type_name), + ); + return; + } + if wilds > 0 { + for field in pfields { + if let PatKind::Wild = field.pat.kind { + wilds -= 1; + if wilds > 0 { + span_lint( + cx, + UNNEEDED_FIELD_PATTERN, + field.span, + "you matched a field with a wildcard pattern, consider using `..` instead", + ); + } else { + let mut normal = vec![]; + + for field in pfields { + match field.pat.kind { + PatKind::Wild => {}, + _ => { + if let Ok(n) = cx.sess().source_map().span_to_snippet(field.span) { + normal.push(n); + } + }, + } + } + + span_lint_and_help( + cx, + UNNEEDED_FIELD_PATTERN, + field.span, + "you matched a field with a wildcard pattern, consider using `..` \ + instead", + None, + &format!("try with `{} {{ {}, .. }}`", type_name, normal[..].join(", ")), + ); + } + } + } + } + } +} diff --git a/clippy_lints/src/misc_early/unneeded_wildcard_pattern.rs b/clippy_lints/src/misc_early/unneeded_wildcard_pattern.rs new file mode 100644 index 00000000000..4dd032d78f1 --- /dev/null +++ b/clippy_lints/src/misc_early/unneeded_wildcard_pattern.rs @@ -0,0 +1,52 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use rustc_ast::ast::{Pat, PatKind}; +use rustc_errors::Applicability; +use rustc_lint::EarlyContext; +use rustc_span::source_map::Span; + +use super::UNNEEDED_WILDCARD_PATTERN; + +pub(super) fn check(cx: &EarlyContext<'_>, pat: &Pat) { + if let PatKind::TupleStruct(_, ref patterns) | PatKind::Tuple(ref patterns) = pat.kind { + if let Some(rest_index) = patterns.iter().position(|pat| pat.is_rest()) { + if let Some((left_index, left_pat)) = patterns[..rest_index] + .iter() + .rev() + .take_while(|pat| matches!(pat.kind, PatKind::Wild)) + .enumerate() + .last() + { + span_lint(cx, left_pat.span.until(patterns[rest_index].span), left_index == 0); + } + + if let Some((right_index, right_pat)) = patterns[rest_index + 1..] + .iter() + .take_while(|pat| matches!(pat.kind, PatKind::Wild)) + .enumerate() + .last() + { + span_lint( + cx, + patterns[rest_index].span.shrink_to_hi().to(right_pat.span), + right_index == 0, + ); + } + } + } +} + +fn span_lint(cx: &EarlyContext<'_>, span: Span, only_one: bool) { + span_lint_and_sugg( + cx, + UNNEEDED_WILDCARD_PATTERN, + span, + if only_one { + "this pattern is unneeded as the `..` pattern can match that element" + } else { + "these patterns are unneeded as the `..` pattern can match those elements" + }, + if only_one { "remove it" } else { "remove them" }, + "".to_string(), + Applicability::MachineApplicable, + ); +} diff --git a/clippy_lints/src/misc_early/unseparated_literal_suffix.rs b/clippy_lints/src/misc_early/unseparated_literal_suffix.rs new file mode 100644 index 00000000000..2018aa6184a --- /dev/null +++ b/clippy_lints/src/misc_early/unseparated_literal_suffix.rs @@ -0,0 +1,26 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use rustc_ast::ast::Lit; +use rustc_errors::Applicability; +use rustc_lint::EarlyContext; + +use super::UNSEPARATED_LITERAL_SUFFIX; + +pub(super) fn check(cx: &EarlyContext<'_>, lit: &Lit, lit_snip: &str, suffix: &str, sugg_type: &str) { + let maybe_last_sep_idx = if let Some(val) = lit_snip.len().checked_sub(suffix.len() + 1) { + val + } else { + return; // It's useless so shouldn't lint. + }; + // Do not lint when literal is unsuffixed. + if !suffix.is_empty() && lit_snip.as_bytes()[maybe_last_sep_idx] != b'_' { + span_lint_and_sugg( + cx, + UNSEPARATED_LITERAL_SUFFIX, + lit.span, + &format!("{} type suffix should be separated by an underscore", sugg_type), + "add an underscore", + format!("{}_{}", &lit_snip[..=maybe_last_sep_idx], suffix), + Applicability::MachineApplicable, + ); + } +} diff --git a/clippy_lints/src/misc_early/zero_prefixed_literal.rs b/clippy_lints/src/misc_early/zero_prefixed_literal.rs new file mode 100644 index 00000000000..4963bba82f2 --- /dev/null +++ b/clippy_lints/src/misc_early/zero_prefixed_literal.rs @@ -0,0 +1,29 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use rustc_ast::ast::Lit; +use rustc_errors::Applicability; +use rustc_lint::EarlyContext; + +use super::ZERO_PREFIXED_LITERAL; + +pub(super) fn check(cx: &EarlyContext<'_>, lit: &Lit, lit_snip: &str) { + span_lint_and_then( + cx, + ZERO_PREFIXED_LITERAL, + lit.span, + "this is a decimal constant", + |diag| { + diag.span_suggestion( + lit.span, + "if you mean to use a decimal constant, remove the `0` to avoid confusion", + lit_snip.trim_start_matches(|c| c == '_' || c == '0').to_string(), + Applicability::MaybeIncorrect, + ); + diag.span_suggestion( + lit.span, + "if you mean to use an octal constant, use `0o`", + format!("0o{}", lit_snip.trim_start_matches(|c| c == '_' || c == '0')), + Applicability::MaybeIncorrect, + ); + }, + ); +} diff --git a/clippy_lints/src/slow_vector_initialization.rs b/clippy_lints/src/slow_vector_initialization.rs index 191781be000..a9ae2b77119 100644 --- a/clippy_lints/src/slow_vector_initialization.rs +++ b/clippy_lints/src/slow_vector_initialization.rs @@ -7,7 +7,7 @@ use rustc_ast::ast::LitKind; use rustc_errors::Applicability; use rustc_hir::intravisit::{walk_block, walk_expr, walk_stmt, NestedVisitorMap, Visitor}; use rustc_hir::{BindingAnnotation, Block, Expr, ExprKind, HirId, PatKind, QPath, Stmt, StmtKind}; -use rustc_lint::{LateContext, LateLintPass, Lint}; +use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::hir::map::Map; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::symbol::sym; @@ -157,26 +157,16 @@ impl SlowVectorInit { vec_alloc: &VecAllocation<'_>, ) { match initialization { - InitializationType::Extend(e) | InitializationType::Resize(e) => Self::emit_lint( - cx, - e, - vec_alloc, - "slow zero-filling initialization", - SLOW_VECTOR_INITIALIZATION, - ), + InitializationType::Extend(e) | InitializationType::Resize(e) => { + Self::emit_lint(cx, e, vec_alloc, "slow zero-filling initialization") + }, }; } - fn emit_lint<'tcx>( - cx: &LateContext<'tcx>, - slow_fill: &Expr<'_>, - vec_alloc: &VecAllocation<'_>, - msg: &str, - lint: &'static Lint, - ) { + fn emit_lint<'tcx>(cx: &LateContext<'tcx>, slow_fill: &Expr<'_>, vec_alloc: &VecAllocation<'_>, msg: &str) { let len_expr = Sugg::hir(cx, vec_alloc.len_expr, "len"); - span_lint_and_then(cx, lint, slow_fill.span, msg, |diag| { + span_lint_and_then(cx, SLOW_VECTOR_INITIALIZATION, slow_fill.span, msg, |diag| { diag.span_suggestion( vec_alloc.allocation_expr.span, "consider replace allocation with", diff --git a/clippy_lints/src/unused_unit.rs b/clippy_lints/src/unused_unit.rs index ce2d0b3ab2f..e14945651f5 100644 --- a/clippy_lints/src/unused_unit.rs +++ b/clippy_lints/src/unused_unit.rs @@ -47,7 +47,9 @@ impl EarlyLintPass for UnusedUnit { if_chain! { if let Some(stmt) = block.stmts.last(); if let ast::StmtKind::Expr(ref expr) = stmt.kind; - if is_unit_expr(expr) && !stmt.span.from_expansion(); + if is_unit_expr(expr); + let ctxt = block.span.ctxt(); + if stmt.span.ctxt() == ctxt && expr.span.ctxt() == ctxt; then { let sp = expr.span; span_lint_and_sugg( diff --git a/clippy_lints/src/utils/conf.rs b/clippy_lints/src/utils/conf.rs index d56855a71c1..52c1dc3bdd2 100644 --- a/clippy_lints/src/utils/conf.rs +++ b/clippy_lints/src/utils/conf.rs @@ -1,121 +1,110 @@ //! Read configurations files. -#![deny(clippy::missing_docs_in_private_items)] +#![allow(clippy::module_name_repetitions)] -use rustc_ast::ast::{LitKind, MetaItemKind, NestedMetaItem}; -use rustc_span::source_map; -use source_map::Span; -use std::lazy::SyncLazy; +use serde::de::{Deserializer, IgnoredAny, IntoDeserializer, MapAccess, Visitor}; +use serde::Deserialize; +use std::error::Error; use std::path::{Path, PathBuf}; -use std::sync::Mutex; use std::{env, fmt, fs, io}; -/// Gets the configuration file from arguments. -pub fn file_from_args(args: &[NestedMetaItem]) -> Result, (&'static str, Span)> { - for arg in args.iter().filter_map(NestedMetaItem::meta_item) { - if arg.has_name(sym!(conf_file)) { - return match arg.kind { - MetaItemKind::Word | MetaItemKind::List(_) => Err(("`conf_file` must be a named value", arg.span)), - MetaItemKind::NameValue(ref value) => { - if let LitKind::Str(ref file, _) = value.kind { - Ok(Some(file.to_string().into())) - } else { - Err(("`conf_file` value must be a string", value.span)) - } - }, - }; - } - } - - Ok(None) +/// Conf with parse errors +#[derive(Default)] +pub struct TryConf { + pub conf: Conf, + pub errors: Vec, } -/// Error from reading a configuration file. -#[derive(Debug)] -pub enum Error { - /// An I/O error. - Io(io::Error), - /// Not valid toml or doesn't fit the expected config format - Toml(String), -} - -impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Io(err) => err.fmt(f), - Self::Toml(err) => err.fmt(f), +impl TryConf { + fn from_error(error: impl Error) -> Self { + Self { + conf: Conf::default(), + errors: vec![error.to_string()], } } } -impl From for Error { - fn from(e: io::Error) -> Self { - Self::Io(e) - } -} - -/// Vec of errors that might be collected during config toml parsing -static ERRORS: SyncLazy>> = SyncLazy::new(|| Mutex::new(Vec::new())); - macro_rules! define_Conf { - ($(#[$doc:meta] ($config:ident, $config_str:literal: $Ty:ty, $default:expr),)+) => { - mod helpers { - use serde::Deserialize; - /// Type used to store lint configuration. - #[derive(Deserialize)] - #[serde(rename_all = "kebab-case", deny_unknown_fields)] - pub struct Conf { - $( - #[$doc] - #[serde(default = $config_str)] - #[serde(with = $config_str)] - pub $config: $Ty, - )+ - #[allow(dead_code)] - #[serde(default)] - third_party: Option<::toml::Value>, + ($( + #[$doc:meta] + $(#[conf_deprecated($dep:literal)])? + ($name:ident: $ty:ty = $default:expr), + )*) => { + /// Clippy lint configuration + pub struct Conf { + $(#[$doc] pub $name: $ty,)* + } + + mod defaults { + $(pub fn $name() -> $ty { $default })* + } + + impl Default for Conf { + fn default() -> Self { + Self { $($name: defaults::$name(),)* } + } + } + + impl<'de> Deserialize<'de> for TryConf { + fn deserialize(deserializer: D) -> Result where D: Deserializer<'de> { + deserializer.deserialize_map(ConfVisitor) + } + } + + #[derive(Deserialize)] + #[serde(field_identifier, rename_all = "kebab-case")] + #[allow(non_camel_case_types)] + enum Field { $($name,)* third_party, } + + struct ConfVisitor; + + impl<'de> Visitor<'de> for ConfVisitor { + type Value = TryConf; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("Conf") } - $( - mod $config { - use serde::Deserialize; - pub fn deserialize<'de, D: serde::Deserializer<'de>>(deserializer: D) -> Result<$Ty, D::Error> { - use super::super::{ERRORS, Error}; - - Ok( - <$Ty>::deserialize(deserializer).unwrap_or_else(|e| { - ERRORS - .lock() - .expect("no threading here") - .push(Error::Toml(e.to_string())); - super::$config() - }) - ) + fn visit_map(self, mut map: V) -> Result where V: MapAccess<'de> { + let mut errors = Vec::new(); + $(let mut $name = None;)* + // could get `Field` here directly, but get `str` first for diagnostics + while let Some(name) = map.next_key::<&str>()? { + match Field::deserialize(name.into_deserializer())? { + $(Field::$name => { + $(errors.push(format!("deprecated field `{}`. {}", name, $dep));)? + match map.next_value() { + Err(e) => errors.push(e.to_string()), + Ok(value) => match $name { + Some(_) => errors.push(format!("duplicate field `{}`", name)), + None => $name = Some(value), + } + } + })* + // white-listed; ignore + Field::third_party => drop(map.next_value::()) } } - - #[must_use] - fn $config() -> $Ty { - let x = $default; - x - } - )+ + let conf = Conf { $($name: $name.unwrap_or_else(defaults::$name),)* }; + Ok(TryConf { conf, errors }) + } } }; } -pub use self::helpers::Conf; +// N.B., this macro is parsed by util/lintlib.py define_Conf! { - /// Lint: CLONED_INSTEAD_OF_COPIED, REDUNDANT_FIELD_NAMES, REDUNDANT_STATIC_LIFETIMES, FILTER_MAP_NEXT, CHECKED_CONVERSIONS, MANUAL_RANGE_CONTAINS, USE_SELF, MEM_REPLACE_WITH_DEFAULT, MANUAL_NON_EXHAUSTIVE, OPTION_AS_REF_DEREF, MAP_UNWRAP_OR, MATCH_LIKE_MATCHES_MACRO, MANUAL_STRIP, MISSING_CONST_FOR_FN, UNNESTED_OR_PATTERNS, FROM_OVER_INTO, PTR_AS_PTR. The minimum rust version that the project supports - (msrv, "msrv": Option, None), + /// Lint: CLONED_INSTEAD_OF_COPIED, REDUNDANT_FIELD_NAMES, REDUNDANT_STATIC_LIFETIMES, FILTER_MAP_NEXT, CHECKED_CONVERSIONS, MANUAL_RANGE_CONTAINS, USE_SELF, MEM_REPLACE_WITH_DEFAULT, MANUAL_NON_EXHAUSTIVE, OPTION_AS_REF_DEREF, MAP_UNWRAP_OR, MATCH_LIKE_MATCHES_MACRO, MANUAL_STRIP, MISSING_CONST_FOR_FN, UNNESTED_OR_PATTERNS, FROM_OVER_INTO, PTR_AS_PTR, IF_THEN_SOME_ELSE_NONE. The minimum rust version that the project supports + (msrv: Option = None), /// Lint: BLACKLISTED_NAME. The list of blacklisted names to lint about. NB: `bar` is not here since it has legitimate uses - (blacklisted_names, "blacklisted_names": Vec, ["foo", "baz", "quux"].iter().map(ToString::to_string).collect()), + (blacklisted_names: Vec = ["foo", "baz", "quux"].iter().map(ToString::to_string).collect()), /// Lint: COGNITIVE_COMPLEXITY. The maximum cognitive complexity a function can have - (cognitive_complexity_threshold, "cognitive_complexity_threshold": u64, 25), + (cognitive_complexity_threshold: u64 = 25), /// DEPRECATED LINT: CYCLOMATIC_COMPLEXITY. Use the Cognitive Complexity lint instead. - (cyclomatic_complexity_threshold, "cyclomatic_complexity_threshold": Option, None), + #[conf_deprecated("Please use `cognitive-complexity-threshold` instead")] + (cyclomatic_complexity_threshold: Option = None), /// Lint: DOC_MARKDOWN. The list of words this lint should not consider as identifiers needing ticks - (doc_valid_idents, "doc_valid_idents": Vec, [ + (doc_valid_idents: Vec = [ "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "DirectX", "ECMAScript", @@ -136,54 +125,47 @@ define_Conf! { "CamelCase", ].iter().map(ToString::to_string).collect()), /// Lint: TOO_MANY_ARGUMENTS. The maximum number of argument a function or method can have - (too_many_arguments_threshold, "too_many_arguments_threshold": u64, 7), + (too_many_arguments_threshold: u64 = 7), /// Lint: TYPE_COMPLEXITY. The maximum complexity a type can have - (type_complexity_threshold, "type_complexity_threshold": u64, 250), + (type_complexity_threshold: u64 = 250), /// Lint: MANY_SINGLE_CHAR_NAMES. The maximum number of single char bindings a scope may have - (single_char_binding_names_threshold, "single_char_binding_names_threshold": u64, 4), + (single_char_binding_names_threshold: u64 = 4), /// Lint: BOXED_LOCAL, USELESS_VEC. The maximum size of objects (in bytes) that will be linted. Larger objects are ok on the heap - (too_large_for_stack, "too_large_for_stack": u64, 200), + (too_large_for_stack: u64 = 200), /// Lint: ENUM_VARIANT_NAMES. The minimum number of enum variants for the lints about variant names to trigger - (enum_variant_name_threshold, "enum_variant_name_threshold": u64, 3), + (enum_variant_name_threshold: u64 = 3), /// Lint: LARGE_ENUM_VARIANT. The maximum size of a enum's variant to avoid box suggestion - (enum_variant_size_threshold, "enum_variant_size_threshold": u64, 200), + (enum_variant_size_threshold: u64 = 200), /// Lint: VERBOSE_BIT_MASK. The maximum allowed size of a bit mask before suggesting to use 'trailing_zeros' - (verbose_bit_mask_threshold, "verbose_bit_mask_threshold": u64, 1), + (verbose_bit_mask_threshold: u64 = 1), /// Lint: DECIMAL_LITERAL_REPRESENTATION. The lower bound for linting decimal literals - (literal_representation_threshold, "literal_representation_threshold": u64, 16384), + (literal_representation_threshold: u64 = 16384), /// Lint: TRIVIALLY_COPY_PASS_BY_REF. The maximum size (in bytes) to consider a `Copy` type for passing by value instead of by reference. - (trivial_copy_size_limit, "trivial_copy_size_limit": Option, None), + (trivial_copy_size_limit: Option = None), /// Lint: LARGE_TYPE_PASS_BY_MOVE. The minimum size (in bytes) to consider a type for passing by reference instead of by value. - (pass_by_value_size_limit, "pass_by_value_size_limit": u64, 256), + (pass_by_value_size_limit: u64 = 256), /// Lint: TOO_MANY_LINES. The maximum number of lines a function or method can have - (too_many_lines_threshold, "too_many_lines_threshold": u64, 100), + (too_many_lines_threshold: u64 = 100), /// Lint: LARGE_STACK_ARRAYS, LARGE_CONST_ARRAYS. The maximum allowed size for arrays on the stack - (array_size_threshold, "array_size_threshold": u64, 512_000), + (array_size_threshold: u64 = 512_000), /// Lint: VEC_BOX. The size of the boxed type in bytes, where boxing in a `Vec` is allowed - (vec_box_size_threshold, "vec_box_size_threshold": u64, 4096), + (vec_box_size_threshold: u64 = 4096), /// Lint: TYPE_REPETITION_IN_BOUNDS. The maximum number of bounds a trait can have to be linted - (max_trait_bounds, "max_trait_bounds": u64, 3), + (max_trait_bounds: u64 = 3), /// Lint: STRUCT_EXCESSIVE_BOOLS. The maximum number of bools a struct can have - (max_struct_bools, "max_struct_bools": u64, 3), + (max_struct_bools: u64 = 3), /// Lint: FN_PARAMS_EXCESSIVE_BOOLS. The maximum number of bools function parameters can have - (max_fn_params_bools, "max_fn_params_bools": u64, 3), + (max_fn_params_bools: u64 = 3), /// Lint: WILDCARD_IMPORTS. Whether to allow certain wildcard imports (prelude, super in tests). - (warn_on_all_wildcard_imports, "warn_on_all_wildcard_imports": bool, false), + (warn_on_all_wildcard_imports: bool = false), /// Lint: DISALLOWED_METHOD. The list of disallowed methods, written as fully qualified paths. - (disallowed_methods, "disallowed_methods": Vec, Vec::::new()), + (disallowed_methods: Vec = Vec::new()), /// Lint: UNREADABLE_LITERAL. Should the fraction of a decimal be linted to include separators. - (unreadable_literal_lint_fractions, "unreadable_literal_lint_fractions": bool, true), + (unreadable_literal_lint_fractions: bool = true), /// Lint: UPPER_CASE_ACRONYMS. Enables verbose mode. Triggers if there is more than one uppercase char next to each other - (upper_case_acronyms_aggressive, "upper_case_acronyms_aggressive": bool, false), + (upper_case_acronyms_aggressive: bool = false), /// Lint: _CARGO_COMMON_METADATA. For internal testing only, ignores the current `publish` settings in the Cargo manifest. - (cargo_ignore_publish, "cargo_ignore_publish": bool, false), -} - -impl Default for Conf { - #[must_use] - fn default() -> Self { - toml::from_str("").expect("we never error on empty config files") - } + (cargo_ignore_publish: bool = false), } /// Search for the configuration file. @@ -217,43 +199,13 @@ pub fn lookup_conf_file() -> io::Result> { } } -/// Produces a `Conf` filled with the default values and forwards the errors -/// -/// Used internally for convenience -fn default(errors: Vec) -> (Conf, Vec) { - (Conf::default(), errors) -} - /// Read the `toml` configuration file. /// /// In case of error, the function tries to continue as much as possible. -pub fn read(path: &Path) -> (Conf, Vec) { +pub fn read(path: &Path) -> TryConf { let content = match fs::read_to_string(path) { + Err(e) => return TryConf::from_error(e), Ok(content) => content, - Err(err) => return default(vec![err.into()]), }; - - assert!(ERRORS.lock().expect("no threading -> mutex always safe").is_empty()); - match toml::from_str(&content) { - Ok(toml) => { - let mut errors = ERRORS.lock().expect("no threading -> mutex always safe").split_off(0); - - let toml_ref: &Conf = &toml; - - let cyc_field: Option = toml_ref.cyclomatic_complexity_threshold; - - if cyc_field.is_some() { - let cyc_err = "found deprecated field `cyclomatic-complexity-threshold`. Please use `cognitive-complexity-threshold` instead.".to_string(); - errors.push(Error::Toml(cyc_err)); - } - - (toml, errors) - }, - Err(e) => { - let mut errors = ERRORS.lock().expect("no threading -> mutex always safe").split_off(0); - errors.push(Error::Toml(e.to_string())); - - default(errors) - }, - } + toml::from_str(&content).unwrap_or_else(TryConf::from_error) } diff --git a/clippy_lints/src/utils/internal_lints.rs b/clippy_lints/src/utils/internal_lints.rs index 3d3d0e19d26..ee7be24eae8 100644 --- a/clippy_lints/src/utils/internal_lints.rs +++ b/clippy_lints/src/utils/internal_lints.rs @@ -32,6 +32,9 @@ use rustc_typeck::hir_ty_to_ty; use std::borrow::{Borrow, Cow}; +#[cfg(feature = "metadata-collector-lint")] +pub mod metadata_collector; + declare_clippy_lint! { /// **What it does:** Checks for various things we like to keep tidy in clippy. /// diff --git a/clippy_lints/src/utils/internal_lints/metadata_collector.rs b/clippy_lints/src/utils/internal_lints/metadata_collector.rs new file mode 100644 index 00000000000..e85637ca758 --- /dev/null +++ b/clippy_lints/src/utils/internal_lints/metadata_collector.rs @@ -0,0 +1,632 @@ +//! This lint is used to collect metadata about clippy lints. This metadata is exported as a json +//! file and then used to generate the [clippy lint list](https://rust-lang.github.io/rust-clippy/master/index.html) +//! +//! This module and therefor the entire lint is guarded by a feature flag called +//! `metadata-collector-lint` +//! +//! The module transforms all lint names to ascii lowercase to ensure that we don't have mismatches +//! during any comparison or mapping. (Please take care of this, it's not fun to spend time on such +//! a simple mistake) + +// # NITs +// - TODO xFrednet 2021-02-13: Collect depreciations and maybe renames + +use if_chain::if_chain; +use rustc_data_structures::fx::FxHashMap; +use rustc_hir::{ + self as hir, def::DefKind, intravisit, intravisit::Visitor, ExprKind, Item, ItemKind, Mutability, QPath, +}; +use rustc_lint::{CheckLintNameResult, LateContext, LateLintPass, LintContext, LintId}; +use rustc_middle::hir::map::Map; +use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_span::{sym, Loc, Span, Symbol}; +use serde::{ser::SerializeStruct, Serialize, Serializer}; +use std::collections::BinaryHeap; +use std::fs::{self, OpenOptions}; +use std::io::prelude::*; +use std::path::Path; + +use crate::utils::internal_lints::is_lint_ref_type; +use clippy_utils::{ + diagnostics::span_lint, last_path_segment, match_function_call, match_path, paths, ty::match_type, + ty::walk_ptrs_ty_depth, +}; + +/// This is the output file of the lint collector. +const OUTPUT_FILE: &str = "../util/gh-pages/metadata_collection.json"; +/// These lints are excluded from the export. +const BLACK_LISTED_LINTS: [&str; 3] = ["lint_author", "deep_code_inspection", "internal_metadata_collector"]; +/// These groups will be ignored by the lint group matcher. This is useful for collections like +/// `clippy::all` +const IGNORED_LINT_GROUPS: [&str; 1] = ["clippy::all"]; +/// Lints within this group will be excluded from the collection +const EXCLUDED_LINT_GROUPS: [&str; 1] = ["clippy::internal"]; + +const LINT_EMISSION_FUNCTIONS: [&[&str]; 7] = [ + &["clippy_utils", "diagnostics", "span_lint"], + &["clippy_utils", "diagnostics", "span_lint_and_help"], + &["clippy_utils", "diagnostics", "span_lint_and_note"], + &["clippy_utils", "diagnostics", "span_lint_hir"], + &["clippy_utils", "diagnostics", "span_lint_and_sugg"], + &["clippy_utils", "diagnostics", "span_lint_and_then"], + &["clippy_utils", "diagnostics", "span_lint_hir_and_then"], +]; +const SUGGESTION_DIAGNOSTIC_BUILDER_METHODS: [(&str, bool); 9] = [ + ("span_suggestion", false), + ("span_suggestion_short", false), + ("span_suggestion_verbose", false), + ("span_suggestion_hidden", false), + ("tool_only_span_suggestion", false), + ("multipart_suggestion", true), + ("multipart_suggestions", true), + ("tool_only_multipart_suggestion", true), + ("span_suggestions", true), +]; +const SUGGESTION_FUNCTIONS: [&[&str]; 2] = [ + &["clippy_utils", "diagnostics", "multispan_sugg"], + &["clippy_utils", "diagnostics", "multispan_sugg_with_applicability"], +]; + +/// The index of the applicability name of `paths::APPLICABILITY_VALUES` +const APPLICABILITY_NAME_INDEX: usize = 2; + +declare_clippy_lint! { + /// **What it does:** Collects metadata about clippy lints for the website. + /// + /// This lint will be used to report problems of syntax parsing. You should hopefully never + /// see this but never say never I guess ^^ + /// + /// **Why is this bad?** This is not a bad thing but definitely a hacky way to do it. See + /// issue [#4310](https://github.com/rust-lang/rust-clippy/issues/4310) for a discussion + /// about the implementation. + /// + /// **Known problems:** Hopefully none. It would be pretty uncool to have a problem here :) + /// + /// **Example output:** + /// ```json,ignore + /// { + /// "id": "internal_metadata_collector", + /// "id_span": { + /// "path": "clippy_lints/src/utils/internal_lints/metadata_collector.rs", + /// "line": 1 + /// }, + /// "group": "clippy::internal", + /// "docs": " **What it does:** Collects metadata about clippy lints for the website. [...] " + /// } + /// ``` + pub INTERNAL_METADATA_COLLECTOR, + internal_warn, + "A busy bee collection metadata about lints" +} + +impl_lint_pass!(MetadataCollector => [INTERNAL_METADATA_COLLECTOR]); + +#[allow(clippy::module_name_repetitions)] +#[derive(Debug, Clone, Default)] +pub struct MetadataCollector { + /// All collected lints + /// + /// We use a Heap here to have the lints added in alphabetic order in the export + lints: BinaryHeap, + applicability_info: FxHashMap, +} + +impl Drop for MetadataCollector { + /// You might ask: How hacky is this? + /// My answer: YES + fn drop(&mut self) { + // The metadata collector gets dropped twice, this makes sure that we only write + // when the list is full + if self.lints.is_empty() { + return; + } + + let mut applicability_info = std::mem::take(&mut self.applicability_info); + + // Mapping the final data + let mut lints = std::mem::take(&mut self.lints).into_sorted_vec(); + lints + .iter_mut() + .for_each(|x| x.applicability = applicability_info.remove(&x.id)); + + // Outputting + if Path::new(OUTPUT_FILE).exists() { + fs::remove_file(OUTPUT_FILE).unwrap(); + } + let mut file = OpenOptions::new().write(true).create(true).open(OUTPUT_FILE).unwrap(); + writeln!(file, "{}", serde_json::to_string_pretty(&lints).unwrap()).unwrap(); + } +} + +#[derive(Debug, Clone, Serialize, PartialEq, Eq, PartialOrd, Ord)] +struct LintMetadata { + id: String, + id_span: SerializableSpan, + group: String, + docs: String, + /// This field is only used in the output and will only be + /// mapped shortly before the actual output. + applicability: Option, +} + +impl LintMetadata { + fn new(id: String, id_span: SerializableSpan, group: String, docs: String) -> Self { + Self { + id, + id_span, + group, + docs, + applicability: None, + } + } +} + +#[derive(Debug, Clone, Serialize, PartialEq, Eq, PartialOrd, Ord)] +struct SerializableSpan { + path: String, + line: usize, +} + +impl std::fmt::Display for SerializableSpan { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}:{}", self.path.rsplit('/').next().unwrap_or_default(), self.line) + } +} + +impl SerializableSpan { + fn from_item(cx: &LateContext<'_>, item: &Item<'_>) -> Self { + Self::from_span(cx, item.ident.span) + } + + fn from_span(cx: &LateContext<'_>, span: Span) -> Self { + let loc: Loc = cx.sess().source_map().lookup_char_pos(span.lo()); + + Self { + path: format!("{}", loc.file.name), + line: loc.line, + } + } +} + +#[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord)] +struct ApplicabilityInfo { + /// Indicates if any of the lint emissions uses multiple spans. This is related to + /// [rustfix#141](https://github.com/rust-lang/rustfix/issues/141) as such suggestions can + /// currently not be applied automatically. + is_multi_part_suggestion: bool, + applicability: Option, +} + +impl Serialize for ApplicabilityInfo { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let index = self.applicability.unwrap_or_default(); + + let mut s = serializer.serialize_struct("ApplicabilityInfo", 2)?; + s.serialize_field("is_multi_part_suggestion", &self.is_multi_part_suggestion)?; + s.serialize_field( + "applicability", + &paths::APPLICABILITY_VALUES[index][APPLICABILITY_NAME_INDEX], + )?; + s.end() + } +} + +impl<'hir> LateLintPass<'hir> for MetadataCollector { + /// Collecting lint declarations like: + /// ```rust, ignore + /// declare_clippy_lint! { + /// /// **What it does:** Something IDK. + /// pub SOME_LINT, + /// internal, + /// "Who am I?" + /// } + /// ``` + fn check_item(&mut self, cx: &LateContext<'hir>, item: &'hir Item<'_>) { + if_chain! { + // item validation + if let ItemKind::Static(ref ty, Mutability::Not, _) = item.kind; + if is_lint_ref_type(cx, ty); + // blacklist check + let lint_name = sym_to_string(item.ident.name).to_ascii_lowercase(); + if !BLACK_LISTED_LINTS.contains(&lint_name.as_str()); + // metadata extraction + if let Some(group) = get_lint_group_or_lint(cx, &lint_name, item); + if let Some(docs) = extract_attr_docs_or_lint(cx, item); + then { + self.lints.push(LintMetadata::new( + lint_name, + SerializableSpan::from_item(cx, item), + group, + docs, + )); + } + } + } + + /// Collecting constant applicability from the actual lint emissions + /// + /// Example: + /// ```rust, ignore + /// span_lint_and_sugg( + /// cx, + /// SOME_LINT, + /// item.span, + /// "Le lint message", + /// "Here comes help:", + /// "#![allow(clippy::all)]", + /// Applicability::MachineApplicable, // <-- Extracts this constant value + /// ); + /// ``` + fn check_expr(&mut self, cx: &LateContext<'hir>, expr: &'hir hir::Expr<'_>) { + if let Some(args) = match_lint_emission(cx, expr) { + let mut emission_info = extract_emission_info(cx, args); + if emission_info.is_empty() { + // See: + // - src/misc.rs:734:9 + // - src/methods/mod.rs:3545:13 + // - src/methods/mod.rs:3496:13 + // We are basically unable to resolve the lint name it self. + return; + } + + for (lint_name, applicability, is_multi_part) in emission_info.drain(..) { + let app_info = self.applicability_info.entry(lint_name).or_default(); + app_info.applicability = applicability; + app_info.is_multi_part_suggestion = is_multi_part; + } + } + } +} + +// ================================================================== +// Lint definition extraction +// ================================================================== +fn sym_to_string(sym: Symbol) -> String { + sym.as_str().to_string() +} + +fn extract_attr_docs_or_lint(cx: &LateContext<'_>, item: &Item<'_>) -> Option { + extract_attr_docs(cx, item).or_else(|| { + lint_collection_error_item(cx, item, "could not collect the lint documentation"); + None + }) +} + +/// This function collects all documentation that has been added to an item using +/// `#[doc = r""]` attributes. Several attributes are aggravated using line breaks +/// +/// ```ignore +/// #[doc = r"Hello world!"] +/// #[doc = r"=^.^="] +/// struct SomeItem {} +/// ``` +/// +/// Would result in `Hello world!\n=^.^=\n` +fn extract_attr_docs(cx: &LateContext<'_>, item: &Item<'_>) -> Option { + cx.tcx + .hir() + .attrs(item.hir_id()) + .iter() + .filter_map(|ref x| x.doc_str().map(|sym| sym.as_str().to_string())) + .reduce(|mut acc, sym| { + acc.push_str(&sym); + acc.push('\n'); + acc + }) +} + +fn get_lint_group_or_lint(cx: &LateContext<'_>, lint_name: &str, item: &'hir Item<'_>) -> Option { + let result = cx.lint_store.check_lint_name(lint_name, Some(sym::clippy)); + if let CheckLintNameResult::Tool(Ok(lint_lst)) = result { + get_lint_group(cx, lint_lst[0]) + .or_else(|| { + lint_collection_error_item(cx, item, "Unable to determine lint group"); + None + }) + .filter(|group| !EXCLUDED_LINT_GROUPS.contains(&group.as_str())) + } else { + lint_collection_error_item(cx, item, "Unable to find lint in lint_store"); + None + } +} + +fn get_lint_group(cx: &LateContext<'_>, lint_id: LintId) -> Option { + for (group_name, lints, _) in &cx.lint_store.get_lint_groups() { + if IGNORED_LINT_GROUPS.contains(group_name) { + continue; + } + + if lints.iter().any(|x| *x == lint_id) { + return Some((*group_name).to_string()); + } + } + + None +} + +// ================================================================== +// Lint emission +// ================================================================== +fn lint_collection_error_item(cx: &LateContext<'_>, item: &Item<'_>, message: &str) { + span_lint( + cx, + INTERNAL_METADATA_COLLECTOR, + item.ident.span, + &format!("metadata collection error for `{}`: {}", item.ident.name, message), + ); +} + +// ================================================================== +// Applicability +// ================================================================== +/// This function checks if a given expression is equal to a simple lint emission function call. +/// It will return the function arguments if the emission matched any function. +fn match_lint_emission<'hir>(cx: &LateContext<'hir>, expr: &'hir hir::Expr<'_>) -> Option<&'hir [hir::Expr<'hir>]> { + LINT_EMISSION_FUNCTIONS + .iter() + .find_map(|emission_fn| match_function_call(cx, expr, emission_fn)) +} + +fn take_higher_applicability(a: Option, b: Option) -> Option { + a.map_or(b, |a| a.max(b.unwrap_or_default()).into()) +} + +fn extract_emission_info<'hir>( + cx: &LateContext<'hir>, + args: &'hir [hir::Expr<'hir>], +) -> Vec<(String, Option, bool)> { + let mut lints = Vec::new(); + let mut applicability = None; + let mut multi_part = false; + + for arg in args { + let (arg_ty, _) = walk_ptrs_ty_depth(cx.typeck_results().expr_ty(&arg)); + + if match_type(cx, arg_ty, &paths::LINT) { + // If we found the lint arg, extract the lint name + let mut resolved_lints = resolve_lints(cx, arg); + lints.append(&mut resolved_lints); + } else if match_type(cx, arg_ty, &paths::APPLICABILITY) { + applicability = resolve_applicability(cx, arg); + } else if arg_ty.is_closure() { + multi_part |= check_is_multi_part(cx, arg); + // TODO xFrednet 2021-03-01: don't use or_else but rather a comparison + applicability = applicability.or_else(|| resolve_applicability(cx, arg)); + } + } + + lints + .drain(..) + .map(|lint_name| (lint_name, applicability, multi_part)) + .collect() +} + +/// Resolves the possible lints that this expression could reference +fn resolve_lints(cx: &LateContext<'hir>, expr: &'hir hir::Expr<'hir>) -> Vec { + let mut resolver = LintResolver::new(cx); + resolver.visit_expr(expr); + resolver.lints +} + +/// This function tries to resolve the linked applicability to the given expression. +fn resolve_applicability(cx: &LateContext<'hir>, expr: &'hir hir::Expr<'hir>) -> Option { + let mut resolver = ApplicabilityResolver::new(cx); + resolver.visit_expr(expr); + resolver.complete() +} + +fn check_is_multi_part(cx: &LateContext<'hir>, closure_expr: &'hir hir::Expr<'hir>) -> bool { + if let ExprKind::Closure(_, _, body_id, _, _) = closure_expr.kind { + let mut scanner = IsMultiSpanScanner::new(cx); + intravisit::walk_body(&mut scanner, cx.tcx.hir().body(body_id)); + return scanner.is_multi_part(); + } else if let Some(local) = get_parent_local(cx, closure_expr) { + if let Some(local_init) = local.init { + return check_is_multi_part(cx, local_init); + } + } + + false +} + +struct LintResolver<'a, 'hir> { + cx: &'a LateContext<'hir>, + lints: Vec, +} + +impl<'a, 'hir> LintResolver<'a, 'hir> { + fn new(cx: &'a LateContext<'hir>) -> Self { + Self { + cx, + lints: Vec::::default(), + } + } +} + +impl<'a, 'hir> intravisit::Visitor<'hir> for LintResolver<'a, 'hir> { + type Map = Map<'hir>; + + fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap { + intravisit::NestedVisitorMap::All(self.cx.tcx.hir()) + } + + fn visit_expr(&mut self, expr: &'hir hir::Expr<'hir>) { + if_chain! { + if let ExprKind::Path(qpath) = &expr.kind; + if let QPath::Resolved(_, path) = qpath; + + let (expr_ty, _) = walk_ptrs_ty_depth(self.cx.typeck_results().expr_ty(&expr)); + if match_type(self.cx, expr_ty, &paths::LINT); + then { + if let hir::def::Res::Def(DefKind::Static, _) = path.res { + let lint_name = last_path_segment(qpath).ident.name; + self.lints.push(sym_to_string(lint_name).to_ascii_lowercase()); + } else if let Some(local) = get_parent_local(self.cx, expr) { + if let Some(local_init) = local.init { + intravisit::walk_expr(self, local_init); + } + } + } + } + + intravisit::walk_expr(self, expr); + } +} + +/// This visitor finds the highest applicability value in the visited expressions +struct ApplicabilityResolver<'a, 'hir> { + cx: &'a LateContext<'hir>, + /// This is the index of hightest `Applicability` for `paths::APPLICABILITY_VALUES` + applicability_index: Option, +} + +impl<'a, 'hir> ApplicabilityResolver<'a, 'hir> { + fn new(cx: &'a LateContext<'hir>) -> Self { + Self { + cx, + applicability_index: None, + } + } + + fn add_new_index(&mut self, new_index: usize) { + self.applicability_index = take_higher_applicability(self.applicability_index, Some(new_index)); + } + + fn complete(self) -> Option { + self.applicability_index + } +} + +impl<'a, 'hir> intravisit::Visitor<'hir> for ApplicabilityResolver<'a, 'hir> { + type Map = Map<'hir>; + + fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap { + intravisit::NestedVisitorMap::All(self.cx.tcx.hir()) + } + + fn visit_path(&mut self, path: &'hir hir::Path<'hir>, _id: hir::HirId) { + for (index, enum_value) in paths::APPLICABILITY_VALUES.iter().enumerate() { + if match_path(path, enum_value) { + self.add_new_index(index); + return; + } + } + } + + fn visit_expr(&mut self, expr: &'hir hir::Expr<'hir>) { + let (expr_ty, _) = walk_ptrs_ty_depth(self.cx.typeck_results().expr_ty(&expr)); + + if_chain! { + if match_type(self.cx, expr_ty, &paths::APPLICABILITY); + if let Some(local) = get_parent_local(self.cx, expr); + if let Some(local_init) = local.init; + then { + intravisit::walk_expr(self, local_init); + } + }; + + // TODO xFrednet 2021-03-01: support function arguments? + + intravisit::walk_expr(self, expr); + } +} + +/// This returns the parent local node if the expression is a reference one +fn get_parent_local(cx: &LateContext<'hir>, expr: &'hir hir::Expr<'hir>) -> Option<&'hir hir::Local<'hir>> { + if let ExprKind::Path(QPath::Resolved(_, path)) = expr.kind { + if let hir::def::Res::Local(local_hir) = path.res { + return get_parent_local_hir_id(cx, local_hir); + } + } + + None +} + +fn get_parent_local_hir_id(cx: &LateContext<'hir>, hir_id: hir::HirId) -> Option<&'hir hir::Local<'hir>> { + let map = cx.tcx.hir(); + + match map.find(map.get_parent_node(hir_id)) { + Some(hir::Node::Local(local)) => Some(local), + Some(hir::Node::Pat(pattern)) => get_parent_local_hir_id(cx, pattern.hir_id), + _ => None, + } +} + +/// This visitor finds the highest applicability value in the visited expressions +struct IsMultiSpanScanner<'a, 'hir> { + cx: &'a LateContext<'hir>, + suggestion_count: usize, +} + +impl<'a, 'hir> IsMultiSpanScanner<'a, 'hir> { + fn new(cx: &'a LateContext<'hir>) -> Self { + Self { + cx, + suggestion_count: 0, + } + } + + /// Add a new single expression suggestion to the counter + fn add_single_span_suggestion(&mut self) { + self.suggestion_count += 1; + } + + /// Signals that a suggestion with possible multiple spans was found + fn add_multi_part_suggestion(&mut self) { + self.suggestion_count += 2; + } + + /// Checks if the suggestions include multiple spanns + fn is_multi_part(&self) -> bool { + self.suggestion_count > 1 + } +} + +impl<'a, 'hir> intravisit::Visitor<'hir> for IsMultiSpanScanner<'a, 'hir> { + type Map = Map<'hir>; + + fn nested_visit_map(&mut self) -> intravisit::NestedVisitorMap { + intravisit::NestedVisitorMap::All(self.cx.tcx.hir()) + } + + fn visit_expr(&mut self, expr: &'hir hir::Expr<'hir>) { + // Early return if the lint is already multi span + if self.is_multi_part() { + return; + } + + match &expr.kind { + ExprKind::Call(fn_expr, _args) => { + let found_function = SUGGESTION_FUNCTIONS + .iter() + .any(|func_path| match_function_call(self.cx, fn_expr, func_path).is_some()); + if found_function { + // These functions are all multi part suggestions + self.add_single_span_suggestion() + } + }, + ExprKind::MethodCall(path, _path_span, arg, _arg_span) => { + let (self_ty, _) = walk_ptrs_ty_depth(self.cx.typeck_results().expr_ty(&arg[0])); + if match_type(self.cx, self_ty, &paths::DIAGNOSTIC_BUILDER) { + let called_method = path.ident.name.as_str().to_string(); + for (method_name, is_multi_part) in &SUGGESTION_DIAGNOSTIC_BUILDER_METHODS { + if *method_name == called_method { + if *is_multi_part { + self.add_multi_part_suggestion(); + } else { + self.add_single_span_suggestion(); + } + break; + } + } + } + }, + _ => {}, + } + + intravisit::walk_expr(self, expr); + } +} diff --git a/clippy_lints/src/utils/mod.rs b/clippy_lints/src/utils/mod.rs index d8b31344e6d..b67448e3a57 100644 --- a/clippy_lints/src/utils/mod.rs +++ b/clippy_lints/src/utils/mod.rs @@ -1,5 +1,5 @@ pub mod author; pub mod conf; pub mod inspector; -#[cfg(feature = "internal-lints")] +#[cfg(any(feature = "internal-lints", feature = "metadata-collector-lint"))] pub mod internal_lints; diff --git a/clippy_utils/Cargo.toml b/clippy_utils/Cargo.toml index d04c5f889dd..0a1d4e11142 100644 --- a/clippy_utils/Cargo.toml +++ b/clippy_utils/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "clippy_utils" -version = "0.1.53" +version = "0.1.54" authors = ["The Rust Clippy Developers"] edition = "2018" publish = false @@ -15,6 +15,7 @@ rustc-semver="1.1.0" [features] internal-lints = [] +metadata-collector-lint = [] [package.metadata.rust-analyzer] # This crate uses #[feature(rustc_private)] diff --git a/clippy_utils/src/diagnostics.rs b/clippy_utils/src/diagnostics.rs index 7f827f1759d..a4efae54894 100644 --- a/clippy_utils/src/diagnostics.rs +++ b/clippy_utils/src/diagnostics.rs @@ -1,4 +1,12 @@ //! Clippy wrappers around rustc's diagnostic functions. +//! +//! These functions are used by the `INTERNAL_METADATA_COLLECTOR` lint to collect the corresponding +//! lint applicability. Please make sure that you update the `LINT_EMISSION_FUNCTIONS` variable in +//! `clippy_lints::utils::internal_lints::metadata_collector` when a new function is added +//! or renamed. +//! +//! Thank you! +//! ~The `INTERNAL_METADATA_COLLECTOR` lint use rustc_errors::{Applicability, DiagnosticBuilder}; use rustc_hir::HirId; diff --git a/clippy_utils/src/hir_utils.rs b/clippy_utils/src/hir_utils.rs index 07ae6e924e2..3b01158acd9 100644 --- a/clippy_utils/src/hir_utils.rs +++ b/clippy_utils/src/hir_utils.rs @@ -713,7 +713,7 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { self.hash_expr(e); for arm in arms { - // TODO: arm.pat? + self.hash_pat(arm.pat); if let Some(ref e) = arm.guard { self.hash_guard(e); } @@ -791,6 +791,72 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { // self.maybe_typeck_results.unwrap().qpath_res(p, id).hash(&mut self.s); } + pub fn hash_pat(&mut self, pat: &Pat<'_>) { + std::mem::discriminant(&pat.kind).hash(&mut self.s); + match pat.kind { + PatKind::Binding(ann, _, _, pat) => { + ann.hash_stable(&mut self.cx.tcx.get_stable_hashing_context(), &mut self.s); + if let Some(pat) = pat { + self.hash_pat(pat); + } + }, + PatKind::Box(pat) => self.hash_pat(pat), + PatKind::Lit(expr) => self.hash_expr(expr), + PatKind::Or(pats) => { + for pat in pats { + self.hash_pat(pat); + } + }, + PatKind::Path(ref qpath) => self.hash_qpath(qpath), + PatKind::Range(s, e, i) => { + if let Some(s) = s { + self.hash_expr(s); + } + if let Some(e) = e { + self.hash_expr(e); + } + i.hash_stable(&mut self.cx.tcx.get_stable_hashing_context(), &mut self.s); + }, + PatKind::Ref(pat, m) => { + self.hash_pat(pat); + m.hash(&mut self.s); + }, + PatKind::Slice(l, m, r) => { + for pat in l { + self.hash_pat(pat); + } + if let Some(pat) = m { + self.hash_pat(pat); + } + for pat in r { + self.hash_pat(pat); + } + }, + PatKind::Struct(ref qpath, fields, e) => { + self.hash_qpath(qpath); + for f in fields { + self.hash_name(f.ident.name); + self.hash_pat(f.pat); + } + e.hash(&mut self.s) + }, + PatKind::Tuple(pats, e) => { + for pat in pats { + self.hash_pat(pat); + } + e.hash(&mut self.s); + }, + PatKind::TupleStruct(ref qpath, pats, e) => { + self.hash_qpath(qpath); + for pat in pats { + self.hash_pat(pat); + } + e.hash(&mut self.s); + }, + PatKind::Wild => {}, + } + } + pub fn hash_path(&mut self, path: &Path<'_>) { match path.res { // constant hash since equality is dependant on inter-expression context @@ -808,6 +874,7 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { match &b.kind { StmtKind::Local(local) => { + self.hash_pat(local.pat); if let Some(ref init) = local.init { self.hash_expr(init); } @@ -827,7 +894,7 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { } } - pub fn hash_lifetime(&mut self, lifetime: &Lifetime) { + pub fn hash_lifetime(&mut self, lifetime: Lifetime) { std::mem::discriminant(&lifetime.name).hash(&mut self.s); if let LifetimeName::Param(ref name) = lifetime.name { std::mem::discriminant(name).hash(&mut self.s); @@ -844,12 +911,8 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { } pub fn hash_ty(&mut self, ty: &Ty<'_>) { - self.hash_tykind(&ty.kind); - } - - pub fn hash_tykind(&mut self, ty: &TyKind<'_>) { - std::mem::discriminant(ty).hash(&mut self.s); - match ty { + std::mem::discriminant(&ty.kind).hash(&mut self.s); + match ty.kind { TyKind::Slice(ty) => { self.hash_ty(ty); }, @@ -857,11 +920,11 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { self.hash_ty(ty); self.hash_body(anon_const.body); }, - TyKind::Ptr(mut_ty) => { + TyKind::Ptr(ref mut_ty) => { self.hash_ty(&mut_ty.ty); mut_ty.mutbl.hash(&mut self.s); }, - TyKind::Rptr(lifetime, mut_ty) => { + TyKind::Rptr(lifetime, ref mut_ty) => { self.hash_lifetime(lifetime); self.hash_ty(&mut_ty.ty); mut_ty.mutbl.hash(&mut self.s); @@ -883,11 +946,11 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { bfn.decl.c_variadic.hash(&mut self.s); }, TyKind::Tup(ty_list) => { - for ty in *ty_list { + for ty in ty_list { self.hash_ty(ty); } }, - TyKind::Path(qpath) => match qpath { + TyKind::Path(ref qpath) => match qpath { QPath::Resolved(ref maybe_ty, ref path) => { if let Some(ref ty) = maybe_ty { self.hash_ty(ty); @@ -927,9 +990,9 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { fn hash_generic_args(&mut self, arg_list: &[GenericArg<'_>]) { for arg in arg_list { - match arg { - GenericArg::Lifetime(ref l) => self.hash_lifetime(l), - GenericArg::Type(ref ty) => self.hash_ty(&ty), + match *arg { + GenericArg::Lifetime(l) => self.hash_lifetime(l), + GenericArg::Type(ref ty) => self.hash_ty(ty), GenericArg::Const(ref ca) => self.hash_body(ca.value.body), } } diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index e81a92eb74c..f5ee49c7d5f 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -61,12 +61,12 @@ use rustc_data_structures::fx::FxHashMap; use rustc_hir as hir; use rustc_hir::def::{DefKind, Res}; use rustc_hir::def_id::{DefId, LOCAL_CRATE}; -use rustc_hir::intravisit::{self, walk_expr, ErasedMap, NestedVisitorMap, Visitor}; +use rustc_hir::intravisit::{self, walk_expr, ErasedMap, FnKind, NestedVisitorMap, Visitor}; use rustc_hir::LangItem::{ResultErr, ResultOk}; use rustc_hir::{ def, Arm, BindingAnnotation, Block, Body, Constness, Destination, Expr, ExprKind, FnDecl, GenericArgs, HirId, Impl, - ImplItem, ImplItemKind, Item, ItemKind, LangItem, Local, MatchSource, Node, Param, Pat, PatKind, Path, PathSegment, - QPath, Stmt, StmtKind, TraitItem, TraitItemKind, TraitRef, TyKind, + ImplItem, ImplItemKind, IsAsync, Item, ItemKind, LangItem, Local, MatchSource, Node, Param, Pat, PatKind, Path, + PathSegment, QPath, Stmt, StmtKind, TraitItem, TraitItemKind, TraitRef, TyKind, }; use rustc_lint::{LateContext, Level, Lint, LintContext}; use rustc_middle::hir::exports::Export; @@ -821,7 +821,13 @@ pub fn get_parent_node(tcx: TyCtxt<'_>, id: HirId) -> Option> { /// Gets the parent expression, if any –- this is useful to constrain a lint. pub fn get_parent_expr<'tcx>(cx: &LateContext<'tcx>, e: &Expr<'_>) -> Option<&'tcx Expr<'tcx>> { - match get_parent_node(cx.tcx, e.hir_id) { + get_parent_expr_for_hir(cx, e.hir_id) +} + +/// This retrieves the parent for the given `HirId` if it's an expression. This is useful for +/// constraint lints +pub fn get_parent_expr_for_hir<'tcx>(cx: &LateContext<'tcx>, hir_id: hir::HirId) -> Option<&'tcx Expr<'tcx>> { + match get_parent_node(cx.tcx, hir_id) { Some(Node::Expr(parent)) => Some(parent), _ => None, } @@ -1301,6 +1307,40 @@ pub fn if_sequence<'tcx>(mut expr: &'tcx Expr<'tcx>) -> (Vec<&'tcx Expr<'tcx>>, (conds, blocks) } +/// Checks if the given function kind is an async function. +pub fn is_async_fn(kind: FnKind<'_>) -> bool { + matches!(kind, FnKind::ItemFn(_, _, header, _) if header.asyncness == IsAsync::Async) +} + +/// Peels away all the compiler generated code surrounding the body of an async function, +pub fn get_async_fn_body(tcx: TyCtxt<'tcx>, body: &Body<'_>) -> Option<&'tcx Expr<'tcx>> { + if let ExprKind::Call( + _, + &[Expr { + kind: ExprKind::Closure(_, _, body, _, _), + .. + }], + ) = body.value.kind + { + if let ExprKind::Block( + Block { + stmts: [], + expr: + Some(Expr { + kind: ExprKind::DropTemps(expr), + .. + }), + .. + }, + _, + ) = tcx.hir().body(body).value.kind + { + return Some(expr); + } + }; + None +} + // Finds the `#[must_use]` attribute, if any pub fn must_use_attr(attrs: &[Attribute]) -> Option<&Attribute> { attrs.iter().find(|a| a.has_name(sym::must_use)) diff --git a/clippy_utils/src/paths.rs b/clippy_utils/src/paths.rs index 1fa439639b2..8037d670500 100644 --- a/clippy_utils/src/paths.rs +++ b/clippy_utils/src/paths.rs @@ -5,6 +5,17 @@ //! See for more information. pub const ANY_TRAIT: [&str; 3] = ["core", "any", "Any"]; +#[cfg(feature = "metadata-collector-lint")] +pub const APPLICABILITY: [&str; 2] = ["rustc_lint_defs", "Applicability"]; +#[cfg(feature = "metadata-collector-lint")] +pub const APPLICABILITY_VALUES: [[&str; 3]; 4] = [ + ["rustc_lint_defs", "Applicability", "Unspecified"], + ["rustc_lint_defs", "Applicability", "HasPlaceholders"], + ["rustc_lint_defs", "Applicability", "MaybeIncorrect"], + ["rustc_lint_defs", "Applicability", "MachineApplicable"], +]; +#[cfg(feature = "metadata-collector-lint")] +pub const DIAGNOSTIC_BUILDER: [&str; 3] = ["rustc_errors", "diagnostic_builder", "DiagnosticBuilder"]; pub const ARC_PTR_EQ: [&str; 4] = ["alloc", "sync", "Arc", "ptr_eq"]; pub const ASMUT_TRAIT: [&str; 3] = ["core", "convert", "AsMut"]; pub const ASREF_TRAIT: [&str; 3] = ["core", "convert", "AsRef"]; @@ -72,7 +83,7 @@ pub const KW_MODULE: [&str; 3] = ["rustc_span", "symbol", "kw"]; #[cfg(feature = "internal-lints")] pub const LATE_CONTEXT: [&str; 2] = ["rustc_lint", "LateContext"]; pub const LINKED_LIST: [&str; 4] = ["alloc", "collections", "linked_list", "LinkedList"]; -#[cfg(feature = "internal-lints")] +#[cfg(any(feature = "internal-lints", feature = "metadata-collector-lint"))] pub const LINT: [&str; 2] = ["rustc_lint_defs", "Lint"]; pub const MEM_DISCRIMINANT: [&str; 3] = ["core", "mem", "discriminant"]; pub const MEM_FORGET: [&str; 3] = ["core", "mem", "forget"]; diff --git a/clippy_utils/src/source.rs b/clippy_utils/src/source.rs index 53180d1f9f5..4d49b43bde9 100644 --- a/clippy_utils/src/source.rs +++ b/clippy_utils/src/source.rs @@ -280,17 +280,17 @@ pub fn snippet_with_context( default: &'a str, applicability: &mut Applicability, ) -> (Cow<'a, str>, bool) { - let outer_span = hygiene::walk_chain(span, outer); - let (span, is_macro_call) = if outer_span.ctxt() == outer { - (outer_span, span.ctxt() != outer) - } else { - // The span is from a macro argument, and the outer context is the macro using the argument - if *applicability != Applicability::Unspecified { - *applicability = Applicability::MaybeIncorrect; - } - // TODO: get the argument span. - (span, false) - }; + let (span, is_macro_call) = walk_span_to_context(span, outer).map_or_else( + || { + // The span is from a macro argument, and the outer context is the macro using the argument + if *applicability != Applicability::Unspecified { + *applicability = Applicability::MaybeIncorrect; + } + // TODO: get the argument span. + (span, false) + }, + |outer_span| (outer_span, span.ctxt() != outer), + ); ( snippet_with_applicability(cx, span, default, applicability), @@ -298,6 +298,37 @@ pub fn snippet_with_context( ) } +/// Walks the span up to the target context, thereby returning the macro call site if the span is +/// inside a macro expansion, or the original span if it is not. Note this will return `None` in the +/// case of the span being in a macro expansion, but the target context is from expanding a macro +/// argument. +/// +/// Given the following +/// +/// ```rust,ignore +/// macro_rules! m { ($e:expr) => { f($e) }; } +/// g(m!(0)) +/// ``` +/// +/// If called with a span of the call to `f` and a context of the call to `g` this will return a +/// span containing `m!(0)`. However, if called with a span of the literal `0` this will give a span +/// containing `0` as the context is the same as the outer context. +/// +/// This will traverse through multiple macro calls. Given the following: +/// +/// ```rust,ignore +/// macro_rules! m { ($e:expr) => { n!($e, 0) }; } +/// macro_rules! n { ($e:expr, $f:expr) => { f($e, $f) }; } +/// g(m!(0)) +/// ``` +/// +/// If called with a span of the call to `f` and a context of the call to `g` this will return a +/// span containing `m!(0)`. +pub fn walk_span_to_context(span: Span, outer: SyntaxContext) -> Option { + let outer_span = hygiene::walk_chain(span, outer); + (outer_span.ctxt() == outer).then(|| outer_span) +} + /// Removes block comments from the given `Vec` of lines. /// /// # Examples diff --git a/clippy_utils/src/visitors.rs b/clippy_utils/src/visitors.rs index 5a8c629e333..d431bdf34ee 100644 --- a/clippy_utils/src/visitors.rs +++ b/clippy_utils/src/visitors.rs @@ -1,7 +1,7 @@ use crate::path_to_local_id; use rustc_hir as hir; -use rustc_hir::intravisit::{self, walk_expr, NestedVisitorMap, Visitor}; -use rustc_hir::{Arm, Body, Expr, HirId, Stmt}; +use rustc_hir::intravisit::{self, walk_expr, ErasedMap, NestedVisitorMap, Visitor}; +use rustc_hir::{Arm, Block, Body, Destination, Expr, ExprKind, HirId, Stmt}; use rustc_lint::LateContext; use rustc_middle::hir::map::Map; @@ -188,3 +188,54 @@ impl<'v> Visitor<'v> for LocalUsedVisitor<'v> { NestedVisitorMap::OnlyBodies(self.hir) } } + +pub trait Visitable<'tcx> { + fn visit>(self, v: &mut V); +} +impl Visitable<'tcx> for &'tcx Expr<'tcx> { + fn visit>(self, v: &mut V) { + v.visit_expr(self) + } +} +impl Visitable<'tcx> for &'tcx Block<'tcx> { + fn visit>(self, v: &mut V) { + v.visit_block(self) + } +} +impl<'tcx> Visitable<'tcx> for &'tcx Stmt<'tcx> { + fn visit>(self, v: &mut V) { + v.visit_stmt(self) + } +} +impl<'tcx> Visitable<'tcx> for &'tcx Body<'tcx> { + fn visit>(self, v: &mut V) { + v.visit_body(self) + } +} +impl<'tcx> Visitable<'tcx> for &'tcx Arm<'tcx> { + fn visit>(self, v: &mut V) { + v.visit_arm(self) + } +} + +pub fn visit_break_exprs<'tcx>( + node: impl Visitable<'tcx>, + f: impl FnMut(&'tcx Expr<'tcx>, Destination, Option<&'tcx Expr<'tcx>>), +) { + struct V(F); + impl<'tcx, F: FnMut(&'tcx Expr<'tcx>, Destination, Option<&'tcx Expr<'tcx>>)> Visitor<'tcx> for V { + type Map = ErasedMap<'tcx>; + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } + + fn visit_expr(&mut self, e: &'tcx Expr<'_>) { + if let ExprKind::Break(dest, sub_expr) = e.kind { + self.0(e, dest, sub_expr) + } + walk_expr(self, e); + } + } + + node.visit(&mut V(f)); +} diff --git a/doc/adding_lints.md b/doc/adding_lints.md index d6cc6d0c2c7..5a06afedbf4 100644 --- a/doc/adding_lints.md +++ b/doc/adding_lints.md @@ -454,7 +454,7 @@ in `clippy_lints/src/utils/conf.rs`: ```rust define_Conf! { /// Lint: LIST, OF, LINTS, . The minimum rust version that the project supports - (msrv, "msrv": Option, None), + (msrv: Option = None), ... } ``` @@ -562,7 +562,7 @@ in the following steps: like this: ```rust /// Lint: LINT_NAME. - (configuration_ident, "configuration_value": Type, DefaultValue), + (configuration_ident: Type = DefaultValue), ``` The configuration value and identifier should usually be the same. The doc comment will be automatically added to the lint documentation. diff --git a/rust-toolchain b/rust-toolchain index cd398451783..593162f09a7 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1,3 +1,3 @@ [toolchain] -channel = "nightly-2021-04-22" +channel = "nightly-2021-05-06" components = ["llvm-tools-preview", "rustc-dev", "rust-src"] diff --git a/src/driver.rs b/src/driver.rs index 750a23e8c98..0c82f37d6a2 100644 --- a/src/driver.rs +++ b/src/driver.rs @@ -106,7 +106,7 @@ impl rustc_driver::Callbacks for ClippyCallbacks { (previous)(sess, lint_store); } - let conf = clippy_lints::read_conf(&[], sess); + let conf = clippy_lints::read_conf(sess); clippy_lints::register_plugins(lint_store, sess, &conf); clippy_lints::register_pre_expansion_lints(lint_store); clippy_lints::register_renamed(lint_store); diff --git a/tests/dogfood.rs b/tests/dogfood.rs index d92530f073f..6524fd4706c 100644 --- a/tests/dogfood.rs +++ b/tests/dogfood.rs @@ -1,3 +1,8 @@ +//! This test is a part of quality control and makes clippy eat what it produces. Awesome lints and +//! long error messages +//! +//! See [Eating your own dog food](https://en.wikipedia.org/wiki/Eating_your_own_dog_food) for context + // Dogfood cannot run on Windows #![cfg(not(windows))] #![feature(once_cell)] @@ -17,12 +22,14 @@ fn dogfood_clippy() { return; } let root_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let enable_metadata_collection = std::env::var("ENABLE_METADATA_COLLECTION").unwrap_or_else(|_| "0".to_string()); let mut command = Command::new(&*CLIPPY_PATH); command .current_dir(root_dir) .env("CLIPPY_DOGFOOD", "1") .env("CARGO_INCREMENTAL", "0") + .env("ENABLE_METADATA_COLLECTION", &enable_metadata_collection) .arg("clippy") .arg("--all-targets") .arg("--all-features") diff --git a/tests/ui-toml/bad_toml_type/conf_bad_type.stderr b/tests/ui-toml/bad_toml_type/conf_bad_type.stderr index efd02bcbb6e..c7bc261de6c 100644 --- a/tests/ui-toml/bad_toml_type/conf_bad_type.stderr +++ b/tests/ui-toml/bad_toml_type/conf_bad_type.stderr @@ -1,4 +1,4 @@ -error: error reading Clippy's configuration file `$DIR/clippy.toml`: invalid type: integer `42`, expected a sequence +error: error reading Clippy's configuration file `$DIR/clippy.toml`: invalid type: integer `42`, expected a sequence for key `blacklisted-names` error: aborting due to previous error diff --git a/tests/ui-toml/conf_deprecated_key/conf_deprecated_key.stderr b/tests/ui-toml/conf_deprecated_key/conf_deprecated_key.stderr index 34267c0daf7..90021a034a3 100644 --- a/tests/ui-toml/conf_deprecated_key/conf_deprecated_key.stderr +++ b/tests/ui-toml/conf_deprecated_key/conf_deprecated_key.stderr @@ -1,4 +1,4 @@ -error: error reading Clippy's configuration file `$DIR/clippy.toml`: found deprecated field `cyclomatic-complexity-threshold`. Please use `cognitive-complexity-threshold` instead. +error: error reading Clippy's configuration file `$DIR/clippy.toml`: deprecated field `cyclomatic-complexity-threshold`. Please use `cognitive-complexity-threshold` instead error: aborting due to previous error diff --git a/tests/ui/builtin-type-shadow.rs b/tests/ui/builtin_type_shadow.rs similarity index 100% rename from tests/ui/builtin-type-shadow.rs rename to tests/ui/builtin_type_shadow.rs diff --git a/tests/ui/builtin-type-shadow.stderr b/tests/ui/builtin_type_shadow.stderr similarity index 89% rename from tests/ui/builtin-type-shadow.stderr rename to tests/ui/builtin_type_shadow.stderr index f42b246afd2..47a8a1e623e 100644 --- a/tests/ui/builtin-type-shadow.stderr +++ b/tests/ui/builtin_type_shadow.stderr @@ -1,5 +1,5 @@ error: this generic shadows the built-in type `u32` - --> $DIR/builtin-type-shadow.rs:4:8 + --> $DIR/builtin_type_shadow.rs:4:8 | LL | fn foo(a: u32) -> u32 { | ^^^ @@ -7,7 +7,7 @@ LL | fn foo(a: u32) -> u32 { = note: `-D clippy::builtin-type-shadow` implied by `-D warnings` error[E0308]: mismatched types - --> $DIR/builtin-type-shadow.rs:5:5 + --> $DIR/builtin_type_shadow.rs:5:5 | LL | fn foo(a: u32) -> u32 { | --- --- expected `u32` because of return type diff --git a/tests/ui/comparison_chain.rs b/tests/ui/comparison_chain.rs index 3b03f8c7dfe..c12c6a31027 100644 --- a/tests/ui/comparison_chain.rs +++ b/tests/ui/comparison_chain.rs @@ -203,4 +203,32 @@ mod issue_5212 { } } +enum Sign { + Negative, + Positive, + Zero, +} + +impl Sign { + const fn sign_i8(n: i8) -> Self { + if n == 0 { + Sign::Zero + } else if n > 0 { + Sign::Positive + } else { + Sign::Negative + } + } +} + +const fn sign_i8(n: i8) -> Sign { + if n == 0 { + Sign::Zero + } else if n > 0 { + Sign::Positive + } else { + Sign::Negative + } +} + fn main() {} diff --git a/tests/ui/copy_iterator.rs b/tests/ui/copy_iterator.rs index e3d5928be23..ae67ebded43 100644 --- a/tests/ui/copy_iterator.rs +++ b/tests/ui/copy_iterator.rs @@ -16,8 +16,6 @@ impl Iterator for Countdown { fn main() { let my_iterator = Countdown(5); - let a: Vec<_> = my_iterator.take(1).collect(); - assert_eq!(a.len(), 1); - let b: Vec<_> = my_iterator.collect(); - assert_eq!(b.len(), 5); + assert_eq!(my_iterator.take(1).count(), 1); + assert_eq!(my_iterator.count(), 5); } diff --git a/tests/ui/crashes/ice-7169.rs b/tests/ui/crashes/ice-7169.rs new file mode 100644 index 00000000000..82095febc19 --- /dev/null +++ b/tests/ui/crashes/ice-7169.rs @@ -0,0 +1,9 @@ +#[derive(Default)] +struct A { + a: Vec>, + b: T, +} + +fn main() { + if let Ok(_) = Ok::<_, ()>(A::::default()) {} +} diff --git a/tests/ui/crashes/ice-7169.stderr b/tests/ui/crashes/ice-7169.stderr new file mode 100644 index 00000000000..5a9cd32380a --- /dev/null +++ b/tests/ui/crashes/ice-7169.stderr @@ -0,0 +1,10 @@ +error: redundant pattern matching, consider using `is_ok()` + --> $DIR/ice-7169.rs:8:12 + | +LL | if let Ok(_) = Ok::<_, ()>(A::::default()) {} + | -------^^^^^-------------------------------------- help: try this: `if Ok::<_, ()>(A::::default()).is_ok()` + | + = note: `-D clippy::redundant-pattern-matching` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/tests/ui/eval_order_dependence.rs b/tests/ui/eval_order_dependence.rs index d806bc6d401..d742856bc41 100644 --- a/tests/ui/eval_order_dependence.rs +++ b/tests/ui/eval_order_dependence.rs @@ -1,3 +1,5 @@ +// edition:2018 + #[warn(clippy::eval_order_dependence)] #[allow( unused_assignments, @@ -107,3 +109,7 @@ fn main() { }, ); } + +async fn issue_6925() { + let _ = vec![async { true }.await, async { false }.await]; +} diff --git a/tests/ui/eval_order_dependence.stderr b/tests/ui/eval_order_dependence.stderr index 8f4fa2228f7..35eb85e95a3 100644 --- a/tests/ui/eval_order_dependence.stderr +++ b/tests/ui/eval_order_dependence.stderr @@ -1,48 +1,48 @@ -error: unsequenced read of a variable - --> $DIR/eval_order_dependence.rs:15:9 +error: unsequenced read of `x` + --> $DIR/eval_order_dependence.rs:17:9 | LL | } + x; | ^ | = note: `-D clippy::eval-order-dependence` implied by `-D warnings` note: whether read occurs before this write depends on evaluation order - --> $DIR/eval_order_dependence.rs:13:9 + --> $DIR/eval_order_dependence.rs:15:9 | LL | x = 1; | ^^^^^ -error: unsequenced read of a variable - --> $DIR/eval_order_dependence.rs:18:5 +error: unsequenced read of `x` + --> $DIR/eval_order_dependence.rs:20:5 | LL | x += { | ^ | note: whether read occurs before this write depends on evaluation order - --> $DIR/eval_order_dependence.rs:19:9 + --> $DIR/eval_order_dependence.rs:21:9 | LL | x = 20; | ^^^^^^ -error: unsequenced read of a variable - --> $DIR/eval_order_dependence.rs:31:12 +error: unsequenced read of `x` + --> $DIR/eval_order_dependence.rs:33:12 | LL | a: x, | ^ | note: whether read occurs before this write depends on evaluation order - --> $DIR/eval_order_dependence.rs:33:13 + --> $DIR/eval_order_dependence.rs:35:13 | LL | x = 6; | ^^^^^ -error: unsequenced read of a variable - --> $DIR/eval_order_dependence.rs:40:9 +error: unsequenced read of `x` + --> $DIR/eval_order_dependence.rs:42:9 | LL | x += { | ^ | note: whether read occurs before this write depends on evaluation order - --> $DIR/eval_order_dependence.rs:41:13 + --> $DIR/eval_order_dependence.rs:43:13 | LL | x = 20; | ^^^^^^ diff --git a/tests/ui/implicit_return.fixed b/tests/ui/implicit_return.fixed index 59f7ad9c106..7698b88a88c 100644 --- a/tests/ui/implicit_return.fixed +++ b/tests/ui/implicit_return.fixed @@ -1,7 +1,8 @@ +// edition:2018 // run-rustfix #![warn(clippy::implicit_return)] -#![allow(clippy::needless_return, unused)] +#![allow(clippy::needless_return, clippy::needless_bool, unused, clippy::never_loop)] fn test_end_of_fn() -> bool { if true { @@ -12,7 +13,6 @@ fn test_end_of_fn() -> bool { return true } -#[allow(clippy::needless_bool)] fn test_if_block() -> bool { if true { return true } else { return false } } @@ -25,7 +25,6 @@ fn test_match(x: bool) -> bool { } } -#[allow(clippy::needless_return)] fn test_match_with_unreachable(x: bool) -> bool { match x { true => return false, @@ -33,14 +32,12 @@ fn test_match_with_unreachable(x: bool) -> bool { } } -#[allow(clippy::never_loop)] fn test_loop() -> bool { loop { return true; } } -#[allow(clippy::never_loop)] fn test_loop_with_block() -> bool { loop { { @@ -49,7 +46,6 @@ fn test_loop_with_block() -> bool { } } -#[allow(clippy::never_loop)] fn test_loop_with_nests() -> bool { loop { if true { @@ -83,15 +79,53 @@ fn test_return_macro() -> String { return format!("test {}", "test") } -fn main() { - let _ = test_end_of_fn(); - let _ = test_if_block(); - let _ = test_match(true); - let _ = test_match_with_unreachable(true); - let _ = test_loop(); - let _ = test_loop_with_block(); - let _ = test_loop_with_nests(); - let _ = test_loop_with_if_let(); - test_closure(); - let _ = test_return_macro(); +fn macro_branch_test() -> bool { + macro_rules! m { + ($t:expr, $f:expr) => { + if true { $t } else { $f } + }; + } + return m!(true, false) } + +fn loop_test() -> bool { + 'outer: loop { + if true { + return true; + } + + let _ = loop { + if false { + return false; + } + if true { + break true; + } + }; + } +} + +fn loop_macro_test() -> bool { + macro_rules! m { + ($e:expr) => { + break $e + }; + } + return loop { + m!(true); + } +} + +fn divergent_test() -> bool { + fn diverge() -> ! { + panic!() + } + diverge() +} + +// issue #6940 +async fn foo() -> bool { + return true +} + +fn main() {} diff --git a/tests/ui/implicit_return.rs b/tests/ui/implicit_return.rs index 2c1bc046515..45bbc2ec670 100644 --- a/tests/ui/implicit_return.rs +++ b/tests/ui/implicit_return.rs @@ -1,7 +1,8 @@ +// edition:2018 // run-rustfix #![warn(clippy::implicit_return)] -#![allow(clippy::needless_return, unused)] +#![allow(clippy::needless_return, clippy::needless_bool, unused, clippy::never_loop)] fn test_end_of_fn() -> bool { if true { @@ -12,7 +13,6 @@ fn test_end_of_fn() -> bool { true } -#[allow(clippy::needless_bool)] fn test_if_block() -> bool { if true { true } else { false } } @@ -25,7 +25,6 @@ fn test_match(x: bool) -> bool { } } -#[allow(clippy::needless_return)] fn test_match_with_unreachable(x: bool) -> bool { match x { true => return false, @@ -33,14 +32,12 @@ fn test_match_with_unreachable(x: bool) -> bool { } } -#[allow(clippy::never_loop)] fn test_loop() -> bool { loop { break true; } } -#[allow(clippy::never_loop)] fn test_loop_with_block() -> bool { loop { { @@ -49,7 +46,6 @@ fn test_loop_with_block() -> bool { } } -#[allow(clippy::never_loop)] fn test_loop_with_nests() -> bool { loop { if true { @@ -83,15 +79,53 @@ fn test_return_macro() -> String { format!("test {}", "test") } -fn main() { - let _ = test_end_of_fn(); - let _ = test_if_block(); - let _ = test_match(true); - let _ = test_match_with_unreachable(true); - let _ = test_loop(); - let _ = test_loop_with_block(); - let _ = test_loop_with_nests(); - let _ = test_loop_with_if_let(); - test_closure(); - let _ = test_return_macro(); +fn macro_branch_test() -> bool { + macro_rules! m { + ($t:expr, $f:expr) => { + if true { $t } else { $f } + }; + } + m!(true, false) } + +fn loop_test() -> bool { + 'outer: loop { + if true { + break true; + } + + let _ = loop { + if false { + break 'outer false; + } + if true { + break true; + } + }; + } +} + +fn loop_macro_test() -> bool { + macro_rules! m { + ($e:expr) => { + break $e + }; + } + loop { + m!(true); + } +} + +fn divergent_test() -> bool { + fn diverge() -> ! { + panic!() + } + diverge() +} + +// issue #6940 +async fn foo() -> bool { + true +} + +fn main() {} diff --git a/tests/ui/implicit_return.stderr b/tests/ui/implicit_return.stderr index 3608319e5bd..16fe9ed444f 100644 --- a/tests/ui/implicit_return.stderr +++ b/tests/ui/implicit_return.stderr @@ -1,5 +1,5 @@ error: missing `return` statement - --> $DIR/implicit_return.rs:12:5 + --> $DIR/implicit_return.rs:13:5 | LL | true | ^^^^ help: add `return` as shown: `return true` @@ -31,40 +31,79 @@ LL | false => { true }, | ^^^^ help: add `return` as shown: `return true` error: missing `return` statement - --> $DIR/implicit_return.rs:39:9 + --> $DIR/implicit_return.rs:37:9 | LL | break true; | ^^^^^^^^^^ help: change `break` to `return` as shown: `return true` error: missing `return` statement - --> $DIR/implicit_return.rs:47:13 + --> $DIR/implicit_return.rs:44:13 | LL | break true; | ^^^^^^^^^^ help: change `break` to `return` as shown: `return true` error: missing `return` statement - --> $DIR/implicit_return.rs:56:13 + --> $DIR/implicit_return.rs:52:13 | LL | break true; | ^^^^^^^^^^ help: change `break` to `return` as shown: `return true` error: missing `return` statement - --> $DIR/implicit_return.rs:74:18 + --> $DIR/implicit_return.rs:70:18 | LL | let _ = || { true }; | ^^^^ help: add `return` as shown: `return true` error: missing `return` statement - --> $DIR/implicit_return.rs:75:16 + --> $DIR/implicit_return.rs:71:16 | LL | let _ = || true; | ^^^^ help: add `return` as shown: `return true` error: missing `return` statement - --> $DIR/implicit_return.rs:83:5 + --> $DIR/implicit_return.rs:79:5 | LL | format!("test {}", "test") | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: add `return` as shown: `return format!("test {}", "test")` -error: aborting due to 11 previous errors +error: missing `return` statement + --> $DIR/implicit_return.rs:88:5 + | +LL | m!(true, false) + | ^^^^^^^^^^^^^^^ help: add `return` as shown: `return m!(true, false)` + +error: missing `return` statement + --> $DIR/implicit_return.rs:94:13 + | +LL | break true; + | ^^^^^^^^^^ help: change `break` to `return` as shown: `return true` + +error: missing `return` statement + --> $DIR/implicit_return.rs:99:17 + | +LL | break 'outer false; + | ^^^^^^^^^^^^^^^^^^ help: change `break` to `return` as shown: `return false` + +error: missing `return` statement + --> $DIR/implicit_return.rs:114:5 + | +LL | / loop { +LL | | m!(true); +LL | | } + | |_____^ + | +help: add `return` as shown + | +LL | return loop { +LL | m!(true); +LL | } + | + +error: missing `return` statement + --> $DIR/implicit_return.rs:128:5 + | +LL | true + | ^^^^ help: add `return` as shown: `return true` + +error: aborting due to 16 previous errors diff --git a/tests/ui/infinite_loop.rs b/tests/ui/infinite_loop.rs index 72591f12baf..3d8fb8507e5 100644 --- a/tests/ui/infinite_loop.rs +++ b/tests/ui/infinite_loop.rs @@ -192,11 +192,23 @@ fn while_loop_with_break_and_return() { } } +fn immutable_condition_false_positive(mut n: u64) -> u32 { + let mut count = 0; + while { + n >>= 1; + n != 0 + } { + count += 1; + } + count +} + fn main() { immutable_condition(); unused_var(); used_immutable(); internally_mutable(); + immutable_condition_false_positive(5); let mut c = Counter { count: 0 }; c.inc_n(5); diff --git a/tests/ui/needless_collect_indirect.rs b/tests/ui/needless_collect_indirect.rs index 0918a6868ab..2458bf1e490 100644 --- a/tests/ui/needless_collect_indirect.rs +++ b/tests/ui/needless_collect_indirect.rs @@ -1,4 +1,4 @@ -use std::collections::{HashMap, VecDeque}; +use std::collections::{BinaryHeap, HashMap, LinkedList, VecDeque}; fn main() { let sample = [1; 5]; @@ -43,3 +43,35 @@ fn main() { .collect::>(); } } + +mod issue7110 { + // #7110 - lint for type annotation cases + use super::*; + + fn lint_vec(string: &str) -> usize { + let buffer: Vec<&str> = string.split('/').collect(); + buffer.len() + } + fn lint_vec_deque() -> usize { + let sample = [1; 5]; + let indirect_len: VecDeque<_> = sample.iter().collect(); + indirect_len.len() + } + fn lint_linked_list() -> usize { + let sample = [1; 5]; + let indirect_len: LinkedList<_> = sample.iter().collect(); + indirect_len.len() + } + fn lint_binary_heap() -> usize { + let sample = [1; 5]; + let indirect_len: BinaryHeap<_> = sample.iter().collect(); + indirect_len.len() + } + fn dont_lint(string: &str) -> usize { + let buffer: Vec<&str> = string.split('/').collect(); + for buff in &buffer { + println!("{}", buff); + } + buffer.len() + } +} diff --git a/tests/ui/needless_collect_indirect.stderr b/tests/ui/needless_collect_indirect.stderr index c773b841f3b..f094e182a48 100644 --- a/tests/ui/needless_collect_indirect.stderr +++ b/tests/ui/needless_collect_indirect.stderr @@ -69,5 +69,61 @@ LL | LL | sample.into_iter().any(|x| x == a); | -error: aborting due to 5 previous errors +error: avoid using `collect()` when not needed + --> $DIR/needless_collect_indirect.rs:52:51 + | +LL | let buffer: Vec<&str> = string.split('/').collect(); + | ^^^^^^^ +LL | buffer.len() + | ------------ the iterator could be used here instead + | +help: take the original Iterator's count instead of collecting it and finding the length + | +LL | +LL | string.split('/').count() + | + +error: avoid using `collect()` when not needed + --> $DIR/needless_collect_indirect.rs:57:55 + | +LL | let indirect_len: VecDeque<_> = sample.iter().collect(); + | ^^^^^^^ +LL | indirect_len.len() + | ------------------ the iterator could be used here instead + | +help: take the original Iterator's count instead of collecting it and finding the length + | +LL | +LL | sample.iter().count() + | + +error: avoid using `collect()` when not needed + --> $DIR/needless_collect_indirect.rs:62:57 + | +LL | let indirect_len: LinkedList<_> = sample.iter().collect(); + | ^^^^^^^ +LL | indirect_len.len() + | ------------------ the iterator could be used here instead + | +help: take the original Iterator's count instead of collecting it and finding the length + | +LL | +LL | sample.iter().count() + | + +error: avoid using `collect()` when not needed + --> $DIR/needless_collect_indirect.rs:67:57 + | +LL | let indirect_len: BinaryHeap<_> = sample.iter().collect(); + | ^^^^^^^ +LL | indirect_len.len() + | ------------------ the iterator could be used here instead + | +help: take the original Iterator's count instead of collecting it and finding the length + | +LL | +LL | sample.iter().count() + | + +error: aborting due to 9 previous errors diff --git a/tests/ui/single_char_pattern.fixed b/tests/ui/single_char_pattern.fixed index d8b5f19e144..fcbe9af9f56 100644 --- a/tests/ui/single_char_pattern.fixed +++ b/tests/ui/single_char_pattern.fixed @@ -33,6 +33,8 @@ fn main() { x.rmatch_indices('x'); x.trim_start_matches('x'); x.trim_end_matches('x'); + x.strip_prefix('x'); + x.strip_suffix('x'); // Make sure we escape characters correctly. x.split('\n'); x.split('\''); diff --git a/tests/ui/single_char_pattern.rs b/tests/ui/single_char_pattern.rs index a7bc73e3756..b8bc20f4070 100644 --- a/tests/ui/single_char_pattern.rs +++ b/tests/ui/single_char_pattern.rs @@ -33,6 +33,8 @@ fn main() { x.rmatch_indices("x"); x.trim_start_matches("x"); x.trim_end_matches("x"); + x.strip_prefix("x"); + x.strip_suffix("x"); // Make sure we escape characters correctly. x.split("\n"); x.split("'"); diff --git a/tests/ui/single_char_pattern.stderr b/tests/ui/single_char_pattern.stderr index ee4e7e50efd..6d94d8a34e3 100644 --- a/tests/ui/single_char_pattern.stderr +++ b/tests/ui/single_char_pattern.stderr @@ -121,64 +121,76 @@ LL | x.trim_end_matches("x"); | ^^^ help: try using a `char` instead: `'x'` error: single-character string constant used as pattern - --> $DIR/single_char_pattern.rs:37:13 + --> $DIR/single_char_pattern.rs:36:20 + | +LL | x.strip_prefix("x"); + | ^^^ help: try using a `char` instead: `'x'` + +error: single-character string constant used as pattern + --> $DIR/single_char_pattern.rs:37:20 + | +LL | x.strip_suffix("x"); + | ^^^ help: try using a `char` instead: `'x'` + +error: single-character string constant used as pattern + --> $DIR/single_char_pattern.rs:39:13 | LL | x.split("/n"); | ^^^^ help: try using a `char` instead: `'/n'` error: single-character string constant used as pattern - --> $DIR/single_char_pattern.rs:38:13 + --> $DIR/single_char_pattern.rs:40:13 | LL | x.split("'"); | ^^^ help: try using a `char` instead: `'/''` error: single-character string constant used as pattern - --> $DIR/single_char_pattern.rs:39:13 + --> $DIR/single_char_pattern.rs:41:13 | LL | x.split("/'"); | ^^^^ help: try using a `char` instead: `'/''` error: single-character string constant used as pattern - --> $DIR/single_char_pattern.rs:44:31 + --> $DIR/single_char_pattern.rs:46:31 | LL | x.replace(";", ",").split(","); // issue #2978 | ^^^ help: try using a `char` instead: `','` error: single-character string constant used as pattern - --> $DIR/single_char_pattern.rs:45:19 + --> $DIR/single_char_pattern.rs:47:19 | LL | x.starts_with("/x03"); // issue #2996 | ^^^^^^ help: try using a `char` instead: `'/x03'` error: single-character string constant used as pattern - --> $DIR/single_char_pattern.rs:52:13 + --> $DIR/single_char_pattern.rs:54:13 | LL | x.split(r"a"); | ^^^^ help: try using a `char` instead: `'a'` error: single-character string constant used as pattern - --> $DIR/single_char_pattern.rs:53:13 + --> $DIR/single_char_pattern.rs:55:13 | LL | x.split(r#"a"#); | ^^^^^^ help: try using a `char` instead: `'a'` error: single-character string constant used as pattern - --> $DIR/single_char_pattern.rs:54:13 + --> $DIR/single_char_pattern.rs:56:13 | LL | x.split(r###"a"###); | ^^^^^^^^^^ help: try using a `char` instead: `'a'` error: single-character string constant used as pattern - --> $DIR/single_char_pattern.rs:55:13 + --> $DIR/single_char_pattern.rs:57:13 | LL | x.split(r###"'"###); | ^^^^^^^^^^ help: try using a `char` instead: `'/''` error: single-character string constant used as pattern - --> $DIR/single_char_pattern.rs:56:13 + --> $DIR/single_char_pattern.rs:58:13 | LL | x.split(r###"#"###); | ^^^^^^^^^^ help: try using a `char` instead: `'#'` -error: aborting due to 30 previous errors +error: aborting due to 32 previous errors diff --git a/tests/ui/unnecessary_filter_map.rs b/tests/ui/unnecessary_filter_map.rs index af858e4abcf..c58181f518d 100644 --- a/tests/ui/unnecessary_filter_map.rs +++ b/tests/ui/unnecessary_filter_map.rs @@ -15,3 +15,7 @@ fn main() { let _ = (0..4).filter_map(i32::checked_abs); } + +fn filter_map_none_changes_item_type() -> impl Iterator { + "".chars().filter_map(|_| None) +} diff --git a/tests/ui/unused_unit.fixed b/tests/ui/unused_unit.fixed index a192ebde3eb..7bb43cf7ae8 100644 --- a/tests/ui/unused_unit.fixed +++ b/tests/ui/unused_unit.fixed @@ -80,3 +80,10 @@ fn test2(){} #[rustfmt::skip] fn test3(){} + +fn macro_expr() { + macro_rules! e { + () => (()); + } + e!() +} diff --git a/tests/ui/unused_unit.rs b/tests/ui/unused_unit.rs index 96041a7dd85..21073fb802a 100644 --- a/tests/ui/unused_unit.rs +++ b/tests/ui/unused_unit.rs @@ -80,3 +80,10 @@ fn test2() ->(){} #[rustfmt::skip] fn test3()-> (){} + +fn macro_expr() { + macro_rules! e { + () => (()); + } + e!() +} diff --git a/util/lintlib.py b/util/lintlib.py index d0d9beb9b2d..5707cf0ce0f 100644 --- a/util/lintlib.py +++ b/util/lintlib.py @@ -14,7 +14,7 @@ lintname_re = re.compile(r'''pub\s+([A-Z_][A-Z_0-9]*)''') group_re = re.compile(r'''\s*([a-z_][a-z_0-9]+)''') conf_re = re.compile(r'''define_Conf! {\n([^}]*)\n}''', re.MULTILINE) confvar_re = re.compile( - r'''/// Lint: ([\w,\s]+)\. (.*)\n\s*\([^,]+,\s+"([^"]+)":\s+([^,]+),\s+([^\.\)]+).*\),''', re.MULTILINE) + r'''/// Lint: ([\w,\s]+)\. (.*)\n\s*\(([^:]+):\s*([^\s=]+)\s*=\s*([^\.\)]+).*\),''', re.MULTILINE) comment_re = re.compile(r'''\s*/// ?(.*)''') lint_levels = { From 037ddf282bbf011babd4b7f9a851f6028fc6fd70 Mon Sep 17 00:00:00 2001 From: flip1995 Date: Thu, 6 May 2021 15:22:17 +0200 Subject: [PATCH 04/58] Update CHANGELOG.md --- CHANGELOG.md | 196 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 190 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 204d56e2a98..fce3fccd075 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,11 +6,195 @@ document. ## Unreleased / In Rust Nightly -[6ed6f1e...master](https://github.com/rust-lang/rust-clippy/compare/6ed6f1e...master) +[7c7683c...master](https://github.com/rust-lang/rust-clippy/compare/7c7683c...master) ## Rust 1.52 -Current beta, release 2021-05-06 +Current beta, release 2021-06-17 + +[6ed6f1e...7c7683c](https://github.com/rust-lang/rust-clippy/compare/6ed6f1e...7c7683c) + +### New Lints + +* [`option_filter_map`] + [#6342](https://github.com/rust-lang/rust-clippy/pull/6342) +* [`branches_sharing_code`] + [#6463](https://github.com/rust-lang/rust-clippy/pull/6463) +* [`needless_for_each`] + [#6706](https://github.com/rust-lang/rust-clippy/pull/6706) +* [`if_then_some_else_none`] + [#6859](https://github.com/rust-lang/rust-clippy/pull/6859) +* [`non_octal_unix_permissions`] + [#7001](https://github.com/rust-lang/rust-clippy/pull/7001) +* [`unnecessary_self_imports`] + [#7072](https://github.com/rust-lang/rust-clippy/pull/7072) +* [`bool_assert_comparison`] + [#7083](https://github.com/rust-lang/rust-clippy/pull/7083) +* [`cloned_instead_of_copied`] + [#7098](https://github.com/rust-lang/rust-clippy/pull/7098) +* [`flat_map_option`] + [#7101](https://github.com/rust-lang/rust-clippy/pull/7101) + +### Moves and Deprecations + +* Deprecate [`filter_map`] lint + [#7059](https://github.com/rust-lang/rust-clippy/pull/7059) +* Move [`transmute_ptr_to_ptr`] to `pedantic` + [#7102](https://github.com/rust-lang/rust-clippy/pull/7102) + +### Enhancements + +* [`mem_replace_with_default`]: Also lint on common std constructors + [#6820](https://github.com/rust-lang/rust-clippy/pull/6820) +* [`wrong_self_convention`]: Also lint on `to_*_mut` methods + [#6828](https://github.com/rust-lang/rust-clippy/pull/6828) +* [`wildcard_enum_match_arm`], [`match_wildcard_for_single_variants`]: + [#6863](https://github.com/rust-lang/rust-clippy/pull/6863) + * Attempt to find a common path prefix in suggestion + * Don't lint on `Option` and `Result` + * Consider `Self` prefix +* [`explicit_deref_methods`]: Also lint on chained `deref` calls + [#6865](https://github.com/rust-lang/rust-clippy/pull/6865) +* [`or_fun_call`]: Also lint on `unsafe` blocks + [#6928](https://github.com/rust-lang/rust-clippy/pull/6928) +* [`vec_box`], [`linkedlist`], [`option_option`]: Also lint in `const` and + `static` items [#6938](https://github.com/rust-lang/rust-clippy/pull/6938) +* [`search_is_some`]: Also check for `is_none` + [#6942](https://github.com/rust-lang/rust-clippy/pull/6942) +* [`string_lit_as_bytes`]: Also lint on `into_bytes` + [#6959](https://github.com/rust-lang/rust-clippy/pull/6959) +* [`len_without_is_empty`]: Also lint if function signatures of `len` and + `is_empty` don't match + [#6980](https://github.com/rust-lang/rust-clippy/pull/6980) +* [`redundant_pattern_matching`]: Also lint if the pattern is a `&` pattern + [#6991](https://github.com/rust-lang/rust-clippy/pull/6991) +* [`clone_on_copy`]: Also lint on chained method calls taking `self` by value + [#7000](https://github.com/rust-lang/rust-clippy/pull/7000) +* [`missing_panics_doc`]: Also lint on `assert_eq!` and `assert_ne!` + [#7029](https://github.com/rust-lang/rust-clippy/pull/7029) +* [`needless_return`]: Also lint in `async` functions + [#7067](https://github.com/rust-lang/rust-clippy/pull/7067) +* [`unused_io_amount`]: Also lint on expressions like `_.read().ok()?` + [#7100](https://github.com/rust-lang/rust-clippy/pull/7100) +* [`iter_cloned_collect`]: Also lint on large arrays, since const-generics are + now stable [#7138](https://github.com/rust-lang/rust-clippy/pull/7138) + +### False Positive Fixes + +* [`upper_case_acronyms`]: No longer lints on public items + [#6805](https://github.com/rust-lang/rust-clippy/pull/6805) +* [`suspicious_map`]: No longer lints when side effects may occur inside the + `map` call [#6831](https://github.com/rust-lang/rust-clippy/pull/6831) +* [`manual_map`], [`manual_unwrap_or`]: No longer lints in `const` functions + [#6917](https://github.com/rust-lang/rust-clippy/pull/6917) +* [`wrong_self_convention`]: Now respects `Copy` types + [#6924](https://github.com/rust-lang/rust-clippy/pull/6924) +* [`needless_question_mark`]: No longer lints if the `?` and the `Some(..)` come + from different macro contexts [#6935](https://github.com/rust-lang/rust-clippy/pull/6935) +* [`map_entry`]: Better detect if the entry API can be used + [#6937](https://github.com/rust-lang/rust-clippy/pull/6937) +* [`or_fun_call`]: No longer lints on some `len` function calls + [#6950](https://github.com/rust-lang/rust-clippy/pull/6950) +* [`new_ret_no_self`]: No longer lints when `Self` is returned with different + generic arguments [#6952](https://github.com/rust-lang/rust-clippy/pull/6952) +* [`upper_case_acronyms`]: No longer lints on public items + [#6981](https://github.com/rust-lang/rust-clippy/pull/6981) +* [`explicit_into_iter_loop`]: Only lint when `into_iter` is an implementation + of `IntoIterator` [#6982](https://github.com/rust-lang/rust-clippy/pull/6982) +* [`expl_impl_clone_on_copy`]: Take generic constraints into account before + suggesting to use `derive` instead + [#6993](https://github.com/rust-lang/rust-clippy/pull/6993) +* [`missing_panics_doc`]: No longer lints when only debug-assertions are used + [#6996](https://github.com/rust-lang/rust-clippy/pull/6996) +* [`clone_on_copy`]: Only lint when using the `Clone` trait + [#7000](https://github.com/rust-lang/rust-clippy/pull/7000) +* [`wrong_self_convention`]: No longer lints inside a trait implementation + [#7002](https://github.com/rust-lang/rust-clippy/pull/7002) +* [`redundant_clone`]: No longer lints when the cloned value is modified while + the clone is in use + [#7011](https://github.com/rust-lang/rust-clippy/pull/7011) +* [`same_item_push`]: No longer lints if the `Vec` is used in the loop body + [#7018](https://github.com/rust-lang/rust-clippy/pull/7018) +* [`cargo_common_metadata`]: Remove author requirement + [#7026](https://github.com/rust-lang/rust-clippy/pull/7026) +* [`panic_in_result_fn`]: No longer lints on `debug_assert` family + [#7060](https://github.com/rust-lang/rust-clippy/pull/7060) +* [`panic`]: No longer wrongfully lints on `debug_assert` with message + [#7063](https://github.com/rust-lang/rust-clippy/pull/7063) +* [`wrong_self_convention`]: No longer lints in trait implementations where no + `self` is involved [#7064](https://github.com/rust-lang/rust-clippy/pull/7064) +* [`missing_const_for_fn`]: No longer lints when unstable `const` function is + involved [#7076](https://github.com/rust-lang/rust-clippy/pull/7076) +* [`suspicious_else_formatting`]: Allow Allman style braces + [#7087](https://github.com/rust-lang/rust-clippy/pull/7087) +* [`inconsistent_struct_constructor`]: No longer lints in macros + [#7097](https://github.com/rust-lang/rust-clippy/pull/7097) +* [`single_component_path_imports`]: No longer lints on macro re-exports + [#7120](https://github.com/rust-lang/rust-clippy/pull/7120) + +### Suggestion Fixes/Improvements + +* [`redundant_pattern_matching`]: Add a note when applying this lint would + change the drop order + [#6568](https://github.com/rust-lang/rust-clippy/pull/6568) +* [`write_literal`], [`print_literal`]: Add auto-applicable suggestion + [#6821](https://github.com/rust-lang/rust-clippy/pull/6821) +* [`manual_map`]: Fix suggestion for complex `if let ... else` chains + [#6856](https://github.com/rust-lang/rust-clippy/pull/6856) +* [`inconsistent_struct_constructor`]: Make lint description and message clearer + [#6892](https://github.com/rust-lang/rust-clippy/pull/6892) +* [`map_entry`]: Now suggests `or_insert`, `insert_with` or `match _.entry(_)` + as appropriate [#6937](https://github.com/rust-lang/rust-clippy/pull/6937) +* [`manual_flatten`]: Suggest to insert `copied` if necessary + [#6962](https://github.com/rust-lang/rust-clippy/pull/6962) +* [`redundant_slicing`]: Fix suggestion when a re-borrow might be required or + when the value is from a macro call + [#6975](https://github.com/rust-lang/rust-clippy/pull/6975) +* [`match_wildcard_for_single_variants`]: Fix suggestion for hidden variant + [#6988](https://github.com/rust-lang/rust-clippy/pull/6988) +* [`clone_on_copy`]: Correct suggestion when the cloned value is a macro call + [#7000](https://github.com/rust-lang/rust-clippy/pull/7000) +* [`manual_map`]: Fix suggestion at the end of an if chain + [#7004](https://github.com/rust-lang/rust-clippy/pull/7004) +* Fix needless parenthesis output in multiple lint suggestions + [#7013](https://github.com/rust-lang/rust-clippy/pull/7013) +* [`needless_collect`]: Better explanation in the lint message + [#7020](https://github.com/rust-lang/rust-clippy/pull/7020) +* [`useless_vec`]: Now considers mutability + [#7036](https://github.com/rust-lang/rust-clippy/pull/7036) +* [`useless_format`]: Wrap the content in braces if necessary + [#7092](https://github.com/rust-lang/rust-clippy/pull/7092) +* [`single_match`]: Don't suggest an equality check for types which don't + implement `PartialEq` + [#7093](https://github.com/rust-lang/rust-clippy/pull/7093) +* [`from_over_into`]: Mention type in help message + [#7099](https://github.com/rust-lang/rust-clippy/pull/7099) +* [`manual_unwrap_or`]: Fix invalid code suggestion due to a macro call + [#7136](https://github.com/rust-lang/rust-clippy/pull/7136) + +### ICE Fixes + +* [`macro_use_imports`] + [#7022](https://github.com/rust-lang/rust-clippy/pull/7022) +* [`missing_panics_doc`] + [#7034](https://github.com/rust-lang/rust-clippy/pull/7034) +* [`tabs_in_doc_comments`] + [#7039](https://github.com/rust-lang/rust-clippy/pull/7039) +* [`missing_const_for_fn`] + [#7128](https://github.com/rust-lang/rust-clippy/pull/7128) + +### Others + +* [Clippy's lint + list](https://rust-lang.github.io/rust-clippy/master/index.html) now supports + themes [#7030](https://github.com/rust-lang/rust-clippy/pull/7030) +* Lints that were uplifted to `rustc` now mention the new `rustc` name in the + deprecation warning + [#7056](https://github.com/rust-lang/rust-clippy/pull/7056) + +## Rust 1.52 + +Current stable, released 2021-05-06 [3e41797...6ed6f1e](https://github.com/rust-lang/rust-clippy/compare/3e41797...6ed6f1e) @@ -99,7 +283,7 @@ Current beta, release 2021-05-06 [#6682](https://github.com/rust-lang/rust-clippy/pull/6682) * [`unit_arg`]: No longer lints on unit arguments when they come from a path expression. [#6601](https://github.com/rust-lang/rust-clippy/pull/6601) -* [`cargo_common_metadata`]: No longer lints if +* [`cargo_common_metadata`]: No longer lints if [`publish = false`](https://doc.rust-lang.org/cargo/reference/manifest.html#the-publish-field) is defined in the manifest [#6650](https://github.com/rust-lang/rust-clippy/pull/6650) @@ -124,11 +308,11 @@ Current beta, release 2021-05-06 * [`useless_format`]: Improved the documentation example [#6854](https://github.com/rust-lang/rust-clippy/pull/6854) -* Clippy's [`README.md`]: Includes a new subsection on running Clippy as a rustc wrapper +* Clippy's [`README.md`]: Includes a new subsection on running Clippy as a rustc wrapper [#6782](https://github.com/rust-lang/rust-clippy/pull/6782) ### Others -* Running `cargo clippy` after `cargo check` now works as expected +* Running `cargo clippy` after `cargo check` now works as expected (`cargo clippy` and `cargo check` no longer shares the same build cache) [#6687](https://github.com/rust-lang/rust-clippy/pull/6687) * Cargo now re-runs Clippy if arguments after `--` provided to `cargo clippy` are changed. @@ -145,7 +329,7 @@ Current beta, release 2021-05-06 ## Rust 1.51 -Current stable, released 2021-03-25 +Released 2021-03-25 [4911ab1...3e41797](https://github.com/rust-lang/rust-clippy/compare/4911ab1...3e41797) From ab3094b3dbf0bf6423a9cf0509a170eee89f106e Mon Sep 17 00:00:00 2001 From: Mateusz Gacek <96mateusz.gacek@gmail.com> Date: Thu, 6 May 2021 10:49:31 -0700 Subject: [PATCH 05/58] wrong_self_convention: For `to_*` variant don't lint in trait impl taking `self` when non-`Copy` type It relaxes rules for `to_*` variant, so it doesn't lint in trait definitions and implementations anymore. Although, non-`Copy` type implementing trait's `to_*` method taking `self` feels not good (consumes ownership, so should be rather named `into_`), it would be better if this case was a pedantic lint (allow-by-default) instead. --- clippy_lints/src/methods/wrong_self_convention.rs | 2 +- tests/ui/wrong_self_convention2.rs | 2 +- tests/ui/wrong_self_convention2.stderr | 11 ----------- 3 files changed, 2 insertions(+), 13 deletions(-) delete mode 100644 tests/ui/wrong_self_convention2.stderr diff --git a/clippy_lints/src/methods/wrong_self_convention.rs b/clippy_lints/src/methods/wrong_self_convention.rs index 6e2bcb113c2..1773c26c251 100644 --- a/clippy_lints/src/methods/wrong_self_convention.rs +++ b/clippy_lints/src/methods/wrong_self_convention.rs @@ -22,7 +22,7 @@ const CONVENTIONS: [(&[Convention], &[SelfKind]); 9] = [ // Conversion using `to_` can use borrowed (non-Copy types) or owned (Copy types). // Source: https://rust-lang.github.io/api-guidelines/naming.html#ad-hoc-conversions-follow-as_-to_-into_-conventions-c-conv (&[Convention::StartsWith("to_"), Convention::NotEndsWith("_mut"), Convention::IsSelfTypeCopy(false), - Convention::IsTraitItem(false)], &[SelfKind::Ref]), + Convention::IsTraitItem(false), Convention::ImplementsTrait(false)], &[SelfKind::Ref]), (&[Convention::StartsWith("to_"), Convention::NotEndsWith("_mut"), Convention::IsSelfTypeCopy(true), Convention::IsTraitItem(false), Convention::ImplementsTrait(false)], &[SelfKind::Value]), ]; diff --git a/tests/ui/wrong_self_convention2.rs b/tests/ui/wrong_self_convention2.rs index ae3a740d405..18202ef2989 100644 --- a/tests/ui/wrong_self_convention2.rs +++ b/tests/ui/wrong_self_convention2.rs @@ -23,7 +23,7 @@ mod issue6983 { } struct FooNoCopy; - // trigger lint + // don't trigger impl ToU64 for FooNoCopy { fn to_u64(self) -> u64 { 2 diff --git a/tests/ui/wrong_self_convention2.stderr b/tests/ui/wrong_self_convention2.stderr deleted file mode 100644 index 0ca1a390974..00000000000 --- a/tests/ui/wrong_self_convention2.stderr +++ /dev/null @@ -1,11 +0,0 @@ -error: methods with the following characteristics: (`to_*` and `self` type is not `Copy`) usually take `self` by reference - --> $DIR/wrong_self_convention2.rs:28:19 - | -LL | fn to_u64(self) -> u64 { - | ^^^^ - | - = note: `-D clippy::wrong-self-convention` implied by `-D warnings` - = help: consider choosing a less ambiguous name - -error: aborting due to previous error - From 300f628111d1203ce7d59096c0a1def82116fd8f Mon Sep 17 00:00:00 2001 From: Scott McMurray Date: Fri, 30 Apr 2021 18:40:34 -0700 Subject: [PATCH 06/58] Fix clippy --- clippy_lints/src/needless_question_mark.rs | 2 +- clippy_lints/src/try_err.rs | 2 +- clippy_lints/src/unused_io_amount.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/clippy_lints/src/needless_question_mark.rs b/clippy_lints/src/needless_question_mark.rs index 7b156a8c49d..d8417c7dc70 100644 --- a/clippy_lints/src/needless_question_mark.rs +++ b/clippy_lints/src/needless_question_mark.rs @@ -147,7 +147,7 @@ fn is_some_or_ok_call<'a>(cx: &'a LateContext<'_>, expr: &'a Expr<'_>) -> Option if let ExprKind::Call(called, args) = &inner_expr_with_q.kind; if args.len() == 1; - if let ExprKind::Path(QPath::LangItem(LangItem::TryIntoResult, _)) = &called.kind; + if let ExprKind::Path(QPath::LangItem(LangItem::TryTraitBranch, _)) = &called.kind; then { // Extract inner expr type from match argument generated by // question mark operator diff --git a/clippy_lints/src/try_err.rs b/clippy_lints/src/try_err.rs index ebb39ea4877..f2ba2b2ecf6 100644 --- a/clippy_lints/src/try_err.rs +++ b/clippy_lints/src/try_err.rs @@ -64,7 +64,7 @@ impl<'tcx> LateLintPass<'tcx> for TryErr { if let ExprKind::Match(match_arg, _, MatchSource::TryDesugar) = expr.kind; if let ExprKind::Call(match_fun, try_args) = match_arg.kind; if let ExprKind::Path(ref match_fun_path) = match_fun.kind; - if matches!(match_fun_path, QPath::LangItem(LangItem::TryIntoResult, _)); + if matches!(match_fun_path, QPath::LangItem(LangItem::TryTraitBranch, _)); if let Some(try_arg) = try_args.get(0); if let ExprKind::Call(err_fun, err_args) = try_arg.kind; if let Some(err_arg) = err_args.get(0); diff --git a/clippy_lints/src/unused_io_amount.rs b/clippy_lints/src/unused_io_amount.rs index 3387f35bac3..c27a6d4e347 100644 --- a/clippy_lints/src/unused_io_amount.rs +++ b/clippy_lints/src/unused_io_amount.rs @@ -45,7 +45,7 @@ impl<'tcx> LateLintPass<'tcx> for UnusedIoAmount { if let hir::ExprKind::Call(func, args) = res.kind { if matches!( func.kind, - hir::ExprKind::Path(hir::QPath::LangItem(hir::LangItem::TryIntoResult, _)) + hir::ExprKind::Path(hir::QPath::LangItem(hir::LangItem::TryTraitBranch, _)) ) { check_map_error(cx, &args[0], expr); } From 5f3aae61af8ab129b743d13b14ec5519e97cc445 Mon Sep 17 00:00:00 2001 From: Thomas Otto Date: Thu, 6 May 2021 23:51:01 +0200 Subject: [PATCH 07/58] Handle write!(buf, "\n") case better Make `write!(buf, "\n")` suggest `writeln!(buf)` by removing the trailing comma from `writeln!(buf, )`. changelog: [`write_with_newline`] suggestion on only "\n" improved --- clippy_lints/src/write.rs | 23 ++++++++++++++--------- tests/ui/write_with_newline.stderr | 4 ++-- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/clippy_lints/src/write.rs b/clippy_lints/src/write.rs index 7e962472c07..d0e79efa70d 100644 --- a/clippy_lints/src/write.rs +++ b/clippy_lints/src/write.rs @@ -279,8 +279,15 @@ impl EarlyLintPass for Write { span_lint(cx, PRINT_STDERR, mac.span(), "use of `eprintln!`"); self.lint_println_empty_string(cx, mac); } else if mac.path == sym!(write) { - if let (Some(fmt_str), _) = self.check_tts(cx, mac.args.inner_tokens(), true) { + if let (Some(fmt_str), dest) = self.check_tts(cx, mac.args.inner_tokens(), true) { if check_newlines(&fmt_str) { + let (nl_span, only_nl) = newline_span(&fmt_str); + let nl_span = match (dest, only_nl) { + // Special case of `write!(buf, "\n")`: Mark everything from the end of + // `buf` for removal so no trailing comma [`writeln!(buf, )`] remains. + (Some(dest_expr), true) => Span::new(dest_expr.span.hi(), nl_span.hi(), nl_span.ctxt()), + _ => nl_span, + }; span_lint_and_then( cx, WRITE_WITH_NEWLINE, @@ -289,10 +296,7 @@ impl EarlyLintPass for Write { |err| { err.multipart_suggestion( "use `writeln!()` instead", - vec![ - (mac.path.span, String::from("writeln")), - (newline_span(&fmt_str), String::new()), - ], + vec![(mac.path.span, String::from("writeln")), (nl_span, String::new())], Applicability::MachineApplicable, ); }, @@ -329,12 +333,13 @@ impl EarlyLintPass for Write { /// Given a format string that ends in a newline and its span, calculates the span of the /// newline, or the format string itself if the format string consists solely of a newline. -fn newline_span(fmtstr: &StrLit) -> Span { +/// Return this and a boolean indicating whether it only consisted of a newline. +fn newline_span(fmtstr: &StrLit) -> (Span, bool) { let sp = fmtstr.span; let contents = &fmtstr.symbol.as_str(); if *contents == r"\n" { - return sp; + return (sp, true); } let newline_sp_hi = sp.hi() @@ -351,7 +356,7 @@ fn newline_span(fmtstr: &StrLit) -> Span { panic!("expected format string to contain a newline"); }; - sp.with_lo(newline_sp_hi - newline_sp_len).with_hi(newline_sp_hi) + (sp.with_lo(newline_sp_hi - newline_sp_len).with_hi(newline_sp_hi), false) } /// Stores a list of replacement spans for each argument, but only if all the replacements used an @@ -613,7 +618,7 @@ impl Write { |err| { err.multipart_suggestion( &format!("use `{}!` instead", suggested), - vec![(mac.path.span, suggested), (newline_span(&fmt_str), String::new())], + vec![(mac.path.span, suggested), (newline_span(&fmt_str).0, String::new())], Applicability::MachineApplicable, ); }, diff --git a/tests/ui/write_with_newline.stderr b/tests/ui/write_with_newline.stderr index a14e86122ee..cecc2ea9406 100644 --- a/tests/ui/write_with_newline.stderr +++ b/tests/ui/write_with_newline.stderr @@ -51,8 +51,8 @@ LL | write!(&mut v, "/n"); | help: use `writeln!()` instead | -LL | writeln!(&mut v, ); - | ^^^^^^^ -- +LL | writeln!(&mut v); + | ^^^^^^^ -- error: using `write!()` with a format string that ends in a single newline --> $DIR/write_with_newline.rs:36:5 From 91851d8040da1569366ef977dd9a43a70c7519e9 Mon Sep 17 00:00:00 2001 From: Kevin Reid Date: Thu, 6 May 2021 17:18:45 -0700 Subject: [PATCH 08/58] Fix duplicated "Rust 1.52" version section header The most recent changelog update 037ddf282bbf011babd4b7f9a851f6028fc6fd70 accompanying the 1.52 release added a second "Rust 1.52" section header, with the result that the Rust release announcement https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html is linking to the "current beta" changelog section for Clippy rather than the stable changelog. I don't know the release process but based on previous changes to this file, I assume the correct thing to do is to mark the topmost section as being for Rust 1.53, not 1.52. --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fce3fccd075..0a328a20dd4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ document. [7c7683c...master](https://github.com/rust-lang/rust-clippy/compare/7c7683c...master) -## Rust 1.52 +## Rust 1.53 Current beta, release 2021-06-17 From a21607d9b504281a00325065955dd825334ad6ef Mon Sep 17 00:00:00 2001 From: Mateusz Gacek <96mateusz.gacek@gmail.com> Date: Wed, 5 May 2021 12:08:24 -0700 Subject: [PATCH 09/58] needless_collect: For `BTreeMap` and `HashMap` lint only `is_empty` - `len` might produce different results than `count` - they don't have `contain` but `contains_key` method --- clippy_lints/src/loops/needless_collect.rs | 64 +++++++++++++--------- tests/ui/needless_collect.fixed | 10 +++- tests/ui/needless_collect.rs | 8 ++- tests/ui/needless_collect.stderr | 14 +++-- 4 files changed, 63 insertions(+), 33 deletions(-) diff --git a/clippy_lints/src/loops/needless_collect.rs b/clippy_lints/src/loops/needless_collect.rs index 9662a0b22a3..1ed58faa925 100644 --- a/clippy_lints/src/loops/needless_collect.rs +++ b/clippy_lints/src/loops/needless_collect.rs @@ -10,7 +10,6 @@ use rustc_hir::intravisit::{walk_block, walk_expr, NestedVisitorMap, Visitor}; use rustc_hir::{Block, Expr, ExprKind, GenericArg, GenericArgs, HirId, Local, Pat, PatKind, QPath, StmtKind, Ty}; use rustc_lint::LateContext; use rustc_middle::hir::map::Map; - use rustc_span::symbol::{sym, Ident}; use rustc_span::{MultiSpan, Span}; @@ -28,32 +27,45 @@ fn check_needless_collect_direct_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateCont if let Some(generic_args) = chain_method.args; if let Some(GenericArg::Type(ref ty)) = generic_args.args.get(0); if let Some(ty) = cx.typeck_results().node_type_opt(ty.hir_id); - if is_type_diagnostic_item(cx, ty, sym::vec_type) - || is_type_diagnostic_item(cx, ty, sym::vecdeque_type) - || match_type(cx, ty, &paths::BTREEMAP) - || is_type_diagnostic_item(cx, ty, sym::hashmap_type); - if let Some(sugg) = match &*method.ident.name.as_str() { - "len" => Some("count()".to_string()), - "is_empty" => Some("next().is_none()".to_string()), - "contains" => { - let contains_arg = snippet(cx, args[1].span, "??"); - let (arg, pred) = contains_arg - .strip_prefix('&') - .map_or(("&x", &*contains_arg), |s| ("x", s)); - Some(format!("any(|{}| x == {})", arg, pred)) - } - _ => None, - }; then { - span_lint_and_sugg( - cx, - NEEDLESS_COLLECT, - method0_span.with_hi(expr.span.hi()), - NEEDLESS_COLLECT_MSG, - "replace with", - sugg, - Applicability::MachineApplicable, - ); + let is_empty_sugg = Some("next().is_none()".to_string()); + let method_name = &*method.ident.name.as_str(); + let sugg = if is_type_diagnostic_item(cx, ty, sym::vec_type) || + is_type_diagnostic_item(cx, ty, sym::vecdeque_type) { + match method_name { + "len" => Some("count()".to_string()), + "is_empty" => is_empty_sugg, + "contains" => { + let contains_arg = snippet(cx, args[1].span, "??"); + let (arg, pred) = contains_arg + .strip_prefix('&') + .map_or(("&x", &*contains_arg), |s| ("x", s)); + Some(format!("any(|{}| x == {})", arg, pred)) + } + _ => None, + } + } + else if match_type(cx, ty, &paths::BTREEMAP) || + is_type_diagnostic_item(cx, ty, sym::hashmap_type) { + match method_name { + "is_empty" => is_empty_sugg, + _ => None, + } + } + else { + None + }; + if let Some(sugg) = sugg { + span_lint_and_sugg( + cx, + NEEDLESS_COLLECT, + method0_span.with_hi(expr.span.hi()), + NEEDLESS_COLLECT_MSG, + "replace with", + sugg, + Applicability::MachineApplicable, + ); + } } } } diff --git a/tests/ui/needless_collect.fixed b/tests/ui/needless_collect.fixed index af6c7bf15ea..d7595569681 100644 --- a/tests/ui/needless_collect.fixed +++ b/tests/ui/needless_collect.fixed @@ -2,7 +2,7 @@ #![allow(unused, clippy::suspicious_map, clippy::iter_count)] -use std::collections::{BTreeSet, HashMap, HashSet}; +use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; #[warn(clippy::needless_collect)] #[allow(unused_variables, clippy::iter_cloned_collect, clippy::iter_next_slice)] @@ -13,7 +13,13 @@ fn main() { // Empty } sample.iter().cloned().any(|x| x == 1); - sample.iter().map(|x| (x, x)).count(); + // #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(); + + sample.iter().map(|x| (x, x)).next().is_none(); + sample.iter().map(|x| (x, x)).next().is_none(); + // Notice the `HashSet`--this should not be linted sample.iter().collect::>().len(); // Neither should this diff --git a/tests/ui/needless_collect.rs b/tests/ui/needless_collect.rs index 6ae14f370b1..9883c75b745 100644 --- a/tests/ui/needless_collect.rs +++ b/tests/ui/needless_collect.rs @@ -2,7 +2,7 @@ #![allow(unused, clippy::suspicious_map, clippy::iter_count)] -use std::collections::{BTreeSet, HashMap, HashSet}; +use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; #[warn(clippy::needless_collect)] #[allow(unused_variables, clippy::iter_cloned_collect, clippy::iter_next_slice)] @@ -13,7 +13,13 @@ fn main() { // Empty } sample.iter().cloned().collect::>().contains(&1); + // #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(); + + sample.iter().map(|x| (x, x)).collect::>().is_empty(); + sample.iter().map(|x| (x, x)).collect::>().is_empty(); + // Notice the `HashSet`--this should not be linted sample.iter().collect::>().len(); // Neither should this diff --git a/tests/ui/needless_collect.stderr b/tests/ui/needless_collect.stderr index 2a9539d5975..3acdf66a42e 100644 --- a/tests/ui/needless_collect.stderr +++ b/tests/ui/needless_collect.stderr @@ -19,10 +19,16 @@ LL | sample.iter().cloned().collect::>().contains(&1); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `any(|x| x == 1)` error: avoid using `collect()` when not needed - --> $DIR/needless_collect.rs:16:35 + --> $DIR/needless_collect.rs:20:35 | -LL | sample.iter().map(|x| (x, x)).collect::>().len(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `count()` +LL | sample.iter().map(|x| (x, x)).collect::>().is_empty(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `next().is_none()` -error: aborting due to 4 previous errors +error: avoid using `collect()` when not needed + --> $DIR/needless_collect.rs:21:35 + | +LL | sample.iter().map(|x| (x, x)).collect::>().is_empty(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `next().is_none()` + +error: aborting due to 5 previous errors From 59ccc1efb368a9a3b036510f7b2d56519608da85 Mon Sep 17 00:00:00 2001 From: Mateusz Gacek <96mateusz.gacek@gmail.com> Date: Wed, 5 May 2021 12:16:57 -0700 Subject: [PATCH 10/58] needless_collect: replace paths with diag items Related to: #5393 --- clippy_lints/src/loops/needless_collect.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/clippy_lints/src/loops/needless_collect.rs b/clippy_lints/src/loops/needless_collect.rs index 1ed58faa925..6a9aa08426c 100644 --- a/clippy_lints/src/loops/needless_collect.rs +++ b/clippy_lints/src/loops/needless_collect.rs @@ -2,8 +2,8 @@ use super::NEEDLESS_COLLECT; use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; use clippy_utils::source::snippet; use clippy_utils::sugg::Sugg; -use clippy_utils::ty::{is_type_diagnostic_item, match_type}; -use clippy_utils::{is_trait_method, path_to_local_id, paths}; +use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::{is_trait_method, path_to_local_id}; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::intravisit::{walk_block, walk_expr, NestedVisitorMap, Visitor}; @@ -30,7 +30,7 @@ fn check_needless_collect_direct_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateCont then { let is_empty_sugg = Some("next().is_none()".to_string()); let method_name = &*method.ident.name.as_str(); - let sugg = if is_type_diagnostic_item(cx, ty, sym::vec_type) || + let sugg = if is_type_diagnostic_item(cx, ty, sym::vec_type) || is_type_diagnostic_item(cx, ty, sym::vecdeque_type) { match method_name { "len" => Some("count()".to_string()), @@ -45,7 +45,7 @@ fn check_needless_collect_direct_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateCont _ => None, } } - else if match_type(cx, ty, &paths::BTREEMAP) || + else if is_type_diagnostic_item(cx, ty, sym::BTreeMap) || is_type_diagnostic_item(cx, ty, sym::hashmap_type) { match method_name { "is_empty" => is_empty_sugg, @@ -98,7 +98,7 @@ fn check_needless_collect_indirect_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateCo if is_type_diagnostic_item(cx, ty, sym::vec_type) || is_type_diagnostic_item(cx, ty, sym::vecdeque_type) || is_type_diagnostic_item(cx, ty, sym::BinaryHeap) || - match_type(cx, ty, &paths::LINKED_LIST); + is_type_diagnostic_item(cx, ty, sym::LinkedList); if let Some(iter_calls) = detect_iter_and_into_iters(block, *ident); if let [iter_call] = &*iter_calls; then { From 171789eb4526fe4ef65514f26056ca4551915cca Mon Sep 17 00:00:00 2001 From: Mateusz Gacek <96mateusz.gacek@gmail.com> Date: Wed, 5 May 2021 12:17:49 -0700 Subject: [PATCH 11/58] needless_collect: Lint `LinkedList` and `BinaryHeap` in direct usage. Those two types are supported already when used indirectly. This commit adds support for direct usage as well. --- clippy_lints/src/loops/needless_collect.rs | 4 ++- tests/ui/needless_collect.fixed | 11 ++++++- tests/ui/needless_collect.rs | 11 ++++++- tests/ui/needless_collect.stderr | 38 +++++++++++++++++++++- 4 files changed, 60 insertions(+), 4 deletions(-) diff --git a/clippy_lints/src/loops/needless_collect.rs b/clippy_lints/src/loops/needless_collect.rs index 6a9aa08426c..89c95f3d127 100644 --- a/clippy_lints/src/loops/needless_collect.rs +++ b/clippy_lints/src/loops/needless_collect.rs @@ -31,7 +31,9 @@ fn check_needless_collect_direct_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateCont let is_empty_sugg = Some("next().is_none()".to_string()); let method_name = &*method.ident.name.as_str(); let sugg = if is_type_diagnostic_item(cx, ty, sym::vec_type) || - is_type_diagnostic_item(cx, ty, sym::vecdeque_type) { + is_type_diagnostic_item(cx, ty, sym::vecdeque_type) || + is_type_diagnostic_item(cx, ty, sym::LinkedList) || + is_type_diagnostic_item(cx, ty, sym::BinaryHeap) { match method_name { "len" => Some("count()".to_string()), "is_empty" => is_empty_sugg, diff --git a/tests/ui/needless_collect.fixed b/tests/ui/needless_collect.fixed index d7595569681..6ecbbcb6249 100644 --- a/tests/ui/needless_collect.fixed +++ b/tests/ui/needless_collect.fixed @@ -2,7 +2,7 @@ #![allow(unused, clippy::suspicious_map, clippy::iter_count)] -use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; +use std::collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, LinkedList}; #[warn(clippy::needless_collect)] #[allow(unused_variables, clippy::iter_cloned_collect, clippy::iter_next_slice)] @@ -24,4 +24,13 @@ fn main() { sample.iter().collect::>().len(); // Neither should this sample.iter().collect::>().len(); + + sample.iter().count(); + sample.iter().next().is_none(); + sample.iter().cloned().any(|x| x == 1); + sample.iter().any(|x| x == &1); + + // `BinaryHeap` doesn't have `contains` method + sample.iter().count(); + sample.iter().next().is_none(); } diff --git a/tests/ui/needless_collect.rs b/tests/ui/needless_collect.rs index 9883c75b745..8dc69bcf5b3 100644 --- a/tests/ui/needless_collect.rs +++ b/tests/ui/needless_collect.rs @@ -2,7 +2,7 @@ #![allow(unused, clippy::suspicious_map, clippy::iter_count)] -use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; +use std::collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, LinkedList}; #[warn(clippy::needless_collect)] #[allow(unused_variables, clippy::iter_cloned_collect, clippy::iter_next_slice)] @@ -24,4 +24,13 @@ fn main() { sample.iter().collect::>().len(); // Neither should this sample.iter().collect::>().len(); + + sample.iter().collect::>().len(); + sample.iter().collect::>().is_empty(); + sample.iter().cloned().collect::>().contains(&1); + sample.iter().collect::>().contains(&&1); + + // `BinaryHeap` doesn't have `contains` method + sample.iter().collect::>().len(); + sample.iter().collect::>().is_empty(); } diff --git a/tests/ui/needless_collect.stderr b/tests/ui/needless_collect.stderr index 3acdf66a42e..039091627a8 100644 --- a/tests/ui/needless_collect.stderr +++ b/tests/ui/needless_collect.stderr @@ -30,5 +30,41 @@ error: avoid using `collect()` when not needed LL | sample.iter().map(|x| (x, x)).collect::>().is_empty(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `next().is_none()` -error: aborting due to 5 previous errors +error: avoid using `collect()` when not needed + --> $DIR/needless_collect.rs:28:19 + | +LL | sample.iter().collect::>().len(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `count()` + +error: avoid using `collect()` when not needed + --> $DIR/needless_collect.rs:29:19 + | +LL | sample.iter().collect::>().is_empty(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `next().is_none()` + +error: avoid using `collect()` when not needed + --> $DIR/needless_collect.rs:30:28 + | +LL | sample.iter().cloned().collect::>().contains(&1); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `any(|x| x == 1)` + +error: avoid using `collect()` when not needed + --> $DIR/needless_collect.rs:31:19 + | +LL | sample.iter().collect::>().contains(&&1); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `any(|x| x == &1)` + +error: avoid using `collect()` when not needed + --> $DIR/needless_collect.rs:34:19 + | +LL | sample.iter().collect::>().len(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `count()` + +error: avoid using `collect()` when not needed + --> $DIR/needless_collect.rs:35:19 + | +LL | sample.iter().collect::>().is_empty(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace with: `next().is_none()` + +error: aborting due to 11 previous errors From 4d283ce8e49bde4f224597fca08e042339d65ee3 Mon Sep 17 00:00:00 2001 From: Cameron Steffen Date: Fri, 7 May 2021 14:53:02 -0500 Subject: [PATCH 12/58] Fix duplicate unknown lint errors --- tests/ui/deprecated.stderr | 8 +------- tests/ui/deprecated_old.stderr | 8 +------- tests/ui/rename.stderr | 8 +------- tests/ui/unknown_clippy_lints.stderr | 8 +------- 4 files changed, 4 insertions(+), 28 deletions(-) diff --git a/tests/ui/deprecated.stderr b/tests/ui/deprecated.stderr index 3e125c1dab5..e5de839dbc5 100644 --- a/tests/ui/deprecated.stderr +++ b/tests/ui/deprecated.stderr @@ -84,11 +84,5 @@ error: lint `clippy::filter_map` has been removed: this lint has been replaced b LL | #[warn(clippy::filter_map)] | ^^^^^^^^^^^^^^^^^^ -error: lint `clippy::unstable_as_slice` has been removed: `Vec::as_slice` has been stabilized in 1.7 - --> $DIR/deprecated.rs:1:8 - | -LL | #[warn(clippy::unstable_as_slice)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^ - -error: aborting due to 15 previous errors +error: aborting due to 14 previous errors diff --git a/tests/ui/deprecated_old.stderr b/tests/ui/deprecated_old.stderr index b8550078c46..8043ab0058a 100644 --- a/tests/ui/deprecated_old.stderr +++ b/tests/ui/deprecated_old.stderr @@ -18,11 +18,5 @@ error: lint `misaligned_transmute` has been removed: this lint has been split in LL | #[warn(misaligned_transmute)] | ^^^^^^^^^^^^^^^^^^^^ -error: lint `unstable_as_slice` has been removed: `Vec::as_slice` has been stabilized in 1.7 - --> $DIR/deprecated_old.rs:1:8 - | -LL | #[warn(unstable_as_slice)] - | ^^^^^^^^^^^^^^^^^ - -error: aborting due to 4 previous errors +error: aborting due to 3 previous errors diff --git a/tests/ui/rename.stderr b/tests/ui/rename.stderr index a9e80394604..c5d633ff86b 100644 --- a/tests/ui/rename.stderr +++ b/tests/ui/rename.stderr @@ -24,11 +24,5 @@ error: lint `clippy::const_static_lifetime` has been renamed to `clippy::redunda LL | #[warn(clippy::const_static_lifetime)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::redundant_static_lifetimes` -error: lint `clippy::cyclomatic_complexity` has been renamed to `clippy::cognitive_complexity` - --> $DIR/rename.rs:10:9 - | -LL | #![warn(clippy::cyclomatic_complexity)] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::cognitive_complexity` - -error: aborting due to 5 previous errors +error: aborting due to 4 previous errors diff --git a/tests/ui/unknown_clippy_lints.stderr b/tests/ui/unknown_clippy_lints.stderr index 94a667e5898..421bf5ffa9a 100644 --- a/tests/ui/unknown_clippy_lints.stderr +++ b/tests/ui/unknown_clippy_lints.stderr @@ -48,11 +48,5 @@ error: unknown lint: `clippy::const_static_lifetim` LL | #[warn(clippy::const_static_lifetim)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: did you mean: `clippy::redundant_static_lifetimes` -error: unknown lint: `clippy::All` - --> $DIR/unknown_clippy_lints.rs:5:10 - | -LL | #![allow(clippy::All)] - | ^^^^^^^^^^^ help: did you mean: `clippy::all` - -error: aborting due to 9 previous errors +error: aborting due to 8 previous errors From 1b2ca3067e042b3c490d7f9b38258205bc2096f0 Mon Sep 17 00:00:00 2001 From: William Chargin Date: Fri, 7 May 2021 20:24:07 -0700 Subject: [PATCH 13/58] Move `inconsistent_struct_constructor` to pedantic The whole point of named fields is that we don't have to worry about order. The names, not the position, communicate the information, so worrying about consistency for consistency's sake is pedantic to a *T*. Fixes #7192. wchargin-branch: inconsistent-struct-constructor-pedantic wchargin-source: 4fe078a21c77ceb625e58fa3b90b613fc4fa6a76 --- clippy_lints/src/inconsistent_struct_constructor.rs | 2 +- clippy_lints/src/lib.rs | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/clippy_lints/src/inconsistent_struct_constructor.rs b/clippy_lints/src/inconsistent_struct_constructor.rs index d138c3a8acf..3b635071f28 100644 --- a/clippy_lints/src/inconsistent_struct_constructor.rs +++ b/clippy_lints/src/inconsistent_struct_constructor.rs @@ -58,7 +58,7 @@ declare_clippy_lint! { /// Foo { x, y }; /// ``` pub INCONSISTENT_STRUCT_CONSTRUCTOR, - style, + pedantic, "the order of the field init shorthand is inconsistent with the order in the struct definition" } diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 79fe4d06d07..783bef73214 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -1364,6 +1364,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(if_not_else::IF_NOT_ELSE), LintId::of(implicit_hasher::IMPLICIT_HASHER), LintId::of(implicit_saturating_sub::IMPLICIT_SATURATING_SUB), + LintId::of(inconsistent_struct_constructor::INCONSISTENT_STRUCT_CONSTRUCTOR), LintId::of(infinite_iter::MAYBE_INFINITE_ITER), LintId::of(invalid_upcast_comparisons::INVALID_UPCAST_COMPARISONS), LintId::of(items_after_statements::ITEMS_AFTER_STATEMENTS), @@ -1510,7 +1511,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(identity_op::IDENTITY_OP), LintId::of(if_let_mutex::IF_LET_MUTEX), LintId::of(if_let_some_result::IF_LET_SOME_RESULT), - LintId::of(inconsistent_struct_constructor::INCONSISTENT_STRUCT_CONSTRUCTOR), LintId::of(indexing_slicing::OUT_OF_BOUNDS_INDEXING), LintId::of(infinite_iter::INFINITE_ITER), LintId::of(inherent_to_string::INHERENT_TO_STRING), @@ -1763,7 +1763,6 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(functions::MUST_USE_UNIT), LintId::of(functions::RESULT_UNIT_ERR), LintId::of(if_let_some_result::IF_LET_SOME_RESULT), - LintId::of(inconsistent_struct_constructor::INCONSISTENT_STRUCT_CONSTRUCTOR), LintId::of(inherent_to_string::INHERENT_TO_STRING), LintId::of(len_zero::COMPARISON_TO_EMPTY), LintId::of(len_zero::LEN_WITHOUT_IS_EMPTY), From d849e9586ecd203e63bfc549098f80fa11725252 Mon Sep 17 00:00:00 2001 From: xFrednet Date: Sat, 8 May 2021 13:29:59 +0200 Subject: [PATCH 14/58] Metadata collection monster eating deprecated lints --- clippy_lints/src/deprecated_lints.rs | 11 ++- clippy_lints/src/lib.rs | 2 + .../internal_lints/metadata_collector.rs | 70 ++++++++++++++----- 3 files changed, 62 insertions(+), 21 deletions(-) diff --git a/clippy_lints/src/deprecated_lints.rs b/clippy_lints/src/deprecated_lints.rs index 4688b3d5105..fa8add20688 100644 --- a/clippy_lints/src/deprecated_lints.rs +++ b/clippy_lints/src/deprecated_lints.rs @@ -1,6 +1,13 @@ +/// This struct fakes the `Lint` declaration that is usually created by `declare_lint!`. This +/// enables the simple extraction of the metadata without changing the current depreciation +/// declaration. +pub struct ClippyDeprecatedLint; + macro_rules! declare_deprecated_lint { - (pub $name: ident, $_reason: expr) => { - declare_lint!(pub $name, Allow, "deprecated lint") + { $(#[$attr:meta])* pub $name: ident, $_reason: expr} => { + $(#[$attr])* + #[allow(dead_code)] + pub static $name: ClippyDeprecatedLint = ClippyDeprecatedLint {}; } } diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 79fe4d06d07..8bdd041576f 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -162,6 +162,8 @@ macro_rules! extract_msrv_attr { mod consts; #[macro_use] mod utils; +#[cfg(feature = "metadata-collector-lint")] +mod deprecated_lints; // begin lints modules, do not remove this comment, it’s used in `update_lints` mod absurd_extreme_comparisons; diff --git a/clippy_lints/src/utils/internal_lints/metadata_collector.rs b/clippy_lints/src/utils/internal_lints/metadata_collector.rs index e85637ca758..af734fa5572 100644 --- a/clippy_lints/src/utils/internal_lints/metadata_collector.rs +++ b/clippy_lints/src/utils/internal_lints/metadata_collector.rs @@ -28,7 +28,7 @@ use std::path::Path; use crate::utils::internal_lints::is_lint_ref_type; use clippy_utils::{ - diagnostics::span_lint, last_path_segment, match_function_call, match_path, paths, ty::match_type, + diagnostics::span_lint, last_path_segment, match_def_path, match_function_call, match_path, paths, ty::match_type, ty::walk_ptrs_ty_depth, }; @@ -41,6 +41,8 @@ const BLACK_LISTED_LINTS: [&str; 3] = ["lint_author", "deep_code_inspection", "i const IGNORED_LINT_GROUPS: [&str; 1] = ["clippy::all"]; /// Lints within this group will be excluded from the collection const EXCLUDED_LINT_GROUPS: [&str; 1] = ["clippy::internal"]; +/// Collected deprecated lint will be assigned to this group in the JSON output +const DEPRECATED_LINT_GROUP_STR: &str = "DEPRECATED"; const LINT_EMISSION_FUNCTIONS: [&[&str]; 7] = [ &["clippy_utils", "diagnostics", "span_lint"], @@ -66,6 +68,7 @@ const SUGGESTION_FUNCTIONS: [&[&str]; 2] = [ &["clippy_utils", "diagnostics", "multispan_sugg"], &["clippy_utils", "diagnostics", "multispan_sugg_with_applicability"], ]; +const DEPRECATED_LINT_TYPE: [&str; 3] = ["clippy_lints", "deprecated_lints", "ClippyDeprecatedLint"]; /// The index of the applicability name of `paths::APPLICABILITY_VALUES` const APPLICABILITY_NAME_INDEX: usize = 2; @@ -225,23 +228,42 @@ impl<'hir> LateLintPass<'hir> for MetadataCollector { /// } /// ``` fn check_item(&mut self, cx: &LateContext<'hir>, item: &'hir Item<'_>) { - if_chain! { - // item validation - if let ItemKind::Static(ref ty, Mutability::Not, _) = item.kind; - if is_lint_ref_type(cx, ty); - // blacklist check - let lint_name = sym_to_string(item.ident.name).to_ascii_lowercase(); - if !BLACK_LISTED_LINTS.contains(&lint_name.as_str()); - // metadata extraction - if let Some(group) = get_lint_group_or_lint(cx, &lint_name, item); - if let Some(docs) = extract_attr_docs_or_lint(cx, item); - then { - self.lints.push(LintMetadata::new( - lint_name, - SerializableSpan::from_item(cx, item), - group, - docs, - )); + if let ItemKind::Static(ref ty, Mutability::Not, _) = item.kind { + // Normal lint + if_chain! { + // item validation + if is_lint_ref_type(cx, ty); + // blacklist check + let lint_name = sym_to_string(item.ident.name).to_ascii_lowercase(); + if !BLACK_LISTED_LINTS.contains(&lint_name.as_str()); + // metadata extraction + if let Some(group) = get_lint_group_or_lint(cx, &lint_name, item); + if let Some(docs) = extract_attr_docs_or_lint(cx, item); + then { + self.lints.push(LintMetadata::new( + lint_name, + SerializableSpan::from_item(cx, item), + group, + docs, + )); + } + } + + if_chain! { + if is_deprecated_lint(cx, ty); + // blacklist check + let lint_name = sym_to_string(item.ident.name).to_ascii_lowercase(); + if !BLACK_LISTED_LINTS.contains(&lint_name.as_str()); + // Metadata the little we can get from a deprecated lint + if let Some(docs) = extract_attr_docs_or_lint(cx, item); + then { + self.lints.push(LintMetadata::new( + lint_name, + SerializableSpan::from_item(cx, item), + DEPRECATED_LINT_GROUP_STR.to_string(), + docs, + )); + } } } } @@ -268,7 +290,7 @@ impl<'hir> LateLintPass<'hir> for MetadataCollector { // - src/misc.rs:734:9 // - src/methods/mod.rs:3545:13 // - src/methods/mod.rs:3496:13 - // We are basically unable to resolve the lint name it self. + // We are basically unable to resolve the lint name itself. return; } @@ -347,6 +369,16 @@ fn get_lint_group(cx: &LateContext<'_>, lint_id: LintId) -> Option { None } +fn is_deprecated_lint(cx: &LateContext<'_>, ty: &hir::Ty<'_>) -> bool { + if let hir::TyKind::Path(ref path) = ty.kind { + if let hir::def::Res::Def(DefKind::Struct, def_id) = cx.qpath_res(path, ty.hir_id) { + return match_def_path(cx, def_id, &DEPRECATED_LINT_TYPE); + } + } + + false +} + // ================================================================== // Lint emission // ================================================================== From 98b11c8f7bfd7efd5044b6011ba1f4d71f46b223 Mon Sep 17 00:00:00 2001 From: bors Date: Wed, 5 May 2021 17:33:46 +0000 Subject: [PATCH 15/58] Auto merge of #7170 - flip1995:revert_drop_order, r=llogiq Fix stack overflow issue in `redundant_pattern_matching` Fixes #7169 ~~cc `@Jarcho` Since tomorrow is release day and we need to get this also fixed in beta, I'll just revert the PR instead of looking into the root issue. Your changes are good, so if you have an idea what could cause this stack overflow and know how to fix it, please open a PR that reverts this revert with a fix.~~ r? `@llogiq` changelog: none (fixes stack overflow, but this was introduced in this release cycle) --- clippy_lints/src/matches.rs | 16 ++++++++++++---- tests/ui/crashes/ice-7169.rs | 9 +++++++++ tests/ui/crashes/ice-7169.stderr | 10 ++++++++++ 3 files changed, 31 insertions(+), 4 deletions(-) create mode 100644 tests/ui/crashes/ice-7169.rs create mode 100644 tests/ui/crashes/ice-7169.stderr diff --git a/clippy_lints/src/matches.rs b/clippy_lints/src/matches.rs index 13b2a834b0a..50e2898bdaa 100644 --- a/clippy_lints/src/matches.rs +++ b/clippy_lints/src/matches.rs @@ -1712,6 +1712,7 @@ mod redundant_pattern_match { use clippy_utils::{is_lang_ctor, is_qpath_def_path, is_trait_method, paths}; use if_chain::if_chain; use rustc_ast::ast::LitKind; + use rustc_data_structures::fx::FxHashSet; use rustc_errors::Applicability; use rustc_hir::LangItem::{OptionNone, OptionSome, PollPending, PollReady, ResultErr, ResultOk}; use rustc_hir::{ @@ -1739,6 +1740,13 @@ mod redundant_pattern_match { /// deallocate memory. For these types, and composites containing them, changing the drop order /// won't result in any observable side effects. fn type_needs_ordered_drop(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { + type_needs_ordered_drop_inner(cx, ty, &mut FxHashSet::default()) + } + + fn type_needs_ordered_drop_inner(cx: &LateContext<'tcx>, ty: Ty<'tcx>, seen: &mut FxHashSet>) -> bool { + if !seen.insert(ty) { + return false; + } if !ty.needs_drop(cx.tcx, cx.param_env) { false } else if !cx @@ -1750,12 +1758,12 @@ mod redundant_pattern_match { // This type doesn't implement drop, so no side effects here. // Check if any component type has any. match ty.kind() { - ty::Tuple(_) => ty.tuple_fields().any(|ty| type_needs_ordered_drop(cx, ty)), - ty::Array(ty, _) => type_needs_ordered_drop(cx, ty), + ty::Tuple(_) => ty.tuple_fields().any(|ty| type_needs_ordered_drop_inner(cx, ty, seen)), + ty::Array(ty, _) => type_needs_ordered_drop_inner(cx, ty, seen), ty::Adt(adt, subs) => adt .all_fields() .map(|f| f.ty(cx.tcx, subs)) - .any(|ty| type_needs_ordered_drop(cx, ty)), + .any(|ty| type_needs_ordered_drop_inner(cx, ty, seen)), _ => true, } } @@ -1772,7 +1780,7 @@ mod redundant_pattern_match { { // Check all of the generic arguments. if let ty::Adt(_, subs) = ty.kind() { - subs.types().any(|ty| type_needs_ordered_drop(cx, ty)) + subs.types().any(|ty| type_needs_ordered_drop_inner(cx, ty, seen)) } else { true } diff --git a/tests/ui/crashes/ice-7169.rs b/tests/ui/crashes/ice-7169.rs new file mode 100644 index 00000000000..82095febc19 --- /dev/null +++ b/tests/ui/crashes/ice-7169.rs @@ -0,0 +1,9 @@ +#[derive(Default)] +struct A { + a: Vec>, + b: T, +} + +fn main() { + if let Ok(_) = Ok::<_, ()>(A::::default()) {} +} diff --git a/tests/ui/crashes/ice-7169.stderr b/tests/ui/crashes/ice-7169.stderr new file mode 100644 index 00000000000..5a9cd32380a --- /dev/null +++ b/tests/ui/crashes/ice-7169.stderr @@ -0,0 +1,10 @@ +error: redundant pattern matching, consider using `is_ok()` + --> $DIR/ice-7169.rs:8:12 + | +LL | if let Ok(_) = Ok::<_, ()>(A::::default()) {} + | -------^^^^^-------------------------------------- help: try this: `if Ok::<_, ()>(A::::default()).is_ok()` + | + = note: `-D clippy::redundant-pattern-matching` implied by `-D warnings` + +error: aborting due to previous error + From c0095be6ee375ab1965a424502cfa1844d3b889f Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Tue, 11 May 2021 00:40:25 +0200 Subject: [PATCH 16/58] fix clippy test --- tests/ui/new_without_default.rs | 1 - tests/ui/new_without_default.stderr | 12 ++++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/tests/ui/new_without_default.rs b/tests/ui/new_without_default.rs index 64659b63f46..58094646b50 100644 --- a/tests/ui/new_without_default.rs +++ b/tests/ui/new_without_default.rs @@ -1,4 +1,3 @@ -#![feature(const_fn)] #![allow(dead_code, clippy::missing_safety_doc)] #![warn(clippy::new_without_default)] diff --git a/tests/ui/new_without_default.stderr b/tests/ui/new_without_default.stderr index 973836f75a9..56c5fe1c618 100644 --- a/tests/ui/new_without_default.stderr +++ b/tests/ui/new_without_default.stderr @@ -1,5 +1,5 @@ error: you should consider adding a `Default` implementation for `Foo` - --> $DIR/new_without_default.rs:8:5 + --> $DIR/new_without_default.rs:7:5 | LL | / pub fn new() -> Foo { LL | | Foo @@ -17,7 +17,7 @@ LL | } | error: you should consider adding a `Default` implementation for `Bar` - --> $DIR/new_without_default.rs:16:5 + --> $DIR/new_without_default.rs:15:5 | LL | / pub fn new() -> Self { LL | | Bar @@ -34,7 +34,7 @@ LL | } | error: you should consider adding a `Default` implementation for `LtKo<'c>` - --> $DIR/new_without_default.rs:80:5 + --> $DIR/new_without_default.rs:79:5 | LL | / pub fn new() -> LtKo<'c> { LL | | unimplemented!() @@ -51,7 +51,7 @@ LL | } | error: you should consider adding a `Default` implementation for `NewNotEqualToDerive` - --> $DIR/new_without_default.rs:157:5 + --> $DIR/new_without_default.rs:156:5 | LL | / pub fn new() -> Self { LL | | NewNotEqualToDerive { foo: 1 } @@ -68,7 +68,7 @@ LL | } | error: you should consider adding a `Default` implementation for `FooGenerics` - --> $DIR/new_without_default.rs:165:5 + --> $DIR/new_without_default.rs:164:5 | LL | / pub fn new() -> Self { LL | | Self(Default::default()) @@ -85,7 +85,7 @@ LL | } | error: you should consider adding a `Default` implementation for `BarGenerics` - --> $DIR/new_without_default.rs:172:5 + --> $DIR/new_without_default.rs:171:5 | LL | / pub fn new() -> Self { LL | | Self(Default::default()) From a988a90e112a68dd48222105f57556f97ea35fbd Mon Sep 17 00:00:00 2001 From: Fridtjof Stoldt Date: Mon, 10 May 2021 12:24:24 +0200 Subject: [PATCH 17/58] Update clippy_lints/src/deprecated_lints.rs Co-authored-by: Philipp Krones --- clippy_lints/src/deprecated_lints.rs | 2 +- clippy_lints/src/utils/internal_lints/metadata_collector.rs | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/clippy_lints/src/deprecated_lints.rs b/clippy_lints/src/deprecated_lints.rs index fa8add20688..50ffc2e3f19 100644 --- a/clippy_lints/src/deprecated_lints.rs +++ b/clippy_lints/src/deprecated_lints.rs @@ -1,5 +1,5 @@ /// This struct fakes the `Lint` declaration that is usually created by `declare_lint!`. This -/// enables the simple extraction of the metadata without changing the current depreciation +/// enables the simple extraction of the metadata without changing the current deprecation /// declaration. pub struct ClippyDeprecatedLint; diff --git a/clippy_lints/src/utils/internal_lints/metadata_collector.rs b/clippy_lints/src/utils/internal_lints/metadata_collector.rs index af734fa5572..fc4912ba52f 100644 --- a/clippy_lints/src/utils/internal_lints/metadata_collector.rs +++ b/clippy_lints/src/utils/internal_lints/metadata_collector.rs @@ -8,9 +8,6 @@ //! during any comparison or mapping. (Please take care of this, it's not fun to spend time on such //! a simple mistake) -// # NITs -// - TODO xFrednet 2021-02-13: Collect depreciations and maybe renames - use if_chain::if_chain; use rustc_data_structures::fx::FxHashMap; use rustc_hir::{ From 5ba236f3039241077302fddd7acb671e62195af6 Mon Sep 17 00:00:00 2001 From: Arya Kumar Date: Tue, 11 May 2021 19:34:14 +0000 Subject: [PATCH 18/58] added `needless_bitwise_bool` lint --- CHANGELOG.md | 1 + clippy_lints/src/lib.rs | 4 ++ clippy_lints/src/needless_bitwise_bool.rs | 84 +++++++++++++++++++++++ tests/ui/needless_bitwise_bool.fixed | 40 +++++++++++ tests/ui/needless_bitwise_bool.rs | 40 +++++++++++ tests/ui/needless_bitwise_bool.stderr | 10 +++ 6 files changed, 179 insertions(+) create mode 100644 clippy_lints/src/needless_bitwise_bool.rs create mode 100644 tests/ui/needless_bitwise_bool.fixed create mode 100644 tests/ui/needless_bitwise_bool.rs create mode 100644 tests/ui/needless_bitwise_bool.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a328a20dd4..3645f27427d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2549,6 +2549,7 @@ Released 2018-09-13 [`mutex_integer`]: https://rust-lang.github.io/rust-clippy/master/index.html#mutex_integer [`naive_bytecount`]: https://rust-lang.github.io/rust-clippy/master/index.html#naive_bytecount [`needless_arbitrary_self_type`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_arbitrary_self_type +[`needless_bitwise_bool`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_bitwise_bool [`needless_bool`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_bool [`needless_borrow`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrow [`needless_borrowed_reference`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_borrowed_reference diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 783bef73214..5b5fc452771 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -288,6 +288,7 @@ mod mut_reference; mod mutable_debug_assertion; mod mutex_atomic; mod needless_arbitrary_self_type; +mod needless_bitwise_bool; mod needless_bool; mod needless_borrow; mod needless_borrowed_ref; @@ -833,6 +834,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: mutex_atomic::MUTEX_ATOMIC, mutex_atomic::MUTEX_INTEGER, needless_arbitrary_self_type::NEEDLESS_ARBITRARY_SELF_TYPE, + needless_bitwise_bool::NEEDLESS_BITWISE_BOOL, needless_bool::BOOL_COMPARISON, needless_bool::NEEDLESS_BOOL, needless_borrow::NEEDLESS_BORROW, @@ -1018,6 +1020,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: let type_complexity_threshold = conf.type_complexity_threshold; store.register_late_pass(move || box types::Types::new(vec_box_size_threshold, type_complexity_threshold)); store.register_late_pass(|| box booleans::NonminimalBool); + store.register_late_pass(|| box needless_bitwise_bool::NeedlessBitwiseBool); store.register_late_pass(|| box eq_op::EqOp); store.register_late_pass(|| box enum_clike::UnportableVariant); store.register_late_pass(|| box float_literal::FloatLiteral); @@ -1392,6 +1395,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(misc::USED_UNDERSCORE_BINDING), LintId::of(misc_early::UNSEPARATED_LITERAL_SUFFIX), LintId::of(mut_mut::MUT_MUT), + LintId::of(needless_bitwise_bool::NEEDLESS_BITWISE_BOOL), LintId::of(needless_continue::NEEDLESS_CONTINUE), LintId::of(needless_for_each::NEEDLESS_FOR_EACH), LintId::of(needless_pass_by_value::NEEDLESS_PASS_BY_VALUE), diff --git a/clippy_lints/src/needless_bitwise_bool.rs b/clippy_lints/src/needless_bitwise_bool.rs new file mode 100644 index 00000000000..95febf4a2ad --- /dev/null +++ b/clippy_lints/src/needless_bitwise_bool.rs @@ -0,0 +1,84 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::in_macro; +use clippy_utils::source::snippet_opt; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// **What it does:** + /// Checks for uses of bitwise and/or operators between booleans, where performance may be improved by using + /// a lazy and. + /// + /// **Why is this bad?** + /// The bitwise operators do not support short-circuiting, so it may hinder code performance. + /// Additionally, boolean logic "masked" as bitwise logic is not caught by lints like `unnecessary_fold` + /// + /// **Known problems:** + /// This lint evaluates only when the right side is determined to have no side effects. At this time, that + /// determination is quite conservative. + /// + /// **Example:** + /// + /// ```rust + /// if x & !y {} // where both x and y are booleans + /// ``` + /// Use instead: + /// ```rust + /// if x && !y {} + /// ``` + pub NEEDLESS_BITWISE_BOOL, + pedantic, + "Boolean expressions that use bitwise rather than lazy operators" +} + +declare_lint_pass!(NeedlessBitwiseBool => [NEEDLESS_BITWISE_BOOL]); + +fn is_bitwise_operation(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + let ty = cx.typeck_results().expr_ty(expr); + if_chain! { + if !in_macro(expr.span); + if let (&ExprKind::Binary(ref op, _, right), &ty::Bool) = (&expr.kind, &ty.kind()); + if op.node == BinOpKind::BitAnd || op.node == BinOpKind::BitOr; + if let ExprKind::Call(..) | ExprKind::MethodCall(..) | ExprKind::Binary(..) | ExprKind::Unary(..) = right.kind; + if !right.can_have_side_effects(); + then { + return true; + } + } + false +} + +fn suggession_snippet(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option { + if let ExprKind::Binary(ref op, left, right) = expr.kind { + if let (Some(l_snippet), Some(r_snippet)) = (snippet_opt(cx, left.span), snippet_opt(cx, right.span)) { + let op_snippet = match op.node { + BinOpKind::BitAnd => "&&", + _ => "||", + }; + return Some(format!("{} {} {}", l_snippet, op_snippet, r_snippet)); + } + } + None +} + +impl LateLintPass<'_> for NeedlessBitwiseBool { + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { + if is_bitwise_operation(cx, expr) { + span_lint_and_then( + cx, + NEEDLESS_BITWISE_BOOL, + expr.span, + "use of bitwise operator instead of lazy operator between booleans", + |diag| { + if let Some(sugg) = suggession_snippet(cx, expr) { + diag.span_suggestion(expr.span, "try", sugg, Applicability::MachineApplicable); + } + }, + ); + } + } +} diff --git a/tests/ui/needless_bitwise_bool.fixed b/tests/ui/needless_bitwise_bool.fixed new file mode 100644 index 00000000000..5e1ea663a10 --- /dev/null +++ b/tests/ui/needless_bitwise_bool.fixed @@ -0,0 +1,40 @@ +// run-rustfix + +#![warn(clippy::needless_bitwise_bool)] + +fn returns_bool() -> bool { + true +} + +const fn const_returns_bool() -> bool { + false +} + +fn main() { + let (x, y) = (false, true); + if x & y { + println!("true") + } + if returns_bool() & x { + println!("true") + } + if !returns_bool() & returns_bool() { + println!("true") + } + if y && !x { + println!("true") + } + + // BELOW: lints we hope to catch as `Expr::can_have_side_effects` improves. + if y & !const_returns_bool() { + println!("true") // This is a const function, in an UnOp + } + + if y & "abcD".is_empty() { + println!("true") // This is a const method call + } + + if y & (0 < 1) { + println!("true") // This is a BinOp with no side effects + } +} diff --git a/tests/ui/needless_bitwise_bool.rs b/tests/ui/needless_bitwise_bool.rs new file mode 100644 index 00000000000..f3075fba0a2 --- /dev/null +++ b/tests/ui/needless_bitwise_bool.rs @@ -0,0 +1,40 @@ +// run-rustfix + +#![warn(clippy::needless_bitwise_bool)] + +fn returns_bool() -> bool { + true +} + +const fn const_returns_bool() -> bool { + false +} + +fn main() { + let (x, y) = (false, true); + if x & y { + println!("true") + } + if returns_bool() & x { + println!("true") + } + if !returns_bool() & returns_bool() { + println!("true") + } + if y & !x { + println!("true") + } + + // BELOW: lints we hope to catch as `Expr::can_have_side_effects` improves. + if y & !const_returns_bool() { + println!("true") // This is a const function, in an UnOp + } + + if y & "abcD".is_empty() { + println!("true") // This is a const method call + } + + if y & (0 < 1) { + println!("true") // This is a BinOp with no side effects + } +} diff --git a/tests/ui/needless_bitwise_bool.stderr b/tests/ui/needless_bitwise_bool.stderr new file mode 100644 index 00000000000..63c88ef63f5 --- /dev/null +++ b/tests/ui/needless_bitwise_bool.stderr @@ -0,0 +1,10 @@ +error: use of bitwise operator instead of lazy operator between booleans + --> $DIR/needless_bitwise_bool.rs:24:8 + | +LL | if y & !x { + | ^^^^^^ help: try: `y && !x` + | + = note: `-D clippy::needless-bitwise-bool` implied by `-D warnings` + +error: aborting due to previous error + From ab73020d400d4eaba228cdc5ec2f16338e755360 Mon Sep 17 00:00:00 2001 From: Aaron Hill Date: Sun, 2 Aug 2020 19:52:16 -0400 Subject: [PATCH 19/58] Implement span quoting for proc-macros This PR implements span quoting, allowing proc-macros to produce spans pointing *into their own crate*. This is used by the unstable `proc_macro::quote!` macro, allowing us to get error messages like this: ``` error[E0412]: cannot find type `MissingType` in this scope --> $DIR/auxiliary/span-from-proc-macro.rs:37:20 | LL | pub fn error_from_attribute(_args: TokenStream, _input: TokenStream) -> TokenStream { | ----------------------------------------------------------------------------------- in this expansion of procedural macro `#[error_from_attribute]` ... LL | field: MissingType | ^^^^^^^^^^^ not found in this scope | ::: $DIR/span-from-proc-macro.rs:8:1 | LL | #[error_from_attribute] | ----------------------- in this macro invocation ``` Here, `MissingType` occurs inside the implementation of the proc-macro `#[error_from_attribute]`. Previosuly, this would always result in a span pointing at `#[error_from_attribute]` This will make many proc-macro-related error message much more useful - when a proc-macro generates code containing an error, users will get an error message pointing directly at that code (within the macro definition), instead of always getting a span pointing at the macro invocation site. This is implemented as follows: * When a proc-macro crate is being *compiled*, it causes the `quote!` macro to get run. This saves all of the sapns in the input to `quote!` into the metadata of *the proc-macro-crate* (which we are currently compiling). The `quote!` macro then expands to a call to `proc_macro::Span::recover_proc_macro_span(id)`, where `id` is an opaque identifier for the span in the crate metadata. * When the same proc-macro crate is *run* (e.g. it is loaded from disk and invoked by some consumer crate), the call to `proc_macro::Span::recover_proc_macro_span` causes us to load the span from the proc-macro crate's metadata. The proc-macro then produces a `TokenStream` containing a `Span` pointing into the proc-macro crate itself. The recursive nature of 'quote!' can be difficult to understand at first. The file `src/test/ui/proc-macro/quote-debug.stdout` shows the output of the `quote!` macro, which should make this eaier to understand. This PR also supports custom quoting spans in custom quote macros (e.g. the `quote` crate). All span quoting goes through the `proc_macro::quote_span` method, which can be called by a custom quote macro to perform span quoting. An example of this usage is provided in `src/test/ui/proc-macro/auxiliary/custom-quote.rs` Custom quoting currently has a few limitations: In order to quote a span, we need to generate a call to `proc_macro::Span::recover_proc_macro_span`. However, proc-macros support renaming the `proc_macro` crate, so we can't simply hardcode this path. Previously, the `quote_span` method used the path `crate::Span` - however, this only works when it is called by the builtin `quote!` macro in the same crate. To support being called from arbitrary crates, we need access to the name of the `proc_macro` crate to generate a path. This PR adds an additional argument to `quote_span` to specify the name of the `proc_macro` crate. Howver, this feels kind of hacky, and we may want to change this before stabilizing anything quote-related. Additionally, using `quote_span` currently requires enabling the `proc_macro_internals` feature. The builtin `quote!` macro has an `#[allow_internal_unstable]` attribute, but this won't work for custom quote implementations. This will likely require some additional tricks to apply `allow_internal_unstable` to the span of `proc_macro::Span::recover_proc_macro_span`. --- clippy_lints/src/misc.rs | 2 +- clippy_lints/src/unit_types/unit_cmp.rs | 2 +- clippy_utils/src/lib.rs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/clippy_lints/src/misc.rs b/clippy_lints/src/misc.rs index 0b0cd9be46c..6966d798c53 100644 --- a/clippy_lints/src/misc.rs +++ b/clippy_lints/src/misc.rs @@ -660,7 +660,7 @@ fn in_attributes_expansion(expr: &Expr<'_>) -> bool { use rustc_span::hygiene::MacroKind; if expr.span.from_expansion() { let data = expr.span.ctxt().outer_expn_data(); - matches!(data.kind, ExpnKind::Macro(MacroKind::Attr, _)) + matches!(data.kind, ExpnKind::Macro { kind: MacroKind::Attr, name: _, proc_macro: _ }) } else { false } diff --git a/clippy_lints/src/unit_types/unit_cmp.rs b/clippy_lints/src/unit_types/unit_cmp.rs index 85257f3113c..d22f7d9a96b 100644 --- a/clippy_lints/src/unit_types/unit_cmp.rs +++ b/clippy_lints/src/unit_types/unit_cmp.rs @@ -8,7 +8,7 @@ use super::UNIT_CMP; pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) { if expr.span.from_expansion() { if let Some(callee) = expr.span.source_callee() { - if let ExpnKind::Macro(MacroKind::Bang, symbol) = callee.kind { + if let ExpnKind::Macro { kind: MacroKind::Bang, name: symbol, proc_macro: _ } = callee.kind { if let ExprKind::Binary(ref cmp, left, _) = expr.kind { let op = cmp.node; if op.is_comparison() && cx.typeck_results().expr_ty(left).is_unit() { diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index f5ee49c7d5f..9a0b72f06bb 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -947,7 +947,7 @@ pub fn is_expn_of(mut span: Span, name: &str) -> Option { let data = span.ctxt().outer_expn_data(); let new_span = data.call_site; - if let ExpnKind::Macro(MacroKind::Bang, mac_name) = data.kind { + if let ExpnKind::Macro { kind: MacroKind::Bang, name: mac_name, proc_macro: _ } = data.kind { if mac_name.as_str() == name { return Some(new_span); } @@ -975,7 +975,7 @@ pub fn is_direct_expn_of(span: Span, name: &str) -> Option { let data = span.ctxt().outer_expn_data(); let new_span = data.call_site; - if let ExpnKind::Macro(MacroKind::Bang, mac_name) = data.kind { + if let ExpnKind::Macro { kind: MacroKind::Bang, name: mac_name, proc_macro: _ } = data.kind { if mac_name.as_str() == name { return Some(new_span); } From c5a3f1c30633b09db91d7af82141a23641c8a70e Mon Sep 17 00:00:00 2001 From: Camille GILLOT Date: Tue, 11 May 2021 11:42:01 +0200 Subject: [PATCH 20/58] Use () for HIR queries. --- clippy_lints/src/derive.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/derive.rs b/clippy_lints/src/derive.rs index 647af3bdc04..e742cd626ab 100644 --- a/clippy_lints/src/derive.rs +++ b/clippy_lints/src/derive.rs @@ -12,7 +12,7 @@ use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::hir::map::Map; use rustc_middle::ty::{self, Ty}; use rustc_session::{declare_lint_pass, declare_tool_lint}; -use rustc_span::{def_id::LOCAL_CRATE, source_map::Span}; +use rustc_span::{source_map::Span}; declare_clippy_lint! { /// **What it does:** Checks for deriving `Hash` but implementing `PartialEq` @@ -312,7 +312,7 @@ fn check_copy_clone<'tcx>(cx: &LateContext<'tcx>, item: &Item<'_>, trait_ref: &T if ty_subs.non_erasable_generics().next().is_some() { let has_copy_impl = cx .tcx - .all_local_trait_impls(LOCAL_CRATE) + .all_local_trait_impls(()) .get(©_id) .map_or(false, |impls| { impls From b03b74734b35649a92500686347a8678b48746e5 Mon Sep 17 00:00:00 2001 From: Camille GILLOT Date: Tue, 11 May 2021 12:00:59 +0200 Subject: [PATCH 21/58] Use () for entry_fn. --- clippy_utils/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 9a0b72f06bb..9ac9500b4eb 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -60,7 +60,7 @@ use rustc_ast::ast::{self, Attribute, BorrowKind, LitKind}; use rustc_data_structures::fx::FxHashMap; use rustc_hir as hir; use rustc_hir::def::{DefKind, Res}; -use rustc_hir::def_id::{DefId, LOCAL_CRATE}; +use rustc_hir::def_id::DefId; use rustc_hir::intravisit::{self, walk_expr, ErasedMap, FnKind, NestedVisitorMap, Visitor}; use rustc_hir::LangItem::{ResultErr, ResultOk}; use rustc_hir::{ @@ -677,7 +677,7 @@ pub fn method_chain_args<'a>(expr: &'a Expr<'_>, methods: &[&str]) -> Option, def_id: DefId) -> bool { cx.tcx - .entry_fn(LOCAL_CRATE) + .entry_fn(()) .map_or(false, |(entry_fn_def_id, _)| def_id == entry_fn_def_id) } From 44c1ef879773fa7893529bcd18f9024bc91bb8f6 Mon Sep 17 00:00:00 2001 From: Camille GILLOT Date: Tue, 11 May 2021 13:39:19 +0200 Subject: [PATCH 22/58] Use () for inherent_impls. --- clippy_lints/src/inherent_impl.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clippy_lints/src/inherent_impl.rs b/clippy_lints/src/inherent_impl.rs index c31013e49be..4e0b1ae78df 100644 --- a/clippy_lints/src/inherent_impl.rs +++ b/clippy_lints/src/inherent_impl.rs @@ -3,7 +3,7 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::in_macro; use rustc_hir::def_id::DefIdMap; -use rustc_hir::{def_id, Crate, Impl, Item, ItemKind}; +use rustc_hir::{Crate, Impl, Item, ItemKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::Span; @@ -68,7 +68,7 @@ impl<'tcx> LateLintPass<'tcx> for MultipleInherentImpl { fn check_crate_post(&mut self, cx: &LateContext<'tcx>, krate: &'tcx Crate<'_>) { if !krate.items.is_empty() { // Retrieve all inherent implementations from the crate, grouped by type - for impls in cx.tcx.crate_inherent_impls(def_id::LOCAL_CRATE).inherent_impls.values() { + for impls in cx.tcx.crate_inherent_impls(()).inherent_impls.values() { // Filter out implementations that have generic params (type or lifetime) let mut impl_spans = impls.iter().filter_map(|impl_def| self.impls.get(impl_def)); if let Some(initial_span) = impl_spans.next() { From 210ec728e5d7f428b22b78ee721ed6507cc6f925 Mon Sep 17 00:00:00 2001 From: xFrednet Date: Tue, 11 May 2021 20:23:52 +0200 Subject: [PATCH 23/58] Metadata collection monster searching for configurations --- clippy_lints/src/lib.rs | 2 +- clippy_lints/src/utils/conf.rs | 24 ++++- .../internal_lints/metadata_collector.rs | 88 ++++++++++++++++++- 3 files changed, 109 insertions(+), 5 deletions(-) diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 0c86441cf3f..f0fae6ee1c7 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -1009,7 +1009,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: #[cfg(feature = "metadata-collector-lint")] { if std::env::var("ENABLE_METADATA_COLLECTION").eq(&Ok("1".to_string())) { - store.register_late_pass(|| box utils::internal_lints::metadata_collector::MetadataCollector::default()); + store.register_late_pass(|| box utils::internal_lints::metadata_collector::MetadataCollector::new()); } } diff --git a/clippy_lints/src/utils/conf.rs b/clippy_lints/src/utils/conf.rs index 52c1dc3bdd2..98b86f73a1f 100644 --- a/clippy_lints/src/utils/conf.rs +++ b/clippy_lints/src/utils/conf.rs @@ -26,13 +26,13 @@ impl TryConf { macro_rules! define_Conf { ($( - #[$doc:meta] + #[doc = $doc:literal] $(#[conf_deprecated($dep:literal)])? ($name:ident: $ty:ty = $default:expr), )*) => { /// Clippy lint configuration pub struct Conf { - $(#[$doc] pub $name: $ty,)* + $(#[doc = $doc] pub $name: $ty,)* } mod defaults { @@ -89,6 +89,24 @@ macro_rules! define_Conf { Ok(TryConf { conf, errors }) } } + + #[cfg(feature = "metadata-collector-lint")] + pub mod metadata { + use crate::utils::internal_lints::metadata_collector::ClippyConfigurationBasicInfo; + + pub(crate) fn get_configuration_metadata() -> Vec { + vec![ + $( + ClippyConfigurationBasicInfo { + name: stringify!($name), + config_type: stringify!($ty), + default: stringify!($default), + doc_comment: $doc, + }, + )+ + ] + } + } }; } @@ -100,7 +118,7 @@ define_Conf! { (blacklisted_names: Vec = ["foo", "baz", "quux"].iter().map(ToString::to_string).collect()), /// Lint: COGNITIVE_COMPLEXITY. The maximum cognitive complexity a function can have (cognitive_complexity_threshold: u64 = 25), - /// DEPRECATED LINT: CYCLOMATIC_COMPLEXITY. Use the Cognitive Complexity lint instead. + /// Lint: CYCLOMATIC_COMPLEXITY. Use the Cognitive Complexity lint instead. #[conf_deprecated("Please use `cognitive-complexity-threshold` instead")] (cyclomatic_complexity_threshold: Option = None), /// Lint: DOC_MARKDOWN. The list of words this lint should not consider as identifiers needing ticks diff --git a/clippy_lints/src/utils/internal_lints/metadata_collector.rs b/clippy_lints/src/utils/internal_lints/metadata_collector.rs index fc4912ba52f..308f61beec3 100644 --- a/clippy_lints/src/utils/internal_lints/metadata_collector.rs +++ b/clippy_lints/src/utils/internal_lints/metadata_collector.rs @@ -102,13 +102,24 @@ declare_clippy_lint! { impl_lint_pass!(MetadataCollector => [INTERNAL_METADATA_COLLECTOR]); #[allow(clippy::module_name_repetitions)] -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone)] pub struct MetadataCollector { /// All collected lints /// /// We use a Heap here to have the lints added in alphabetic order in the export lints: BinaryHeap, applicability_info: FxHashMap, + config: Vec, +} + +impl MetadataCollector { + pub fn new() -> Self { + Self { + lints: BinaryHeap::::default(), + applicability_info: FxHashMap::::default(), + config: collect_configs(), + } + } } impl Drop for MetadataCollector { @@ -214,6 +225,81 @@ impl Serialize for ApplicabilityInfo { } } +#[derive(Debug)] +pub(crate) struct ClippyConfigurationBasicInfo { + pub name: &'static str, + pub config_type: &'static str, + pub default: &'static str, + pub doc_comment: &'static str, +} + +#[derive(Debug, Clone, Default)] +struct ClippyConfiguration { + name: String, + lints: Vec, + doc: String, + config_type: &'static str, + default: String, +} + +// ================================================================== +// Configuration +// ================================================================== +fn collect_configs() -> Vec { + let cons = crate::utils::conf::metadata::get_configuration_metadata(); + cons.iter() + .map(move |x| { + let (lints, doc) = parse_config_field_doc(x.doc_comment) + .unwrap_or_else(|| (vec![], "[ERROR] MALFORMED DOC COMMENT".to_string())); + + ClippyConfiguration { + name: to_kebab(x.name), + lints, + doc, + config_type: x.config_type, + default: x.default.to_string(), + } + }) + .collect() +} + +/// This parses the field documentation of the config struct. +/// +/// ```rust, ignore +/// parse_config_field_doc(cx, "Lint: LINT_NAME_1, LINT_NAME_2. Papa penguin, papa penguin") +/// ``` +/// +/// Would yield: +/// ```rust, ignore +/// Some(["lint_name_1", "lint_name_2"], "Papa penguin, papa penguin") +/// ``` +fn parse_config_field_doc(doc_comment: &str) -> Option<(Vec, String)> { + const DOC_START: &str = " Lint: "; + if_chain! { + if doc_comment.starts_with(DOC_START); + if let Some(split_pos) = doc_comment.find('.'); + then { + let mut doc_comment = doc_comment.to_string(); + let documentation = doc_comment.split_off(split_pos); + + doc_comment.make_ascii_lowercase(); + let lints: Vec = doc_comment.split_off(DOC_START.len()).split(", ").map(str::to_string).collect(); + + Some((lints, documentation)) + } else { + None + } + } +} + +/// Transforms a given `snake_case_string` to a tasty `kebab-case-string` +fn to_kebab(config_name: &str) -> String { + config_name.replace('_', "-") +} + +// ================================================================== +// Lint pass +// ================================================================== impl<'hir> LateLintPass<'hir> for MetadataCollector { /// Collecting lint declarations like: /// ```rust, ignore From 88ae2d1155c48d1c2b0dc4d049f0411778a94035 Mon Sep 17 00:00:00 2001 From: xFrednet Date: Tue, 11 May 2021 21:47:10 +0200 Subject: [PATCH 24/58] Metadata formatting the configuration section --- .../internal_lints/metadata_collector.rs | 58 +++++++++++++++++-- 1 file changed, 54 insertions(+), 4 deletions(-) diff --git a/clippy_lints/src/utils/internal_lints/metadata_collector.rs b/clippy_lints/src/utils/internal_lints/metadata_collector.rs index 308f61beec3..bb1c8ae9585 100644 --- a/clippy_lints/src/utils/internal_lints/metadata_collector.rs +++ b/clippy_lints/src/utils/internal_lints/metadata_collector.rs @@ -19,6 +19,7 @@ use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::{sym, Loc, Span, Symbol}; use serde::{ser::SerializeStruct, Serialize, Serializer}; use std::collections::BinaryHeap; +use std::fmt; use std::fs::{self, OpenOptions}; use std::io::prelude::*; use std::path::Path; @@ -41,6 +42,30 @@ const EXCLUDED_LINT_GROUPS: [&str; 1] = ["clippy::internal"]; /// Collected deprecated lint will be assigned to this group in the JSON output const DEPRECATED_LINT_GROUP_STR: &str = "DEPRECATED"; +/// This template will be used to format the configuration section in the lint documentation. +/// The `configurations` parameter will be replaced with one or multiple formatted +/// `ClippyConfiguration` instances. See `CONFIGURATION_VALUE_TEMPLATE` for further customizations +macro_rules! CONFIGURATION_SECTION_TEMPLATE { + () => { + r#" +**Configuration** +This lint has the following configuration variables: + +{configurations} +"# + }; +} +/// This template will be used to format an individual `ClippyConfiguration` instance in the +/// lint documentation. +/// +/// The format function will provide strings for the following parameters: `name`, `ty`, `doc` and +/// `default` +macro_rules! CONFIGURATION_VALUE_TEMPLATE { + () => { + "* {name}: {ty}: {doc} (defaults to `{default}`)\n" + }; +} + const LINT_EMISSION_FUNCTIONS: [&[&str]; 7] = [ &["clippy_utils", "diagnostics", "span_lint"], &["clippy_utils", "diagnostics", "span_lint_and_help"], @@ -120,6 +145,14 @@ impl MetadataCollector { config: collect_configs(), } } + + fn get_lint_configs(&self, lint_name: &str) -> Option { + self.config + .iter() + .filter_map(|x| x.lints.iter().any(|x| x == lint_name).then(|| format!("{}", x))) + .reduce(|acc, x| acc + &x) + .map(|configurations| format!(CONFIGURATION_SECTION_TEMPLATE!(), configurations = configurations)) + } } impl Drop for MetadataCollector { @@ -225,6 +258,9 @@ impl Serialize for ApplicabilityInfo { } } +// ================================================================== +// Configuration +// ================================================================== #[derive(Debug)] pub(crate) struct ClippyConfigurationBasicInfo { pub name: &'static str, @@ -242,9 +278,6 @@ struct ClippyConfiguration { default: String, } -// ================================================================== -// Configuration -// ================================================================== fn collect_configs() -> Vec { let cons = crate::utils::conf::metadata::get_configuration_metadata(); cons.iter() @@ -297,6 +330,19 @@ fn to_kebab(config_name: &str) -> String { config_name.replace('_', "-") } +impl fmt::Display for ClippyConfiguration { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + CONFIGURATION_VALUE_TEMPLATE!(), + name = self.name, + ty = self.config_type, + doc = self.doc, + default = self.default + ) + } +} + // ================================================================== // Lint pass // ================================================================== @@ -321,8 +367,12 @@ impl<'hir> LateLintPass<'hir> for MetadataCollector { if !BLACK_LISTED_LINTS.contains(&lint_name.as_str()); // metadata extraction if let Some(group) = get_lint_group_or_lint(cx, &lint_name, item); - if let Some(docs) = extract_attr_docs_or_lint(cx, item); + if let Some(mut docs) = extract_attr_docs_or_lint(cx, item); then { + if let Some(configuration_section) = self.get_lint_configs(&lint_name) { + docs.push_str(&configuration_section); + } + self.lints.push(LintMetadata::new( lint_name, SerializableSpan::from_item(cx, item), From b03642e51f4c1cfc51858dd792aa689b0e6597d7 Mon Sep 17 00:00:00 2001 From: xFrednet Date: Wed, 12 May 2021 17:30:04 +0200 Subject: [PATCH 25/58] Metadata collection clarifying default configuration values --- .../src/utils/internal_lints/metadata_collector.rs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/clippy_lints/src/utils/internal_lints/metadata_collector.rs b/clippy_lints/src/utils/internal_lints/metadata_collector.rs index bb1c8ae9585..4a8a46be041 100644 --- a/clippy_lints/src/utils/internal_lints/metadata_collector.rs +++ b/clippy_lints/src/utils/internal_lints/metadata_collector.rs @@ -290,12 +290,22 @@ fn collect_configs() -> Vec { lints, doc, config_type: x.config_type, - default: x.default.to_string(), + default: clarify_default(x.default), } }) .collect() } +fn clarify_default(default: &'static str) -> String { + if let Some((_start, init)) = default.split_once('[') { + if let Some((init, _end)) = init.split_once(']') { + return format!("[{}]", init); + } + } + + default.to_string() +} + /// This parses the field documentation of the config struct. /// /// ```rust, ignore From b740a04dc49b32185e69a2128bcb8425d82b0c66 Mon Sep 17 00:00:00 2001 From: xFrednet Date: Wed, 12 May 2021 18:47:32 +0200 Subject: [PATCH 26/58] Metadata collection collecting configuration deprecation reason --- clippy_lints/src/utils/conf.rs | 21 +++++++++++++------ .../internal_lints/metadata_collector.rs | 7 +++++-- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/clippy_lints/src/utils/conf.rs b/clippy_lints/src/utils/conf.rs index 98b86f73a1f..25163bb9f37 100644 --- a/clippy_lints/src/utils/conf.rs +++ b/clippy_lints/src/utils/conf.rs @@ -97,11 +97,20 @@ macro_rules! define_Conf { pub(crate) fn get_configuration_metadata() -> Vec { vec![ $( - ClippyConfigurationBasicInfo { - name: stringify!($name), - config_type: stringify!($ty), - default: stringify!($default), - doc_comment: $doc, + { + #[allow(unused_mut, unused_assignments)] + let mut deprecation_reason = None; + + // only set if a deprecation reason was set + $(deprecation_reason = Some(stringify!($dep));)? + + ClippyConfigurationBasicInfo { + name: stringify!($name), + config_type: stringify!($ty), + default: stringify!($default), + doc_comment: $doc, + deprecation_reason, + } }, )+ ] @@ -118,7 +127,7 @@ define_Conf! { (blacklisted_names: Vec = ["foo", "baz", "quux"].iter().map(ToString::to_string).collect()), /// Lint: COGNITIVE_COMPLEXITY. The maximum cognitive complexity a function can have (cognitive_complexity_threshold: u64 = 25), - /// Lint: CYCLOMATIC_COMPLEXITY. Use the Cognitive Complexity lint instead. + /// DEPRECATED LINT: CYCLOMATIC_COMPLEXITY. Use the Cognitive Complexity lint instead. #[conf_deprecated("Please use `cognitive-complexity-threshold` instead")] (cyclomatic_complexity_threshold: Option = None), /// Lint: DOC_MARKDOWN. The list of words this lint should not consider as identifiers needing ticks diff --git a/clippy_lints/src/utils/internal_lints/metadata_collector.rs b/clippy_lints/src/utils/internal_lints/metadata_collector.rs index 4a8a46be041..bd0de8ad034 100644 --- a/clippy_lints/src/utils/internal_lints/metadata_collector.rs +++ b/clippy_lints/src/utils/internal_lints/metadata_collector.rs @@ -267,6 +267,7 @@ pub(crate) struct ClippyConfigurationBasicInfo { pub config_type: &'static str, pub default: &'static str, pub doc_comment: &'static str, + pub deprecation_reason: Option<&'static str>, } #[derive(Debug, Clone, Default)] @@ -276,6 +277,7 @@ struct ClippyConfiguration { doc: String, config_type: &'static str, default: String, + deprecation_reason: Option<&'static str>, } fn collect_configs() -> Vec { @@ -291,18 +293,19 @@ fn collect_configs() -> Vec { doc, config_type: x.config_type, default: clarify_default(x.default), + deprecation_reason: x.deprecation_reason, } }) .collect() } fn clarify_default(default: &'static str) -> String { - if let Some((_start, init)) = default.split_once('[') { + if let Some((_start, init)) = default.split_once('[') { if let Some((init, _end)) = init.split_once(']') { return format!("[{}]", init); } } - + default.to_string() } From 4698b366c4e213b6a97339fcfc997f686ccba390 Mon Sep 17 00:00:00 2001 From: Aaron Hill Date: Sat, 13 Feb 2021 14:52:25 -0500 Subject: [PATCH 27/58] Show macro name in 'this error originates in macro' message When there are multiple macros in use, it can be difficult to tell which one was responsible for producing an error. --- tests/ui-internal/default_lint.stderr | 2 +- tests/ui-internal/if_chain_style.stderr | 2 +- .../ui-internal/lint_without_lint_pass.stderr | 2 +- tests/ui/assertions_on_constants.stderr | 18 +++++++------- .../checked_unwrap/simple_conditionals.stderr | 2 +- tests/ui/collapsible_match2.stderr | 2 +- tests/ui/crashes/ice-6255.stderr | 2 +- .../others.stderr | 2 +- .../traits.stderr | 2 +- tests/ui/deref_addrof.stderr | 4 ++-- tests/ui/derive_hash_xor_eq.stderr | 8 +++---- tests/ui/derive_ord_xor_partial_ord.stderr | 8 +++---- tests/ui/doc_unsafe.stderr | 2 +- tests/ui/eq_op_macros.stderr | 8 +++---- tests/ui/fallible_impl_from.stderr | 6 ++--- tests/ui/implicit_hasher.stderr | 6 ++--- tests/ui/item_after_statement.stderr | 2 +- tests/ui/match_same_arms2.stderr | 4 ++-- tests/ui/mem_discriminant.stderr | 2 +- tests/ui/mem_replace_macro.stderr | 2 +- tests/ui/missing_panics_doc.stderr | 12 +++++----- tests/ui/mut_mut.stderr | 2 +- tests/ui/needless_question_mark.stderr | 2 +- tests/ui/option_env_unwrap.stderr | 8 +++---- tests/ui/panic_in_result_fn.stderr | 12 +++++----- tests/ui/panic_in_result_fn_assertions.stderr | 6 ++--- tests/ui/panicking_macros.stderr | 24 +++++++++---------- tests/ui/pattern_type_mismatch/syntax.stderr | 2 +- tests/ui/toplevel_ref_arg.stderr | 2 +- tests/ui/toplevel_ref_arg_non_rustfix.stderr | 2 +- tests/ui/try_err.stderr | 4 ++-- tests/ui/unit_cmp.stderr | 8 +++---- tests/ui/unsafe_derive_deserialize.stderr | 8 +++---- tests/ui/unseparated_prefix_literals.stderr | 2 +- 34 files changed, 90 insertions(+), 90 deletions(-) diff --git a/tests/ui-internal/default_lint.stderr b/tests/ui-internal/default_lint.stderr index 5c5836a7d29..4735573a47d 100644 --- a/tests/ui-internal/default_lint.stderr +++ b/tests/ui-internal/default_lint.stderr @@ -15,7 +15,7 @@ note: the lint level is defined here LL | #![deny(clippy::internal)] | ^^^^^^^^^^^^^^^^ = note: `#[deny(clippy::default_lint)]` implied by `#[deny(clippy::internal)]` - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::declare_tool_lint` (in Nightly builds, run with -Z macro-backtrace for more info) error: aborting due to previous error diff --git a/tests/ui-internal/if_chain_style.stderr b/tests/ui-internal/if_chain_style.stderr index b53c3ea05da..d0f100f0069 100644 --- a/tests/ui-internal/if_chain_style.stderr +++ b/tests/ui-internal/if_chain_style.stderr @@ -56,7 +56,7 @@ LL | | } LL | | } | |_____^ | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `__if_chain` (in Nightly builds, run with -Z macro-backtrace for more info) error: `let` expression should be above the `if_chain!` --> $DIR/if_chain_style.rs:40:9 diff --git a/tests/ui-internal/lint_without_lint_pass.stderr b/tests/ui-internal/lint_without_lint_pass.stderr index 1257dae96d7..e308e13da13 100644 --- a/tests/ui-internal/lint_without_lint_pass.stderr +++ b/tests/ui-internal/lint_without_lint_pass.stderr @@ -15,7 +15,7 @@ note: the lint level is defined here LL | #![deny(clippy::internal)] | ^^^^^^^^^^^^^^^^ = note: `#[deny(clippy::lint_without_lint_pass)]` implied by `#[deny(clippy::internal)]` - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `declare_tool_lint` (in Nightly builds, run with -Z macro-backtrace for more info) error: aborting due to previous error diff --git a/tests/ui/assertions_on_constants.stderr b/tests/ui/assertions_on_constants.stderr index c66fdf093f5..1eb87d89fad 100644 --- a/tests/ui/assertions_on_constants.stderr +++ b/tests/ui/assertions_on_constants.stderr @@ -6,7 +6,7 @@ LL | assert!(true); | = note: `-D clippy::assertions-on-constants` implied by `-D warnings` = help: remove it - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `assert` (in Nightly builds, run with -Z macro-backtrace for more info) error: `assert!(false)` should probably be replaced --> $DIR/assertions_on_constants.rs:12:5 @@ -15,7 +15,7 @@ LL | assert!(false); | ^^^^^^^^^^^^^^^ | = help: use `panic!()` or `unreachable!()` - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `assert` (in Nightly builds, run with -Z macro-backtrace for more info) error: `assert!(true)` will be optimized out by the compiler --> $DIR/assertions_on_constants.rs:13:5 @@ -24,7 +24,7 @@ LL | assert!(true, "true message"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: remove it - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `assert` (in Nightly builds, run with -Z macro-backtrace for more info) error: `assert!(false, "false message")` should probably be replaced --> $DIR/assertions_on_constants.rs:14:5 @@ -33,7 +33,7 @@ LL | assert!(false, "false message"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: use `panic!("false message")` or `unreachable!("false message")` - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `assert` (in Nightly builds, run with -Z macro-backtrace for more info) error: `assert!(false, msg.to_uppercase())` should probably be replaced --> $DIR/assertions_on_constants.rs:17:5 @@ -42,7 +42,7 @@ LL | assert!(false, msg.to_uppercase()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: use `panic!(msg.to_uppercase())` or `unreachable!(msg.to_uppercase())` - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `assert` (in Nightly builds, run with -Z macro-backtrace for more info) error: `assert!(true)` will be optimized out by the compiler --> $DIR/assertions_on_constants.rs:20:5 @@ -51,7 +51,7 @@ LL | assert!(B); | ^^^^^^^^^^^ | = help: remove it - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `assert` (in Nightly builds, run with -Z macro-backtrace for more info) error: `assert!(false)` should probably be replaced --> $DIR/assertions_on_constants.rs:23:5 @@ -60,7 +60,7 @@ LL | assert!(C); | ^^^^^^^^^^^ | = help: use `panic!()` or `unreachable!()` - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `assert` (in Nightly builds, run with -Z macro-backtrace for more info) error: `assert!(false, "C message")` should probably be replaced --> $DIR/assertions_on_constants.rs:24:5 @@ -69,7 +69,7 @@ LL | assert!(C, "C message"); | ^^^^^^^^^^^^^^^^^^^^^^^^ | = help: use `panic!("C message")` or `unreachable!("C message")` - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `assert` (in Nightly builds, run with -Z macro-backtrace for more info) error: `debug_assert!(true)` will be optimized out by the compiler --> $DIR/assertions_on_constants.rs:26:5 @@ -78,7 +78,7 @@ LL | debug_assert!(true); | ^^^^^^^^^^^^^^^^^^^^ | = help: remove it - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::assert` (in Nightly builds, run with -Z macro-backtrace for more info) error: aborting due to 9 previous errors diff --git a/tests/ui/checked_unwrap/simple_conditionals.stderr b/tests/ui/checked_unwrap/simple_conditionals.stderr index 416ec1a01ab..a4bc058fe20 100644 --- a/tests/ui/checked_unwrap/simple_conditionals.stderr +++ b/tests/ui/checked_unwrap/simple_conditionals.stderr @@ -55,7 +55,7 @@ LL | $a.unwrap(); // unnecessary LL | m!(x); | ------ in this macro invocation | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `m` (in Nightly builds, run with -Z macro-backtrace for more info) error: you checked before that `unwrap()` cannot fail, instead of checking and unwrapping, it's better to use `if let` or `match` --> $DIR/simple_conditionals.rs:54:9 diff --git a/tests/ui/collapsible_match2.stderr b/tests/ui/collapsible_match2.stderr index ffef32d1fde..8975b2efbae 100644 --- a/tests/ui/collapsible_match2.stderr +++ b/tests/ui/collapsible_match2.stderr @@ -55,7 +55,7 @@ LL | mac!(res_opt => Ok(val), val => Some(n), foo(n)); | ^^^ ^^^^^^^ with this pattern | | | replace this binding - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `mac` (in Nightly builds, run with -Z macro-backtrace for more info) error: unnecessary nested match --> $DIR/collapsible_match2.rs:51:20 diff --git a/tests/ui/crashes/ice-6255.stderr b/tests/ui/crashes/ice-6255.stderr index d973ea1e23a..5dbf9d440dd 100644 --- a/tests/ui/crashes/ice-6255.stderr +++ b/tests/ui/crashes/ice-6255.stderr @@ -7,7 +7,7 @@ LL | extern crate std as core; LL | define_other_core!(); | --------------------- in this macro invocation | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `define_other_core` (in Nightly builds, run with -Z macro-backtrace for more info) error: aborting due to previous error diff --git a/tests/ui/declare_interior_mutable_const/others.stderr b/tests/ui/declare_interior_mutable_const/others.stderr index 6153c96edc4..7c9d705fa98 100644 --- a/tests/ui/declare_interior_mutable_const/others.stderr +++ b/tests/ui/declare_interior_mutable_const/others.stderr @@ -33,7 +33,7 @@ LL | const $name: $ty = $e; LL | declare_const!(_ONCE: Once = Once::new()); //~ ERROR interior mutable | ------------------------------------------ in this macro invocation | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `declare_const` (in Nightly builds, run with -Z macro-backtrace for more info) error: aborting due to 4 previous errors diff --git a/tests/ui/declare_interior_mutable_const/traits.stderr b/tests/ui/declare_interior_mutable_const/traits.stderr index bb77f39b62c..bed385b5273 100644 --- a/tests/ui/declare_interior_mutable_const/traits.stderr +++ b/tests/ui/declare_interior_mutable_const/traits.stderr @@ -15,7 +15,7 @@ LL | const $name: $ty = $e; LL | declare_const!(ANOTHER_ATOMIC: AtomicUsize = Self::ATOMIC); //~ ERROR interior mutable | ----------------------------------------------------------- in this macro invocation | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `declare_const` (in Nightly builds, run with -Z macro-backtrace for more info) error: a `const` item should never be interior mutable --> $DIR/traits.rs:43:5 diff --git a/tests/ui/deref_addrof.stderr b/tests/ui/deref_addrof.stderr index e85b30fa56e..1a14f31af8d 100644 --- a/tests/ui/deref_addrof.stderr +++ b/tests/ui/deref_addrof.stderr @@ -57,7 +57,7 @@ LL | *& $visitor LL | m!(self) | -------- in this macro invocation | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `m` (in Nightly builds, run with -Z macro-backtrace for more info) error: immediately dereferencing a reference --> $DIR/deref_addrof.rs:51:9 @@ -68,7 +68,7 @@ LL | *& mut $visitor LL | m_mut!(self) | ------------ in this macro invocation | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `m_mut` (in Nightly builds, run with -Z macro-backtrace for more info) error: aborting due to 10 previous errors diff --git a/tests/ui/derive_hash_xor_eq.stderr b/tests/ui/derive_hash_xor_eq.stderr index 2287a548fe4..b383072ca4d 100644 --- a/tests/ui/derive_hash_xor_eq.stderr +++ b/tests/ui/derive_hash_xor_eq.stderr @@ -14,7 +14,7 @@ LL | | true LL | | } LL | | } | |_^ - = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the derive macro `Hash` (in Nightly builds, run with -Z macro-backtrace for more info) error: you are deriving `Hash` but have implemented `PartialEq` explicitly --> $DIR/derive_hash_xor_eq.rs:19:10 @@ -31,7 +31,7 @@ LL | | true LL | | } LL | | } | |_^ - = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the derive macro `Hash` (in Nightly builds, run with -Z macro-backtrace for more info) error: you are implementing `Hash` explicitly but have derived `PartialEq` --> $DIR/derive_hash_xor_eq.rs:31:1 @@ -46,7 +46,7 @@ note: `PartialEq` implemented here | LL | #[derive(PartialEq)] | ^^^^^^^^^ - = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the derive macro `PartialEq` (in Nightly builds, run with -Z macro-backtrace for more info) error: you are implementing `Hash` explicitly but have derived `PartialEq` --> $DIR/derive_hash_xor_eq.rs:49:5 @@ -61,7 +61,7 @@ note: `PartialEq` implemented here | LL | #[derive(PartialEq)] | ^^^^^^^^^ - = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the derive macro `PartialEq` (in Nightly builds, run with -Z macro-backtrace for more info) error: aborting due to 4 previous errors diff --git a/tests/ui/derive_ord_xor_partial_ord.stderr b/tests/ui/derive_ord_xor_partial_ord.stderr index 97b46a4aa89..32896c99dad 100644 --- a/tests/ui/derive_ord_xor_partial_ord.stderr +++ b/tests/ui/derive_ord_xor_partial_ord.stderr @@ -14,7 +14,7 @@ LL | | Some(other.cmp(self)) LL | | } LL | | } | |_^ - = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the derive macro `Ord` (in Nightly builds, run with -Z macro-backtrace for more info) error: you are deriving `Ord` but have implemented `PartialOrd` explicitly --> $DIR/derive_ord_xor_partial_ord.rs:30:10 @@ -31,7 +31,7 @@ LL | | Some(other.cmp(self)) LL | | } LL | | } | |_^ - = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the derive macro `Ord` (in Nightly builds, run with -Z macro-backtrace for more info) error: you are implementing `Ord` explicitly but have derived `PartialOrd` --> $DIR/derive_ord_xor_partial_ord.rs:42:1 @@ -48,7 +48,7 @@ note: `PartialOrd` implemented here | LL | #[derive(PartialOrd, PartialEq, Eq)] | ^^^^^^^^^^ - = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the derive macro `PartialOrd` (in Nightly builds, run with -Z macro-backtrace for more info) error: you are implementing `Ord` explicitly but have derived `PartialOrd` --> $DIR/derive_ord_xor_partial_ord.rs:62:5 @@ -65,7 +65,7 @@ note: `PartialOrd` implemented here | LL | #[derive(PartialOrd, PartialEq, Eq)] | ^^^^^^^^^^ - = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the derive macro `PartialOrd` (in Nightly builds, run with -Z macro-backtrace for more info) error: aborting due to 4 previous errors diff --git a/tests/ui/doc_unsafe.stderr b/tests/ui/doc_unsafe.stderr index c784d41ba17..73b53f3431e 100644 --- a/tests/ui/doc_unsafe.stderr +++ b/tests/ui/doc_unsafe.stderr @@ -41,7 +41,7 @@ LL | | } LL | very_unsafe!(); | --------------- in this macro invocation | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `very_unsafe` (in Nightly builds, run with -Z macro-backtrace for more info) error: aborting due to 5 previous errors diff --git a/tests/ui/eq_op_macros.stderr b/tests/ui/eq_op_macros.stderr index fb9378108b9..a28961e7568 100644 --- a/tests/ui/eq_op_macros.stderr +++ b/tests/ui/eq_op_macros.stderr @@ -8,7 +8,7 @@ LL | assert_in_macro_def!(); | ----------------------- in this macro invocation | = note: `-D clippy::eq-op` implied by `-D warnings` - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `assert_in_macro_def` (in Nightly builds, run with -Z macro-backtrace for more info) error: identical args used in this `assert_ne!` macro call --> $DIR/eq_op_macros.rs:8:20 @@ -19,7 +19,7 @@ LL | assert_ne!(a, a); LL | assert_in_macro_def!(); | ----------------------- in this macro invocation | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `assert_in_macro_def` (in Nightly builds, run with -Z macro-backtrace for more info) error: identical args used in this `assert_eq!` macro call --> $DIR/eq_op_macros.rs:22:16 @@ -54,7 +54,7 @@ LL | debug_assert_eq!(a, a); LL | assert_in_macro_def!(); | ----------------------- in this macro invocation | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `assert_in_macro_def` (in Nightly builds, run with -Z macro-backtrace for more info) error: identical args used in this `debug_assert_ne!` macro call --> $DIR/eq_op_macros.rs:10:26 @@ -65,7 +65,7 @@ LL | debug_assert_ne!(a, a); LL | assert_in_macro_def!(); | ----------------------- in this macro invocation | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `assert_in_macro_def` (in Nightly builds, run with -Z macro-backtrace for more info) error: identical args used in this `debug_assert_eq!` macro call --> $DIR/eq_op_macros.rs:38:22 diff --git a/tests/ui/fallible_impl_from.stderr b/tests/ui/fallible_impl_from.stderr index a938d234fa0..64c8ea85727 100644 --- a/tests/ui/fallible_impl_from.stderr +++ b/tests/ui/fallible_impl_from.stderr @@ -38,7 +38,7 @@ note: potential failure(s) | LL | panic!(); | ^^^^^^^^^ - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) error: consider implementing `TryFrom` instead --> $DIR/fallible_impl_from.rs:35:1 @@ -65,7 +65,7 @@ LL | } else if s.parse::().unwrap() != 42 { | ^^^^^^^^^^^^^^^^^^^^^^^^^ LL | panic!("{:?}", s); | ^^^^^^^^^^^^^^^^^^ - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) error: consider implementing `TryFrom` instead --> $DIR/fallible_impl_from.rs:53:1 @@ -87,7 +87,7 @@ LL | if s.parse::().ok().unwrap() != 42 { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ LL | panic!("{:?}", s); | ^^^^^^^^^^^^^^^^^^ - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) error: aborting due to 4 previous errors diff --git a/tests/ui/implicit_hasher.stderr b/tests/ui/implicit_hasher.stderr index 2b06d661772..41ca6485c4c 100644 --- a/tests/ui/implicit_hasher.stderr +++ b/tests/ui/implicit_hasher.stderr @@ -109,7 +109,7 @@ LL | impl Foo for HashMap { LL | gen!(impl); | ----------- in this macro invocation | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `gen` (in Nightly builds, run with -Z macro-backtrace for more info) help: consider adding a type parameter | LL | impl Foo for HashMap { @@ -128,7 +128,7 @@ LL | pub fn $name(_map: &mut HashMap, _set: &mut HashSet) LL | gen!(fn bar); | ------------- in this macro invocation | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `gen` (in Nightly builds, run with -Z macro-backtrace for more info) help: consider adding a type parameter | LL | pub fn $name(_map: &mut HashMap, _set: &mut HashSet) {} @@ -143,7 +143,7 @@ LL | pub fn $name(_map: &mut HashMap, _set: &mut HashSet) LL | gen!(fn bar); | ------------- in this macro invocation | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `gen` (in Nightly builds, run with -Z macro-backtrace for more info) help: consider adding a type parameter | LL | pub fn $name(_map: &mut HashMap, _set: &mut HashSet) {} diff --git a/tests/ui/item_after_statement.stderr b/tests/ui/item_after_statement.stderr index 68a3c81b6a8..bcb163d4bc1 100644 --- a/tests/ui/item_after_statement.stderr +++ b/tests/ui/item_after_statement.stderr @@ -27,7 +27,7 @@ LL | | } LL | b!(); | ----- in this macro invocation | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `b` (in Nightly builds, run with -Z macro-backtrace for more info) error: aborting due to 3 previous errors diff --git a/tests/ui/match_same_arms2.stderr b/tests/ui/match_same_arms2.stderr index 95f9494cdc9..430021a0f7f 100644 --- a/tests/ui/match_same_arms2.stderr +++ b/tests/ui/match_same_arms2.stderr @@ -121,7 +121,7 @@ help: consider refactoring into `(Ok(x), Some(_)) | (Ok(_), Some(x))` | LL | (Ok(x), Some(_)) => println!("ok {}", x), | ^^^^^^^^^^^^^^^^ - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info) error: this `match` has identical arm bodies --> $DIR/match_same_arms2.rs:117:18 @@ -139,7 +139,7 @@ help: consider refactoring into `Ok(3) | Ok(_)` | LL | Ok(3) => println!("ok"), | ^^^^^ - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info) error: this `match` has identical arm bodies --> $DIR/match_same_arms2.rs:144:14 diff --git a/tests/ui/mem_discriminant.stderr b/tests/ui/mem_discriminant.stderr index 8d9810970ad..36a225b7594 100644 --- a/tests/ui/mem_discriminant.stderr +++ b/tests/ui/mem_discriminant.stderr @@ -72,7 +72,7 @@ LL | mem_discriminant_but_in_a_macro!(&rro); | | help: try dereferencing: `*rro` | in this macro invocation | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `mem_discriminant_but_in_a_macro` (in Nightly builds, run with -Z macro-backtrace for more info) error: calling `mem::discriminant` on non-enum type `&&&&&std::option::Option` --> $DIR/mem_discriminant.rs:34:5 diff --git a/tests/ui/mem_replace_macro.stderr b/tests/ui/mem_replace_macro.stderr index 4971a91050b..b4963acc455 100644 --- a/tests/ui/mem_replace_macro.stderr +++ b/tests/ui/mem_replace_macro.stderr @@ -8,7 +8,7 @@ LL | take!(s); | --------- in this macro invocation | = note: `-D clippy::mem-replace-with-default` implied by `-D warnings` - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `take` (in Nightly builds, run with -Z macro-backtrace for more info) error: aborting due to previous error diff --git a/tests/ui/missing_panics_doc.stderr b/tests/ui/missing_panics_doc.stderr index ba96a6a12b5..8d882cc6e0d 100644 --- a/tests/ui/missing_panics_doc.stderr +++ b/tests/ui/missing_panics_doc.stderr @@ -27,7 +27,7 @@ note: first possible panic found here | LL | panic!("This function panics") | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) error: docs for function which may panic missing `# Panics` section --> $DIR/missing_panics_doc.rs:18:1 @@ -42,7 +42,7 @@ note: first possible panic found here | LL | todo!() | ^^^^^^^ - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) error: docs for function which may panic missing `# Panics` section --> $DIR/missing_panics_doc.rs:23:1 @@ -61,7 +61,7 @@ note: first possible panic found here | LL | panic!() | ^^^^^^^^ - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) error: docs for function which may panic missing `# Panics` section --> $DIR/missing_panics_doc.rs:32:1 @@ -76,7 +76,7 @@ note: first possible panic found here | LL | if true { unreachable!() } else { panic!() } | ^^^^^^^^ - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) error: docs for function which may panic missing `# Panics` section --> $DIR/missing_panics_doc.rs:37:1 @@ -92,7 +92,7 @@ note: first possible panic found here | LL | assert_eq!(x, 0); | ^^^^^^^^^^^^^^^^^ - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `assert_eq` (in Nightly builds, run with -Z macro-backtrace for more info) error: docs for function which may panic missing `# Panics` section --> $DIR/missing_panics_doc.rs:43:1 @@ -108,7 +108,7 @@ note: first possible panic found here | LL | assert_ne!(x, 0); | ^^^^^^^^^^^^^^^^^ - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `assert_ne` (in Nightly builds, run with -Z macro-backtrace for more info) error: aborting due to 7 previous errors diff --git a/tests/ui/mut_mut.stderr b/tests/ui/mut_mut.stderr index 44e81422714..0fed6953cb8 100644 --- a/tests/ui/mut_mut.stderr +++ b/tests/ui/mut_mut.stderr @@ -21,7 +21,7 @@ LL | &mut $p LL | let mut z = mut_ptr!(&mut 3u32); | ------------------- in this macro invocation | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `mut_ptr` (in Nightly builds, run with -Z macro-backtrace for more info) error: this expression mutably borrows a mutable reference. Consider reborrowing --> $DIR/mut_mut.rs:22:21 diff --git a/tests/ui/needless_question_mark.stderr b/tests/ui/needless_question_mark.stderr index afd68d91e51..f1f05d1af3a 100644 --- a/tests/ui/needless_question_mark.stderr +++ b/tests/ui/needless_question_mark.stderr @@ -75,7 +75,7 @@ LL | || -> Option<_> { Some(Some($expr)?) }() LL | let _x = some_and_qmark_in_macro!(x?); | ---------------------------- in this macro invocation | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `some_and_qmark_in_macro` (in Nightly builds, run with -Z macro-backtrace for more info) error: aborting due to 12 previous errors diff --git a/tests/ui/option_env_unwrap.stderr b/tests/ui/option_env_unwrap.stderr index 8de9c8a9d29..e6a58b0b2b7 100644 --- a/tests/ui/option_env_unwrap.stderr +++ b/tests/ui/option_env_unwrap.stderr @@ -25,7 +25,7 @@ LL | let _ = option_env_unwrap!("PATH"); | -------------------------- in this macro invocation | = help: consider using the `env!` macro instead - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `option_env_unwrap` (in Nightly builds, run with -Z macro-backtrace for more info) error: this will panic at run-time if the environment variable doesn't exist at compile-time --> $DIR/option_env_unwrap.rs:12:9 @@ -37,7 +37,7 @@ LL | let _ = option_env_unwrap!("PATH", "environment variable PATH isn't set | ----------------------------------------------------------------- in this macro invocation | = help: consider using the `env!` macro instead - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `option_env_unwrap` (in Nightly builds, run with -Z macro-backtrace for more info) error: this will panic at run-time if the environment variable doesn't exist at compile-time --> $DIR/option_env_unwrap.rs:21:13 @@ -46,7 +46,7 @@ LL | let _ = option_env_unwrap_external!("PATH"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: consider using the `env!` macro instead - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `option_env_unwrap_external` (in Nightly builds, run with -Z macro-backtrace for more info) error: this will panic at run-time if the environment variable doesn't exist at compile-time --> $DIR/option_env_unwrap.rs:22:13 @@ -55,7 +55,7 @@ LL | let _ = option_env_unwrap_external!("PATH", "environment variable PATH | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = help: consider using the `env!` macro instead - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `option_env_unwrap_external` (in Nightly builds, run with -Z macro-backtrace for more info) error: aborting due to 6 previous errors diff --git a/tests/ui/panic_in_result_fn.stderr b/tests/ui/panic_in_result_fn.stderr index eb744b0c198..8d6e40c30a1 100644 --- a/tests/ui/panic_in_result_fn.stderr +++ b/tests/ui/panic_in_result_fn.stderr @@ -14,7 +14,7 @@ note: return Err() instead of panicking | LL | panic!("error"); | ^^^^^^^^^^^^^^^^ - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) error: used `unimplemented!()`, `unreachable!()`, `todo!()`, `panic!()` or assertion in a function that returns `Result` --> $DIR/panic_in_result_fn.rs:12:5 @@ -31,7 +31,7 @@ note: return Err() instead of panicking | LL | unimplemented!(); | ^^^^^^^^^^^^^^^^^ - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `unimplemented` (in Nightly builds, run with -Z macro-backtrace for more info) error: used `unimplemented!()`, `unreachable!()`, `todo!()`, `panic!()` or assertion in a function that returns `Result` --> $DIR/panic_in_result_fn.rs:17:5 @@ -48,7 +48,7 @@ note: return Err() instead of panicking | LL | unreachable!(); | ^^^^^^^^^^^^^^^ - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `unreachable` (in Nightly builds, run with -Z macro-backtrace for more info) error: used `unimplemented!()`, `unreachable!()`, `todo!()`, `panic!()` or assertion in a function that returns `Result` --> $DIR/panic_in_result_fn.rs:22:5 @@ -65,7 +65,7 @@ note: return Err() instead of panicking | LL | todo!("Finish this"); | ^^^^^^^^^^^^^^^^^^^^^ - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `todo` (in Nightly builds, run with -Z macro-backtrace for more info) error: used `unimplemented!()`, `unreachable!()`, `todo!()`, `panic!()` or assertion in a function that returns `Result` --> $DIR/panic_in_result_fn.rs:53:1 @@ -82,7 +82,7 @@ note: return Err() instead of panicking | LL | panic!("error"); | ^^^^^^^^^^^^^^^^ - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::panic::panic_2015` (in Nightly builds, run with -Z macro-backtrace for more info) error: used `unimplemented!()`, `unreachable!()`, `todo!()`, `panic!()` or assertion in a function that returns `Result` --> $DIR/panic_in_result_fn.rs:68:1 @@ -99,7 +99,7 @@ note: return Err() instead of panicking | LL | todo!("finish main method"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `todo` (in Nightly builds, run with -Z macro-backtrace for more info) error: aborting due to 6 previous errors diff --git a/tests/ui/panic_in_result_fn_assertions.stderr b/tests/ui/panic_in_result_fn_assertions.stderr index a17f043737d..4c39b37d879 100644 --- a/tests/ui/panic_in_result_fn_assertions.stderr +++ b/tests/ui/panic_in_result_fn_assertions.stderr @@ -15,7 +15,7 @@ note: return Err() instead of panicking | LL | assert!(x == 5, "wrong argument"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `assert` (in Nightly builds, run with -Z macro-backtrace for more info) error: used `unimplemented!()`, `unreachable!()`, `todo!()`, `panic!()` or assertion in a function that returns `Result` --> $DIR/panic_in_result_fn_assertions.rs:13:5 @@ -33,7 +33,7 @@ note: return Err() instead of panicking | LL | assert_eq!(x, 5); | ^^^^^^^^^^^^^^^^^ - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `assert_eq` (in Nightly builds, run with -Z macro-backtrace for more info) error: used `unimplemented!()`, `unreachable!()`, `todo!()`, `panic!()` or assertion in a function that returns `Result` --> $DIR/panic_in_result_fn_assertions.rs:19:5 @@ -51,7 +51,7 @@ note: return Err() instead of panicking | LL | assert_ne!(x, 1); | ^^^^^^^^^^^^^^^^^ - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `assert_ne` (in Nightly builds, run with -Z macro-backtrace for more info) error: aborting due to 3 previous errors diff --git a/tests/ui/panicking_macros.stderr b/tests/ui/panicking_macros.stderr index ffced49690e..2e83c305a67 100644 --- a/tests/ui/panicking_macros.stderr +++ b/tests/ui/panicking_macros.stderr @@ -25,7 +25,7 @@ LL | todo!(); | ^^^^^^^^ | = note: `-D clippy::todo` implied by `-D warnings` - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `todo` (in Nightly builds, run with -Z macro-backtrace for more info) error: `todo` should not be present in production code --> $DIR/panicking_macros.rs:17:5 @@ -33,7 +33,7 @@ error: `todo` should not be present in production code LL | todo!("message"); | ^^^^^^^^^^^^^^^^^ | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `todo` (in Nightly builds, run with -Z macro-backtrace for more info) error: `todo` should not be present in production code --> $DIR/panicking_macros.rs:18:5 @@ -41,7 +41,7 @@ error: `todo` should not be present in production code LL | todo!("{} {}", "panic with", "multiple arguments"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `todo` (in Nightly builds, run with -Z macro-backtrace for more info) error: `unimplemented` should not be present in production code --> $DIR/panicking_macros.rs:24:5 @@ -50,7 +50,7 @@ LL | unimplemented!(); | ^^^^^^^^^^^^^^^^^ | = note: `-D clippy::unimplemented` implied by `-D warnings` - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `unimplemented` (in Nightly builds, run with -Z macro-backtrace for more info) error: `unimplemented` should not be present in production code --> $DIR/panicking_macros.rs:25:5 @@ -58,7 +58,7 @@ error: `unimplemented` should not be present in production code LL | unimplemented!("message"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `unimplemented` (in Nightly builds, run with -Z macro-backtrace for more info) error: `unimplemented` should not be present in production code --> $DIR/panicking_macros.rs:26:5 @@ -66,7 +66,7 @@ error: `unimplemented` should not be present in production code LL | unimplemented!("{} {}", "panic with", "multiple arguments"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `unimplemented` (in Nightly builds, run with -Z macro-backtrace for more info) error: usage of the `unreachable!` macro --> $DIR/panicking_macros.rs:32:5 @@ -75,7 +75,7 @@ LL | unreachable!(); | ^^^^^^^^^^^^^^^ | = note: `-D clippy::unreachable` implied by `-D warnings` - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `unreachable` (in Nightly builds, run with -Z macro-backtrace for more info) error: usage of the `unreachable!` macro --> $DIR/panicking_macros.rs:33:5 @@ -83,7 +83,7 @@ error: usage of the `unreachable!` macro LL | unreachable!("message"); | ^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::unreachable` (in Nightly builds, run with -Z macro-backtrace for more info) error: usage of the `unreachable!` macro --> $DIR/panicking_macros.rs:34:5 @@ -91,7 +91,7 @@ error: usage of the `unreachable!` macro LL | unreachable!("{} {}", "panic with", "multiple arguments"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `unreachable` (in Nightly builds, run with -Z macro-backtrace for more info) error: `panic` should not be present in production code --> $DIR/panicking_macros.rs:40:5 @@ -105,7 +105,7 @@ error: `todo` should not be present in production code LL | todo!(); | ^^^^^^^^ | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `todo` (in Nightly builds, run with -Z macro-backtrace for more info) error: `unimplemented` should not be present in production code --> $DIR/panicking_macros.rs:42:5 @@ -113,7 +113,7 @@ error: `unimplemented` should not be present in production code LL | unimplemented!(); | ^^^^^^^^^^^^^^^^^ | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `unimplemented` (in Nightly builds, run with -Z macro-backtrace for more info) error: usage of the `unreachable!` macro --> $DIR/panicking_macros.rs:43:5 @@ -121,7 +121,7 @@ error: usage of the `unreachable!` macro LL | unreachable!(); | ^^^^^^^^^^^^^^^ | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `unreachable` (in Nightly builds, run with -Z macro-backtrace for more info) error: aborting due to 16 previous errors diff --git a/tests/ui/pattern_type_mismatch/syntax.stderr b/tests/ui/pattern_type_mismatch/syntax.stderr index 5a5186bd4fc..f309b273982 100644 --- a/tests/ui/pattern_type_mismatch/syntax.stderr +++ b/tests/ui/pattern_type_mismatch/syntax.stderr @@ -73,7 +73,7 @@ LL | matching_macro!(value); | ----------------------- in this macro invocation | = help: use `*` to dereference the match expression or explicitly match against a `&_` pattern and adjust the enclosed variable bindings - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `matching_macro` (in Nightly builds, run with -Z macro-backtrace for more info) error: aborting due to 9 previous errors diff --git a/tests/ui/toplevel_ref_arg.stderr b/tests/ui/toplevel_ref_arg.stderr index 15cb933fedc..48e7d9ddd5a 100644 --- a/tests/ui/toplevel_ref_arg.stderr +++ b/tests/ui/toplevel_ref_arg.stderr @@ -39,7 +39,7 @@ LL | let ref _y = 42; LL | gen_binding!(); | --------------- in this macro invocation | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `gen_binding` (in Nightly builds, run with -Z macro-backtrace for more info) error: aborting due to 6 previous errors diff --git a/tests/ui/toplevel_ref_arg_non_rustfix.stderr b/tests/ui/toplevel_ref_arg_non_rustfix.stderr index b8cfd987394..31f8c103ede 100644 --- a/tests/ui/toplevel_ref_arg_non_rustfix.stderr +++ b/tests/ui/toplevel_ref_arg_non_rustfix.stderr @@ -15,7 +15,7 @@ LL | fn fun_example(ref _x: usize) {} LL | gen_function!(); | ---------------- in this macro invocation | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `gen_function` (in Nightly builds, run with -Z macro-backtrace for more info) error: aborting due to 2 previous errors diff --git a/tests/ui/try_err.stderr b/tests/ui/try_err.stderr index 8f332a9b649..09efc16c154 100644 --- a/tests/ui/try_err.stderr +++ b/tests/ui/try_err.stderr @@ -37,7 +37,7 @@ LL | Err(_) => Err(1)?, LL | try_validation!(Ok::<_, i32>(5)); | --------------------------------- in this macro invocation | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `try_validation` (in Nightly builds, run with -Z macro-backtrace for more info) error: returning an `Err(_)` with the `?` operator --> $DIR/try_err.rs:102:23 @@ -48,7 +48,7 @@ LL | Err(_) => Err(ret_one!())?, LL | try_validation_in_macro!(Ok::<_, i32>(5)); | ------------------------------------------ in this macro invocation | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `try_validation_in_macro` (in Nightly builds, run with -Z macro-backtrace for more info) error: returning an `Err(_)` with the `?` operator --> $DIR/try_err.rs:141:9 diff --git a/tests/ui/unit_cmp.stderr b/tests/ui/unit_cmp.stderr index c8c0a85dfc1..75017cab057 100644 --- a/tests/ui/unit_cmp.stderr +++ b/tests/ui/unit_cmp.stderr @@ -34,7 +34,7 @@ LL | | } LL | | ); | |______^ | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `assert_eq` (in Nightly builds, run with -Z macro-backtrace for more info) error: `debug_assert_eq` of unit values detected. This will always succeed --> $DIR/unit_cmp.rs:32:5 @@ -48,7 +48,7 @@ LL | | } LL | | ); | |______^ | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::assert_eq` (in Nightly builds, run with -Z macro-backtrace for more info) error: `assert_ne` of unit values detected. This will always fail --> $DIR/unit_cmp.rs:41:5 @@ -62,7 +62,7 @@ LL | | } LL | | ); | |______^ | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `assert_ne` (in Nightly builds, run with -Z macro-backtrace for more info) error: `debug_assert_ne` of unit values detected. This will always fail --> $DIR/unit_cmp.rs:49:5 @@ -76,7 +76,7 @@ LL | | } LL | | ); | |______^ | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `$crate::assert_ne` (in Nightly builds, run with -Z macro-backtrace for more info) error: aborting due to 6 previous errors diff --git a/tests/ui/unsafe_derive_deserialize.stderr b/tests/ui/unsafe_derive_deserialize.stderr index 1978bd95a67..18c4276c6dd 100644 --- a/tests/ui/unsafe_derive_deserialize.stderr +++ b/tests/ui/unsafe_derive_deserialize.stderr @@ -6,7 +6,7 @@ LL | #[derive(Deserialize)] | = note: `-D clippy::unsafe-derive-deserialize` implied by `-D warnings` = help: consider implementing `serde::Deserialize` manually. See https://serde.rs/impl-deserialize.html - = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the derive macro `Deserialize` (in Nightly builds, run with -Z macro-backtrace for more info) error: you are deriving `serde::Deserialize` on a type that has methods using `unsafe` --> $DIR/unsafe_derive_deserialize.rs:16:10 @@ -15,7 +15,7 @@ LL | #[derive(Deserialize)] | ^^^^^^^^^^^ | = help: consider implementing `serde::Deserialize` manually. See https://serde.rs/impl-deserialize.html - = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the derive macro `Deserialize` (in Nightly builds, run with -Z macro-backtrace for more info) error: you are deriving `serde::Deserialize` on a type that has methods using `unsafe` --> $DIR/unsafe_derive_deserialize.rs:22:10 @@ -24,7 +24,7 @@ LL | #[derive(Deserialize)] | ^^^^^^^^^^^ | = help: consider implementing `serde::Deserialize` manually. See https://serde.rs/impl-deserialize.html - = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the derive macro `Deserialize` (in Nightly builds, run with -Z macro-backtrace for more info) error: you are deriving `serde::Deserialize` on a type that has methods using `unsafe` --> $DIR/unsafe_derive_deserialize.rs:30:10 @@ -33,7 +33,7 @@ LL | #[derive(Deserialize)] | ^^^^^^^^^^^ | = help: consider implementing `serde::Deserialize` manually. See https://serde.rs/impl-deserialize.html - = note: this error originates in a derive macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the derive macro `Deserialize` (in Nightly builds, run with -Z macro-backtrace for more info) error: aborting due to 4 previous errors diff --git a/tests/ui/unseparated_prefix_literals.stderr b/tests/ui/unseparated_prefix_literals.stderr index d7dd526bcb9..a0c0be7a9d1 100644 --- a/tests/ui/unseparated_prefix_literals.stderr +++ b/tests/ui/unseparated_prefix_literals.stderr @@ -51,7 +51,7 @@ LL | 42usize LL | let _ = lit_from_macro!(); | ----------------- in this macro invocation | - = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `lit_from_macro` (in Nightly builds, run with -Z macro-backtrace for more info) error: integer type suffix should be separated by an underscore --> $DIR/unseparated_prefix_literals.rs:40:16 From 0d4604dc05acd45d2401f4973d6085a4eba50016 Mon Sep 17 00:00:00 2001 From: xFrednet Date: Thu, 13 May 2021 01:41:22 +0200 Subject: [PATCH 28/58] Added `cargo collect-metadata` as a alias for the metadata collection lint --- .cargo/config | 1 + tests/dogfood.rs | 70 ++++++++++++++++++++++++++++-------------------- 2 files changed, 42 insertions(+), 29 deletions(-) diff --git a/.cargo/config b/.cargo/config index 9b5add4df1c..e95ea224cb6 100644 --- a/.cargo/config +++ b/.cargo/config @@ -2,6 +2,7 @@ uitest = "test --test compile-test" dev = "run --target-dir clippy_dev/target --package clippy_dev --bin clippy_dev --manifest-path clippy_dev/Cargo.toml --" lintcheck = "run --target-dir lintcheck/target --package lintcheck --bin lintcheck --manifest-path lintcheck/Cargo.toml -- " +collect-metadata = "test --test dogfood --features metadata-collector-lint -- run_metadata_collection_lint --ignored" [build] rustflags = ["-Zunstable-options"] diff --git a/tests/dogfood.rs b/tests/dogfood.rs index 6524fd4706c..5d9f128753f 100644 --- a/tests/dogfood.rs +++ b/tests/dogfood.rs @@ -22,14 +22,12 @@ fn dogfood_clippy() { return; } let root_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - let enable_metadata_collection = std::env::var("ENABLE_METADATA_COLLECTION").unwrap_or_else(|_| "0".to_string()); let mut command = Command::new(&*CLIPPY_PATH); command .current_dir(root_dir) .env("CLIPPY_DOGFOOD", "1") .env("CARGO_INCREMENTAL", "0") - .env("ENABLE_METADATA_COLLECTION", &enable_metadata_collection) .arg("clippy") .arg("--all-targets") .arg("--all-features") @@ -157,10 +155,9 @@ fn dogfood_subprojects() { if cargo::is_rustc_test_suite() { return; } - let root_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); // NOTE: `path_dep` crate is omitted on purpose here - for d in &[ + for project in &[ "clippy_workspace_tests", "clippy_workspace_tests/src", "clippy_workspace_tests/subcrate", @@ -170,34 +167,49 @@ fn dogfood_subprojects() { "clippy_utils", "rustc_tools_util", ] { - let mut command = Command::new(&*CLIPPY_PATH); - command - .current_dir(root_dir.join(d)) - .env("CLIPPY_DOGFOOD", "1") - .env("CARGO_INCREMENTAL", "0") - .arg("clippy") - .arg("--all-targets") - .arg("--all-features") - .arg("--") - .args(&["-D", "clippy::all"]) - .args(&["-D", "clippy::pedantic"]) - .arg("-Cdebuginfo=0"); // disable debuginfo to generate less data in the target dir - - // internal lints only exist if we build with the internal-lints feature - if cfg!(feature = "internal-lints") { - command.args(&["-D", "clippy::internal"]); - } - - let output = command.output().unwrap(); - - println!("status: {}", output.status); - println!("stdout: {}", String::from_utf8_lossy(&output.stdout)); - println!("stderr: {}", String::from_utf8_lossy(&output.stderr)); - - assert!(output.status.success()); + run_clippy_for_project(project); } // NOTE: Since tests run in parallel we can't run cargo commands on the same workspace at the // same time, so we test this immediately after the dogfood for workspaces. test_no_deps_ignores_path_deps_in_workspaces(); } + +#[test] +#[ignore] +#[cfg(feature = "metadata-collector-lint")] +fn run_metadata_collection_lint() { + std::env::set_var("ENABLE_METADATA_COLLECTION", "1"); + run_clippy_for_project("clippy_lints"); +} + +fn run_clippy_for_project(project: &str) { + let root_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + + let mut command = Command::new(&*CLIPPY_PATH); + + command + .current_dir(root_dir.join(project)) + .env("CLIPPY_DOGFOOD", "1") + .env("CARGO_INCREMENTAL", "0") + .arg("clippy") + .arg("--all-targets") + .arg("--all-features") + .arg("--") + .args(&["-D", "clippy::all"]) + .args(&["-D", "clippy::pedantic"]) + .arg("-Cdebuginfo=0"); // disable debuginfo to generate less data in the target dir + + // internal lints only exist if we build with the internal-lints feature + if cfg!(feature = "internal-lints") { + command.args(&["-D", "clippy::internal"]); + } + + let output = command.output().unwrap(); + + println!("status: {}", output.status); + println!("stdout: {}", String::from_utf8_lossy(&output.stdout)); + println!("stderr: {}", String::from_utf8_lossy(&output.stderr)); + + assert!(output.status.success()); +} From 8214bf04450db9e57975993290d3c35a66a40707 Mon Sep 17 00:00:00 2001 From: Yoshitomo Nakanishi Date: Fri, 16 Apr 2021 16:07:20 +0900 Subject: [PATCH 29/58] match_single_binding: Fix invalid suggestion when match scrutinee has side effects --- clippy_lints/src/matches.rs | 37 ++++++++++++++++++++------- tests/ui/match_single_binding.fixed | 5 +--- tests/ui/match_single_binding.rs | 10 +------- tests/ui/match_single_binding.stderr | 13 +--------- tests/ui/match_single_binding2.fixed | 16 ++++++++++++ tests/ui/match_single_binding2.rs | 18 +++++++++++++ tests/ui/match_single_binding2.stderr | 36 +++++++++++++++++++++++++- 7 files changed, 100 insertions(+), 35 deletions(-) diff --git a/clippy_lints/src/matches.rs b/clippy_lints/src/matches.rs index 44b4eb29035..345b360e633 100644 --- a/clippy_lints/src/matches.rs +++ b/clippy_lints/src/matches.rs @@ -1479,15 +1479,34 @@ fn check_match_single_binding<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[A ); }, PatKind::Wild => { - span_lint_and_sugg( - cx, - MATCH_SINGLE_BINDING, - expr.span, - "this match could be replaced by its body itself", - "consider using the match body instead", - snippet_body, - Applicability::MachineApplicable, - ); + if ex.can_have_side_effects() { + let indent = " ".repeat(indent_of(cx, expr.span).unwrap_or(0)); + let sugg = format!( + "{};\n{}{}", + snippet_with_applicability(cx, ex.span, "..", &mut applicability), + indent, + snippet_body + ); + span_lint_and_sugg( + cx, + MATCH_SINGLE_BINDING, + expr.span, + "this match could be replaced by its scrutinee and body", + "consider using the scrutinee and body instead", + sugg, + applicability, + ) + } else { + span_lint_and_sugg( + cx, + MATCH_SINGLE_BINDING, + expr.span, + "this match could be replaced by its body itself", + "consider using the match body instead", + snippet_body, + Applicability::MachineApplicable, + ); + } }, _ => (), } diff --git a/tests/ui/match_single_binding.fixed b/tests/ui/match_single_binding.fixed index 526e94b10bd..30bf6402253 100644 --- a/tests/ui/match_single_binding.fixed +++ b/tests/ui/match_single_binding.fixed @@ -94,10 +94,7 @@ fn main() { 0 => println!("Disabled branch"), _ => println!("Enabled branch"), } - // Lint - let x = 1; - let y = 1; - println!("Single branch"); + // Ok let x = 1; let y = 1; diff --git a/tests/ui/match_single_binding.rs b/tests/ui/match_single_binding.rs index 6a2ca7c5e93..d8bb80d8b96 100644 --- a/tests/ui/match_single_binding.rs +++ b/tests/ui/match_single_binding.rs @@ -106,15 +106,7 @@ fn main() { 0 => println!("Disabled branch"), _ => println!("Enabled branch"), } - // Lint - let x = 1; - let y = 1; - match match y { - 0 => 1, - _ => 2, - } { - _ => println!("Single branch"), - } + // Ok let x = 1; let y = 1; diff --git a/tests/ui/match_single_binding.stderr b/tests/ui/match_single_binding.stderr index cbbf5d29c02..795c8c3e24d 100644 --- a/tests/ui/match_single_binding.stderr +++ b/tests/ui/match_single_binding.stderr @@ -167,16 +167,5 @@ LL | unwrapped LL | }) | -error: this match could be replaced by its body itself - --> $DIR/match_single_binding.rs:112:5 - | -LL | / match match y { -LL | | 0 => 1, -LL | | _ => 2, -LL | | } { -LL | | _ => println!("Single branch"), -LL | | } - | |_____^ help: consider using the match body instead: `println!("Single branch");` - -error: aborting due to 12 previous errors +error: aborting due to 11 previous errors diff --git a/tests/ui/match_single_binding2.fixed b/tests/ui/match_single_binding2.fixed index e73a85b73d7..a91fcc2125d 100644 --- a/tests/ui/match_single_binding2.fixed +++ b/tests/ui/match_single_binding2.fixed @@ -34,4 +34,20 @@ fn main() { }, None => println!("nothing"), } + + fn side_effects() {} + + // Lint (scrutinee has side effects) + // issue #7094 + side_effects(); + println!("Side effects"); + + // Lint (scrutinee has side effects) + // issue #7094 + let x = 1; + match x { + 0 => 1, + _ => 2, + }; + println!("Single branch"); } diff --git a/tests/ui/match_single_binding2.rs b/tests/ui/match_single_binding2.rs index 7362cb390e5..476386ebabe 100644 --- a/tests/ui/match_single_binding2.rs +++ b/tests/ui/match_single_binding2.rs @@ -34,4 +34,22 @@ fn main() { }, None => println!("nothing"), } + + fn side_effects() {} + + // Lint (scrutinee has side effects) + // issue #7094 + match side_effects() { + _ => println!("Side effects"), + } + + // Lint (scrutinee has side effects) + // issue #7094 + let x = 1; + match match x { + 0 => 1, + _ => 2, + } { + _ => println!("Single branch"), + } } diff --git a/tests/ui/match_single_binding2.stderr b/tests/ui/match_single_binding2.stderr index bc18d191aa3..4372f55af87 100644 --- a/tests/ui/match_single_binding2.stderr +++ b/tests/ui/match_single_binding2.stderr @@ -30,5 +30,39 @@ LL | let (a, b) = get_tup(); LL | println!("a {:?} and b {:?}", a, b); | -error: aborting due to 2 previous errors +error: this match could be replaced by its scrutinee and body + --> $DIR/match_single_binding2.rs:42:5 + | +LL | / match side_effects() { +LL | | _ => println!("Side effects"), +LL | | } + | |_____^ + | +help: consider using the scrutinee and body instead + | +LL | side_effects(); +LL | println!("Side effects"); + | + +error: this match could be replaced by its scrutinee and body + --> $DIR/match_single_binding2.rs:49:5 + | +LL | / match match x { +LL | | 0 => 1, +LL | | _ => 2, +LL | | } { +LL | | _ => println!("Single branch"), +LL | | } + | |_____^ + | +help: consider using the scrutinee and body instead + | +LL | match x { +LL | 0 => 1, +LL | _ => 2, +LL | }; +LL | println!("Single branch"); + | + +error: aborting due to 4 previous errors From daca50a515f28218142d9945ca714f7420b4fc75 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Wed, 24 Mar 2021 09:32:29 -0400 Subject: [PATCH 30/58] Improvements to `while_let_on_iterator` * Suggest `&mut iter` when the iterator is used after the loop. * Suggest `&mut iter` when the iterator is a field in a struct. * Don't lint when the iterator is a field in a struct, and the struct is used in the loop. * Lint when the loop is nested in another loop, but suggest `&mut iter` unless the iterator is from a local declared inside the loop. --- clippy_lints/src/doc.rs | 2 +- .../src/loops/while_let_on_iterator.rs | 475 ++++++++++++------ clippy_utils/src/lib.rs | 18 + clippy_utils/src/sugg.rs | 2 +- clippy_utils/src/visitors.rs | 36 +- tests/ui/while_let_on_iterator.fixed | 164 ++++-- tests/ui/while_let_on_iterator.rs | 164 ++++-- tests/ui/while_let_on_iterator.stderr | 72 ++- 8 files changed, 721 insertions(+), 212 deletions(-) diff --git a/clippy_lints/src/doc.rs b/clippy_lints/src/doc.rs index fb53b55ebd6..e67ec4e06c5 100644 --- a/clippy_lints/src/doc.rs +++ b/clippy_lints/src/doc.rs @@ -383,7 +383,7 @@ pub fn strip_doc_comment_decoration(doc: &str, comment_kind: CommentKind, span: let mut no_stars = String::with_capacity(doc.len()); for line in doc.lines() { let mut chars = line.chars(); - while let Some(c) = chars.next() { + for c in &mut chars { if c.is_whitespace() { no_stars.push(c); } else { diff --git a/clippy_lints/src/loops/while_let_on_iterator.rs b/clippy_lints/src/loops/while_let_on_iterator.rs index 82715d9bafa..75e92f08df1 100644 --- a/clippy_lints/src/loops/while_let_on_iterator.rs +++ b/clippy_lints/src/loops/while_let_on_iterator.rs @@ -1,170 +1,353 @@ -use super::utils::{LoopNestVisitor, Nesting}; use super::WHILE_LET_ON_ITERATOR; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_with_applicability; -use clippy_utils::ty::implements_trait; -use clippy_utils::usage::mutated_variables; -use clippy_utils::{ - get_enclosing_block, is_refutable, is_trait_method, last_path_segment, path_to_local, path_to_local_id, -}; -use if_chain::if_chain; +use clippy_utils::{get_enclosing_loop, is_refutable, is_trait_method, match_def_path, paths, visitors::is_res_used}; use rustc_errors::Applicability; -use rustc_hir::intravisit::{walk_block, walk_expr, NestedVisitorMap, Visitor}; -use rustc_hir::{Expr, ExprKind, HirId, MatchSource, Node, PatKind}; +use rustc_hir::intravisit::{walk_expr, ErasedMap, NestedVisitorMap, Visitor}; +use rustc_hir::{def::Res, Expr, ExprKind, HirId, Local, MatchSource, Node, PatKind, QPath, UnOp}; use rustc_lint::LateContext; -use rustc_middle::hir::map::Map; -use rustc_span::symbol::sym; +use rustc_span::{symbol::sym, Span, Symbol}; pub(super) fn check(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - if let ExprKind::Match(match_expr, arms, MatchSource::WhileLetDesugar) = expr.kind { - let pat = &arms[0].pat.kind; - if let (&PatKind::TupleStruct(ref qpath, pat_args, _), &ExprKind::MethodCall(method_path, _, method_args, _)) = - (pat, &match_expr.kind) - { - let iter_expr = &method_args[0]; + if let ExprKind::Match(scrutinee_expr, [arm, _], MatchSource::WhileLetDesugar) = expr.kind { + let some_pat = match arm.pat.kind { + PatKind::TupleStruct(QPath::Resolved(None, path), sub_pats, _) => match path.res { + Res::Def(_, id) if match_def_path(cx, id, &paths::OPTION_SOME) => sub_pats.first(), + _ => return, + }, + _ => return, + }; - // Don't lint when the iterator is recreated on every iteration - if_chain! { - if let ExprKind::MethodCall(..) | ExprKind::Call(..) = iter_expr.kind; - if let Some(iter_def_id) = cx.tcx.get_diagnostic_item(sym::Iterator); - if implements_trait(cx, cx.typeck_results().expr_ty(iter_expr), iter_def_id, &[]); - then { + let iter_expr = match scrutinee_expr.kind { + ExprKind::MethodCall(name, _, [iter_expr], _) + if name.ident.name == sym::next && is_trait_method(cx, scrutinee_expr, sym::Iterator) => + { + if let Some(iter_expr) = try_parse_iter_expr(cx, iter_expr) { + iter_expr + } else { return; } } + _ => return, + }; - let lhs_constructor = last_path_segment(qpath); - if method_path.ident.name == sym::next - && is_trait_method(cx, match_expr, sym::Iterator) - && lhs_constructor.ident.name == sym::Some - && (pat_args.is_empty() - || !is_refutable(cx, pat_args[0]) - && !is_used_inside(cx, iter_expr, arms[0].body) - && !is_iterator_used_after_while_let(cx, iter_expr) - && !is_nested(cx, expr, &method_args[0])) - { - let mut applicability = Applicability::MachineApplicable; - let iterator = snippet_with_applicability(cx, method_args[0].span, "_", &mut applicability); - let loop_var = if pat_args.is_empty() { - "_".to_string() - } else { - snippet_with_applicability(cx, pat_args[0].span, "_", &mut applicability).into_owned() - }; - span_lint_and_sugg( - cx, - WHILE_LET_ON_ITERATOR, - expr.span.with_hi(match_expr.span.hi()), - "this loop could be written as a `for` loop", - "try", - format!("for {} in {}", loop_var, iterator), - applicability, - ); - } + // Needed to find an outer loop, if there are any. + let loop_expr = if let Some((_, Node::Expr(e))) = cx.tcx.hir().parent_iter(expr.hir_id).nth(1) { + e + } else { + return; + }; + + // Refutable patterns don't work with for loops. + // The iterator also can't be accessed withing the loop. + if some_pat.map_or(true, |p| is_refutable(cx, p)) || uses_iter(cx, &iter_expr, arm.body) { + return; } + + // If the iterator is a field or the iterator is accessed after the loop is complete it needs to be + // borrowed mutably. + // TODO: If the struct can be partially moved from and the struct isn't used afterwards a mutable + // borrow of a field isn't necessary. + let ref_mut = if !iter_expr.fields.is_empty() || needs_mutable_borrow(cx, &iter_expr, loop_expr) { + "&mut " + } else { + "" + }; + let mut applicability = Applicability::MachineApplicable; + let iterator = snippet_with_applicability(cx, iter_expr.span, "_", &mut applicability); + let loop_var = some_pat.map_or_else( + || "_".into(), + |pat| snippet_with_applicability(cx, pat.span, "_", &mut applicability).into_owned(), + ); + span_lint_and_sugg( + cx, + WHILE_LET_ON_ITERATOR, + expr.span.with_hi(scrutinee_expr.span.hi()), + "this loop could be written as a `for` loop", + "try", + format!("for {} in {}{}", loop_var, ref_mut, iterator), + applicability, + ); } } -fn is_used_inside<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, container: &'tcx Expr<'_>) -> bool { - let def_id = match path_to_local(expr) { - Some(id) => id, - None => return false, - }; - if let Some(used_mutably) = mutated_variables(container, cx) { - if used_mutably.contains(&def_id) { - return true; - } - } - false +#[derive(Debug)] +struct IterExpr { + /// The span of the whole expression, not just the path and fields stored here. + span: Span, + /// The fields used, in order of child to parent. + fields: Vec, + /// The path being used. + path: Res, } - -fn is_iterator_used_after_while_let<'tcx>(cx: &LateContext<'tcx>, iter_expr: &'tcx Expr<'_>) -> bool { - let def_id = match path_to_local(iter_expr) { - Some(id) => id, - None => return false, - }; - let mut visitor = VarUsedAfterLoopVisitor { - def_id, - iter_expr_id: iter_expr.hir_id, - past_while_let: false, - var_used_after_while_let: false, - }; - if let Some(enclosing_block) = get_enclosing_block(cx, def_id) { - walk_block(&mut visitor, enclosing_block); - } - visitor.var_used_after_while_let -} - -fn is_nested(cx: &LateContext<'_>, match_expr: &Expr<'_>, iter_expr: &Expr<'_>) -> bool { - if_chain! { - if let Some(loop_block) = get_enclosing_block(cx, match_expr.hir_id); - let parent_node = cx.tcx.hir().get_parent_node(loop_block.hir_id); - if let Some(Node::Expr(loop_expr)) = cx.tcx.hir().find(parent_node); - then { - return is_loop_nested(cx, loop_expr, iter_expr) - } - } - false -} - -fn is_loop_nested(cx: &LateContext<'_>, loop_expr: &Expr<'_>, iter_expr: &Expr<'_>) -> bool { - let mut id = loop_expr.hir_id; - let iter_id = if let Some(id) = path_to_local(iter_expr) { - id - } else { - return true; - }; +/// Parses any expression to find out which field of which variable is used. Will return `None` if +/// the expression might have side effects. +fn try_parse_iter_expr(cx: &LateContext<'_>, mut e: &Expr<'_>) -> Option { + let span = e.span; + let mut fields = Vec::new(); loop { - let parent = cx.tcx.hir().get_parent_node(id); - if parent == id { - return false; + match e.kind { + ExprKind::Path(ref path) => { + break Some(IterExpr { + span, + fields, + path: cx.qpath_res(path, e.hir_id), + }); + }, + ExprKind::Field(base, name) => { + fields.push(name.name); + e = base; + }, + // Dereferencing a pointer has no side effects and doesn't affect which field is being used. + ExprKind::Unary(UnOp::Deref, base) if cx.typeck_results().expr_ty(base).is_ref() => e = base, + + // Shouldn't have side effects, but there's no way to trace which field is used. So forget which fields have + // already been seen. + ExprKind::Index(base, idx) if !idx.can_have_side_effects() => { + fields.clear(); + e = base; + }, + ExprKind::Unary(UnOp::Deref, base) => { + fields.clear(); + e = base; + }, + + // No effect and doesn't affect which field is being used. + ExprKind::DropTemps(base) | ExprKind::AddrOf(_, _, base) | ExprKind::Type(base, _) => e = base, + _ => break None, } - match cx.tcx.hir().find(parent) { - Some(Node::Expr(expr)) => { - if let ExprKind::Loop(..) = expr.kind { + } +} + +fn is_expr_same_field(cx: &LateContext<'_>, mut e: &Expr<'_>, mut fields: &[Symbol], path_res: Res) -> bool { + loop { + match (&e.kind, fields) { + (&ExprKind::Field(base, name), [head_field, tail_fields @ ..]) if name.name == *head_field => { + e = base; + fields = tail_fields; + }, + (ExprKind::Path(path), []) => { + break cx.qpath_res(path, e.hir_id) == path_res; + }, + (&(ExprKind::DropTemps(base) | ExprKind::AddrOf(_, _, base) | ExprKind::Type(base, _)), _) => e = base, + _ => break false, + } + } +} + +/// Checks if the given expression is the same field as, is a child of, of the parent of the given +/// field. Used to check if the expression can be used while the given field is borrowed. +fn is_expr_same_child_or_parent_field(cx: &LateContext<'_>, expr: &Expr<'_>, fields: &[Symbol], path_res: Res) -> bool { + match expr.kind { + ExprKind::Field(base, name) => { + if let Some((head_field, tail_fields)) = fields.split_first() { + if name.name == *head_field && is_expr_same_field(cx, base, fields, path_res) { return true; - }; - }, - Some(Node::Block(block)) => { - let mut block_visitor = LoopNestVisitor { - hir_id: id, - iterator: iter_id, - nesting: Nesting::Unknown, - }; - walk_block(&mut block_visitor, block); - if block_visitor.nesting == Nesting::RuledOut { - return false; } - }, - Some(Node::Stmt(_)) => (), - _ => { - return false; - }, - } - id = parent; - } -} - -struct VarUsedAfterLoopVisitor { - def_id: HirId, - iter_expr_id: HirId, - past_while_let: bool, - var_used_after_while_let: bool, -} - -impl<'tcx> Visitor<'tcx> for VarUsedAfterLoopVisitor { - type Map = Map<'tcx>; - - fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { - if self.past_while_let { - if path_to_local_id(expr, self.def_id) { - self.var_used_after_while_let = true; + // Check if the expression is a parent field + let mut fields_iter = tail_fields.iter(); + while let Some(field) = fields_iter.next() { + if *field == name.name && is_expr_same_field(cx, base, fields_iter.as_slice(), path_res) { + return true; + } + } } - } else if self.iter_expr_id == expr.hir_id { - self.past_while_let = true; - } - walk_expr(self, expr); - } - fn nested_visit_map(&mut self) -> NestedVisitorMap { - NestedVisitorMap::None + + // Check if the expression is a child field. + let mut e = base; + loop { + match e.kind { + ExprKind::Field(..) if is_expr_same_field(cx, e, fields, path_res) => break true, + ExprKind::Field(base, _) | ExprKind::DropTemps(base) | ExprKind::Type(base, _) => e = base, + ExprKind::Path(ref path) if fields.is_empty() => { + break cx.qpath_res(path, e.hir_id) == path_res; + }, + _ => break false, + } + } + }, + // If the path matches, this is either an exact match, of the expression is a parent of the field. + ExprKind::Path(ref path) => cx.qpath_res(path, expr.hir_id) == path_res, + ExprKind::DropTemps(base) | ExprKind::Type(base, _) | ExprKind::AddrOf(_, _, base) => { + is_expr_same_child_or_parent_field(cx, base, fields, path_res) + }, + _ => false, + } +} + +/// Strips off all field and path expressions. Used to skip them after failing to check for +/// equality. +fn skip_fields_and_path(expr: &'tcx Expr<'_>) -> (Option<&'tcx Expr<'tcx>>, bool) { + let mut e = expr; + let e = loop { + match e.kind { + ExprKind::Field(base, _) | ExprKind::DropTemps(base) | ExprKind::Type(base, _) => e = base, + ExprKind::Path(_) => return (None, true), + _ => break e, + } + }; + (Some(e), e.hir_id != expr.hir_id) +} + +/// Checks if the given expression uses the iterator. +fn uses_iter(cx: &LateContext<'tcx>, iter_expr: &IterExpr, container: &'tcx Expr<'_>) -> bool { + struct V<'a, 'b, 'tcx> { + cx: &'a LateContext<'tcx>, + iter_expr: &'b IterExpr, + uses_iter: bool, + } + impl Visitor<'tcx> for V<'_, '_, 'tcx> { + type Map = ErasedMap<'tcx>; + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } + + fn visit_expr(&mut self, e: &'tcx Expr<'_>) { + if self.uses_iter { + // return + } else if is_expr_same_child_or_parent_field(self.cx, e, &self.iter_expr.fields, self.iter_expr.path) { + self.uses_iter = true; + } else if let (e, true) = skip_fields_and_path(e) { + if let Some(e) = e { + self.visit_expr(e); + } + } else if let ExprKind::Closure(_, _, id, _, _) = e.kind { + if is_res_used(self.cx, self.iter_expr.path, id) { + self.uses_iter = true; + } + } else { + walk_expr(self, e); + } + } + } + + let mut v = V { + cx, + iter_expr, + uses_iter: false, + }; + v.visit_expr(container); + v.uses_iter +} + +#[allow(clippy::too_many_lines)] +fn needs_mutable_borrow(cx: &LateContext<'tcx>, iter_expr: &IterExpr, loop_expr: &'tcx Expr<'_>) -> bool { + struct AfterLoopVisitor<'a, 'b, 'tcx> { + cx: &'a LateContext<'tcx>, + iter_expr: &'b IterExpr, + loop_id: HirId, + after_loop: bool, + used_iter: bool, + } + impl Visitor<'tcx> for AfterLoopVisitor<'_, '_, 'tcx> { + type Map = ErasedMap<'tcx>; + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } + + fn visit_expr(&mut self, e: &'tcx Expr<'_>) { + if self.used_iter { + return; + } + if self.after_loop { + if is_expr_same_child_or_parent_field(self.cx, e, &self.iter_expr.fields, self.iter_expr.path) { + self.used_iter = true; + } else if let (e, true) = skip_fields_and_path(e) { + if let Some(e) = e { + self.visit_expr(e); + } + } else if let ExprKind::Closure(_, _, id, _, _) = e.kind { + self.used_iter |= is_res_used(self.cx, self.iter_expr.path, id); + } else { + walk_expr(self, e); + } + } else if self.loop_id == e.hir_id { + self.after_loop = true; + } else { + walk_expr(self, e); + } + } + } + + struct NestedLoopVisitor<'a, 'b, 'tcx> { + cx: &'a LateContext<'tcx>, + iter_expr: &'b IterExpr, + local_id: HirId, + loop_id: HirId, + after_loop: bool, + found_local: bool, + used_after: bool, + } + impl Visitor<'tcx> for NestedLoopVisitor<'a, 'b, 'tcx> { + type Map = ErasedMap<'tcx>; + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::None + } + + fn visit_local(&mut self, l: &'tcx Local<'_>) { + if !self.after_loop { + l.pat.each_binding_or_first(&mut |_, id, _, _| { + if id == self.local_id { + self.found_local = true; + } + }); + } + if let Some(e) = l.init { + self.visit_expr(e); + } + } + + fn visit_expr(&mut self, e: &'tcx Expr<'_>) { + if self.used_after { + return; + } + if self.after_loop { + if is_expr_same_child_or_parent_field(self.cx, e, &self.iter_expr.fields, self.iter_expr.path) { + self.used_after = true; + } else if let (e, true) = skip_fields_and_path(e) { + if let Some(e) = e { + self.visit_expr(e); + } + } else if let ExprKind::Closure(_, _, id, _, _) = e.kind { + self.used_after |= is_res_used(self.cx, self.iter_expr.path, id); + } else { + walk_expr(self, e); + } + } else if e.hir_id == self.loop_id { + self.after_loop = true; + } else { + walk_expr(self, e); + } + } + } + + if let Some(e) = get_enclosing_loop(cx.tcx, loop_expr) { + // The iterator expression will be used on the next iteration unless it is declared within the outer + // loop. + let local_id = match iter_expr.path { + Res::Local(id) => id, + _ => return true, + }; + let mut v = NestedLoopVisitor { + cx, + iter_expr, + local_id, + loop_id: loop_expr.hir_id, + after_loop: false, + found_local: false, + used_after: false, + }; + v.visit_expr(e); + v.used_after || !v.found_local + } else { + let mut v = AfterLoopVisitor { + cx, + iter_expr, + loop_id: loop_expr.hir_id, + after_loop: false, + used_iter: false, + }; + v.visit_expr(&cx.tcx.hir().body(cx.enclosing_body.unwrap()).value); + v.used_iter } } diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 7ca9d3a860d..371fe23bedc 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -855,6 +855,24 @@ pub fn get_enclosing_block<'tcx>(cx: &LateContext<'tcx>, hir_id: HirId) -> Optio }) } +/// Gets the loop enclosing the given expression, if any. +pub fn get_enclosing_loop(tcx: TyCtxt<'tcx>, expr: &Expr<'_>) -> Option<&'tcx Expr<'tcx>> { + let map = tcx.hir(); + for (_, node) in map.parent_iter(expr.hir_id) { + match node { + Node::Expr( + e @ Expr { + kind: ExprKind::Loop(..), + .. + }, + ) => return Some(e), + Node::Expr(_) | Node::Stmt(_) | Node::Block(_) | Node::Local(_) | Node::Arm(_) => (), + _ => break, + } + } + None +} + /// Gets the parent node if it's an impl block. pub fn get_parent_as_impl(tcx: TyCtxt<'_>, id: HirId) -> Option<&Impl<'_>> { let map = tcx.hir(); diff --git a/clippy_utils/src/sugg.rs b/clippy_utils/src/sugg.rs index 0633a19391f..0c950661757 100644 --- a/clippy_utils/src/sugg.rs +++ b/clippy_utils/src/sugg.rs @@ -289,7 +289,7 @@ fn has_enclosing_paren(sugg: impl AsRef) -> bool { let mut chars = sugg.as_ref().chars(); if let Some('(') = chars.next() { let mut depth = 1; - while let Some(c) = chars.next() { + for c in &mut chars { if c == '(' { depth += 1; } else if c == ')' { diff --git a/clippy_utils/src/visitors.rs b/clippy_utils/src/visitors.rs index d431bdf34ee..ffd15076026 100644 --- a/clippy_utils/src/visitors.rs +++ b/clippy_utils/src/visitors.rs @@ -1,7 +1,7 @@ use crate::path_to_local_id; use rustc_hir as hir; use rustc_hir::intravisit::{self, walk_expr, ErasedMap, NestedVisitorMap, Visitor}; -use rustc_hir::{Arm, Block, Body, Destination, Expr, ExprKind, HirId, Stmt}; +use rustc_hir::{def::Res, Arm, Block, Body, BodyId, Destination, Expr, ExprKind, HirId, Stmt}; use rustc_lint::LateContext; use rustc_middle::hir::map::Map; @@ -218,6 +218,7 @@ impl<'tcx> Visitable<'tcx> for &'tcx Arm<'tcx> { } } +/// Calls the given function for each break expression. pub fn visit_break_exprs<'tcx>( node: impl Visitable<'tcx>, f: impl FnMut(&'tcx Expr<'tcx>, Destination, Option<&'tcx Expr<'tcx>>), @@ -239,3 +240,36 @@ pub fn visit_break_exprs<'tcx>( node.visit(&mut V(f)); } + +/// Checks if the given resolved path is used the body. +pub fn is_res_used(cx: &LateContext<'_>, res: Res, body: BodyId) -> bool { + struct V<'a, 'tcx> { + cx: &'a LateContext<'tcx>, + res: Res, + found: bool, + } + impl Visitor<'tcx> for V<'_, 'tcx> { + type Map = Map<'tcx>; + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::OnlyBodies(self.cx.tcx.hir()) + } + + fn visit_expr(&mut self, e: &'tcx Expr<'_>) { + if self.found { + return; + } + + if let ExprKind::Path(p) = &e.kind { + if self.cx.qpath_res(p, e.hir_id) == self.res { + self.found = true; + } + } else { + walk_expr(self, e) + } + } + } + + let mut v = V { cx, res, found: false }; + v.visit_expr(&cx.tcx.hir().body(body).value); + v.found +} diff --git a/tests/ui/while_let_on_iterator.fixed b/tests/ui/while_let_on_iterator.fixed index 749393db124..0562db48a88 100644 --- a/tests/ui/while_let_on_iterator.fixed +++ b/tests/ui/while_let_on_iterator.fixed @@ -1,7 +1,7 @@ // run-rustfix #![warn(clippy::while_let_on_iterator)] -#![allow(clippy::never_loop, unreachable_code, unused_mut)] +#![allow(clippy::never_loop, unreachable_code, unused_mut, dead_code)] fn base() { let mut iter = 1..20; @@ -38,13 +38,6 @@ fn base() { println!("next: {:?}", iter.next()); } - // or this - let mut iter = 1u32..20; - while let Some(_) = iter.next() { - break; - } - println!("Remaining iter {:?}", iter); - // or this let mut iter = 1u32..20; while let Some(_) = iter.next() { @@ -135,18 +128,6 @@ fn refutable2() { fn nested_loops() { let a = [42, 1337]; - let mut y = a.iter(); - loop { - // x is reused, so don't lint here - while let Some(_) = y.next() {} - } - - let mut y = a.iter(); - for _ in 0..2 { - while let Some(_) = y.next() { - // y is reused, don't lint - } - } loop { let mut y = a.iter(); @@ -205,13 +186,138 @@ fn issue1654() { } } -fn main() { - base(); - refutable(); - refutable2(); - nested_loops(); - issue1121(); - issue2965(); - issue3670(); - issue1654(); +fn issue6491() { + // Used in outer loop, needs &mut + let mut it = 1..40; + while let Some(n) = it.next() { + for m in &mut it { + if m % 10 == 0 { + break; + } + println!("doing something with m: {}", m); + } + println!("n still is {}", n); + } + + // This is fine, inner loop uses a new iterator. + let mut it = 1..40; + for n in it { + let mut it = 1..40; + for m in it { + if m % 10 == 0 { + break; + } + println!("doing something with m: {}", m); + } + + // Weird binding shouldn't change anything. + let (mut it, _) = (1..40, 0); + for m in it { + if m % 10 == 0 { + break; + } + println!("doing something with m: {}", m); + } + + // Used after the loop, needs &mut. + let mut it = 1..40; + for m in &mut it { + if m % 10 == 0 { + break; + } + println!("doing something with m: {}", m); + } + println!("next item {}", it.next().unwrap()); + + println!("n still is {}", n); + } } + +fn issue6231() { + // Closure in the outer loop, needs &mut + let mut it = 1..40; + let mut opt = Some(0); + while let Some(n) = opt.take().or_else(|| it.next()) { + for m in &mut it { + if n % 10 == 0 { + break; + } + println!("doing something with m: {}", m); + } + println!("n still is {}", n); + } +} + +fn issue1924() { + struct S(T); + impl> S { + fn f(&mut self) -> Option { + // Used as a field. + for i in &mut self.0 { + if !(3..=7).contains(&i) { + return Some(i); + } + } + None + } + + fn f2(&mut self) -> Option { + // Don't lint, self borrowed inside the loop + while let Some(i) = self.0.next() { + if i == 1 { + return self.f(); + } + } + None + } + } + impl> S<(S, Option)> { + fn f3(&mut self) -> Option { + // Don't lint, self borrowed inside the loop + while let Some(i) = self.0.0.0.next() { + if i == 1 { + return self.0.0.f(); + } + } + while let Some(i) = self.0.0.0.next() { + if i == 1 { + return self.f3(); + } + } + // This one is fine, a different field is borrowed + for i in &mut self.0.0.0 { + if i == 1 { + return self.0.1.take(); + } + } + None + } + } + + struct S2(T, u32); + impl> Iterator for S2 { + type Item = u32; + fn next(&mut self) -> Option { + self.0.next() + } + } + + // Don't lint, field of the iterator is accessed in the loop + let mut it = S2(1..40, 0); + while let Some(n) = it.next() { + if n == it.1 { + break; + } + } + + // Needs &mut, field of the iterator is accessed after the loop + let mut it = S2(1..40, 0); + for n in &mut it { + if n == 0 { + break; + } + } + println!("iterator field {}", it.1); +} + +fn main() {} diff --git a/tests/ui/while_let_on_iterator.rs b/tests/ui/while_let_on_iterator.rs index 30e3b82a7cc..a7c217dc7fd 100644 --- a/tests/ui/while_let_on_iterator.rs +++ b/tests/ui/while_let_on_iterator.rs @@ -1,7 +1,7 @@ // run-rustfix #![warn(clippy::while_let_on_iterator)] -#![allow(clippy::never_loop, unreachable_code, unused_mut)] +#![allow(clippy::never_loop, unreachable_code, unused_mut, dead_code)] fn base() { let mut iter = 1..20; @@ -38,13 +38,6 @@ fn base() { println!("next: {:?}", iter.next()); } - // or this - let mut iter = 1u32..20; - while let Some(_) = iter.next() { - break; - } - println!("Remaining iter {:?}", iter); - // or this let mut iter = 1u32..20; while let Some(_) = iter.next() { @@ -135,18 +128,6 @@ fn refutable2() { fn nested_loops() { let a = [42, 1337]; - let mut y = a.iter(); - loop { - // x is reused, so don't lint here - while let Some(_) = y.next() {} - } - - let mut y = a.iter(); - for _ in 0..2 { - while let Some(_) = y.next() { - // y is reused, don't lint - } - } loop { let mut y = a.iter(); @@ -205,13 +186,138 @@ fn issue1654() { } } -fn main() { - base(); - refutable(); - refutable2(); - nested_loops(); - issue1121(); - issue2965(); - issue3670(); - issue1654(); +fn issue6491() { + // Used in outer loop, needs &mut + let mut it = 1..40; + while let Some(n) = it.next() { + while let Some(m) = it.next() { + if m % 10 == 0 { + break; + } + println!("doing something with m: {}", m); + } + println!("n still is {}", n); + } + + // This is fine, inner loop uses a new iterator. + let mut it = 1..40; + while let Some(n) = it.next() { + let mut it = 1..40; + while let Some(m) = it.next() { + if m % 10 == 0 { + break; + } + println!("doing something with m: {}", m); + } + + // Weird binding shouldn't change anything. + let (mut it, _) = (1..40, 0); + while let Some(m) = it.next() { + if m % 10 == 0 { + break; + } + println!("doing something with m: {}", m); + } + + // Used after the loop, needs &mut. + let mut it = 1..40; + while let Some(m) = it.next() { + if m % 10 == 0 { + break; + } + println!("doing something with m: {}", m); + } + println!("next item {}", it.next().unwrap()); + + println!("n still is {}", n); + } } + +fn issue6231() { + // Closure in the outer loop, needs &mut + let mut it = 1..40; + let mut opt = Some(0); + while let Some(n) = opt.take().or_else(|| it.next()) { + while let Some(m) = it.next() { + if n % 10 == 0 { + break; + } + println!("doing something with m: {}", m); + } + println!("n still is {}", n); + } +} + +fn issue1924() { + struct S(T); + impl> S { + fn f(&mut self) -> Option { + // Used as a field. + while let Some(i) = self.0.next() { + if i < 3 || i > 7 { + return Some(i); + } + } + None + } + + fn f2(&mut self) -> Option { + // Don't lint, self borrowed inside the loop + while let Some(i) = self.0.next() { + if i == 1 { + return self.f(); + } + } + None + } + } + impl> S<(S, Option)> { + fn f3(&mut self) -> Option { + // Don't lint, self borrowed inside the loop + while let Some(i) = self.0.0.0.next() { + if i == 1 { + return self.0.0.f(); + } + } + while let Some(i) = self.0.0.0.next() { + if i == 1 { + return self.f3(); + } + } + // This one is fine, a different field is borrowed + while let Some(i) = self.0.0.0.next() { + if i == 1 { + return self.0.1.take(); + } + } + None + } + } + + struct S2(T, u32); + impl> Iterator for S2 { + type Item = u32; + fn next(&mut self) -> Option { + self.0.next() + } + } + + // Don't lint, field of the iterator is accessed in the loop + let mut it = S2(1..40, 0); + while let Some(n) = it.next() { + if n == it.1 { + break; + } + } + + // Needs &mut, field of the iterator is accessed after the loop + let mut it = S2(1..40, 0); + while let Some(n) = it.next() { + if n == 0 { + break; + } + } + println!("iterator field {}", it.1); +} + +fn main() {} diff --git a/tests/ui/while_let_on_iterator.stderr b/tests/ui/while_let_on_iterator.stderr index 6554977c798..0feca257410 100644 --- a/tests/ui/while_let_on_iterator.stderr +++ b/tests/ui/while_let_on_iterator.stderr @@ -19,28 +19,90 @@ LL | while let Some(_) = iter.next() {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for _ in iter` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:101:9 + --> $DIR/while_let_on_iterator.rs:94:9 | LL | while let Some([..]) = it.next() {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for [..] in it` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:108:9 + --> $DIR/while_let_on_iterator.rs:101:9 | LL | while let Some([_x]) = it.next() {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for [_x] in it` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:121:9 + --> $DIR/while_let_on_iterator.rs:114:9 | LL | while let Some(x @ [_]) = it.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for x @ [_] in it` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:153:9 + --> $DIR/while_let_on_iterator.rs:134:9 | LL | while let Some(_) = y.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for _ in y` -error: aborting due to 7 previous errors +error: this loop could be written as a `for` loop + --> $DIR/while_let_on_iterator.rs:193:9 + | +LL | while let Some(m) = it.next() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for m in &mut it` + +error: this loop could be written as a `for` loop + --> $DIR/while_let_on_iterator.rs:204:5 + | +LL | while let Some(n) = it.next() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for n in it` + +error: this loop could be written as a `for` loop + --> $DIR/while_let_on_iterator.rs:206:9 + | +LL | while let Some(m) = it.next() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for m in it` + +error: this loop could be written as a `for` loop + --> $DIR/while_let_on_iterator.rs:215:9 + | +LL | while let Some(m) = it.next() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for m in it` + +error: this loop could be written as a `for` loop + --> $DIR/while_let_on_iterator.rs:224:9 + | +LL | while let Some(m) = it.next() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for m in &mut it` + +error: this loop could be written as a `for` loop + --> $DIR/while_let_on_iterator.rs:241:9 + | +LL | while let Some(m) = it.next() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for m in &mut it` + +error: this loop could be written as a `for` loop + --> $DIR/while_let_on_iterator.rs:256:13 + | +LL | while let Some(i) = self.0.next() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for i in &mut self.0` + +error: manual `!RangeInclusive::contains` implementation + --> $DIR/while_let_on_iterator.rs:257:20 + | +LL | if i < 3 || i > 7 { + | ^^^^^^^^^^^^^^ help: use: `!(3..=7).contains(&i)` + | + = note: `-D clippy::manual-range-contains` implied by `-D warnings` + +error: this loop could be written as a `for` loop + --> $DIR/while_let_on_iterator.rs:288:13 + | +LL | while let Some(i) = self.0.0.0.next() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for i in &mut self.0.0.0` + +error: this loop could be written as a `for` loop + --> $DIR/while_let_on_iterator.rs:315:5 + | +LL | while let Some(n) = it.next() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for n in &mut it` + +error: aborting due to 17 previous errors From 4713e25ab07badc863fff05fcd5bcf9852cf375e Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Tue, 13 Apr 2021 09:30:01 -0400 Subject: [PATCH 31/58] Cleanup of `while_let_on_iterator` --- .../src/loops/while_let_on_iterator.rs | 120 +++++++++--------- clippy_utils/src/visitors.rs | 2 +- tests/ui/while_let_on_iterator.fixed | 9 +- tests/ui/while_let_on_iterator.rs | 9 +- tests/ui/while_let_on_iterator.stderr | 10 +- 5 files changed, 82 insertions(+), 68 deletions(-) diff --git a/clippy_lints/src/loops/while_let_on_iterator.rs b/clippy_lints/src/loops/while_let_on_iterator.rs index 75e92f08df1..63560047578 100644 --- a/clippy_lints/src/loops/while_let_on_iterator.rs +++ b/clippy_lints/src/loops/while_let_on_iterator.rs @@ -2,6 +2,7 @@ use super::WHILE_LET_ON_ITERATOR; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_with_applicability; use clippy_utils::{get_enclosing_loop, is_refutable, is_trait_method, match_def_path, paths, visitors::is_res_used}; +use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::intravisit::{walk_expr, ErasedMap, NestedVisitorMap, Visitor}; use rustc_hir::{def::Res, Expr, ExprKind, HirId, Local, MatchSource, Node, PatKind, QPath, UnOp}; @@ -9,66 +10,57 @@ use rustc_lint::LateContext; use rustc_span::{symbol::sym, Span, Symbol}; pub(super) fn check(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - if let ExprKind::Match(scrutinee_expr, [arm, _], MatchSource::WhileLetDesugar) = expr.kind { - let some_pat = match arm.pat.kind { - PatKind::TupleStruct(QPath::Resolved(None, path), sub_pats, _) => match path.res { - Res::Def(_, id) if match_def_path(cx, id, &paths::OPTION_SOME) => sub_pats.first(), - _ => return, - }, - _ => return, - }; - - let iter_expr = match scrutinee_expr.kind { - ExprKind::MethodCall(name, _, [iter_expr], _) - if name.ident.name == sym::next && is_trait_method(cx, scrutinee_expr, sym::Iterator) => - { - if let Some(iter_expr) = try_parse_iter_expr(cx, iter_expr) { - iter_expr - } else { - return; - } - } - _ => return, - }; - - // Needed to find an outer loop, if there are any. - let loop_expr = if let Some((_, Node::Expr(e))) = cx.tcx.hir().parent_iter(expr.hir_id).nth(1) { - e + let (scrutinee_expr, iter_expr, some_pat, loop_expr) = if_chain! { + if let ExprKind::Match(scrutinee_expr, [arm, _], MatchSource::WhileLetDesugar) = expr.kind; + // check for `Some(..)` pattern + if let PatKind::TupleStruct(QPath::Resolved(None, pat_path), some_pat, _) = arm.pat.kind; + if let Res::Def(_, pat_did) = pat_path.res; + if match_def_path(cx, pat_did, &paths::OPTION_SOME); + // check for call to `Iterator::next` + if let ExprKind::MethodCall(method_name, _, [iter_expr], _) = scrutinee_expr.kind; + if method_name.ident.name == sym::next; + if is_trait_method(cx, scrutinee_expr, sym::Iterator); + if let Some(iter_expr) = try_parse_iter_expr(cx, iter_expr); + // get the loop containing the match expression + if let Some((_, Node::Expr(loop_expr))) = cx.tcx.hir().parent_iter(expr.hir_id).nth(1); + if !uses_iter(cx, &iter_expr, arm.body); + then { + (scrutinee_expr, iter_expr, some_pat, loop_expr) } else { return; - }; - - // Refutable patterns don't work with for loops. - // The iterator also can't be accessed withing the loop. - if some_pat.map_or(true, |p| is_refutable(cx, p)) || uses_iter(cx, &iter_expr, arm.body) { - return; } + }; - // If the iterator is a field or the iterator is accessed after the loop is complete it needs to be - // borrowed mutably. - // TODO: If the struct can be partially moved from and the struct isn't used afterwards a mutable - // borrow of a field isn't necessary. - let ref_mut = if !iter_expr.fields.is_empty() || needs_mutable_borrow(cx, &iter_expr, loop_expr) { - "&mut " - } else { - "" - }; - let mut applicability = Applicability::MachineApplicable; - let iterator = snippet_with_applicability(cx, iter_expr.span, "_", &mut applicability); - let loop_var = some_pat.map_or_else( - || "_".into(), - |pat| snippet_with_applicability(cx, pat.span, "_", &mut applicability).into_owned(), - ); - span_lint_and_sugg( - cx, - WHILE_LET_ON_ITERATOR, - expr.span.with_hi(scrutinee_expr.span.hi()), - "this loop could be written as a `for` loop", - "try", - format!("for {} in {}{}", loop_var, ref_mut, iterator), - applicability, - ); - } + let mut applicability = Applicability::MachineApplicable; + let loop_var = if let Some(some_pat) = some_pat.first() { + if is_refutable(cx, some_pat) { + // Refutable patterns don't work with for loops. + return; + } + snippet_with_applicability(cx, some_pat.span, "..", &mut applicability) + } else { + "_".into() + }; + + // If the iterator is a field or the iterator is accessed after the loop is complete it needs to be + // borrowed mutably. TODO: If the struct can be partially moved from and the struct isn't used + // afterwards a mutable borrow of a field isn't necessary. + let ref_mut = if !iter_expr.fields.is_empty() || needs_mutable_borrow(cx, &iter_expr, loop_expr) { + "&mut " + } else { + "" + }; + + let iterator = snippet_with_applicability(cx, iter_expr.span, "_", &mut applicability); + span_lint_and_sugg( + cx, + WHILE_LET_ON_ITERATOR, + expr.span.with_hi(scrutinee_expr.span.hi()), + "this loop could be written as a `for` loop", + "try", + format!("for {} in {}{}", loop_var, ref_mut, iterator), + applicability, + ); } #[derive(Debug)] @@ -135,8 +127,10 @@ fn is_expr_same_field(cx: &LateContext<'_>, mut e: &Expr<'_>, mut fields: &[Symb } } -/// Checks if the given expression is the same field as, is a child of, of the parent of the given -/// field. Used to check if the expression can be used while the given field is borrowed. +/// Checks if the given expression is the same field as, is a child of, or is the parent of the +/// given field. Used to check if the expression can be used while the given field is borrowed +/// mutably. e.g. if checking for `x.y`, then `x.y`, `x.y.z`, and `x` will all return true, but +/// `x.z`, and `y` will return false. fn is_expr_same_child_or_parent_field(cx: &LateContext<'_>, expr: &Expr<'_>, fields: &[Symbol], path_res: Res) -> bool { match expr.kind { ExprKind::Field(base, name) => { @@ -166,7 +160,7 @@ fn is_expr_same_child_or_parent_field(cx: &LateContext<'_>, expr: &Expr<'_>, fie } } }, - // If the path matches, this is either an exact match, of the expression is a parent of the field. + // If the path matches, this is either an exact match, or the expression is a parent of the field. ExprKind::Path(ref path) => cx.qpath_res(path, expr.hir_id) == path_res, ExprKind::DropTemps(base) | ExprKind::Type(base, _) | ExprKind::AddrOf(_, _, base) => { is_expr_same_child_or_parent_field(cx, base, fields, path_res) @@ -175,8 +169,8 @@ fn is_expr_same_child_or_parent_field(cx: &LateContext<'_>, expr: &Expr<'_>, fie } } -/// Strips off all field and path expressions. Used to skip them after failing to check for -/// equality. +/// Strips off all field and path expressions. This will return true if a field or path has been +/// skipped. Used to skip them after failing to check for equality. fn skip_fields_and_path(expr: &'tcx Expr<'_>) -> (Option<&'tcx Expr<'tcx>>, bool) { let mut e = expr; let e = loop { @@ -257,7 +251,7 @@ fn needs_mutable_borrow(cx: &LateContext<'tcx>, iter_expr: &IterExpr, loop_expr: self.visit_expr(e); } } else if let ExprKind::Closure(_, _, id, _, _) = e.kind { - self.used_iter |= is_res_used(self.cx, self.iter_expr.path, id); + self.used_iter = is_res_used(self.cx, self.iter_expr.path, id); } else { walk_expr(self, e); } @@ -309,7 +303,7 @@ fn needs_mutable_borrow(cx: &LateContext<'tcx>, iter_expr: &IterExpr, loop_expr: self.visit_expr(e); } } else if let ExprKind::Closure(_, _, id, _, _) = e.kind { - self.used_after |= is_res_used(self.cx, self.iter_expr.path, id); + self.used_after = is_res_used(self.cx, self.iter_expr.path, id); } else { walk_expr(self, e); } diff --git a/clippy_utils/src/visitors.rs b/clippy_utils/src/visitors.rs index ffd15076026..ecdc666b5f6 100644 --- a/clippy_utils/src/visitors.rs +++ b/clippy_utils/src/visitors.rs @@ -241,7 +241,7 @@ pub fn visit_break_exprs<'tcx>( node.visit(&mut V(f)); } -/// Checks if the given resolved path is used the body. +/// Checks if the given resolved path is used in the given body. pub fn is_res_used(cx: &LateContext<'_>, res: Res, body: BodyId) -> bool { struct V<'a, 'tcx> { cx: &'a LateContext<'tcx>, diff --git a/tests/ui/while_let_on_iterator.fixed b/tests/ui/while_let_on_iterator.fixed index 0562db48a88..389297eff0c 100644 --- a/tests/ui/while_let_on_iterator.fixed +++ b/tests/ui/while_let_on_iterator.fixed @@ -288,6 +288,8 @@ fn issue1924() { for i in &mut self.0.0.0 { if i == 1 { return self.0.1.take(); + } else { + self.0.1 = Some(i); } } None @@ -320,4 +322,9 @@ fn issue1924() { println!("iterator field {}", it.1); } -fn main() {} +fn main() { + let mut it = 0..20; + for _ in it { + println!("test"); + } +} diff --git a/tests/ui/while_let_on_iterator.rs b/tests/ui/while_let_on_iterator.rs index a7c217dc7fd..df932724a0d 100644 --- a/tests/ui/while_let_on_iterator.rs +++ b/tests/ui/while_let_on_iterator.rs @@ -288,6 +288,8 @@ fn issue1924() { while let Some(i) = self.0.0.0.next() { if i == 1 { return self.0.1.take(); + } else { + self.0.1 = Some(i); } } None @@ -320,4 +322,9 @@ fn issue1924() { println!("iterator field {}", it.1); } -fn main() {} +fn main() { + let mut it = 0..20; + while let Some(..) = it.next() { + println!("test"); + } +} diff --git a/tests/ui/while_let_on_iterator.stderr b/tests/ui/while_let_on_iterator.stderr index 0feca257410..e8741f74981 100644 --- a/tests/ui/while_let_on_iterator.stderr +++ b/tests/ui/while_let_on_iterator.stderr @@ -99,10 +99,16 @@ LL | while let Some(i) = self.0.0.0.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for i in &mut self.0.0.0` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:315:5 + --> $DIR/while_let_on_iterator.rs:317:5 | LL | while let Some(n) = it.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for n in &mut it` -error: aborting due to 17 previous errors +error: this loop could be written as a `for` loop + --> $DIR/while_let_on_iterator.rs:327:5 + | +LL | while let Some(..) = it.next() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for _ in it` + +error: aborting due to 18 previous errors From cd0db8a4599619dd43af5f39834a7566d06e5b50 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Wed, 12 May 2021 21:41:59 -0400 Subject: [PATCH 32/58] Fix test comment for `while_let_on_iterator` --- tests/ui/while_let_on_iterator.fixed | 6 ++---- tests/ui/while_let_on_iterator.rs | 6 ++---- tests/ui/while_let_on_iterator.stderr | 22 +++++++++++----------- 3 files changed, 15 insertions(+), 19 deletions(-) diff --git a/tests/ui/while_let_on_iterator.fixed b/tests/ui/while_let_on_iterator.fixed index 389297eff0c..c3e2cf0c4a4 100644 --- a/tests/ui/while_let_on_iterator.fixed +++ b/tests/ui/while_let_on_iterator.fixed @@ -148,10 +148,8 @@ fn issue1121() { } fn issue2965() { - // This should not cause an ICE and suggest: - // - // for _ in values.iter() {} - // + // This should not cause an ICE + use std::collections::HashSet; let mut values = HashSet::new(); values.insert(1); diff --git a/tests/ui/while_let_on_iterator.rs b/tests/ui/while_let_on_iterator.rs index df932724a0d..1717006a449 100644 --- a/tests/ui/while_let_on_iterator.rs +++ b/tests/ui/while_let_on_iterator.rs @@ -148,10 +148,8 @@ fn issue1121() { } fn issue2965() { - // This should not cause an ICE and suggest: - // - // for _ in values.iter() {} - // + // This should not cause an ICE + use std::collections::HashSet; let mut values = HashSet::new(); values.insert(1); diff --git a/tests/ui/while_let_on_iterator.stderr b/tests/ui/while_let_on_iterator.stderr index e8741f74981..eff559bef7e 100644 --- a/tests/ui/while_let_on_iterator.stderr +++ b/tests/ui/while_let_on_iterator.stderr @@ -43,49 +43,49 @@ LL | while let Some(_) = y.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for _ in y` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:193:9 + --> $DIR/while_let_on_iterator.rs:191:9 | LL | while let Some(m) = it.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for m in &mut it` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:204:5 + --> $DIR/while_let_on_iterator.rs:202:5 | LL | while let Some(n) = it.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for n in it` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:206:9 + --> $DIR/while_let_on_iterator.rs:204:9 | LL | while let Some(m) = it.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for m in it` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:215:9 + --> $DIR/while_let_on_iterator.rs:213:9 | LL | while let Some(m) = it.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for m in it` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:224:9 + --> $DIR/while_let_on_iterator.rs:222:9 | LL | while let Some(m) = it.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for m in &mut it` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:241:9 + --> $DIR/while_let_on_iterator.rs:239:9 | LL | while let Some(m) = it.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for m in &mut it` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:256:13 + --> $DIR/while_let_on_iterator.rs:254:13 | LL | while let Some(i) = self.0.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for i in &mut self.0` error: manual `!RangeInclusive::contains` implementation - --> $DIR/while_let_on_iterator.rs:257:20 + --> $DIR/while_let_on_iterator.rs:255:20 | LL | if i < 3 || i > 7 { | ^^^^^^^^^^^^^^ help: use: `!(3..=7).contains(&i)` @@ -93,19 +93,19 @@ LL | if i < 3 || i > 7 { = note: `-D clippy::manual-range-contains` implied by `-D warnings` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:288:13 + --> $DIR/while_let_on_iterator.rs:286:13 | LL | while let Some(i) = self.0.0.0.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for i in &mut self.0.0.0` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:317:5 + --> $DIR/while_let_on_iterator.rs:315:5 | LL | while let Some(n) = it.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for n in &mut it` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:327:5 + --> $DIR/while_let_on_iterator.rs:325:5 | LL | while let Some(..) = it.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for _ in it` From cd241b33cb8379311286f53c4afc6e671ddfded1 Mon Sep 17 00:00:00 2001 From: ThibsG Date: Thu, 13 May 2021 10:24:29 +0200 Subject: [PATCH 33/58] Trigger `wrong_self_convention` only if it has implicit self --- clippy_lints/src/methods/mod.rs | 24 ++++++++++++++---------- tests/ui/wrong_self_convention2.rs | 23 +++++++++++++++++++++++ tests/ui/wrong_self_convention2.stderr | 19 +++++++++++++++++++ 3 files changed, 56 insertions(+), 10 deletions(-) create mode 100644 tests/ui/wrong_self_convention2.stderr diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 0b1b6304def..e0d29682146 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -1838,16 +1838,18 @@ impl<'tcx> LateLintPass<'tcx> for Methods { } } - wrong_self_convention::check( - cx, - &name, - item.vis.node.is_pub(), - self_ty, - first_arg_ty, - first_arg.pat.span, - implements_trait, - false - ); + if sig.decl.implicit_self.has_implicit_self() { + wrong_self_convention::check( + cx, + &name, + item.vis.node.is_pub(), + self_ty, + first_arg_ty, + first_arg.pat.span, + implements_trait, + false + ); + } } } @@ -1903,7 +1905,9 @@ impl<'tcx> LateLintPass<'tcx> for Methods { if_chain! { if let TraitItemKind::Fn(ref sig, _) = item.kind; + if sig.decl.implicit_self.has_implicit_self(); if let Some(first_arg_ty) = sig.decl.inputs.iter().next(); + then { let first_arg_span = first_arg_ty.span; let first_arg_ty = hir_ty_to_ty(cx.tcx, first_arg_ty); diff --git a/tests/ui/wrong_self_convention2.rs b/tests/ui/wrong_self_convention2.rs index 18202ef2989..b2c6503de49 100644 --- a/tests/ui/wrong_self_convention2.rs +++ b/tests/ui/wrong_self_convention2.rs @@ -42,3 +42,26 @@ mod issue7032 { } } } + +mod issue7179 { + pub struct S(i32); + + impl S { + // don't trigger (`s` is not `self`) + pub fn from_be(s: Self) -> Self { + S(i32::from_be(s.0)) + } + + // lint + pub fn from_be_self(self) -> Self { + S(i32::from_be(self.0)) + } + } + + trait T { + // don't trigger (`s` is not `self`) + fn from_be(s: Self) -> Self; + // lint + fn from_be_self(self) -> Self; + } +} diff --git a/tests/ui/wrong_self_convention2.stderr b/tests/ui/wrong_self_convention2.stderr new file mode 100644 index 00000000000..d2d74ce099e --- /dev/null +++ b/tests/ui/wrong_self_convention2.stderr @@ -0,0 +1,19 @@ +error: methods called `from_*` usually take no `self` + --> $DIR/wrong_self_convention2.rs:56:29 + | +LL | pub fn from_be_self(self) -> Self { + | ^^^^ + | + = note: `-D clippy::wrong-self-convention` implied by `-D warnings` + = help: consider choosing a less ambiguous name + +error: methods called `from_*` usually take no `self` + --> $DIR/wrong_self_convention2.rs:65:25 + | +LL | fn from_be_self(self) -> Self; + | ^^^^ + | + = help: consider choosing a less ambiguous name + +error: aborting due to 2 previous errors + From 368e621265ab7f3066d1e3e56b0633bca3f7e831 Mon Sep 17 00:00:00 2001 From: ThibsG Date: Thu, 13 May 2021 11:50:25 +0200 Subject: [PATCH 34/58] Stop linting `else if let` pattern in `option_if_let_else` lint --- clippy_lints/src/option_if_let_else.rs | 4 +++- tests/ui/option_if_let_else.fixed | 6 +++++- tests/ui/option_if_let_else.stderr | 13 +------------ 3 files changed, 9 insertions(+), 14 deletions(-) diff --git a/clippy_lints/src/option_if_let_else.rs b/clippy_lints/src/option_if_let_else.rs index e527adbb892..2d98b275de7 100644 --- a/clippy_lints/src/option_if_let_else.rs +++ b/clippy_lints/src/option_if_let_else.rs @@ -2,7 +2,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::sugg::Sugg; use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::usage::contains_return_break_continue_macro; -use clippy_utils::{eager_or_lazy, get_enclosing_block, in_macro, is_lang_ctor}; +use clippy_utils::{eager_or_lazy, get_enclosing_block, in_macro, is_else_clause, is_lang_ctor}; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::LangItem::OptionSome; @@ -161,6 +161,7 @@ fn detect_option_if_let_else<'tcx>( if_chain! { if !in_macro(expr.span); // Don't lint macros, because it behaves weirdly if let ExprKind::Match(cond_expr, arms, MatchSource::IfLetDesugar{contains_else_clause: true}) = &expr.kind; + if !is_else_clause(cx.tcx, expr); if arms.len() == 2; if !is_result_ok(cx, cond_expr); // Don't lint on Result::ok because a different lint does it already if let PatKind::TupleStruct(struct_qpath, &[inner_pat], _) = &arms[0].pat.kind; @@ -168,6 +169,7 @@ fn detect_option_if_let_else<'tcx>( if let PatKind::Binding(bind_annotation, _, id, _) = &inner_pat.kind; if !contains_return_break_continue_macro(arms[0].body); if !contains_return_break_continue_macro(arms[1].body); + then { let capture_mut = if bind_annotation == &BindingAnnotation::Mutable { "mut " } else { "" }; let some_body = extract_body_from_arm(&arms[0])?; diff --git a/tests/ui/option_if_let_else.fixed b/tests/ui/option_if_let_else.fixed index 47e7460fa7a..769ccc14bc1 100644 --- a/tests/ui/option_if_let_else.fixed +++ b/tests/ui/option_if_let_else.fixed @@ -10,7 +10,11 @@ fn bad1(string: Option<&str>) -> (bool, &str) { fn else_if_option(string: Option<&str>) -> Option<(bool, &str)> { if string.is_none() { None - } else { string.map_or(Some((false, "")), |x| Some((true, x))) } + } else if let Some(x) = string { + Some((true, x)) + } else { + Some((false, "")) + } } fn unop_bad(string: &Option<&str>, mut num: Option) { diff --git a/tests/ui/option_if_let_else.stderr b/tests/ui/option_if_let_else.stderr index 7aab068800a..4ebb068d22e 100644 --- a/tests/ui/option_if_let_else.stderr +++ b/tests/ui/option_if_let_else.stderr @@ -10,17 +10,6 @@ LL | | } | = note: `-D clippy::option-if-let-else` implied by `-D warnings` -error: use Option::map_or instead of an if let/else - --> $DIR/option_if_let_else.rs:17:12 - | -LL | } else if let Some(x) = string { - | ____________^ -LL | | Some((true, x)) -LL | | } else { -LL | | Some((false, "")) -LL | | } - | |_____^ help: try: `{ string.map_or(Some((false, "")), |x| Some((true, x))) }` - error: use Option::map_or instead of an if let/else --> $DIR/option_if_let_else.rs:25:13 | @@ -159,5 +148,5 @@ error: use Option::map_or instead of an if let/else LL | let _ = if let Some(x) = optional { x + 2 } else { 5 }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `optional.map_or(5, |x| x + 2)` -error: aborting due to 12 previous errors +error: aborting due to 11 previous errors From b24929044877b0a3710f40b3cf27b61d79b5d5df Mon Sep 17 00:00:00 2001 From: Mateusz Gacek <96mateusz.gacek@gmail.com> Date: Thu, 13 May 2021 17:09:59 +0200 Subject: [PATCH 35/58] needless_collect: use snippet_with_applicability + small code refactor - using early returns. --- clippy_lints/src/loops/needless_collect.rs | 37 +++++++++++----------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/clippy_lints/src/loops/needless_collect.rs b/clippy_lints/src/loops/needless_collect.rs index 89c95f3d127..d3406780888 100644 --- a/clippy_lints/src/loops/needless_collect.rs +++ b/clippy_lints/src/loops/needless_collect.rs @@ -1,6 +1,6 @@ use super::NEEDLESS_COLLECT; use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; -use clippy_utils::source::snippet; +use clippy_utils::source::{snippet, snippet_with_applicability}; use clippy_utils::sugg::Sugg; use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::{is_trait_method, path_to_local_id}; @@ -28,46 +28,45 @@ fn check_needless_collect_direct_usage<'tcx>(expr: &'tcx Expr<'_>, cx: &LateCont if let Some(GenericArg::Type(ref ty)) = generic_args.args.get(0); if let Some(ty) = cx.typeck_results().node_type_opt(ty.hir_id); then { - let is_empty_sugg = Some("next().is_none()".to_string()); + let mut applicability = Applicability::MachineApplicable; + let is_empty_sugg = "next().is_none()".to_string(); let method_name = &*method.ident.name.as_str(); let sugg = if is_type_diagnostic_item(cx, ty, sym::vec_type) || is_type_diagnostic_item(cx, ty, sym::vecdeque_type) || is_type_diagnostic_item(cx, ty, sym::LinkedList) || is_type_diagnostic_item(cx, ty, sym::BinaryHeap) { match method_name { - "len" => Some("count()".to_string()), + "len" => "count()".to_string(), "is_empty" => is_empty_sugg, "contains" => { - let contains_arg = snippet(cx, args[1].span, "??"); + let contains_arg = snippet_with_applicability(cx, args[1].span, "??", &mut applicability); let (arg, pred) = contains_arg .strip_prefix('&') .map_or(("&x", &*contains_arg), |s| ("x", s)); - Some(format!("any(|{}| x == {})", arg, pred)) + format!("any(|{}| x == {})", arg, pred) } - _ => None, + _ => return, } } else if is_type_diagnostic_item(cx, ty, sym::BTreeMap) || is_type_diagnostic_item(cx, ty, sym::hashmap_type) { match method_name { "is_empty" => is_empty_sugg, - _ => None, + _ => return, } } else { - None + return; }; - if let Some(sugg) = sugg { - span_lint_and_sugg( - cx, - NEEDLESS_COLLECT, - method0_span.with_hi(expr.span.hi()), - NEEDLESS_COLLECT_MSG, - "replace with", - sugg, - Applicability::MachineApplicable, - ); - } + span_lint_and_sugg( + cx, + NEEDLESS_COLLECT, + method0_span.with_hi(expr.span.hi()), + NEEDLESS_COLLECT_MSG, + "replace with", + sugg, + applicability, + ); } } } From 18c702955b6c557fe864d73964c472f9455c2208 Mon Sep 17 00:00:00 2001 From: ThibsG Date: Thu, 13 May 2021 23:28:40 +0200 Subject: [PATCH 36/58] Add sized trait for `wrong_self_convention` lint test --- tests/ui/wrong_self_convention2.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/ui/wrong_self_convention2.rs b/tests/ui/wrong_self_convention2.rs index b2c6503de49..3a72174d03d 100644 --- a/tests/ui/wrong_self_convention2.rs +++ b/tests/ui/wrong_self_convention2.rs @@ -64,4 +64,8 @@ mod issue7179 { // lint fn from_be_self(self) -> Self; } + + trait Foo: Sized { + fn as_byte_slice(slice: &[Self]) -> &[u8]; + } } From 7d83f98ff3d6019bd255715ff5f2ba7000d2018b Mon Sep 17 00:00:00 2001 From: ayushmishra2005 Date: Fri, 14 May 2021 08:57:33 +0530 Subject: [PATCH 37/58] Improve match statements --- tests/ui-toml/min_rust_version/min_rust_version.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/ui-toml/min_rust_version/min_rust_version.rs b/tests/ui-toml/min_rust_version/min_rust_version.rs index bc41efa42a1..bf7da4015d3 100644 --- a/tests/ui-toml/min_rust_version/min_rust_version.rs +++ b/tests/ui-toml/min_rust_version/min_rust_version.rs @@ -31,10 +31,7 @@ fn option_as_ref_deref() { } fn match_like_matches() { - let _y = match Some(5) { - Some(0) => true, - _ => false, - }; + let _y = matches!(Some(5), Some(0)); } fn match_same_arms() { From 9013bf299ed296dd04c272f005a2495e0c9fea66 Mon Sep 17 00:00:00 2001 From: ayushmishra2005 Date: Fri, 14 May 2021 17:30:26 +0530 Subject: [PATCH 38/58] Addressed PR coments --- tests/ui-toml/min_rust_version/min_rust_version.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tests/ui-toml/min_rust_version/min_rust_version.rs b/tests/ui-toml/min_rust_version/min_rust_version.rs index bf7da4015d3..bc41efa42a1 100644 --- a/tests/ui-toml/min_rust_version/min_rust_version.rs +++ b/tests/ui-toml/min_rust_version/min_rust_version.rs @@ -31,7 +31,10 @@ fn option_as_ref_deref() { } fn match_like_matches() { - let _y = matches!(Some(5), Some(0)); + let _y = match Some(5) { + Some(0) => true, + _ => false, + }; } fn match_same_arms() { From c6e0e843d279c0c4703e2b3326826423b791dee2 Mon Sep 17 00:00:00 2001 From: Jackson Lewis Date: Fri, 14 May 2021 16:45:18 -0700 Subject: [PATCH 39/58] Implement unnecessary-async and UI test --- clippy_lints/src/lib.rs | 4 ++ clippy_lints/src/unnecessary_async.rs | 92 +++++++++++++++++++++++++++ tests/ui/unnecessary_async.rs | 15 +++++ tests/ui/unnecessary_async.stderr | 13 ++++ 4 files changed, 124 insertions(+) create mode 100644 clippy_lints/src/unnecessary_async.rs create mode 100644 tests/ui/unnecessary_async.rs create mode 100644 tests/ui/unnecessary_async.stderr diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 1a74f641554..e8b8b2e9117 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -360,6 +360,7 @@ mod unnamed_address; mod unnecessary_self_imports; mod unnecessary_sort_by; mod unnecessary_wraps; +mod unnecessary_async; mod unnested_or_patterns; mod unsafe_removed_from_name; mod unused_io_amount; @@ -954,6 +955,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: unit_types::UNIT_CMP, unnamed_address::FN_ADDRESS_COMPARISONS, unnamed_address::VTABLE_ADDRESS_COMPARISONS, + unnecessary_async::UNNECESSARY_ASYNC, unnecessary_self_imports::UNNECESSARY_SELF_IMPORTS, unnecessary_sort_by::UNNECESSARY_SORT_BY, unnecessary_wraps::UNNECESSARY_WRAPS, @@ -1271,6 +1273,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| box manual_map::ManualMap); store.register_late_pass(move || box if_then_some_else_none::IfThenSomeElseNone::new(msrv)); store.register_early_pass(|| box bool_assert_comparison::BoolAssertComparison); + store.register_late_pass(|| box unnecessary_async::UnnecessaryAsync); store.register_group(true, "clippy::restriction", Some("clippy_restriction"), vec![ LintId::of(arithmetic::FLOAT_ARITHMETIC), @@ -1412,6 +1415,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(unicode::NON_ASCII_LITERAL), LintId::of(unicode::UNICODE_NOT_NFC), LintId::of(unit_types::LET_UNIT_VALUE), + LintId::of(unnecessary_async::UNNECESSARY_ASYNC), LintId::of(unnecessary_wraps::UNNECESSARY_WRAPS), LintId::of(unnested_or_patterns::UNNESTED_OR_PATTERNS), LintId::of(unused_self::UNUSED_SELF), diff --git a/clippy_lints/src/unnecessary_async.rs b/clippy_lints/src/unnecessary_async.rs new file mode 100644 index 00000000000..15a90ba42d2 --- /dev/null +++ b/clippy_lints/src/unnecessary_async.rs @@ -0,0 +1,92 @@ +use clippy_utils::diagnostics::span_lint_and_help; +use rustc_hir::intravisit::{walk_expr, walk_fn, FnKind, NestedVisitorMap, Visitor}; +use rustc_hir::{Body, Expr, ExprKind, FnDecl, FnHeader, HirId, IsAsync, Item, ItemKind, YieldSource}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::hir::map::Map; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_span::Span; + +declare_clippy_lint! { + /// **What it does:** Checks for functions that are declared `async` but have no `.await`s inside of them. + /// + /// **Why is this bad?** Async functions with no async code create overhead, both mentally and computationally. + /// Callers of async methods either need to be calling from an async function themselves or run it on an executor, both of which + /// causes runtime overhead and hassle for the caller. + /// + /// **Known problems:** None + /// + /// **Example:** + /// + /// ```rust + /// // Bad + /// async fn get_random_number() -> i64 { + /// 4 // Chosen by fair dice roll. Guaranteed to be random. + /// } + /// let number_future = get_random_number(); + /// + /// // Good + /// fn get_random_number_improved() -> i64 { + /// 4 // Chosen by fair dice roll. Guaranteed to be random. + /// } + /// let number_future = async { get_random_number_improved() }; + /// ``` + pub UNNECESSARY_ASYNC, + pedantic, + "finds async functions with no await statements" +} + +declare_lint_pass!(UnnecessaryAsync => [UNNECESSARY_ASYNC]); + +struct AsyncFnVisitor<'a, 'tcx> { + cx: &'a LateContext<'tcx>, + found_await: bool, +} + +impl<'a, 'tcx> Visitor<'tcx> for AsyncFnVisitor<'a, 'tcx> { + type Map = Map<'tcx>; + + fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) { + if let ExprKind::Yield(_, YieldSource::Await { .. }) = ex.kind { + self.found_await = true; + } + walk_expr(self, ex); + } + + fn nested_visit_map(&mut self) -> NestedVisitorMap { + NestedVisitorMap::OnlyBodies(self.cx.tcx.hir()) + } +} + +impl<'tcx> LateLintPass<'tcx> for UnnecessaryAsync { + fn check_item(&mut self, _: &LateContext<'tcx>, item: &'tcx Item<'tcx>) { + if let ItemKind::Trait(..) = item.kind { + return; + } + } + fn check_fn( + &mut self, + cx: &LateContext<'tcx>, + fn_kind: FnKind<'tcx>, + fn_decl: &'tcx FnDecl<'tcx>, + body: &Body<'tcx>, + span: Span, + hir_id: HirId, + ) { + if let FnKind::ItemFn(_, _, FnHeader { asyncness, .. }, _) = &fn_kind { + if matches!(asyncness, IsAsync::Async) { + let mut visitor = AsyncFnVisitor { cx, found_await: false }; + walk_fn(&mut visitor, fn_kind, fn_decl, body.id(), span, hir_id); + if !visitor.found_await { + span_lint_and_help( + cx, + UNNECESSARY_ASYNC, + span, + "unnecessary `async` for function with no await statements", + None, + "consider removing the `async` from this function", + ); + } + } + } + } +} diff --git a/tests/ui/unnecessary_async.rs b/tests/ui/unnecessary_async.rs new file mode 100644 index 00000000000..7d63083b13e --- /dev/null +++ b/tests/ui/unnecessary_async.rs @@ -0,0 +1,15 @@ +// edition:2018 +#![warn(clippy::unnecessary_async)] + +async fn foo() -> i32 { + 4 +} + +async fn bar() -> i32 { + foo().await +} + +fn main() { + foo(); + bar(); +} diff --git a/tests/ui/unnecessary_async.stderr b/tests/ui/unnecessary_async.stderr new file mode 100644 index 00000000000..5542580e45d --- /dev/null +++ b/tests/ui/unnecessary_async.stderr @@ -0,0 +1,13 @@ +error: unnecessary `async` for function with no await statements + --> $DIR/unnecessary_async.rs:4:1 + | +LL | / async fn foo() -> i32 { +LL | | 4 +LL | | } + | |_^ + | + = note: `-D clippy::unnecessary-async` implied by `-D warnings` + = help: consider removing the `async` from this function + +error: aborting due to previous error + From 1d8f3b51e662bb66e2b696d0c13057d6ba89f979 Mon Sep 17 00:00:00 2001 From: Jackson Lewis Date: Fri, 14 May 2021 17:07:30 -0700 Subject: [PATCH 40/58] Unnecessary -> Unused --- clippy_lints/src/lib.rs | 8 ++++---- .../src/{unnecessary_async.rs => unused_async.rs} | 10 +++++----- tests/ui/{unnecessary_async.rs => unused_async.rs} | 2 +- .../{unnecessary_async.stderr => unused_async.stderr} | 6 +++--- 4 files changed, 13 insertions(+), 13 deletions(-) rename clippy_lints/src/{unnecessary_async.rs => unused_async.rs} (91%) rename tests/ui/{unnecessary_async.rs => unused_async.rs} (78%) rename tests/ui/{unnecessary_async.stderr => unused_async.stderr} (50%) diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index e8b8b2e9117..52e64d6a48f 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -360,7 +360,7 @@ mod unnamed_address; mod unnecessary_self_imports; mod unnecessary_sort_by; mod unnecessary_wraps; -mod unnecessary_async; +mod unused_async; mod unnested_or_patterns; mod unsafe_removed_from_name; mod unused_io_amount; @@ -955,7 +955,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: unit_types::UNIT_CMP, unnamed_address::FN_ADDRESS_COMPARISONS, unnamed_address::VTABLE_ADDRESS_COMPARISONS, - unnecessary_async::UNNECESSARY_ASYNC, + unused_async::UNUSED_ASYNC, unnecessary_self_imports::UNNECESSARY_SELF_IMPORTS, unnecessary_sort_by::UNNECESSARY_SORT_BY, unnecessary_wraps::UNNECESSARY_WRAPS, @@ -1273,7 +1273,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| box manual_map::ManualMap); store.register_late_pass(move || box if_then_some_else_none::IfThenSomeElseNone::new(msrv)); store.register_early_pass(|| box bool_assert_comparison::BoolAssertComparison); - store.register_late_pass(|| box unnecessary_async::UnnecessaryAsync); + store.register_late_pass(|| box unused_async::UnusedAsync); store.register_group(true, "clippy::restriction", Some("clippy_restriction"), vec![ LintId::of(arithmetic::FLOAT_ARITHMETIC), @@ -1415,7 +1415,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(unicode::NON_ASCII_LITERAL), LintId::of(unicode::UNICODE_NOT_NFC), LintId::of(unit_types::LET_UNIT_VALUE), - LintId::of(unnecessary_async::UNNECESSARY_ASYNC), + LintId::of(unused_async::UNUSED_ASYNC), LintId::of(unnecessary_wraps::UNNECESSARY_WRAPS), LintId::of(unnested_or_patterns::UNNESTED_OR_PATTERNS), LintId::of(unused_self::UNUSED_SELF), diff --git a/clippy_lints/src/unnecessary_async.rs b/clippy_lints/src/unused_async.rs similarity index 91% rename from clippy_lints/src/unnecessary_async.rs rename to clippy_lints/src/unused_async.rs index 15a90ba42d2..18ee07d3a95 100644 --- a/clippy_lints/src/unnecessary_async.rs +++ b/clippy_lints/src/unused_async.rs @@ -30,12 +30,12 @@ declare_clippy_lint! { /// } /// let number_future = async { get_random_number_improved() }; /// ``` - pub UNNECESSARY_ASYNC, + pub UNUSED_ASYNC, pedantic, "finds async functions with no await statements" } -declare_lint_pass!(UnnecessaryAsync => [UNNECESSARY_ASYNC]); +declare_lint_pass!(UnusedAsync => [UNUSED_ASYNC]); struct AsyncFnVisitor<'a, 'tcx> { cx: &'a LateContext<'tcx>, @@ -57,7 +57,7 @@ impl<'a, 'tcx> Visitor<'tcx> for AsyncFnVisitor<'a, 'tcx> { } } -impl<'tcx> LateLintPass<'tcx> for UnnecessaryAsync { +impl<'tcx> LateLintPass<'tcx> for UnusedAsync { fn check_item(&mut self, _: &LateContext<'tcx>, item: &'tcx Item<'tcx>) { if let ItemKind::Trait(..) = item.kind { return; @@ -79,9 +79,9 @@ impl<'tcx> LateLintPass<'tcx> for UnnecessaryAsync { if !visitor.found_await { span_lint_and_help( cx, - UNNECESSARY_ASYNC, + UNUSED_ASYNC, span, - "unnecessary `async` for function with no await statements", + "unused `async` for function with no await statements", None, "consider removing the `async` from this function", ); diff --git a/tests/ui/unnecessary_async.rs b/tests/ui/unused_async.rs similarity index 78% rename from tests/ui/unnecessary_async.rs rename to tests/ui/unused_async.rs index 7d63083b13e..4f4203f5fdb 100644 --- a/tests/ui/unnecessary_async.rs +++ b/tests/ui/unused_async.rs @@ -1,5 +1,5 @@ // edition:2018 -#![warn(clippy::unnecessary_async)] +#![warn(clippy::unused_async)] async fn foo() -> i32 { 4 diff --git a/tests/ui/unnecessary_async.stderr b/tests/ui/unused_async.stderr similarity index 50% rename from tests/ui/unnecessary_async.stderr rename to tests/ui/unused_async.stderr index 5542580e45d..8b834d205b1 100644 --- a/tests/ui/unnecessary_async.stderr +++ b/tests/ui/unused_async.stderr @@ -1,12 +1,12 @@ -error: unnecessary `async` for function with no await statements - --> $DIR/unnecessary_async.rs:4:1 +error: unused `async` for function with no await statements + --> $DIR/unused_async.rs:4:1 | LL | / async fn foo() -> i32 { LL | | 4 LL | | } | |_^ | - = note: `-D clippy::unnecessary-async` implied by `-D warnings` + = note: `-D clippy::unused-async` implied by `-D warnings` = help: consider removing the `async` from this function error: aborting due to previous error From 75ef9dc708e669bccbc18c3be4873c613058ef30 Mon Sep 17 00:00:00 2001 From: Jackson Lewis Date: Fri, 14 May 2021 17:12:25 -0700 Subject: [PATCH 41/58] update_lints --- CHANGELOG.md | 1 + clippy_lints/src/lib.rs | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 204d56e2a98..c922cc7e763 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2538,6 +2538,7 @@ Released 2018-09-13 [`unsound_collection_transmute`]: https://rust-lang.github.io/rust-clippy/master/index.html#unsound_collection_transmute [`unstable_as_mut_slice`]: https://rust-lang.github.io/rust-clippy/master/index.html#unstable_as_mut_slice [`unstable_as_slice`]: https://rust-lang.github.io/rust-clippy/master/index.html#unstable_as_slice +[`unused_async`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_async [`unused_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_collect [`unused_io_amount`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_io_amount [`unused_self`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_self diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 52e64d6a48f..2f5642f24f2 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -360,9 +360,9 @@ mod unnamed_address; mod unnecessary_self_imports; mod unnecessary_sort_by; mod unnecessary_wraps; -mod unused_async; mod unnested_or_patterns; mod unsafe_removed_from_name; +mod unused_async; mod unused_io_amount; mod unused_self; mod unused_unit; @@ -955,12 +955,12 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: unit_types::UNIT_CMP, unnamed_address::FN_ADDRESS_COMPARISONS, unnamed_address::VTABLE_ADDRESS_COMPARISONS, - unused_async::UNUSED_ASYNC, unnecessary_self_imports::UNNECESSARY_SELF_IMPORTS, unnecessary_sort_by::UNNECESSARY_SORT_BY, unnecessary_wraps::UNNECESSARY_WRAPS, unnested_or_patterns::UNNESTED_OR_PATTERNS, unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME, + unused_async::UNUSED_ASYNC, unused_io_amount::UNUSED_IO_AMOUNT, unused_self::UNUSED_SELF, unused_unit::UNUSED_UNIT, @@ -1415,9 +1415,9 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: LintId::of(unicode::NON_ASCII_LITERAL), LintId::of(unicode::UNICODE_NOT_NFC), LintId::of(unit_types::LET_UNIT_VALUE), - LintId::of(unused_async::UNUSED_ASYNC), LintId::of(unnecessary_wraps::UNNECESSARY_WRAPS), LintId::of(unnested_or_patterns::UNNESTED_OR_PATTERNS), + LintId::of(unused_async::UNUSED_ASYNC), LintId::of(unused_self::UNUSED_SELF), LintId::of(wildcard_imports::ENUM_GLOB_USE), LintId::of(wildcard_imports::WILDCARD_IMPORTS), From f810c11d3c44af357ad7af6cf9fc9ab582591c01 Mon Sep 17 00:00:00 2001 From: xFrednet Date: Sat, 15 May 2021 19:00:49 +0200 Subject: [PATCH 42/58] Applying PR suggestions and cleaning up --- clippy_lints/src/utils/conf.rs | 27 ++++---- .../internal_lints/metadata_collector.rs | 61 ++++++++----------- 2 files changed, 39 insertions(+), 49 deletions(-) diff --git a/clippy_lints/src/utils/conf.rs b/clippy_lints/src/utils/conf.rs index 25163bb9f37..fd2dddb3b96 100644 --- a/clippy_lints/src/utils/conf.rs +++ b/clippy_lints/src/utils/conf.rs @@ -92,25 +92,26 @@ macro_rules! define_Conf { #[cfg(feature = "metadata-collector-lint")] pub mod metadata { - use crate::utils::internal_lints::metadata_collector::ClippyConfigurationBasicInfo; + use crate::utils::internal_lints::metadata_collector::ClippyConfiguration; - pub(crate) fn get_configuration_metadata() -> Vec { + macro_rules! wrap_option { + () => (None); + ($x:literal) => (Some($x)); + } + + pub(crate) fn get_configuration_metadata() -> Vec { vec![ $( { - #[allow(unused_mut, unused_assignments)] - let mut deprecation_reason = None; + let deprecation_reason = wrap_option!($($dep)?); - // only set if a deprecation reason was set - $(deprecation_reason = Some(stringify!($dep));)? - - ClippyConfigurationBasicInfo { - name: stringify!($name), - config_type: stringify!($ty), - default: stringify!($default), - doc_comment: $doc, + ClippyConfiguration::new( + stringify!($name), + stringify!($ty), + format!("{:?}", super::defaults::$name()), + $doc, deprecation_reason, - } + ) }, )+ ] diff --git a/clippy_lints/src/utils/internal_lints/metadata_collector.rs b/clippy_lints/src/utils/internal_lints/metadata_collector.rs index bd0de8ad034..b7c86ae6e18 100644 --- a/clippy_lints/src/utils/internal_lints/metadata_collector.rs +++ b/clippy_lints/src/utils/internal_lints/metadata_collector.rs @@ -149,7 +149,8 @@ impl MetadataCollector { fn get_lint_configs(&self, lint_name: &str) -> Option { self.config .iter() - .filter_map(|x| x.lints.iter().any(|x| x == lint_name).then(|| format!("{}", x))) + .filter(|config| config.lints.iter().any(|lint| lint == lint_name)) + .map(ToString::to_string) .reduce(|acc, x| acc + &x) .map(|configurations| format!(CONFIGURATION_SECTION_TEMPLATE!(), configurations = configurations)) } @@ -261,52 +262,40 @@ impl Serialize for ApplicabilityInfo { // ================================================================== // Configuration // ================================================================== -#[derive(Debug)] -pub(crate) struct ClippyConfigurationBasicInfo { - pub name: &'static str, - pub config_type: &'static str, - pub default: &'static str, - pub doc_comment: &'static str, - pub deprecation_reason: Option<&'static str>, -} - #[derive(Debug, Clone, Default)] -struct ClippyConfiguration { +pub struct ClippyConfiguration { name: String, - lints: Vec, - doc: String, config_type: &'static str, default: String, + lints: Vec, + doc: String, deprecation_reason: Option<&'static str>, } -fn collect_configs() -> Vec { - let cons = crate::utils::conf::metadata::get_configuration_metadata(); - cons.iter() - .map(move |x| { - let (lints, doc) = parse_config_field_doc(x.doc_comment) - .unwrap_or_else(|| (vec![], "[ERROR] MALFORMED DOC COMMENT".to_string())); +impl ClippyConfiguration { + pub fn new( + name: &'static str, + config_type: &'static str, + default: String, + doc_comment: &'static str, + deprecation_reason: Option<&'static str>, + ) -> Self { + let (lints, doc) = parse_config_field_doc(doc_comment) + .unwrap_or_else(|| (vec![], "[ERROR] MALFORMED DOC COMMENT".to_string())); - ClippyConfiguration { - name: to_kebab(x.name), - lints, - doc, - config_type: x.config_type, - default: clarify_default(x.default), - deprecation_reason: x.deprecation_reason, - } - }) - .collect() -} - -fn clarify_default(default: &'static str) -> String { - if let Some((_start, init)) = default.split_once('[') { - if let Some((init, _end)) = init.split_once(']') { - return format!("[{}]", init); + Self { + name: to_kebab(name), + lints, + doc, + config_type, + default, + deprecation_reason, } } +} - default.to_string() +fn collect_configs() -> Vec { + crate::utils::conf::metadata::get_configuration_metadata() } /// This parses the field documentation of the config struct. From ebf065e5172227a99743f4032c5bfff80d5630e6 Mon Sep 17 00:00:00 2001 From: Takayuki Nakata Date: Mon, 17 May 2021 22:18:50 +0900 Subject: [PATCH 43/58] Fix a `manual_unwrap_or` FP with deref coercion --- clippy_lints/src/manual_unwrap_or.rs | 3 +++ tests/ui/manual_unwrap_or.fixed | 8 ++++++++ tests/ui/manual_unwrap_or.rs | 8 ++++++++ 3 files changed, 19 insertions(+) diff --git a/clippy_lints/src/manual_unwrap_or.rs b/clippy_lints/src/manual_unwrap_or.rs index 520162559e5..cf183d4c16f 100644 --- a/clippy_lints/src/manual_unwrap_or.rs +++ b/clippy_lints/src/manual_unwrap_or.rs @@ -11,6 +11,7 @@ use rustc_hir::{Arm, Expr, ExprKind, PatKind}; use rustc_lint::LintContext; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::lint::in_external_macro; +use rustc_middle::ty::adjustment::Adjust; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::sym; @@ -87,6 +88,8 @@ fn lint_manual_unwrap_or<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { if let PatKind::Binding(_, binding_hir_id, ..) = unwrap_pat.kind; if path_to_local_id(unwrap_arm.body, binding_hir_id); if !contains_return_break_continue_macro(or_arm.body); + if !cx.typeck_results().expr_adjustments(unwrap_arm.body).iter() + .any(|a| matches!(a.kind, Adjust::Deref(Some(..)))); then { Some(or_arm) } else { diff --git a/tests/ui/manual_unwrap_or.fixed b/tests/ui/manual_unwrap_or.fixed index e7a29596b73..1efecb0ba12 100644 --- a/tests/ui/manual_unwrap_or.fixed +++ b/tests/ui/manual_unwrap_or.fixed @@ -163,4 +163,12 @@ mod issue6965 { } } +use std::rc::Rc; +fn format_name(name: Option<&Rc>) -> &str { + match name { + None => "", + Some(name) => name, + } +} + fn main() {} diff --git a/tests/ui/manual_unwrap_or.rs b/tests/ui/manual_unwrap_or.rs index 66006b6c616..95d585ad18a 100644 --- a/tests/ui/manual_unwrap_or.rs +++ b/tests/ui/manual_unwrap_or.rs @@ -205,4 +205,12 @@ mod issue6965 { } } +use std::rc::Rc; +fn format_name(name: Option<&Rc>) -> &str { + match name { + None => "", + Some(name) => name, + } +} + fn main() {} From 0dcde712246d417c9cdfedb73b518edaa2b8c60a Mon Sep 17 00:00:00 2001 From: Arya Kumar Date: Mon, 17 May 2021 15:00:15 +0000 Subject: [PATCH 44/58] Fix missing variable init in lint example --- clippy_lints/src/needless_bitwise_bool.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/clippy_lints/src/needless_bitwise_bool.rs b/clippy_lints/src/needless_bitwise_bool.rs index 95febf4a2ad..b30bfbd4294 100644 --- a/clippy_lints/src/needless_bitwise_bool.rs +++ b/clippy_lints/src/needless_bitwise_bool.rs @@ -24,10 +24,12 @@ declare_clippy_lint! { /// **Example:** /// /// ```rust + /// let (x,y) = (true, false); /// if x & !y {} // where both x and y are booleans /// ``` /// Use instead: /// ```rust + /// let (x,y) = (true, false); /// if x && !y {} /// ``` pub NEEDLESS_BITWISE_BOOL, From 2fb35ce4f0ea8d33bbe207c8a1c8822ebb90c813 Mon Sep 17 00:00:00 2001 From: ThibsG Date: Thu, 13 May 2021 21:40:20 +0200 Subject: [PATCH 45/58] Add generic args for comparison in `use_self` and `useless_conversion` lints --- clippy_lints/src/use_self.rs | 10 ++++----- clippy_lints/src/useless_conversion.rs | 15 +++++++------ clippy_utils/src/ty.rs | 24 +++++++++++++++++++++ tests/ui/use_self.fixed | 30 ++++++++++++++++++++++++++ tests/ui/use_self.rs | 30 ++++++++++++++++++++++++++ tests/ui/use_self.stderr | 8 ++++++- tests/ui/useless_conversion.fixed | 19 ++++++++++++++++ tests/ui/useless_conversion.rs | 19 ++++++++++++++++ tests/ui/useless_conversion.stderr | 20 ++++++++++++++++- 9 files changed, 161 insertions(+), 14 deletions(-) diff --git a/clippy_lints/src/use_self.rs b/clippy_lints/src/use_self.rs index aa4d16633ff..2ad6fa77f48 100644 --- a/clippy_lints/src/use_self.rs +++ b/clippy_lints/src/use_self.rs @@ -1,12 +1,12 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_opt; +use clippy_utils::ty::same_type_and_consts; use clippy_utils::{in_macro, meets_msrv, msrvs}; use if_chain::if_chain; use rustc_errors::Applicability; -use rustc_hir as hir; -use rustc_hir::def::DefKind; use rustc_hir::{ - def, + self as hir, + def::{self, DefKind}, def_id::LocalDefId, intravisit::{walk_ty, NestedVisitorMap, Visitor}, Expr, ExprKind, FnRetTy, FnSig, GenericArg, HirId, Impl, ImplItemKind, Item, ItemKind, Node, Path, PathSegment, @@ -14,7 +14,7 @@ use rustc_hir::{ }; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::hir::map::Map; -use rustc_middle::ty::{AssocKind, Ty, TyS}; +use rustc_middle::ty::{AssocKind, Ty}; use rustc_semver::RustcVersion; use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::{BytePos, Span}; @@ -459,7 +459,7 @@ fn in_impl(cx: &LateContext<'tcx>, hir_ty: &hir::Ty<'_>) -> bool { fn should_lint_ty(hir_ty: &hir::Ty<'_>, ty: Ty<'_>, self_ty: Ty<'_>) -> bool { if_chain! { - if TyS::same_type(ty, self_ty); + if same_type_and_consts(ty, self_ty); if let TyKind::Path(QPath::Resolved(_, path)) = hir_ty.kind; then { !matches!(path.res, def::Res::SelfTy(..)) diff --git a/clippy_lints/src/useless_conversion.rs b/clippy_lints/src/useless_conversion.rs index 7edb280be73..2be99fb761b 100644 --- a/clippy_lints/src/useless_conversion.rs +++ b/clippy_lints/src/useless_conversion.rs @@ -1,13 +1,13 @@ use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg}; use clippy_utils::source::{snippet, snippet_with_macro_callsite}; use clippy_utils::sugg::Sugg; -use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::ty::{is_type_diagnostic_item, same_type_and_consts}; use clippy_utils::{get_parent_expr, match_def_path, match_trait_method, paths}; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind, HirId, MatchSource}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::{self, TyS}; +use rustc_middle::ty; use rustc_session::{declare_tool_lint, impl_lint_pass}; use rustc_span::sym; @@ -67,7 +67,7 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion { if match_trait_method(cx, e, &paths::INTO) && &*name.ident.as_str() == "into" { let a = cx.typeck_results().expr_ty(e); let b = cx.typeck_results().expr_ty(&args[0]); - if TyS::same_type(a, b) { + if same_type_and_consts(a, b) { let sugg = snippet_with_macro_callsite(cx, args[0].span, "").to_string(); span_lint_and_sugg( cx, @@ -90,7 +90,7 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion { } let a = cx.typeck_results().expr_ty(e); let b = cx.typeck_results().expr_ty(&args[0]); - if TyS::same_type(a, b) { + if same_type_and_consts(a, b) { let sugg = snippet(cx, args[0].span, "").into_owned(); span_lint_and_sugg( cx, @@ -110,7 +110,8 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion { if is_type_diagnostic_item(cx, a, sym::result_type); if let ty::Adt(_, substs) = a.kind(); if let Some(a_type) = substs.types().next(); - if TyS::same_type(a_type, b); + if same_type_and_consts(a_type, b); + then { span_lint_and_help( cx, @@ -137,7 +138,7 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion { if is_type_diagnostic_item(cx, a, sym::result_type); if let ty::Adt(_, substs) = a.kind(); if let Some(a_type) = substs.types().next(); - if TyS::same_type(a_type, b); + if same_type_and_consts(a_type, b); then { let hint = format!("consider removing `{}()`", snippet(cx, path.span, "TryFrom::try_from")); @@ -154,7 +155,7 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion { if_chain! { if match_def_path(cx, def_id, &paths::FROM_FROM); - if TyS::same_type(a, b); + if same_type_and_consts(a, b); then { let sugg = Sugg::hir_with_macro_callsite(cx, &args[0], "").maybe_par(); diff --git a/clippy_utils/src/ty.rs b/clippy_utils/src/ty.rs index 64a80f2554f..e1f8aff3740 100644 --- a/clippy_utils/src/ty.rs +++ b/clippy_utils/src/ty.rs @@ -322,3 +322,27 @@ pub fn walk_ptrs_ty_depth(ty: Ty<'_>) -> (Ty<'_>, usize) { } inner(ty, 0) } + +/// Returns `true` if types `a` and `b` are same types having same `Const` generic args, +/// otherwise returns `false` +pub fn same_type_and_consts(a: Ty<'tcx>, b: Ty<'tcx>) -> bool { + match (&a.kind(), &b.kind()) { + (&ty::Adt(did_a, substs_a), &ty::Adt(did_b, substs_b)) => { + if did_a != did_b { + return false; + } + + substs_a + .iter() + .zip(substs_b.iter()) + .all(|(arg_a, arg_b)| match (arg_a.unpack(), arg_b.unpack()) { + (GenericArgKind::Const(inner_a), GenericArgKind::Const(inner_b)) => inner_a == inner_b, + (GenericArgKind::Type(type_a), GenericArgKind::Type(type_b)) => { + same_type_and_consts(type_a, type_b) + }, + _ => true, + }) + }, + _ => a == b, + } +} diff --git a/tests/ui/use_self.fixed b/tests/ui/use_self.fixed index 1282befdfb3..631da6fe066 100644 --- a/tests/ui/use_self.fixed +++ b/tests/ui/use_self.fixed @@ -462,3 +462,33 @@ mod issue6818 { a: i32, } } + +mod issue7206 { + struct MyStruct; + impl From> for MyStruct<'b'> { + fn from(_s: MyStruct<'a'>) -> Self { + Self + } + } + + // keep linting non-`Const` generic args + struct S<'a> { + inner: &'a str, + } + + struct S2 { + inner: T, + } + + impl S2 { + fn new() -> Self { + unimplemented!(); + } + } + + impl<'a> S2> { + fn new_again() -> Self { + Self::new() + } + } +} diff --git a/tests/ui/use_self.rs b/tests/ui/use_self.rs index 7aaac7b2414..7a10d755faa 100644 --- a/tests/ui/use_self.rs +++ b/tests/ui/use_self.rs @@ -462,3 +462,33 @@ mod issue6818 { a: i32, } } + +mod issue7206 { + struct MyStruct; + impl From> for MyStruct<'b'> { + fn from(_s: MyStruct<'a'>) -> Self { + Self + } + } + + // keep linting non-`Const` generic args + struct S<'a> { + inner: &'a str, + } + + struct S2 { + inner: T, + } + + impl S2 { + fn new() -> Self { + unimplemented!(); + } + } + + impl<'a> S2> { + fn new_again() -> Self { + S2::new() + } + } +} diff --git a/tests/ui/use_self.stderr b/tests/ui/use_self.stderr index a32a9b9157d..cf6222c9b45 100644 --- a/tests/ui/use_self.stderr +++ b/tests/ui/use_self.stderr @@ -162,5 +162,11 @@ error: unnecessary structure name repetition LL | A::new::(submod::B {}) | ^ help: use the applicable keyword: `Self` -error: aborting due to 27 previous errors +error: unnecessary structure name repetition + --> $DIR/use_self.rs:491:13 + | +LL | S2::new() + | ^^ help: use the applicable keyword: `Self` + +error: aborting due to 28 previous errors diff --git a/tests/ui/useless_conversion.fixed b/tests/ui/useless_conversion.fixed index 03977de9455..76aa82068d6 100644 --- a/tests/ui/useless_conversion.fixed +++ b/tests/ui/useless_conversion.fixed @@ -70,4 +70,23 @@ fn main() { let a: i32 = 1; let b: i32 = 1; let _ = (a + b) * 3; + + // see #7205 + let s: Foo<'a'> = Foo; + let _: Foo<'b'> = s.into(); + let s2: Foo<'a'> = Foo; + let _: Foo<'a'> = s2; + let s3: Foo<'a'> = Foo; + let _ = s3; + let s4: Foo<'a'> = Foo; + let _ = vec![s4, s4, s4].into_iter(); +} + +#[derive(Copy, Clone)] +struct Foo; + +impl From> for Foo<'b'> { + fn from(_s: Foo<'a'>) -> Self { + Foo + } } diff --git a/tests/ui/useless_conversion.rs b/tests/ui/useless_conversion.rs index f6e094c1661..ccee7abb404 100644 --- a/tests/ui/useless_conversion.rs +++ b/tests/ui/useless_conversion.rs @@ -70,4 +70,23 @@ fn main() { let a: i32 = 1; let b: i32 = 1; let _ = i32::from(a + b) * 3; + + // see #7205 + let s: Foo<'a'> = Foo; + let _: Foo<'b'> = s.into(); + let s2: Foo<'a'> = Foo; + let _: Foo<'a'> = s2.into(); + let s3: Foo<'a'> = Foo; + let _ = Foo::<'a'>::from(s3); + let s4: Foo<'a'> = Foo; + let _ = vec![s4, s4, s4].into_iter().into_iter(); +} + +#[derive(Copy, Clone)] +struct Foo; + +impl From> for Foo<'b'> { + fn from(_s: Foo<'a'>) -> Self { + Foo + } } diff --git a/tests/ui/useless_conversion.stderr b/tests/ui/useless_conversion.stderr index 26a33595031..e6760f700f3 100644 --- a/tests/ui/useless_conversion.stderr +++ b/tests/ui/useless_conversion.stderr @@ -70,5 +70,23 @@ error: useless conversion to the same type: `i32` LL | let _ = i32::from(a + b) * 3; | ^^^^^^^^^^^^^^^^ help: consider removing `i32::from()`: `(a + b)` -error: aborting due to 11 previous errors +error: useless conversion to the same type: `Foo<'a'>` + --> $DIR/useless_conversion.rs:78:23 + | +LL | let _: Foo<'a'> = s2.into(); + | ^^^^^^^^^ help: consider removing `.into()`: `s2` + +error: useless conversion to the same type: `Foo<'a'>` + --> $DIR/useless_conversion.rs:80:13 + | +LL | let _ = Foo::<'a'>::from(s3); + | ^^^^^^^^^^^^^^^^^^^^ help: consider removing `Foo::<'a'>::from()`: `s3` + +error: useless conversion to the same type: `std::vec::IntoIter>` + --> $DIR/useless_conversion.rs:82:13 + | +LL | let _ = vec![s4, s4, s4].into_iter().into_iter(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider removing `.into_iter()`: `vec![s4, s4, s4].into_iter()` + +error: aborting due to 14 previous errors From b2270e1f459cbeb1b6f4281d230990a40fe993d8 Mon Sep 17 00:00:00 2001 From: Cameron Steffen Date: Sun, 16 May 2021 17:40:29 -0500 Subject: [PATCH 46/58] Simplify manual_unwrap_or --- clippy_lints/src/manual_unwrap_or.rs | 23 ++++------------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/clippy_lints/src/manual_unwrap_or.rs b/clippy_lints/src/manual_unwrap_or.rs index cf183d4c16f..d7e8d180f7e 100644 --- a/clippy_lints/src/manual_unwrap_or.rs +++ b/clippy_lints/src/manual_unwrap_or.rs @@ -54,21 +54,6 @@ impl LateLintPass<'_> for ManualUnwrapOr { } } -#[derive(Copy, Clone)] -enum Case { - Option, - Result, -} - -impl Case { - fn unwrap_fn_path(&self) -> &str { - match self { - Case::Option => "Option::unwrap_or", - Case::Result => "Result::unwrap_or", - } - } -} - fn lint_manual_unwrap_or<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { fn applicable_or_arm<'a>(cx: &LateContext<'_>, arms: &'a [Arm<'a>]) -> Option<&'a Arm<'a>> { if_chain! { @@ -101,10 +86,10 @@ fn lint_manual_unwrap_or<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { if_chain! { if let ExprKind::Match(scrutinee, match_arms, _) = expr.kind; let ty = cx.typeck_results().expr_ty(scrutinee); - if let Some(case) = if is_type_diagnostic_item(cx, ty, sym::option_type) { - Some(Case::Option) + if let Some(ty_name) = if is_type_diagnostic_item(cx, ty, sym::option_type) { + Some("Option") } else if is_type_diagnostic_item(cx, ty, sym::result_type) { - Some(Case::Result) + Some("Result") } else { None }; @@ -127,7 +112,7 @@ fn lint_manual_unwrap_or<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { span_lint_and_sugg( cx, MANUAL_UNWRAP_OR, expr.span, - &format!("this pattern reimplements `{}`", case.unwrap_fn_path()), + &format!("this pattern reimplements `{}::unwrap_or`", ty_name), "replace with", format!( "{}.unwrap_or({})", From ca0f0002a4dd7a1572fc571483771a67d1d73a37 Mon Sep 17 00:00:00 2001 From: Cameron Steffen Date: Mon, 17 May 2021 14:02:42 -0500 Subject: [PATCH 47/58] Remove dead code after not linting else if let --- clippy_lints/src/option_if_let_else.rs | 45 ++------------------------ 1 file changed, 3 insertions(+), 42 deletions(-) diff --git a/clippy_lints/src/option_if_let_else.rs b/clippy_lints/src/option_if_let_else.rs index 2d98b275de7..b6af4175edf 100644 --- a/clippy_lints/src/option_if_let_else.rs +++ b/clippy_lints/src/option_if_let_else.rs @@ -2,7 +2,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::sugg::Sugg; use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::usage::contains_return_break_continue_macro; -use clippy_utils::{eager_or_lazy, get_enclosing_block, in_macro, is_else_clause, is_lang_ctor}; +use clippy_utils::{eager_or_lazy, in_macro, is_else_clause, is_lang_ctor}; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::LangItem::OptionSome; @@ -81,7 +81,6 @@ struct OptionIfLetElseOccurence { method_sugg: String, some_expr: String, none_expr: String, - wrap_braces: bool, } /// Extracts the body of a given arm. If the arm contains only an expression, @@ -106,37 +105,6 @@ fn extract_body_from_arm<'a>(arm: &'a Arm<'a>) -> Option<&'a Expr<'a>> { } } -/// If this is the else body of an if/else expression, then we need to wrap -/// it in curly braces. Otherwise, we don't. -fn should_wrap_in_braces(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { - get_enclosing_block(cx, expr.hir_id).map_or(false, |parent| { - let mut should_wrap = false; - - if let Some(Expr { - kind: - ExprKind::Match( - _, - arms, - MatchSource::IfLetDesugar { - contains_else_clause: true, - }, - ), - .. - }) = parent.expr - { - should_wrap = expr.hir_id == arms[1].body.hir_id; - } else if let Some(Expr { - kind: ExprKind::If(_, _, Some(else_clause)), - .. - }) = parent.expr - { - should_wrap = expr.hir_id == else_clause.hir_id; - } - - should_wrap - }) -} - fn format_option_in_sugg(cx: &LateContext<'_>, cond_expr: &Expr<'_>, as_ref: bool, as_mut: bool) -> String { format!( "{}{}", @@ -176,7 +144,6 @@ fn detect_option_if_let_else<'tcx>( let none_body = extract_body_from_arm(&arms[1])?; let method_sugg = if eager_or_lazy::is_eagerness_candidate(cx, none_body) { "map_or" } else { "map_or_else" }; let capture_name = id.name.to_ident_string(); - let wrap_braces = should_wrap_in_braces(cx, expr); let (as_ref, as_mut) = match &cond_expr.kind { ExprKind::AddrOf(_, Mutability::Not, _) => (true, false), ExprKind::AddrOf(_, Mutability::Mut, _) => (false, true), @@ -192,7 +159,6 @@ fn detect_option_if_let_else<'tcx>( method_sugg: method_sugg.to_string(), some_expr: format!("|{}{}| {}", capture_mut, capture_name, Sugg::hir(cx, some_body, "..")), none_expr: format!("{}{}", if method_sugg == "map_or" { "" } else { "|| " }, Sugg::hir(cx, none_body, "..")), - wrap_braces, }) } else { None @@ -210,13 +176,8 @@ impl<'tcx> LateLintPass<'tcx> for OptionIfLetElse { format!("use Option::{} instead of an if let/else", detection.method_sugg).as_str(), "try", format!( - "{}{}.{}({}, {}){}", - if detection.wrap_braces { "{ " } else { "" }, - detection.option, - detection.method_sugg, - detection.none_expr, - detection.some_expr, - if detection.wrap_braces { " }" } else { "" }, + "{}.{}({}, {})", + detection.option, detection.method_sugg, detection.none_expr, detection.some_expr, ), Applicability::MaybeIncorrect, ); From 8356c485a99890339022a7afd99fc13ae09e5805 Mon Sep 17 00:00:00 2001 From: Cameron Steffen Date: Mon, 17 May 2021 14:20:26 -0500 Subject: [PATCH 48/58] Fix manual_unwrap_or FP deref reference --- clippy_lints/src/manual_unwrap_or.rs | 4 +--- tests/ui/manual_unwrap_or.fixed | 7 +++++++ tests/ui/manual_unwrap_or.rs | 7 +++++++ 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/clippy_lints/src/manual_unwrap_or.rs b/clippy_lints/src/manual_unwrap_or.rs index d7e8d180f7e..2f579edd6ad 100644 --- a/clippy_lints/src/manual_unwrap_or.rs +++ b/clippy_lints/src/manual_unwrap_or.rs @@ -11,7 +11,6 @@ use rustc_hir::{Arm, Expr, ExprKind, PatKind}; use rustc_lint::LintContext; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::lint::in_external_macro; -use rustc_middle::ty::adjustment::Adjust; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::sym; @@ -72,9 +71,8 @@ fn lint_manual_unwrap_or<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { if is_lang_ctor(cx, qpath, OptionSome) || is_lang_ctor(cx, qpath, ResultOk); if let PatKind::Binding(_, binding_hir_id, ..) = unwrap_pat.kind; if path_to_local_id(unwrap_arm.body, binding_hir_id); + if cx.typeck_results().expr_adjustments(unwrap_arm.body).is_empty(); if !contains_return_break_continue_macro(or_arm.body); - if !cx.typeck_results().expr_adjustments(unwrap_arm.body).iter() - .any(|a| matches!(a.kind, Adjust::Deref(Some(..)))); then { Some(or_arm) } else { diff --git a/tests/ui/manual_unwrap_or.fixed b/tests/ui/manual_unwrap_or.fixed index 1efecb0ba12..3717f962745 100644 --- a/tests/ui/manual_unwrap_or.fixed +++ b/tests/ui/manual_unwrap_or.fixed @@ -171,4 +171,11 @@ fn format_name(name: Option<&Rc>) -> &str { } } +fn implicit_deref_ref() { + let _: &str = match Some(&"bye") { + None => "hi", + Some(s) => s, + }; +} + fn main() {} diff --git a/tests/ui/manual_unwrap_or.rs b/tests/ui/manual_unwrap_or.rs index 95d585ad18a..989adde1f5b 100644 --- a/tests/ui/manual_unwrap_or.rs +++ b/tests/ui/manual_unwrap_or.rs @@ -213,4 +213,11 @@ fn format_name(name: Option<&Rc>) -> &str { } } +fn implicit_deref_ref() { + let _: &str = match Some(&"bye") { + None => "hi", + Some(s) => s, + }; +} + fn main() {} From be540e65965d6ab0e3a04d6fa05651a701e6b8a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20A=2E=20Muci=C3=B1o?= Date: Mon, 17 May 2021 21:59:08 -0500 Subject: [PATCH 49/58] Remove powi, "square can be computed more efficiently" powi(2) produces exactly the same native code as x * x --- clippy_lints/src/floating_point_arithmetic.rs | 12 +------ tests/ui/floating_point_powi.fixed | 5 +-- tests/ui/floating_point_powi.rs | 5 +-- tests/ui/floating_point_powi.stderr | 34 ++++++------------- 4 files changed, 18 insertions(+), 38 deletions(-) diff --git a/clippy_lints/src/floating_point_arithmetic.rs b/clippy_lints/src/floating_point_arithmetic.rs index e0b687b0205..08f28cd54c5 100644 --- a/clippy_lints/src/floating_point_arithmetic.rs +++ b/clippy_lints/src/floating_point_arithmetic.rs @@ -323,7 +323,7 @@ fn check_powi(cx: &LateContext<'_>, expr: &Expr<'_>, args: &[Expr<'_>]) { cx, SUBOPTIMAL_FLOPS, parent.span, - "square can be computed more efficiently", + "multiply and add expressions can be calculated more efficiently and accurately", "consider using", format!( "{}.mul_add({}, {})", @@ -337,16 +337,6 @@ fn check_powi(cx: &LateContext<'_>, expr: &Expr<'_>, args: &[Expr<'_>]) { return; } } - - span_lint_and_sugg( - cx, - SUBOPTIMAL_FLOPS, - expr.span, - "square can be computed more efficiently", - "consider using", - format!("{} * {}", Sugg::hir(cx, &args[0], ".."), Sugg::hir(cx, &args[0], "..")), - Applicability::MachineApplicable, - ); } } } diff --git a/tests/ui/floating_point_powi.fixed b/tests/ui/floating_point_powi.fixed index 56762400593..85f7c531e39 100644 --- a/tests/ui/floating_point_powi.fixed +++ b/tests/ui/floating_point_powi.fixed @@ -4,8 +4,6 @@ fn main() { let one = 1; let x = 3f32; - let _ = x * x; - let _ = x * x; let y = 4f32; let _ = x.mul_add(x, y); @@ -13,7 +11,10 @@ fn main() { let _ = x.mul_add(x, y).sqrt(); let _ = y.mul_add(y, x).sqrt(); // Cases where the lint shouldn't be applied + let _ = x.powi(2); + let _ = x.powi(1 + 1); let _ = x.powi(3); + let _ = x.powi(4) + y; let _ = x.powi(one + 1); let _ = (x.powi(2) + y.powi(2)).sqrt(); } diff --git a/tests/ui/floating_point_powi.rs b/tests/ui/floating_point_powi.rs index 1f800e4628d..ece61d1bec4 100644 --- a/tests/ui/floating_point_powi.rs +++ b/tests/ui/floating_point_powi.rs @@ -4,8 +4,6 @@ fn main() { let one = 1; let x = 3f32; - let _ = x.powi(2); - let _ = x.powi(1 + 1); let y = 4f32; let _ = x.powi(2) + y; @@ -13,7 +11,10 @@ fn main() { let _ = (x.powi(2) + y).sqrt(); let _ = (x + y.powi(2)).sqrt(); // Cases where the lint shouldn't be applied + let _ = x.powi(2); + let _ = x.powi(1 + 1); let _ = x.powi(3); + let _ = x.powi(4) + y; let _ = x.powi(one + 1); let _ = (x.powi(2) + y.powi(2)).sqrt(); } diff --git a/tests/ui/floating_point_powi.stderr b/tests/ui/floating_point_powi.stderr index d5a5f1bcca1..37d840988bb 100644 --- a/tests/ui/floating_point_powi.stderr +++ b/tests/ui/floating_point_powi.stderr @@ -1,40 +1,28 @@ -error: square can be computed more efficiently - --> $DIR/floating_point_powi.rs:7:13 - | -LL | let _ = x.powi(2); - | ^^^^^^^^^ help: consider using: `x * x` - | - = note: `-D clippy::suboptimal-flops` implied by `-D warnings` - -error: square can be computed more efficiently - --> $DIR/floating_point_powi.rs:8:13 - | -LL | let _ = x.powi(1 + 1); - | ^^^^^^^^^^^^^ help: consider using: `x * x` - -error: square can be computed more efficiently - --> $DIR/floating_point_powi.rs:11:13 +error: multiply and add expressions can be calculated more efficiently and accurately + --> $DIR/floating_point_powi.rs:9:13 | LL | let _ = x.powi(2) + y; | ^^^^^^^^^^^^^ help: consider using: `x.mul_add(x, y)` + | + = note: `-D clippy::suboptimal-flops` implied by `-D warnings` -error: square can be computed more efficiently - --> $DIR/floating_point_powi.rs:12:13 +error: multiply and add expressions can be calculated more efficiently and accurately + --> $DIR/floating_point_powi.rs:10:13 | LL | let _ = x + y.powi(2); | ^^^^^^^^^^^^^ help: consider using: `y.mul_add(y, x)` -error: square can be computed more efficiently - --> $DIR/floating_point_powi.rs:13:13 +error: multiply and add expressions can be calculated more efficiently and accurately + --> $DIR/floating_point_powi.rs:11:13 | LL | let _ = (x.powi(2) + y).sqrt(); | ^^^^^^^^^^^^^^^ help: consider using: `x.mul_add(x, y)` -error: square can be computed more efficiently - --> $DIR/floating_point_powi.rs:14:13 +error: multiply and add expressions can be calculated more efficiently and accurately + --> $DIR/floating_point_powi.rs:12:13 | LL | let _ = (x + y.powi(2)).sqrt(); | ^^^^^^^^^^^^^^^ help: consider using: `y.mul_add(y, x)` -error: aborting due to 6 previous errors +error: aborting due to 4 previous errors From fd442c3f113f2e5dd799dad9488ba77f9b937630 Mon Sep 17 00:00:00 2001 From: hi-rustin Date: Tue, 18 May 2021 11:47:53 +0800 Subject: [PATCH 50/58] Add the command to add upstream remote --- doc/basics.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/basics.md b/doc/basics.md index 5226875cc21..e2e307ce4f6 100644 --- a/doc/basics.md +++ b/doc/basics.md @@ -28,6 +28,8 @@ git clone git@github.com:/rust-clippy If you've already cloned Clippy in the past, update it to the latest version: ```bash +# If the upstream remote has not been added yet +git remote add upstream https://github.com/rust-lang/rust-clippy # upstream has to be the remote of the rust-lang/rust-clippy repo git fetch upstream # make sure that you are on the master branch From 716d03f86bc9d72e56c2d803fd76ff44f29c9b3a Mon Sep 17 00:00:00 2001 From: flip1995 Date: Tue, 18 May 2021 10:03:36 +0200 Subject: [PATCH 51/58] CI: update rustup before installing the toolchain on windows --- .github/workflows/clippy_bors.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/clippy_bors.yml b/.github/workflows/clippy_bors.yml index ae6f1aa1b30..f27fee87dc1 100644 --- a/.github/workflows/clippy_bors.yml +++ b/.github/workflows/clippy_bors.yml @@ -90,6 +90,11 @@ jobs: - name: Checkout uses: actions/checkout@v2.3.3 + # FIXME: should not be necessary once 1.24.2 is the default version on the windows runner + - name: Update rustup + run: rustup self update + if: runner.os == 'Windows' + - name: Install toolchain run: rustup show active-toolchain From ff452d5ba68e4d8b8d8c3b2977f0c8635c6380e9 Mon Sep 17 00:00:00 2001 From: flip1995 Date: Tue, 18 May 2021 11:01:00 +0200 Subject: [PATCH 52/58] Deny warning in every main sub-crate This enables the same warnings that are enabled in `clippy_lints` also in `clippy_utils` and `clippy_dev`. Then it makes sure, that the `deny-warnings` feature is passed down to `clippy_lints` and `clippy_utils` when compiling Clippy. --- Cargo.toml | 2 +- clippy_dev/src/lib.rs | 4 +++- clippy_dev/src/main.rs | 2 ++ clippy_lints/Cargo.toml | 2 +- clippy_utils/Cargo.toml | 1 + clippy_utils/src/lib.rs | 7 +++++++ 6 files changed, 15 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f010e609604..848476a9d05 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,7 +49,7 @@ rustc-workspace-hack = "1.0.0" rustc_tools_util = { version = "0.2.0", path = "rustc_tools_util" } [features] -deny-warnings = [] +deny-warnings = ["clippy_lints/deny-warnings"] integration = ["tempfile"] internal-lints = ["clippy_lints/internal-lints"] metadata-collector-lint = ["internal-lints", "clippy_lints/metadata-collector-lint"] diff --git a/clippy_dev/src/lib.rs b/clippy_dev/src/lib.rs index 1e5a140e964..69f42aca8b6 100644 --- a/clippy_dev/src/lib.rs +++ b/clippy_dev/src/lib.rs @@ -1,5 +1,7 @@ -#![cfg_attr(feature = "deny-warnings", deny(warnings))] #![feature(once_cell)] +#![cfg_attr(feature = "deny-warnings", deny(warnings))] +// warn on lints, that are included in `rust-lang/rust`s bootstrap +#![warn(rust_2018_idioms, unused_lifetimes)] use itertools::Itertools; use regex::Regex; diff --git a/clippy_dev/src/main.rs b/clippy_dev/src/main.rs index f4da783502c..7040c257c83 100644 --- a/clippy_dev/src/main.rs +++ b/clippy_dev/src/main.rs @@ -1,4 +1,6 @@ #![cfg_attr(feature = "deny-warnings", deny(warnings))] +// warn on lints, that are included in `rust-lang/rust`s bootstrap +#![warn(rust_2018_idioms, unused_lifetimes)] use clap::{App, Arg, ArgMatches, SubCommand}; use clippy_dev::{bless, fmt, ide_setup, new_lint, serve, stderr_length_check, update_lints}; diff --git a/clippy_lints/Cargo.toml b/clippy_lints/Cargo.toml index 7ceb1da6a6e..48f2972ec58 100644 --- a/clippy_lints/Cargo.toml +++ b/clippy_lints/Cargo.toml @@ -30,7 +30,7 @@ rustc-semver = "1.1.0" url = { version = "2.1.0", features = ["serde"] } [features] -deny-warnings = [] +deny-warnings = ["clippy_utils/deny-warnings"] # build clippy with internal lints enabled, off by default internal-lints = ["clippy_utils/internal-lints"] metadata-collector-lint = ["serde_json", "clippy_utils/metadata-collector-lint"] diff --git a/clippy_utils/Cargo.toml b/clippy_utils/Cargo.toml index 0a1d4e11142..93ed3b18400 100644 --- a/clippy_utils/Cargo.toml +++ b/clippy_utils/Cargo.toml @@ -14,6 +14,7 @@ unicode-normalization = "0.1" rustc-semver="1.1.0" [features] +deny-warnings = [] internal-lints = [] metadata-collector-lint = [] diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 371fe23bedc..886dde4b977 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -3,7 +3,14 @@ #![feature(iter_zip)] #![feature(rustc_private)] #![recursion_limit = "512"] +#![cfg_attr(feature = "deny-warnings", deny(warnings))] #![allow(clippy::missing_errors_doc, clippy::missing_panics_doc, clippy::must_use_candidate)] +// warn on the same lints as `clippy_lints` +#![warn(trivial_casts, trivial_numeric_casts)] +// warn on lints, that are included in `rust-lang/rust`s bootstrap +#![warn(rust_2018_idioms, unused_lifetimes)] +// warn on rustc internal lints +#![warn(rustc::internal)] // FIXME: switch to something more ergonomic here, once available. // (Currently there is no way to opt into sysroot crates without `extern crate`.) From 9586ddb0461c9696f372502371ab0f3710192f34 Mon Sep 17 00:00:00 2001 From: flip1995 Date: Tue, 18 May 2021 11:08:19 +0200 Subject: [PATCH 53/58] Fix fallout from not ignoring warnings anymore --- clippy_utils/src/lib.rs | 3 +-- clippy_utils/src/ty.rs | 7 +++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 886dde4b977..0ae0450cbe0 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -20,7 +20,6 @@ extern crate rustc_attr; extern crate rustc_data_structures; extern crate rustc_errors; extern crate rustc_hir; -extern crate rustc_hir_pretty; extern crate rustc_infer; extern crate rustc_lexer; extern crate rustc_lint; @@ -1333,7 +1332,7 @@ pub fn if_sequence<'tcx>(mut expr: &'tcx Expr<'tcx>) -> (Vec<&'tcx Expr<'tcx>>, } /// Checks if the given function kind is an async function. -pub fn is_async_fn(kind: FnKind) -> bool { +pub fn is_async_fn(kind: FnKind<'_>) -> bool { matches!(kind, FnKind::ItemFn(_, _, header, _) if header.asyncness == IsAsync::Async) } diff --git a/clippy_utils/src/ty.rs b/clippy_utils/src/ty.rs index e1f8aff3740..c36e215f184 100644 --- a/clippy_utils/src/ty.rs +++ b/clippy_utils/src/ty.rs @@ -2,9 +2,8 @@ #![allow(clippy::module_name_repetitions)] -use std::collections::HashMap; - use rustc_ast::ast::Mutability; +use rustc_data_structures::fx::FxHashMap; use rustc_hir as hir; use rustc_hir::def_id::DefId; use rustc_hir::{TyKind, Unsafety}; @@ -184,14 +183,14 @@ pub fn is_must_use_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool { /// Checks if `Ty` is normalizable. This function is useful /// to avoid crashes on `layout_of`. pub fn is_normalizable<'tcx>(cx: &LateContext<'tcx>, param_env: ty::ParamEnv<'tcx>, ty: Ty<'tcx>) -> bool { - is_normalizable_helper(cx, param_env, ty, &mut HashMap::new()) + is_normalizable_helper(cx, param_env, ty, &mut FxHashMap::default()) } fn is_normalizable_helper<'tcx>( cx: &LateContext<'tcx>, param_env: ty::ParamEnv<'tcx>, ty: Ty<'tcx>, - cache: &mut HashMap, bool>, + cache: &mut FxHashMap, bool>, ) -> bool { if let Some(&cached_result) = cache.get(ty) { return cached_result; From a149ba26fa07b42b40f6e38f69ef74ecd756d68f Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Tue, 18 May 2021 10:51:59 -0400 Subject: [PATCH 54/58] Fix ICE in `implicit_return` async functions always return a value --- clippy_lints/src/implicit_return.rs | 6 +++++- tests/ui/crashes/ice-7231.rs | 10 ++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 tests/ui/crashes/ice-7231.rs diff --git a/clippy_lints/src/implicit_return.rs b/clippy_lints/src/implicit_return.rs index 30174fa2100..260a8f50157 100644 --- a/clippy_lints/src/implicit_return.rs +++ b/clippy_lints/src/implicit_return.rs @@ -147,7 +147,11 @@ fn lint_implicit_returns( visit_break_exprs(block, |break_expr, dest, sub_expr| { if dest.target_id.ok() == Some(expr.hir_id) { if call_site_span.is_none() && break_expr.span.ctxt() == ctxt { - lint_break(cx, break_expr.span, sub_expr.unwrap().span); + // At this point sub_expr can be `None` in async functions which either diverge, or return the + // unit type. + if let Some(sub_expr) = sub_expr { + lint_break(cx, break_expr.span, sub_expr.span); + } } else { // the break expression is from a macro call, add a return to the loop add_return = true; diff --git a/tests/ui/crashes/ice-7231.rs b/tests/ui/crashes/ice-7231.rs new file mode 100644 index 00000000000..5595d8d1d62 --- /dev/null +++ b/tests/ui/crashes/ice-7231.rs @@ -0,0 +1,10 @@ +// edition:2018 +#![allow(clippy::never_loop)] + +async fn f() { + loop { + break; + } +} + +fn main() {} From 760f70312e6b894427cfc68a066bc1e87513337c Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Thu, 15 Apr 2021 12:20:43 -0400 Subject: [PATCH 55/58] Improve `multiple_inherent_impl` lint Treat different generic arguments as different types. Allow the lint to be ignored on the type definition, or any impl blocks. --- clippy_lints/src/inherent_impl.rs | 139 ++++++++++++++++++++---------- clippy_lints/src/lib.rs | 2 +- tests/ui/impl.rs | 31 +++++++ tests/ui/impl.stderr | 30 ++++++- 4 files changed, 155 insertions(+), 47 deletions(-) diff --git a/clippy_lints/src/inherent_impl.rs b/clippy_lints/src/inherent_impl.rs index c31013e49be..980bcc78f5d 100644 --- a/clippy_lints/src/inherent_impl.rs +++ b/clippy_lints/src/inherent_impl.rs @@ -1,12 +1,16 @@ //! lint on inherent implementations -use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::in_macro; -use rustc_hir::def_id::DefIdMap; -use rustc_hir::{def_id, Crate, Impl, Item, ItemKind}; +use clippy_utils::diagnostics::span_lint_and_note; +use clippy_utils::{in_macro, is_allowed}; +use rustc_data_structures::fx::FxHashMap; +use rustc_hir::{ + def_id::{LocalDefId, LOCAL_CRATE}, + Crate, Item, ItemKind, Node, +}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_session::{declare_tool_lint, impl_lint_pass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::Span; +use std::collections::hash_map::Entry; declare_clippy_lint! { /// **What it does:** Checks for multiple inherent implementations of a struct @@ -40,51 +44,96 @@ declare_clippy_lint! { "Multiple inherent impl that could be grouped" } -#[allow(clippy::module_name_repetitions)] -#[derive(Default)] -pub struct MultipleInherentImpl { - impls: DefIdMap, -} - -impl_lint_pass!(MultipleInherentImpl => [MULTIPLE_INHERENT_IMPL]); +declare_lint_pass!(MultipleInherentImpl => [MULTIPLE_INHERENT_IMPL]); impl<'tcx> LateLintPass<'tcx> for MultipleInherentImpl { - fn check_item(&mut self, _: &LateContext<'tcx>, item: &'tcx Item<'_>) { - if let ItemKind::Impl(Impl { - ref generics, - of_trait: None, - .. - }) = item.kind - { - // Remember for each inherent implementation encountered its span and generics - // but filter out implementations that have generic params (type or lifetime) - // or are derived from a macro - if !in_macro(item.span) && generics.params.is_empty() { - self.impls.insert(item.def_id.to_def_id(), item.span); - } - } - } + fn check_crate_post(&mut self, cx: &LateContext<'tcx>, _: &'tcx Crate<'_>) { + // Map from a type to it's first impl block. Needed to distinguish generic arguments. + // e.g. `Foo` and `Foo` + let mut type_map = FxHashMap::default(); + // List of spans to lint. (lint_span, first_span) + let mut lint_spans = Vec::new(); - fn check_crate_post(&mut self, cx: &LateContext<'tcx>, krate: &'tcx Crate<'_>) { - if !krate.items.is_empty() { - // Retrieve all inherent implementations from the crate, grouped by type - for impls in cx.tcx.crate_inherent_impls(def_id::LOCAL_CRATE).inherent_impls.values() { - // Filter out implementations that have generic params (type or lifetime) - let mut impl_spans = impls.iter().filter_map(|impl_def| self.impls.get(impl_def)); - if let Some(initial_span) = impl_spans.next() { - impl_spans.for_each(|additional_span| { - span_lint_and_then( - cx, - MULTIPLE_INHERENT_IMPL, - *additional_span, - "multiple implementations of this structure", - |diag| { - diag.span_note(*initial_span, "first implementation here"); - }, - ) - }) + for (_, impl_ids) in cx + .tcx + .crate_inherent_impls(LOCAL_CRATE) + .inherent_impls + .iter() + .filter(|(id, impls)| { + impls.len() > 1 + // Check for `#[allow]` on the type definition + && !is_allowed( + cx, + MULTIPLE_INHERENT_IMPL, + cx.tcx.hir().local_def_id_to_hir_id(id.expect_local()), + ) + }) + { + for impl_id in impl_ids.iter().map(|id| id.expect_local()) { + match type_map.entry(cx.tcx.type_of(impl_id)) { + 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)); + }, + Entry::Occupied(mut e) => { + if let Some(span) = get_impl_span(cx, impl_id) { + let first_span = match *e.get() { + IdOrSpan::Span(s) => s, + IdOrSpan::Id(id) => { + if let Some(s) = get_impl_span(cx, id) { + // Remember the span of the first block. + *e.get_mut() = IdOrSpan::Span(s); + s + } else { + // The first impl block isn't considered by the lint. Replace it with the + // current one. + *e.get_mut() = IdOrSpan::Span(span); + continue; + } + }, + }; + lint_spans.push((span, first_span)); + } + }, } } + + // Switching to the next type definition, no need to keep the current entries around. + type_map.clear(); + } + + // `TyCtxt::crate_inherent_impls` doesn't have a defined order. Sort the lint output first. + lint_spans.sort_by_key(|x| x.0.lo()); + for (span, first_span) in lint_spans { + span_lint_and_note( + cx, + MULTIPLE_INHERENT_IMPL, + span, + "multiple implementations of this structure", + Some(first_span), + "first implementation here", + ); } } } + +/// Gets the span for the given impl block unless it's not being considered by the lint. +fn get_impl_span(cx: &LateContext<'_>, id: LocalDefId) -> Option { + let id = cx.tcx.hir().local_def_id_to_hir_id(id); + if let Node::Item(&Item { + kind: ItemKind::Impl(ref impl_item), + span, + .. + }) = cx.tcx.hir().get(id) + { + (!in_macro(span) && impl_item.generics.params.is_empty() && !is_allowed(cx, MULTIPLE_INHERENT_IMPL, id)) + .then(|| span) + } else { + None + } +} + +enum IdOrSpan { + Id(LocalDefId), + Span(Span), +} diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 78a95b00403..47c576bac48 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -1154,7 +1154,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_early_pass(|| box suspicious_operation_groupings::SuspiciousOperationGroupings); store.register_late_pass(|| box suspicious_trait_impl::SuspiciousImpl); store.register_late_pass(|| box map_unit_fn::MapUnit); - store.register_late_pass(|| box inherent_impl::MultipleInherentImpl::default()); + store.register_late_pass(|| box inherent_impl::MultipleInherentImpl); store.register_late_pass(|| box neg_cmp_op_on_partial_ord::NoNegCompOpForPartialOrd); store.register_late_pass(|| box unwrap::Unwrap); store.register_late_pass(|| box duration_subsec::DurationSubsec); diff --git a/tests/ui/impl.rs b/tests/ui/impl.rs index 1c46e3a5337..39443775015 100644 --- a/tests/ui/impl.rs +++ b/tests/ui/impl.rs @@ -33,4 +33,35 @@ impl fmt::Debug for MyStruct { } } +// issue #5772 +struct WithArgs(T); +impl WithArgs { + fn f1() {} +} +impl WithArgs { + fn f2() {} +} +impl WithArgs { + fn f3() {} +} + +// Ok, the struct is allowed to have multiple impls. +#[allow(clippy::multiple_inherent_impl)] +struct Allowed; +impl Allowed {} +impl Allowed {} +impl Allowed {} + +struct AllowedImpl; +#[allow(clippy::multiple_inherent_impl)] +impl AllowedImpl {} +// Ok, the first block is skipped by this lint. +impl AllowedImpl {} + +struct OneAllowedImpl; +impl OneAllowedImpl {} +#[allow(clippy::multiple_inherent_impl)] +impl OneAllowedImpl {} +impl OneAllowedImpl {} // Lint, only one of the three blocks is allowed. + fn main() {} diff --git a/tests/ui/impl.stderr b/tests/ui/impl.stderr index aab688cc2d8..8703ecac93e 100644 --- a/tests/ui/impl.stderr +++ b/tests/ui/impl.stderr @@ -31,5 +31,33 @@ LL | | fn first() {} LL | | } | |_^ -error: aborting due to 2 previous errors +error: multiple implementations of this structure + --> $DIR/impl.rs:44:1 + | +LL | / impl WithArgs { +LL | | fn f3() {} +LL | | } + | |_^ + | +note: first implementation here + --> $DIR/impl.rs:41:1 + | +LL | / impl WithArgs { +LL | | fn f2() {} +LL | | } + | |_^ + +error: multiple implementations of this structure + --> $DIR/impl.rs:65:1 + | +LL | impl OneAllowedImpl {} // Lint, only one of the three blocks is allowed. + | ^^^^^^^^^^^^^^^^^^^^^^ + | +note: first implementation here + --> $DIR/impl.rs:62:1 + | +LL | impl OneAllowedImpl {} + | ^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 4 previous errors From 8036d7f7842e77fde3be49b4c475665a61d9f560 Mon Sep 17 00:00:00 2001 From: xFrednet Date: Wed, 19 May 2021 16:32:09 +0200 Subject: [PATCH 56/58] Adding the default lint level to the metadata collection And stripping the clippy:: prefix from the group --- .../internal_lints/metadata_collector.rs | 72 +++++++++++++++---- 1 file changed, 59 insertions(+), 13 deletions(-) diff --git a/clippy_lints/src/utils/internal_lints/metadata_collector.rs b/clippy_lints/src/utils/internal_lints/metadata_collector.rs index b7c86ae6e18..19f20376c6f 100644 --- a/clippy_lints/src/utils/internal_lints/metadata_collector.rs +++ b/clippy_lints/src/utils/internal_lints/metadata_collector.rs @@ -37,10 +37,28 @@ const BLACK_LISTED_LINTS: [&str; 3] = ["lint_author", "deep_code_inspection", "i /// These groups will be ignored by the lint group matcher. This is useful for collections like /// `clippy::all` const IGNORED_LINT_GROUPS: [&str; 1] = ["clippy::all"]; -/// Lints within this group will be excluded from the collection -const EXCLUDED_LINT_GROUPS: [&str; 1] = ["clippy::internal"]; +/// Lints within this group will be excluded from the collection. These groups +/// have to be defined without the `clippy::` prefix. +const EXCLUDED_LINT_GROUPS: [&str; 1] = ["internal"]; /// Collected deprecated lint will be assigned to this group in the JSON output -const DEPRECATED_LINT_GROUP_STR: &str = "DEPRECATED"; +const DEPRECATED_LINT_GROUP_STR: &str = "deprecated"; +/// This is the lint level for deprecated lints that will be displayed in the lint list +const DEPRECATED_LINT_LEVEL: &str = "none"; +/// This array holds Clippy's lint groups with their corresponding default lint level. The +/// lint level for deprecated lints is set in `DEPRECATED_LINT_LEVEL`. +const DEFAULT_LINT_LEVELS: [(&str, &str); 8] = [ + ("correctness", "deny"), + ("restriction", "allow"), + ("style", "warm"), + ("pedantic", "allow"), + ("complexity", "warn"), + ("perf", "warn"), + ("cargo", "allow"), + ("nursery", "allow"), +]; +/// This prefix is in front of the lint groups in the lint store. The prefix will be trimmed +/// to only keep the actual lint group in the output. +const CLIPPY_LINT_GROUP_PREFIX: &str = "clippy::"; /// This template will be used to format the configuration section in the lint documentation. /// The `configurations` parameter will be replaced with one or multiple formatted @@ -188,6 +206,7 @@ struct LintMetadata { id: String, id_span: SerializableSpan, group: String, + level: &'static str, docs: String, /// This field is only used in the output and will only be /// mapped shortly before the actual output. @@ -195,11 +214,12 @@ struct LintMetadata { } impl LintMetadata { - fn new(id: String, id_span: SerializableSpan, group: String, docs: String) -> Self { + fn new(id: String, id_span: SerializableSpan, group: String, level: &'static str, docs: String) -> Self { Self { id, id_span, group, + level, docs, applicability: None, } @@ -368,7 +388,7 @@ impl<'hir> LateLintPass<'hir> for MetadataCollector { let lint_name = sym_to_string(item.ident.name).to_ascii_lowercase(); if !BLACK_LISTED_LINTS.contains(&lint_name.as_str()); // metadata extraction - if let Some(group) = get_lint_group_or_lint(cx, &lint_name, item); + if let Some((group, level)) = get_lint_group_and_level_or_lint(cx, &lint_name, item); if let Some(mut docs) = extract_attr_docs_or_lint(cx, item); then { if let Some(configuration_section) = self.get_lint_configs(&lint_name) { @@ -379,6 +399,7 @@ impl<'hir> LateLintPass<'hir> for MetadataCollector { lint_name, SerializableSpan::from_item(cx, item), group, + level, docs, )); } @@ -396,6 +417,7 @@ impl<'hir> LateLintPass<'hir> for MetadataCollector { lint_name, SerializableSpan::from_item(cx, item), DEPRECATED_LINT_GROUP_STR.to_string(), + DEPRECATED_LINT_LEVEL, docs, )); } @@ -475,15 +497,32 @@ fn extract_attr_docs(cx: &LateContext<'_>, item: &Item<'_>) -> Option { }) } -fn get_lint_group_or_lint(cx: &LateContext<'_>, lint_name: &str, item: &'hir Item<'_>) -> Option { +fn get_lint_group_and_level_or_lint( + cx: &LateContext<'_>, + lint_name: &str, + item: &'hir Item<'_>, +) -> Option<(String, &'static str)> { let result = cx.lint_store.check_lint_name(lint_name, Some(sym::clippy)); if let CheckLintNameResult::Tool(Ok(lint_lst)) = result { - get_lint_group(cx, lint_lst[0]) - .or_else(|| { - lint_collection_error_item(cx, item, "Unable to determine lint group"); + if let Some(group) = get_lint_group(cx, lint_lst[0]) { + if EXCLUDED_LINT_GROUPS.contains(&group.as_str()) { + return None; + } + + if let Some(level) = get_lint_level_from_group(&group) { + Some((group, level)) + } else { + lint_collection_error_item( + cx, + item, + &format!("Unable to determine lint level for found group `{}`", group), + ); None - }) - .filter(|group| !EXCLUDED_LINT_GROUPS.contains(&group.as_str())) + } + } else { + lint_collection_error_item(cx, item, "Unable to determine lint group"); + None + } } else { lint_collection_error_item(cx, item, "Unable to find lint in lint_store"); None @@ -496,14 +535,21 @@ fn get_lint_group(cx: &LateContext<'_>, lint_id: LintId) -> Option { continue; } - if lints.iter().any(|x| *x == lint_id) { - return Some((*group_name).to_string()); + if lints.iter().any(|group_lint| *group_lint == lint_id) { + let group = group_name.strip_prefix(CLIPPY_LINT_GROUP_PREFIX).unwrap_or(group_name); + return Some((*group).to_string()); } } None } +fn get_lint_level_from_group(lint_group: &str) -> Option<&'static str> { + DEFAULT_LINT_LEVELS + .iter() + .find_map(|(group_name, group_level)| (*group_name == lint_group).then(|| *group_level)) +} + fn is_deprecated_lint(cx: &LateContext<'_>, ty: &hir::Ty<'_>) -> bool { if let hir::TyKind::Path(ref path) = ty.kind { if let hir::def::Res::Def(DefKind::Struct, def_id) = cx.qpath_res(path, ty.hir_id) { From 9dc366bc4d8a32197f28cb94b9c0e0a51108c809 Mon Sep 17 00:00:00 2001 From: Fridtjof Stoldt Date: Wed, 19 May 2021 17:23:33 +0200 Subject: [PATCH 57/58] Fixed a type Co-authored-by: Philipp Krones --- clippy_lints/src/utils/internal_lints/metadata_collector.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clippy_lints/src/utils/internal_lints/metadata_collector.rs b/clippy_lints/src/utils/internal_lints/metadata_collector.rs index 19f20376c6f..d6879022057 100644 --- a/clippy_lints/src/utils/internal_lints/metadata_collector.rs +++ b/clippy_lints/src/utils/internal_lints/metadata_collector.rs @@ -49,7 +49,7 @@ const DEPRECATED_LINT_LEVEL: &str = "none"; const DEFAULT_LINT_LEVELS: [(&str, &str); 8] = [ ("correctness", "deny"), ("restriction", "allow"), - ("style", "warm"), + ("style", "warn"), ("pedantic", "allow"), ("complexity", "warn"), ("perf", "warn"), From 559ce6d7720930031a6f64be2931b847e37b986b Mon Sep 17 00:00:00 2001 From: flip1995 Date: Thu, 20 May 2021 12:06:22 +0200 Subject: [PATCH 58/58] Bump nightly version -> 2021-05-20 --- rust-toolchain | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rust-toolchain b/rust-toolchain index 593162f09a7..cb8cb0978f6 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1,3 +1,3 @@ [toolchain] -channel = "nightly-2021-05-06" +channel = "nightly-2021-05-20" components = ["llvm-tools-preview", "rustc-dev", "rust-src"]