From ad3f3c7fb67329778bf26771cd4fbfc68e175daf Mon Sep 17 00:00:00 2001 From: Alex Macleod Date: Thu, 25 Jul 2024 17:53:29 +0000 Subject: [PATCH 01/18] Skip locking span interner for some syntax context checks --- compiler/rustc_span/src/lib.rs | 6 ------ compiler/rustc_span/src/span_encoding.rs | 14 +++++++++++--- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/compiler/rustc_span/src/lib.rs b/compiler/rustc_span/src/lib.rs index 7c8ac3be4beca..6cb8491067d2c 100644 --- a/compiler/rustc_span/src/lib.rs +++ b/compiler/rustc_span/src/lib.rs @@ -564,12 +564,6 @@ impl Span { !self.is_dummy() && sm.is_span_accessible(self) } - /// Returns `true` if this span comes from any kind of macro, desugaring or inlining. - #[inline] - pub fn from_expansion(self) -> bool { - !self.ctxt().is_root() - } - /// Returns `true` if `span` originates in a derive-macro's expansion. pub fn in_derive_expansion(self) -> bool { matches!(self.ctxt().outer_expn_data().kind, ExpnKind::Macro(MacroKind::Derive, _)) diff --git a/compiler/rustc_span/src/span_encoding.rs b/compiler/rustc_span/src/span_encoding.rs index 53d7b7511a621..f56da203fa8c3 100644 --- a/compiler/rustc_span/src/span_encoding.rs +++ b/compiler/rustc_span/src/span_encoding.rs @@ -305,6 +305,13 @@ impl Span { } } + /// Returns `true` if this span comes from any kind of macro, desugaring or inlining. + #[inline] + pub fn from_expansion(self) -> bool { + // If the span is fully inferred then ctxt > MAX_CTXT + self.inline_ctxt().map_or(true, |ctxt| !ctxt.is_root()) + } + /// Returns `true` if this is a dummy span with any hygienic context. #[inline] pub fn is_dummy(self) -> bool { @@ -372,9 +379,10 @@ impl Span { pub fn eq_ctxt(self, other: Span) -> bool { match (self.inline_ctxt(), other.inline_ctxt()) { (Ok(ctxt1), Ok(ctxt2)) => ctxt1 == ctxt2, - (Ok(ctxt), Err(index)) | (Err(index), Ok(ctxt)) => { - with_span_interner(|interner| ctxt == interner.spans[index].ctxt) - } + // If `inline_ctxt` returns `Ok` the context is <= MAX_CTXT. + // If it returns `Err` the span is fully interned and the context is > MAX_CTXT. + // As these do not overlap an `Ok` and `Err` result cannot have an equal context. + (Ok(_), Err(_)) | (Err(_), Ok(_)) => false, (Err(index1), Err(index2)) => with_span_interner(|interner| { interner.spans[index1].ctxt == interner.spans[index2].ctxt }), From 6205bcf0f15038a74b9c156a541ea29479c50ffd Mon Sep 17 00:00:00 2001 From: GnomedDev Date: Fri, 1 Nov 2024 19:30:37 +0000 Subject: [PATCH 02/18] Add match-based manual try to clippy::question_mark --- clippy_lints/src/question_mark.rs | 170 +++++++++++++++++++++++++++++- tests/ui/question_mark.fixed | 46 ++++++++ tests/ui/question_mark.rs | 61 +++++++++++ tests/ui/question_mark.stderr | 63 +++++++++-- 4 files changed, 327 insertions(+), 13 deletions(-) diff --git a/clippy_lints/src/question_mark.rs b/clippy_lints/src/question_mark.rs index 28968866e1f8e..ce629717617a2 100644 --- a/clippy_lints/src/question_mark.rs +++ b/clippy_lints/src/question_mark.rs @@ -8,18 +8,18 @@ use clippy_utils::source::snippet_with_applicability; use clippy_utils::ty::{implements_trait, is_type_diagnostic_item}; use clippy_utils::{ eq_expr_value, higher, is_else_clause, is_in_const_context, is_lint_allowed, is_path_lang_item, is_res_lang_ctor, - pat_and_expr_can_be_question_mark, path_to_local, path_to_local_id, peel_blocks, peel_blocks_with_stmt, - span_contains_comment, + pat_and_expr_can_be_question_mark, path_res, path_to_local, path_to_local_id, peel_blocks, peel_blocks_with_stmt, + span_contains_cfg, span_contains_comment, }; use rustc_errors::Applicability; use rustc_hir::LangItem::{self, OptionNone, OptionSome, ResultErr, ResultOk}; use rustc_hir::def::Res; use rustc_hir::{ - BindingMode, Block, Body, ByRef, Expr, ExprKind, LetStmt, Mutability, Node, PatKind, PathSegment, QPath, Stmt, - StmtKind, + Arm, BindingMode, Block, Body, ByRef, Expr, ExprKind, FnRetTy, HirId, LetStmt, MatchSource, Mutability, Node, Pat, + PatKind, PathSegment, QPath, Stmt, StmtKind, TyKind, }; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::Ty; +use rustc_middle::ty::{self, Ty}; use rustc_session::impl_lint_pass; use rustc_span::sym; use rustc_span::symbol::Symbol; @@ -58,6 +58,9 @@ pub struct QuestionMark { /// if it is greater than zero. /// As for why we need this in the first place: try_block_depth_stack: Vec, + /// Keeps track of the number of inferred return type closures we are inside, to avoid problems + /// with the `Err(x.into())` expansion being ambiguious. + inferred_ret_closure_stack: u16, } impl_lint_pass!(QuestionMark => [QUESTION_MARK, MANUAL_LET_ELSE]); @@ -68,6 +71,7 @@ impl QuestionMark { msrv: conf.msrv.clone(), matches_behaviour: conf.matches_for_let_else, try_block_depth_stack: Vec::new(), + inferred_ret_closure_stack: 0, } } } @@ -271,6 +275,135 @@ fn check_is_none_or_err_and_early_return<'tcx>(cx: &LateContext<'tcx>, expr: &Ex } } +#[derive(Clone, Copy, Debug)] +enum TryMode { + Result, + Option, +} + +fn find_try_mode<'tcx>(cx: &LateContext<'tcx>, scrutinee: &Expr<'tcx>) -> Option { + let scrutinee_ty = cx.typeck_results().expr_ty_adjusted(scrutinee); + let ty::Adt(scrutinee_adt_def, _) = scrutinee_ty.kind() else { + return None; + }; + + match cx.tcx.get_diagnostic_name(scrutinee_adt_def.did())? { + sym::Result => Some(TryMode::Result), + sym::Option => Some(TryMode::Option), + _ => None, + } +} + +// Check that `pat` is `{ctor_lang_item}(val)`, returning `val`. +fn extract_ctor_call<'a, 'tcx>( + cx: &LateContext<'tcx>, + expected_ctor: LangItem, + pat: &'a Pat<'tcx>, +) -> Option<&'a Pat<'tcx>> { + if let PatKind::TupleStruct(variant_path, [val_binding], _) = &pat.kind + && is_res_lang_ctor(cx, cx.qpath_res(variant_path, pat.hir_id), expected_ctor) + { + Some(val_binding) + } else { + None + } +} + +// Extracts the local ID of a plain `val` pattern. +fn extract_binding_pat(pat: &Pat<'_>) -> Option { + if let PatKind::Binding(BindingMode::NONE, binding, _, None) = pat.kind { + Some(binding) + } else { + None + } +} + +fn check_arm_is_some_or_ok<'tcx>(cx: &LateContext<'tcx>, mode: TryMode, arm: &Arm<'tcx>) -> bool { + let happy_ctor = match mode { + TryMode::Result => ResultOk, + TryMode::Option => OptionSome, + }; + + // Check for `Ok(val)` or `Some(val)` + if arm.guard.is_none() + && let Some(val_binding) = extract_ctor_call(cx, happy_ctor, arm.pat) + // Extract out `val` + && let Some(binding) = extract_binding_pat(val_binding) + // Check body is just `=> val` + && path_to_local_id(peel_blocks(arm.body), binding) + { + true + } else { + false + } +} + +fn check_arm_is_none_or_err<'tcx>(cx: &LateContext<'tcx>, mode: TryMode, arm: &Arm<'tcx>) -> bool { + if arm.guard.is_some() { + return false; + } + + let arm_body = peel_blocks(arm.body); + match mode { + TryMode::Result => { + // Check that pat is Err(val) + if let Some(ok_pat) = extract_ctor_call(cx, ResultErr, arm.pat) + && let Some(ok_val) = extract_binding_pat(ok_pat) + // check `=> return Err(...)` + && let ExprKind::Ret(Some(wrapped_ret_expr)) = arm_body.kind + && let ExprKind::Call(ok_ctor, [ret_expr]) = wrapped_ret_expr.kind + && is_res_lang_ctor(cx, path_res(cx, ok_ctor), ResultErr) + // check `...` is `val` from binding + && path_to_local_id(ret_expr, ok_val) + { + true + } else { + false + } + }, + TryMode::Option => { + // Check the pat is `None` + if is_res_lang_ctor(cx, path_res(cx, arm.pat), OptionNone) + // Check `=> return None` + && let ExprKind::Ret(Some(ret_expr)) = arm_body.kind + && is_res_lang_ctor(cx, path_res(cx, ret_expr), OptionNone) + && !ret_expr.span.from_expansion() + { + true + } else { + false + } + }, + } +} + +fn check_arms_are_try<'tcx>(cx: &LateContext<'tcx>, mode: TryMode, arm1: &Arm<'tcx>, arm2: &Arm<'tcx>) -> bool { + (check_arm_is_some_or_ok(cx, mode, arm1) && check_arm_is_none_or_err(cx, mode, arm2)) + || (check_arm_is_some_or_ok(cx, mode, arm2) && check_arm_is_none_or_err(cx, mode, arm1)) +} + +fn check_if_try_match<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) { + if let ExprKind::Match(scrutinee, [arm1, arm2], MatchSource::Normal | MatchSource::Postfix) = expr.kind + && !expr.span.from_expansion() + && let Some(mode) = find_try_mode(cx, scrutinee) + && !span_contains_cfg(cx, expr.span) + && check_arms_are_try(cx, mode, arm1, arm2) + { + let mut applicability = Applicability::MachineApplicable; + let snippet = snippet_with_applicability(cx, scrutinee.span.source_callsite(), "..", &mut applicability); + + span_lint_and_sugg( + cx, + QUESTION_MARK, + expr.span, + "this `match` expression can be replaced with `?`", + "try instead", + snippet.into_owned() + "?", + applicability, + ); + } +} + fn check_if_let_some_or_err_and_early_return<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) { if let Some(higher::IfLet { let_pat, @@ -339,6 +472,17 @@ fn is_try_block(cx: &LateContext<'_>, bl: &Block<'_>) -> bool { } } +fn is_inferred_ret_closure(expr: &Expr<'_>) -> bool { + let ExprKind::Closure(closure) = expr.kind else { + return false; + }; + + match closure.fn_decl.output { + FnRetTy::Return(ret_ty) => matches!(ret_ty.kind, TyKind::Infer), + FnRetTy::DefaultReturn(_) => true, + } +} + impl<'tcx> LateLintPass<'tcx> for QuestionMark { fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) { if !is_lint_allowed(cx, QUESTION_MARK_USED, stmt.hir_id) { @@ -350,11 +494,27 @@ impl<'tcx> LateLintPass<'tcx> for QuestionMark { } self.check_manual_let_else(cx, stmt); } + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if is_inferred_ret_closure(expr) { + self.inferred_ret_closure_stack += 1; + return; + } + if !self.inside_try_block() && !is_in_const_context(cx) && is_lint_allowed(cx, QUESTION_MARK_USED, expr.hir_id) { check_is_none_or_err_and_early_return(cx, expr); check_if_let_some_or_err_and_early_return(cx, expr); + + if self.inferred_ret_closure_stack == 0 { + check_if_try_match(cx, expr); + } + } + } + + fn check_expr_post(&mut self, _: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) { + if is_inferred_ret_closure(expr) { + self.inferred_ret_closure_stack -= 1; } } diff --git a/tests/ui/question_mark.fixed b/tests/ui/question_mark.fixed index 679388372e618..fb5bb953b7249 100644 --- a/tests/ui/question_mark.fixed +++ b/tests/ui/question_mark.fixed @@ -96,12 +96,42 @@ impl MoveStruct { } fn func() -> Option { + macro_rules! opt_none { + () => { + None + }; + } + fn f() -> Option { Some(String::new()) } f()?; + let _val = f()?; + + let s: &str = match &Some(String::new()) { + Some(v) => v, + None => return None, + }; + + f()?; + + opt_none!()?; + + match f() { + Some(x) => x, + None => return opt_none!(), + }; + + match f() { + Some(val) => { + println!("{val}"); + val + }, + None => return None, + }; + Some(0) } @@ -114,6 +144,10 @@ fn result_func(x: Result) -> Result { x?; + let _val = func_returning_result()?; + + func_returning_result()?; + // No warning let y = if let Ok(x) = x { x @@ -157,6 +191,18 @@ fn result_func(x: Result) -> Result { Ok(y) } +fn infer_check() { + let closure = |x: Result| { + // `?` would fail here, as it expands to `Err(val.into())` which is not constrained. + let _val = match x { + Ok(val) => val, + Err(val) => return Err(val), + }; + + Ok(()) + }; +} + // see issue #8019 pub enum NotOption { None, diff --git a/tests/ui/question_mark.rs b/tests/ui/question_mark.rs index 601ab78bf5aab..f266bd9d5adfa 100644 --- a/tests/ui/question_mark.rs +++ b/tests/ui/question_mark.rs @@ -124,6 +124,12 @@ impl MoveStruct { } fn func() -> Option { + macro_rules! opt_none { + () => { + None + }; + } + fn f() -> Option { Some(String::new()) } @@ -132,6 +138,39 @@ fn func() -> Option { return None; } + let _val = match f() { + Some(val) => val, + None => return None, + }; + + let s: &str = match &Some(String::new()) { + Some(v) => v, + None => return None, + }; + + match f() { + Some(val) => val, + None => return None, + }; + + match opt_none!() { + Some(x) => x, + None => return None, + }; + + match f() { + Some(x) => x, + None => return opt_none!(), + }; + + match f() { + Some(val) => { + println!("{val}"); + val + }, + None => return None, + }; + Some(0) } @@ -146,6 +185,16 @@ fn result_func(x: Result) -> Result { return x; } + let _val = match func_returning_result() { + Ok(val) => val, + Err(err) => return Err(err), + }; + + match func_returning_result() { + Ok(val) => val, + Err(err) => return Err(err), + }; + // No warning let y = if let Ok(x) = x { x @@ -189,6 +238,18 @@ fn result_func(x: Result) -> Result { Ok(y) } +fn infer_check() { + let closure = |x: Result| { + // `?` would fail here, as it expands to `Err(val.into())` which is not constrained. + let _val = match x { + Ok(val) => val, + Err(val) => return Err(val), + }; + + Ok(()) + }; +} + // see issue #8019 pub enum NotOption { None, diff --git a/tests/ui/question_mark.stderr b/tests/ui/question_mark.stderr index 5f26a7ea2c3e3..8ba5f59066901 100644 --- a/tests/ui/question_mark.stderr +++ b/tests/ui/question_mark.stderr @@ -94,29 +94,76 @@ LL | | }; | |_________^ help: replace it with: `self.opt?` error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:131:5 + --> tests/ui/question_mark.rs:137:5 | LL | / if f().is_none() { LL | | return None; LL | | } | |_____^ help: replace it with: `f()?;` +error: this `match` expression can be replaced with `?` + --> tests/ui/question_mark.rs:141:16 + | +LL | let _val = match f() { + | ________________^ +LL | | Some(val) => val, +LL | | None => return None, +LL | | }; + | |_____^ help: try instead: `f()?` + +error: this `match` expression can be replaced with `?` + --> tests/ui/question_mark.rs:151:5 + | +LL | / match f() { +LL | | Some(val) => val, +LL | | None => return None, +LL | | }; + | |_____^ help: try instead: `f()?` + +error: this `match` expression can be replaced with `?` + --> tests/ui/question_mark.rs:156:5 + | +LL | / match opt_none!() { +LL | | Some(x) => x, +LL | | None => return None, +LL | | }; + | |_____^ help: try instead: `opt_none!()?` + error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:143:13 + --> tests/ui/question_mark.rs:182:13 | LL | let _ = if let Ok(x) = x { x } else { return x }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: replace it with: `x?` error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:145:5 + --> tests/ui/question_mark.rs:184:5 | LL | / if x.is_err() { LL | | return x; LL | | } | |_____^ help: replace it with: `x?;` +error: this `match` expression can be replaced with `?` + --> tests/ui/question_mark.rs:188:16 + | +LL | let _val = match func_returning_result() { + | ________________^ +LL | | Ok(val) => val, +LL | | Err(err) => return Err(err), +LL | | }; + | |_____^ help: try instead: `func_returning_result()?` + +error: this `match` expression can be replaced with `?` + --> tests/ui/question_mark.rs:193:5 + | +LL | / match func_returning_result() { +LL | | Ok(val) => val, +LL | | Err(err) => return Err(err), +LL | | }; + | |_____^ help: try instead: `func_returning_result()?` + error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:213:5 + --> tests/ui/question_mark.rs:274:5 | LL | / if let Err(err) = func_returning_result() { LL | | return Err(err); @@ -124,7 +171,7 @@ LL | | } | |_____^ help: replace it with: `func_returning_result()?;` error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:220:5 + --> tests/ui/question_mark.rs:281:5 | LL | / if let Err(err) = func_returning_result() { LL | | return Err(err); @@ -132,7 +179,7 @@ LL | | } | |_____^ help: replace it with: `func_returning_result()?;` error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:297:13 + --> tests/ui/question_mark.rs:358:13 | LL | / if a.is_none() { LL | | return None; @@ -142,12 +189,12 @@ LL | | } | |_____________^ help: replace it with: `a?;` error: this `let...else` may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:357:5 + --> tests/ui/question_mark.rs:418:5 | LL | / let Some(v) = bar.foo.owned.clone() else { LL | | return None; LL | | }; | |______^ help: replace it with: `let v = bar.foo.owned.clone()?;` -error: aborting due to 17 previous errors +error: aborting due to 22 previous errors From 139bb25927f75c53e05d0c36dc9cfbc8081750c5 Mon Sep 17 00:00:00 2001 From: GnomedDev Date: Sat, 2 Nov 2024 13:41:16 +0000 Subject: [PATCH 03/18] Avoid linting for closures with inferred return types --- clippy_lints/src/question_mark.rs | 4 ++-- tests/ui/question_mark.fixed | 10 ++++++++++ tests/ui/question_mark.rs | 10 ++++++++++ tests/ui/question_mark.stderr | 8 ++++---- 4 files changed, 26 insertions(+), 6 deletions(-) diff --git a/clippy_lints/src/question_mark.rs b/clippy_lints/src/question_mark.rs index ce629717617a2..7d1ba45c3ea6d 100644 --- a/clippy_lints/src/question_mark.rs +++ b/clippy_lints/src/question_mark.rs @@ -16,7 +16,7 @@ use rustc_hir::LangItem::{self, OptionNone, OptionSome, ResultErr, ResultOk}; use rustc_hir::def::Res; use rustc_hir::{ Arm, BindingMode, Block, Body, ByRef, Expr, ExprKind, FnRetTy, HirId, LetStmt, MatchSource, Mutability, Node, Pat, - PatKind, PathSegment, QPath, Stmt, StmtKind, TyKind, + PatKind, PathSegment, QPath, Stmt, StmtKind, }; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{self, Ty}; @@ -478,7 +478,7 @@ fn is_inferred_ret_closure(expr: &Expr<'_>) -> bool { }; match closure.fn_decl.output { - FnRetTy::Return(ret_ty) => matches!(ret_ty.kind, TyKind::Infer), + FnRetTy::Return(ret_ty) => ret_ty.is_suggestable_infer_ty(), FnRetTy::DefaultReturn(_) => true, } } diff --git a/tests/ui/question_mark.fixed b/tests/ui/question_mark.fixed index fb5bb953b7249..b6e148e9f7724 100644 --- a/tests/ui/question_mark.fixed +++ b/tests/ui/question_mark.fixed @@ -201,6 +201,16 @@ fn infer_check() { Ok(()) }; + + let closure = |x: Result| -> Result<(), _> { + // `?` would fail here, as it expands to `Err(val.into())` which is not constrained. + let _val = match x { + Ok(val) => val, + Err(val) => return Err(val), + }; + + Ok(()) + }; } // see issue #8019 diff --git a/tests/ui/question_mark.rs b/tests/ui/question_mark.rs index f266bd9d5adfa..48dc9eb0a6261 100644 --- a/tests/ui/question_mark.rs +++ b/tests/ui/question_mark.rs @@ -248,6 +248,16 @@ fn infer_check() { Ok(()) }; + + let closure = |x: Result| -> Result<(), _> { + // `?` would fail here, as it expands to `Err(val.into())` which is not constrained. + let _val = match x { + Ok(val) => val, + Err(val) => return Err(val), + }; + + Ok(()) + }; } // see issue #8019 diff --git a/tests/ui/question_mark.stderr b/tests/ui/question_mark.stderr index 8ba5f59066901..0a48c4e80cb60 100644 --- a/tests/ui/question_mark.stderr +++ b/tests/ui/question_mark.stderr @@ -163,7 +163,7 @@ LL | | }; | |_____^ help: try instead: `func_returning_result()?` error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:274:5 + --> tests/ui/question_mark.rs:284:5 | LL | / if let Err(err) = func_returning_result() { LL | | return Err(err); @@ -171,7 +171,7 @@ LL | | } | |_____^ help: replace it with: `func_returning_result()?;` error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:281:5 + --> tests/ui/question_mark.rs:291:5 | LL | / if let Err(err) = func_returning_result() { LL | | return Err(err); @@ -179,7 +179,7 @@ LL | | } | |_____^ help: replace it with: `func_returning_result()?;` error: this block may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:358:13 + --> tests/ui/question_mark.rs:368:13 | LL | / if a.is_none() { LL | | return None; @@ -189,7 +189,7 @@ LL | | } | |_____________^ help: replace it with: `a?;` error: this `let...else` may be rewritten with the `?` operator - --> tests/ui/question_mark.rs:418:5 + --> tests/ui/question_mark.rs:428:5 | LL | / let Some(v) = bar.foo.owned.clone() else { LL | | return None; From a0b7681a6f5e0522a4e8255961523370256bfd7d Mon Sep 17 00:00:00 2001 From: TheSlapstickDictator <6685366+TheSlapstickDictator@users.noreply.github.com> Date: Sat, 2 Nov 2024 19:47:11 -0700 Subject: [PATCH 04/18] fix: `identity_op` suggestions use correct parenthesis The `identity_op` lint was suggesting code fixes that resulted in incorrect or broken code, due to missing parenthesis in the fix that changed the semantics of the code. For a binary expression, `left op right`, if the `left` was redundant, it would check if the right side needed parenthesis, but if the `right` was redundant, it would just assume that the left side did not need parenthesis. This can result in either rustfix generating broken code and failing, or code that has different behavior than before the fix. e.g. `-(x + y + 0)` would turn into `-x + y`, changing the behavior, and `1u64 + (x + y + 0i32) as u64` where `x: i32` and `y: i32` would turn into `1u64 + x + y as u64`, creating broken code where `x` cannot be added to the other values, as it was never cast to `u64`. This commit fixes both of these cases by always checking the non-redundant child of a binary expression for needed parenthesis, and makes it so if we need parenthesis, but they already exist, we don't add any redundant ones. Fixes #13470 --- clippy_lints/src/operators/identity_op.rs | 211 +++++++++++----------- tests/ui/identity_op.fixed | 42 ++++- tests/ui/identity_op.rs | 40 ++++ tests/ui/identity_op.stderr | 64 ++++++- 4 files changed, 252 insertions(+), 105 deletions(-) diff --git a/clippy_lints/src/operators/identity_op.rs b/clippy_lints/src/operators/identity_op.rs index 830be50c8ba4f..d8430f6492d24 100644 --- a/clippy_lints/src/operators/identity_op.rs +++ b/clippy_lints/src/operators/identity_op.rs @@ -42,83 +42,53 @@ pub(crate) fn check<'tcx>( match op { BinOpKind::Add | BinOpKind::BitOr | BinOpKind::BitXor => { - let _ = check_op( - cx, - left, - 0, - expr.span, - peeled_right_span, - needs_parenthesis(cx, expr, right), - right_is_coerced_to_value, - ) || check_op( - cx, - right, - 0, - expr.span, - peeled_left_span, - Parens::Unneeded, - left_is_coerced_to_value, - ); + if is_redundant_op(cx, left, 0) { + let paren = needs_parenthesis(cx, expr, right); + span_ineffective_operation(cx, expr.span, peeled_right_span, paren, right_is_coerced_to_value); + } else if is_redundant_op(cx, right, 0) { + let paren = needs_parenthesis(cx, expr, left); + span_ineffective_operation(cx, expr.span, peeled_left_span, paren, left_is_coerced_to_value); + } }, BinOpKind::Shl | BinOpKind::Shr | BinOpKind::Sub => { - let _ = check_op( - cx, - right, - 0, - expr.span, - peeled_left_span, - Parens::Unneeded, - left_is_coerced_to_value, - ); + if is_redundant_op(cx, right, 0) { + span_ineffective_operation( + cx, + expr.span, + peeled_left_span, + Parens::Unneeded, + left_is_coerced_to_value, + ); + } }, BinOpKind::Mul => { - let _ = check_op( - cx, - left, - 1, - expr.span, - peeled_right_span, - needs_parenthesis(cx, expr, right), - right_is_coerced_to_value, - ) || check_op( - cx, - right, - 1, - expr.span, - peeled_left_span, - Parens::Unneeded, - left_is_coerced_to_value, - ); + if is_redundant_op(cx, left, 1) { + let paren = needs_parenthesis(cx, expr, right); + span_ineffective_operation(cx, expr.span, peeled_right_span, paren, right_is_coerced_to_value); + } else if is_redundant_op(cx, right, 1) { + let paren = needs_parenthesis(cx, expr, left); + span_ineffective_operation(cx, expr.span, peeled_left_span, paren, left_is_coerced_to_value); + } }, BinOpKind::Div => { - let _ = check_op( - cx, - right, - 1, - expr.span, - peeled_left_span, - Parens::Unneeded, - left_is_coerced_to_value, - ); + if is_redundant_op(cx, right, 1) { + span_ineffective_operation( + cx, + expr.span, + peeled_left_span, + Parens::Unneeded, + left_is_coerced_to_value, + ); + } }, BinOpKind::BitAnd => { - let _ = check_op( - cx, - left, - -1, - expr.span, - peeled_right_span, - needs_parenthesis(cx, expr, right), - right_is_coerced_to_value, - ) || check_op( - cx, - right, - -1, - expr.span, - peeled_left_span, - Parens::Unneeded, - left_is_coerced_to_value, - ); + if is_redundant_op(cx, left, -1) { + let paren = needs_parenthesis(cx, expr, right); + span_ineffective_operation(cx, expr.span, peeled_right_span, paren, right_is_coerced_to_value); + } else if is_redundant_op(cx, right, -1) { + let paren = needs_parenthesis(cx, expr, left); + span_ineffective_operation(cx, expr.span, peeled_left_span, paren, left_is_coerced_to_value); + } }, BinOpKind::Rem => check_remainder(cx, left, right, expr.span, left.span), _ => (), @@ -138,43 +108,75 @@ enum Parens { Unneeded, } -/// Checks if `left op right` needs parenthesis when reduced to `right` +/// Checks if a binary expression needs parenthesis when reduced to just its +/// right or left child. +/// +/// e.g. `-(x + y + 0)` cannot be reduced to `-x + y`, as the behavior changes silently. +/// e.g. `1u64 + ((x + y + 0i32) as u64)` cannot be reduced to `1u64 + x + y as u64`, since +/// the the cast expression will not apply to the same expression. /// e.g. `0 + if b { 1 } else { 2 } + if b { 3 } else { 4 }` cannot be reduced /// to `if b { 1 } else { 2 } + if b { 3 } else { 4 }` where the `if` could be -/// interpreted as a statement +/// interpreted as a statement. The same behavior happens for `match`, `loop`, +/// and blocks. +/// e.g. `2 * (0 + { a })` can be reduced to `2 * { a }` without the need for parenthesis, +/// but `1 * ({ a } + 4)` cannot be reduced to `{ a } + 4`, as a block at the start of a line +/// will be interpreted as a statement instead of an expression. /// -/// See #8724 -fn needs_parenthesis(cx: &LateContext<'_>, binary: &Expr<'_>, right: &Expr<'_>) -> Parens { - match right.kind { +/// See #8724, #13470 +fn needs_parenthesis(cx: &LateContext<'_>, binary: &Expr<'_>, child: &Expr<'_>) -> Parens { + match child.kind { ExprKind::Binary(_, lhs, _) | ExprKind::Cast(lhs, _) => { - // ensure we're checking against the leftmost expression of `right` - // - // ~~~ `lhs` - // 0 + {4} * 2 - // ~~~~~~~ `right` - return needs_parenthesis(cx, binary, lhs); + // For casts and binary expressions, we want to add parenthesis if + // the parent HIR node is an expression with a different precedence, + // or if the parent HIR node is a Block or Stmt, and the new left hand side + // would be treated as a statement rather than an expression. + if let Some((_, parent)) = cx.tcx.hir().parent_iter(binary.hir_id).next() { + if let Node::Expr(expr) = parent { + if child.precedence().order() != expr.precedence().order() { + return Parens::Needed; + } + return Parens::Unneeded; + } + match parent { + Node::Block(_) | Node::Stmt(_) => { + // ensure we're checking against the leftmost expression of `child` + // + // ~~~~~~~~~~~ `binary` + // ~~~ `lhs` + // 0 + {4} * 2 + // ~~~~~~~ `child` + return needs_parenthesis(cx, binary, lhs); + }, + _ => return Parens::Unneeded, + } + } }, - ExprKind::If(..) | ExprKind::Match(..) | ExprKind::Block(..) | ExprKind::Loop(..) => {}, - _ => return Parens::Unneeded, - } + ExprKind::If(..) | ExprKind::Match(..) | ExprKind::Block(..) | ExprKind::Loop(..) => { + // For if, match, block, and loop expressions, we want to add parenthesis if + // the closest ancestor node that is not an expression is a block or statement. + // This would mean that the rustfix suggestion will appear at the start of a line, which causes + // these expressions to be interpreted as statements if they do not have parenthesis. + let mut prev_id = binary.hir_id; + for (_, parent) in cx.tcx.hir().parent_iter(binary.hir_id) { + if let Node::Expr(expr) = parent + && let ExprKind::Binary(_, lhs, _) | ExprKind::Cast(lhs, _) | ExprKind::Unary(_, lhs) = expr.kind + && lhs.hir_id == prev_id + { + // keep going until we find a node that encompasses left of `binary` + prev_id = expr.hir_id; + continue; + } - let mut prev_id = binary.hir_id; - for (_, node) in cx.tcx.hir().parent_iter(binary.hir_id) { - if let Node::Expr(expr) = node - && let ExprKind::Binary(_, lhs, _) | ExprKind::Cast(lhs, _) = expr.kind - && lhs.hir_id == prev_id - { - // keep going until we find a node that encompasses left of `binary` - prev_id = expr.hir_id; - continue; - } - - match node { - Node::Block(_) | Node::Stmt(_) => break, - _ => return Parens::Unneeded, - }; + match parent { + Node::Block(_) | Node::Stmt(_) => return Parens::Needed, + _ => return Parens::Unneeded, + }; + } + }, + _ => { + return Parens::Unneeded; + }, } - Parens::Needed } @@ -199,7 +201,7 @@ fn check_remainder(cx: &LateContext<'_>, left: &Expr<'_>, right: &Expr<'_>, span } } -fn check_op(cx: &LateContext<'_>, e: &Expr<'_>, m: i8, span: Span, arg: Span, parens: Parens, is_erased: bool) -> bool { +fn is_redundant_op(cx: &LateContext<'_>, e: &Expr<'_>, m: i8) -> bool { if let Some(Constant::Int(v)) = ConstEvalCtxt::new(cx).eval_simple(e).map(Constant::peel_refs) { let check = match *cx.typeck_results().expr_ty(e).peel_refs().kind() { ty::Int(ity) => unsext(cx.tcx, -1_i128, ity), @@ -212,7 +214,6 @@ fn check_op(cx: &LateContext<'_>, e: &Expr<'_>, m: i8, span: Span, arg: Span, pa 1 => v == 1, _ => unreachable!(), } { - span_ineffective_operation(cx, span, arg, parens, is_erased); return true; } } @@ -234,7 +235,13 @@ fn span_ineffective_operation( expr_snippet.into_owned() }; let suggestion = match parens { - Parens::Needed => format!("({expr_snippet})"), + Parens::Needed => { + if !expr_snippet.starts_with('(') && !expr_snippet.ends_with(')') { + format!("({expr_snippet})") + } else { + expr_snippet + } + }, Parens::Unneeded => expr_snippet, }; diff --git a/tests/ui/identity_op.fixed b/tests/ui/identity_op.fixed index b18d8560f6f8b..18b7401768df0 100644 --- a/tests/ui/identity_op.fixed +++ b/tests/ui/identity_op.fixed @@ -149,7 +149,7 @@ fn main() { 2 * { a }; //~^ ERROR: this operation has no effect - (({ a } + 4)); + ({ a } + 4); //~^ ERROR: this operation has no effect 1; //~^ ERROR: this operation has no effect @@ -212,3 +212,43 @@ fn issue_12050() { //~^ ERROR: this operation has no effect } } + +fn issue_13470() { + let x = 1i32; + let y = 1i32; + // Removes the + 0i32 while keeping the parentheses around x + y so the cast operation works + let _: u64 = (x + y) as u64; + //~^ ERROR: this operation has no effect + // both of the next two lines should look the same after rustfix + let _: u64 = 1u64 & (x + y) as u64; + //~^ ERROR: this operation has no effect + // Same as above, but with extra redundant parenthesis + let _: u64 = 1u64 & (x + y) as u64; + //~^ ERROR: this operation has no effect + // Should maintain parenthesis even if the surrounding expr has the same precedence + let _: u64 = 5u64 + (x + y) as u64; + //~^ ERROR: this operation has no effect + + // If we don't maintain the parens here, the behavior changes + let _ = -(x + y); + //~^ ERROR: this operation has no effect + // Maintain parenthesis if the parent expr is of higher precedence + let _ = 2i32 * (x + y); + //~^ ERROR: this operation has no effect + // No need for parenthesis if the parent expr is of equal precedence + let _ = 2i32 + x + y; + //~^ ERROR: this operation has no effect + // But make sure that inner parens still exist + let z = 1i32; + let _ = 2 + x + (y * z); + //~^ ERROR: this operation has no effect + // Maintain parenthesis if the parent expr is of lower precedence + // This is for clarity, and clippy will not warn on these being unnecessary + let _ = 2i32 + (x * y); + //~^ ERROR: this operation has no effect + + let x = 1i16; + let y = 1i16; + let _: u64 = 1u64 + (x as i32 + y as i32) as u64; + //~^ ERROR: this operation has no effect +} diff --git a/tests/ui/identity_op.rs b/tests/ui/identity_op.rs index f1f01b424478c..55483f4e5fb3b 100644 --- a/tests/ui/identity_op.rs +++ b/tests/ui/identity_op.rs @@ -212,3 +212,43 @@ fn issue_12050() { //~^ ERROR: this operation has no effect } } + +fn issue_13470() { + let x = 1i32; + let y = 1i32; + // Removes the + 0i32 while keeping the parentheses around x + y so the cast operation works + let _: u64 = (x + y + 0i32) as u64; + //~^ ERROR: this operation has no effect + // both of the next two lines should look the same after rustfix + let _: u64 = 1u64 & (x + y + 0i32) as u64; + //~^ ERROR: this operation has no effect + // Same as above, but with extra redundant parenthesis + let _: u64 = 1u64 & ((x + y) + 0i32) as u64; + //~^ ERROR: this operation has no effect + // Should maintain parenthesis even if the surrounding expr has the same precedence + let _: u64 = 5u64 + ((x + y) + 0i32) as u64; + //~^ ERROR: this operation has no effect + + // If we don't maintain the parens here, the behavior changes + let _ = -(x + y + 0i32); + //~^ ERROR: this operation has no effect + // Maintain parenthesis if the parent expr is of higher precedence + let _ = 2i32 * (x + y + 0i32); + //~^ ERROR: this operation has no effect + // No need for parenthesis if the parent expr is of equal precedence + let _ = 2i32 + (x + y + 0i32); + //~^ ERROR: this operation has no effect + // But make sure that inner parens still exist + let z = 1i32; + let _ = 2 + (x + (y * z) + 0); + //~^ ERROR: this operation has no effect + // Maintain parenthesis if the parent expr is of lower precedence + // This is for clarity, and clippy will not warn on these being unnecessary + let _ = 2i32 + (x * y * 1i32); + //~^ ERROR: this operation has no effect + + let x = 1i16; + let y = 1i16; + let _: u64 = 1u64 + ((x as i32 + y as i32) as u64 + 0u64); + //~^ ERROR: this operation has no effect +} diff --git a/tests/ui/identity_op.stderr b/tests/ui/identity_op.stderr index 9fff86b86f9f6..856bebfa1ffc6 100644 --- a/tests/ui/identity_op.stderr +++ b/tests/ui/identity_op.stderr @@ -221,7 +221,7 @@ error: this operation has no effect --> tests/ui/identity_op.rs:152:5 | LL | 1 * ({ a } + 4); - | ^^^^^^^^^^^^^^^ help: consider reducing it to: `(({ a } + 4))` + | ^^^^^^^^^^^^^^^ help: consider reducing it to: `({ a } + 4)` error: this operation has no effect --> tests/ui/identity_op.rs:154:5 @@ -313,5 +313,65 @@ error: this operation has no effect LL | let _: i32 = **&&*&x + 0; | ^^^^^^^^^^^ help: consider reducing it to: `***&&*&x` -error: aborting due to 52 previous errors +error: this operation has no effect + --> tests/ui/identity_op.rs:220:18 + | +LL | let _: u64 = (x + y + 0i32) as u64; + | ^^^^^^^^^^^^^^ help: consider reducing it to: `(x + y)` + +error: this operation has no effect + --> tests/ui/identity_op.rs:223:25 + | +LL | let _: u64 = 1u64 & (x + y + 0i32) as u64; + | ^^^^^^^^^^^^^^ help: consider reducing it to: `(x + y)` + +error: this operation has no effect + --> tests/ui/identity_op.rs:226:25 + | +LL | let _: u64 = 1u64 & ((x + y) + 0i32) as u64; + | ^^^^^^^^^^^^^^^^ help: consider reducing it to: `(x + y)` + +error: this operation has no effect + --> tests/ui/identity_op.rs:229:25 + | +LL | let _: u64 = 5u64 + ((x + y) + 0i32) as u64; + | ^^^^^^^^^^^^^^^^ help: consider reducing it to: `(x + y)` + +error: this operation has no effect + --> tests/ui/identity_op.rs:233:14 + | +LL | let _ = -(x + y + 0i32); + | ^^^^^^^^^^^^^^ help: consider reducing it to: `(x + y)` + +error: this operation has no effect + --> tests/ui/identity_op.rs:236:20 + | +LL | let _ = 2i32 * (x + y + 0i32); + | ^^^^^^^^^^^^^^ help: consider reducing it to: `(x + y)` + +error: this operation has no effect + --> tests/ui/identity_op.rs:239:20 + | +LL | let _ = 2i32 + (x + y + 0i32); + | ^^^^^^^^^^^^^^ help: consider reducing it to: `x + y` + +error: this operation has no effect + --> tests/ui/identity_op.rs:243:17 + | +LL | let _ = 2 + (x + (y * z) + 0); + | ^^^^^^^^^^^^^^^^^ help: consider reducing it to: `x + (y * z)` + +error: this operation has no effect + --> tests/ui/identity_op.rs:247:20 + | +LL | let _ = 2i32 + (x * y * 1i32); + | ^^^^^^^^^^^^^^ help: consider reducing it to: `(x * y)` + +error: this operation has no effect + --> tests/ui/identity_op.rs:252:25 + | +LL | let _: u64 = 1u64 + ((x as i32 + y as i32) as u64 + 0u64); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider reducing it to: `(x as i32 + y as i32) as u64` + +error: aborting due to 62 previous errors From 6d738f6bc032ca539d08a7d59674f1e74e539674 Mon Sep 17 00:00:00 2001 From: TheSlapstickDictator <6685366+TheSlapstickDictator@users.noreply.github.com> Date: Sun, 3 Nov 2024 09:23:03 -0800 Subject: [PATCH 05/18] Fix parens getting removed for non-associative operators --- clippy_lints/src/operators/identity_op.rs | 22 ++++++---------------- tests/ui/identity_op.fixed | 5 +++-- tests/ui/identity_op.rs | 5 +++-- tests/ui/identity_op.stderr | 12 ++++++------ 4 files changed, 18 insertions(+), 26 deletions(-) diff --git a/clippy_lints/src/operators/identity_op.rs b/clippy_lints/src/operators/identity_op.rs index d8430f6492d24..e3ce1ae9427d3 100644 --- a/clippy_lints/src/operators/identity_op.rs +++ b/clippy_lints/src/operators/identity_op.rs @@ -52,13 +52,8 @@ pub(crate) fn check<'tcx>( }, BinOpKind::Shl | BinOpKind::Shr | BinOpKind::Sub => { if is_redundant_op(cx, right, 0) { - span_ineffective_operation( - cx, - expr.span, - peeled_left_span, - Parens::Unneeded, - left_is_coerced_to_value, - ); + let paren = needs_parenthesis(cx, expr, left); + span_ineffective_operation(cx, expr.span, peeled_left_span, paren, left_is_coerced_to_value); } }, BinOpKind::Mul => { @@ -127,17 +122,12 @@ fn needs_parenthesis(cx: &LateContext<'_>, binary: &Expr<'_>, child: &Expr<'_>) match child.kind { ExprKind::Binary(_, lhs, _) | ExprKind::Cast(lhs, _) => { // For casts and binary expressions, we want to add parenthesis if - // the parent HIR node is an expression with a different precedence, - // or if the parent HIR node is a Block or Stmt, and the new left hand side - // would be treated as a statement rather than an expression. + // the parent HIR node is an expression, or if the parent HIR node + // is a Block or Stmt, and the new left hand side would need + // parenthesis be treated as a statement rather than an expression. if let Some((_, parent)) = cx.tcx.hir().parent_iter(binary.hir_id).next() { - if let Node::Expr(expr) = parent { - if child.precedence().order() != expr.precedence().order() { - return Parens::Needed; - } - return Parens::Unneeded; - } match parent { + Node::Expr(_) => return Parens::Needed, Node::Block(_) | Node::Stmt(_) => { // ensure we're checking against the leftmost expression of `child` // diff --git a/tests/ui/identity_op.fixed b/tests/ui/identity_op.fixed index 18b7401768df0..927142d5f8022 100644 --- a/tests/ui/identity_op.fixed +++ b/tests/ui/identity_op.fixed @@ -235,8 +235,9 @@ fn issue_13470() { // Maintain parenthesis if the parent expr is of higher precedence let _ = 2i32 * (x + y); //~^ ERROR: this operation has no effect - // No need for parenthesis if the parent expr is of equal precedence - let _ = 2i32 + x + y; + // Maintain parenthesis if the parent expr is the same precedence + // as not all operations are associative + let _ = 2i32 - (x - y); //~^ ERROR: this operation has no effect // But make sure that inner parens still exist let z = 1i32; diff --git a/tests/ui/identity_op.rs b/tests/ui/identity_op.rs index 55483f4e5fb3b..a50546929a82e 100644 --- a/tests/ui/identity_op.rs +++ b/tests/ui/identity_op.rs @@ -235,8 +235,9 @@ fn issue_13470() { // Maintain parenthesis if the parent expr is of higher precedence let _ = 2i32 * (x + y + 0i32); //~^ ERROR: this operation has no effect - // No need for parenthesis if the parent expr is of equal precedence - let _ = 2i32 + (x + y + 0i32); + // Maintain parenthesis if the parent expr is the same precedence + // as not all operations are associative + let _ = 2i32 - (x - y - 0i32); //~^ ERROR: this operation has no effect // But make sure that inner parens still exist let z = 1i32; diff --git a/tests/ui/identity_op.stderr b/tests/ui/identity_op.stderr index 856bebfa1ffc6..79d1a7bd81c36 100644 --- a/tests/ui/identity_op.stderr +++ b/tests/ui/identity_op.stderr @@ -350,25 +350,25 @@ LL | let _ = 2i32 * (x + y + 0i32); | ^^^^^^^^^^^^^^ help: consider reducing it to: `(x + y)` error: this operation has no effect - --> tests/ui/identity_op.rs:239:20 + --> tests/ui/identity_op.rs:240:20 | -LL | let _ = 2i32 + (x + y + 0i32); - | ^^^^^^^^^^^^^^ help: consider reducing it to: `x + y` +LL | let _ = 2i32 - (x - y - 0i32); + | ^^^^^^^^^^^^^^ help: consider reducing it to: `(x - y)` error: this operation has no effect - --> tests/ui/identity_op.rs:243:17 + --> tests/ui/identity_op.rs:244:17 | LL | let _ = 2 + (x + (y * z) + 0); | ^^^^^^^^^^^^^^^^^ help: consider reducing it to: `x + (y * z)` error: this operation has no effect - --> tests/ui/identity_op.rs:247:20 + --> tests/ui/identity_op.rs:248:20 | LL | let _ = 2i32 + (x * y * 1i32); | ^^^^^^^^^^^^^^ help: consider reducing it to: `(x * y)` error: this operation has no effect - --> tests/ui/identity_op.rs:252:25 + --> tests/ui/identity_op.rs:253:25 | LL | let _: u64 = 1u64 + ((x as i32 + y as i32) as u64 + 0u64); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider reducing it to: `(x as i32 + y as i32) as u64` From 1b7239d9548d5d6672ac05ba1f48019a29b9790d Mon Sep 17 00:00:00 2001 From: TheSlapstickDictator <6685366+TheSlapstickDictator@users.noreply.github.com> Date: Mon, 4 Nov 2024 19:19:58 -0800 Subject: [PATCH 06/18] Fixing a missed check for needs_parenthesis in division --- clippy_lints/src/operators/identity_op.rs | 9 ++------- tests/ui/identity_op.fixed | 3 +++ tests/ui/identity_op.rs | 3 +++ tests/ui/identity_op.stderr | 18 ++++++++++++------ 4 files changed, 20 insertions(+), 13 deletions(-) diff --git a/clippy_lints/src/operators/identity_op.rs b/clippy_lints/src/operators/identity_op.rs index e3ce1ae9427d3..7699378572760 100644 --- a/clippy_lints/src/operators/identity_op.rs +++ b/clippy_lints/src/operators/identity_op.rs @@ -67,13 +67,8 @@ pub(crate) fn check<'tcx>( }, BinOpKind::Div => { if is_redundant_op(cx, right, 1) { - span_ineffective_operation( - cx, - expr.span, - peeled_left_span, - Parens::Unneeded, - left_is_coerced_to_value, - ); + let paren = needs_parenthesis(cx, expr, left); + span_ineffective_operation(cx, expr.span, peeled_left_span, paren, left_is_coerced_to_value); } }, BinOpKind::BitAnd => { diff --git a/tests/ui/identity_op.fixed b/tests/ui/identity_op.fixed index 927142d5f8022..b80ba701c3ad7 100644 --- a/tests/ui/identity_op.fixed +++ b/tests/ui/identity_op.fixed @@ -232,6 +232,9 @@ fn issue_13470() { // If we don't maintain the parens here, the behavior changes let _ = -(x + y); //~^ ERROR: this operation has no effect + // Similarly, we need to maintain parens here + let _ = -(x / y); + //~^ ERROR: this operation has no effect // Maintain parenthesis if the parent expr is of higher precedence let _ = 2i32 * (x + y); //~^ ERROR: this operation has no effect diff --git a/tests/ui/identity_op.rs b/tests/ui/identity_op.rs index a50546929a82e..3e20fa6f2b89d 100644 --- a/tests/ui/identity_op.rs +++ b/tests/ui/identity_op.rs @@ -232,6 +232,9 @@ fn issue_13470() { // If we don't maintain the parens here, the behavior changes let _ = -(x + y + 0i32); //~^ ERROR: this operation has no effect + // Similarly, we need to maintain parens here + let _ = -(x / y / 1i32); + //~^ ERROR: this operation has no effect // Maintain parenthesis if the parent expr is of higher precedence let _ = 2i32 * (x + y + 0i32); //~^ ERROR: this operation has no effect diff --git a/tests/ui/identity_op.stderr b/tests/ui/identity_op.stderr index 79d1a7bd81c36..3f5cab5404d04 100644 --- a/tests/ui/identity_op.stderr +++ b/tests/ui/identity_op.stderr @@ -344,34 +344,40 @@ LL | let _ = -(x + y + 0i32); | ^^^^^^^^^^^^^^ help: consider reducing it to: `(x + y)` error: this operation has no effect - --> tests/ui/identity_op.rs:236:20 + --> tests/ui/identity_op.rs:236:14 + | +LL | let _ = -(x / y / 1i32); + | ^^^^^^^^^^^^^^ help: consider reducing it to: `(x / y)` + +error: this operation has no effect + --> tests/ui/identity_op.rs:239:20 | LL | let _ = 2i32 * (x + y + 0i32); | ^^^^^^^^^^^^^^ help: consider reducing it to: `(x + y)` error: this operation has no effect - --> tests/ui/identity_op.rs:240:20 + --> tests/ui/identity_op.rs:243:20 | LL | let _ = 2i32 - (x - y - 0i32); | ^^^^^^^^^^^^^^ help: consider reducing it to: `(x - y)` error: this operation has no effect - --> tests/ui/identity_op.rs:244:17 + --> tests/ui/identity_op.rs:247:17 | LL | let _ = 2 + (x + (y * z) + 0); | ^^^^^^^^^^^^^^^^^ help: consider reducing it to: `x + (y * z)` error: this operation has no effect - --> tests/ui/identity_op.rs:248:20 + --> tests/ui/identity_op.rs:251:20 | LL | let _ = 2i32 + (x * y * 1i32); | ^^^^^^^^^^^^^^ help: consider reducing it to: `(x * y)` error: this operation has no effect - --> tests/ui/identity_op.rs:253:25 + --> tests/ui/identity_op.rs:256:25 | LL | let _: u64 = 1u64 + ((x as i32 + y as i32) as u64 + 0u64); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider reducing it to: `(x as i32 + y as i32) as u64` -error: aborting due to 62 previous errors +error: aborting due to 63 previous errors From 6ced8c33c058fa1df65a363abcdc5e2c5828fa66 Mon Sep 17 00:00:00 2001 From: Philipp Krones Date: Thu, 7 Nov 2024 22:31:20 +0100 Subject: [PATCH 07/18] Merge commit 'f712eb5cdccd121d0569af12f20e6a0fabe4364d' into clippy-subtree-update --- .github/workflows/clippy_dev.yml | 50 +- .../{clippy_bors.yml => clippy_mq.yml} | 51 +- .../workflows/{clippy.yml => clippy_pr.yml} | 40 +- .github/workflows/deploy.yml | 2 +- .github/workflows/remark.yml | 46 +- CHANGELOG.md | 7 + CONTRIBUTING.md | 16 +- Cargo.toml | 2 +- README.md | 3 +- book/src/README.md | 3 +- book/src/development/README.md | 1 - .../development/common_tools_writing_lints.md | 4 +- book/src/development/method_checking.md | 15 +- book/src/lint_configuration.md | 33 +- clippy_config/src/conf.rs | 46 +- clippy_config/src/lib.rs | 1 + clippy_config/src/msrvs.rs | 13 +- clippy_config/src/types.rs | 301 ++++++++++ clippy_dev/src/serve.rs | 8 +- clippy_dev/src/update_lints.rs | 20 +- .../src/arbitrary_source_item_ordering.rs | 531 ++++++++++++++++++ clippy_lints/src/attrs/mod.rs | 2 +- clippy_lints/src/attrs/useless_attribute.rs | 5 +- clippy_lints/src/await_holding_invalid.rs | 2 +- clippy_lints/src/booleans.rs | 15 + clippy_lints/src/borrow_deref_ref.rs | 4 +- clippy_lints/src/casts/cast_ptr_alignment.rs | 2 +- clippy_lints/src/casts/unnecessary_cast.rs | 3 +- clippy_lints/src/checked_conversions.rs | 4 +- clippy_lints/src/cognitive_complexity.rs | 2 +- clippy_lints/src/copies.rs | 16 +- clippy_lints/src/copy_iterator.rs | 2 +- clippy_lints/src/ctfe.rs | 1 - clippy_lints/src/declare_clippy_lint.rs | 49 +- clippy_lints/src/declared_lints.rs | 6 + clippy_lints/src/derivable_impls.rs | 2 +- clippy_lints/src/derive.rs | 40 +- clippy_lints/src/disallowed_macros.rs | 23 +- clippy_lints/src/disallowed_script_idents.rs | 2 +- clippy_lints/src/doc/mod.rs | 8 +- clippy_lints/src/empty_drop.rs | 2 +- clippy_lints/src/eta_reduction.rs | 18 +- clippy_lints/src/explicit_write.rs | 2 +- clippy_lints/src/from_over_into.rs | 29 +- clippy_lints/src/from_raw_with_void_ptr.rs | 2 +- clippy_lints/src/functions/mod.rs | 2 +- clippy_lints/src/functions/ref_option.rs | 6 +- clippy_lints/src/implicit_hasher.rs | 4 +- clippy_lints/src/infinite_iter.rs | 6 +- clippy_lints/src/iter_without_into_iter.rs | 4 +- clippy_lints/src/large_const_arrays.rs | 4 +- clippy_lints/src/large_include_file.rs | 63 ++- clippy_lints/src/len_zero.rs | 2 +- clippy_lints/src/lib.rs | 12 +- clippy_lints/src/loops/explicit_iter_loop.rs | 6 +- clippy_lints/src/loops/infinite_loop.rs | 59 +- .../src/loops/while_immutable_condition.rs | 15 +- .../src/loops/while_let_on_iterator.rs | 60 +- clippy_lints/src/manual_async_fn.rs | 23 +- clippy_lints/src/manual_bits.rs | 5 +- clippy_lints/src/manual_hash_one.rs | 4 +- clippy_lints/src/manual_is_ascii_check.rs | 2 +- .../src/matches/match_like_matches.rs | 2 +- .../src/matches/match_str_case_mismatch.rs | 30 +- clippy_lints/src/matches/redundant_guards.rs | 12 +- .../src/matches/redundant_pattern_match.rs | 15 +- clippy_lints/src/matches/single_match.rs | 5 +- clippy_lints/src/methods/filter_map.rs | 8 +- clippy_lints/src/methods/is_empty.rs | 13 +- .../src/methods/iter_out_of_bounds.rs | 5 +- .../src/methods/map_all_any_identity.rs | 43 ++ .../map_with_unused_argument_over_ranges.rs | 134 +++++ clippy_lints/src/methods/mod.rs | 134 ++++- clippy_lints/src/methods/needless_as_bytes.rs | 28 + clippy_lints/src/methods/needless_collect.rs | 2 +- .../src/methods/read_line_without_trim.rs | 4 +- .../src/methods/unnecessary_filter_map.rs | 33 +- clippy_lints/src/minmax.rs | 4 +- clippy_lints/src/missing_doc.rs | 25 +- clippy_lints/src/missing_fields_in_debug.rs | 2 +- clippy_lints/src/needless_continue.rs | 5 +- clippy_lints/src/needless_late_init.rs | 4 +- clippy_lints/src/needless_maybe_sized.rs | 2 +- clippy_lints/src/needless_pass_by_ref_mut.rs | 2 +- clippy_lints/src/no_effect.rs | 8 +- clippy_lints/src/no_mangle_with_rust_abi.rs | 13 +- .../src/non_octal_unix_permissions.rs | 4 +- clippy_lints/src/operators/float_cmp.rs | 2 +- clippy_lints/src/partialeq_ne_impl.rs | 2 +- clippy_lints/src/ptr_offset_with_cast.rs | 2 +- clippy_lints/src/question_mark.rs | 6 +- clippy_lints/src/ref_option_ref.rs | 2 +- clippy_lints/src/returns.rs | 2 +- clippy_lints/src/semicolon_block.rs | 27 +- clippy_lints/src/serde_api.rs | 2 +- .../src/single_component_path_imports.rs | 2 +- .../src/slow_vector_initialization.rs | 6 +- clippy_lints/src/strings.rs | 6 +- .../src/suspicious_operation_groupings.rs | 28 +- clippy_lints/src/swap.rs | 2 +- clippy_lints/src/trait_bounds.rs | 10 +- clippy_lints/src/transmute/eager_transmute.rs | 4 +- .../src/undocumented_unsafe_blocks.rs | 2 +- clippy_lints/src/unit_types/let_unit_value.rs | 2 +- clippy_lints/src/unit_types/unit_arg.rs | 2 +- clippy_lints/src/unused_io_amount.rs | 28 +- clippy_lints/src/utils/attr_collector.rs | 40 ++ clippy_lints/src/utils/author.rs | 2 +- clippy_lints/src/utils/internal_lints.rs | 1 + .../utils/internal_lints/collapsible_calls.rs | 2 +- .../internal_lints/lint_without_lint_pass.rs | 14 +- .../utils/internal_lints/msrv_attr_impl.rs | 2 +- .../internal_lints/slow_symbol_comparisons.rs | 69 +++ clippy_lints/src/utils/mod.rs | 2 + clippy_utils/src/check_proc_macro.rs | 28 +- clippy_utils/src/consts.rs | 6 + clippy_utils/src/diagnostics.rs | 63 ++- clippy_utils/src/higher.rs | 8 +- clippy_utils/src/hir_utils.rs | 15 +- clippy_utils/src/lib.rs | 30 +- clippy_utils/src/ty.rs | 19 +- clippy_utils/src/ty/type_certainty/mod.rs | 6 +- clippy_utils/src/usage.rs | 18 +- clippy_utils/src/visitors.rs | 83 ++- rust-toolchain | 2 +- tests/compile-test.rs | 3 +- tests/ui-internal/lint_without_lint_pass.rs | 5 +- .../ui-internal/slow_symbol_comparisons.fixed | 24 + tests/ui-internal/slow_symbol_comparisons.rs | 24 + .../slow_symbol_comparisons.stderr | 23 + .../ui-internal/unnecessary_symbol_str.fixed | 1 + tests/ui-internal/unnecessary_symbol_str.rs | 1 + .../ui-internal/unnecessary_symbol_str.stderr | 10 +- .../bad_conf_1/clippy.toml | 1 + .../bad_conf_2/clippy.toml | 1 + .../bad_conf_3/clippy.toml | 1 + .../default/clippy.toml | 0 .../default_exp/clippy.toml | 12 + .../only_enum/clippy.toml | 1 + .../only_impl/clippy.toml | 1 + .../only_trait/clippy.toml | 1 + .../ordering_good.bad_conf_1.stderr | 8 + .../ordering_good.bad_conf_2.stderr | 8 + .../ordering_good.bad_conf_3.stderr | 8 + .../ordering_good.rs | 186 ++++++ .../ordering_good_var_1.rs | 174 ++++++ .../ordering_mixed.default.stderr | 226 ++++++++ .../ordering_mixed.rs | 185 ++++++ .../ordering_mixed_var_1.rs | 174 ++++++ .../ordering_mixed_var_1.var_1.stderr | 100 ++++ .../ordering_only_enum.only_enum.stderr | 16 + .../ordering_only_enum.rs | 43 ++ .../ordering_only_impl.only_impl.stderr | 40 ++ .../ordering_only_impl.rs | 67 +++ .../ordering_only_trait.only_trait.stderr | 40 ++ .../ordering_only_trait.rs | 48 ++ .../var_1/clippy.toml | 1 + .../disallowed_macros.stderr | 62 +- .../large_include_file/large_include_file.rs | 7 +- .../large_include_file.stderr | 12 +- tests/ui-toml/private-doc-errors/doc_lints.rs | 6 + .../toml_unknown_key/conf_unknown_key.stderr | 9 + tests/ui/allow_attributes.fixed | 7 + tests/ui/allow_attributes.rs | 7 + tests/ui/allow_attributes.stderr | 6 +- tests/{ui-internal => ui}/author.rs | 2 - tests/{ui-internal => ui}/author.stdout | 0 tests/{ui-internal => ui}/author/blocks.rs | 0 .../{ui-internal => ui}/author/blocks.stdout | 0 tests/{ui-internal => ui}/author/call.rs | 0 tests/{ui-internal => ui}/author/call.stdout | 0 tests/{ui-internal => ui}/author/if.rs | 0 tests/{ui-internal => ui}/author/if.stdout | 0 .../{ui-internal => ui}/author/issue_3849.rs | 0 .../author/issue_3849.stdout | 0 tests/{ui-internal => ui}/author/loop.rs | 0 tests/{ui-internal => ui}/author/loop.stdout | 0 .../author/macro_in_closure.rs | 0 .../author/macro_in_closure.stdout | 0 .../author/macro_in_loop.rs | 0 .../author/macro_in_loop.stdout | 0 tests/{ui-internal => ui}/author/matches.rs | 0 .../{ui-internal => ui}/author/matches.stdout | 0 tests/{ui-internal => ui}/author/repeat.rs | 0 .../{ui-internal => ui}/author/repeat.stdout | 0 tests/{ui-internal => ui}/author/struct.rs | 0 .../{ui-internal => ui}/author/struct.stdout | 0 tests/ui/auxiliary/proc_macro_derive.rs | 50 +- tests/ui/borrow_deref_ref.fixed | 6 + tests/ui/borrow_deref_ref.rs | 6 + tests/ui/const_is_empty.rs | 14 + tests/ui/const_is_empty.stderr | 8 +- tests/ui/doc/doc-fixable.fixed | 3 +- tests/ui/doc/doc-fixable.rs | 3 +- tests/ui/doc/doc-fixable.stderr | 44 +- tests/ui/infinite_loops.rs | 20 + tests/ui/infinite_loops.stderr | 42 +- tests/ui/iter_without_into_iter.stderr | 16 +- tests/ui/large_const_arrays.fixed | 2 + tests/ui/large_const_arrays.rs | 2 + tests/ui/large_const_arrays.stderr | 22 +- tests/ui/manual_bits.fixed | 11 + tests/ui/manual_bits.rs | 11 + tests/ui/map_all_any_identity.fixed | 21 + tests/ui/map_all_any_identity.rs | 21 + tests/ui/map_all_any_identity.stderr | 26 + ...map_with_unused_argument_over_ranges.fixed | 73 +++ .../map_with_unused_argument_over_ranges.rs | 73 +++ ...ap_with_unused_argument_over_ranges.stderr | 223 ++++++++ .../missing_const_for_fn/could_be_const.fixed | 1 - .../ui/missing_const_for_fn/could_be_const.rs | 1 - .../could_be_const.stderr | 52 +- tests/ui/missing_doc.rs | 8 + tests/ui/missing_doc.stderr | 11 +- tests/ui/needless_as_bytes.fixed | 50 ++ tests/ui/needless_as_bytes.rs | 50 ++ tests/ui/needless_as_bytes.stderr | 29 + tests/ui/needless_continue.rs | 17 + tests/ui/needless_continue.stderr | 10 +- tests/ui/no_lints.rs | 3 - tests/ui/no_mangle_with_rust_abi.rs | 29 +- tests/ui/no_mangle_with_rust_abi.stderr | 27 +- tests/ui/no_mangle_with_rust_abi_2021.rs | 57 ++ tests/ui/no_mangle_with_rust_abi_2021.stderr | 83 +++ tests/ui/nonminimal_bool.stderr | 8 +- tests/ui/nonminimal_bool_methods.fixed | 30 +- tests/ui/nonminimal_bool_methods.rs | 24 + tests/ui/nonminimal_bool_methods.stderr | 86 ++- tests/ui/rename.rs | 2 +- tests/ui/rename.stderr | 14 +- tests/ui/repeat_vec_with_capacity.fixed | 1 + tests/ui/repeat_vec_with_capacity.rs | 1 + tests/ui/repeat_vec_with_capacity.stderr | 6 +- tests/ui/semicolon_outside_block.fixed | 8 + tests/ui/semicolon_outside_block.rs | 8 + tests/ui/semicolon_outside_block.stderr | 30 +- tests/ui/suspicious_map.rs | 1 + tests/ui/suspicious_map.stderr | 4 +- tests/ui/unnecessary_filter_map.rs | 13 +- tests/ui/unnecessary_filter_map.stderr | 53 +- tests/ui/unnecessary_find_map.rs | 9 +- tests/ui/unnecessary_find_map.stderr | 30 +- tests/ui/unused_io_amount.rs | 14 + triagebot.toml | 2 - util/gh-pages/index_template.html | 43 +- util/gh-pages/script.js | 22 +- util/gh-pages/style.css | 39 +- util/gh-pages/theme.js | 4 + 248 files changed, 5023 insertions(+), 900 deletions(-) rename .github/workflows/{clippy_bors.yml => clippy_mq.yml} (82%) rename .github/workflows/{clippy.yml => clippy_pr.yml} (57%) create mode 100644 clippy_lints/src/arbitrary_source_item_ordering.rs create mode 100644 clippy_lints/src/methods/map_all_any_identity.rs create mode 100644 clippy_lints/src/methods/map_with_unused_argument_over_ranges.rs create mode 100644 clippy_lints/src/methods/needless_as_bytes.rs create mode 100644 clippy_lints/src/utils/attr_collector.rs create mode 100644 clippy_lints/src/utils/internal_lints/slow_symbol_comparisons.rs create mode 100644 tests/ui-internal/slow_symbol_comparisons.fixed create mode 100644 tests/ui-internal/slow_symbol_comparisons.rs create mode 100644 tests/ui-internal/slow_symbol_comparisons.stderr create mode 100644 tests/ui-toml/arbitrary_source_item_ordering/bad_conf_1/clippy.toml create mode 100644 tests/ui-toml/arbitrary_source_item_ordering/bad_conf_2/clippy.toml create mode 100644 tests/ui-toml/arbitrary_source_item_ordering/bad_conf_3/clippy.toml create mode 100644 tests/ui-toml/arbitrary_source_item_ordering/default/clippy.toml create mode 100644 tests/ui-toml/arbitrary_source_item_ordering/default_exp/clippy.toml create mode 100644 tests/ui-toml/arbitrary_source_item_ordering/only_enum/clippy.toml create mode 100644 tests/ui-toml/arbitrary_source_item_ordering/only_impl/clippy.toml create mode 100644 tests/ui-toml/arbitrary_source_item_ordering/only_trait/clippy.toml create mode 100644 tests/ui-toml/arbitrary_source_item_ordering/ordering_good.bad_conf_1.stderr create mode 100644 tests/ui-toml/arbitrary_source_item_ordering/ordering_good.bad_conf_2.stderr create mode 100644 tests/ui-toml/arbitrary_source_item_ordering/ordering_good.bad_conf_3.stderr create mode 100644 tests/ui-toml/arbitrary_source_item_ordering/ordering_good.rs create mode 100644 tests/ui-toml/arbitrary_source_item_ordering/ordering_good_var_1.rs create mode 100644 tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.default.stderr create mode 100644 tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs create mode 100644 tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed_var_1.rs create mode 100644 tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed_var_1.var_1.stderr create mode 100644 tests/ui-toml/arbitrary_source_item_ordering/ordering_only_enum.only_enum.stderr create mode 100644 tests/ui-toml/arbitrary_source_item_ordering/ordering_only_enum.rs create mode 100644 tests/ui-toml/arbitrary_source_item_ordering/ordering_only_impl.only_impl.stderr create mode 100644 tests/ui-toml/arbitrary_source_item_ordering/ordering_only_impl.rs create mode 100644 tests/ui-toml/arbitrary_source_item_ordering/ordering_only_trait.only_trait.stderr create mode 100644 tests/ui-toml/arbitrary_source_item_ordering/ordering_only_trait.rs create mode 100644 tests/ui-toml/arbitrary_source_item_ordering/var_1/clippy.toml rename tests/{ui-internal => ui}/author.rs (72%) rename tests/{ui-internal => ui}/author.stdout (100%) rename tests/{ui-internal => ui}/author/blocks.rs (100%) rename tests/{ui-internal => ui}/author/blocks.stdout (100%) rename tests/{ui-internal => ui}/author/call.rs (100%) rename tests/{ui-internal => ui}/author/call.stdout (100%) rename tests/{ui-internal => ui}/author/if.rs (100%) rename tests/{ui-internal => ui}/author/if.stdout (100%) rename tests/{ui-internal => ui}/author/issue_3849.rs (100%) rename tests/{ui-internal => ui}/author/issue_3849.stdout (100%) rename tests/{ui-internal => ui}/author/loop.rs (100%) rename tests/{ui-internal => ui}/author/loop.stdout (100%) rename tests/{ui-internal => ui}/author/macro_in_closure.rs (100%) rename tests/{ui-internal => ui}/author/macro_in_closure.stdout (100%) rename tests/{ui-internal => ui}/author/macro_in_loop.rs (100%) rename tests/{ui-internal => ui}/author/macro_in_loop.stdout (100%) rename tests/{ui-internal => ui}/author/matches.rs (100%) rename tests/{ui-internal => ui}/author/matches.stdout (100%) rename tests/{ui-internal => ui}/author/repeat.rs (100%) rename tests/{ui-internal => ui}/author/repeat.stdout (100%) rename tests/{ui-internal => ui}/author/struct.rs (100%) rename tests/{ui-internal => ui}/author/struct.stdout (100%) create mode 100644 tests/ui/map_all_any_identity.fixed create mode 100644 tests/ui/map_all_any_identity.rs create mode 100644 tests/ui/map_all_any_identity.stderr create mode 100644 tests/ui/map_with_unused_argument_over_ranges.fixed create mode 100644 tests/ui/map_with_unused_argument_over_ranges.rs create mode 100644 tests/ui/map_with_unused_argument_over_ranges.stderr create mode 100644 tests/ui/needless_as_bytes.fixed create mode 100644 tests/ui/needless_as_bytes.rs create mode 100644 tests/ui/needless_as_bytes.stderr delete mode 100644 tests/ui/no_lints.rs create mode 100644 tests/ui/no_mangle_with_rust_abi_2021.rs create mode 100644 tests/ui/no_mangle_with_rust_abi_2021.stderr diff --git a/.github/workflows/clippy_dev.yml b/.github/workflows/clippy_dev.yml index cf0a8bde202ad..bcb3193ad6708 100644 --- a/.github/workflows/clippy_dev.yml +++ b/.github/workflows/clippy_dev.yml @@ -1,17 +1,8 @@ name: Clippy Dev Test on: - push: - branches: - - auto - - try + merge_group: pull_request: - # Only run on paths, that get checked by the clippy_dev tool - paths: - - 'CHANGELOG.md' - - 'README.md' - - '**.stderr' - - '**.rs' env: RUST_BACKTRACE: 1 @@ -47,28 +38,21 @@ jobs: cargo check git reset --hard HEAD - # These jobs doesn't actually test anything, but they're only used to tell - # bors the build completed, as there is no practical way to detect when a - # workflow is successful listening to webhooks only. - # - # ALL THE PREVIOUS JOBS NEED TO BE ADDED TO THE `needs` SECTION OF THIS JOB! - - end-success: - name: bors dev test finished - if: github.event.pusher.name == 'bors' && success() - runs-on: ubuntu-latest - needs: [clippy_dev] - - steps: - - name: Mark the job as successful - run: exit 0 - - end-failure: - name: bors dev test finished - if: github.event.pusher.name == 'bors' && (failure() || cancelled()) + conclusion_dev: + needs: [ clippy_dev ] + # We need to ensure this job does *not* get skipped if its dependencies fail, + # because a skipped job is considered a success by GitHub. So we have to + # overwrite `if:`. We use `!cancelled()` to ensure the job does still not get run + # when the workflow is canceled manually. + # + # ALL THE PREVIOUS JOBS NEED TO BE ADDED TO THE `needs` SECTION OF THIS JOB! + if: ${{ !cancelled() }} runs-on: ubuntu-latest - needs: [clippy_dev] - steps: - - name: Mark the job as a failure - run: exit 1 + # Manually check the status of all dependencies. `if: failure()` does not work. + - name: Conclusion + run: | + # Print the dependent jobs to see them in the CI log + jq -C <<< '${{ toJson(needs) }}' + # Check if all jobs that we depend on (in the needs array) were successful. + jq --exit-status 'all(.result == "success")' <<< '${{ toJson(needs) }}' diff --git a/.github/workflows/clippy_bors.yml b/.github/workflows/clippy_mq.yml similarity index 82% rename from .github/workflows/clippy_bors.yml rename to .github/workflows/clippy_mq.yml index 026771e6fcf42..496220480508b 100644 --- a/.github/workflows/clippy_bors.yml +++ b/.github/workflows/clippy_mq.yml @@ -1,10 +1,7 @@ -name: Clippy Test (bors) +name: Clippy Test (merge queue) on: - push: - branches: - - auto - - try + merge_group: env: RUST_BACKTRACE: 1 @@ -13,11 +10,6 @@ env: CARGO_INCREMENTAL: 0 RUSTFLAGS: -D warnings -concurrency: - # For a given workflow, if we push to the same branch, cancel all previous builds on that branch. - group: "${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}" - cancel-in-progress: true - defaults: run: shell: bash @@ -218,28 +210,21 @@ jobs: env: INTEGRATION: ${{ matrix.integration }} - # These jobs doesn't actually test anything, but they're only used to tell - # bors the build completed, as there is no practical way to detect when a - # workflow is successful listening to webhooks only. - # - # ALL THE PREVIOUS JOBS NEED TO BE ADDED TO THE `needs` SECTION OF THIS JOB! - - end-success: - name: bors test finished - if: github.event.pusher.name == 'bors' && success() + conclusion: + needs: [ changelog, base, metadata_collection, integration_build, integration ] + # We need to ensure this job does *not* get skipped if its dependencies fail, + # because a skipped job is considered a success by GitHub. So we have to + # overwrite `if:`. We use `!cancelled()` to ensure the job does still not get run + # when the workflow is canceled manually. + # + # ALL THE PREVIOUS JOBS NEED TO BE ADDED TO THE `needs` SECTION OF THIS JOB! + if: ${{ !cancelled() }} runs-on: ubuntu-latest - needs: [changelog, base, metadata_collection, integration_build, integration] - - steps: - - name: Mark the job as successful - run: exit 0 - - end-failure: - name: bors test finished - if: github.event.pusher.name == 'bors' && (failure() || cancelled()) - runs-on: ubuntu-latest - needs: [changelog, base, metadata_collection, integration_build, integration] - steps: - - name: Mark the job as a failure - run: exit 1 + # Manually check the status of all dependencies. `if: failure()` does not work. + - name: Conclusion + run: | + # Print the dependent jobs to see them in the CI log + jq -C <<< '${{ toJson(needs) }}' + # Check if all jobs that we depend on (in the needs array) were successful. + jq --exit-status 'all(.result == "success")' <<< '${{ toJson(needs) }}' diff --git a/.github/workflows/clippy.yml b/.github/workflows/clippy_pr.yml similarity index 57% rename from .github/workflows/clippy.yml rename to .github/workflows/clippy_pr.yml index 0a0538490cce9..2e5b5bd41dfbb 100644 --- a/.github/workflows/clippy.yml +++ b/.github/workflows/clippy_pr.yml @@ -1,24 +1,7 @@ name: Clippy Test on: - push: - # Ignore bors branches, since they are covered by `clippy_bors.yml` - branches-ignore: - - auto - - try - # Don't run Clippy tests, when only text files were modified - paths-ignore: - - 'COPYRIGHT' - - 'LICENSE-*' - - '**.md' - - '**.txt' pull_request: - # Don't run Clippy tests, when only text files were modified - paths-ignore: - - 'COPYRIGHT' - - 'LICENSE-*' - - '**.md' - - '**.txt' env: RUST_BACKTRACE: 1 @@ -35,7 +18,7 @@ concurrency: jobs: base: - # NOTE: If you modify this job, make sure you copy the changes to clippy_bors.yml + # NOTE: If you modify this job, make sure you copy the changes to clippy_mq.yml runs-on: ubuntu-latest steps: @@ -73,3 +56,24 @@ jobs: run: .github/driver.sh env: OS: ${{ runner.os }} + + # We need to have the "conclusion" job also on PR CI, to make it possible + # to add PRs to a merge queue. + conclusion: + needs: [ base ] + # We need to ensure this job does *not* get skipped if its dependencies fail, + # because a skipped job is considered a success by GitHub. So we have to + # overwrite `if:`. We use `!cancelled()` to ensure the job does still not get run + # when the workflow is canceled manually. + # + # ALL THE PREVIOUS JOBS NEED TO BE ADDED TO THE `needs` SECTION OF THIS JOB! + if: ${{ !cancelled() }} + runs-on: ubuntu-latest + steps: + # Manually check the status of all dependencies. `if: failure()` does not work. + - name: Conclusion + run: | + # Print the dependent jobs to see them in the CI log + jq -C <<< '${{ toJson(needs) }}' + # Check if all jobs that we depend on (in the needs array) were successful. + jq --exit-status 'all(.result == "success")' <<< '${{ toJson(needs) }}' diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 94f494b65c47c..32dc251c836f6 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -52,7 +52,7 @@ jobs: run: cargo generate-lockfile - name: Cache - uses: Swatinem/rust-cache@v2.7.0 + uses: Swatinem/rust-cache@v2 with: save-if: ${{ github.ref == 'refs/heads/master' }} diff --git a/.github/workflows/remark.yml b/.github/workflows/remark.yml index a1b011dc32d84..0d402fe706410 100644 --- a/.github/workflows/remark.yml +++ b/.github/workflows/remark.yml @@ -1,13 +1,8 @@ name: Remark on: - push: - branches: - - auto - - try + merge_group: pull_request: - paths: - - '**.md' jobs: remark: @@ -45,28 +40,21 @@ jobs: - name: Build mdbook run: mdbook build book - # These jobs doesn't actually test anything, but they're only used to tell - # bors the build completed, as there is no practical way to detect when a - # workflow is successful listening to webhooks only. - # - # ALL THE PREVIOUS JOBS NEED TO BE ADDED TO THE `needs` SECTION OF THIS JOB! - - end-success: - name: bors remark test finished - if: github.event.pusher.name == 'bors' && success() - runs-on: ubuntu-latest - needs: [remark] - - steps: - - name: Mark the job as successful - run: exit 0 - - end-failure: - name: bors remark test finished - if: github.event.pusher.name == 'bors' && (failure() || cancelled()) + conclusion_remark: + needs: [ remark ] + # We need to ensure this job does *not* get skipped if its dependencies fail, + # because a skipped job is considered a success by GitHub. So we have to + # overwrite `if:`. We use `!cancelled()` to ensure the job does still not get run + # when the workflow is canceled manually. + # + # ALL THE PREVIOUS JOBS NEED TO BE ADDED TO THE `needs` SECTION OF THIS JOB! + if: ${{ !cancelled() }} runs-on: ubuntu-latest - needs: [remark] - steps: - - name: Mark the job as a failure - run: exit 1 + # Manually check the status of all dependencies. `if: failure()` does not work. + - name: Conclusion + run: | + # Print the dependent jobs to see them in the CI log + jq -C <<< '${{ toJson(needs) }}' + # Check if all jobs that we depend on (in the needs array) were successful. + jq --exit-status 'all(.result == "success")' <<< '${{ toJson(needs) }}' diff --git a/CHANGELOG.md b/CHANGELOG.md index 4bdbc91db9397..161fa630ed470 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5331,6 +5331,7 @@ Released 2018-09-13 [`almost_complete_range`]: https://rust-lang.github.io/rust-clippy/master/index.html#almost_complete_range [`almost_swapped`]: https://rust-lang.github.io/rust-clippy/master/index.html#almost_swapped [`approx_constant`]: https://rust-lang.github.io/rust-clippy/master/index.html#approx_constant +[`arbitrary_source_item_ordering`]: https://rust-lang.github.io/rust-clippy/master/index.html#arbitrary_source_item_ordering [`arc_with_non_send_sync`]: https://rust-lang.github.io/rust-clippy/master/index.html#arc_with_non_send_sync [`arithmetic_side_effects`]: https://rust-lang.github.io/rust-clippy/master/index.html#arithmetic_side_effects [`as_conversions`]: https://rust-lang.github.io/rust-clippy/master/index.html#as_conversions @@ -5689,6 +5690,7 @@ Released 2018-09-13 [`manual_unwrap_or_default`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_unwrap_or_default [`manual_while_let_some`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_while_let_some [`many_single_char_names`]: https://rust-lang.github.io/rust-clippy/master/index.html#many_single_char_names +[`map_all_any_identity`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_all_any_identity [`map_clone`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_clone [`map_collect_result_unit`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_collect_result_unit [`map_entry`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_entry @@ -5696,6 +5698,7 @@ Released 2018-09-13 [`map_flatten`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_flatten [`map_identity`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_identity [`map_unwrap_or`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_unwrap_or +[`map_with_unused_argument_over_ranges`]: https://rust-lang.github.io/rust-clippy/master/index.html#map_with_unused_argument_over_ranges [`match_as_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_as_ref [`match_bool`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_bool [`match_like_matches_macro`]: https://rust-lang.github.io/rust-clippy/master/index.html#match_like_matches_macro @@ -5761,6 +5764,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_as_bytes`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_as_bytes [`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_bool_assign`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_bool_assign @@ -6205,12 +6209,14 @@ Released 2018-09-13 [`max-trait-bounds`]: https://doc.rust-lang.org/clippy/lint_configuration.html#max-trait-bounds [`min-ident-chars-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#min-ident-chars-threshold [`missing-docs-in-crate-items`]: https://doc.rust-lang.org/clippy/lint_configuration.html#missing-docs-in-crate-items +[`module-item-order-groupings`]: https://doc.rust-lang.org/clippy/lint_configuration.html#module-item-order-groupings [`msrv`]: https://doc.rust-lang.org/clippy/lint_configuration.html#msrv [`pass-by-value-size-limit`]: https://doc.rust-lang.org/clippy/lint_configuration.html#pass-by-value-size-limit [`pub-underscore-fields-behavior`]: https://doc.rust-lang.org/clippy/lint_configuration.html#pub-underscore-fields-behavior [`semicolon-inside-block-ignore-singleline`]: https://doc.rust-lang.org/clippy/lint_configuration.html#semicolon-inside-block-ignore-singleline [`semicolon-outside-block-ignore-multiline`]: https://doc.rust-lang.org/clippy/lint_configuration.html#semicolon-outside-block-ignore-multiline [`single-char-binding-names-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#single-char-binding-names-threshold +[`source-item-ordering`]: https://doc.rust-lang.org/clippy/lint_configuration.html#source-item-ordering [`stack-size-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#stack-size-threshold [`standard-macro-braces`]: https://doc.rust-lang.org/clippy/lint_configuration.html#standard-macro-braces [`struct-field-name-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#struct-field-name-threshold @@ -6218,6 +6224,7 @@ Released 2018-09-13 [`too-large-for-stack`]: https://doc.rust-lang.org/clippy/lint_configuration.html#too-large-for-stack [`too-many-arguments-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#too-many-arguments-threshold [`too-many-lines-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#too-many-lines-threshold +[`trait-assoc-item-kinds-order`]: https://doc.rust-lang.org/clippy/lint_configuration.html#trait-assoc-item-kinds-order [`trivial-copy-size-limit`]: https://doc.rust-lang.org/clippy/lint_configuration.html#trivial-copy-size-limit [`type-complexity-threshold`]: https://doc.rust-lang.org/clippy/lint_configuration.html#type-complexity-threshold [`unnecessary-box-size`]: https://doc.rust-lang.org/clippy/lint_configuration.html#unnecessary-box-size diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b1a59238c8266..1f6c918fc6cc6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -21,7 +21,6 @@ All contributors are expected to follow the [Rust Code of Conduct]. - [Rust Analyzer](#rust-analyzer) - [How Clippy works](#how-clippy-works) - [Issue and PR triage](#issue-and-pr-triage) - - [Bors and Homu](#bors-and-homu) - [Contributions](#contributions) - [License](#license) @@ -213,16 +212,6 @@ We have prioritization labels and a sync-blocker label, which are described belo Or rather: before the sync this should be addressed, e.g. by removing a lint again, so it doesn't hit beta/stable. -## Bors and Homu - -We use a bot powered by [Homu][homu] to help automate testing and landing of pull -requests in Clippy. The bot's username is @bors. - -You can find the Clippy bors queue [here][homu_queue]. - -If you have @bors permissions, you can find an overview of the available -commands [here][homu_instructions]. - [triage]: https://forge.rust-lang.org/release/triage-procedure.html [l-crash]: https://github.com/rust-lang/rust-clippy/labels/L-crash [l-bug]: https://github.com/rust-lang/rust-clippy/labels/L-bug @@ -230,9 +219,6 @@ commands [here][homu_instructions]. [p-medium]: https://github.com/rust-lang/rust-clippy/labels/P-medium [p-high]: https://github.com/rust-lang/rust-clippy/labels/P-high [l-sync-blocker]: https://github.com/rust-lang/rust-clippy/labels/L-sync-blocker -[homu]: https://github.com/rust-lang/homu -[homu_instructions]: https://bors.rust-lang.org/ -[homu_queue]: https://bors.rust-lang.org/queue/clippy ## Contributions @@ -244,7 +230,7 @@ All PRs should include a `changelog` entry with a short comment explaining the c "what do you believe is important from an outsider's perspective?" Often, PRs are only related to a single property of a lint, and then it's good to mention that one. Otherwise, it's better to include too much detail than too little. -Clippy's [changelog] is created from these comments. Every release, someone gets all commits from bors with a +Clippy's [changelog] is created from these comments. Every release, someone gets all merge commits with a `changelog: XYZ` entry and combines them into the changelog. This is a manual process. Examples: diff --git a/Cargo.toml b/Cargo.toml index 1f7784fc489f6..50a2afbfc0720 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,7 +39,7 @@ toml = "0.7.3" walkdir = "2.3" filetime = "0.2.9" itertools = "0.12" -pulldown-cmark = "0.11" +pulldown-cmark = { version = "0.11", default-features = false, features = ["html"] } rinja = { version = "0.3", default-features = false, features = ["config"] } # UI test dependencies diff --git a/README.md b/README.md index ec76a6dfb08e9..cb3a22d4288fa 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,10 @@ # Clippy -[![Clippy Test](https://github.com/rust-lang/rust-clippy/workflows/Clippy%20Test%20(bors)/badge.svg?branch=auto&event=push)](https://github.com/rust-lang/rust-clippy/actions?query=workflow%3A%22Clippy+Test+(bors)%22+event%3Apush+branch%3Aauto) [![License: MIT OR Apache-2.0](https://img.shields.io/crates/l/clippy.svg)](#license) A collection of lints to catch common mistakes and improve your [Rust](https://github.com/rust-lang/rust) code. -[There are over 700 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html) +[There are over 750 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html) Lints are divided into categories, each with a default [lint level](https://doc.rust-lang.org/rustc/lints/levels.html). You can choose how much Clippy is supposed to ~~annoy~~ help you by changing the lint level by category. diff --git a/book/src/README.md b/book/src/README.md index 7bdfb97c3acf6..5d2c3972b060a 100644 --- a/book/src/README.md +++ b/book/src/README.md @@ -1,12 +1,11 @@ # Clippy -[![Clippy Test](https://github.com/rust-lang/rust-clippy/workflows/Clippy%20Test%20(bors)/badge.svg?branch=auto&event=push)](https://github.com/rust-lang/rust-clippy/actions?query=workflow%3A%22Clippy+Test+(bors)%22+event%3Apush+branch%3Aauto) [![License: MIT OR Apache-2.0](https://img.shields.io/crates/l/clippy.svg)](https://github.com/rust-lang/rust-clippy#license) A collection of lints to catch common mistakes and improve your [Rust](https://github.com/rust-lang/rust) code. -[There are over 700 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html) +[There are over 750 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html) Lints are divided into categories, each with a default [lint level](https://doc.rust-lang.org/rustc/lints/levels.html). You can choose how diff --git a/book/src/development/README.md b/book/src/development/README.md index 8f09f66f59586..b33cdc00ead67 100644 --- a/book/src/development/README.md +++ b/book/src/development/README.md @@ -53,7 +53,6 @@ book](../lints.md). > - IDE setup > - High level overview on how Clippy works > - Triage procedure -> - Bors and Homu [ast]: https://rustc-dev-guide.rust-lang.org/syntax-intro.html [hir]: https://rustc-dev-guide.rust-lang.org/hir.html diff --git a/book/src/development/common_tools_writing_lints.md b/book/src/development/common_tools_writing_lints.md index 09171d86a2097..77910917963ee 100644 --- a/book/src/development/common_tools_writing_lints.md +++ b/book/src/development/common_tools_writing_lints.md @@ -68,7 +68,7 @@ impl<'tcx> LateLintPass<'tcx> for MyStructLint { // Check our expr is calling a method if let hir::ExprKind::MethodCall(path, _, _self_arg, ..) = &expr.kind // Check the name of this method is `some_method` - && path.ident.name == sym!(some_method) + && path.ident.name.as_str() == "some_method" // Optionally, check the type of the self argument. // - See "Checking for a specific type" { @@ -167,7 +167,7 @@ impl<'tcx> LateLintPass<'tcx> for MyTypeImpl { // Check if item is a method/function if let ImplItemKind::Fn(ref signature, _) = impl_item.kind // Check the method is named `some_method` - && impl_item.ident.name == sym!(some_method) + && impl_item.ident.name.as_str() == "some_method" // We can also check it has a parameter `self` && signature.decl.implicit_self.has_implicit_self() // We can go further and even check if its return type is `String` diff --git a/book/src/development/method_checking.md b/book/src/development/method_checking.md index 56d1be37519e9..9c5d4b516db2c 100644 --- a/book/src/development/method_checking.md +++ b/book/src/development/method_checking.md @@ -3,8 +3,8 @@ In some scenarios we might want to check for methods when developing a lint. There are two kinds of questions that we might be curious about: -- Invocation: Does an expression call a specific method? -- Definition: Does an `impl` define a method? +- Invocation: Does an expression call a specific method? +- Definition: Does an `impl` define a method? ## Checking if an `expr` is calling a specific method @@ -23,7 +23,7 @@ impl<'tcx> LateLintPass<'tcx> for OurFancyMethodLint { // Check our expr is calling a method with pattern matching if let hir::ExprKind::MethodCall(path, _, [self_arg, ..]) = &expr.kind // Check if the name of this method is `our_fancy_method` - && path.ident.name == sym!(our_fancy_method) + && path.ident.name.as_str() == "our_fancy_method" // We can check the type of the self argument whenever necessary. // (It's necessary if we want to check that method is specifically belonging to a specific trait, // for example, a `map` method could belong to user-defined trait instead of to `Iterator`) @@ -41,10 +41,6 @@ information on the pattern matching. As mentioned in [Define Lints](defining_lints.md#lint-types), the `methods` lint type is full of pattern matching with `MethodCall` in case the reader wishes to explore more. -Additionally, we use the [`clippy_utils::sym!`][sym] macro to conveniently -convert an input `our_fancy_method` into a `Symbol` and compare that symbol to -the [`Ident`]'s name in the [`PathSegment`] in the [`MethodCall`]. - ## Checking if a `impl` block implements a method While sometimes we want to check whether a method is being called or not, other @@ -71,7 +67,7 @@ impl<'tcx> LateLintPass<'tcx> for MyTypeImpl { // Check if item is a method/function if let ImplItemKind::Fn(ref signature, _) = impl_item.kind // Check the method is named `our_fancy_method` - && impl_item.ident.name == sym!(our_fancy_method) + && impl_item.ident.name.as_str() == "our_fancy_method" // We can also check it has a parameter `self` && signature.decl.implicit_self.has_implicit_self() // We can go even further and even check if its return type is `String` @@ -85,9 +81,6 @@ impl<'tcx> LateLintPass<'tcx> for MyTypeImpl { [`check_impl_item`]: https://doc.rust-lang.org/stable/nightly-rustc/rustc_lint/trait.LateLintPass.html#method.check_impl_item [`ExprKind`]: https://doc.rust-lang.org/beta/nightly-rustc/rustc_hir/hir/enum.ExprKind.html -[`Ident`]: https://doc.rust-lang.org/beta/nightly-rustc/rustc_span/symbol/struct.Ident.html [`ImplItem`]: https://doc.rust-lang.org/stable/nightly-rustc/rustc_hir/hir/struct.ImplItem.html [`LateLintPass`]: https://doc.rust-lang.org/stable/nightly-rustc/rustc_lint/trait.LateLintPass.html [`MethodCall`]: https://doc.rust-lang.org/beta/nightly-rustc/rustc_hir/hir/enum.ExprKind.html#variant.MethodCall -[`PathSegment`]: https://doc.rust-lang.org/beta/nightly-rustc/rustc_hir/hir/struct.PathSegment.html -[sym]: https://doc.rust-lang.org/stable/nightly-rustc/clippy_utils/macro.sym.html diff --git a/book/src/lint_configuration.md b/book/src/lint_configuration.md index 43b551ae21617..670b5cbef8233 100644 --- a/book/src/lint_configuration.md +++ b/book/src/lint_configuration.md @@ -456,7 +456,7 @@ default configuration of Clippy. By default, any configuration will replace the * `doc-valid-idents = ["ClipPy"]` would replace the default list with `["ClipPy"]`. * `doc-valid-idents = ["ClipPy", ".."]` would append `ClipPy` to the default list. -**Default Value:** `["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "AccessKit", "CoreFoundation", "CoreGraphics", "CoreText", "DevOps", "Direct2D", "Direct3D", "DirectWrite", "DirectX", "ECMAScript", "GPLv2", "GPLv3", "GitHub", "GitLab", "IPv4", "IPv6", "ClojureScript", "CoffeeScript", "JavaScript", "PostScript", "PureScript", "TypeScript", "WebAssembly", "NaN", "NaNs", "OAuth", "GraphQL", "OCaml", "OpenAL", "OpenDNS", "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenTelemetry", "OpenType", "WebGL", "WebGL2", "WebGPU", "WebRTC", "WebSocket", "WebTransport", "WebP", "OpenExr", "YCbCr", "sRGB", "TensorFlow", "TrueType", "iOS", "macOS", "FreeBSD", "NetBSD", "OpenBSD", "TeX", "LaTeX", "BibTeX", "BibLaTeX", "MinGW", "CamelCase"]` +**Default Value:** `["KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "MHz", "GHz", "THz", "AccessKit", "CoAP", "CoreFoundation", "CoreGraphics", "CoreText", "DevOps", "Direct2D", "Direct3D", "DirectWrite", "DirectX", "ECMAScript", "GPLv2", "GPLv3", "GitHub", "GitLab", "IPv4", "IPv6", "ClojureScript", "CoffeeScript", "JavaScript", "PostScript", "PureScript", "TypeScript", "WebAssembly", "NaN", "NaNs", "OAuth", "GraphQL", "OCaml", "OpenAL", "OpenDNS", "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenTelemetry", "OpenType", "WebGL", "WebGL2", "WebGPU", "WebRTC", "WebSocket", "WebTransport", "WebP", "OpenExr", "YCbCr", "sRGB", "TensorFlow", "TrueType", "iOS", "macOS", "FreeBSD", "NetBSD", "OpenBSD", "TeX", "LaTeX", "BibTeX", "BibLaTeX", "MinGW", "CamelCase"]` --- **Affected lints:** @@ -666,6 +666,16 @@ crate. For example, `pub(crate)` items. * [`missing_docs_in_private_items`](https://rust-lang.github.io/rust-clippy/master/index.html#missing_docs_in_private_items) +## `module-item-order-groupings` +The named groupings of different source item kinds within modules. + +**Default Value:** `[["modules", ["extern_crate", "mod", "foreign_mod"]], ["use", ["use"]], ["macros", ["macro"]], ["global_asm", ["global_asm"]], ["UPPER_SNAKE_CASE", ["static", "const"]], ["PascalCase", ["ty_alias", "enum", "struct", "union", "trait", "trait_alias", "impl"]], ["lower_snake_case", ["fn"]]]` + +--- +**Affected lints:** +* [`arbitrary_source_item_ordering`](https://rust-lang.github.io/rust-clippy/master/index.html#arbitrary_source_item_ordering) + + ## `msrv` The minimum rust version that the project supports. Defaults to the `rust-version` field in `Cargo.toml` @@ -710,6 +720,7 @@ The minimum rust version that the project supports. Defaults to the `rust-versio * [`manual_try_fold`](https://rust-lang.github.io/rust-clippy/master/index.html#manual_try_fold) * [`map_clone`](https://rust-lang.github.io/rust-clippy/master/index.html#map_clone) * [`map_unwrap_or`](https://rust-lang.github.io/rust-clippy/master/index.html#map_unwrap_or) +* [`map_with_unused_argument_over_ranges`](https://rust-lang.github.io/rust-clippy/master/index.html#map_with_unused_argument_over_ranges) * [`match_like_matches_macro`](https://rust-lang.github.io/rust-clippy/master/index.html#match_like_matches_macro) * [`mem_replace_with_default`](https://rust-lang.github.io/rust-clippy/master/index.html#mem_replace_with_default) * [`missing_const_for_fn`](https://rust-lang.github.io/rust-clippy/master/index.html#missing_const_for_fn) @@ -783,6 +794,16 @@ The maximum number of single char bindings a scope may have * [`many_single_char_names`](https://rust-lang.github.io/rust-clippy/master/index.html#many_single_char_names) +## `source-item-ordering` +Which kind of elements should be ordered internally, possible values being `enum`, `impl`, `module`, `struct`, `trait`. + +**Default Value:** `["enum", "impl", "module", "struct", "trait"]` + +--- +**Affected lints:** +* [`arbitrary_source_item_ordering`](https://rust-lang.github.io/rust-clippy/master/index.html#arbitrary_source_item_ordering) + + ## `stack-size-threshold` The maximum allowed stack size for functions in bytes @@ -862,6 +883,16 @@ The maximum number of lines a function or method can have * [`too_many_lines`](https://rust-lang.github.io/rust-clippy/master/index.html#too_many_lines) +## `trait-assoc-item-kinds-order` +The order of associated items in traits. + +**Default Value:** `["const", "type", "fn"]` + +--- +**Affected lints:** +* [`arbitrary_source_item_ordering`](https://rust-lang.github.io/rust-clippy/master/index.html#arbitrary_source_item_ordering) + + ## `trivial-copy-size-limit` The maximum size (in bytes) to consider a `Copy` type for passing by value instead of by reference. By default there is no limit diff --git a/clippy_config/src/conf.rs b/clippy_config/src/conf.rs index 4757c0b1339a7..600d5b6e2c8d9 100644 --- a/clippy_config/src/conf.rs +++ b/clippy_config/src/conf.rs @@ -1,6 +1,10 @@ use crate::ClippyConfiguration; use crate::msrvs::Msrv; -use crate::types::{DisallowedPath, MacroMatcher, MatchLintBehaviour, PubUnderscoreFieldsBehaviour, Rename}; +use crate::types::{ + DisallowedPath, MacroMatcher, MatchLintBehaviour, PubUnderscoreFieldsBehaviour, Rename, SourceItemOrdering, + SourceItemOrderingCategory, SourceItemOrderingModuleItemGroupings, SourceItemOrderingModuleItemKind, + SourceItemOrderingTraitAssocItemKind, SourceItemOrderingTraitAssocItemKinds, +}; use rustc_errors::Applicability; use rustc_session::Session; use rustc_span::edit_distance::edit_distance; @@ -17,8 +21,9 @@ use std::{cmp, env, fmt, fs, io}; #[rustfmt::skip] const DEFAULT_DOC_VALID_IDENTS: &[&str] = &[ "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", + "MHz", "GHz", "THz", "AccessKit", - "CoreFoundation", "CoreGraphics", "CoreText", + "CoAP", "CoreFoundation", "CoreGraphics", "CoreText", "DevOps", "Direct2D", "Direct3D", "DirectWrite", "DirectX", "ECMAScript", @@ -46,6 +51,29 @@ const DEFAULT_ALLOWED_IDENTS_BELOW_MIN_CHARS: &[&str] = &["i", "j", "x", "y", "z const DEFAULT_ALLOWED_PREFIXES: &[&str] = &["to", "as", "into", "from", "try_into", "try_from"]; const DEFAULT_ALLOWED_TRAITS_WITH_RENAMED_PARAMS: &[&str] = &["core::convert::From", "core::convert::TryFrom", "core::str::FromStr"]; +const DEFAULT_MODULE_ITEM_ORDERING_GROUPS: &[(&str, &[SourceItemOrderingModuleItemKind])] = { + #[allow(clippy::enum_glob_use)] // Very local glob use for legibility. + use SourceItemOrderingModuleItemKind::*; + &[ + ("modules", &[ExternCrate, Mod, ForeignMod]), + ("use", &[Use]), + ("macros", &[Macro]), + ("global_asm", &[GlobalAsm]), + ("UPPER_SNAKE_CASE", &[Static, Const]), + ("PascalCase", &[TyAlias, Enum, Struct, Union, Trait, TraitAlias, Impl]), + ("lower_snake_case", &[Fn]), + ] +}; +const DEFAULT_TRAIT_ASSOC_ITEM_KINDS_ORDER: &[SourceItemOrderingTraitAssocItemKind] = { + #[allow(clippy::enum_glob_use)] // Very local glob use for legibility. + use SourceItemOrderingTraitAssocItemKind::*; + &[Const, Type, Fn] +}; +const DEFAULT_SOURCE_ITEM_ORDERING: &[SourceItemOrderingCategory] = { + #[allow(clippy::enum_glob_use)] // Very local glob use for legibility. + use SourceItemOrderingCategory::*; + &[Enum, Impl, Module, Struct, Trait] +}; /// Conf with parse errors #[derive(Default)] @@ -102,7 +130,9 @@ pub fn sanitize_explanation(raw_docs: &str) -> String { // Remove tags and hidden code: let mut explanation = String::with_capacity(128); let mut in_code = false; - for line in raw_docs.lines().map(str::trim) { + for line in raw_docs.lines() { + let line = line.strip_prefix(' ').unwrap_or(line); + if let Some(lang) = line.strip_prefix("```") { let tag = lang.split_once(',').map_or(lang, |(left, _)| left); if !in_code && matches!(tag, "" | "rust" | "ignore" | "should_panic" | "no_run" | "compile_fail") { @@ -530,6 +560,9 @@ define_Conf! { /// crate. For example, `pub(crate)` items. #[lints(missing_docs_in_private_items)] missing_docs_in_crate_items: bool = false, + /// The named groupings of different source item kinds within modules. + #[lints(arbitrary_source_item_ordering)] + module_item_order_groupings: SourceItemOrderingModuleItemGroupings = DEFAULT_MODULE_ITEM_ORDERING_GROUPS.into(), /// The minimum rust version that the project supports. Defaults to the `rust-version` field in `Cargo.toml` #[default_text = "current version"] #[lints( @@ -570,6 +603,7 @@ define_Conf! { manual_try_fold, map_clone, map_unwrap_or, + map_with_unused_argument_over_ranges, match_like_matches_macro, mem_replace_with_default, missing_const_for_fn, @@ -608,6 +642,9 @@ define_Conf! { /// The maximum number of single char bindings a scope may have #[lints(many_single_char_names)] single_char_binding_names_threshold: u64 = 4, + /// Which kind of elements should be ordered internally, possible values being `enum`, `impl`, `module`, `struct`, `trait`. + #[lints(arbitrary_source_item_ordering)] + source_item_ordering: SourceItemOrdering = DEFAULT_SOURCE_ITEM_ORDERING.into(), /// The maximum allowed stack size for functions in bytes #[lints(large_stack_frames)] stack_size_threshold: u64 = 512_000, @@ -637,6 +674,9 @@ define_Conf! { /// The maximum number of lines a function or method can have #[lints(too_many_lines)] too_many_lines_threshold: u64 = 100, + /// The order of associated items in traits. + #[lints(arbitrary_source_item_ordering)] + trait_assoc_item_kinds_order: SourceItemOrderingTraitAssocItemKinds = DEFAULT_TRAIT_ASSOC_ITEM_KINDS_ORDER.into(), /// The maximum size (in bytes) to consider a `Copy` type for passing by value instead of by /// reference. By default there is no limit #[default_text = "target_pointer_width * 2"] diff --git a/clippy_config/src/lib.rs b/clippy_config/src/lib.rs index 42651521f8d7a..1c3f32c2514ca 100644 --- a/clippy_config/src/lib.rs +++ b/clippy_config/src/lib.rs @@ -20,6 +20,7 @@ extern crate rustc_driver; extern crate rustc_errors; extern crate rustc_session; extern crate rustc_span; +extern crate smallvec; mod conf; mod metadata; diff --git a/clippy_config/src/msrvs.rs b/clippy_config/src/msrvs.rs index 2f4da4cba3df3..764ca8fb50ab4 100644 --- a/clippy_config/src/msrvs.rs +++ b/clippy_config/src/msrvs.rs @@ -3,6 +3,7 @@ use rustc_attr::parse_version; use rustc_session::{RustcVersion, Session}; use rustc_span::{Symbol, sym}; use serde::Deserialize; +use smallvec::{SmallVec, smallvec}; use std::fmt; macro_rules! msrv_aliases { @@ -18,7 +19,7 @@ macro_rules! msrv_aliases { // names may refer to stabilized feature flags or library items msrv_aliases! { 1,83,0 { CONST_EXTERN_FN, CONST_FLOAT_BITS_CONV, CONST_FLOAT_CLASSIFY } - 1,82,0 { IS_NONE_OR } + 1,82,0 { IS_NONE_OR, REPEAT_N } 1,81,0 { LINT_REASONS_STABILIZATION } 1,80,0 { BOX_INTO_ITER} 1,77,0 { C_STR_LITERALS } @@ -54,7 +55,7 @@ msrv_aliases! { 1,33,0 { UNDERSCORE_IMPORTS } 1,30,0 { ITERATOR_FIND_MAP, TOOL_ATTRIBUTES } 1,29,0 { ITER_FLATTEN } - 1,28,0 { FROM_BOOL } + 1,28,0 { FROM_BOOL, REPEAT_WITH } 1,27,0 { ITERATOR_TRY_FOLD } 1,26,0 { RANGE_INCLUSIVE, STRING_RETAIN } 1,24,0 { IS_ASCII_DIGIT } @@ -67,7 +68,7 @@ msrv_aliases! { /// Tracks the current MSRV from `clippy.toml`, `Cargo.toml` or set via `#[clippy::msrv]` #[derive(Debug, Clone)] pub struct Msrv { - stack: Vec, + stack: SmallVec<[RustcVersion; 2]>, } impl fmt::Display for Msrv { @@ -87,14 +88,14 @@ impl<'de> Deserialize<'de> for Msrv { { let v = String::deserialize(deserializer)?; parse_version(Symbol::intern(&v)) - .map(|v| Msrv { stack: vec![v] }) + .map(|v| Msrv { stack: smallvec![v] }) .ok_or_else(|| serde::de::Error::custom("not a valid Rust version")) } } impl Msrv { pub fn empty() -> Msrv { - Msrv { stack: Vec::new() } + Msrv { stack: SmallVec::new() } } pub fn read_cargo(&mut self, sess: &Session) { @@ -103,7 +104,7 @@ impl Msrv { .and_then(|v| parse_version(Symbol::intern(&v))); match (self.current(), cargo_msrv) { - (None, Some(cargo_msrv)) => self.stack = vec![cargo_msrv], + (None, Some(cargo_msrv)) => self.stack = smallvec![cargo_msrv], (Some(clippy_msrv), Some(cargo_msrv)) => { if clippy_msrv != cargo_msrv { sess.dcx().warn(format!( diff --git a/clippy_config/src/types.rs b/clippy_config/src/types.rs index bab63911182d1..fe5764241487e 100644 --- a/clippy_config/src/types.rs +++ b/clippy_config/src/types.rs @@ -1,5 +1,6 @@ use serde::de::{self, Deserializer, Visitor}; use serde::{Deserialize, Serialize, ser}; +use std::collections::HashMap; use std::fmt; #[derive(Debug, Deserialize)] @@ -102,6 +103,306 @@ impl<'de> Deserialize<'de> for MacroMatcher { } } +/// Represents the item categories that can be ordered by the source ordering lint. +#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] +#[serde(rename_all = "snake_case")] +pub enum SourceItemOrderingCategory { + Enum, + Impl, + Module, + Struct, + Trait, +} + +/// Represents which item categories are enabled for ordering. +/// +/// The [`Deserialize`] implementation checks that there are no duplicates in +/// the user configuration. +pub struct SourceItemOrdering(Vec); + +impl SourceItemOrdering { + pub fn contains(&self, category: &SourceItemOrderingCategory) -> bool { + self.0.contains(category) + } +} + +impl From for SourceItemOrdering +where + T: Into>, +{ + fn from(value: T) -> Self { + Self(value.into()) + } +} + +impl core::fmt::Debug for SourceItemOrdering { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +impl<'de> Deserialize<'de> for SourceItemOrdering { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let items = Vec::::deserialize(deserializer)?; + let mut items_set = std::collections::HashSet::new(); + + for item in &items { + if items_set.contains(item) { + return Err(de::Error::custom(format!( + "The category \"{item:?}\" was enabled more than once in the source ordering configuration." + ))); + } + items_set.insert(item); + } + + Ok(Self(items)) + } +} + +impl Serialize for SourceItemOrdering { + fn serialize(&self, serializer: S) -> Result + where + S: ser::Serializer, + { + self.0.serialize(serializer) + } +} + +/// Represents the items that can occur within a module. +#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)] +#[serde(rename_all = "snake_case")] +pub enum SourceItemOrderingModuleItemKind { + ExternCrate, + Mod, + ForeignMod, + Use, + Macro, + GlobalAsm, + Static, + Const, + TyAlias, + Enum, + Struct, + Union, + Trait, + TraitAlias, + Impl, + Fn, +} + +impl SourceItemOrderingModuleItemKind { + pub fn all_variants() -> Vec { + #[allow(clippy::enum_glob_use)] // Very local glob use for legibility. + use SourceItemOrderingModuleItemKind::*; + vec![ + ExternCrate, + Mod, + ForeignMod, + Use, + Macro, + GlobalAsm, + Static, + Const, + TyAlias, + Enum, + Struct, + Union, + Trait, + TraitAlias, + Impl, + Fn, + ] + } +} + +/// Represents the configured ordering of items within a module. +/// +/// The [`Deserialize`] implementation checks that no item kinds have been +/// omitted and that there are no duplicates in the user configuration. +#[derive(Clone)] +pub struct SourceItemOrderingModuleItemGroupings { + groups: Vec<(String, Vec)>, + lut: HashMap, +} + +impl SourceItemOrderingModuleItemGroupings { + fn build_lut( + groups: &[(String, Vec)], + ) -> HashMap { + let mut lut = HashMap::new(); + for (group_index, (_, items)) in groups.iter().enumerate() { + for item in items { + lut.insert(item.clone(), group_index); + } + } + lut + } + + pub fn module_level_order_of(&self, item: &SourceItemOrderingModuleItemKind) -> Option { + self.lut.get(item).copied() + } +} + +impl From<&[(&str, &[SourceItemOrderingModuleItemKind])]> for SourceItemOrderingModuleItemGroupings { + fn from(value: &[(&str, &[SourceItemOrderingModuleItemKind])]) -> Self { + let groups: Vec<(String, Vec)> = + value.iter().map(|item| (item.0.to_string(), item.1.to_vec())).collect(); + let lut = Self::build_lut(&groups); + Self { groups, lut } + } +} + +impl core::fmt::Debug for SourceItemOrderingModuleItemGroupings { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.groups.fmt(f) + } +} + +impl<'de> Deserialize<'de> for SourceItemOrderingModuleItemGroupings { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let groups = Vec::<(String, Vec)>::deserialize(deserializer)?; + let items_total: usize = groups.iter().map(|(_, v)| v.len()).sum(); + let lut = Self::build_lut(&groups); + + let mut expected_items = SourceItemOrderingModuleItemKind::all_variants(); + for item in lut.keys() { + expected_items.retain(|i| i != item); + } + + let all_items = SourceItemOrderingModuleItemKind::all_variants(); + if expected_items.is_empty() && items_total == all_items.len() { + let Some(use_group_index) = lut.get(&SourceItemOrderingModuleItemKind::Use) else { + return Err(de::Error::custom("Error in internal LUT.")); + }; + let Some((_, use_group_items)) = groups.get(*use_group_index) else { + return Err(de::Error::custom("Error in internal LUT.")); + }; + if use_group_items.len() > 1 { + return Err(de::Error::custom( + "The group containing the \"use\" item kind may not contain any other item kinds. \ + The \"use\" items will (generally) be sorted by rustfmt already. \ + Therefore it makes no sense to implement linting rules that may conflict with rustfmt.", + )); + } + + Ok(Self { groups, lut }) + } else if items_total != all_items.len() { + Err(de::Error::custom(format!( + "Some module item kinds were configured more than once, or were missing, in the source ordering configuration. \ + The module item kinds are: {all_items:?}" + ))) + } else { + Err(de::Error::custom(format!( + "Not all module item kinds were part of the configured source ordering rule. \ + All item kinds must be provided in the config, otherwise the required source ordering would remain ambiguous. \ + The module item kinds are: {all_items:?}" + ))) + } + } +} + +impl Serialize for SourceItemOrderingModuleItemGroupings { + fn serialize(&self, serializer: S) -> Result + where + S: ser::Serializer, + { + self.groups.serialize(serializer) + } +} + +/// Represents all kinds of trait associated items. +#[derive(Clone, Debug, Deserialize, PartialEq, PartialOrd, Serialize)] +#[serde(rename_all = "snake_case")] +pub enum SourceItemOrderingTraitAssocItemKind { + Const, + Fn, + Type, +} + +impl SourceItemOrderingTraitAssocItemKind { + pub fn all_variants() -> Vec { + #[allow(clippy::enum_glob_use)] // Very local glob use for legibility. + use SourceItemOrderingTraitAssocItemKind::*; + vec![Const, Fn, Type] + } +} + +/// Represents the order in which associated trait items should be ordered. +/// +/// The reason to wrap a `Vec` in a newtype is to be able to implement +/// [`Deserialize`]. Implementing `Deserialize` allows for implementing checks +/// on configuration completeness at the time of loading the clippy config, +/// letting the user know if there's any issues with the config (e.g. not +/// listing all item kinds that should be sorted). +#[derive(Clone)] +pub struct SourceItemOrderingTraitAssocItemKinds(Vec); + +impl SourceItemOrderingTraitAssocItemKinds { + pub fn index_of(&self, item: &SourceItemOrderingTraitAssocItemKind) -> Option { + self.0.iter().position(|i| i == item) + } +} + +impl From for SourceItemOrderingTraitAssocItemKinds +where + T: Into>, +{ + fn from(value: T) -> Self { + Self(value.into()) + } +} + +impl core::fmt::Debug for SourceItemOrderingTraitAssocItemKinds { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +impl<'de> Deserialize<'de> for SourceItemOrderingTraitAssocItemKinds { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let items = Vec::::deserialize(deserializer)?; + + let mut expected_items = SourceItemOrderingTraitAssocItemKind::all_variants(); + for item in &items { + expected_items.retain(|i| i != item); + } + + let all_items = SourceItemOrderingTraitAssocItemKind::all_variants(); + if expected_items.is_empty() && items.len() == all_items.len() { + Ok(Self(items)) + } else if items.len() != all_items.len() { + Err(de::Error::custom(format!( + "Some trait associated item kinds were configured more than once, or were missing, in the source ordering configuration. \ + The trait associated item kinds are: {all_items:?}", + ))) + } else { + Err(de::Error::custom(format!( + "Not all trait associated item kinds were part of the configured source ordering rule. \ + All item kinds must be provided in the config, otherwise the required source ordering would remain ambiguous. \ + The trait associated item kinds are: {all_items:?}" + ))) + } + } +} + +impl Serialize for SourceItemOrderingTraitAssocItemKinds { + fn serialize(&self, serializer: S) -> Result + where + S: ser::Serializer, + { + self.0.serialize(serializer) + } +} + // these impls are never actually called but are used by the various config options that default to // empty lists macro_rules! unimplemented_serialize { diff --git a/clippy_dev/src/serve.rs b/clippy_dev/src/serve.rs index d367fefec619c..a2d1236629fd2 100644 --- a/clippy_dev/src/serve.rs +++ b/clippy_dev/src/serve.rs @@ -20,8 +20,14 @@ pub fn run(port: u16, lint: Option) -> ! { loop { let index_time = mtime("util/gh-pages/index.html"); + let times = [ + "clippy_lints/src", + "util/gh-pages/index_template.html", + "tests/compile-test.rs", + ] + .map(mtime); - if index_time < mtime("clippy_lints/src") || index_time < mtime("util/gh-pages/index_template.html") { + if times.iter().any(|&time| index_time < time) { Command::new(env::var("CARGO").unwrap_or("cargo".into())) .arg("collect-metadata") .spawn() diff --git a/clippy_dev/src/update_lints.rs b/clippy_dev/src/update_lints.rs index d6ed36d52f4e0..795456ad3c56d 100644 --- a/clippy_dev/src/update_lints.rs +++ b/clippy_dev/src/update_lints.rs @@ -762,13 +762,19 @@ fn parse_contents(contents: &str, module: &str, lints: &mut Vec) { Literal{..}(desc) ); - if let Some(LintDeclSearchResult { - token_kind: TokenKind::CloseBrace, - range, - .. - }) = iter.next() - { - lints.push(Lint::new(name, group, desc, module, start..range.end)); + if let Some(end) = iter.find_map(|t| { + if let LintDeclSearchResult { + token_kind: TokenKind::CloseBrace, + range, + .. + } = t + { + Some(range.end) + } else { + None + } + }) { + lints.push(Lint::new(name, group, desc, module, start..end)); } } } diff --git a/clippy_lints/src/arbitrary_source_item_ordering.rs b/clippy_lints/src/arbitrary_source_item_ordering.rs new file mode 100644 index 0000000000000..8719f61a89034 --- /dev/null +++ b/clippy_lints/src/arbitrary_source_item_ordering.rs @@ -0,0 +1,531 @@ +use clippy_config::Conf; +use clippy_config::types::{ + SourceItemOrderingCategory, SourceItemOrderingModuleItemGroupings, SourceItemOrderingModuleItemKind, + SourceItemOrderingTraitAssocItemKind, SourceItemOrderingTraitAssocItemKinds, +}; +use clippy_utils::diagnostics::span_lint_and_note; +use rustc_hir::{ + AssocItemKind, FieldDef, HirId, ImplItemRef, IsAuto, Item, ItemKind, Mod, QPath, TraitItemRef, TyKind, UseKind, + Variant, VariantData, +}; +use rustc_lint::{LateContext, LateLintPass, LintContext}; +use rustc_middle::lint::in_external_macro; +use rustc_session::impl_lint_pass; + +declare_clippy_lint! { + /// ### What it does + /// + /// Confirms that items are sorted in source files as per configuration. + /// + /// ### Why restrict this? + /// + /// Keeping a consistent ordering throughout the codebase helps with working + /// as a team, and possibly improves maintainability of the codebase. The + /// idea is that by defining a consistent and enforceable rule for how + /// source files are structured, less time will be wasted during reviews on + /// a topic that is (under most circumstances) not relevant to the logic + /// implemented in the code. Sometimes this will be referred to as + /// "bikeshedding". + /// + /// ### Default Ordering and Configuration + /// + /// As there is no generally applicable rule, and each project may have + /// different requirements, the lint can be configured with high + /// granularity. The configuration is split into two stages: + /// + /// 1. Which item kinds that should have an internal order enforced. + /// 2. Individual ordering rules per item kind. + /// + /// The item kinds that can be linted are: + /// - Module (with customized groupings, alphabetical within) + /// - Trait (with customized order of associated items, alphabetical within) + /// - Enum, Impl, Struct (purely alphabetical) + /// + /// #### Module Item Order + /// + /// Due to the large variation of items within modules, the ordering can be + /// configured on a very granular level. Item kinds can be grouped together + /// arbitrarily, items within groups will be ordered alphabetically. The + /// following table shows the default groupings: + /// + /// | Group | Item Kinds | + /// |--------------------|----------------------| + /// | `modules` | "mod", "foreign_mod" | + /// | `use` | "use" | + /// | `macros` | "macro" | + /// | `global_asm` | "global_asm" | + /// | `UPPER_SNAKE_CASE` | "static", "const" | + /// | `PascalCase` | "ty_alias", "opaque_ty", "enum", "struct", "union", "trait", "trait_alias", "impl" | + /// | `lower_snake_case` | "fn" | + /// + /// All item kinds must be accounted for to create an enforceable linting + /// rule set. + /// + /// ### Known Problems + /// + /// #### Performance Impact + /// + /// Keep in mind, that ordering source code alphabetically can lead to + /// reduced performance in cases where the most commonly used enum variant + /// isn't the first entry anymore, and similar optimizations that can reduce + /// branch misses, cache locality and such. Either don't use this lint if + /// that's relevant, or disable the lint in modules or items specifically + /// where it matters. Other solutions can be to use profile guided + /// optimization (PGO), post-link optimization (e.g. using BOLT for LLVM), + /// or other advanced optimization methods. A good starting point to dig + /// into optimization is [cargo-pgo][cargo-pgo]. + /// + /// #### Lints on a Contains basis + /// + /// The lint can be disabled only on a "contains" basis, but not per element + /// within a "container", e.g. the lint works per-module, per-struct, + /// per-enum, etc. but not for "don't order this particular enum variant". + /// + /// #### Module documentation + /// + /// Module level rustdoc comments are not part of the resulting syntax tree + /// and as such cannot be linted from within `check_mod`. Instead, the + /// `rustdoc::missing_documentation` lint may be used. + /// + /// #### Module Tests + /// + /// This lint does not implement detection of module tests (or other feature + /// dependent elements for that matter). To lint the location of mod tests, + /// the lint `items_after_test_module` can be used instead. + /// + /// ### Example + /// + /// ```no_run + /// trait TraitUnordered { + /// const A: bool; + /// const C: bool; + /// const B: bool; + /// + /// type SomeType; + /// + /// fn a(); + /// fn c(); + /// fn b(); + /// } + /// ``` + /// + /// Use instead: + /// ```no_run + /// trait TraitOrdered { + /// const A: bool; + /// const B: bool; + /// const C: bool; + /// + /// type SomeType; + /// + /// fn a(); + /// fn b(); + /// fn c(); + /// } + /// ``` + /// + /// [cargo-pgo]: https://github.com/Kobzol/cargo-pgo/blob/main/README.md + /// + #[clippy::version = "1.82.0"] + pub ARBITRARY_SOURCE_ITEM_ORDERING, + restriction, + "arbitrary source item ordering" +} + +impl_lint_pass!(ArbitrarySourceItemOrdering => [ARBITRARY_SOURCE_ITEM_ORDERING]); + +#[derive(Debug)] +#[allow(clippy::struct_excessive_bools)] // Bools are cached feature flags. +pub struct ArbitrarySourceItemOrdering { + assoc_types_order: SourceItemOrderingTraitAssocItemKinds, + enable_ordering_for_enum: bool, + enable_ordering_for_impl: bool, + enable_ordering_for_module: bool, + enable_ordering_for_struct: bool, + enable_ordering_for_trait: bool, + module_item_order_groupings: SourceItemOrderingModuleItemGroupings, +} + +impl ArbitrarySourceItemOrdering { + pub fn new(conf: &'static Conf) -> Self { + #[allow(clippy::enum_glob_use)] // Very local glob use for legibility. + use SourceItemOrderingCategory::*; + Self { + assoc_types_order: conf.trait_assoc_item_kinds_order.clone(), + enable_ordering_for_enum: conf.source_item_ordering.contains(&Enum), + enable_ordering_for_impl: conf.source_item_ordering.contains(&Impl), + enable_ordering_for_module: conf.source_item_ordering.contains(&Module), + enable_ordering_for_struct: conf.source_item_ordering.contains(&Struct), + enable_ordering_for_trait: conf.source_item_ordering.contains(&Trait), + module_item_order_groupings: conf.module_item_order_groupings.clone(), + } + } + + /// Produces a linting warning for incorrectly ordered impl items. + fn lint_impl_item(&self, cx: &T, item: &ImplItemRef, before_item: &ImplItemRef) { + span_lint_and_note( + cx, + ARBITRARY_SOURCE_ITEM_ORDERING, + item.span, + format!( + "incorrect ordering of impl items (defined order: {:?})", + self.assoc_types_order + ), + Some(before_item.span), + format!("should be placed before `{}`", before_item.ident.as_str(),), + ); + } + + /// Produces a linting warning for incorrectly ordered item members. + fn lint_member_name( + cx: &T, + ident: &rustc_span::symbol::Ident, + before_ident: &rustc_span::symbol::Ident, + ) { + span_lint_and_note( + cx, + ARBITRARY_SOURCE_ITEM_ORDERING, + ident.span, + "incorrect ordering of items (must be alphabetically ordered)", + Some(before_ident.span), + format!("should be placed before `{}`", before_ident.as_str(),), + ); + } + + fn lint_member_item(cx: &T, item: &Item<'_>, before_item: &Item<'_>) { + let span = if item.ident.as_str().is_empty() { + &item.span + } else { + &item.ident.span + }; + + let (before_span, note) = if before_item.ident.as_str().is_empty() { + ( + &before_item.span, + "should be placed before the following item".to_owned(), + ) + } else { + ( + &before_item.ident.span, + format!("should be placed before `{}`", before_item.ident.as_str(),), + ) + }; + + // This catches false positives where generated code gets linted. + if span == before_span { + return; + } + + span_lint_and_note( + cx, + ARBITRARY_SOURCE_ITEM_ORDERING, + *span, + "incorrect ordering of items (must be alphabetically ordered)", + Some(*before_span), + note, + ); + } + + /// Produces a linting warning for incorrectly ordered trait items. + fn lint_trait_item(&self, cx: &T, item: &TraitItemRef, before_item: &TraitItemRef) { + span_lint_and_note( + cx, + ARBITRARY_SOURCE_ITEM_ORDERING, + item.span, + format!( + "incorrect ordering of trait items (defined order: {:?})", + self.assoc_types_order + ), + Some(before_item.span), + format!("should be placed before `{}`", before_item.ident.as_str(),), + ); + } +} + +impl<'tcx> LateLintPass<'tcx> for ArbitrarySourceItemOrdering { + fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) { + match &item.kind { + ItemKind::Enum(enum_def, _generics) if self.enable_ordering_for_enum => { + let mut cur_v: Option<&Variant<'_>> = None; + for variant in enum_def.variants { + if in_external_macro(cx.sess(), variant.span) { + continue; + } + + if let Some(cur_v) = cur_v { + if cur_v.ident.name.as_str() > variant.ident.name.as_str() && cur_v.span != variant.span { + Self::lint_member_name(cx, &variant.ident, &cur_v.ident); + } + } + cur_v = Some(variant); + } + }, + ItemKind::Struct(VariantData::Struct { fields, .. }, _generics) if self.enable_ordering_for_struct => { + let mut cur_f: Option<&FieldDef<'_>> = None; + for field in *fields { + if in_external_macro(cx.sess(), field.span) { + continue; + } + + if let Some(cur_f) = cur_f { + if cur_f.ident.name.as_str() > field.ident.name.as_str() && cur_f.span != field.span { + Self::lint_member_name(cx, &field.ident, &cur_f.ident); + } + } + cur_f = Some(field); + } + }, + ItemKind::Trait(is_auto, _safety, _generics, _generic_bounds, item_ref) + if self.enable_ordering_for_trait && *is_auto == IsAuto::No => + { + let mut cur_t: Option<&TraitItemRef> = None; + + for item in *item_ref { + if in_external_macro(cx.sess(), item.span) { + continue; + } + + if let Some(cur_t) = cur_t { + let cur_t_kind = convert_assoc_item_kind(cur_t.kind); + let cur_t_kind_index = self.assoc_types_order.index_of(&cur_t_kind); + let item_kind = convert_assoc_item_kind(item.kind); + let item_kind_index = self.assoc_types_order.index_of(&item_kind); + + if cur_t_kind == item_kind && cur_t.ident.name.as_str() > item.ident.name.as_str() { + Self::lint_member_name(cx, &item.ident, &cur_t.ident); + } else if cur_t_kind_index > item_kind_index { + self.lint_trait_item(cx, item, cur_t); + } + } + cur_t = Some(item); + } + }, + ItemKind::Impl(trait_impl) if self.enable_ordering_for_impl => { + let mut cur_t: Option<&ImplItemRef> = None; + + for item in trait_impl.items { + if in_external_macro(cx.sess(), item.span) { + continue; + } + + if let Some(cur_t) = cur_t { + let cur_t_kind = convert_assoc_item_kind(cur_t.kind); + let cur_t_kind_index = self.assoc_types_order.index_of(&cur_t_kind); + let item_kind = convert_assoc_item_kind(item.kind); + let item_kind_index = self.assoc_types_order.index_of(&item_kind); + + if cur_t_kind == item_kind && cur_t.ident.name.as_str() > item.ident.name.as_str() { + Self::lint_member_name(cx, &item.ident, &cur_t.ident); + } else if cur_t_kind_index > item_kind_index { + self.lint_impl_item(cx, item, cur_t); + } + } + cur_t = Some(item); + } + }, + _ => {}, // Catch-all for `ItemKinds` that don't have fields. + } + } + + fn check_mod(&mut self, cx: &LateContext<'tcx>, module: &'tcx Mod<'tcx>, _: HirId) { + struct CurItem<'a> { + item: &'a Item<'a>, + order: usize, + name: String, + } + let mut cur_t: Option> = None; + + if !self.enable_ordering_for_module { + return; + } + + let items = module.item_ids.iter().map(|&id| cx.tcx.hir().item(id)); + + // Iterates over the items within a module. + // + // As of 2023-05-09, the Rust compiler will hold the entries in the same + // order as they appear in the source code, which is convenient for us, + // as no sorting by source map/line of code has to be applied. + // + for item in items { + if in_external_macro(cx.sess(), item.span) { + continue; + } + + // The following exceptions (skipping with `continue;`) may not be + // complete, edge cases have not been explored further than what + // appears in the existing code base. + if item.ident.name == rustc_span::symbol::kw::Empty { + if let ItemKind::Impl(_) = item.kind { + // Sorting trait impls for unnamed types makes no sense. + if get_item_name(item).is_empty() { + continue; + } + } else if let ItemKind::ForeignMod { .. } = item.kind { + continue; + } else if let ItemKind::GlobalAsm(_) = item.kind { + continue; + } else if let ItemKind::Use(path, use_kind) = item.kind { + if path.segments.is_empty() { + // Use statements that contain braces get caught here. + // They will still be linted internally. + continue; + } else if path.segments.len() >= 2 + && (path.segments[0].ident.name == rustc_span::sym::std + || path.segments[0].ident.name == rustc_span::sym::core) + && path.segments[1].ident.name == rustc_span::sym::prelude + { + // Filters the autogenerated prelude use statement. + // e.g. `use std::prelude::rustc_2021` + } else if use_kind == UseKind::Glob { + // Filters glob kinds of uses. + // e.g. `use std::sync::*` + } else { + // This can be used for debugging. + // println!("Unknown autogenerated use statement: {:?}", item); + } + continue; + } + } + + if item.ident.name.as_str().starts_with('_') { + // Filters out unnamed macro-like impls for various derives, + // e.g. serde::Serialize or num_derive::FromPrimitive. + continue; + } + + if item.ident.name == rustc_span::sym::std && item.span.is_dummy() { + if let ItemKind::ExternCrate(None) = item.kind { + // Filters the auto-included Rust standard library. + continue; + } + println!("Unknown item: {item:?}"); + } + + let item_kind = convert_module_item_kind(&item.kind); + let module_level_order = self + .module_item_order_groupings + .module_level_order_of(&item_kind) + .unwrap_or_default(); + + if let Some(cur_t) = cur_t.as_ref() { + use std::cmp::Ordering; // Better legibility. + match module_level_order.cmp(&cur_t.order) { + Ordering::Less => { + Self::lint_member_item(cx, item, cur_t.item); + }, + Ordering::Equal if item_kind == SourceItemOrderingModuleItemKind::Use => { + // Skip ordering use statements, as these should be ordered by rustfmt. + }, + Ordering::Equal if cur_t.name > get_item_name(item) => { + Self::lint_member_item(cx, item, cur_t.item); + }, + Ordering::Equal | Ordering::Greater => { + // Nothing to do in this case, they're already in the right order. + }, + } + } + + // Makes a note of the current item for comparison with the next. + cur_t = Some(CurItem { + order: module_level_order, + item, + name: get_item_name(item), + }); + } + } +} + +/// Converts a [`rustc_hir::AssocItemKind`] to a +/// [`SourceItemOrderingTraitAssocItemKind`]. +/// +/// This is implemented here because `rustc_hir` is not a dependency of +/// `clippy_config`. +fn convert_assoc_item_kind(value: AssocItemKind) -> SourceItemOrderingTraitAssocItemKind { + #[allow(clippy::enum_glob_use)] // Very local glob use for legibility. + use SourceItemOrderingTraitAssocItemKind::*; + match value { + AssocItemKind::Const { .. } => Const, + AssocItemKind::Type { .. } => Type, + AssocItemKind::Fn { .. } => Fn, + } +} + +/// Converts a [`rustc_hir::ItemKind`] to a +/// [`SourceItemOrderingModuleItemKind`]. +/// +/// This is implemented here because `rustc_hir` is not a dependency of +/// `clippy_config`. +fn convert_module_item_kind(value: &ItemKind<'_>) -> SourceItemOrderingModuleItemKind { + #[allow(clippy::enum_glob_use)] // Very local glob use for legibility. + use SourceItemOrderingModuleItemKind::*; + match value { + ItemKind::ExternCrate(..) => ExternCrate, + ItemKind::Use(..) => Use, + ItemKind::Static(..) => Static, + ItemKind::Const(..) => Const, + ItemKind::Fn(..) => Fn, + ItemKind::Macro(..) => Macro, + ItemKind::Mod(..) => Mod, + ItemKind::ForeignMod { .. } => ForeignMod, + ItemKind::GlobalAsm(..) => GlobalAsm, + ItemKind::TyAlias(..) => TyAlias, + ItemKind::Enum(..) => Enum, + ItemKind::Struct(..) => Struct, + ItemKind::Union(..) => Union, + ItemKind::Trait(..) => Trait, + ItemKind::TraitAlias(..) => TraitAlias, + ItemKind::Impl(..) => Impl, + } +} + +/// Gets the item name for sorting purposes, which in the general case is +/// `item.ident.name`. +/// +/// For trait impls, the name used for sorting will be the written path of +/// `item.self_ty` plus the written path of `item.of_trait`, joined with +/// exclamation marks. Exclamation marks are used because they are the first +/// printable ASCII character. +/// +/// Trait impls generated using a derive-macro will have their path rewritten, +/// such that for example `Default` is `$crate::default::Default`, and +/// `std::clone::Clone` is `$crate::clone::Clone`. This behaviour is described +/// further in the [Rust Reference, Paths Chapter][rust_ref]. +/// +/// [rust_ref]: https://doc.rust-lang.org/reference/paths.html#crate-1 +fn get_item_name(item: &Item<'_>) -> String { + match item.kind { + ItemKind::Impl(im) => { + if let TyKind::Path(path) = im.self_ty.kind { + match path { + QPath::Resolved(_, path) => { + let segs = path.segments.iter(); + let mut segs: Vec = segs.map(|s| s.ident.name.as_str().to_owned()).collect(); + + if let Some(of_trait) = im.of_trait { + let mut trait_segs: Vec = of_trait + .path + .segments + .iter() + .map(|s| s.ident.name.as_str().to_owned()) + .collect(); + segs.append(&mut trait_segs); + } + + segs.push(String::new()); + segs.join("!!") + }, + QPath::TypeRelative(_, _path_seg) => { + // This case doesn't exist in the clippy tests codebase. + String::new() + }, + QPath::LangItem(_, _) => String::new(), + } + } else { + // Impls for anything that isn't a named type can be skipped. + String::new() + } + }, + _ => item.ident.name.as_str().to_owned(), + } +} diff --git a/clippy_lints/src/attrs/mod.rs b/clippy_lints/src/attrs/mod.rs index 684756ce87f1d..c29c9f08cf707 100644 --- a/clippy_lints/src/attrs/mod.rs +++ b/clippy_lints/src/attrs/mod.rs @@ -14,7 +14,7 @@ mod utils; use clippy_config::Conf; use clippy_config::msrvs::{self, Msrv}; -use rustc_ast::{Attribute, MetaItemInner, MetaItemKind, self as ast}; +use rustc_ast::{self as ast, Attribute, MetaItemInner, MetaItemKind}; use rustc_hir::{ImplItem, Item, TraitItem}; use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass}; use rustc_session::impl_lint_pass; diff --git a/clippy_lints/src/attrs/useless_attribute.rs b/clippy_lints/src/attrs/useless_attribute.rs index 92b9f9cba5251..dc5a68570896d 100644 --- a/clippy_lints/src/attrs/useless_attribute.rs +++ b/clippy_lints/src/attrs/useless_attribute.rs @@ -1,10 +1,9 @@ -use super::utils::{extract_clippy_lint, is_lint_level, is_word}; use super::USELESS_ATTRIBUTE; +use super::utils::{extract_clippy_lint, is_lint_level, is_word}; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::{SpanRangeExt, first_line_of_span}; -use rustc_ast::MetaItemInner; +use rustc_ast::{Attribute, Item, ItemKind, MetaItemInner}; use rustc_errors::Applicability; -use rustc_ast::{Item, ItemKind, Attribute}; use rustc_lint::{EarlyContext, LintContext}; use rustc_middle::lint::in_external_macro; use rustc_span::sym; diff --git a/clippy_lints/src/await_holding_invalid.rs b/clippy_lints/src/await_holding_invalid.rs index 9952d0af99e54..6948cf560a539 100644 --- a/clippy_lints/src/await_holding_invalid.rs +++ b/clippy_lints/src/await_holding_invalid.rs @@ -15,7 +15,7 @@ declare_clippy_lint! { /// `MutexGuard`. /// /// ### Why is this bad? - /// The Mutex types found in [`std::sync`][https://doc.rust-lang.org/stable/std/sync/] and + /// The Mutex types found in [`std::sync`](https://doc.rust-lang.org/stable/std/sync/) and /// [`parking_lot`](https://docs.rs/parking_lot/latest/parking_lot/) are /// not designed to operate in an async context across await points. /// diff --git a/clippy_lints/src/booleans.rs b/clippy_lints/src/booleans.rs index 26888f7e3a0ed..26a20bc99a05a 100644 --- a/clippy_lints/src/booleans.rs +++ b/clippy_lints/src/booleans.rs @@ -203,6 +203,21 @@ fn check_simplify_not(cx: &LateContext<'_>, msrv: &Msrv, expr: &Expr<'_>) { && let Some(suggestion) = simplify_not(cx, msrv, inner) && cx.tcx.lint_level_at_node(NONMINIMAL_BOOL, expr.hir_id).0 != Level::Allow { + use clippy_utils::sugg::{Sugg, has_enclosing_paren}; + let maybe_par = if let Some(sug) = Sugg::hir_opt(cx, inner) { + match sug { + Sugg::BinOp(..) => true, + Sugg::MaybeParen(sug) if !has_enclosing_paren(&sug) => true, + _ => false, + } + } else { + false + }; + let suggestion = if maybe_par { + format!("({suggestion})") + } else { + suggestion + }; span_lint_and_sugg( cx, NONMINIMAL_BOOL, diff --git a/clippy_lints/src/borrow_deref_ref.rs b/clippy_lints/src/borrow_deref_ref.rs index cba8224b84c9e..f2551a05b1a22 100644 --- a/clippy_lints/src/borrow_deref_ref.rs +++ b/clippy_lints/src/borrow_deref_ref.rs @@ -4,7 +4,7 @@ use clippy_utils::source::SpanRangeExt; use clippy_utils::ty::implements_trait; use clippy_utils::{get_parent_expr, is_from_proc_macro, is_lint_allowed}; use rustc_errors::Applicability; -use rustc_hir::{ExprKind, UnOp}; +use rustc_hir::{BorrowKind, ExprKind, UnOp}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::mir::Mutability; use rustc_middle::ty; @@ -49,7 +49,7 @@ declare_lint_pass!(BorrowDerefRef => [BORROW_DEREF_REF]); impl<'tcx> LateLintPass<'tcx> for BorrowDerefRef { fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &rustc_hir::Expr<'tcx>) { - if let ExprKind::AddrOf(_, Mutability::Not, addrof_target) = e.kind + if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, addrof_target) = e.kind && let ExprKind::Unary(UnOp::Deref, deref_target) = addrof_target.kind && !matches!(deref_target.kind, ExprKind::Unary(UnOp::Deref, ..)) && !e.span.from_expansion() diff --git a/clippy_lints/src/casts/cast_ptr_alignment.rs b/clippy_lints/src/casts/cast_ptr_alignment.rs index b11b967f8bc78..205357afd8408 100644 --- a/clippy_lints/src/casts/cast_ptr_alignment.rs +++ b/clippy_lints/src/casts/cast_ptr_alignment.rs @@ -20,7 +20,7 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>) { ); lint_cast_ptr_alignment(cx, expr, cast_from, cast_to); } else if let ExprKind::MethodCall(method_path, self_arg, [], _) = &expr.kind { - if method_path.ident.name == sym!(cast) + if method_path.ident.name.as_str() == "cast" && let Some(generic_args) = method_path.args && let [GenericArg::Type(cast_to)] = generic_args.args // There probably is no obvious reason to do this, just to be consistent with `as` cases. diff --git a/clippy_lints/src/casts/unnecessary_cast.rs b/clippy_lints/src/casts/unnecessary_cast.rs index 811d33c8bde2f..abd80abffe696 100644 --- a/clippy_lints/src/casts/unnecessary_cast.rs +++ b/clippy_lints/src/casts/unnecessary_cast.rs @@ -268,8 +268,7 @@ fn is_cast_from_ty_alias<'tcx>(cx: &LateContext<'tcx>, expr: impl Visitable<'tcx if !snippet .split("->") .skip(1) - .map(|s| snippet_eq_ty(s, cast_from) || s.split("where").any(|ty| snippet_eq_ty(ty, cast_from))) - .any(|a| a) + .any(|s| snippet_eq_ty(s, cast_from) || s.split("where").any(|ty| snippet_eq_ty(ty, cast_from))) { return ControlFlow::Break(()); } diff --git a/clippy_lints/src/checked_conversions.rs b/clippy_lints/src/checked_conversions.rs index 2e7f91a842e1e..f76e399517cfd 100644 --- a/clippy_lints/src/checked_conversions.rs +++ b/clippy_lints/src/checked_conversions.rs @@ -232,7 +232,7 @@ fn get_types_from_cast<'a>( // or `to_type::MAX as from_type` let call_from_cast: Option<(&Expr<'_>, &str)> = if let ExprKind::Cast(limit, from_type) = &expr.kind // to_type::max_value(), from_type - && let TyKind::Path(ref from_type_path) = &from_type.kind + && let TyKind::Path(from_type_path) = &from_type.kind && let Some(from_sym) = int_ty_to_sym(from_type_path) { Some((limit, from_sym)) @@ -245,7 +245,7 @@ fn get_types_from_cast<'a>( if let ExprKind::Call(from_func, [limit]) = &expr.kind // `from_type::from, to_type::max_value()` // `from_type::from` - && let ExprKind::Path(ref path) = &from_func.kind + && let ExprKind::Path(path) = &from_func.kind && let Some(from_sym) = get_implementing_type(path, INTS, "from") { Some((limit, from_sym)) diff --git a/clippy_lints/src/cognitive_complexity.rs b/clippy_lints/src/cognitive_complexity.rs index 477435236a527..383fae7992b61 100644 --- a/clippy_lints/src/cognitive_complexity.rs +++ b/clippy_lints/src/cognitive_complexity.rs @@ -30,7 +30,7 @@ declare_clippy_lint! { #[clippy::version = "1.35.0"] pub COGNITIVE_COMPLEXITY, nursery, - "functions that should be split up into multiple functions" + "functions that should be split up into multiple functions", @eval_always = true } diff --git a/clippy_lints/src/copies.rs b/clippy_lints/src/copies.rs index d90a22bb62a60..c4afdc757d81f 100644 --- a/clippy_lints/src/copies.rs +++ b/clippy_lints/src/copies.rs @@ -540,24 +540,22 @@ fn check_for_warn_of_moved_symbol(cx: &LateContext<'_>, symbols: &[(HirId, Symbo .iter() .filter(|&&(_, name)| !name.as_str().starts_with('_')) .any(|&(_, name)| { - let mut walker = ContainsName { - name, - result: false, - cx, - }; + let mut walker = ContainsName { name, cx }; // Scan block - block + let mut res = block .stmts .iter() .filter(|stmt| !ignore_span.overlaps(stmt.span)) - .for_each(|stmt| intravisit::walk_stmt(&mut walker, stmt)); + .try_for_each(|stmt| intravisit::walk_stmt(&mut walker, stmt)); if let Some(expr) = block.expr { - intravisit::walk_expr(&mut walker, expr); + if res.is_continue() { + res = intravisit::walk_expr(&mut walker, expr); + } } - walker.result + res.is_break() }) }) } diff --git a/clippy_lints/src/copy_iterator.rs b/clippy_lints/src/copy_iterator.rs index 50fd76a3a4774..4ecf3e41611b1 100644 --- a/clippy_lints/src/copy_iterator.rs +++ b/clippy_lints/src/copy_iterator.rs @@ -37,7 +37,7 @@ declare_lint_pass!(CopyIterator => [COPY_ITERATOR]); impl<'tcx> LateLintPass<'tcx> for CopyIterator { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { if let ItemKind::Impl(Impl { - of_trait: Some(ref trait_ref), + of_trait: Some(trait_ref), .. }) = item.kind && let ty = cx.tcx.type_of(item.owner_id).instantiate_identity() diff --git a/clippy_lints/src/ctfe.rs b/clippy_lints/src/ctfe.rs index 181514e8372b8..589b99518a09b 100644 --- a/clippy_lints/src/ctfe.rs +++ b/clippy_lints/src/ctfe.rs @@ -5,7 +5,6 @@ use rustc_lint::{LateContext, LateLintPass}; use rustc_session::declare_lint_pass; use rustc_span::Span; - declare_lint_pass! { /// Ensures that Constant-time Function Evaluation is being done (specifically, MIR lint passes). /// As Clippy deactivates codegen, this lint ensures that CTFE (used in hard errors) is still ran. diff --git a/clippy_lints/src/declare_clippy_lint.rs b/clippy_lints/src/declare_clippy_lint.rs index a785a9d377c5b..4d908af4084f3 100644 --- a/clippy_lints/src/declare_clippy_lint.rs +++ b/clippy_lints/src/declare_clippy_lint.rs @@ -4,7 +4,7 @@ macro_rules! declare_clippy_lint { (@ $(#[doc = $lit:literal])* pub $lint_name:ident, - $category:ident, + $level:ident, $lintcategory:expr, $desc:literal, $version_expr:expr, @@ -15,7 +15,7 @@ macro_rules! declare_clippy_lint { $(#[doc = $lit])* #[clippy::version = $version_lit] pub clippy::$lint_name, - $category, + $level, $desc, report_in_external_macro:true $(, @eval_always = $eval_always)? @@ -35,12 +35,13 @@ macro_rules! declare_clippy_lint { pub $lint_name:ident, restriction, $desc:literal - $(@eval_always = $eval_always: literal)? + $(, @eval_always = $eval_always: literal)? ) => { declare_clippy_lint! {@ $(#[doc = $lit])* pub $lint_name, Allow, crate::LintCategory::Restriction, $desc, - Some($version), $version $(, $eval_always)? + Some($version), $version + $(, $eval_always)? } }; ( @@ -49,12 +50,13 @@ macro_rules! declare_clippy_lint { pub $lint_name:ident, style, $desc:literal - $(@eval_always = $eval_always: literal)? + $(, @eval_always = $eval_always: literal)? ) => { declare_clippy_lint! {@ $(#[doc = $lit])* pub $lint_name, Warn, crate::LintCategory::Style, $desc, - Some($version), $version $(, $eval_always)? + Some($version), $version + $(, $eval_always)? } }; ( @@ -63,12 +65,13 @@ macro_rules! declare_clippy_lint { pub $lint_name:ident, correctness, $desc:literal - $(@eval_always = $eval_always: literal)? + $(, @eval_always = $eval_always: literal)? ) => { declare_clippy_lint! {@ $(#[doc = $lit])* pub $lint_name, Deny, crate::LintCategory::Correctness, $desc, - Some($version), $version $(, $eval_always)? + Some($version), $version + $(, $eval_always)? } }; @@ -78,12 +81,13 @@ macro_rules! declare_clippy_lint { pub $lint_name:ident, perf, $desc:literal - $(@eval_always = $eval_always: literal)? + $(, @eval_always = $eval_always: literal)? ) => { declare_clippy_lint! {@ $(#[doc = $lit])* pub $lint_name, Warn, crate::LintCategory::Perf, $desc, - Some($version), $version $(, $eval_always)? + Some($version), $version + $(, $eval_always)? } }; ( @@ -92,12 +96,13 @@ macro_rules! declare_clippy_lint { pub $lint_name:ident, complexity, $desc:literal - $(@eval_always = $eval_always: literal)? + $(, @eval_always = $eval_always: literal)? ) => { declare_clippy_lint! {@ $(#[doc = $lit])* pub $lint_name, Warn, crate::LintCategory::Complexity, $desc, - Some($version), $version $(, $eval_always)? + Some($version), $version + $(, $eval_always)? } }; ( @@ -106,12 +111,13 @@ macro_rules! declare_clippy_lint { pub $lint_name:ident, suspicious, $desc:literal - $(@eval_always = $eval_always: literal)? + $(, @eval_always = $eval_always: literal)? ) => { declare_clippy_lint! {@ $(#[doc = $lit])* pub $lint_name, Warn, crate::LintCategory::Suspicious, $desc, - Some($version), $version $(, $eval_always)? + Some($version), $version + $(, $eval_always)? } }; ( @@ -120,12 +126,13 @@ macro_rules! declare_clippy_lint { pub $lint_name:ident, nursery, $desc:literal - $(@eval_always = $eval_always: literal)? + $(, @eval_always = $eval_always: literal)? ) => { declare_clippy_lint! {@ $(#[doc = $lit])* pub $lint_name, Allow, crate::LintCategory::Nursery, $desc, - Some($version), $version $(, $eval_always)? + Some($version), $version + $(, $eval_always)? } }; ( @@ -134,12 +141,13 @@ macro_rules! declare_clippy_lint { pub $lint_name:ident, pedantic, $desc:literal - $(@eval_always = $eval_always: literal)? + $(, @eval_always = $eval_always: literal)? ) => { declare_clippy_lint! {@ $(#[doc = $lit])* pub $lint_name, Allow, crate::LintCategory::Pedantic, $desc, - Some($version), $version $(, $eval_always)? + Some($version), $version + $(, $eval_always)? } }; ( @@ -148,12 +156,13 @@ macro_rules! declare_clippy_lint { pub $lint_name:ident, cargo, $desc:literal - $(@eval_always = $eval_always: literal)? + $(, @eval_always = $eval_always: literal)? ) => { declare_clippy_lint! {@ $(#[doc = $lit])* pub $lint_name, Allow, crate::LintCategory::Cargo, $desc, - Some($version), $version $(, $eval_always)? + Some($version), $version + $(, $eval_always)? } }; diff --git a/clippy_lints/src/declared_lints.rs b/clippy_lints/src/declared_lints.rs index 3c4e75df8abe4..edb52851e0cb8 100644 --- a/clippy_lints/src/declared_lints.rs +++ b/clippy_lints/src/declared_lints.rs @@ -28,12 +28,15 @@ pub static LINTS: &[&crate::LintInfo] = &[ #[cfg(feature = "internal")] crate::utils::internal_lints::produce_ice::PRODUCE_ICE_INFO, #[cfg(feature = "internal")] + crate::utils::internal_lints::slow_symbol_comparisons::SLOW_SYMBOL_COMPARISONS_INFO, + #[cfg(feature = "internal")] crate::utils::internal_lints::unnecessary_def_path::UNNECESSARY_DEF_PATH_INFO, #[cfg(feature = "internal")] crate::utils::internal_lints::unsorted_clippy_utils_paths::UNSORTED_CLIPPY_UTILS_PATHS_INFO, crate::absolute_paths::ABSOLUTE_PATHS_INFO, crate::almost_complete_range::ALMOST_COMPLETE_RANGE_INFO, crate::approx_const::APPROX_CONSTANT_INFO, + crate::arbitrary_source_item_ordering::ARBITRARY_SOURCE_ITEM_ORDERING_INFO, crate::arc_with_non_send_sync::ARC_WITH_NON_SEND_SYNC_INFO, crate::as_conversions::AS_CONVERSIONS_INFO, crate::asm_syntax::INLINE_ASM_X86_ATT_SYNTAX_INFO, @@ -414,14 +417,17 @@ pub static LINTS: &[&crate::LintInfo] = &[ crate::methods::MANUAL_SPLIT_ONCE_INFO, crate::methods::MANUAL_STR_REPEAT_INFO, crate::methods::MANUAL_TRY_FOLD_INFO, + crate::methods::MAP_ALL_ANY_IDENTITY_INFO, crate::methods::MAP_CLONE_INFO, crate::methods::MAP_COLLECT_RESULT_UNIT_INFO, crate::methods::MAP_ERR_IGNORE_INFO, crate::methods::MAP_FLATTEN_INFO, crate::methods::MAP_IDENTITY_INFO, crate::methods::MAP_UNWRAP_OR_INFO, + crate::methods::MAP_WITH_UNUSED_ARGUMENT_OVER_RANGES_INFO, crate::methods::MUT_MUTEX_LOCK_INFO, crate::methods::NAIVE_BYTECOUNT_INFO, + crate::methods::NEEDLESS_AS_BYTES_INFO, crate::methods::NEEDLESS_CHARACTER_ITERATION_INFO, crate::methods::NEEDLESS_COLLECT_INFO, crate::methods::NEEDLESS_OPTION_AS_DEREF_INFO, diff --git a/clippy_lints/src/derivable_impls.rs b/clippy_lints/src/derivable_impls.rs index 2920bbb4c81e8..2b6bfafe695be 100644 --- a/clippy_lints/src/derivable_impls.rs +++ b/clippy_lints/src/derivable_impls.rs @@ -187,7 +187,7 @@ fn check_enum<'tcx>(cx: &LateContext<'tcx>, item: &'tcx Item<'_>, func_expr: &Ex impl<'tcx> LateLintPass<'tcx> for DerivableImpls { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { if let ItemKind::Impl(Impl { - of_trait: Some(ref trait_ref), + of_trait: Some(trait_ref), items: [child], self_ty, .. diff --git a/clippy_lints/src/derive.rs b/clippy_lints/src/derive.rs index e8e21edd49435..e569c4dc78637 100644 --- a/clippy_lints/src/derive.rs +++ b/clippy_lints/src/derive.rs @@ -1,3 +1,5 @@ +use std::ops::ControlFlow; + use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_then, span_lint_hir_and_then}; use clippy_utils::ty::{implements_trait, implements_trait_with_env, is_copy}; use clippy_utils::{has_non_exhaustive_attr, is_lint_allowed, match_def_path, paths}; @@ -202,7 +204,7 @@ declare_lint_pass!(Derive => [ impl<'tcx> LateLintPass<'tcx> for Derive { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { if let ItemKind::Impl(Impl { - of_trait: Some(ref trait_ref), + of_trait: Some(trait_ref), .. }) = item.kind { @@ -371,9 +373,8 @@ fn check_unsafe_derive_deserialize<'tcx>( ty: Ty<'tcx>, ) { fn has_unsafe<'tcx>(cx: &LateContext<'tcx>, item: &'tcx Item<'_>) -> bool { - let mut visitor = UnsafeVisitor { cx, has_unsafe: false }; - walk_item(&mut visitor, item); - visitor.has_unsafe + let mut visitor = UnsafeVisitor { cx }; + walk_item(&mut visitor, item).is_break() } if let Some(trait_def_id) = trait_ref.trait_def_id() @@ -406,38 +407,37 @@ fn check_unsafe_derive_deserialize<'tcx>( struct UnsafeVisitor<'a, 'tcx> { cx: &'a LateContext<'tcx>, - has_unsafe: bool, } impl<'tcx> Visitor<'tcx> for UnsafeVisitor<'_, 'tcx> { + type Result = ControlFlow<()>; type NestedFilter = nested_filter::All; - fn visit_fn(&mut self, kind: FnKind<'tcx>, decl: &'tcx FnDecl<'_>, body_id: BodyId, _: Span, id: LocalDefId) { - if self.has_unsafe { - return; - } - + fn visit_fn( + &mut self, + kind: FnKind<'tcx>, + decl: &'tcx FnDecl<'_>, + body_id: BodyId, + _: Span, + id: LocalDefId, + ) -> Self::Result { if let Some(header) = kind.header() && header.safety == Safety::Unsafe { - self.has_unsafe = true; + ControlFlow::Break(()) + } else { + walk_fn(self, kind, decl, body_id, id) } - - walk_fn(self, kind, decl, body_id, id); } - fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { - if self.has_unsafe { - return; - } - + fn visit_expr(&mut self, expr: &'tcx Expr<'_>) -> Self::Result { if let ExprKind::Block(block, _) = expr.kind { if block.rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) { - self.has_unsafe = true; + return ControlFlow::Break(()); } } - walk_expr(self, expr); + walk_expr(self, expr) } fn nested_visit_map(&mut self) -> Self::Map { diff --git a/clippy_lints/src/disallowed_macros.rs b/clippy_lints/src/disallowed_macros.rs index b51d343132b26..bdd49bf8aa7cc 100644 --- a/clippy_lints/src/disallowed_macros.rs +++ b/clippy_lints/src/disallowed_macros.rs @@ -2,7 +2,6 @@ use clippy_config::Conf; use clippy_utils::create_disallowed_map; use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir_and_then}; use clippy_utils::macros::macro_backtrace; -use rustc_ast::Attribute; use rustc_data_structures::fx::FxHashSet; use rustc_errors::Diag; use rustc_hir::def_id::DefIdMap; @@ -14,6 +13,8 @@ use rustc_middle::ty::TyCtxt; use rustc_session::impl_lint_pass; use rustc_span::{ExpnId, MacroKind, Span}; +use crate::utils::attr_collector::AttrStorage; + declare_clippy_lint! { /// ### What it does /// Denies the configured macros in clippy.toml @@ -64,14 +65,19 @@ pub struct DisallowedMacros { // Track the most recently seen node that can have a `derive` attribute. // Needed to use the correct lint level. derive_src: Option, + + // When a macro is disallowed in an early pass, it's stored + // and emitted during the late pass. This happens for attributes. + earlies: AttrStorage, } impl DisallowedMacros { - pub fn new(tcx: TyCtxt<'_>, conf: &'static Conf) -> Self { + pub fn new(tcx: TyCtxt<'_>, conf: &'static Conf, earlies: AttrStorage) -> Self { Self { disallowed: create_disallowed_map(tcx, &conf.disallowed_macros), seen: FxHashSet::default(), derive_src: None, + earlies, } } @@ -114,6 +120,15 @@ impl DisallowedMacros { impl_lint_pass!(DisallowedMacros => [DISALLOWED_MACROS]); impl LateLintPass<'_> for DisallowedMacros { + fn check_crate(&mut self, cx: &LateContext<'_>) { + // once we check a crate in the late pass we can emit the early pass lints + if let Some(attr_spans) = self.earlies.clone().0.get() { + for span in attr_spans { + self.check(cx, *span, None); + } + } + } + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { self.check(cx, expr.span, None); // `$t + $t` can have the context of $t, check also the span of the binary operator @@ -164,8 +179,4 @@ impl LateLintPass<'_> for DisallowedMacros { fn check_path(&mut self, cx: &LateContext<'_>, path: &Path<'_>, _: HirId) { self.check(cx, path.span, None); } - - fn check_attribute(&mut self, cx: &LateContext<'_>, attr: &Attribute) { - self.check(cx, attr.span, self.derive_src); - } } diff --git a/clippy_lints/src/disallowed_script_idents.rs b/clippy_lints/src/disallowed_script_idents.rs index f79264e6b04a1..53c24a3faf1d8 100644 --- a/clippy_lints/src/disallowed_script_idents.rs +++ b/clippy_lints/src/disallowed_script_idents.rs @@ -80,7 +80,7 @@ impl EarlyLintPass for DisallowedScriptIdents { let mut symbols: Vec<_> = symbols.iter().collect(); symbols.sort_unstable_by_key(|k| k.1); - for (symbol, &span) in &symbols { + for &(symbol, &span) in &symbols { // Note: `symbol.as_str()` is an expensive operation, thus should not be called // more than once for a single symbol. let symbol_str = symbol.as_str(); diff --git a/clippy_lints/src/doc/mod.rs b/clippy_lints/src/doc/mod.rs index 89c6a4e08dc44..df7c37a192ad4 100644 --- a/clippy_lints/src/doc/mod.rs +++ b/clippy_lints/src/doc/mod.rs @@ -427,11 +427,11 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does - /// Checks if the first line in the documentation of items listed in module page is too long. + /// Checks if the first paragraph in the documentation of items listed in the module page is too long. /// /// ### Why is this bad? - /// Documentation will show the first paragraph of the doscstring in the summary page of a - /// module, so having a nice, short summary in the first paragraph is part of writing good docs. + /// Documentation will show the first paragraph of the docstring in the summary page of a + /// module. Having a nice, short summary in the first paragraph is part of writing good docs. /// /// ### Example /// ```no_run @@ -453,7 +453,7 @@ declare_clippy_lint! { #[clippy::version = "1.82.0"] pub TOO_LONG_FIRST_DOC_PARAGRAPH, nursery, - "ensure that the first line of a documentation paragraph isn't too long" + "ensure the first documentation paragraph is short" } declare_clippy_lint! { diff --git a/clippy_lints/src/empty_drop.rs b/clippy_lints/src/empty_drop.rs index b66dd2108fc9b..10a84b1b2ff2b 100644 --- a/clippy_lints/src/empty_drop.rs +++ b/clippy_lints/src/empty_drop.rs @@ -36,7 +36,7 @@ declare_lint_pass!(EmptyDrop => [EMPTY_DROP]); impl LateLintPass<'_> for EmptyDrop { fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) { if let ItemKind::Impl(Impl { - of_trait: Some(ref trait_ref), + of_trait: Some(trait_ref), items: [child], .. }) = item.kind diff --git a/clippy_lints/src/eta_reduction.rs b/clippy_lints/src/eta_reduction.rs index 6ca599ed36113..de10b7bf53391 100644 --- a/clippy_lints/src/eta_reduction.rs +++ b/clippy_lints/src/eta_reduction.rs @@ -9,8 +9,7 @@ use rustc_hir::{BindingMode, Expr, ExprKind, FnRetTy, Param, PatKind, QPath, Saf use rustc_infer::infer::TyCtxtInferExt; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{ - self, Binder, ClosureKind, FnSig, GenericArg, GenericArgKind, List, Region, Ty, TypeVisitableExt, - TypeckResults, + self, Binder, ClosureKind, FnSig, GenericArg, GenericArgKind, List, Region, Ty, TypeVisitableExt, TypeckResults, }; use rustc_session::declare_lint_pass; use rustc_span::symbol::sym; @@ -204,11 +203,16 @@ fn check_clousure<'tcx>(cx: &LateContext<'tcx>, outer_receiver: Option<&Expr<'tc // 'cuz currently nothing changes after deleting this check. local_used_in(cx, l, args) || local_used_after_expr(cx, l, expr) }) { - match cx.tcx.infer_ctxt().build(cx.typing_mode()).err_ctxt().type_implements_fn_trait( - cx.param_env, - Binder::bind_with_vars(callee_ty_adjusted, List::empty()), - ty::PredicatePolarity::Positive, - ) { + match cx + .tcx + .infer_ctxt() + .build(cx.typing_mode()) + .err_ctxt() + .type_implements_fn_trait( + cx.param_env, + Binder::bind_with_vars(callee_ty_adjusted, List::empty()), + ty::PredicatePolarity::Positive, + ) { // Mutable closure is used after current expr; we cannot consume it. Ok((ClosureKind::FnMut, _)) => snippet = format!("&mut {snippet}"), Ok((ClosureKind::Fn, _)) if !callee_ty_raw.is_ref() => { diff --git a/clippy_lints/src/explicit_write.rs b/clippy_lints/src/explicit_write.rs index 4e4434ec7d19c..0550c22761a56 100644 --- a/clippy_lints/src/explicit_write.rs +++ b/clippy_lints/src/explicit_write.rs @@ -58,7 +58,7 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitWrite { // match call to write_fmt && let ExprKind::MethodCall(write_fun, write_recv, [write_arg], _) = *look_in_block(cx, &write_call.kind) && let ExprKind::Call(write_recv_path, []) = write_recv.kind - && write_fun.ident.name == sym!(write_fmt) + && write_fun.ident.name.as_str() == "write_fmt" && let Some(def_id) = path_def_id(cx, write_recv_path) { // match calls to std::io::stdout() / std::io::stderr () diff --git a/clippy_lints/src/from_over_into.rs b/clippy_lints/src/from_over_into.rs index d5a2e06863dc5..14da0b515a51c 100644 --- a/clippy_lints/src/from_over_into.rs +++ b/clippy_lints/src/from_over_into.rs @@ -1,3 +1,5 @@ +use std::ops::ControlFlow; + use clippy_config::Conf; use clippy_config::msrvs::{self, Msrv}; use clippy_utils::diagnostics::span_lint_and_then; @@ -115,25 +117,26 @@ impl<'tcx> LateLintPass<'tcx> for FromOverInto { } /// Finds the occurrences of `Self` and `self` +/// +/// Returns `ControlFlow::break` if any of the `self`/`Self` usages were from an expansion, or the +/// body contained a binding already named `val`. struct SelfFinder<'a, 'tcx> { cx: &'a LateContext<'tcx>, /// Occurrences of `Self` upper: Vec, /// Occurrences of `self` lower: Vec, - /// If any of the `self`/`Self` usages were from an expansion, or the body contained a binding - /// already named `val` - invalid: bool, } impl<'tcx> Visitor<'tcx> for SelfFinder<'_, 'tcx> { + type Result = ControlFlow<()>; type NestedFilter = OnlyBodies; fn nested_visit_map(&mut self) -> Self::Map { self.cx.tcx.hir() } - fn visit_path(&mut self, path: &Path<'tcx>, _id: HirId) { + fn visit_path(&mut self, path: &Path<'tcx>, _id: HirId) -> Self::Result { for segment in path.segments { match segment.ident.name { kw::SelfLower => self.lower.push(segment.ident.span), @@ -141,17 +144,19 @@ impl<'tcx> Visitor<'tcx> for SelfFinder<'_, 'tcx> { _ => continue, } - self.invalid |= segment.ident.span.from_expansion(); + if segment.ident.span.from_expansion() { + return ControlFlow::Break(()); + } } - if !self.invalid { - walk_path(self, path); - } + walk_path(self, path) } - fn visit_name(&mut self, name: Symbol) { + fn visit_name(&mut self, name: Symbol) -> Self::Result { if name == sym::val { - self.invalid = true; + ControlFlow::Break(()) + } else { + ControlFlow::Continue(()) } } } @@ -209,11 +214,9 @@ fn convert_to_from( cx, upper: Vec::new(), lower: Vec::new(), - invalid: false, }; - finder.visit_expr(body.value); - if finder.invalid { + if finder.visit_expr(body.value).is_break() { return None; } diff --git a/clippy_lints/src/from_raw_with_void_ptr.rs b/clippy_lints/src/from_raw_with_void_ptr.rs index d62d008d480f7..c8828c9361576 100644 --- a/clippy_lints/src/from_raw_with_void_ptr.rs +++ b/clippy_lints/src/from_raw_with_void_ptr.rs @@ -41,7 +41,7 @@ impl LateLintPass<'_> for FromRawWithVoidPtr { fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { if let ExprKind::Call(box_from_raw, [arg]) = expr.kind && let ExprKind::Path(QPath::TypeRelative(ty, seg)) = box_from_raw.kind - && seg.ident.name == sym!(from_raw) + && seg.ident.name.as_str() == "from_raw" && let Some(type_str) = path_def_id(cx, ty).and_then(|id| def_id_matches_type(cx, id)) && let arg_kind = cx.typeck_results().expr_ty(arg).kind() && let ty::RawPtr(ty, _) = arg_kind diff --git a/clippy_lints/src/functions/mod.rs b/clippy_lints/src/functions/mod.rs index 91d73c2a9c962..be3d0f7ad6343 100644 --- a/clippy_lints/src/functions/mod.rs +++ b/clippy_lints/src/functions/mod.rs @@ -441,7 +441,7 @@ declare_clippy_lint! { /// fn bar(&self) -> Option<&String> { None } /// # } /// ``` - #[clippy::version = "1.82.0"] + #[clippy::version = "1.83.0"] pub REF_OPTION, pedantic, "function signature uses `&Option` instead of `Option<&T>`" diff --git a/clippy_lints/src/functions/ref_option.rs b/clippy_lints/src/functions/ref_option.rs index 373ecd74cb379..aba0fbcb9feb3 100644 --- a/clippy_lints/src/functions/ref_option.rs +++ b/clippy_lints/src/functions/ref_option.rs @@ -15,9 +15,9 @@ use rustc_span::{Span, sym}; fn check_ty<'a>(cx: &LateContext<'a>, param: &rustc_hir::Ty<'a>, param_ty: Ty<'a>, fixes: &mut Vec<(Span, String)>) { if let ty::Ref(_, opt_ty, Mutability::Not) = param_ty.kind() && is_type_diagnostic_item(cx, *opt_ty, sym::Option) - && let ty::Adt(_, opt_gen) = opt_ty.kind() - && let [gen] = opt_gen.as_slice() - && let GenericArgKind::Type(gen_ty) = gen.unpack() + && let ty::Adt(_, opt_gen_args) = opt_ty.kind() + && let [gen_arg] = opt_gen_args.as_slice() + && let GenericArgKind::Type(gen_ty) = gen_arg.unpack() && !gen_ty.is_ref() // Need to gen the original spans, so first parsing mid, and hir parsing afterward && let hir::TyKind::Ref(lifetime, hir::MutTy { ty, .. }) = param.kind diff --git a/clippy_lints/src/implicit_hasher.rs b/clippy_lints/src/implicit_hasher.rs index 4c5375730b8e3..c370f206c8f83 100644 --- a/clippy_lints/src/implicit_hasher.rs +++ b/clippy_lints/src/implicit_hasher.rs @@ -340,7 +340,7 @@ impl<'tcx> Visitor<'tcx> for ImplicitHasherConstructorVisitor<'_, '_, 'tcx> { if self.cx.tcx.is_diagnostic_item(sym::HashMap, ty_did) { if method.ident.name == sym::new { self.suggestions.insert(e.span, "HashMap::default()".to_string()); - } else if method.ident.name == sym!(with_capacity) { + } else if method.ident.name.as_str() == "with_capacity" { self.suggestions.insert( e.span, format!( @@ -352,7 +352,7 @@ impl<'tcx> Visitor<'tcx> for ImplicitHasherConstructorVisitor<'_, '_, 'tcx> { } else if self.cx.tcx.is_diagnostic_item(sym::HashSet, ty_did) { if method.ident.name == sym::new { self.suggestions.insert(e.span, "HashSet::default()".to_string()); - } else if method.ident.name == sym!(with_capacity) { + } else if method.ident.name.as_str() == "with_capacity" { self.suggestions.insert( e.span, format!( diff --git a/clippy_lints/src/infinite_iter.rs b/clippy_lints/src/infinite_iter.rs index 71c317552ee1a..48874d6064b6b 100644 --- a/clippy_lints/src/infinite_iter.rs +++ b/clippy_lints/src/infinite_iter.rs @@ -157,7 +157,7 @@ fn is_infinite(cx: &LateContext<'_>, expr: &Expr<'_>) -> Finiteness { .and(cap); } } - if method.ident.name == sym!(flat_map) && args.len() == 1 { + if method.ident.name.as_str() == "flat_map" && args.len() == 1 { if let ExprKind::Closure(&Closure { body, .. }) = args[0].kind { let body = cx.tcx.hir().body(body); return is_infinite(cx, body.value); @@ -224,7 +224,7 @@ fn complete_infinite_iter(cx: &LateContext<'_>, expr: &Expr<'_>) -> Finiteness { return MaybeInfinite.and(is_infinite(cx, receiver)); } } - if method.ident.name == sym!(last) && args.is_empty() { + if method.ident.name.as_str() == "last" && args.is_empty() { let not_double_ended = cx .tcx .get_diagnostic_item(sym::DoubleEndedIterator) @@ -234,7 +234,7 @@ fn complete_infinite_iter(cx: &LateContext<'_>, expr: &Expr<'_>) -> Finiteness { if not_double_ended { return is_infinite(cx, receiver); } - } else if method.ident.name == sym!(collect) { + } else if method.ident.name.as_str() == "collect" { let ty = cx.typeck_results().expr_ty(expr); if matches!( get_type_diagnostic_name(cx, ty), diff --git a/clippy_lints/src/iter_without_into_iter.rs b/clippy_lints/src/iter_without_into_iter.rs index 1e6404190d3cf..314d0dfa26ce2 100644 --- a/clippy_lints/src/iter_without_into_iter.rs +++ b/clippy_lints/src/iter_without_into_iter.rs @@ -142,7 +142,7 @@ impl LateLintPass<'_> for IterWithoutIntoIter { ty.peel_refs().is_slice() || get_adt_inherent_method(cx, ty, expected_method_name).is_some() }) && let Some(iter_assoc_span) = imp.items.iter().find_map(|item| { - if item.ident.name == sym!(IntoIter) { + if item.ident.name.as_str() == "IntoIter" { Some(cx.tcx.hir().impl_item(item.id).expect_type().span) } else { None @@ -247,8 +247,8 @@ impl {self_ty_without_ref} {{ let sugg = format!( " impl IntoIterator for {self_ty_snippet} {{ - type IntoIter = {ret_ty}; type Item = {iter_ty}; + type IntoIter = {ret_ty}; fn into_iter(self) -> Self::IntoIter {{ self.iter() }} diff --git a/clippy_lints/src/large_const_arrays.rs b/clippy_lints/src/large_const_arrays.rs index 5d2b521b25055..aa8b4d88d2f27 100644 --- a/clippy_lints/src/large_const_arrays.rs +++ b/clippy_lints/src/large_const_arrays.rs @@ -4,7 +4,7 @@ use rustc_errors::Applicability; use rustc_hir::{Item, ItemKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::layout::LayoutOf; -use rustc_middle::ty::{self, ConstKind}; +use rustc_middle::ty::{self, ParamEnv}; use rustc_session::impl_lint_pass; use rustc_span::{BytePos, Pos, Span}; @@ -56,7 +56,7 @@ impl<'tcx> LateLintPass<'tcx> for LargeConstArrays { && !item.span.from_expansion() && let ty = cx.tcx.type_of(item.owner_id).instantiate_identity() && let ty::Array(element_type, cst) = ty.kind() - && let ConstKind::Value(_, ty::ValTree::Leaf(element_count)) = cst.kind() + && let Ok((_, ty::ValTree::Leaf(element_count))) = cst.eval_valtree(cx.tcx, ParamEnv::empty(), item.span) && let element_count = element_count.to_target_usize(cx.tcx) && let Ok(element_size) = cx.layout_of(*element_type).map(|l| l.size.bytes()) && u128::from(self.maximum_allowed_size) < u128::from(element_count) * u128::from(element_size) diff --git a/clippy_lints/src/large_include_file.rs b/clippy_lints/src/large_include_file.rs index f2f841dcec33d..ab3d19f89c3fc 100644 --- a/clippy_lints/src/large_include_file.rs +++ b/clippy_lints/src/large_include_file.rs @@ -1,11 +1,12 @@ use clippy_config::Conf; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::macros::root_macro_call_first_node; -use rustc_ast::LitKind; +use clippy_utils::source::snippet_opt; +use rustc_ast::{AttrArgs, AttrArgsEq, AttrKind, Attribute, LitKind}; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::impl_lint_pass; -use rustc_span::sym; +use rustc_span::{Span, sym}; declare_clippy_lint! { /// ### What it does @@ -51,6 +52,24 @@ impl LargeIncludeFile { impl_lint_pass!(LargeIncludeFile => [LARGE_INCLUDE_FILE]); +impl LargeIncludeFile { + fn emit_lint(&self, cx: &LateContext<'_>, span: Span) { + #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] + span_lint_and_then( + cx, + LARGE_INCLUDE_FILE, + span, + "attempted to include a large file", + |diag| { + diag.note(format!( + "the configuration allows a maximum size of {} bytes", + self.max_file_size + )); + }, + ); + } +} + impl LateLintPass<'_> for LargeIncludeFile { fn check_expr(&mut self, cx: &LateContext<'_>, expr: &'_ Expr<'_>) { if let ExprKind::Lit(lit) = &expr.kind @@ -66,19 +85,33 @@ impl LateLintPass<'_> for LargeIncludeFile { && (cx.tcx.is_diagnostic_item(sym::include_bytes_macro, macro_call.def_id) || cx.tcx.is_diagnostic_item(sym::include_str_macro, macro_call.def_id)) { - #[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")] - span_lint_and_then( - cx, - LARGE_INCLUDE_FILE, - expr.span.source_callsite(), - "attempted to include a large file", - |diag| { - diag.note(format!( - "the configuration allows a maximum size of {} bytes", - self.max_file_size - )); - }, - ); + self.emit_lint(cx, expr.span.source_callsite()); + } + } + + fn check_attribute(&mut self, cx: &LateContext<'_>, attr: &Attribute) { + if !attr.span.from_expansion() + // Currently, rustc limits the usage of macro at the top-level of attributes, + // so we don't need to recurse into each level. + && let AttrKind::Normal(ref normal) = attr.kind + && let AttrArgs::Eq(_, AttrArgsEq::Hir(ref meta)) = normal.item.args + && !attr.span.contains(meta.span) + // Since the `include_str` is already expanded at this point, we can only take the + // whole attribute snippet and then modify for our suggestion. + && let Some(snippet) = snippet_opt(cx, attr.span) + // We cannot remove this because a `#[doc = include_str!("...")]` attribute can + // occupy several lines. + && let Some(start) = snippet.find('[') + && let Some(end) = snippet.rfind(']') + && let snippet = &snippet[start + 1..end] + // We check that the expansion actually comes from `include_str!` and not just from + // another macro. + && let Some(sub_snippet) = snippet.trim().strip_prefix("doc") + && let Some(sub_snippet) = sub_snippet.trim().strip_prefix("=") + && let sub_snippet = sub_snippet.trim() + && (sub_snippet.starts_with("include_str!") || sub_snippet.starts_with("include_bytes!")) + { + self.emit_lint(cx, attr.span); } } } diff --git a/clippy_lints/src/len_zero.rs b/clippy_lints/src/len_zero.rs index 47c65ee6d0ba9..b7887ef76a537 100644 --- a/clippy_lints/src/len_zero.rs +++ b/clippy_lints/src/len_zero.rs @@ -629,7 +629,7 @@ fn has_is_empty(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { .filter_by_name_unhygienic(is_empty) .any(|item| is_is_empty(cx, item)) }), - ty::Alias(ty::Projection, ref proj) => has_is_empty_impl(cx, proj.def_id), + ty::Alias(ty::Projection, proj) => has_is_empty_impl(cx, proj.def_id), ty::Adt(id, _) => has_is_empty_impl(cx, id.did()), ty::Array(..) | ty::Slice(..) | ty::Str => true, _ => false, diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index 3fd07ced0e4ae..c9064df25ac89 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -73,6 +73,7 @@ pub mod deprecated_lints; mod absolute_paths; mod almost_complete_range; mod approx_const; +mod arbitrary_source_item_ordering; mod arc_with_non_send_sync; mod as_conversions; mod asm_syntax; @@ -400,6 +401,7 @@ use clippy_config::{Conf, get_configuration_metadata, sanitize_explanation}; use clippy_utils::macros::FormatArgsStorage; use rustc_data_structures::fx::FxHashSet; use rustc_lint::{Lint, LintId}; +use utils::attr_collector::{AttrCollector, AttrStorage}; /// Register all pre expansion lints /// @@ -464,6 +466,7 @@ pub(crate) enum LintCategory { #[cfg(feature = "internal")] Internal, } + #[allow(clippy::enum_glob_use)] use LintCategory::*; @@ -585,6 +588,10 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { )) }); + let attr_storage = AttrStorage::default(); + let attrs = attr_storage.clone(); + store.register_early_pass(move || Box::new(AttrCollector::new(attrs.clone()))); + // all the internal lints #[cfg(feature = "internal")] { @@ -606,6 +613,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { store.register_late_pass(|_| { Box::new(utils::internal_lints::almost_standard_lint_formulation::AlmostStandardFormulation::new()) }); + store.register_late_pass(|_| Box::new(utils::internal_lints::slow_symbol_comparisons::SlowSymbolComparisons)); } store.register_late_pass(|_| Box::new(ctfe::ClippyCtfe)); @@ -795,7 +803,8 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { store.register_late_pass(|_| Box::new(unwrap_in_result::UnwrapInResult)); store.register_late_pass(|_| Box::new(semicolon_if_nothing_returned::SemicolonIfNothingReturned)); store.register_late_pass(|_| Box::new(async_yields_async::AsyncYieldsAsync)); - store.register_late_pass(move |tcx| Box::new(disallowed_macros::DisallowedMacros::new(tcx, conf))); + let attrs = attr_storage.clone(); + store.register_late_pass(move |tcx| Box::new(disallowed_macros::DisallowedMacros::new(tcx, conf, attrs.clone()))); store.register_late_pass(move |tcx| Box::new(disallowed_methods::DisallowedMethods::new(tcx, conf))); store.register_early_pass(|| Box::new(asm_syntax::InlineAsmX86AttSyntax)); store.register_early_pass(|| Box::new(asm_syntax::InlineAsmX86IntelSyntax)); @@ -953,5 +962,6 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) { store.register_late_pass(move |_| Box::new(unused_trait_names::UnusedTraitNames::new(conf))); store.register_late_pass(|_| Box::new(manual_ignore_case_cmp::ManualIgnoreCaseCmp)); store.register_late_pass(|_| Box::new(unnecessary_literal_bound::UnnecessaryLiteralBound)); + store.register_late_pass(move |_| Box::new(arbitrary_source_item_ordering::ArbitrarySourceItemOrdering::new(conf))); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/clippy_lints/src/loops/explicit_iter_loop.rs b/clippy_lints/src/loops/explicit_iter_loop.rs index 119e410b91c71..ee561ea85ed1f 100644 --- a/clippy_lints/src/loops/explicit_iter_loop.rs +++ b/clippy_lints/src/loops/explicit_iter_loop.rs @@ -29,11 +29,7 @@ pub(super) fn check( if !msrv.meets(msrvs::ARRAY_INTO_ITERATOR) { return; } - } else if count - .try_to_target_usize(cx.tcx) - .map_or(true, |x| x > 32) - && !msrv.meets(msrvs::ARRAY_IMPL_ANY_LEN) - { + } else if count.try_to_target_usize(cx.tcx).map_or(true, |x| x > 32) && !msrv.meets(msrvs::ARRAY_IMPL_ANY_LEN) { return; } } diff --git a/clippy_lints/src/loops/infinite_loop.rs b/clippy_lints/src/loops/infinite_loop.rs index e25c03db53442..9f543b79bac44 100644 --- a/clippy_lints/src/loops/infinite_loop.rs +++ b/clippy_lints/src/loops/infinite_loop.rs @@ -1,12 +1,13 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::{fn_def_id, is_from_proc_macro, is_lint_allowed}; use hir::intravisit::{Visitor, walk_expr}; -use hir::{Expr, ExprKind, FnRetTy, FnSig, Node}; +use hir::{Expr, ExprKind, FnRetTy, FnSig, Node, TyKind}; use rustc_ast::Label; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_lint::{LateContext, LintContext}; use rustc_middle::lint::in_external_macro; +use rustc_span::sym; use super::INFINITE_LOOP; @@ -25,13 +26,7 @@ pub(super) fn check<'tcx>( return; }; // Or, its parent function is already returning `Never` - if matches!( - parent_fn_ret, - FnRetTy::Return(hir::Ty { - kind: hir::TyKind::Never, - .. - }) - ) { + if is_never_return(parent_fn_ret) { return; } @@ -69,6 +64,16 @@ pub(super) fn check<'tcx>( fn get_parent_fn_ret_ty<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>) -> Option> { for (_, parent_node) in cx.tcx.hir().parent_iter(expr.hir_id) { match parent_node { + // Skip `Coroutine` closures, these are the body of `async fn`, not async closures. + // This is because we still need to backtrack one parent node to get the `OpaqueDef` ty. + Node::Expr(Expr { + kind: + ExprKind::Closure(hir::Closure { + kind: hir::ClosureKind::Coroutine(_), + .. + }), + .. + }) => (), Node::Item(hir::Item { kind: hir::ItemKind::Fn(FnSig { decl, .. }, _, _), .. @@ -143,3 +148,41 @@ impl<'hir> Visitor<'hir> for LoopVisitor<'hir, '_> { } } } + +/// Return `true` if the given [`FnRetTy`] is never (!). +/// +/// Note: This function also take care of return type of async fn, +/// as the actual type is behind an [`OpaqueDef`](TyKind::OpaqueDef). +fn is_never_return(ret_ty: FnRetTy<'_>) -> bool { + let FnRetTy::Return(hir_ty) = ret_ty else { return false }; + + match hir_ty.kind { + TyKind::Never => true, + TyKind::OpaqueDef(hir::OpaqueTy { + origin: hir::OpaqueTyOrigin::AsyncFn { .. }, + bounds, + .. + }) => { + if let Some(trait_ref) = bounds.iter().find_map(|b| b.trait_ref()) + && let Some(segment) = trait_ref + .path + .segments + .iter() + .find(|seg| seg.ident.name == sym::future_trait) + && let Some(args) = segment.args + && let Some(cst_kind) = args + .constraints + .iter() + .find_map(|cst| (cst.ident.name == sym::Output).then_some(cst.kind)) + && let hir::AssocItemConstraintKind::Equality { + term: hir::Term::Ty(ty), + } = cst_kind + { + matches!(ty.kind, TyKind::Never) + } else { + false + } + }, + _ => false, + } +} diff --git a/clippy_lints/src/loops/while_immutable_condition.rs b/clippy_lints/src/loops/while_immutable_condition.rs index 1a1cde3c5bd63..7da4fa76e2c7f 100644 --- a/clippy_lints/src/loops/while_immutable_condition.rs +++ b/clippy_lints/src/loops/while_immutable_condition.rs @@ -19,10 +19,8 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, cond: &'tcx Expr<'_>, expr: &' cx, ids: HirIdSet::default(), def_ids: DefIdMap::default(), - skip: false, }; - var_visitor.visit_expr(cond); - if var_visitor.skip { + if var_visitor.visit_expr(cond).is_break() { return; } let used_in_condition = &var_visitor.ids; @@ -81,7 +79,6 @@ struct VarCollectorVisitor<'a, 'tcx> { cx: &'a LateContext<'tcx>, ids: HirIdSet, def_ids: DefIdMap, - skip: bool, } impl<'tcx> VarCollectorVisitor<'_, 'tcx> { @@ -104,11 +101,15 @@ impl<'tcx> VarCollectorVisitor<'_, 'tcx> { } impl<'tcx> Visitor<'tcx> for VarCollectorVisitor<'_, 'tcx> { - fn visit_expr(&mut self, ex: &'tcx Expr<'_>) { + type Result = ControlFlow<()>; + fn visit_expr(&mut self, ex: &'tcx Expr<'_>) -> Self::Result { match ex.kind { - ExprKind::Path(_) => self.insert_def_id(ex), + ExprKind::Path(_) => { + self.insert_def_id(ex); + ControlFlow::Continue(()) + }, // If there is any function/method call… we just stop analysis - ExprKind::Call(..) | ExprKind::MethodCall(..) => self.skip = true, + ExprKind::Call(..) | ExprKind::MethodCall(..) => ControlFlow::Break(()), _ => walk_expr(self, ex), } diff --git a/clippy_lints/src/loops/while_let_on_iterator.rs b/clippy_lints/src/loops/while_let_on_iterator.rs index 74467522619e9..b7e37c1a876f8 100644 --- a/clippy_lints/src/loops/while_let_on_iterator.rs +++ b/clippy_lints/src/loops/while_let_on_iterator.rs @@ -1,3 +1,5 @@ +use std::ops::ControlFlow; + use super::WHILE_LET_ON_ITERATOR; use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_with_applicability; @@ -204,35 +206,32 @@ fn uses_iter<'tcx>(cx: &LateContext<'tcx>, iter_expr: &IterExpr, container: &'tc struct V<'a, 'b, 'tcx> { cx: &'a LateContext<'tcx>, iter_expr: &'b IterExpr, - uses_iter: bool, } impl<'tcx> Visitor<'tcx> for V<'_, '_, 'tcx> { - 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; + type Result = ControlFlow<()>; + fn visit_expr(&mut self, e: &'tcx Expr<'_>) -> Self::Result { + if is_expr_same_child_or_parent_field(self.cx, e, &self.iter_expr.fields, self.iter_expr.path) { + ControlFlow::Break(()) } else if let (e, true) = skip_fields_and_path(e) { if let Some(e) = e { - self.visit_expr(e); + self.visit_expr(e) + } else { + ControlFlow::Continue(()) } } else if let ExprKind::Closure(&Closure { body: id, .. }) = e.kind { if is_res_used(self.cx, self.iter_expr.path, id) { - self.uses_iter = true; + ControlFlow::Break(()) + } else { + ControlFlow::Continue(()) } } else { - walk_expr(self, e); + walk_expr(self, e) } } } - let mut v = V { - cx, - iter_expr, - uses_iter: false, - }; - v.visit_expr(container); - v.uses_iter + let mut v = V { cx, iter_expr }; + v.visit_expr(container).is_break() } #[expect(clippy::too_many_lines)] @@ -242,34 +241,38 @@ fn needs_mutable_borrow(cx: &LateContext<'_>, iter_expr: &IterExpr, loop_expr: & iter_expr: &'b IterExpr, loop_id: HirId, after_loop: bool, - used_iter: bool, } impl<'tcx> Visitor<'tcx> for AfterLoopVisitor<'_, '_, 'tcx> { type NestedFilter = OnlyBodies; + type Result = ControlFlow<()>; fn nested_visit_map(&mut self) -> Self::Map { self.cx.tcx.hir() } - fn visit_expr(&mut self, e: &'tcx Expr<'_>) { - if self.used_iter { - return; - } + fn visit_expr(&mut self, e: &'tcx Expr<'_>) -> Self::Result { 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; + ControlFlow::Break(()) } else if let (e, true) = skip_fields_and_path(e) { if let Some(e) = e { - self.visit_expr(e); + self.visit_expr(e) + } else { + ControlFlow::Continue(()) } } else if let ExprKind::Closure(&Closure { body: id, .. }) = e.kind { - self.used_iter = is_res_used(self.cx, self.iter_expr.path, id); + if is_res_used(self.cx, self.iter_expr.path, id) { + ControlFlow::Break(()) + } else { + ControlFlow::Continue(()) + } } else { - walk_expr(self, e); + walk_expr(self, e) } } else if self.loop_id == e.hir_id { self.after_loop = true; + ControlFlow::Continue(()) } else { - walk_expr(self, e); + walk_expr(self, e) } } } @@ -347,9 +350,8 @@ fn needs_mutable_borrow(cx: &LateContext<'_>, iter_expr: &IterExpr, loop_expr: & 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 + v.visit_expr(cx.tcx.hir().body(cx.enclosing_body.unwrap()).value) + .is_break() } } diff --git a/clippy_lints/src/manual_async_fn.rs b/clippy_lints/src/manual_async_fn.rs index c904137da1a15..3c77db84a4054 100644 --- a/clippy_lints/src/manual_async_fn.rs +++ b/clippy_lints/src/manual_async_fn.rs @@ -106,13 +106,12 @@ impl<'tcx> LateLintPass<'tcx> for ManualAsyncFn { fn future_trait_ref<'tcx>(cx: &LateContext<'tcx>, opaque: &'tcx OpaqueTy<'tcx>) -> Option<&'tcx TraitRef<'tcx>> { if let Some(trait_ref) = opaque.bounds.iter().find_map(|bound| { - if let GenericBound::Trait(poly) = bound { - Some(&poly.trait_ref) - } else { - None - } - }) - && trait_ref.trait_def_id() == cx.tcx.lang_items().future_trait() + if let GenericBound::Trait(poly) = bound { + Some(&poly.trait_ref) + } else { + None + } + }) && trait_ref.trait_def_id() == cx.tcx.lang_items().future_trait() { return Some(trait_ref); } @@ -156,16 +155,18 @@ fn captures_all_lifetimes(cx: &LateContext<'_>, fn_def_id: LocalDefId, opaque_de .tcx .opaque_captured_lifetimes(opaque_def_id) .iter() - .filter(|&(lifetime, _)| match *lifetime { - ResolvedArg::EarlyBound(_) | ResolvedArg::LateBound(ty::INNERMOST, _, _) => true, - _ => false, + .filter(|&(lifetime, _)| { + matches!( + *lifetime, + ResolvedArg::EarlyBound(_) | ResolvedArg::LateBound(ty::INNERMOST, _, _) + ) }) .count(); num_captured_lifetimes == num_early_lifetimes + num_late_lifetimes } fn desugared_async_block<'tcx>(cx: &LateContext<'tcx>, block: &'tcx Block<'tcx>) -> Option<&'tcx Body<'tcx>> { - if let Some(Expr { + if let Some(&Expr { kind: ExprKind::Closure(&Closure { kind, body, .. }), .. }) = block.expr diff --git a/clippy_lints/src/manual_bits.rs b/clippy_lints/src/manual_bits.rs index 1bd8813e3480a..fd71167f814d2 100644 --- a/clippy_lints/src/manual_bits.rs +++ b/clippy_lints/src/manual_bits.rs @@ -7,8 +7,7 @@ use rustc_ast::ast::LitKind; use rustc_data_structures::packed::Pu128; use rustc_errors::Applicability; use rustc_hir::{BinOpKind, Expr, ExprKind, GenericArg, QPath}; -use rustc_lint::{LateContext, LateLintPass, LintContext}; -use rustc_middle::lint::in_external_macro; +use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{self, Ty}; use rustc_session::impl_lint_pass; use rustc_span::sym; @@ -53,7 +52,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualBits { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { if let ExprKind::Binary(bin_op, left_expr, right_expr) = expr.kind && let BinOpKind::Mul = &bin_op.node - && !in_external_macro(cx.sess(), expr.span) + && !expr.span.from_expansion() && self.msrv.meets(msrvs::MANUAL_BITS) && let ctxt = expr.span.ctxt() && left_expr.span.ctxt() == ctxt diff --git a/clippy_lints/src/manual_hash_one.rs b/clippy_lints/src/manual_hash_one.rs index c62bd65bea683..7a9c99637428a 100644 --- a/clippy_lints/src/manual_hash_one.rs +++ b/clippy_lints/src/manual_hash_one.rs @@ -68,7 +68,7 @@ impl LateLintPass<'_> for ManualHashOne { && let Some(init) = local.init && !init.span.from_expansion() && let ExprKind::MethodCall(seg, build_hasher, [], _) = init.kind - && seg.ident.name == sym!(build_hasher) + && seg.ident.name.as_str() == "build_hasher" && let Node::Stmt(local_stmt) = cx.tcx.parent_hir_node(local.hir_id) && let Node::Block(block) = cx.tcx.parent_hir_node(local_stmt.hir_id) @@ -96,7 +96,7 @@ impl LateLintPass<'_> for ManualHashOne { && let Node::Expr(finish_expr) = cx.tcx.parent_hir_node(path_expr.hir_id) && !finish_expr.span.from_expansion() && let ExprKind::MethodCall(seg, _, [], _) = finish_expr.kind - && seg.ident.name == sym!(finish) + && seg.ident.name.as_str() == "finish" && self.msrv.meets(msrvs::BUILD_HASHER_HASH_ONE) { diff --git a/clippy_lints/src/manual_is_ascii_check.rs b/clippy_lints/src/manual_is_ascii_check.rs index 24207705743d1..dec8c5d85dec2 100644 --- a/clippy_lints/src/manual_is_ascii_check.rs +++ b/clippy_lints/src/manual_is_ascii_check.rs @@ -105,7 +105,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualIsAsciiCheck { check_is_ascii(cx, macro_call.span, recv, &range, None); } } else if let ExprKind::MethodCall(path, receiver, [arg], ..) = expr.kind - && path.ident.name == sym!(contains) + && path.ident.name.as_str() == "contains" && let Some(higher::Range { start: Some(start), end: Some(end), diff --git a/clippy_lints/src/matches/match_like_matches.rs b/clippy_lints/src/matches/match_like_matches.rs index 64cb7a06ce9c1..6d62b530ae7e8 100644 --- a/clippy_lints/src/matches/match_like_matches.rs +++ b/clippy_lints/src/matches/match_like_matches.rs @@ -148,7 +148,7 @@ fn find_bool_lit(ex: &ExprKind<'_>) -> Option { }) => Some(*b), ExprKind::Block( rustc_hir::Block { - stmts: &[], + stmts: [], expr: Some(exp), .. }, diff --git a/clippy_lints/src/matches/match_str_case_mismatch.rs b/clippy_lints/src/matches/match_str_case_mismatch.rs index 463aa602bc8b9..1267fc9d0a531 100644 --- a/clippy_lints/src/matches/match_str_case_mismatch.rs +++ b/clippy_lints/src/matches/match_str_case_mismatch.rs @@ -1,3 +1,5 @@ +use std::ops::ControlFlow; + use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::ty::is_type_lang_item; use rustc_ast::ast::LitKind; @@ -23,11 +25,8 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, scrutinee: &'tcx Expr<'_>, arm if let ty::Ref(_, ty, _) = cx.typeck_results().expr_ty(scrutinee).kind() && let ty::Str = ty.kind() { - let mut visitor = MatchExprVisitor { cx, case_method: None }; - - visitor.visit_expr(scrutinee); - - if let Some(case_method) = visitor.case_method { + let mut visitor = MatchExprVisitor { cx }; + if let ControlFlow::Break(case_method) = visitor.visit_expr(scrutinee) { if let Some((bad_case_span, bad_case_sym)) = verify_case(&case_method, arms) { lint(cx, &case_method, bad_case_span, bad_case_sym.as_str()); } @@ -37,30 +36,33 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, scrutinee: &'tcx Expr<'_>, arm struct MatchExprVisitor<'a, 'tcx> { cx: &'a LateContext<'tcx>, - case_method: Option, } impl<'tcx> Visitor<'tcx> for MatchExprVisitor<'_, 'tcx> { - fn visit_expr(&mut self, ex: &'tcx Expr<'_>) { - match ex.kind { - ExprKind::MethodCall(segment, receiver, [], _) if self.case_altered(segment.ident.as_str(), receiver) => {}, - _ => walk_expr(self, ex), + type Result = ControlFlow; + fn visit_expr(&mut self, ex: &'tcx Expr<'_>) -> Self::Result { + if let ExprKind::MethodCall(segment, receiver, [], _) = ex.kind { + let result = self.case_altered(segment.ident.as_str(), receiver); + if result.is_break() { + return result; + } } + + walk_expr(self, ex) } } impl MatchExprVisitor<'_, '_> { - fn case_altered(&mut self, segment_ident: &str, receiver: &Expr<'_>) -> bool { + fn case_altered(&mut self, segment_ident: &str, receiver: &Expr<'_>) -> ControlFlow { if let Some(case_method) = get_case_method(segment_ident) { let ty = self.cx.typeck_results().expr_ty(receiver).peel_refs(); if is_type_lang_item(self.cx, ty, LangItem::String) || ty.kind() == &ty::Str { - self.case_method = Some(case_method); - return true; + return ControlFlow::Break(case_method); } } - false + ControlFlow::Continue(()) } } diff --git a/clippy_lints/src/matches/redundant_guards.rs b/clippy_lints/src/matches/redundant_guards.rs index 42d9efe4ff6fd..9e54475033c86 100644 --- a/clippy_lints/src/matches/redundant_guards.rs +++ b/clippy_lints/src/matches/redundant_guards.rs @@ -10,7 +10,7 @@ use rustc_hir::def::{DefKind, Res}; use rustc_hir::{Arm, BinOpKind, Expr, ExprKind, MatchSource, Node, PatKind, UnOp}; use rustc_lint::LateContext; use rustc_span::symbol::Ident; -use rustc_span::{Span, Symbol, sym}; +use rustc_span::{Span, sym}; use std::borrow::Cow; use std::ops::ControlFlow; @@ -95,7 +95,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'tcx>], msrv: } else if let ExprKind::MethodCall(path, recv, args, ..) = guard.kind && let Some(binding) = get_pat_binding(cx, recv, outer_arm) { - check_method_calls(cx, outer_arm, path.ident.name, recv, args, guard, &binding); + check_method_calls(cx, outer_arm, path.ident.name.as_str(), recv, args, guard, &binding); } } } @@ -103,7 +103,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'tcx>], msrv: fn check_method_calls<'tcx>( cx: &LateContext<'tcx>, arm: &Arm<'tcx>, - method: Symbol, + method: &str, recv: &Expr<'_>, args: &[Expr<'_>], if_expr: &Expr<'_>, @@ -112,7 +112,7 @@ fn check_method_calls<'tcx>( let ty = cx.typeck_results().expr_ty(recv).peel_refs(); let slice_like = ty.is_slice() || ty.is_array(); - let sugg = if method == sym!(is_empty) { + let sugg = if method == "is_empty" { // `s if s.is_empty()` becomes "" // `arr if arr.is_empty()` becomes [] @@ -137,9 +137,9 @@ fn check_method_calls<'tcx>( if needles.is_empty() { sugg.insert_str(1, ".."); - } else if method == sym!(starts_with) { + } else if method == "starts_with" { sugg.insert_str(sugg.len() - 1, ", .."); - } else if method == sym!(ends_with) { + } else if method == "ends_with" { sugg.insert_str(1, ".., "); } else { return; diff --git a/clippy_lints/src/matches/redundant_pattern_match.rs b/clippy_lints/src/matches/redundant_pattern_match.rs index 9ca75fb26155a..ca45ed6ea59c8 100644 --- a/clippy_lints/src/matches/redundant_pattern_match.rs +++ b/clippy_lints/src/matches/redundant_pattern_match.rs @@ -319,10 +319,9 @@ fn found_good_method<'tcx>( node: (&PatKind<'_>, &PatKind<'_>), ) -> Option<(&'static str, Option<&'tcx Expr<'tcx>>)> { match node { - ( - PatKind::TupleStruct(ref path_left, patterns_left, _), - PatKind::TupleStruct(ref path_right, patterns_right, _), - ) if patterns_left.len() == 1 && patterns_right.len() == 1 => { + (PatKind::TupleStruct(path_left, patterns_left, _), PatKind::TupleStruct(path_right, patterns_right, _)) + if patterns_left.len() == 1 && patterns_right.len() == 1 => + { if let (PatKind::Wild, PatKind::Wild) = (&patterns_left[0].kind, &patterns_right[0].kind) { find_good_method_for_match( cx, @@ -350,8 +349,8 @@ fn found_good_method<'tcx>( None } }, - (PatKind::TupleStruct(ref path_left, patterns, _), PatKind::Path(ref path_right)) - | (PatKind::Path(ref path_left), PatKind::TupleStruct(ref path_right, patterns, _)) + (PatKind::TupleStruct(path_left, patterns, _), PatKind::Path(path_right)) + | (PatKind::Path(path_left), PatKind::TupleStruct(path_right, patterns, _)) if patterns.len() == 1 => { if let PatKind::Wild = patterns[0].kind { @@ -381,14 +380,14 @@ fn found_good_method<'tcx>( None } }, - (PatKind::TupleStruct(ref path_left, patterns, _), PatKind::Wild) if patterns.len() == 1 => { + (PatKind::TupleStruct(path_left, patterns, _), PatKind::Wild) if patterns.len() == 1 => { if let PatKind::Wild = patterns[0].kind { get_good_method(cx, arms, path_left) } else { None } }, - (PatKind::Path(ref path_left), PatKind::Wild) => get_good_method(cx, arms, path_left), + (PatKind::Path(path_left), PatKind::Wild) => get_good_method(cx, arms, path_left), _ => None, } } diff --git a/clippy_lints/src/matches/single_match.rs b/clippy_lints/src/matches/single_match.rs index 047d070a13153..2aea25b5f12af 100644 --- a/clippy_lints/src/matches/single_match.rs +++ b/clippy_lints/src/matches/single_match.rs @@ -247,7 +247,10 @@ impl<'a> PatState<'a> { let states = match self { Self::Wild => return None, Self::Other => { - *self = Self::StdEnum(cx.arena.alloc_from_iter((0..adt.variants().len()).map(|_| Self::Other))); + *self = Self::StdEnum( + cx.arena + .alloc_from_iter(std::iter::repeat_with(|| Self::Other).take(adt.variants().len())), + ); let Self::StdEnum(x) = self else { unreachable!(); }; diff --git a/clippy_lints/src/methods/filter_map.rs b/clippy_lints/src/methods/filter_map.rs index 06482a743ddf4..c1653b65e9827 100644 --- a/clippy_lints/src/methods/filter_map.rs +++ b/clippy_lints/src/methods/filter_map.rs @@ -21,8 +21,8 @@ fn is_method(cx: &LateContext<'_>, expr: &Expr<'_>, method_name: Symbol) -> bool ExprKind::Path(QPath::TypeRelative(_, mname)) => mname.ident.name == method_name, ExprKind::Path(QPath::Resolved(_, segments)) => segments.segments.last().unwrap().ident.name == method_name, ExprKind::MethodCall(segment, _, _, _) => segment.ident.name == method_name, - ExprKind::Closure(&Closure { body, .. }) => { - let body = cx.tcx.hir().body(body); + ExprKind::Closure(Closure { body, .. }) => { + let body = cx.tcx.hir().body(*body); let closure_expr = peel_blocks(body.value); match closure_expr.kind { ExprKind::MethodCall(PathSegment { ident, .. }, receiver, ..) => { @@ -234,12 +234,12 @@ impl<'tcx> OffendingFilterExpr<'tcx> { // the latter only calls `effect` once let side_effect_expr_span = receiver.can_have_side_effects().then_some(receiver.span); - if cx.tcx.is_diagnostic_item(sym::Option, recv_ty.did()) && path.ident.name == sym!(is_some) { + if cx.tcx.is_diagnostic_item(sym::Option, recv_ty.did()) && path.ident.name.as_str() == "is_some" { Some(Self::IsSome { receiver, side_effect_expr_span, }) - } else if cx.tcx.is_diagnostic_item(sym::Result, recv_ty.did()) && path.ident.name == sym!(is_ok) { + } else if cx.tcx.is_diagnostic_item(sym::Result, recv_ty.did()) && path.ident.name.as_str() == "is_ok" { Some(Self::IsOk { receiver, side_effect_expr_span, diff --git a/clippy_lints/src/methods/is_empty.rs b/clippy_lints/src/methods/is_empty.rs index cc82f6cfd6384..a0c21faaa4cc8 100644 --- a/clippy_lints/src/methods/is_empty.rs +++ b/clippy_lints/src/methods/is_empty.rs @@ -1,6 +1,7 @@ use clippy_utils::consts::ConstEvalCtxt; use clippy_utils::diagnostics::span_lint; -use clippy_utils::{find_binding_init, path_to_local}; +use clippy_utils::macros::{is_assert_macro, root_macro_call}; +use clippy_utils::{find_binding_init, get_parent_expr, is_inside_always_const_context, path_to_local}; use rustc_hir::{Expr, HirId}; use rustc_lint::{LateContext, LintContext}; use rustc_middle::lint::in_external_macro; @@ -14,6 +15,16 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &'_ Expr<'_>, receiver: &Expr<'_ if in_external_macro(cx.sess(), expr.span) || !receiver.span.eq_ctxt(expr.span) { return; } + if let Some(parent) = get_parent_expr(cx, expr) { + if let Some(parent) = get_parent_expr(cx, parent) { + if is_inside_always_const_context(cx.tcx, expr.hir_id) + && let Some(macro_call) = root_macro_call(parent.span) + && is_assert_macro(cx, macro_call.def_id) + { + return; + } + } + } let init_expr = expr_or_init(cx, receiver); if !receiver.span.eq_ctxt(init_expr.span) { return; diff --git a/clippy_lints/src/methods/iter_out_of_bounds.rs b/clippy_lints/src/methods/iter_out_of_bounds.rs index 9d462bd184590..9a62b719a8fb8 100644 --- a/clippy_lints/src/methods/iter_out_of_bounds.rs +++ b/clippy_lints/src/methods/iter_out_of_bounds.rs @@ -29,10 +29,7 @@ fn get_iterator_length<'tcx>(cx: &LateContext<'tcx>, iter: &'tcx Expr<'tcx>) -> if cx.tcx.is_diagnostic_item(sym::ArrayIntoIter, did) { // For array::IntoIter, the length is the second generic // parameter. - substs - .const_at(1) - .try_to_target_usize(cx.tcx) - .map(u128::from) + substs.const_at(1).try_to_target_usize(cx.tcx).map(u128::from) } else if cx.tcx.is_diagnostic_item(sym::SliceIter, did) && let ExprKind::MethodCall(_, recv, ..) = iter.kind { diff --git a/clippy_lints/src/methods/map_all_any_identity.rs b/clippy_lints/src/methods/map_all_any_identity.rs new file mode 100644 index 0000000000000..ac11baa2d54ca --- /dev/null +++ b/clippy_lints/src/methods/map_all_any_identity.rs @@ -0,0 +1,43 @@ +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::source::SpanRangeExt; +use clippy_utils::{is_expr_identity_function, is_trait_method}; +use rustc_errors::Applicability; +use rustc_hir::Expr; +use rustc_lint::LateContext; +use rustc_span::{Span, sym}; + +use super::MAP_ALL_ANY_IDENTITY; + +#[allow(clippy::too_many_arguments)] +pub(super) fn check( + cx: &LateContext<'_>, + expr: &Expr<'_>, + recv: &Expr<'_>, + map_call_span: Span, + map_arg: &Expr<'_>, + any_call_span: Span, + any_arg: &Expr<'_>, + method: &str, +) { + if is_trait_method(cx, expr, sym::Iterator) + && is_trait_method(cx, recv, sym::Iterator) + && is_expr_identity_function(cx, any_arg) + && let map_any_call_span = map_call_span.with_hi(any_call_span.hi()) + && let Some(map_arg) = map_arg.span.get_source_text(cx) + { + span_lint_and_then( + cx, + MAP_ALL_ANY_IDENTITY, + map_any_call_span, + format!("usage of `.map(...).{method}(identity)`"), + |diag| { + diag.span_suggestion_verbose( + map_any_call_span, + format!("use `.{method}(...)` instead"), + format!("{method}({map_arg})"), + Applicability::MachineApplicable, + ); + }, + ); + } +} diff --git a/clippy_lints/src/methods/map_with_unused_argument_over_ranges.rs b/clippy_lints/src/methods/map_with_unused_argument_over_ranges.rs new file mode 100644 index 0000000000000..fc656fd78ba2d --- /dev/null +++ b/clippy_lints/src/methods/map_with_unused_argument_over_ranges.rs @@ -0,0 +1,134 @@ +use crate::methods::MAP_WITH_UNUSED_ARGUMENT_OVER_RANGES; +use clippy_config::msrvs::{self, Msrv}; +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::source::snippet_with_applicability; +use clippy_utils::sugg::Sugg; +use clippy_utils::{eager_or_lazy, higher, usage}; +use rustc_ast::LitKind; +use rustc_ast::ast::RangeLimits; +use rustc_data_structures::packed::Pu128; +use rustc_errors::Applicability; +use rustc_hir::{Body, Closure, Expr, ExprKind}; +use rustc_lint::LateContext; +use rustc_span::Span; + +fn extract_count_with_applicability( + cx: &LateContext<'_>, + range: higher::Range<'_>, + applicability: &mut Applicability, +) -> Option { + let start = range.start?; + let end = range.end?; + // TODO: This doens't handle if either the start or end are negative literals, or if the start is + // not a literal. In the first case, we need to be careful about how we handle computing the + // count to avoid overflows. In the second, we may need to add parenthesis to make the + // suggestion correct. + if let ExprKind::Lit(lit) = start.kind + && let LitKind::Int(Pu128(lower_bound), _) = lit.node + { + if let ExprKind::Lit(lit) = end.kind + && let LitKind::Int(Pu128(upper_bound), _) = lit.node + { + // Here we can explicitly calculate the number of iterations + let count = if upper_bound >= lower_bound { + match range.limits { + RangeLimits::HalfOpen => upper_bound - lower_bound, + RangeLimits::Closed => (upper_bound - lower_bound).checked_add(1)?, + } + } else { + 0 + }; + return Some(format!("{count}")); + } + let end_snippet = Sugg::hir_with_applicability(cx, end, "...", applicability) + .maybe_par() + .into_string(); + if lower_bound == 0 { + if range.limits == RangeLimits::Closed { + return Some(format!("{end_snippet} + 1")); + } + return Some(end_snippet); + } + if range.limits == RangeLimits::Closed { + return Some(format!("{end_snippet} - {}", lower_bound - 1)); + } + return Some(format!("{end_snippet} - {lower_bound}")); + } + None +} + +pub(super) fn check( + cx: &LateContext<'_>, + ex: &Expr<'_>, + receiver: &Expr<'_>, + arg: &Expr<'_>, + msrv: &Msrv, + method_call_span: Span, +) { + let mut applicability = Applicability::MaybeIncorrect; + if let Some(range) = higher::Range::hir(receiver) + && let ExprKind::Closure(Closure { body, .. }) = arg.kind + && let body_hir = cx.tcx.hir().body(*body) + && let Body { + params: [param], + value: body_expr, + } = body_hir + && !usage::BindingUsageFinder::are_params_used(cx, body_hir) + && let Some(count) = extract_count_with_applicability(cx, range, &mut applicability) + { + let method_to_use_name; + let new_span; + let use_take; + + if eager_or_lazy::switch_to_eager_eval(cx, body_expr) { + if msrv.meets(msrvs::REPEAT_N) { + method_to_use_name = "repeat_n"; + let body_snippet = snippet_with_applicability(cx, body_expr.span, "..", &mut applicability); + new_span = (arg.span, format!("{body_snippet}, {count}")); + use_take = false; + } else { + method_to_use_name = "repeat"; + let body_snippet = snippet_with_applicability(cx, body_expr.span, "..", &mut applicability); + new_span = (arg.span, body_snippet.to_string()); + use_take = true; + } + } else if msrv.meets(msrvs::REPEAT_WITH) { + method_to_use_name = "repeat_with"; + new_span = (param.span, String::new()); + use_take = true; + } else { + return; + } + + // We need to provide nonempty parts to diag.multipart_suggestion so we + // collate all our parts here and then remove those that are empty. + let mut parts = vec![ + ( + receiver.span.to(method_call_span), + format!("std::iter::{method_to_use_name}"), + ), + new_span, + ]; + if use_take { + parts.push((ex.span.shrink_to_hi(), format!(".take({count})"))); + } + + span_lint_and_then( + cx, + MAP_WITH_UNUSED_ARGUMENT_OVER_RANGES, + ex.span, + "map of a closure that does not depend on its parameter over a range", + |diag| { + diag.multipart_suggestion( + if use_take { + format!("remove the explicit range and use `{method_to_use_name}` and `take`") + } else { + format!("remove the explicit range and use `{method_to_use_name}`") + }, + parts, + applicability, + ); + }, + ); + } +} diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 2a391870d70a2..b1809796355d0 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -60,13 +60,16 @@ mod manual_ok_or; mod manual_saturating_arithmetic; mod manual_str_repeat; mod manual_try_fold; +mod map_all_any_identity; mod map_clone; mod map_collect_result_unit; mod map_err_ignore; mod map_flatten; mod map_identity; mod map_unwrap_or; +mod map_with_unused_argument_over_ranges; mod mut_mutex_lock; +mod needless_as_bytes; mod needless_character_iteration; mod needless_collect; mod needless_option_as_deref; @@ -4166,6 +4169,90 @@ declare_clippy_lint! { "calling `.first().is_some()` or `.first().is_none()` instead of `.is_empty()`" } +declare_clippy_lint! { + /// ### What it does + /// It detects useless calls to `str::as_bytes()` before calling `len()` or `is_empty()`. + /// + /// ### Why is this bad? + /// The `len()` and `is_empty()` methods are also directly available on strings, and they + /// return identical results. In particular, `len()` on a string returns the number of + /// bytes. + /// + /// ### Example + /// ``` + /// let len = "some string".as_bytes().len(); + /// let b = "some string".as_bytes().is_empty(); + /// ``` + /// Use instead: + /// ``` + /// let len = "some string".len(); + /// let b = "some string".is_empty(); + /// ``` + #[clippy::version = "1.84.0"] + pub NEEDLESS_AS_BYTES, + complexity, + "detect useless calls to `as_bytes()`" +} + +declare_clippy_lint! { + /// ### What it does + /// Checks for usage of `.map(…)`, followed by `.all(identity)` or `.any(identity)`. + /// + /// ### Why is this bad? + /// The `.all(…)` or `.any(…)` methods can be called directly in place of `.map(…)`. + /// + /// ### Example + /// ``` + /// # let mut v = [""]; + /// let e1 = v.iter().map(|s| s.is_empty()).all(|a| a); + /// let e2 = v.iter().map(|s| s.is_empty()).any(std::convert::identity); + /// ``` + /// Use instead: + /// ``` + /// # let mut v = [""]; + /// let e1 = v.iter().all(|s| s.is_empty()); + /// let e2 = v.iter().any(|s| s.is_empty()); + /// ``` + #[clippy::version = "1.84.0"] + pub MAP_ALL_ANY_IDENTITY, + complexity, + "combine `.map(_)` followed by `.all(identity)`/`.any(identity)` into a single call" +} + +declare_clippy_lint! { + /// ### What it does + /// + /// Checks for `Iterator::map` over ranges without using the parameter which + /// could be more clearly expressed using `std::iter::repeat(...).take(...)` + /// or `std::iter::repeat_n`. + /// + /// ### Why is this bad? + /// + /// It expresses the intent more clearly to `take` the correct number of times + /// from a generating function than to apply a closure to each number in a + /// range only to discard them. + /// + /// ### Example + /// + /// ```no_run + /// let random_numbers : Vec<_> = (0..10).map(|_| { 3 + 1 }).collect(); + /// ``` + /// Use instead: + /// ```no_run + /// let f : Vec<_> = std::iter::repeat( 3 + 1 ).take(10).collect(); + /// ``` + /// + /// ### Known Issues + /// + /// This lint may suggest replacing a `Map` with a `Take`. + /// The former implements some traits that the latter does not, such as + /// `DoubleEndedIterator`. + #[clippy::version = "1.84.0"] + pub MAP_WITH_UNUSED_ARGUMENT_OVER_RANGES, + restriction, + "map of a trivial closure (not dependent on parameter) over a range" +} + pub struct Methods { avoid_breaking_exported_api: bool, msrv: Msrv, @@ -4327,6 +4414,9 @@ impl_lint_pass!(Methods => [ NEEDLESS_CHARACTER_ITERATION, MANUAL_INSPECT, UNNECESSARY_MIN_OR_MAX, + NEEDLESS_AS_BYTES, + MAP_ALL_ANY_IDENTITY, + MAP_WITH_UNUSED_ARGUMENT_OVER_RANGES, ]); /// Extracts a method call name, args, and `Span` of the method name. @@ -4534,15 +4624,21 @@ impl Methods { ("all", [arg]) => { unused_enumerate_index::check(cx, expr, recv, arg); needless_character_iteration::check(cx, expr, recv, arg, true); - if let Some(("cloned", recv2, [], _, _)) = method_call(recv) { - iter_overeager_cloned::check( - cx, - expr, - recv, - recv2, - iter_overeager_cloned::Op::NeedlessMove(arg), - false, - ); + match method_call(recv) { + Some(("cloned", recv2, [], _, _)) => { + iter_overeager_cloned::check( + cx, + expr, + recv, + recv2, + iter_overeager_cloned::Op::NeedlessMove(arg), + false, + ); + }, + Some(("map", _, [map_arg], _, map_call_span)) => { + map_all_any_identity::check(cx, expr, recv, map_call_span, map_arg, call_span, arg, "all"); + }, + _ => {}, } }, ("and_then", [arg]) => { @@ -4571,6 +4667,9 @@ impl Methods { { string_lit_chars_any::check(cx, expr, recv, param, peel_blocks(body.value), &self.msrv); }, + Some(("map", _, [map_arg], _, map_call_span)) => { + map_all_any_identity::check(cx, expr, recv, map_call_span, map_arg, call_span, arg, "any"); + }, _ => {}, } }, @@ -4764,8 +4863,14 @@ impl Methods { unit_hash::check(cx, expr, recv, arg); }, ("is_empty", []) => { - if let Some(("as_str", recv, [], as_str_span, _)) = method_call(recv) { - redundant_as_str::check(cx, expr, recv, as_str_span, span); + match method_call(recv) { + Some(("as_bytes", prev_recv, [], _, _)) => { + needless_as_bytes::check(cx, "is_empty", recv, prev_recv, expr.span); + }, + Some(("as_str", recv, [], as_str_span, _)) => { + redundant_as_str::check(cx, expr, recv, as_str_span, span); + }, + _ => {}, } is_empty::check(cx, expr, recv); }, @@ -4795,6 +4900,11 @@ impl Methods { ); } }, + ("len", []) => { + if let Some(("as_bytes", prev_recv, [], _, _)) = method_call(recv) { + needless_as_bytes::check(cx, "len", recv, prev_recv, expr.span); + } + }, ("lock", []) => { mut_mutex_lock::check(cx, expr, recv, span); }, @@ -4802,6 +4912,7 @@ impl Methods { if name == "map" { unused_enumerate_index::check(cx, expr, recv, m_arg); map_clone::check(cx, expr, recv, m_arg, &self.msrv); + map_with_unused_argument_over_ranges::check(cx, expr, recv, m_arg, &self.msrv, span); match method_call(recv) { Some((map_name @ ("iter" | "into_iter"), recv2, _, _, _)) => { iter_kv_map::check(cx, map_name, expr, recv2, m_arg, &self.msrv); @@ -5182,7 +5293,6 @@ impl ShouldImplTraitCase { } #[rustfmt::skip] -#[expect(clippy::large_const_arrays, reason = "`Span` is not sync, so this can't be static")] const TRAIT_METHODS: [ShouldImplTraitCase; 30] = [ ShouldImplTraitCase::new("std::ops::Add", "add", 2, FN_HEADER, SelfKind::Value, OutType::Any, true), ShouldImplTraitCase::new("std::convert::AsMut", "as_mut", 1, FN_HEADER, SelfKind::RefMut, OutType::Ref, true), diff --git a/clippy_lints/src/methods/needless_as_bytes.rs b/clippy_lints/src/methods/needless_as_bytes.rs new file mode 100644 index 0000000000000..75e9f31723039 --- /dev/null +++ b/clippy_lints/src/methods/needless_as_bytes.rs @@ -0,0 +1,28 @@ +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::sugg::Sugg; +use clippy_utils::ty::is_type_lang_item; +use rustc_errors::Applicability; +use rustc_hir::{Expr, LangItem}; +use rustc_lint::LateContext; +use rustc_span::Span; + +use super::NEEDLESS_AS_BYTES; + +pub fn check(cx: &LateContext<'_>, method: &str, recv: &Expr<'_>, prev_recv: &Expr<'_>, span: Span) { + if cx.typeck_results().expr_ty_adjusted(recv).peel_refs().is_slice() + && let ty1 = cx.typeck_results().expr_ty_adjusted(prev_recv).peel_refs() + && (is_type_lang_item(cx, ty1, LangItem::String) || ty1.is_str()) + { + let mut app = Applicability::MachineApplicable; + let sugg = Sugg::hir_with_context(cx, prev_recv, span.ctxt(), "..", &mut app); + span_lint_and_sugg( + cx, + NEEDLESS_AS_BYTES, + span, + "needless call to `as_bytes()`", + format!("`{method}()` can be called directly on strings"), + format!("{sugg}.{method}()"), + app, + ); + } +} diff --git a/clippy_lints/src/methods/needless_collect.rs b/clippy_lints/src/methods/needless_collect.rs index 96a31812ca475..055107068ae03 100644 --- a/clippy_lints/src/methods/needless_collect.rs +++ b/clippy_lints/src/methods/needless_collect.rs @@ -322,7 +322,7 @@ impl<'tcx> Visitor<'tcx> for IterFunctionVisitor<'_, 'tcx> { // Check function calls on our collection if let ExprKind::MethodCall(method_name, recv, args, _) = &expr.kind { if args.is_empty() - && method_name.ident.name == sym!(collect) + && method_name.ident.name.as_str() == "collect" && is_trait_method(self.cx, expr, sym::Iterator) { self.current_mutably_captured_ids = get_captured_ids(self.cx, self.cx.typeck_results().expr_ty(recv)); diff --git a/clippy_lints/src/methods/read_line_without_trim.rs b/clippy_lints/src/methods/read_line_without_trim.rs index 65e545ed03a02..db2b9d4d92fb6 100644 --- a/clippy_lints/src/methods/read_line_without_trim.rs +++ b/clippy_lints/src/methods/read_line_without_trim.rs @@ -44,7 +44,7 @@ pub fn check(cx: &LateContext<'_>, call: &Expr<'_>, recv: &Expr<'_>, arg: &Expr< if let Some(parent) = get_parent_expr(cx, expr) { let data = if let ExprKind::MethodCall(segment, recv, args, span) = parent.kind { if args.is_empty() - && segment.ident.name == sym!(parse) + && segment.ident.name.as_str() == "parse" && let parse_result_ty = cx.typeck_results().expr_ty(parent) && is_type_diagnostic_item(cx, parse_result_ty, sym::Result) && let ty::Adt(_, substs) = parse_result_ty.kind() @@ -58,7 +58,7 @@ pub fn check(cx: &LateContext<'_>, call: &Expr<'_>, recv: &Expr<'_>, arg: &Expr< "calling `.parse()` on a string without trimming the trailing newline character", "checking", )) - } else if segment.ident.name == sym!(ends_with) + } else if segment.ident.name.as_str() == "ends_with" && recv.span == expr.span && let [arg] = args && expr_is_string_literal_without_trailing_newline(arg) diff --git a/clippy_lints/src/methods/unnecessary_filter_map.rs b/clippy_lints/src/methods/unnecessary_filter_map.rs index ca46da81fa053..bab439015c5f6 100644 --- a/clippy_lints/src/methods/unnecessary_filter_map.rs +++ b/clippy_lints/src/methods/unnecessary_filter_map.rs @@ -1,13 +1,15 @@ use super::utils::clone_or_copy_needed; -use clippy_utils::diagnostics::span_lint; +use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::ty::is_copy; use clippy_utils::usage::mutated_variables; use clippy_utils::visitors::{Descend, for_each_expr_without_closures}; -use clippy_utils::{is_res_lang_ctor, is_trait_method, path_res, path_to_local_id}; +use clippy_utils::{MaybePath, is_res_lang_ctor, is_trait_method, path_res, path_to_local_id}; use core::ops::ControlFlow; +use rustc_errors::Applicability; use rustc_hir as hir; use rustc_hir::LangItem::{OptionNone, OptionSome}; use rustc_lint::LateContext; +use rustc_middle::query::Key; use rustc_middle::ty; use rustc_span::sym; @@ -36,9 +38,25 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>, a ControlFlow::Continue(Descend::Yes) } }); - let in_ty = cx.typeck_results().node_type(body.params[0].hir_id); let sugg = if !found_filtering { + // Check if the closure is .filter_map(|x| Some(x)) + if name == "filter_map" + && let hir::ExprKind::Call(expr, args) = body.value.kind + && is_res_lang_ctor(cx, path_res(cx, expr), OptionSome) + && arg_id.ty_def_id() == args[0].hir_id().ty_def_id() + && let hir::ExprKind::Path(_) = args[0].kind + { + span_lint_and_sugg( + cx, + UNNECESSARY_FILTER_MAP, + expr.span, + format!("{name} is unnecessary"), + "try removing the filter_map", + String::new(), + Applicability::MaybeIncorrect, + ); + } if name == "filter_map" { "map" } else { "map(..).next()" } } else if !found_mapping && !mutates_arg && (!clone_or_copy_needed || is_copy(cx, in_ty)) { match cx.typeck_results().expr_ty(body.value).kind() { @@ -52,7 +70,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>, a } else { return; }; - span_lint( + span_lint_and_sugg( cx, if name == "filter_map" { UNNECESSARY_FILTER_MAP @@ -60,7 +78,10 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>, a UNNECESSARY_FIND_MAP }, expr.span, - format!("this `.{name}` can be written more simply using `.{sugg}`"), + format!("this `.{name}` can be written more simply"), + "try instead", + sugg.to_string(), + Applicability::MaybeIncorrect, ); } } @@ -78,7 +99,7 @@ fn check_expression<'tcx>(cx: &LateContext<'tcx>, arg_id: hir::HirId, expr: &'tc (true, true) }, hir::ExprKind::MethodCall(segment, recv, [arg], _) => { - if segment.ident.name == sym!(then_some) + if segment.ident.name.as_str() == "then_some" && cx.typeck_results().expr_ty(recv).is_bool() && path_to_local_id(arg, arg_id) { diff --git a/clippy_lints/src/minmax.rs b/clippy_lints/src/minmax.rs index e95864c6db8b5..ed89b3b34386f 100644 --- a/clippy_lints/src/minmax.rs +++ b/clippy_lints/src/minmax.rs @@ -79,9 +79,9 @@ fn min_max<'a, 'tcx>(cx: &LateContext<'tcx>, expr: &'a Expr<'a>) -> Option<(MinM }, ExprKind::MethodCall(path, receiver, args @ [_], _) => { if cx.typeck_results().expr_ty(receiver).is_floating_point() || is_trait_method(cx, expr, sym::Ord) { - if path.ident.name == sym!(max) { + if path.ident.name.as_str() == "max" { fetch_const(cx, Some(receiver), args, MinMax::Max) - } else if path.ident.name == sym!(min) { + } else if path.ident.name.as_str() == "min" { fetch_const(cx, Some(receiver), args, MinMax::Min) } else { None diff --git a/clippy_lints/src/missing_doc.rs b/clippy_lints/src/missing_doc.rs index ce508d85d6348..1300c7d106285 100644 --- a/clippy_lints/src/missing_doc.rs +++ b/clippy_lints/src/missing_doc.rs @@ -12,11 +12,13 @@ use clippy_utils::is_from_proc_macro; use clippy_utils::source::SpanRangeExt; use rustc_ast::ast::{self, MetaItem, MetaItemKind}; use rustc_hir as hir; +use rustc_hir::def::DefKind; use rustc_hir::def_id::LocalDefId; use rustc_lint::{LateContext, LateLintPass, LintContext}; use rustc_middle::ty::Visibility; use rustc_session::impl_lint_pass; use rustc_span::def_id::CRATE_DEF_ID; +use rustc_span::symbol::kw; use rustc_span::{Span, sym}; declare_clippy_lint! { @@ -110,6 +112,21 @@ impl MissingDoc { return; } + if let Some(parent_def_id) = cx.tcx.opt_parent(def_id.to_def_id()) + && let DefKind::AnonConst + | DefKind::AssocConst + | DefKind::AssocFn + | DefKind::Closure + | DefKind::Const + | DefKind::Fn + | DefKind::InlineConst + | DefKind::Static { .. } + | DefKind::SyntheticCoroutineBody = cx.tcx.def_kind(parent_def_id) + { + // Nested item has no generated documentation, so it doesn't need to be documented. + return; + } + let has_doc = attrs .iter() .any(|a| a.doc_str().is_some() || Self::has_include(a.meta())) @@ -184,8 +201,12 @@ impl<'tcx> LateLintPass<'tcx> for MissingDoc { } } }, - hir::ItemKind::Const(..) - | hir::ItemKind::Enum(..) + hir::ItemKind::Const(..) => { + if it.ident.name == kw::Underscore { + note_prev_span_then_ret!(self.prev_span, it.span); + } + }, + hir::ItemKind::Enum(..) | hir::ItemKind::Macro(..) | hir::ItemKind::Mod(..) | hir::ItemKind::Static(..) diff --git a/clippy_lints/src/missing_fields_in_debug.rs b/clippy_lints/src/missing_fields_in_debug.rs index fc01b6753f1c7..f28b431ab997e 100644 --- a/clippy_lints/src/missing_fields_in_debug.rs +++ b/clippy_lints/src/missing_fields_in_debug.rs @@ -116,7 +116,7 @@ fn should_lint<'tcx>( if path.ident.name == sym::debug_struct && is_type_diagnostic_item(cx, recv_ty, sym::Formatter) { has_debug_struct = true; - } else if path.ident.name == sym!(finish_non_exhaustive) + } else if path.ident.name.as_str() == "finish_non_exhaustive" && is_type_diagnostic_item(cx, recv_ty, sym::DebugStruct) { has_finish_non_exhaustive = true; diff --git a/clippy_lints/src/needless_continue.rs b/clippy_lints/src/needless_continue.rs index ce97370d4d9fa..c48367729336f 100644 --- a/clippy_lints/src/needless_continue.rs +++ b/clippy_lints/src/needless_continue.rs @@ -330,10 +330,11 @@ fn suggestion_snippet_for_continue_inside_else(cx: &EarlyContext<'_>, data: &Lin } fn check_and_warn(cx: &EarlyContext<'_>, expr: &ast::Expr) { - if let ast::ExprKind::Loop(loop_block, ..) = &expr.kind + if let ast::ExprKind::Loop(loop_block, loop_label, ..) = &expr.kind && let Some(last_stmt) = loop_block.stmts.last() && let ast::StmtKind::Expr(inner_expr) | ast::StmtKind::Semi(inner_expr) = &last_stmt.kind - && let ast::ExprKind::Continue(_) = inner_expr.kind + && let ast::ExprKind::Continue(continue_label) = inner_expr.kind + && compare_labels(loop_label.as_ref(), continue_label.as_ref()) { span_lint_and_help( cx, diff --git a/clippy_lints/src/needless_late_init.rs b/clippy_lints/src/needless_late_init.rs index 46cdb82130f97..a67addea94866 100644 --- a/clippy_lints/src/needless_late_init.rs +++ b/clippy_lints/src/needless_late_init.rs @@ -347,7 +347,7 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessLateInit { if let LetStmt { init: None, pat: - &Pat { + Pat { kind: PatKind::Binding(BindingMode::NONE, binding_id, _, None), .. }, @@ -357,7 +357,7 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessLateInit { && let Some((_, Node::Stmt(local_stmt))) = parents.next() && let Some((_, Node::Block(block))) = parents.next() { - check(cx, local, local_stmt, block, binding_id); + check(cx, local, local_stmt, block, *binding_id); } } } diff --git a/clippy_lints/src/needless_maybe_sized.rs b/clippy_lints/src/needless_maybe_sized.rs index 9a1c397b5b237..852a0ce8c6d57 100644 --- a/clippy_lints/src/needless_maybe_sized.rs +++ b/clippy_lints/src/needless_maybe_sized.rs @@ -1,7 +1,7 @@ use clippy_utils::diagnostics::span_lint_and_then; use rustc_errors::Applicability; use rustc_hir::def_id::{DefId, DefIdMap}; -use rustc_hir::{GenericBound, Generics, PolyTraitRef, TraitBoundModifiers, BoundPolarity, WherePredicate}; +use rustc_hir::{BoundPolarity, GenericBound, Generics, PolyTraitRef, TraitBoundModifiers, WherePredicate}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{ClauseKind, PredicatePolarity}; use rustc_session::declare_lint_pass; diff --git a/clippy_lints/src/needless_pass_by_ref_mut.rs b/clippy_lints/src/needless_pass_by_ref_mut.rs index f8c815d97295e..b7dc269061cf8 100644 --- a/clippy_lints/src/needless_pass_by_ref_mut.rs +++ b/clippy_lints/src/needless_pass_by_ref_mut.rs @@ -183,7 +183,7 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByRefMut<'tcx> { .iter() .zip(fn_sig.inputs()) .zip(body.params) - .filter(|((&input, &ty), arg)| !should_skip(cx, input, ty, arg)) + .filter(|&((&input, &ty), arg)| !should_skip(cx, input, ty, arg)) .peekable(); if it.peek().is_none() { return; diff --git a/clippy_lints/src/no_effect.rs b/clippy_lints/src/no_effect.rs index 2e5195d459f4d..74536028b5d46 100644 --- a/clippy_lints/src/no_effect.rs +++ b/clippy_lints/src/no_effect.rs @@ -159,8 +159,12 @@ impl NoEffect { // Remove `impl Future` to get `T` if cx.tcx.ty_is_opaque_future(ret_ty) - && let Some(true_ret_ty) = - cx.tcx.infer_ctxt().build(cx.typing_mode()).err_ctxt().get_impl_future_output_ty(ret_ty) + && let Some(true_ret_ty) = cx + .tcx + .infer_ctxt() + .build(cx.typing_mode()) + .err_ctxt() + .get_impl_future_output_ty(ret_ty) { ret_ty = true_ret_ty; } diff --git a/clippy_lints/src/no_mangle_with_rust_abi.rs b/clippy_lints/src/no_mangle_with_rust_abi.rs index 8d5a523fd8f6a..64587e08ddc6f 100644 --- a/clippy_lints/src/no_mangle_with_rust_abi.rs +++ b/clippy_lints/src/no_mangle_with_rust_abi.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::source::snippet_with_applicability; +use clippy_utils::source::{snippet, snippet_with_applicability}; use rustc_errors::Applicability; use rustc_hir::{Item, ItemKind}; use rustc_lint::{LateContext, LateLintPass}; @@ -18,13 +18,13 @@ declare_clippy_lint! { /// Rust ABI can break this at any point. /// /// ### Example - /// ```no_run + /// ```rust,ignore /// #[no_mangle] /// fn example(arg_one: u32, arg_two: usize) {} /// ``` /// /// Use instead: - /// ```no_run + /// ```rust,ignore /// #[no_mangle] /// extern "C" fn example(arg_one: u32, arg_two: usize) {} /// ``` @@ -40,24 +40,25 @@ impl<'tcx> LateLintPass<'tcx> for NoMangleWithRustAbi { if let ItemKind::Fn(fn_sig, _, _) = &item.kind { let attrs = cx.tcx.hir().attrs(item.hir_id()); let mut app = Applicability::MaybeIncorrect; - let snippet = snippet_with_applicability(cx, fn_sig.span, "..", &mut app); + let fn_snippet = snippet_with_applicability(cx, fn_sig.span.with_hi(item.ident.span.lo()), "..", &mut app); for attr in attrs { if let Some(ident) = attr.ident() && ident.name == rustc_span::sym::no_mangle && fn_sig.header.abi == Abi::Rust - && let Some((fn_attrs, _)) = snippet.split_once("fn") + && let Some((fn_attrs, _)) = fn_snippet.rsplit_once("fn") && !fn_attrs.contains("extern") { let sugg_span = fn_sig .span .with_lo(fn_sig.span.lo() + BytePos::from_usize(fn_attrs.len())) .shrink_to_lo(); + let attr_snippet = snippet(cx, attr.span, ".."); span_lint_and_then( cx, NO_MANGLE_WITH_RUST_ABI, fn_sig.span, - "`#[no_mangle]` set on a function with the default (`Rust`) ABI", + format!("`{attr_snippet}` set on a function with the default (`Rust`) ABI"), |diag| { diag.span_suggestion(sugg_span, "set an ABI", "extern \"C\" ", app) .span_suggestion(sugg_span, "or explicitly set the default", "extern \"Rust\" ", app); diff --git a/clippy_lints/src/non_octal_unix_permissions.rs b/clippy_lints/src/non_octal_unix_permissions.rs index 3f156aa551040..0caa19cd84436 100644 --- a/clippy_lints/src/non_octal_unix_permissions.rs +++ b/clippy_lints/src/non_octal_unix_permissions.rs @@ -43,12 +43,12 @@ impl<'tcx> LateLintPass<'tcx> for NonOctalUnixPermissions { match &expr.kind { ExprKind::MethodCall(path, func, [param], _) => { if let Some(adt) = cx.typeck_results().expr_ty(func).peel_refs().ty_adt_def() - && ((path.ident.name == sym!(mode) + && ((path.ident.name.as_str() == "mode" && matches!( cx.tcx.get_diagnostic_name(adt.did()), Some(sym::FsOpenOptions | sym::DirBuilder) )) - || (path.ident.name == sym!(set_mode) + || (path.ident.name.as_str() == "set_mode" && cx.tcx.is_diagnostic_item(sym::FsPermissions, adt.did()))) && let ExprKind::Lit(_) = param.kind && param.span.eq_ctxt(expr.span) diff --git a/clippy_lints/src/operators/float_cmp.rs b/clippy_lints/src/operators/float_cmp.rs index 8e394944c2130..ab5f91c1d6725 100644 --- a/clippy_lints/src/operators/float_cmp.rs +++ b/clippy_lints/src/operators/float_cmp.rs @@ -107,7 +107,7 @@ fn is_signum(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { } if let ExprKind::MethodCall(method_name, self_arg, [], _) = expr.kind - && sym!(signum) == method_name.ident.name + && method_name.ident.name.as_str() == "signum" // Check that the receiver of the signum() is a float (expressions[0] is the receiver of // the method call) { diff --git a/clippy_lints/src/partialeq_ne_impl.rs b/clippy_lints/src/partialeq_ne_impl.rs index 18e6aad9c9a08..794bef7b32143 100644 --- a/clippy_lints/src/partialeq_ne_impl.rs +++ b/clippy_lints/src/partialeq_ne_impl.rs @@ -34,7 +34,7 @@ declare_lint_pass!(PartialEqNeImpl => [PARTIALEQ_NE_IMPL]); impl<'tcx> LateLintPass<'tcx> for PartialEqNeImpl { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { if let ItemKind::Impl(Impl { - of_trait: Some(ref trait_ref), + of_trait: Some(trait_ref), items: impl_items, .. }) = item.kind diff --git a/clippy_lints/src/ptr_offset_with_cast.rs b/clippy_lints/src/ptr_offset_with_cast.rs index 56d07aeae1758..808a7e005c679 100644 --- a/clippy_lints/src/ptr_offset_with_cast.rs +++ b/clippy_lints/src/ptr_offset_with_cast.rs @@ -96,7 +96,7 @@ fn expr_as_ptr_offset_call<'tcx>( if path_segment.ident.name == sym::offset { return Some((arg_0, arg_1, Method::Offset)); } - if path_segment.ident.name == sym!(wrapping_offset) { + if path_segment.ident.name.as_str() == "wrapping_offset" { return Some((arg_0, arg_1, Method::WrappingOffset)); } } diff --git a/clippy_lints/src/question_mark.rs b/clippy_lints/src/question_mark.rs index 9344cb41993a7..cb374fcf20e3b 100644 --- a/clippy_lints/src/question_mark.rs +++ b/clippy_lints/src/question_mark.rs @@ -93,7 +93,7 @@ enum IfBlockType<'hir> { fn find_let_else_ret_expression<'hir>(block: &'hir Block<'hir>) -> Option<&'hir Expr<'hir>> { if let Block { - stmts: &[], + stmts: [], expr: Some(els), .. } = block @@ -163,8 +163,8 @@ fn is_early_return(smbl: Symbol, cx: &LateContext<'_>, if_block: &IfBlockType<'_ is_type_diagnostic_item(cx, caller_ty, smbl) && expr_return_none_or_err(smbl, cx, if_then, caller, None) && match smbl { - sym::Option => call_sym == sym!(is_none), - sym::Result => call_sym == sym!(is_err), + sym::Option => call_sym.as_str() == "is_none", + sym::Result => call_sym.as_str() == "is_err", _ => false, } }, diff --git a/clippy_lints/src/ref_option_ref.rs b/clippy_lints/src/ref_option_ref.rs index 19ce08bde10c9..7d59bf24d9372 100644 --- a/clippy_lints/src/ref_option_ref.rs +++ b/clippy_lints/src/ref_option_ref.rs @@ -39,7 +39,7 @@ impl<'tcx> LateLintPass<'tcx> for RefOptionRef { fn check_ty(&mut self, cx: &LateContext<'tcx>, ty: &'tcx Ty<'tcx>) { if let TyKind::Ref(_, ref mut_ty) = ty.kind && mut_ty.mutbl == Mutability::Not - && let TyKind::Path(ref qpath) = &mut_ty.ty.kind + && let TyKind::Path(qpath) = &mut_ty.ty.kind && let last = last_path_segment(qpath) && let Some(def_id) = last.res.opt_def_id() && cx.tcx.is_diagnostic_item(sym::Option, def_id) diff --git a/clippy_lints/src/returns.rs b/clippy_lints/src/returns.rs index 110dea8fb8eb8..1e0f6dff1abeb 100644 --- a/clippy_lints/src/returns.rs +++ b/clippy_lints/src/returns.rs @@ -347,7 +347,7 @@ fn check_final_expr<'tcx>( let peeled_drop_expr = expr.peel_drop_temps(); match &peeled_drop_expr.kind { // simple return is always "bad" - ExprKind::Ret(ref inner) => { + ExprKind::Ret(inner) => { // check if expr return nothing let ret_span = if inner.is_none() && replacement == RetReplacement::Empty { extend_span_to_previous_non_ws(cx, peeled_drop_expr.span) diff --git a/clippy_lints/src/semicolon_block.rs b/clippy_lints/src/semicolon_block.rs index 09f1c11235291..d85f4a8fa0160 100644 --- a/clippy_lints/src/semicolon_block.rs +++ b/clippy_lints/src/semicolon_block.rs @@ -101,17 +101,21 @@ impl SemicolonBlock { ); } - fn semicolon_outside_block( - &self, - cx: &LateContext<'_>, - block: &Block<'_>, - tail_stmt_expr: &Expr<'_>, - semi_span: Span, - ) { + fn semicolon_outside_block(&self, cx: &LateContext<'_>, block: &Block<'_>, tail_stmt_expr: &Expr<'_>) { let insert_span = block.span.with_lo(block.span.hi()); - // account for macro calls - let semi_span = cx.sess().source_map().stmt_span(semi_span, block.span); - let remove_span = semi_span.with_lo(tail_stmt_expr.span.source_callsite().hi()); + + // For macro call semicolon statements (`mac!();`), the statement's span does not actually + // include the semicolon itself, so use `mac_call_stmt_semi_span`, which finds the semicolon + // based on a source snippet. + // (Does not use `stmt_span` as that requires `.from_expansion()` to return true, + // which is not the case for e.g. `line!();` and `asm!();`) + let Some(remove_span) = cx + .sess() + .source_map() + .mac_call_stmt_semi_span(tail_stmt_expr.span.source_callsite()) + else { + return; + }; if self.semicolon_outside_block_ignore_multiline && get_line(cx, remove_span) != get_line(cx, insert_span) { return; @@ -150,13 +154,12 @@ impl LateLintPass<'_> for SemicolonBlock { }; let &Stmt { kind: StmtKind::Semi(expr), - span, .. } = stmt else { return; }; - self.semicolon_outside_block(cx, block, expr, span); + self.semicolon_outside_block(cx, block, expr); }, StmtKind::Semi(Expr { kind: ExprKind::Block(block @ Block { expr: Some(tail), .. }, _), diff --git a/clippy_lints/src/serde_api.rs b/clippy_lints/src/serde_api.rs index fcf1c4cc5c272..6a0dfde2d9c9c 100644 --- a/clippy_lints/src/serde_api.rs +++ b/clippy_lints/src/serde_api.rs @@ -26,7 +26,7 @@ declare_lint_pass!(SerdeApi => [SERDE_API_MISUSE]); impl<'tcx> LateLintPass<'tcx> for SerdeApi { fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) { if let ItemKind::Impl(Impl { - of_trait: Some(ref trait_ref), + of_trait: Some(trait_ref), items, .. }) = item.kind diff --git a/clippy_lints/src/single_component_path_imports.rs b/clippy_lints/src/single_component_path_imports.rs index 9fdee8543a854..fa08245350429 100644 --- a/clippy_lints/src/single_component_path_imports.rs +++ b/clippy_lints/src/single_component_path_imports.rs @@ -174,7 +174,7 @@ impl SingleComponentPathImports { } match &item.kind { - ItemKind::Mod(_, ModKind::Loaded(ref items, ..)) => { + ItemKind::Mod(_, ModKind::Loaded(items, ..)) => { self.check_mod(items); }, ItemKind::MacroDef(MacroDef { macro_rules: true, .. }) => { diff --git a/clippy_lints/src/slow_vector_initialization.rs b/clippy_lints/src/slow_vector_initialization.rs index ec6e45256d149..8c8f11569c8c2 100644 --- a/clippy_lints/src/slow_vector_initialization.rs +++ b/clippy_lints/src/slow_vector_initialization.rs @@ -235,7 +235,7 @@ impl<'tcx> VectorInitializationVisitor<'_, 'tcx> { if self.initialization_found && let ExprKind::MethodCall(path, self_arg, [extend_arg], _) = expr.kind && path_to_local_id(self_arg, self.vec_alloc.local_id) - && path.ident.name == sym!(extend) + && path.ident.name.as_str() == "extend" && self.is_repeat_take(extend_arg) { self.slow_expression = Some(InitializationType::Extend(expr)); @@ -247,7 +247,7 @@ impl<'tcx> VectorInitializationVisitor<'_, 'tcx> { if self.initialization_found && let ExprKind::MethodCall(path, self_arg, [len_arg, fill_arg], _) = expr.kind && path_to_local_id(self_arg, self.vec_alloc.local_id) - && path.ident.name == sym!(resize) + && path.ident.name.as_str() == "resize" // Check that is filled with 0 && is_integer_literal(fill_arg, 0) { @@ -269,7 +269,7 @@ impl<'tcx> VectorInitializationVisitor<'_, 'tcx> { /// Returns `true` if give expression is `repeat(0).take(...)` fn is_repeat_take(&mut self, expr: &'tcx Expr<'tcx>) -> bool { if let ExprKind::MethodCall(take_path, recv, [len_arg], _) = expr.kind - && take_path.ident.name == sym!(take) + && take_path.ident.name.as_str() == "take" // Check that take is applied to `repeat(0)` && self.is_repeat_zero(recv) { diff --git a/clippy_lints/src/strings.rs b/clippy_lints/src/strings.rs index bf49bb6016293..e09c07060065c 100644 --- a/clippy_lints/src/strings.rs +++ b/clippy_lints/src/strings.rs @@ -286,7 +286,7 @@ impl<'tcx> LateLintPass<'tcx> for StringLitAsBytes { if !in_external_macro(cx.sess(), e.span) && let ExprKind::MethodCall(path, receiver, ..) = &e.kind - && path.ident.name == sym!(as_bytes) + && path.ident.name.as_str() == "as_bytes" && let ExprKind::Lit(lit) = &receiver.kind && let LitKind::Str(lit_content, _) = &lit.node { @@ -332,7 +332,7 @@ impl<'tcx> LateLintPass<'tcx> for StringLitAsBytes { } if let ExprKind::MethodCall(path, recv, [], _) = &e.kind - && path.ident.name == sym!(into_bytes) + && path.ident.name.as_str() == "into_bytes" && let ExprKind::MethodCall(path, recv, [], _) = &recv.kind && matches!(path.ident.name.as_str(), "to_owned" | "to_string") && let ExprKind::Lit(lit) = &recv.kind @@ -493,7 +493,7 @@ impl<'tcx> LateLintPass<'tcx> for TrimSplitWhitespace { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'_>) { let tyckres = cx.typeck_results(); if let ExprKind::MethodCall(path, split_recv, [], split_ws_span) = expr.kind - && path.ident.name == sym!(split_whitespace) + && path.ident.name.as_str() == "split_whitespace" && let Some(split_ws_def_id) = tyckres.type_dependent_def_id(expr.hir_id) && cx.tcx.is_diagnostic_item(sym::str_split_whitespace, split_ws_def_id) && let ExprKind::MethodCall(path, _trim_recv, [], trim_span) = split_recv.kind diff --git a/clippy_lints/src/suspicious_operation_groupings.rs b/clippy_lints/src/suspicious_operation_groupings.rs index be32dc0aab0b6..0d809c17989de 100644 --- a/clippy_lints/src/suspicious_operation_groupings.rs +++ b/clippy_lints/src/suspicious_operation_groupings.rs @@ -334,7 +334,7 @@ fn strip_non_ident_wrappers(expr: &Expr) -> &Expr { let mut output = expr; loop { output = match &output.kind { - ExprKind::Paren(ref inner) | ExprKind::Unary(_, ref inner) => inner, + ExprKind::Paren(inner) | ExprKind::Unary(_, inner) => inner, _ => { return output; }, @@ -348,13 +348,13 @@ fn extract_related_binops(kind: &ExprKind) -> Option>> { fn if_statement_binops(kind: &ExprKind) -> Option>> { match kind { - ExprKind::If(ref condition, _, _) => chained_binops(&condition.kind), - ExprKind::Paren(ref e) => if_statement_binops(&e.kind), - ExprKind::Block(ref block, _) => { + ExprKind::If(condition, _, _) => chained_binops(&condition.kind), + ExprKind::Paren(e) => if_statement_binops(&e.kind), + ExprKind::Block(block, _) => { let mut output = None; for stmt in &block.stmts { - match stmt.kind { - StmtKind::Expr(ref e) | StmtKind::Semi(ref e) => { + match &stmt.kind { + StmtKind::Expr(e) | StmtKind::Semi(e) => { output = append_opt_vecs(output, if_statement_binops(&e.kind)); }, _ => {}, @@ -383,7 +383,7 @@ fn append_opt_vecs(target_opt: Option>, source_opt: Option>) -> fn chained_binops(kind: &ExprKind) -> Option>> { match kind { ExprKind::Binary(_, left_outer, right_outer) => chained_binops_helper(left_outer, right_outer), - ExprKind::Paren(ref e) | ExprKind::Unary(_, ref e) => chained_binops(&e.kind), + ExprKind::Paren(e) | ExprKind::Unary(_, e) => chained_binops(&e.kind), _ => None, } } @@ -391,16 +391,14 @@ fn chained_binops(kind: &ExprKind) -> Option>> { fn chained_binops_helper<'expr>(left_outer: &'expr Expr, right_outer: &'expr Expr) -> Option>> { match (&left_outer.kind, &right_outer.kind) { ( - ExprKind::Paren(ref left_e) | ExprKind::Unary(_, ref left_e), - ExprKind::Paren(ref right_e) | ExprKind::Unary(_, ref right_e), + ExprKind::Paren(left_e) | ExprKind::Unary(_, left_e), + ExprKind::Paren(right_e) | ExprKind::Unary(_, right_e), ) => chained_binops_helper(left_e, right_e), - (ExprKind::Paren(ref left_e) | ExprKind::Unary(_, ref left_e), _) => chained_binops_helper(left_e, right_outer), - (_, ExprKind::Paren(ref right_e) | ExprKind::Unary(_, ref right_e)) => { - chained_binops_helper(left_outer, right_e) - }, + (ExprKind::Paren(left_e) | ExprKind::Unary(_, left_e), _) => chained_binops_helper(left_e, right_outer), + (_, ExprKind::Paren(right_e) | ExprKind::Unary(_, right_e)) => chained_binops_helper(left_outer, right_e), ( - ExprKind::Binary(Spanned { node: left_op, .. }, ref left_left, ref left_right), - ExprKind::Binary(Spanned { node: right_op, .. }, ref right_left, ref right_right), + ExprKind::Binary(Spanned { node: left_op, .. }, left_left, left_right), + ExprKind::Binary(Spanned { node: right_op, .. }, right_left, right_right), ) => match ( chained_binops_helper(left_left, left_right), chained_binops_helper(right_left, right_right), diff --git a/clippy_lints/src/swap.rs b/clippy_lints/src/swap.rs index 6cba560393d0d..23362506ccad4 100644 --- a/clippy_lints/src/swap.rs +++ b/clippy_lints/src/swap.rs @@ -392,7 +392,7 @@ impl<'tcx> IndexBinding<'_, 'tcx> { for stmt in self.block.stmts { match stmt.kind { StmtKind::Expr(expr) | StmtKind::Semi(expr) => v.visit_expr(expr), - StmtKind::Let(LetStmt { ref init, .. }) => { + StmtKind::Let(LetStmt { init, .. }) => { if let Some(init) = init.as_ref() { v.visit_expr(init); } diff --git a/clippy_lints/src/trait_bounds.rs b/clippy_lints/src/trait_bounds.rs index 3da4bf67558dd..07bf4319ff0e8 100644 --- a/clippy_lints/src/trait_bounds.rs +++ b/clippy_lints/src/trait_bounds.rs @@ -10,8 +10,8 @@ use rustc_data_structures::unhash::UnhashMap; use rustc_errors::Applicability; use rustc_hir::def::Res; use rustc_hir::{ - GenericBound, Generics, Item, ItemKind, LangItem, Node, Path, PathSegment, PredicateOrigin, QPath, - TraitBoundModifiers, TraitItem, TraitRef, Ty, TyKind, WherePredicate, BoundPolarity, + BoundPolarity, GenericBound, Generics, Item, ItemKind, LangItem, Node, Path, PathSegment, PredicateOrigin, QPath, + TraitBoundModifiers, TraitItem, TraitRef, Ty, TyKind, WherePredicate, }; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::impl_lint_pass; @@ -124,7 +124,7 @@ impl<'tcx> LateLintPass<'tcx> for TraitBounds { let mut self_bounds_map = FxHashMap::default(); for predicate in item.generics.predicates { - if let WherePredicate::BoundPredicate(ref bound_predicate) = predicate + if let WherePredicate::BoundPredicate(bound_predicate) = predicate && bound_predicate.origin != PredicateOrigin::ImplTrait && !bound_predicate.span.from_expansion() && let TyKind::Path(QPath::Resolved(_, Path { segments, .. })) = bound_predicate.bounded_ty.kind @@ -268,7 +268,7 @@ impl TraitBounds { let mut map: UnhashMap, Vec<&GenericBound<'_>>> = UnhashMap::default(); let mut applicability = Applicability::MaybeIncorrect; for bound in generics.predicates { - if let WherePredicate::BoundPredicate(ref p) = bound + if let WherePredicate::BoundPredicate(p) = bound && p.origin != PredicateOrigin::ImplTrait && p.bounds.len() as u64 <= self.max_trait_bounds && !p.span.from_expansion() @@ -379,7 +379,7 @@ struct ComparableTraitRef<'a, 'tcx> { impl PartialEq for ComparableTraitRef<'_, '_> { fn eq(&self, other: &Self) -> bool { - SpanlessEq::new(self.cx).eq_modifiers(self.modifiers, other.modifiers) + SpanlessEq::eq_modifiers(self.modifiers, other.modifiers) && SpanlessEq::new(self.cx) .paths_by_resolution() .eq_path(self.trait_ref.path, other.trait_ref.path) diff --git a/clippy_lints/src/transmute/eager_transmute.rs b/clippy_lints/src/transmute/eager_transmute.rs index 1dfc9f7091e8c..ca9daf2d2a037 100644 --- a/clippy_lints/src/transmute/eager_transmute.rs +++ b/clippy_lints/src/transmute/eager_transmute.rs @@ -43,7 +43,7 @@ fn binops_with_local(cx: &LateContext<'_>, local_expr: &Expr<'_>, expr: &Expr<'_ binops_with_local(cx, local_expr, lhs) || binops_with_local(cx, local_expr, rhs) }, ExprKind::MethodCall(path, receiver, [arg], _) - if path.ident.name == sym!(contains) + if path.ident.name.as_str() == "contains" // ... `contains` called on some kind of range && let Some(receiver_adt) = cx.typeck_results().expr_ty(receiver).peel_refs().ty_adt_def() && let lang_items = cx.tcx.lang_items() @@ -81,7 +81,7 @@ pub(super) fn check<'tcx>( if let Some(then_some_call) = peel_parent_unsafe_blocks(cx, expr) && let ExprKind::MethodCall(path, receiver, [arg], _) = then_some_call.kind && cx.typeck_results().expr_ty(receiver).is_bool() - && path.ident.name == sym!(then_some) + && path.ident.name.as_str() == "then_some" && is_local_with_projections(transmutable) && binops_with_local(cx, transmutable, receiver) && is_normalizable(cx, cx.param_env, from_ty) diff --git a/clippy_lints/src/undocumented_unsafe_blocks.rs b/clippy_lints/src/undocumented_unsafe_blocks.rs index 44eb0a6c593da..0bba611116bbc 100644 --- a/clippy_lints/src/undocumented_unsafe_blocks.rs +++ b/clippy_lints/src/undocumented_unsafe_blocks.rs @@ -295,7 +295,7 @@ fn expr_has_unnecessary_safety_comment<'tcx>( if cx.tcx.hir().parent_iter(expr.hir_id).any(|(_, ref node)| { matches!( node, - Node::Block(&Block { + Node::Block(Block { rules: BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided), .. }), diff --git a/clippy_lints/src/unit_types/let_unit_value.rs b/clippy_lints/src/unit_types/let_unit_value.rs index bf2d75ea1a963..1a1284ce9c43a 100644 --- a/clippy_lints/src/unit_types/let_unit_value.rs +++ b/clippy_lints/src/unit_types/let_unit_value.rs @@ -194,7 +194,7 @@ fn needs_inferred_result_ty( let (id, receiver, args) = match e.kind { ExprKind::Call( Expr { - kind: ExprKind::Path(ref path), + kind: ExprKind::Path(path), hir_id, .. }, diff --git a/clippy_lints/src/unit_types/unit_arg.rs b/clippy_lints/src/unit_types/unit_arg.rs index e7d26fa238e52..0f4bd286145a8 100644 --- a/clippy_lints/src/unit_types/unit_arg.rs +++ b/clippy_lints/src/unit_types/unit_arg.rs @@ -149,7 +149,7 @@ fn is_empty_block(expr: &Expr<'_>) -> bool { expr.kind, ExprKind::Block( Block { - stmts: &[], + stmts: [], expr: None, .. }, diff --git a/clippy_lints/src/unused_io_amount.rs b/clippy_lints/src/unused_io_amount.rs index cf406b817da4f..7c9455bf8abef 100644 --- a/clippy_lints/src/unused_io_amount.rs +++ b/clippy_lints/src/unused_io_amount.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::macros::{is_panic, root_macro_call_first_node}; -use clippy_utils::{is_res_lang_ctor, is_trait_method, match_trait_method, paths, peel_blocks}; +use clippy_utils::{is_res_lang_ctor, is_trait_method, match_def_path, match_trait_method, paths, peel_blocks}; use hir::{ExprKind, HirId, PatKind}; use rustc_hir as hir; use rustc_lint::{LateContext, LateLintPass}; @@ -83,6 +83,28 @@ impl<'tcx> LateLintPass<'tcx> for UnusedIoAmount { /// to consider the arms, and we want to avoid breaking the logic for situations where things /// get desugared to match. fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx hir::Block<'tcx>) { + let fn_def_id = block.hir_id.owner.to_def_id(); + if let Some(impl_id) = cx.tcx.impl_of_method(fn_def_id) + && let Some(trait_id) = cx.tcx.trait_id_of_impl(impl_id) + { + // We don't want to lint inside io::Read or io::Write implementations, as the author has more + // information about their trait implementation than our lint, see https://github.com/rust-lang/rust-clippy/issues/4836 + if cx.tcx.is_diagnostic_item(sym::IoRead, trait_id) || cx.tcx.is_diagnostic_item(sym::IoWrite, trait_id) { + return; + } + + let async_paths: [&[&str]; 4] = [ + &paths::TOKIO_IO_ASYNCREADEXT, + &paths::TOKIO_IO_ASYNCWRITEEXT, + &paths::FUTURES_IO_ASYNCREADEXT, + &paths::FUTURES_IO_ASYNCWRITEEXT, + ]; + + if async_paths.into_iter().any(|path| match_def_path(cx, trait_id, path)) { + return; + } + } + for stmt in block.stmts { if let hir::StmtKind::Semi(exp) = stmt.kind { check_expr(cx, exp); @@ -222,7 +244,7 @@ fn unpack_call_chain<'a>(mut expr: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> { } fn unpack_try<'a>(mut expr: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> { - while let ExprKind::Call(func, [ref arg_0]) = expr.kind + while let ExprKind::Call(func, [arg_0]) = expr.kind && matches!( func.kind, ExprKind::Path(hir::QPath::LangItem(hir::LangItem::TryTraitBranch, ..)) @@ -244,7 +266,7 @@ fn unpack_match<'a>(mut expr: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> { /// waited on. Otherwise return None. fn unpack_await<'a>(expr: &'a hir::Expr<'a>) -> &'a hir::Expr<'a> { if let ExprKind::Match(expr, _, hir::MatchSource::AwaitDesugar) = expr.kind { - if let ExprKind::Call(func, [ref arg_0]) = expr.kind { + if let ExprKind::Call(func, [arg_0]) = expr.kind { if matches!( func.kind, ExprKind::Path(hir::QPath::LangItem(hir::LangItem::IntoFutureIntoFuture, ..)) diff --git a/clippy_lints/src/utils/attr_collector.rs b/clippy_lints/src/utils/attr_collector.rs new file mode 100644 index 0000000000000..1522553bbf523 --- /dev/null +++ b/clippy_lints/src/utils/attr_collector.rs @@ -0,0 +1,40 @@ +use std::mem; +use std::sync::OnceLock; + +use rustc_ast::{Attribute, Crate}; +use rustc_data_structures::sync::Lrc; +use rustc_lint::{EarlyContext, EarlyLintPass}; +use rustc_session::impl_lint_pass; +use rustc_span::Span; + +#[derive(Clone, Default)] +pub struct AttrStorage(pub Lrc>>); + +pub struct AttrCollector { + storage: AttrStorage, + attrs: Vec, +} + +impl AttrCollector { + pub fn new(storage: AttrStorage) -> Self { + Self { + storage, + attrs: Vec::new(), + } + } +} + +impl_lint_pass!(AttrCollector => []); + +impl EarlyLintPass for AttrCollector { + fn check_attribute(&mut self, _cx: &EarlyContext<'_>, attr: &Attribute) { + self.attrs.push(attr.span); + } + + fn check_crate_post(&mut self, _: &EarlyContext<'_>, _: &Crate) { + self.storage + .0 + .set(mem::take(&mut self.attrs)) + .expect("should only be called once"); + } +} diff --git a/clippy_lints/src/utils/author.rs b/clippy_lints/src/utils/author.rs index 31f9d84f5e466..1bf24083665fd 100644 --- a/clippy_lints/src/utils/author.rs +++ b/clippy_lints/src/utils/author.rs @@ -396,7 +396,7 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> { self.pat(field!(let_expr.pat)); // Does what ExprKind::Cast does, only adds a clause for the type // if it's a path - if let Some(TyKind::Path(ref qpath)) = let_expr.value.ty.as_ref().map(|ty| &ty.kind) { + if let Some(TyKind::Path(qpath)) = let_expr.value.ty.as_ref().map(|ty| &ty.kind) { bind!(self, qpath); chain!(self, "let TyKind::Path(ref {qpath}) = {let_expr}.ty.kind"); self.qpath(qpath); diff --git a/clippy_lints/src/utils/internal_lints.rs b/clippy_lints/src/utils/internal_lints.rs index f662c7651f6bb..deb983b6971dc 100644 --- a/clippy_lints/src/utils/internal_lints.rs +++ b/clippy_lints/src/utils/internal_lints.rs @@ -6,5 +6,6 @@ pub mod lint_without_lint_pass; pub mod msrv_attr_impl; pub mod outer_expn_data_pass; pub mod produce_ice; +pub mod slow_symbol_comparisons; pub mod unnecessary_def_path; pub mod unsorted_clippy_utils_paths; diff --git a/clippy_lints/src/utils/internal_lints/collapsible_calls.rs b/clippy_lints/src/utils/internal_lints/collapsible_calls.rs index a3f9abe4f9613..eaeb754a23f1b 100644 --- a/clippy_lints/src/utils/internal_lints/collapsible_calls.rs +++ b/clippy_lints/src/utils/internal_lints/collapsible_calls.rs @@ -79,7 +79,7 @@ impl<'tcx> LateLintPass<'tcx> for CollapsibleCalls { if let ExprKind::Call(func, [call_cx, call_lint, call_sp, call_msg, call_f]) = expr.kind && is_expr_path_def_path(cx, func, &["clippy_utils", "diagnostics", "span_lint_and_then"]) - && let ExprKind::Closure(&Closure { body, .. }) = &call_f.kind + && let ExprKind::Closure(&Closure { body, .. }) = call_f.kind && let body = cx.tcx.hir().body(body) && let only_expr = peel_blocks_with_stmt(body.value) && let ExprKind::MethodCall(ps, recv, span_call_args, _) = &only_expr.kind diff --git a/clippy_lints/src/utils/internal_lints/lint_without_lint_pass.rs b/clippy_lints/src/utils/internal_lints/lint_without_lint_pass.rs index 51235de9f29f8..af38e066559cf 100644 --- a/clippy_lints/src/utils/internal_lints/lint_without_lint_pass.rs +++ b/clippy_lints/src/utils/internal_lints/lint_without_lint_pass.rs @@ -21,7 +21,7 @@ declare_clippy_lint! { /// /// ### Why is this bad? /// The compiler only knows lints via a `LintPass`. Without - /// putting a lint to a `LintPass::get_lints()`'s return, the compiler will not + /// putting a lint to a `LintPass::lint_vec()`'s return, the compiler will not /// know the name of the lint. /// /// ### Known problems @@ -123,7 +123,7 @@ impl<'tcx> LateLintPass<'tcx> for LintWithoutLintPass { .expect("lints must have a description field"); if let ExprKind::Lit(Spanned { - node: LitKind::Str(ref sym, _), + node: LitKind::Str(sym, _), .. }) = field.expr.kind { @@ -159,8 +159,8 @@ impl<'tcx> LateLintPass<'tcx> for LintWithoutLintPass { let body = cx.tcx.hir().body_owned_by( impl_item_refs .iter() - .find(|iiref| iiref.ident.as_str() == "get_lints") - .expect("LintPass needs to implement get_lints") + .find(|iiref| iiref.ident.as_str() == "lint_vec") + .expect("LintPass needs to implement lint_vec") .id .owner_id .def_id, @@ -218,9 +218,7 @@ pub(super) fn is_lint_ref_type(cx: &LateContext<'_>, ty: &hir::Ty<'_>) -> bool { fn check_invalid_clippy_version_attribute(cx: &LateContext<'_>, item: &'_ Item<'_>) { if let Some(value) = extract_clippy_version_value(cx, item) { - // The `sym!` macro doesn't work as it only expects a single token. - // It's better to keep it this way and have a direct `Symbol::intern` call here. - if value == Symbol::intern("pre 1.29.0") { + if value.as_str() == "pre 1.29.0" { return; } @@ -251,7 +249,7 @@ fn check_invalid_clippy_version_attribute(cx: &LateContext<'_>, item: &'_ Item<' pub(super) fn extract_clippy_version_value(cx: &LateContext<'_>, item: &'_ Item<'_>) -> Option { let attrs = cx.tcx.hir().attrs(item.hir_id()); attrs.iter().find_map(|attr| { - if let ast::AttrKind::Normal(ref attr_kind) = &attr.kind + if let ast::AttrKind::Normal(attr_kind) = &attr.kind // Identify attribute && let [tool_name, attr_name] = &attr_kind.item.path.segments[..] && tool_name.ident.name == sym::clippy diff --git a/clippy_lints/src/utils/internal_lints/msrv_attr_impl.rs b/clippy_lints/src/utils/internal_lints/msrv_attr_impl.rs index 63fcbd61528d2..6869224615303 100644 --- a/clippy_lints/src/utils/internal_lints/msrv_attr_impl.rs +++ b/clippy_lints/src/utils/internal_lints/msrv_attr_impl.rs @@ -42,7 +42,7 @@ impl LateLintPass<'_> for MsrvAttrImpl { .filter(|t| matches!(t.unpack(), GenericArgKind::Type(_))) .any(|t| match_type(cx, t.expect_ty(), &paths::MSRV)) }) - && !items.iter().any(|item| item.ident.name == sym!(check_attributes)) + && !items.iter().any(|item| item.ident.name.as_str() == "check_attributes") { let context = if is_late_pass { "LateContext" } else { "EarlyContext" }; let lint_pass = if is_late_pass { "LateLintPass" } else { "EarlyLintPass" }; diff --git a/clippy_lints/src/utils/internal_lints/slow_symbol_comparisons.rs b/clippy_lints/src/utils/internal_lints/slow_symbol_comparisons.rs new file mode 100644 index 0000000000000..3742be0e103d6 --- /dev/null +++ b/clippy_lints/src/utils/internal_lints/slow_symbol_comparisons.rs @@ -0,0 +1,69 @@ +use clippy_utils::consts::{ConstEvalCtxt, Constant}; +use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::source::snippet_with_applicability; +use clippy_utils::ty::match_type; +use clippy_utils::{match_function_call, paths}; +use rustc_errors::Applicability; +use rustc_hir::{BinOpKind, Expr, ExprKind}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::declare_lint_pass; +use rustc_span::Span; + +declare_clippy_lint! { + /// ### What it does + /// + /// Detects symbol comparision using `Symbol::intern`. + /// + /// ### Why is this bad? + /// + /// Comparision via `Symbol::as_str()` is faster if the interned symbols are not reused. + /// + /// ### Example + /// + /// None, see suggestion. + pub SLOW_SYMBOL_COMPARISONS, + internal, + "detects slow comparisions of symbol" +} + +declare_lint_pass!(SlowSymbolComparisons => [SLOW_SYMBOL_COMPARISONS]); + +fn check_slow_comparison<'tcx>( + cx: &LateContext<'tcx>, + op1: &'tcx Expr<'tcx>, + op2: &'tcx Expr<'tcx>, +) -> Option<(Span, String)> { + if match_type(cx, cx.typeck_results().expr_ty(op1), &paths::SYMBOL) + && let Some([symbol_name_expr]) = match_function_call(cx, op2, &paths::SYMBOL_INTERN) + && let Some(Constant::Str(symbol_name)) = ConstEvalCtxt::new(cx).eval_simple(symbol_name_expr) + { + Some((op1.span, symbol_name)) + } else { + None + } +} + +impl<'tcx> LateLintPass<'tcx> for SlowSymbolComparisons { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) { + if let ExprKind::Binary(op, left, right) = expr.kind + && (op.node == BinOpKind::Eq || op.node == BinOpKind::Ne) + && let Some((symbol_span, symbol_name)) = + check_slow_comparison(cx, left, right).or_else(|| check_slow_comparison(cx, right, left)) + { + let mut applicability = Applicability::MachineApplicable; + span_lint_and_sugg( + cx, + SLOW_SYMBOL_COMPARISONS, + expr.span, + "comparing `Symbol` via `Symbol::intern`", + "use `Symbol::as_str` and check the string instead", + format!( + "{}.as_str() {} \"{symbol_name}\"", + snippet_with_applicability(cx, symbol_span, "symbol", &mut applicability), + op.node.as_str() + ), + applicability, + ); + }; + } +} diff --git a/clippy_lints/src/utils/mod.rs b/clippy_lints/src/utils/mod.rs index 13e9ead9a57f9..4476cd1005e7e 100644 --- a/clippy_lints/src/utils/mod.rs +++ b/clippy_lints/src/utils/mod.rs @@ -1,5 +1,7 @@ +pub mod attr_collector; pub mod author; pub mod dump_hir; pub mod format_args_collector; + #[cfg(feature = "internal")] pub mod internal_lints; diff --git a/clippy_utils/src/check_proc_macro.rs b/clippy_utils/src/check_proc_macro.rs index c5e2c8c09a277..75169e05734aa 100644 --- a/clippy_utils/src/check_proc_macro.rs +++ b/clippy_utils/src/check_proc_macro.rs @@ -21,7 +21,7 @@ use rustc_hir::{ ImplItem, ImplItemKind, IsAuto, Item, ItemKind, Lit, LoopSource, MatchSource, MutTy, Node, Path, QPath, Safety, TraitItem, TraitItemKind, Ty, TyKind, UnOp, UnsafeSource, Variant, VariantData, YieldSource, }; -use rustc_lint::{LateContext, LintContext, EarlyContext}; +use rustc_lint::{EarlyContext, LateContext, LintContext}; use rustc_middle::ty::TyCtxt; use rustc_session::Session; use rustc_span::symbol::{Ident, kw}; @@ -63,8 +63,8 @@ fn span_matches_pat(sess: &Session, span: Span, start_pat: Pat, end_pat: Pat) -> Pat::Num => start_str.as_bytes().first().map_or(false, u8::is_ascii_digit), } && match end_pat { Pat::Str(text) => end_str.ends_with(text), - Pat::MultiStr(texts) => texts.iter().any(|s| start_str.ends_with(s)), - Pat::OwnedMultiStr(texts) => texts.iter().any(|s| start_str.starts_with(s)), + Pat::MultiStr(texts) => texts.iter().any(|s| end_str.ends_with(s)), + Pat::OwnedMultiStr(texts) => texts.iter().any(|s| end_str.ends_with(s)), Pat::Sym(sym) => end_str.ends_with(sym.as_str()), Pat::Num => end_str.as_bytes().last().map_or(false, u8::is_ascii_hexdigit), }) @@ -333,26 +333,32 @@ fn attr_search_pat(attr: &Attribute) -> (Pat, Pat) { match attr.kind { AttrKind::Normal(..) => { if let Some(ident) = attr.ident() { - // TODO: I feel like it's likely we can use `Cow` instead but this will require quite a bit of - // refactoring // NOTE: This will likely have false positives, like `allow = 1` - ( - Pat::OwnedMultiStr(vec![ident.to_string(), "#".to_owned()]), - Pat::Str(""), - ) + let ident_string = ident.to_string(); + if attr.style == AttrStyle::Outer { + ( + Pat::OwnedMultiStr(vec!["#[".to_owned() + &ident_string, ident_string]), + Pat::Str(""), + ) + } else { + ( + Pat::OwnedMultiStr(vec!["#![".to_owned() + &ident_string, ident_string]), + Pat::Str(""), + ) + } } else { (Pat::Str("#"), Pat::Str("]")) } }, AttrKind::DocComment(_kind @ CommentKind::Line, ..) => { - if matches!(attr.style, AttrStyle::Outer) { + if attr.style == AttrStyle::Outer { (Pat::Str("///"), Pat::Str("")) } else { (Pat::Str("//!"), Pat::Str("")) } }, AttrKind::DocComment(_kind @ CommentKind::Block, ..) => { - if matches!(attr.style, AttrStyle::Outer) { + if attr.style == AttrStyle::Outer { (Pat::Str("/**"), Pat::Str("*/")) } else { (Pat::Str("/*!"), Pat::Str("*/")) diff --git a/clippy_utils/src/consts.rs b/clippy_utils/src/consts.rs index 67c31abbddaa4..24a02c7ef871a 100644 --- a/clippy_utils/src/consts.rs +++ b/clippy_utils/src/consts.rs @@ -1,3 +1,7 @@ +//! A simple const eval API, for use on arbitrary HIR expressions. +//! +//! This cannot use rustc's const eval, aka miri, as arbitrary HIR expressions cannot be lowered to +//! executable MIR bodies, so we have to do this instead. #![allow(clippy::float_cmp)] use crate::macros::HirNode; @@ -379,6 +383,8 @@ impl Ord for FullInt { /// The context required to evaluate a constant expression. /// /// This is currently limited to constant folding and reading the value of named constants. +/// +/// See the module level documentation for some context. pub struct ConstEvalCtxt<'tcx> { tcx: TyCtxt<'tcx>, param_env: ParamEnv<'tcx>, diff --git a/clippy_utils/src/diagnostics.rs b/clippy_utils/src/diagnostics.rs index 4877fb65d37d7..993035001c1be 100644 --- a/clippy_utils/src/diagnostics.rs +++ b/clippy_utils/src/diagnostics.rs @@ -8,7 +8,9 @@ //! Thank you! //! ~The `INTERNAL_METADATA_COLLECTOR` lint -use rustc_errors::{Applicability, Diag, DiagMessage, MultiSpan, SubdiagMessage}; +use rustc_errors::{ + Applicability, Diag, DiagMessage, EmissionGuarantee, MultiSpan, SubdiagMessage, SubstitutionPart, Suggestions, +}; use rustc_hir::HirId; use rustc_lint::{LateContext, Lint, LintContext}; use rustc_span::Span; @@ -28,6 +30,42 @@ fn docs_link(diag: &mut Diag<'_, ()>, lint: &'static Lint) { } } +/// Makes sure that a diagnostic is well formed. +/// +/// rustc debug asserts a few properties about spans, +/// but the clippy repo uses a distributed rustc build with debug assertions disabled, +/// so this has historically led to problems during subtree syncs where those debug assertions +/// only started triggered there. +/// +/// This function makes sure we also validate them in debug clippy builds. +fn validate_diag(diag: &Diag<'_, impl EmissionGuarantee>) { + let suggestions = match &diag.suggestions { + Suggestions::Enabled(suggs) => &**suggs, + Suggestions::Sealed(suggs) => &**suggs, + Suggestions::Disabled => return, + }; + + for substitution in suggestions.iter().flat_map(|s| &s.substitutions) { + assert_eq!( + substitution + .parts + .iter() + .find(|SubstitutionPart { snippet, span }| snippet.is_empty() && span.is_empty()), + None, + "span must not be empty and have no suggestion" + ); + + assert_eq!( + substitution + .parts + .array_windows() + .find(|[a, b]| a.span.overlaps(b.span)), + None, + "suggestion must not have overlapping parts" + ); + } +} + /// Emit a basic lint message with a `msg` and a `span`. /// /// This is the most primitive of our lint emission methods and can @@ -64,6 +102,9 @@ pub fn span_lint(cx: &T, lint: &'static Lint, sp: impl Into( diag.help(help.into()); } docs_link(diag, lint); + + #[cfg(debug_assertions)] + validate_diag(diag); }); } @@ -175,6 +219,9 @@ pub fn span_lint_and_note( diag.note(note.into()); } docs_link(diag, lint); + + #[cfg(debug_assertions)] + validate_diag(diag); }); } @@ -208,6 +255,9 @@ where diag.primary_message(msg); f(diag); docs_link(diag, lint); + + #[cfg(debug_assertions)] + validate_diag(diag); }); } @@ -240,6 +290,9 @@ pub fn span_lint_hir(cx: &LateContext<'_>, lint: &'static Lint, hir_id: HirId, s cx.tcx.node_span_lint(lint, hir_id, sp, |diag| { diag.primary_message(msg); docs_link(diag, lint); + + #[cfg(debug_assertions)] + validate_diag(diag); }); } @@ -280,6 +333,9 @@ pub fn span_lint_hir_and_then( diag.primary_message(msg); f(diag); docs_link(diag, lint); + + #[cfg(debug_assertions)] + validate_diag(diag); }); } @@ -316,7 +372,7 @@ pub fn span_lint_hir_and_then( /// | /// = note: `-D fold-any` implied by `-D warnings` /// ``` -#[expect(clippy::collapsible_span_lint_calls)] +#[cfg_attr(not(debug_assertions), expect(clippy::collapsible_span_lint_calls))] pub fn span_lint_and_sugg( cx: &T, lint: &'static Lint, @@ -328,5 +384,8 @@ pub fn span_lint_and_sugg( ) { span_lint_and_then(cx, lint, sp, msg.into(), |diag| { diag.span_suggestion(sp, help.into(), sugg, applicability); + + #[cfg(debug_assertions)] + validate_diag(diag); }); } diff --git a/clippy_utils/src/higher.rs b/clippy_utils/src/higher.rs index 3175a9a1dd31f..11bbe734844d3 100644 --- a/clippy_utils/src/higher.rs +++ b/clippy_utils/src/higher.rs @@ -103,7 +103,7 @@ impl<'hir> IfLet<'hir> { /// Parses an `if let` expression pub fn hir(cx: &LateContext<'_>, expr: &Expr<'hir>) -> Option { if let ExprKind::If( - Expr { + &Expr { kind: ExprKind::Let(&hir::LetExpr { pat: let_pat, @@ -381,12 +381,12 @@ impl<'hir> WhileLet<'hir> { /// Parses a desugared `while let` loop pub const fn hir(expr: &Expr<'hir>) -> Option { if let ExprKind::Loop( - Block { + &Block { expr: - Some(Expr { + Some(&Expr { kind: ExprKind::If( - Expr { + &Expr { kind: ExprKind::Let(&hir::LetExpr { pat: let_pat, diff --git a/clippy_utils/src/hir_utils.rs b/clippy_utils/src/hir_utils.rs index 8004bc68b2ea9..cb69f8e5a0ede 100644 --- a/clippy_utils/src/hir_utils.rs +++ b/clippy_utils/src/hir_utils.rs @@ -128,7 +128,7 @@ impl<'a, 'tcx> SpanlessEq<'a, 'tcx> { self.inter_expr().eq_path_segments(left, right) } - pub fn eq_modifiers(&mut self, left: TraitBoundModifiers, right: TraitBoundModifiers) -> bool { + pub fn eq_modifiers(left: TraitBoundModifiers, right: TraitBoundModifiers) -> bool { std::mem::discriminant(&left.constness) == std::mem::discriminant(&right.constness) && std::mem::discriminant(&left.polarity) == std::mem::discriminant(&right.polarity) } @@ -1201,11 +1201,11 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { self.hash_ty(ty); self.hash_pat(pat); }, - TyKind::Ptr(ref mut_ty) => { + TyKind::Ptr(mut_ty) => { self.hash_ty(mut_ty.ty); mut_ty.mutbl.hash(&mut self.s); }, - TyKind::Ref(lifetime, ref mut_ty) => { + TyKind::Ref(lifetime, mut_ty) => { self.hash_lifetime(lifetime); self.hash_ty(mut_ty.ty); mut_ty.mutbl.hash(&mut self.s); @@ -1230,14 +1230,19 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { self.hash_ty(ty); } }, - TyKind::Path(ref qpath) => self.hash_qpath(qpath), + TyKind::Path(qpath) => self.hash_qpath(qpath), TyKind::TraitObject(_, lifetime, _) => { self.hash_lifetime(lifetime); }, TyKind::Typeof(anon_const) => { self.hash_body(anon_const.body); }, - TyKind::Err(_) | TyKind::Infer | TyKind::Never | TyKind::InferDelegation(..) | TyKind::OpaqueDef(_) | TyKind::AnonAdt(_) => {}, + TyKind::Err(_) + | TyKind::Infer + | TyKind::Never + | TyKind::InferDelegation(..) + | TyKind::OpaqueDef(_) + | TyKind::AnonAdt(_) => {}, } } diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 0f712068e6580..50d334acf18e5 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -9,6 +9,7 @@ #![feature(rustc_private)] #![feature(assert_matches)] #![feature(unwrap_infallible)] +#![feature(array_windows)] #![recursion_limit = "512"] #![allow( clippy::missing_errors_doc, @@ -688,11 +689,11 @@ pub fn find_crates(tcx: TyCtxt<'_>, name: Symbol) -> Vec { /// /// This function is expensive and should be used sparingly. pub fn def_path_res(tcx: TyCtxt<'_>, path: &[&str]) -> Vec { - let (base, path) = match *path { + let (base, path) = match path { [primitive] => { return vec![PrimTy::from_name(Symbol::intern(primitive)).map_or(Res::Err, Res::PrimTy)]; }, - [base, ref path @ ..] => (base, path), + [base, path @ ..] => (base, path), _ => return Vec::new(), }; @@ -744,7 +745,7 @@ pub fn def_path_res_with_base(tcx: TyCtxt<'_>, mut base: Vec, mut path: &[& } /// Resolves a def path like `std::vec::Vec` to its [`DefId`]s, see [`def_path_res`]. -pub fn def_path_def_ids(tcx: TyCtxt<'_>, path: &[&str]) -> impl Iterator { +pub fn def_path_def_ids(tcx: TyCtxt<'_>, path: &[&str]) -> impl Iterator + use<> { def_path_res(tcx, path).into_iter().filter_map(|res| res.opt_def_id()) } @@ -934,7 +935,7 @@ pub fn is_default_equivalent(cx: &LateContext<'_>, e: &Expr<'_>) -> bool { } }, ExprKind::Call(repl_func, []) => is_default_equivalent_call(cx, repl_func), - ExprKind::Call(from_func, [ref arg]) => is_default_equivalent_from(cx, from_func, arg), + ExprKind::Call(from_func, [arg]) => is_default_equivalent_from(cx, from_func, arg), ExprKind::Path(qpath) => is_res_lang_ctor(cx, cx.qpath_res(qpath, e.hir_id), OptionNone), ExprKind::AddrOf(rustc_hir::BorrowKind::Ref, _, expr) => matches!(expr.kind, ExprKind::Array([])), _ => false, @@ -947,7 +948,7 @@ fn is_default_equivalent_from(cx: &LateContext<'_>, from_func: &Expr<'_>, arg: & { match arg.kind { ExprKind::Lit(hir::Lit { - node: LitKind::Str(ref sym, _), + node: LitKind::Str(sym, _), .. }) => return sym.is_empty() && is_path_lang_item(cx, ty, LangItem::String), ExprKind::Array([]) => return is_path_diagnostic_item(cx, ty, sym::Vec), @@ -1337,15 +1338,17 @@ pub fn get_item_name(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option { pub struct ContainsName<'a, 'tcx> { pub cx: &'a LateContext<'tcx>, pub name: Symbol, - pub result: bool, } impl<'tcx> Visitor<'tcx> for ContainsName<'_, 'tcx> { + type Result = ControlFlow<()>; type NestedFilter = nested_filter::OnlyBodies; - fn visit_name(&mut self, name: Symbol) { + fn visit_name(&mut self, name: Symbol) -> Self::Result { if self.name == name { - self.result = true; + ControlFlow::Break(()) + } else { + ControlFlow::Continue(()) } } @@ -1356,13 +1359,8 @@ impl<'tcx> Visitor<'tcx> for ContainsName<'_, 'tcx> { /// Checks if an `Expr` contains a certain name. pub fn contains_name<'tcx>(name: Symbol, expr: &'tcx Expr<'_>, cx: &LateContext<'tcx>) -> bool { - let mut cn = ContainsName { - name, - result: false, - cx, - }; - cn.visit_expr(expr); - cn.result + let mut cn = ContainsName { cx, name }; + cn.visit_expr(expr).is_break() } /// Returns `true` if `expr` contains a return expression @@ -3459,7 +3457,7 @@ fn maybe_get_relative_path(from: &DefPath, to: &DefPath, max_super: usize) -> St pub fn is_parent_stmt(cx: &LateContext<'_>, id: HirId) -> bool { matches!( cx.tcx.parent_hir_node(id), - Node::Stmt(..) | Node::Block(Block { stmts: &[], .. }) + Node::Stmt(..) | Node::Block(Block { stmts: [], .. }) ) } diff --git a/clippy_utils/src/ty.rs b/clippy_utils/src/ty.rs index 41785e161d0ce..ce1a20e0066de 100644 --- a/clippy_utils/src/ty.rs +++ b/clippy_utils/src/ty.rs @@ -19,7 +19,7 @@ use rustc_middle::ty::layout::ValidityRequirement; use rustc_middle::ty::{ self, AdtDef, AliasTy, AssocItem, AssocKind, Binder, BoundRegion, FnSig, GenericArg, GenericArgKind, GenericArgsRef, GenericParamDefKind, IntTy, ParamEnv, Region, RegionKind, TraitRef, Ty, TyCtxt, TypeSuperVisitable, - TypeVisitable, TypeVisitableExt, TypeVisitor, UintTy, Upcast, VariantDef, VariantDiscr, TypingMode, + TypeVisitable, TypeVisitableExt, TypeVisitor, TypingMode, UintTy, Upcast, VariantDef, VariantDiscr, }; use rustc_span::symbol::Ident; use rustc_span::{DUMMY_SP, Span, Symbol, sym}; @@ -274,11 +274,7 @@ pub fn implements_trait_with_env_from_iter<'tcx>( .map(|arg| arg.into().unwrap_or_else(|| infcx.next_ty_var(DUMMY_SP).into())) .collect::>(); - let trait_ref = TraitRef::new( - tcx, - trait_id, - [GenericArg::from(ty)].into_iter().chain(args), - ); + let trait_ref = TraitRef::new(tcx, trait_id, [GenericArg::from(ty)].into_iter().chain(args)); debug_assert_matches!( tcx.def_kind(trait_id), @@ -975,9 +971,7 @@ pub fn approx_ty_size<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> u64 { match (cx.layout_of(ty).map(|layout| layout.size.bytes()), ty.kind()) { (Ok(size), _) => size, (Err(_), ty::Tuple(list)) => list.iter().map(|t| approx_ty_size(cx, t)).sum(), - (Err(_), ty::Array(t, n)) => { - n.try_to_target_usize(cx.tcx).unwrap_or_default() * approx_ty_size(cx, *t) - }, + (Err(_), ty::Array(t, n)) => n.try_to_target_usize(cx.tcx).unwrap_or_default() * approx_ty_size(cx, *t), (Err(_), ty::Adt(def, subst)) if def.is_struct() => def .variants() .iter() @@ -1284,7 +1278,12 @@ pub fn make_normalized_projection_with_regions<'tcx>( pub fn normalize_with_regions<'tcx>(tcx: TyCtxt<'tcx>, param_env: ParamEnv<'tcx>, ty: Ty<'tcx>) -> Ty<'tcx> { let cause = ObligationCause::dummy(); - match tcx.infer_ctxt().build(TypingMode::from_param_env(param_env)).at(&cause, param_env).query_normalize(ty) { + match tcx + .infer_ctxt() + .build(TypingMode::from_param_env(param_env)) + .at(&cause, param_env) + .query_normalize(ty) + { Ok(ty) => ty.value, Err(_) => ty, } diff --git a/clippy_utils/src/ty/type_certainty/mod.rs b/clippy_utils/src/ty/type_certainty/mod.rs index 3021f21df1210..d7640ebfb0061 100644 --- a/clippy_utils/src/ty/type_certainty/mod.rs +++ b/clippy_utils/src/ty/type_certainty/mod.rs @@ -302,9 +302,9 @@ fn type_is_inferable_from_arguments(cx: &LateContext<'_>, expr: &Expr<'_>) -> bo // Check that all type parameters appear in the functions input types. (0..(generics.parent_count + generics.own_params.len()) as u32).all(|index| { fn_sig - .inputs() - .iter() - .any(|input_ty| contains_param(*input_ty.skip_binder(), index)) + .inputs() + .iter() + .any(|input_ty| contains_param(*input_ty.skip_binder(), index)) }) } diff --git a/clippy_utils/src/usage.rs b/clippy_utils/src/usage.rs index 4bfc9dd92135e..c8c25456f6965 100644 --- a/clippy_utils/src/usage.rs +++ b/clippy_utils/src/usage.rs @@ -109,34 +109,28 @@ impl<'tcx> Visitor<'tcx> for ParamBindingIdCollector { pub struct BindingUsageFinder<'a, 'tcx> { cx: &'a LateContext<'tcx>, binding_ids: Vec, - usage_found: bool, } impl<'a, 'tcx> BindingUsageFinder<'a, 'tcx> { pub fn are_params_used(cx: &'a LateContext<'tcx>, body: &'tcx hir::Body<'tcx>) -> bool { let mut finder = BindingUsageFinder { cx, binding_ids: ParamBindingIdCollector::collect_binding_hir_ids(body), - usage_found: false, }; - finder.visit_body(body); - finder.usage_found + finder.visit_body(body).is_break() } } impl<'tcx> Visitor<'tcx> for BindingUsageFinder<'_, 'tcx> { + type Result = ControlFlow<()>; type NestedFilter = nested_filter::OnlyBodies; - fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { - if !self.usage_found { - intravisit::walk_expr(self, expr); - } - } - - fn visit_path(&mut self, path: &hir::Path<'tcx>, _: HirId) { + fn visit_path(&mut self, path: &hir::Path<'tcx>, _: HirId) -> Self::Result { if let Res::Local(id) = path.res { if self.binding_ids.contains(&id) { - self.usage_found = true; + return ControlFlow::Break(()); } } + + ControlFlow::Continue(()) } fn nested_visit_map(&mut self) -> Self::Map { diff --git a/clippy_utils/src/visitors.rs b/clippy_utils/src/visitors.rs index 8db6502dbfb20..8f5ec185bf15c 100644 --- a/clippy_utils/src/visitors.rs +++ b/clippy_utils/src/visitors.rs @@ -324,17 +324,15 @@ pub fn is_local_used<'tcx>(cx: &LateContext<'tcx>, visitable: impl Visitable<'tc pub fn is_const_evaluatable<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> bool { struct V<'a, 'tcx> { cx: &'a LateContext<'tcx>, - is_const: bool, } + impl<'tcx> Visitor<'tcx> for V<'_, 'tcx> { + type Result = ControlFlow<()>; type NestedFilter = intravisit::nested_filter::None; - fn visit_expr(&mut self, e: &'tcx Expr<'_>) { - if !self.is_const { - return; - } + fn visit_expr(&mut self, e: &'tcx Expr<'_>) -> Self::Result { match e.kind { - ExprKind::ConstBlock(_) => return, + ExprKind::ConstBlock(_) => return ControlFlow::Continue(()), ExprKind::Call( &Expr { kind: ExprKind::Path(ref p), @@ -394,37 +392,34 @@ pub fn is_const_evaluatable<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> | ExprKind::Type(..) => (), _ => { - self.is_const = false; - return; + return ControlFlow::Break(()); }, } - walk_expr(self, e); + + walk_expr(self, e) } } - let mut v = V { cx, is_const: true }; - v.visit_expr(e); - v.is_const + let mut v = V { cx }; + v.visit_expr(e).is_continue() } /// Checks if the given expression performs an unsafe operation outside of an unsafe block. pub fn is_expr_unsafe<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> bool { struct V<'a, 'tcx> { cx: &'a LateContext<'tcx>, - is_unsafe: bool, } impl<'tcx> Visitor<'tcx> for V<'_, 'tcx> { type NestedFilter = nested_filter::OnlyBodies; + type Result = ControlFlow<()>; + fn nested_visit_map(&mut self) -> Self::Map { self.cx.tcx.hir() } - fn visit_expr(&mut self, e: &'tcx Expr<'_>) { - if self.is_unsafe { - return; - } + fn visit_expr(&mut self, e: &'tcx Expr<'_>) -> Self::Result { match e.kind { ExprKind::Unary(UnOp::Deref, e) if self.cx.typeck_results().expr_ty(e).is_unsafe_ptr() => { - self.is_unsafe = true; + ControlFlow::Break(()) }, ExprKind::MethodCall(..) if self @@ -435,13 +430,13 @@ pub fn is_expr_unsafe<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> bool { self.cx.tcx.fn_sig(id).skip_binder().safety() == Safety::Unsafe }) => { - self.is_unsafe = true; + ControlFlow::Break(()) }, ExprKind::Call(func, _) => match *self.cx.typeck_results().expr_ty(func).peel_refs().kind() { ty::FnDef(id, _) if self.cx.tcx.fn_sig(id).skip_binder().safety() == Safety::Unsafe => { - self.is_unsafe = true; + ControlFlow::Break(()) }, - ty::FnPtr(_, hdr) if hdr.safety == Safety::Unsafe => self.is_unsafe = true, + ty::FnPtr(_, hdr) if hdr.safety == Safety::Unsafe => ControlFlow::Break(()), _ => walk_expr(self, e), }, ExprKind::Path(ref p) @@ -451,56 +446,54 @@ pub fn is_expr_unsafe<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> bool { .opt_def_id() .map_or(false, |id| self.cx.tcx.is_mutable_static(id)) => { - self.is_unsafe = true; + ControlFlow::Break(()) }, _ => walk_expr(self, e), } } - fn visit_block(&mut self, b: &'tcx Block<'_>) { - if !matches!(b.rules, BlockCheckMode::UnsafeBlock(_)) { - walk_block(self, b); + fn visit_block(&mut self, b: &'tcx Block<'_>) -> Self::Result { + if matches!(b.rules, BlockCheckMode::UnsafeBlock(_)) { + ControlFlow::Continue(()) + } else { + walk_block(self, b) } } - fn visit_nested_item(&mut self, id: ItemId) { - if let ItemKind::Impl(i) = &self.cx.tcx.hir().item(id).kind { - self.is_unsafe = i.safety == Safety::Unsafe; + fn visit_nested_item(&mut self, id: ItemId) -> Self::Result { + if let ItemKind::Impl(i) = &self.cx.tcx.hir().item(id).kind + && i.safety == Safety::Unsafe + { + ControlFlow::Break(()) + } else { + ControlFlow::Continue(()) } } } - let mut v = V { cx, is_unsafe: false }; - v.visit_expr(e); - v.is_unsafe + let mut v = V { cx }; + v.visit_expr(e).is_break() } /// Checks if the given expression contains an unsafe block pub fn contains_unsafe_block<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) -> bool { struct V<'cx, 'tcx> { cx: &'cx LateContext<'tcx>, - found_unsafe: bool, } impl<'tcx> Visitor<'tcx> for V<'_, 'tcx> { + type Result = ControlFlow<()>; type NestedFilter = nested_filter::OnlyBodies; fn nested_visit_map(&mut self) -> Self::Map { self.cx.tcx.hir() } - fn visit_block(&mut self, b: &'tcx Block<'_>) { - if self.found_unsafe { - return; - } + fn visit_block(&mut self, b: &'tcx Block<'_>) -> Self::Result { if b.rules == BlockCheckMode::UnsafeBlock(UnsafeSource::UserProvided) { - self.found_unsafe = true; - return; + ControlFlow::Break(()) + } else { + walk_block(self, b) } - walk_block(self, b); } } - let mut v = V { - cx, - found_unsafe: false, - }; - v.visit_expr(e); - v.found_unsafe + let mut v = V { cx }; + v.visit_expr(e).is_break() } /// Runs the given function for each sub-expression producing the final value consumed by the parent diff --git a/rust-toolchain b/rust-toolchain index e11ab40b9de33..37d9cce24650c 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1,4 +1,4 @@ [toolchain] -channel = "nightly-2024-10-18" +channel = "nightly-2024-11-07" components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"] profile = "minimal" diff --git a/tests/compile-test.rs b/tests/compile-test.rs index 5774e20e0be51..b8e0413e97bc1 100644 --- a/tests/compile-test.rs +++ b/tests/compile-test.rs @@ -396,7 +396,8 @@ struct Renderer<'a> { impl Renderer<'_> { fn markdown(input: &str) -> Safe { - let parser = Parser::new_ext(input, Options::all()); + let input = clippy_config::sanitize_explanation(input); + let parser = Parser::new_ext(&input, Options::all()); let mut html_output = String::new(); html::push_html(&mut html_output, parser); // Oh deer, what a hack :O diff --git a/tests/ui-internal/lint_without_lint_pass.rs b/tests/ui-internal/lint_without_lint_pass.rs index 1fd03cfe36da4..d59e9cbbb6173 100644 --- a/tests/ui-internal/lint_without_lint_pass.rs +++ b/tests/ui-internal/lint_without_lint_pass.rs @@ -7,7 +7,7 @@ extern crate rustc_middle; #[macro_use] extern crate rustc_session; extern crate rustc_lint; -use rustc_lint::LintPass; +use rustc_lint::{LintPass, LintVec}; declare_tool_lint! { pub clippy::TEST_LINT, @@ -35,6 +35,9 @@ impl LintPass for Pass { fn name(&self) -> &'static str { "TEST_LINT" } + fn get_lints(&self) -> LintVec { + vec![TEST_LINT] + } } declare_lint_pass!(Pass2 => [TEST_LINT_REGISTERED]); diff --git a/tests/ui-internal/slow_symbol_comparisons.fixed b/tests/ui-internal/slow_symbol_comparisons.fixed new file mode 100644 index 0000000000000..2cbd646a0fd5d --- /dev/null +++ b/tests/ui-internal/slow_symbol_comparisons.fixed @@ -0,0 +1,24 @@ +#![feature(rustc_private)] +#![warn(clippy::slow_symbol_comparisons)] + +extern crate rustc_span; + +use clippy_utils::sym; +use rustc_span::Symbol; + +fn main() { + let symbol = sym!(example); + let other_symbol = sym!(other_example); + + // Should lint + let slow_comparison = symbol.as_str() == "example"; + //~^ error: comparing `Symbol` via `Symbol::intern` + let slow_comparison_macro = symbol.as_str() == "example"; + //~^ error: comparing `Symbol` via `Symbol::intern` + let slow_comparison_backwards = symbol.as_str() == "example"; + //~^ error: comparing `Symbol` via `Symbol::intern` + + // Should not lint + let faster_comparison = symbol.as_str() == "other_example"; + let preinterned_comparison = symbol == other_symbol; +} diff --git a/tests/ui-internal/slow_symbol_comparisons.rs b/tests/ui-internal/slow_symbol_comparisons.rs new file mode 100644 index 0000000000000..0cea3c3fcff9f --- /dev/null +++ b/tests/ui-internal/slow_symbol_comparisons.rs @@ -0,0 +1,24 @@ +#![feature(rustc_private)] +#![warn(clippy::slow_symbol_comparisons)] + +extern crate rustc_span; + +use clippy_utils::sym; +use rustc_span::Symbol; + +fn main() { + let symbol = sym!(example); + let other_symbol = sym!(other_example); + + // Should lint + let slow_comparison = symbol == Symbol::intern("example"); + //~^ error: comparing `Symbol` via `Symbol::intern` + let slow_comparison_macro = symbol == sym!(example); + //~^ error: comparing `Symbol` via `Symbol::intern` + let slow_comparison_backwards = sym!(example) == symbol; + //~^ error: comparing `Symbol` via `Symbol::intern` + + // Should not lint + let faster_comparison = symbol.as_str() == "other_example"; + let preinterned_comparison = symbol == other_symbol; +} diff --git a/tests/ui-internal/slow_symbol_comparisons.stderr b/tests/ui-internal/slow_symbol_comparisons.stderr new file mode 100644 index 0000000000000..72cb20a7fed90 --- /dev/null +++ b/tests/ui-internal/slow_symbol_comparisons.stderr @@ -0,0 +1,23 @@ +error: comparing `Symbol` via `Symbol::intern` + --> tests/ui-internal/slow_symbol_comparisons.rs:14:27 + | +LL | let slow_comparison = symbol == Symbol::intern("example"); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `Symbol::as_str` and check the string instead: `symbol.as_str() == "example"` + | + = note: `-D clippy::slow-symbol-comparisons` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::slow_symbol_comparisons)]` + +error: comparing `Symbol` via `Symbol::intern` + --> tests/ui-internal/slow_symbol_comparisons.rs:16:33 + | +LL | let slow_comparison_macro = symbol == sym!(example); + | ^^^^^^^^^^^^^^^^^^^^^^^ help: use `Symbol::as_str` and check the string instead: `symbol.as_str() == "example"` + +error: comparing `Symbol` via `Symbol::intern` + --> tests/ui-internal/slow_symbol_comparisons.rs:18:37 + | +LL | let slow_comparison_backwards = sym!(example) == symbol; + | ^^^^^^^^^^^^^^^^^^^^^^^ help: use `Symbol::as_str` and check the string instead: `symbol.as_str() == "example"` + +error: aborting due to 3 previous errors + diff --git a/tests/ui-internal/unnecessary_symbol_str.fixed b/tests/ui-internal/unnecessary_symbol_str.fixed index eb79fdbc4b4c8..8e7f020c1f659 100644 --- a/tests/ui-internal/unnecessary_symbol_str.fixed +++ b/tests/ui-internal/unnecessary_symbol_str.fixed @@ -1,6 +1,7 @@ #![feature(rustc_private)] #![deny(clippy::internal)] #![allow( + clippy::slow_symbol_comparisons, clippy::borrow_deref_ref, clippy::unnecessary_operation, unused_must_use, diff --git a/tests/ui-internal/unnecessary_symbol_str.rs b/tests/ui-internal/unnecessary_symbol_str.rs index bbea13af92a54..9aeeb9aaf3aa3 100644 --- a/tests/ui-internal/unnecessary_symbol_str.rs +++ b/tests/ui-internal/unnecessary_symbol_str.rs @@ -1,6 +1,7 @@ #![feature(rustc_private)] #![deny(clippy::internal)] #![allow( + clippy::slow_symbol_comparisons, clippy::borrow_deref_ref, clippy::unnecessary_operation, unused_must_use, diff --git a/tests/ui-internal/unnecessary_symbol_str.stderr b/tests/ui-internal/unnecessary_symbol_str.stderr index 551167a9ff58b..668c11722f91c 100644 --- a/tests/ui-internal/unnecessary_symbol_str.stderr +++ b/tests/ui-internal/unnecessary_symbol_str.stderr @@ -1,5 +1,5 @@ error: unnecessary `Symbol` to string conversion - --> tests/ui-internal/unnecessary_symbol_str.rs:15:5 + --> tests/ui-internal/unnecessary_symbol_str.rs:16:5 | LL | Symbol::intern("foo").as_str() == "clippy"; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Symbol::intern("foo") == rustc_span::sym::clippy` @@ -12,25 +12,25 @@ LL | #![deny(clippy::internal)] = note: `#[deny(clippy::unnecessary_symbol_str)]` implied by `#[deny(clippy::internal)]` error: unnecessary `Symbol` to string conversion - --> tests/ui-internal/unnecessary_symbol_str.rs:16:5 + --> tests/ui-internal/unnecessary_symbol_str.rs:17:5 | LL | Symbol::intern("foo").to_string() == "self"; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Symbol::intern("foo") == rustc_span::symbol::kw::SelfLower` error: unnecessary `Symbol` to string conversion - --> tests/ui-internal/unnecessary_symbol_str.rs:17:5 + --> tests/ui-internal/unnecessary_symbol_str.rs:18:5 | LL | Symbol::intern("foo").to_ident_string() != "Self"; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Symbol::intern("foo") != rustc_span::symbol::kw::SelfUpper` error: unnecessary `Symbol` to string conversion - --> tests/ui-internal/unnecessary_symbol_str.rs:18:5 + --> tests/ui-internal/unnecessary_symbol_str.rs:19:5 | LL | &*Ident::empty().as_str() == "clippy"; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `Ident::empty().name == rustc_span::sym::clippy` error: unnecessary `Symbol` to string conversion - --> tests/ui-internal/unnecessary_symbol_str.rs:19:5 + --> tests/ui-internal/unnecessary_symbol_str.rs:20:5 | LL | "clippy" == Ident::empty().to_string(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `rustc_span::sym::clippy == Ident::empty().name` diff --git a/tests/ui-toml/arbitrary_source_item_ordering/bad_conf_1/clippy.toml b/tests/ui-toml/arbitrary_source_item_ordering/bad_conf_1/clippy.toml new file mode 100644 index 0000000000000..fbdb0c9c37d0f --- /dev/null +++ b/tests/ui-toml/arbitrary_source_item_ordering/bad_conf_1/clippy.toml @@ -0,0 +1 @@ +trait-assoc-item-kinds-order = ["fn", "type", "const", "type"] diff --git a/tests/ui-toml/arbitrary_source_item_ordering/bad_conf_2/clippy.toml b/tests/ui-toml/arbitrary_source_item_ordering/bad_conf_2/clippy.toml new file mode 100644 index 0000000000000..720655e5cce1b --- /dev/null +++ b/tests/ui-toml/arbitrary_source_item_ordering/bad_conf_2/clippy.toml @@ -0,0 +1 @@ +trait-assoc-item-kinds-order = ["const", "type"] diff --git a/tests/ui-toml/arbitrary_source_item_ordering/bad_conf_3/clippy.toml b/tests/ui-toml/arbitrary_source_item_ordering/bad_conf_3/clippy.toml new file mode 100644 index 0000000000000..0dd5407be10b7 --- /dev/null +++ b/tests/ui-toml/arbitrary_source_item_ordering/bad_conf_3/clippy.toml @@ -0,0 +1 @@ +source-item-ordering = ["enum", "impl", "module", "struct", "trait", "struct"] diff --git a/tests/ui-toml/arbitrary_source_item_ordering/default/clippy.toml b/tests/ui-toml/arbitrary_source_item_ordering/default/clippy.toml new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/tests/ui-toml/arbitrary_source_item_ordering/default_exp/clippy.toml b/tests/ui-toml/arbitrary_source_item_ordering/default_exp/clippy.toml new file mode 100644 index 0000000000000..ddca5cfa57796 --- /dev/null +++ b/tests/ui-toml/arbitrary_source_item_ordering/default_exp/clippy.toml @@ -0,0 +1,12 @@ +trait-assoc-item-kinds-order = ["const", "type", "fn"] +source-item-ordering = ["enum", "impl", "module", "struct", "trait"] +module-item-order-groupings = [ + ["modules", ["extern_crate", "mod", "foreign_mod"]], + ["use", ["use"]], + ["macros", ["macro"]], + ["global_asm", ["global_asm"]], + ["UPPER_SNAKE_CASE", ["static", "const"]], + ["PascalCase", ["ty_alias", "enum", "struct", "union", "trait", "trait_alias", "impl"]], + ["lower_snake_case", ["fn"]] +] + diff --git a/tests/ui-toml/arbitrary_source_item_ordering/only_enum/clippy.toml b/tests/ui-toml/arbitrary_source_item_ordering/only_enum/clippy.toml new file mode 100644 index 0000000000000..2144bdc9a0ce7 --- /dev/null +++ b/tests/ui-toml/arbitrary_source_item_ordering/only_enum/clippy.toml @@ -0,0 +1 @@ +source-item-ordering = ["enum"] diff --git a/tests/ui-toml/arbitrary_source_item_ordering/only_impl/clippy.toml b/tests/ui-toml/arbitrary_source_item_ordering/only_impl/clippy.toml new file mode 100644 index 0000000000000..54b6727fabf70 --- /dev/null +++ b/tests/ui-toml/arbitrary_source_item_ordering/only_impl/clippy.toml @@ -0,0 +1 @@ +source-item-ordering = ["impl"] diff --git a/tests/ui-toml/arbitrary_source_item_ordering/only_trait/clippy.toml b/tests/ui-toml/arbitrary_source_item_ordering/only_trait/clippy.toml new file mode 100644 index 0000000000000..b551611c35e41 --- /dev/null +++ b/tests/ui-toml/arbitrary_source_item_ordering/only_trait/clippy.toml @@ -0,0 +1 @@ +source-item-ordering = ["trait"] diff --git a/tests/ui-toml/arbitrary_source_item_ordering/ordering_good.bad_conf_1.stderr b/tests/ui-toml/arbitrary_source_item_ordering/ordering_good.bad_conf_1.stderr new file mode 100644 index 0000000000000..e441c7c1241c9 --- /dev/null +++ b/tests/ui-toml/arbitrary_source_item_ordering/ordering_good.bad_conf_1.stderr @@ -0,0 +1,8 @@ +error: error reading Clippy's configuration file: Some trait associated item kinds were configured more than once, or were missing, in the source ordering configuration. The trait associated item kinds are: [Const, Fn, Type] + --> $DIR/tests/ui-toml/arbitrary_source_item_ordering/bad_conf_1/clippy.toml:1:32 + | +LL | trait-assoc-item-kinds-order = ["fn", "type", "const", "type"] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/ui-toml/arbitrary_source_item_ordering/ordering_good.bad_conf_2.stderr b/tests/ui-toml/arbitrary_source_item_ordering/ordering_good.bad_conf_2.stderr new file mode 100644 index 0000000000000..183f0b0331910 --- /dev/null +++ b/tests/ui-toml/arbitrary_source_item_ordering/ordering_good.bad_conf_2.stderr @@ -0,0 +1,8 @@ +error: error reading Clippy's configuration file: Some trait associated item kinds were configured more than once, or were missing, in the source ordering configuration. The trait associated item kinds are: [Const, Fn, Type] + --> $DIR/tests/ui-toml/arbitrary_source_item_ordering/bad_conf_2/clippy.toml:1:32 + | +LL | trait-assoc-item-kinds-order = ["const", "type"] + | ^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/ui-toml/arbitrary_source_item_ordering/ordering_good.bad_conf_3.stderr b/tests/ui-toml/arbitrary_source_item_ordering/ordering_good.bad_conf_3.stderr new file mode 100644 index 0000000000000..abf58dbd11010 --- /dev/null +++ b/tests/ui-toml/arbitrary_source_item_ordering/ordering_good.bad_conf_3.stderr @@ -0,0 +1,8 @@ +error: error reading Clippy's configuration file: The category "Struct" was enabled more than once in the source ordering configuration. + --> $DIR/tests/ui-toml/arbitrary_source_item_ordering/bad_conf_3/clippy.toml:1:24 + | +LL | source-item-ordering = ["enum", "impl", "module", "struct", "trait", "struct"] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/ui-toml/arbitrary_source_item_ordering/ordering_good.rs b/tests/ui-toml/arbitrary_source_item_ordering/ordering_good.rs new file mode 100644 index 0000000000000..e9bcc30c15e0a --- /dev/null +++ b/tests/ui-toml/arbitrary_source_item_ordering/ordering_good.rs @@ -0,0 +1,186 @@ +//@aux-build:../../ui/auxiliary/proc_macros.rs +//@revisions: default default_exp bad_conf_1 bad_conf_2 bad_conf_3 +//@[default] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/arbitrary_source_item_ordering/default +//@[default_exp] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/arbitrary_source_item_ordering/default_exp +//@[bad_conf_1] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/arbitrary_source_item_ordering/bad_conf_1 +//@[bad_conf_2] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/arbitrary_source_item_ordering/bad_conf_2 +//@[bad_conf_3] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/arbitrary_source_item_ordering/bad_conf_3 + +#![allow(dead_code)] +#![warn(clippy::arbitrary_source_item_ordering)] + +/// This module gets linted before clippy gives up. +mod i_am_just_right { + const AFTER: i8 = 0; + + const BEFORE: i8 = 0; +} + +/// Since the upper module passes linting, the lint now recurses into this module. +mod this_is_in_the_wrong_position { + const A: i8 = 1; + const C: i8 = 0; +} + +// Use statements should not be linted internally - this is normally auto-sorted using rustfmt. +use std::rc::{Rc, Weak}; +use std::sync::{Arc, Barrier, RwLock}; + +const SNAKE_CASE: &str = "zzzzzzzz"; + +const ZIS_SHOULD_BE_EVEN_EARLIER: () = (); + +const ZIS_SHOULD_BE_REALLY_EARLY: () = (); + +trait BasicEmptyTrait {} + +trait CloneSelf { + fn clone_self(&self) -> Self; +} + +enum EnumOrdered { + A, + B, + C, +} + +enum EnumUnordered { + A, + B, + C, +} + +#[allow(clippy::arbitrary_source_item_ordering)] +enum EnumUnorderedAllowed { + A, + B, + C, +} + +struct StructOrdered { + a: bool, + b: bool, + c: bool, +} + +impl BasicEmptyTrait for StructOrdered {} + +impl CloneSelf for StructOrdered { + fn clone_self(&self) -> Self { + Self { + a: true, + b: true, + c: true, + } + } +} + +impl Default for StructOrdered { + fn default() -> Self { + Self { + a: true, + b: true, + c: true, + } + } +} + +impl std::clone::Clone for StructOrdered { + fn clone(&self) -> Self { + Self { + a: true, + b: true, + c: true, + } + } +} + +#[derive(Clone, Default)] +struct StructUnordered { + a: bool, + b: bool, + c: bool, + d: bool, +} + +impl TraitUnordered for StructUnordered { + const A: bool = false; + const B: bool = false; + const C: bool = false; + + type SomeType = (); + + fn a() {} + fn b() {} + fn c() {} +} + +impl TraitUnorderedItemKinds for StructUnordered { + const A: bool = false; + const B: bool = false; + const C: bool = false; + + type SomeType = (); + + fn a() {} + fn b() {} + fn c() {} +} + +struct StructUnorderedGeneric { + _1: std::marker::PhantomData, + a: bool, + b: bool, + c: bool, + d: bool, +} + +trait TraitOrdered { + const A: bool; + const B: bool; + const C: bool; + + type SomeType; + + fn a(); + fn b(); + fn c(); +} + +trait TraitUnordered { + const A: bool; + const B: bool; + const C: bool; + + type SomeType; + + fn a(); + fn b(); + fn c(); +} + +trait TraitUnorderedItemKinds { + const A: bool; + const B: bool; + const C: bool; + + type SomeType; + + fn a(); + fn b(); + fn c(); +} + +#[derive(std::clone::Clone, Default)] +struct ZisShouldBeBeforeZeMainFn; + +fn main() { + // test code goes here +} + +#[cfg(test)] +mod test { + const B: i8 = 1; + + const A: i8 = 0; +} diff --git a/tests/ui-toml/arbitrary_source_item_ordering/ordering_good_var_1.rs b/tests/ui-toml/arbitrary_source_item_ordering/ordering_good_var_1.rs new file mode 100644 index 0000000000000..0fccbd4790bc2 --- /dev/null +++ b/tests/ui-toml/arbitrary_source_item_ordering/ordering_good_var_1.rs @@ -0,0 +1,174 @@ +//@aux-build:../../ui/auxiliary/proc_macros.rs +//@revisions: var_1 +//@[var_1] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/arbitrary_source_item_ordering/var_1 + +#![allow(dead_code)] +#![warn(clippy::arbitrary_source_item_ordering)] + +/// This module gets linted before clippy gives up. +mod i_am_just_right { + const AFTER: i8 = 0; + + const BEFORE: i8 = 0; +} + +/// Since the upper module passes linting, the lint now recurses into this module. +mod this_is_in_the_wrong_position { + const A: i8 = 1; + const C: i8 = 0; +} + +// Use statements should not be linted internally - this is normally auto-sorted using rustfmt. +use std::rc::{Rc, Weak}; +use std::sync::{Arc, Barrier, RwLock}; + +const SNAKE_CASE: &str = "zzzzzzzz"; + +const ZIS_SHOULD_BE_EVEN_EARLIER: () = (); + +const ZIS_SHOULD_BE_REALLY_EARLY: () = (); + +trait BasicEmptyTrait {} + +trait CloneSelf { + fn clone_self(&self) -> Self; +} + +enum EnumOrdered { + A, + B, + C, +} + +enum EnumUnordered { + A, + B, + C, +} + +#[allow(clippy::arbitrary_source_item_ordering)] +enum EnumUnorderedAllowed { + A, + B, + C, +} + +struct StructOrdered { + a: bool, + b: bool, + c: bool, +} + +impl BasicEmptyTrait for StructOrdered {} + +impl CloneSelf for StructOrdered { + fn clone_self(&self) -> Self { + Self { + a: true, + b: true, + c: true, + } + } +} + +impl Default for StructOrdered { + fn default() -> Self { + Self { + a: true, + b: true, + c: true, + } + } +} + +impl std::clone::Clone for StructOrdered { + fn clone(&self) -> Self { + Self { + a: true, + b: true, + c: true, + } + } +} + +#[derive(Clone, Default)] +struct StructUnordered { + a: bool, + b: bool, + c: bool, + d: bool, +} + +impl TraitUnordered for StructUnordered { + fn a() {} + fn b() {} + fn c() {} + + type SomeType = (); + + const A: bool = false; + const B: bool = false; + const C: bool = false; +} + +impl TraitUnorderedItemKinds for StructUnordered { + fn a() {} + + type SomeType = (); + + const A: bool = false; +} + +struct StructUnorderedGeneric { + _1: std::marker::PhantomData, + a: bool, + b: bool, + c: bool, + d: bool, +} + +trait TraitOrdered { + fn a(); + fn b(); + fn c(); + + type SomeType; + + const A: bool; + const B: bool; + const C: bool; +} + +trait TraitUnordered { + fn a(); + fn b(); + fn c(); + + type SomeType; + + const A: bool; + const B: bool; + const C: bool; +} + +trait TraitUnorderedItemKinds { + fn a(); + + type SomeType; + + const A: bool; +} + +#[derive(std::clone::Clone, Default)] +struct ZisShouldBeBeforeZeMainFn; + +fn main() { + // test code goes here +} + +#[cfg(test)] +mod test { + const B: i8 = 1; + + const A: i8 = 0; +} diff --git a/tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.default.stderr b/tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.default.stderr new file mode 100644 index 0000000000000..062bf25ea624c --- /dev/null +++ b/tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.default.stderr @@ -0,0 +1,226 @@ +error: incorrect ordering of items (must be alphabetically ordered) + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:21:14 + | +LL | use std::rc::Weak; + | ^^^^ + | +note: should be placed before `SNAKE_CASE` + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:19:7 + | +LL | const SNAKE_CASE: &str = "zzzzzzzz"; + | ^^^^^^^^^^ + = note: `-D clippy::arbitrary-source-item-ordering` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::arbitrary_source_item_ordering)]` + +error: incorrect ordering of items (must be alphabetically ordered) + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:64:1 + | +LL | / impl CloneSelf for StructOrdered { +LL | | fn clone_self(&self) -> Self { +LL | | Self { +LL | | a: true, +... | +LL | | } +LL | | } + | |_^ + | +note: should be placed before the following item + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:54:1 + | +LL | / impl Default for StructOrdered { +LL | | fn default() -> Self { +LL | | Self { +LL | | a: true, +... | +LL | | } +LL | | } + | |_^ + +error: incorrect ordering of items (must be alphabetically ordered) + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:136:7 + | +LL | const ZIS_SHOULD_BE_REALLY_EARLY: () = (); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: should be placed before `TraitUnorderedItemKinds` + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:124:7 + | +LL | trait TraitUnorderedItemKinds { + | ^^^^^^^^^^^^^^^^^^^^^^^ + +error: incorrect ordering of items (must be alphabetically ordered) + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:151:1 + | +LL | impl BasicEmptyTrait for StructOrdered {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: should be placed before the following item + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:138:1 + | +LL | / impl TraitUnordered for StructUnordered { +LL | | const A: bool = false; +LL | | const C: bool = false; +LL | | const B: bool = false; +... | +LL | | fn b() {} +LL | | } + | |_^ + +error: incorrect ordering of items (must be alphabetically ordered) + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:170:5 + | +LL | mod this_is_in_the_wrong_position { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: should be placed before `main` + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:165:4 + | +LL | fn main() { + | ^^^^ + +error: incorrect ordering of items (must be alphabetically ordered) + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:178:7 + | +LL | const ZIS_SHOULD_BE_EVEN_EARLIER: () = (); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: should be placed before `ZisShouldBeBeforeZeMainFn` + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:176:8 + | +LL | struct ZisShouldBeBeforeZeMainFn; + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: incorrect ordering of items (must be alphabetically ordered) + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:12:11 + | +LL | const AFTER: i8 = 0; + | ^^^^^ + | +note: should be placed before `BEFORE` + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:10:11 + | +LL | const BEFORE: i8 = 0; + | ^^^^^^ + +error: incorrect ordering of items (must be alphabetically ordered) + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:38:5 + | +LL | B, + | ^ + | +note: should be placed before `C` + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:37:5 + | +LL | C, + | ^ + +error: incorrect ordering of items (must be alphabetically ordered) + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:88:5 + | +LL | b: bool, + | ^ + | +note: should be placed before `c` + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:87:5 + | +LL | c: bool, + | ^ + +error: incorrect ordering of items (must be alphabetically ordered) + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:96:5 + | +LL | b: bool, + | ^ + | +note: should be placed before `c` + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:95:5 + | +LL | c: bool, + | ^ + +error: incorrect ordering of items (must be alphabetically ordered) + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:115:11 + | +LL | const B: bool; + | ^ + | +note: should be placed before `C` + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:114:11 + | +LL | const C: bool; + | ^ + +error: incorrect ordering of items (must be alphabetically ordered) + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:121:8 + | +LL | fn b(); + | ^ + | +note: should be placed before `c` + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:120:8 + | +LL | fn c(); + | ^ + +error: incorrect ordering of trait items (defined order: [Const, Type, Fn]) + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:127:5 + | +LL | const A: bool; + | ^^^^^^^^^^^^^^ + | +note: should be placed before `SomeType` + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:125:5 + | +LL | type SomeType; + | ^^^^^^^^^^^^^^ + +error: incorrect ordering of items (must be alphabetically ordered) + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:141:11 + | +LL | const B: bool = false; + | ^ + | +note: should be placed before `C` + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:140:11 + | +LL | const C: bool = false; + | ^ + +error: incorrect ordering of items (must be alphabetically ordered) + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:147:8 + | +LL | fn b() {} + | ^ + | +note: should be placed before `c` + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:146:8 + | +LL | fn c() {} + | ^ + +error: incorrect ordering of impl items (defined order: [Const, Type, Fn]) + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:156:5 + | +LL | const A: bool = false; + | ^^^^^^^^^^^^^^^^^^^^^^ + | +note: should be placed before `SomeType` + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:154:5 + | +LL | type SomeType = (); + | ^^^^^^^^^^^^^^^^^^^ + +error: incorrect ordering of items (must be alphabetically ordered) + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:172:11 + | +LL | const A: i8 = 1; + | ^ + | +note: should be placed before `C` + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs:171:11 + | +LL | const C: i8 = 0; + | ^ + +error: aborting due to 17 previous errors + diff --git a/tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs b/tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs new file mode 100644 index 0000000000000..2765bf935b7ce --- /dev/null +++ b/tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed.rs @@ -0,0 +1,185 @@ +//@aux-build:../../ui/auxiliary/proc_macros.rs +//@revisions: default +//@[default] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/arbitrary_source_item_ordering/default + +#![allow(dead_code)] +#![warn(clippy::arbitrary_source_item_ordering)] + +/// This module gets linted before clippy gives up. +mod i_am_just_right { + const BEFORE: i8 = 0; + + const AFTER: i8 = 0; +} + +// Use statements should not be linted internally - this is normally auto-sorted using rustfmt. +use std::rc::Rc; +use std::sync::{Arc, Barrier, RwLock}; + +const SNAKE_CASE: &str = "zzzzzzzz"; + +use std::rc::Weak; + +trait BasicEmptyTrait {} + +trait CloneSelf { + fn clone_self(&self) -> Self; +} + +enum EnumOrdered { + A, + B, + C, +} + +enum EnumUnordered { + A, + C, + B, +} + +#[allow(clippy::arbitrary_source_item_ordering)] +enum EnumUnorderedAllowed { + A, + C, + B, +} + +struct StructOrdered { + a: bool, + b: bool, + c: bool, +} + +impl Default for StructOrdered { + fn default() -> Self { + Self { + a: true, + b: true, + c: true, + } + } +} + +impl CloneSelf for StructOrdered { + fn clone_self(&self) -> Self { + Self { + a: true, + b: true, + c: true, + } + } +} + +impl std::clone::Clone for StructOrdered { + fn clone(&self) -> Self { + Self { + a: true, + b: true, + c: true, + } + } +} + +#[derive(Default, Clone)] +struct StructUnordered { + a: bool, + c: bool, + b: bool, + d: bool, +} + +struct StructUnorderedGeneric { + _1: std::marker::PhantomData, + a: bool, + c: bool, + b: bool, + d: bool, +} + +trait TraitOrdered { + const A: bool; + const B: bool; + const C: bool; + + type SomeType; + + fn a(); + fn b(); + fn c(); +} + +trait TraitUnordered { + const A: bool; + const C: bool; + const B: bool; + + type SomeType; + + fn a(); + fn c(); + fn b(); +} + +trait TraitUnorderedItemKinds { + type SomeType; + + const A: bool; + const B: bool; + const C: bool; + + fn a(); + fn b(); + fn c(); +} + +const ZIS_SHOULD_BE_REALLY_EARLY: () = (); + +impl TraitUnordered for StructUnordered { + const A: bool = false; + const C: bool = false; + const B: bool = false; + + type SomeType = (); + + fn a() {} + fn c() {} + fn b() {} +} + +// Trait impls should be located just after the type they implement it for. +impl BasicEmptyTrait for StructOrdered {} + +impl TraitUnorderedItemKinds for StructUnordered { + type SomeType = (); + + const A: bool = false; + const B: bool = false; + const C: bool = false; + + fn a() {} + fn b() {} + fn c() {} +} + +fn main() { + // test code goes here +} + +/// Note that the linting pass is stopped before recursing into this module. +mod this_is_in_the_wrong_position { + const C: i8 = 0; + const A: i8 = 1; +} + +#[derive(Default, std::clone::Clone)] +struct ZisShouldBeBeforeZeMainFn; + +const ZIS_SHOULD_BE_EVEN_EARLIER: () = (); + +#[cfg(test)] +mod test { + const B: i8 = 1; + + const A: i8 = 0; +} diff --git a/tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed_var_1.rs b/tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed_var_1.rs new file mode 100644 index 0000000000000..44902336573cc --- /dev/null +++ b/tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed_var_1.rs @@ -0,0 +1,174 @@ +//@aux-build:../../ui/auxiliary/proc_macros.rs +//@revisions: var_1 +//@[var_1] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/arbitrary_source_item_ordering/var_1 + +#![allow(dead_code)] +#![warn(clippy::arbitrary_source_item_ordering)] + +/// This module gets linted before clippy gives up. +mod i_am_just_right { + const AFTER: i8 = 0; + + const BEFORE: i8 = 0; +} + +/// Since the upper module passes linting, the lint now recurses into this module. +mod this_is_in_the_wrong_position { + const A: i8 = 1; + const C: i8 = 0; +} + +// Use statements should not be linted internally - this is normally auto-sorted using rustfmt. +use std::rc::{Rc, Weak}; +use std::sync::{Arc, Barrier, RwLock}; + +const SNAKE_CASE: &str = "zzzzzzzz"; + +const ZIS_SHOULD_BE_EVEN_EARLIER: () = (); + +const ZIS_SHOULD_BE_REALLY_EARLY: () = (); + +trait BasicEmptyTrait {} + +trait CloneSelf { + fn clone_self(&self) -> Self; +} + +enum EnumOrdered { + A, + B, + C, +} + +enum EnumUnordered { + A, + B, + C, +} + +#[allow(clippy::arbitrary_source_item_ordering)] +enum EnumUnorderedAllowed { + A, + B, + C, +} + +struct StructOrdered { + a: bool, + b: bool, + c: bool, +} + +impl BasicEmptyTrait for StructOrdered {} + +impl CloneSelf for StructOrdered { + fn clone_self(&self) -> Self { + Self { + a: true, + b: true, + c: true, + } + } +} + +impl Default for StructOrdered { + fn default() -> Self { + Self { + a: true, + b: true, + c: true, + } + } +} + +impl std::clone::Clone for StructOrdered { + fn clone(&self) -> Self { + Self { + a: true, + b: true, + c: true, + } + } +} + +#[derive(Clone, Default)] +struct StructUnordered { + a: bool, + b: bool, + c: bool, + d: bool, +} + +impl TraitUnordered for StructUnordered { + fn a() {} + fn c() {} + fn b() {} + + type SomeType = (); + + const A: bool = false; + const C: bool = false; + const B: bool = false; +} + +impl TraitUnorderedItemKinds for StructUnordered { + const A: bool = false; + + type SomeType = (); + + fn a() {} +} + +struct StructUnorderedGeneric { + _1: std::marker::PhantomData, + a: bool, + b: bool, + c: bool, + d: bool, +} + +trait TraitOrdered { + fn a(); + fn b(); + fn c(); + + type SomeType; + + const A: bool; + const B: bool; + const C: bool; +} + +trait TraitUnordered { + fn a(); + fn c(); + fn b(); + + type SomeType; + + const A: bool; + const C: bool; + const B: bool; +} + +trait TraitUnorderedItemKinds { + const A: bool; + + type SomeType; + + fn a(); +} + +#[derive(std::clone::Clone, Default)] +struct ZisShouldBeBeforeZeMainFn; + +fn main() { + // test code goes here +} + +#[cfg(test)] +mod test { + const B: i8 = 1; + + const A: i8 = 0; +} diff --git a/tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed_var_1.var_1.stderr b/tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed_var_1.var_1.stderr new file mode 100644 index 0000000000000..f31f7f68c17e7 --- /dev/null +++ b/tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed_var_1.var_1.stderr @@ -0,0 +1,100 @@ +error: incorrect ordering of items (must be alphabetically ordered) + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed_var_1.rs:105:8 + | +LL | fn b() {} + | ^ + | +note: should be placed before `c` + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed_var_1.rs:104:8 + | +LL | fn c() {} + | ^ + = note: `-D clippy::arbitrary-source-item-ordering` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::arbitrary_source_item_ordering)]` + +error: incorrect ordering of items (must be alphabetically ordered) + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed_var_1.rs:111:11 + | +LL | const B: bool = false; + | ^ + | +note: should be placed before `C` + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed_var_1.rs:110:11 + | +LL | const C: bool = false; + | ^ + +error: incorrect ordering of impl items (defined order: [Fn, Type, Const]) + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed_var_1.rs:117:5 + | +LL | type SomeType = (); + | ^^^^^^^^^^^^^^^^^^^ + | +note: should be placed before `A` + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed_var_1.rs:115:5 + | +LL | const A: bool = false; + | ^^^^^^^^^^^^^^^^^^^^^^ + +error: incorrect ordering of impl items (defined order: [Fn, Type, Const]) + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed_var_1.rs:119:5 + | +LL | fn a() {} + | ^^^^^^^^^ + | +note: should be placed before `SomeType` + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed_var_1.rs:117:5 + | +LL | type SomeType = (); + | ^^^^^^^^^^^^^^^^^^^ + +error: incorrect ordering of items (must be alphabetically ordered) + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed_var_1.rs:145:8 + | +LL | fn b(); + | ^ + | +note: should be placed before `c` + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed_var_1.rs:144:8 + | +LL | fn c(); + | ^ + +error: incorrect ordering of items (must be alphabetically ordered) + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed_var_1.rs:151:11 + | +LL | const B: bool; + | ^ + | +note: should be placed before `C` + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed_var_1.rs:150:11 + | +LL | const C: bool; + | ^ + +error: incorrect ordering of trait items (defined order: [Fn, Type, Const]) + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed_var_1.rs:157:5 + | +LL | type SomeType; + | ^^^^^^^^^^^^^^ + | +note: should be placed before `A` + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed_var_1.rs:155:5 + | +LL | const A: bool; + | ^^^^^^^^^^^^^^ + +error: incorrect ordering of trait items (defined order: [Fn, Type, Const]) + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed_var_1.rs:159:5 + | +LL | fn a(); + | ^^^^^^^ + | +note: should be placed before `SomeType` + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_mixed_var_1.rs:157:5 + | +LL | type SomeType; + | ^^^^^^^^^^^^^^ + +error: aborting due to 8 previous errors + diff --git a/tests/ui-toml/arbitrary_source_item_ordering/ordering_only_enum.only_enum.stderr b/tests/ui-toml/arbitrary_source_item_ordering/ordering_only_enum.only_enum.stderr new file mode 100644 index 0000000000000..57069fd8feed7 --- /dev/null +++ b/tests/ui-toml/arbitrary_source_item_ordering/ordering_only_enum.only_enum.stderr @@ -0,0 +1,16 @@ +error: incorrect ordering of items (must be alphabetically ordered) + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_only_enum.rs:22:5 + | +LL | A, + | ^ + | +note: should be placed before `B` + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_only_enum.rs:21:5 + | +LL | B, + | ^ + = note: `-D clippy::arbitrary-source-item-ordering` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::arbitrary_source_item_ordering)]` + +error: aborting due to 1 previous error + diff --git a/tests/ui-toml/arbitrary_source_item_ordering/ordering_only_enum.rs b/tests/ui-toml/arbitrary_source_item_ordering/ordering_only_enum.rs new file mode 100644 index 0000000000000..e8002c4a1633e --- /dev/null +++ b/tests/ui-toml/arbitrary_source_item_ordering/ordering_only_enum.rs @@ -0,0 +1,43 @@ +//@aux-build:../../ui/auxiliary/proc_macros.rs +//@revisions: only_enum +//@[only_enum] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/arbitrary_source_item_ordering/only_enum + +#![allow(dead_code)] +#![warn(clippy::arbitrary_source_item_ordering)] + +fn main() {} + +struct StructUnordered { + b: bool, + a: bool, +} + +enum EnumOrdered { + A, + B, +} + +enum EnumUnordered { + B, + A, +} + +trait TraitUnordered { + const B: bool; + const A: bool; + + type SomeType; + + fn b(); + fn a(); +} + +trait TraitUnorderedItemKinds { + type SomeType; + + const A: bool; + + fn a(); +} + +const ZIS_SHOULD_BE_AT_THE_TOP: () = (); diff --git a/tests/ui-toml/arbitrary_source_item_ordering/ordering_only_impl.only_impl.stderr b/tests/ui-toml/arbitrary_source_item_ordering/ordering_only_impl.only_impl.stderr new file mode 100644 index 0000000000000..40348ecbdae1a --- /dev/null +++ b/tests/ui-toml/arbitrary_source_item_ordering/ordering_only_impl.only_impl.stderr @@ -0,0 +1,40 @@ +error: incorrect ordering of items (must be alphabetically ordered) + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_only_impl.rs:43:8 + | +LL | fn a() {} + | ^ + | +note: should be placed before `b` + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_only_impl.rs:42:8 + | +LL | fn b() {} + | ^ + = note: `-D clippy::arbitrary-source-item-ordering` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::arbitrary_source_item_ordering)]` + +error: incorrect ordering of impl items (defined order: [Const, Type, Fn]) + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_only_impl.rs:45:5 + | +LL | type SomeType = i8; + | ^^^^^^^^^^^^^^^^^^^ + | +note: should be placed before `a` + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_only_impl.rs:43:5 + | +LL | fn a() {} + | ^^^^^^^^^ + +error: incorrect ordering of impl items (defined order: [Const, Type, Fn]) + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_only_impl.rs:47:5 + | +LL | const A: bool = true; + | ^^^^^^^^^^^^^^^^^^^^^ + | +note: should be placed before `SomeType` + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_only_impl.rs:45:5 + | +LL | type SomeType = i8; + | ^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 3 previous errors + diff --git a/tests/ui-toml/arbitrary_source_item_ordering/ordering_only_impl.rs b/tests/ui-toml/arbitrary_source_item_ordering/ordering_only_impl.rs new file mode 100644 index 0000000000000..bd969c865b5ac --- /dev/null +++ b/tests/ui-toml/arbitrary_source_item_ordering/ordering_only_impl.rs @@ -0,0 +1,67 @@ +//@aux-build:../../ui/auxiliary/proc_macros.rs +//@revisions: only_impl +//@[only_impl] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/arbitrary_source_item_ordering/only_impl + +#![allow(dead_code)] +#![warn(clippy::arbitrary_source_item_ordering)] + +fn main() {} + +struct StructUnordered { + b: bool, + a: bool, +} + +struct BasicStruct {} + +trait BasicTrait { + const A: bool; + + type SomeType; + + fn b(); + fn a(); +} + +enum EnumUnordered { + B, + A, +} + +trait TraitUnordered { + const B: bool; + const A: bool; + + type SomeType; + + fn b(); + fn a(); +} + +impl BasicTrait for StructUnordered { + fn b() {} + fn a() {} + + type SomeType = i8; + + const A: bool = true; +} + +trait TraitUnorderedItemKinds { + type SomeType; + + const A: bool; + + fn a(); +} + +const ZIS_SHOULD_BE_AT_THE_TOP: () = (); + +impl BasicTrait for BasicStruct { + const A: bool = true; + + type SomeType = i8; + + fn a() {} + fn b() {} +} diff --git a/tests/ui-toml/arbitrary_source_item_ordering/ordering_only_trait.only_trait.stderr b/tests/ui-toml/arbitrary_source_item_ordering/ordering_only_trait.only_trait.stderr new file mode 100644 index 0000000000000..9b86ebd48e3da --- /dev/null +++ b/tests/ui-toml/arbitrary_source_item_ordering/ordering_only_trait.only_trait.stderr @@ -0,0 +1,40 @@ +error: incorrect ordering of items (must be alphabetically ordered) + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_only_trait.rs:32:11 + | +LL | const A: bool; + | ^ + | +note: should be placed before `B` + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_only_trait.rs:31:11 + | +LL | const B: bool; + | ^ + = note: `-D clippy::arbitrary-source-item-ordering` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::arbitrary_source_item_ordering)]` + +error: incorrect ordering of items (must be alphabetically ordered) + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_only_trait.rs:37:8 + | +LL | fn a(); + | ^ + | +note: should be placed before `b` + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_only_trait.rs:36:8 + | +LL | fn b(); + | ^ + +error: incorrect ordering of trait items (defined order: [Const, Type, Fn]) + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_only_trait.rs:43:5 + | +LL | const A: bool; + | ^^^^^^^^^^^^^^ + | +note: should be placed before `SomeType` + --> tests/ui-toml/arbitrary_source_item_ordering/ordering_only_trait.rs:41:5 + | +LL | type SomeType; + | ^^^^^^^^^^^^^^ + +error: aborting due to 3 previous errors + diff --git a/tests/ui-toml/arbitrary_source_item_ordering/ordering_only_trait.rs b/tests/ui-toml/arbitrary_source_item_ordering/ordering_only_trait.rs new file mode 100644 index 0000000000000..979a52ecb1007 --- /dev/null +++ b/tests/ui-toml/arbitrary_source_item_ordering/ordering_only_trait.rs @@ -0,0 +1,48 @@ +//@aux-build:../../ui/auxiliary/proc_macros.rs +//@revisions: only_trait +//@[only_trait] rustc-env:CLIPPY_CONF_DIR=tests/ui-toml/arbitrary_source_item_ordering/only_trait + +#![allow(dead_code)] +#![warn(clippy::arbitrary_source_item_ordering)] + +fn main() {} + +struct StructUnordered { + b: bool, + a: bool, +} + +trait TraitOrdered { + const A: bool; + const B: bool; + + type SomeType; + + fn a(); + fn b(); +} + +enum EnumUnordered { + B, + A, +} + +trait TraitUnordered { + const B: bool; + const A: bool; + + type SomeType; + + fn b(); + fn a(); +} + +trait TraitUnorderedItemKinds { + type SomeType; + + const A: bool; + + fn a(); +} + +const ZIS_SHOULD_BE_AT_THE_TOP: () = (); diff --git a/tests/ui-toml/arbitrary_source_item_ordering/var_1/clippy.toml b/tests/ui-toml/arbitrary_source_item_ordering/var_1/clippy.toml new file mode 100644 index 0000000000000..bede035e3885b --- /dev/null +++ b/tests/ui-toml/arbitrary_source_item_ordering/var_1/clippy.toml @@ -0,0 +1 @@ +trait-assoc-item-kinds-order = ["fn", "type", "const"] diff --git a/tests/ui-toml/disallowed_macros/disallowed_macros.stderr b/tests/ui-toml/disallowed_macros/disallowed_macros.stderr index ddeb2f8cc7047..2e3386628b4d3 100644 --- a/tests/ui-toml/disallowed_macros/disallowed_macros.stderr +++ b/tests/ui-toml/disallowed_macros/disallowed_macros.stderr @@ -1,11 +1,39 @@ +error: use of a disallowed macro `std::vec` + --> tests/ui-toml/disallowed_macros/disallowed_macros.rs:16:5 + | +LL | vec![1, 2, 3]; + | ^^^^^^^^^^^^^ + | + = note: `-D clippy::disallowed-macros` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::disallowed_macros)]` + +error: use of a disallowed macro `serde::Serialize` + --> tests/ui-toml/disallowed_macros/disallowed_macros.rs:18:14 + | +LL | #[derive(Serialize)] + | ^^^^^^^^^ + | + = note: no serializing + +error: use of a disallowed macro `macros::attr` + --> tests/ui-toml/disallowed_macros/disallowed_macros.rs:31:1 + | +LL | / macros::attr! { +LL | | struct S; +LL | | } + | |_^ + +error: use of a disallowed macro `proc_macros::Derive` + --> tests/ui-toml/disallowed_macros/disallowed_macros.rs:47:10 + | +LL | #[derive(Derive)] + | ^^^^^^ + error: use of a disallowed macro `std::println` --> tests/ui-toml/disallowed_macros/disallowed_macros.rs:13:5 | LL | println!("one"); | ^^^^^^^^^^^^^^^ - | - = note: `-D clippy::disallowed-macros` implied by `-D warnings` - = help: to override `-D warnings` add `#[allow(clippy::disallowed_macros)]` error: use of a disallowed macro `std::println` --> tests/ui-toml/disallowed_macros/disallowed_macros.rs:14:5 @@ -19,20 +47,6 @@ error: use of a disallowed macro `std::cfg` LL | cfg!(unix); | ^^^^^^^^^^ -error: use of a disallowed macro `std::vec` - --> tests/ui-toml/disallowed_macros/disallowed_macros.rs:16:5 - | -LL | vec![1, 2, 3]; - | ^^^^^^^^^^^^^ - -error: use of a disallowed macro `serde::Serialize` - --> tests/ui-toml/disallowed_macros/disallowed_macros.rs:18:14 - | -LL | #[derive(Serialize)] - | ^^^^^^^^^ - | - = note: no serializing - error: use of a disallowed macro `macros::expr` --> tests/ui-toml/disallowed_macros/disallowed_macros.rs:21:13 | @@ -69,14 +83,6 @@ error: use of a disallowed macro `macros::binop` LL | let _ = macros::binop!(1); | ^^^^^^^^^^^^^^^^^ -error: use of a disallowed macro `macros::attr` - --> tests/ui-toml/disallowed_macros/disallowed_macros.rs:31:1 - | -LL | / macros::attr! { -LL | | struct S; -LL | | } - | |_^ - error: use of a disallowed macro `macros::item` --> tests/ui-toml/disallowed_macros/disallowed_macros.rs:36:5 | @@ -95,11 +101,5 @@ error: use of a disallowed macro `macros::item` LL | macros::item!(); | ^^^^^^^^^^^^^^^ -error: use of a disallowed macro `proc_macros::Derive` - --> tests/ui-toml/disallowed_macros/disallowed_macros.rs:47:10 - | -LL | #[derive(Derive)] - | ^^^^^^ - error: aborting due to 16 previous errors diff --git a/tests/ui-toml/large_include_file/large_include_file.rs b/tests/ui-toml/large_include_file/large_include_file.rs index f3dbb6ad1cf59..dc9349f75a081 100644 --- a/tests/ui-toml/large_include_file/large_include_file.rs +++ b/tests/ui-toml/large_include_file/large_include_file.rs @@ -1,8 +1,8 @@ #![warn(clippy::large_include_file)] // Good -const GOOD_INCLUDE_BYTES: &[u8; 581] = include_bytes!("large_include_file.rs"); -const GOOD_INCLUDE_STR: &str = include_str!("large_include_file.rs"); +const GOOD_INCLUDE_BYTES: &[u8; 68] = include_bytes!("../../ui/author.rs"); +const GOOD_INCLUDE_STR: &str = include_str!("../../ui/author.rs"); #[allow(clippy::large_include_file)] const ALLOWED_TOO_BIG_INCLUDE_BYTES: &[u8; 654] = include_bytes!("too_big.txt"); @@ -11,6 +11,9 @@ const ALLOWED_TOO_BIG_INCLUDE_STR: &str = include_str!("too_big.txt"); // Bad const TOO_BIG_INCLUDE_BYTES: &[u8; 654] = include_bytes!("too_big.txt"); +//~^ large_include_file const TOO_BIG_INCLUDE_STR: &str = include_str!("too_big.txt"); +//~^ large_include_file +#[doc = include_str!("too_big.txt")] //~ large_include_file fn main() {} diff --git a/tests/ui-toml/large_include_file/large_include_file.stderr b/tests/ui-toml/large_include_file/large_include_file.stderr index 34224065f0789..9e1494a47bbaa 100644 --- a/tests/ui-toml/large_include_file/large_include_file.stderr +++ b/tests/ui-toml/large_include_file/large_include_file.stderr @@ -9,12 +9,20 @@ LL | const TOO_BIG_INCLUDE_BYTES: &[u8; 654] = include_bytes!("too_big.txt"); = help: to override `-D warnings` add `#[allow(clippy::large_include_file)]` error: attempted to include a large file - --> tests/ui-toml/large_include_file/large_include_file.rs:14:35 + --> tests/ui-toml/large_include_file/large_include_file.rs:15:35 | LL | const TOO_BIG_INCLUDE_STR: &str = include_str!("too_big.txt"); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: the configuration allows a maximum size of 600 bytes -error: aborting due to 2 previous errors +error: attempted to include a large file + --> tests/ui-toml/large_include_file/large_include_file.rs:18:1 + | +LL | #[doc = include_str!("too_big.txt")] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: the configuration allows a maximum size of 600 bytes + +error: aborting due to 3 previous errors diff --git a/tests/ui-toml/private-doc-errors/doc_lints.rs b/tests/ui-toml/private-doc-errors/doc_lints.rs index 79c8751468de2..c5d00c91b052f 100644 --- a/tests/ui-toml/private-doc-errors/doc_lints.rs +++ b/tests/ui-toml/private-doc-errors/doc_lints.rs @@ -51,4 +51,10 @@ pub mod __macro { } } +#[warn(clippy::missing_errors_doc)] +#[test] +fn test() -> Result<(), ()> { + Ok(()) +} + fn main() {} diff --git a/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr b/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr index 5cf9c0fb27102..6fa583fc0417a 100644 --- a/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr +++ b/tests/ui-toml/toml_unknown_key/conf_unknown_key.stderr @@ -54,12 +54,14 @@ error: error reading Clippy's configuration file: unknown field `foobar`, expect max-trait-bounds min-ident-chars-threshold missing-docs-in-crate-items + module-item-order-groupings msrv pass-by-value-size-limit pub-underscore-fields-behavior semicolon-inside-block-ignore-singleline semicolon-outside-block-ignore-multiline single-char-binding-names-threshold + source-item-ordering stack-size-threshold standard-macro-braces struct-field-name-threshold @@ -68,6 +70,7 @@ error: error reading Clippy's configuration file: unknown field `foobar`, expect too-large-for-stack too-many-arguments-threshold too-many-lines-threshold + trait-assoc-item-kinds-order trivial-copy-size-limit type-complexity-threshold unnecessary-box-size @@ -138,12 +141,14 @@ error: error reading Clippy's configuration file: unknown field `barfoo`, expect max-trait-bounds min-ident-chars-threshold missing-docs-in-crate-items + module-item-order-groupings msrv pass-by-value-size-limit pub-underscore-fields-behavior semicolon-inside-block-ignore-singleline semicolon-outside-block-ignore-multiline single-char-binding-names-threshold + source-item-ordering stack-size-threshold standard-macro-braces struct-field-name-threshold @@ -152,6 +157,7 @@ error: error reading Clippy's configuration file: unknown field `barfoo`, expect too-large-for-stack too-many-arguments-threshold too-many-lines-threshold + trait-assoc-item-kinds-order trivial-copy-size-limit type-complexity-threshold unnecessary-box-size @@ -222,12 +228,14 @@ error: error reading Clippy's configuration file: unknown field `allow_mixed_uni max-trait-bounds min-ident-chars-threshold missing-docs-in-crate-items + module-item-order-groupings msrv pass-by-value-size-limit pub-underscore-fields-behavior semicolon-inside-block-ignore-singleline semicolon-outside-block-ignore-multiline single-char-binding-names-threshold + source-item-ordering stack-size-threshold standard-macro-braces struct-field-name-threshold @@ -236,6 +244,7 @@ error: error reading Clippy's configuration file: unknown field `allow_mixed_uni too-large-for-stack too-many-arguments-threshold too-many-lines-threshold + trait-assoc-item-kinds-order trivial-copy-size-limit type-complexity-threshold unnecessary-box-size diff --git a/tests/ui/allow_attributes.fixed b/tests/ui/allow_attributes.fixed index 058dbb77a3205..8f6c962e2f5b4 100644 --- a/tests/ui/allow_attributes.fixed +++ b/tests/ui/allow_attributes.fixed @@ -1,4 +1,5 @@ //@aux-build:proc_macros.rs +//@aux-build:proc_macro_derive.rs #![allow(unused)] #![warn(clippy::allow_attributes)] #![no_main] @@ -65,3 +66,9 @@ fn deny_allow_attributes() -> Option { allow?; Some(42) } + +// Edge case where the generated tokens spans match on #[repr(transparent)] which tricks the proc +// macro check +#[repr(transparent)] +#[derive(proc_macro_derive::AllowLintSameSpan)] // This macro generates tokens with the same span as the whole struct and repr +struct IgnoreDerived; diff --git a/tests/ui/allow_attributes.rs b/tests/ui/allow_attributes.rs index 6d94ce50e4c37..cb6c4dcf71587 100644 --- a/tests/ui/allow_attributes.rs +++ b/tests/ui/allow_attributes.rs @@ -1,4 +1,5 @@ //@aux-build:proc_macros.rs +//@aux-build:proc_macro_derive.rs #![allow(unused)] #![warn(clippy::allow_attributes)] #![no_main] @@ -65,3 +66,9 @@ fn deny_allow_attributes() -> Option { allow?; Some(42) } + +// Edge case where the generated tokens spans match on #[repr(transparent)] which tricks the proc +// macro check +#[repr(transparent)] +#[derive(proc_macro_derive::AllowLintSameSpan)] // This macro generates tokens with the same span as the whole struct and repr +struct IgnoreDerived; diff --git a/tests/ui/allow_attributes.stderr b/tests/ui/allow_attributes.stderr index 023b4d7e40439..5a4ff287acd29 100644 --- a/tests/ui/allow_attributes.stderr +++ b/tests/ui/allow_attributes.stderr @@ -1,5 +1,5 @@ error: #[allow] attribute found - --> tests/ui/allow_attributes.rs:12:3 + --> tests/ui/allow_attributes.rs:13:3 | LL | #[allow(dead_code)] | ^^^^^ help: replace it with: `expect` @@ -8,13 +8,13 @@ LL | #[allow(dead_code)] = help: to override `-D warnings` add `#[allow(clippy::allow_attributes)]` error: #[allow] attribute found - --> tests/ui/allow_attributes.rs:21:30 + --> tests/ui/allow_attributes.rs:22:30 | LL | #[cfg_attr(panic = "unwind", allow(dead_code))] | ^^^^^ help: replace it with: `expect` error: #[allow] attribute found - --> tests/ui/allow_attributes.rs:52:7 + --> tests/ui/allow_attributes.rs:53:7 | LL | #[allow(unused)] | ^^^^^ help: replace it with: `expect` diff --git a/tests/ui-internal/author.rs b/tests/ui/author.rs similarity index 72% rename from tests/ui-internal/author.rs rename to tests/ui/author.rs index eb1f3e3f8704c..0a1be35689670 100644 --- a/tests/ui-internal/author.rs +++ b/tests/ui/author.rs @@ -1,5 +1,3 @@ -#![warn(clippy::author)] - fn main() { #[clippy::author] let x: char = 0x45 as char; diff --git a/tests/ui-internal/author.stdout b/tests/ui/author.stdout similarity index 100% rename from tests/ui-internal/author.stdout rename to tests/ui/author.stdout diff --git a/tests/ui-internal/author/blocks.rs b/tests/ui/author/blocks.rs similarity index 100% rename from tests/ui-internal/author/blocks.rs rename to tests/ui/author/blocks.rs diff --git a/tests/ui-internal/author/blocks.stdout b/tests/ui/author/blocks.stdout similarity index 100% rename from tests/ui-internal/author/blocks.stdout rename to tests/ui/author/blocks.stdout diff --git a/tests/ui-internal/author/call.rs b/tests/ui/author/call.rs similarity index 100% rename from tests/ui-internal/author/call.rs rename to tests/ui/author/call.rs diff --git a/tests/ui-internal/author/call.stdout b/tests/ui/author/call.stdout similarity index 100% rename from tests/ui-internal/author/call.stdout rename to tests/ui/author/call.stdout diff --git a/tests/ui-internal/author/if.rs b/tests/ui/author/if.rs similarity index 100% rename from tests/ui-internal/author/if.rs rename to tests/ui/author/if.rs diff --git a/tests/ui-internal/author/if.stdout b/tests/ui/author/if.stdout similarity index 100% rename from tests/ui-internal/author/if.stdout rename to tests/ui/author/if.stdout diff --git a/tests/ui-internal/author/issue_3849.rs b/tests/ui/author/issue_3849.rs similarity index 100% rename from tests/ui-internal/author/issue_3849.rs rename to tests/ui/author/issue_3849.rs diff --git a/tests/ui-internal/author/issue_3849.stdout b/tests/ui/author/issue_3849.stdout similarity index 100% rename from tests/ui-internal/author/issue_3849.stdout rename to tests/ui/author/issue_3849.stdout diff --git a/tests/ui-internal/author/loop.rs b/tests/ui/author/loop.rs similarity index 100% rename from tests/ui-internal/author/loop.rs rename to tests/ui/author/loop.rs diff --git a/tests/ui-internal/author/loop.stdout b/tests/ui/author/loop.stdout similarity index 100% rename from tests/ui-internal/author/loop.stdout rename to tests/ui/author/loop.stdout diff --git a/tests/ui-internal/author/macro_in_closure.rs b/tests/ui/author/macro_in_closure.rs similarity index 100% rename from tests/ui-internal/author/macro_in_closure.rs rename to tests/ui/author/macro_in_closure.rs diff --git a/tests/ui-internal/author/macro_in_closure.stdout b/tests/ui/author/macro_in_closure.stdout similarity index 100% rename from tests/ui-internal/author/macro_in_closure.stdout rename to tests/ui/author/macro_in_closure.stdout diff --git a/tests/ui-internal/author/macro_in_loop.rs b/tests/ui/author/macro_in_loop.rs similarity index 100% rename from tests/ui-internal/author/macro_in_loop.rs rename to tests/ui/author/macro_in_loop.rs diff --git a/tests/ui-internal/author/macro_in_loop.stdout b/tests/ui/author/macro_in_loop.stdout similarity index 100% rename from tests/ui-internal/author/macro_in_loop.stdout rename to tests/ui/author/macro_in_loop.stdout diff --git a/tests/ui-internal/author/matches.rs b/tests/ui/author/matches.rs similarity index 100% rename from tests/ui-internal/author/matches.rs rename to tests/ui/author/matches.rs diff --git a/tests/ui-internal/author/matches.stdout b/tests/ui/author/matches.stdout similarity index 100% rename from tests/ui-internal/author/matches.stdout rename to tests/ui/author/matches.stdout diff --git a/tests/ui-internal/author/repeat.rs b/tests/ui/author/repeat.rs similarity index 100% rename from tests/ui-internal/author/repeat.rs rename to tests/ui/author/repeat.rs diff --git a/tests/ui-internal/author/repeat.stdout b/tests/ui/author/repeat.stdout similarity index 100% rename from tests/ui-internal/author/repeat.stdout rename to tests/ui/author/repeat.stdout diff --git a/tests/ui-internal/author/struct.rs b/tests/ui/author/struct.rs similarity index 100% rename from tests/ui-internal/author/struct.rs rename to tests/ui/author/struct.rs diff --git a/tests/ui-internal/author/struct.stdout b/tests/ui/author/struct.stdout similarity index 100% rename from tests/ui-internal/author/struct.stdout rename to tests/ui/author/struct.stdout diff --git a/tests/ui/auxiliary/proc_macro_derive.rs b/tests/ui/auxiliary/proc_macro_derive.rs index bd90042c1da82..fbf84337382e1 100644 --- a/tests/ui/auxiliary/proc_macro_derive.rs +++ b/tests/ui/auxiliary/proc_macro_derive.rs @@ -1,4 +1,4 @@ -#![feature(repr128, proc_macro_quote)] +#![feature(repr128, proc_macro_quote, proc_macro_span)] #![allow(incomplete_features)] #![allow(clippy::field_reassign_with_default)] #![allow(clippy::eq_op)] @@ -182,3 +182,51 @@ pub fn non_canonical_clone_derive(_: TokenStream) -> TokenStream { impl Copy for NonCanonicalClone {} } } + +// Derive macro that generates the following but where all generated spans are set to the entire +// input span. +// +// ``` +// #[allow(clippy::missing_const_for_fn)] +// fn check() {} +// ``` +#[proc_macro_derive(AllowLintSameSpan)] +pub fn allow_lint_same_span_derive(input: TokenStream) -> TokenStream { + let mut iter = input.into_iter(); + let first = iter.next().unwrap(); + let last = iter.last().unwrap(); + let span = first.span().join(last.span()).unwrap(); + let span_help = |mut t: TokenTree| -> TokenTree { + t.set_span(span); + t + }; + // Generate the TokenStream but setting all the spans to the entire input span + >::from_iter([ + span_help(Punct::new('#', Spacing::Alone).into()), + span_help( + Group::new( + Delimiter::Bracket, + >::from_iter([ + Ident::new("allow", span).into(), + span_help( + Group::new( + Delimiter::Parenthesis, + >::from_iter([ + Ident::new("clippy", span).into(), + span_help(Punct::new(':', Spacing::Joint).into()), + span_help(Punct::new(':', Spacing::Alone).into()), + Ident::new("missing_const_for_fn", span).into(), + ]), + ) + .into(), + ), + ]), + ) + .into(), + ), + Ident::new("fn", span).into(), + Ident::new("check", span).into(), + span_help(Group::new(Delimiter::Parenthesis, TokenStream::new()).into()), + span_help(Group::new(Delimiter::Brace, TokenStream::new()).into()), + ]) +} diff --git a/tests/ui/borrow_deref_ref.fixed b/tests/ui/borrow_deref_ref.fixed index ea5e983de3bdb..22e984c46d248 100644 --- a/tests/ui/borrow_deref_ref.fixed +++ b/tests/ui/borrow_deref_ref.fixed @@ -71,3 +71,9 @@ mod false_negative { assert_ne!(addr_x, addr_y); } } + +fn issue_13584() { + let s = "Hello, world!\n"; + let p = &raw const *s; + let _ = p as *const i8; +} diff --git a/tests/ui/borrow_deref_ref.rs b/tests/ui/borrow_deref_ref.rs index 8c8905b150e62..61d89193f42f2 100644 --- a/tests/ui/borrow_deref_ref.rs +++ b/tests/ui/borrow_deref_ref.rs @@ -71,3 +71,9 @@ mod false_negative { assert_ne!(addr_x, addr_y); } } + +fn issue_13584() { + let s = "Hello, world!\n"; + let p = &raw const *s; + let _ = p as *const i8; +} diff --git a/tests/ui/const_is_empty.rs b/tests/ui/const_is_empty.rs index 04e0de91ecfbf..b5e27b61548a0 100644 --- a/tests/ui/const_is_empty.rs +++ b/tests/ui/const_is_empty.rs @@ -171,3 +171,17 @@ fn constant_from_external_crate() { let _ = std::env::consts::EXE_EXTENSION.is_empty(); // Do not lint, `exe_ext` comes from the `std` crate } + +fn issue_13106() { + const { + assert!(!NON_EMPTY_STR.is_empty()); + } + + const { + assert!(EMPTY_STR.is_empty()); + } + + const { + EMPTY_STR.is_empty(); + } +} diff --git a/tests/ui/const_is_empty.stderr b/tests/ui/const_is_empty.stderr index 7f80b520b1a44..0afba940d8b4b 100644 --- a/tests/ui/const_is_empty.stderr +++ b/tests/ui/const_is_empty.stderr @@ -157,5 +157,11 @@ error: this expression always evaluates to true LL | let _ = val.is_empty(); | ^^^^^^^^^^^^^^ -error: aborting due to 26 previous errors +error: this expression always evaluates to true + --> tests/ui/const_is_empty.rs:185:9 + | +LL | EMPTY_STR.is_empty(); + | ^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 27 previous errors diff --git a/tests/ui/doc/doc-fixable.fixed b/tests/ui/doc/doc-fixable.fixed index 355f2bc773611..5cf5c608a85ce 100644 --- a/tests/ui/doc/doc-fixable.fixed +++ b/tests/ui/doc/doc-fixable.fixed @@ -54,8 +54,9 @@ fn test_units() { /// This tests allowed identifiers. /// KiB MiB GiB TiB PiB EiB +/// MHz GHz THz /// AccessKit -/// CoreFoundation CoreGraphics CoreText +/// CoAP CoreFoundation CoreGraphics CoreText /// Direct2D Direct3D DirectWrite DirectX /// ECMAScript /// GPLv2 GPLv3 diff --git a/tests/ui/doc/doc-fixable.rs b/tests/ui/doc/doc-fixable.rs index 9ced267762210..420211c653902 100644 --- a/tests/ui/doc/doc-fixable.rs +++ b/tests/ui/doc/doc-fixable.rs @@ -54,8 +54,9 @@ fn test_units() { /// This tests allowed identifiers. /// KiB MiB GiB TiB PiB EiB +/// MHz GHz THz /// AccessKit -/// CoreFoundation CoreGraphics CoreText +/// CoAP CoreFoundation CoreGraphics CoreText /// Direct2D Direct3D DirectWrite DirectX /// ECMAScript /// GPLv2 GPLv3 diff --git a/tests/ui/doc/doc-fixable.stderr b/tests/ui/doc/doc-fixable.stderr index 67c0464149c6e..27a04e4b55830 100644 --- a/tests/ui/doc/doc-fixable.stderr +++ b/tests/ui/doc/doc-fixable.stderr @@ -133,7 +133,7 @@ LL | /// `be_sure_we_got_to_the_end_of_it` | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:78:5 + --> tests/ui/doc/doc-fixable.rs:79:5 | LL | /// be_sure_we_got_to_the_end_of_it | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -144,7 +144,7 @@ LL | /// `be_sure_we_got_to_the_end_of_it` | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:95:5 + --> tests/ui/doc/doc-fixable.rs:96:5 | LL | /// be_sure_we_got_to_the_end_of_it | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -155,7 +155,7 @@ LL | /// `be_sure_we_got_to_the_end_of_it` | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:103:8 + --> tests/ui/doc/doc-fixable.rs:104:8 | LL | /// ## CamelCaseThing | ^^^^^^^^^^^^^^ @@ -166,7 +166,7 @@ LL | /// ## `CamelCaseThing` | ~~~~~~~~~~~~~~~~ error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:106:7 + --> tests/ui/doc/doc-fixable.rs:107:7 | LL | /// # CamelCaseThing | ^^^^^^^^^^^^^^ @@ -177,7 +177,7 @@ LL | /// # `CamelCaseThing` | ~~~~~~~~~~~~~~~~ error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:108:22 + --> tests/ui/doc/doc-fixable.rs:109:22 | LL | /// Not a title #897 CamelCaseThing | ^^^^^^^^^^^^^^ @@ -188,7 +188,7 @@ LL | /// Not a title #897 `CamelCaseThing` | ~~~~~~~~~~~~~~~~ error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:109:5 + --> tests/ui/doc/doc-fixable.rs:110:5 | LL | /// be_sure_we_got_to_the_end_of_it | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -199,7 +199,7 @@ LL | /// `be_sure_we_got_to_the_end_of_it` | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:116:5 + --> tests/ui/doc/doc-fixable.rs:117:5 | LL | /// be_sure_we_got_to_the_end_of_it | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -210,7 +210,7 @@ LL | /// `be_sure_we_got_to_the_end_of_it` | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:129:5 + --> tests/ui/doc/doc-fixable.rs:130:5 | LL | /// be_sure_we_got_to_the_end_of_it | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -221,7 +221,7 @@ LL | /// `be_sure_we_got_to_the_end_of_it` | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:140:43 + --> tests/ui/doc/doc-fixable.rs:141:43 | LL | /** E.g., serialization of an empty list: FooBar | ^^^^^^ @@ -232,7 +232,7 @@ LL | /** E.g., serialization of an empty list: `FooBar` | ~~~~~~~~ error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:145:5 + --> tests/ui/doc/doc-fixable.rs:146:5 | LL | And BarQuz too. | ^^^^^^ @@ -243,7 +243,7 @@ LL | And `BarQuz` too. | ~~~~~~~~ error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:146:1 + --> tests/ui/doc/doc-fixable.rs:147:1 | LL | be_sure_we_got_to_the_end_of_it | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -254,7 +254,7 @@ LL | `be_sure_we_got_to_the_end_of_it` | error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:151:43 + --> tests/ui/doc/doc-fixable.rs:152:43 | LL | /** E.g., serialization of an empty list: FooBar | ^^^^^^ @@ -265,7 +265,7 @@ LL | /** E.g., serialization of an empty list: `FooBar` | ~~~~~~~~ error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:156:5 + --> tests/ui/doc/doc-fixable.rs:157:5 | LL | And BarQuz too. | ^^^^^^ @@ -276,7 +276,7 @@ LL | And `BarQuz` too. | ~~~~~~~~ error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:157:1 + --> tests/ui/doc/doc-fixable.rs:158:1 | LL | be_sure_we_got_to_the_end_of_it | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -287,7 +287,7 @@ LL | `be_sure_we_got_to_the_end_of_it` | error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:168:5 + --> tests/ui/doc/doc-fixable.rs:169:5 | LL | /// be_sure_we_got_to_the_end_of_it | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -298,7 +298,7 @@ LL | /// `be_sure_we_got_to_the_end_of_it` | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:187:22 + --> tests/ui/doc/doc-fixable.rs:188:22 | LL | /// An iterator over mycrate::Collection's values. | ^^^^^^^^^^^^^^^^^^^ @@ -309,7 +309,7 @@ LL | /// An iterator over `mycrate::Collection`'s values. | ~~~~~~~~~~~~~~~~~~~~~ error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:211:34 + --> tests/ui/doc/doc-fixable.rs:212:34 | LL | /// Foo \[bar\] \[baz\] \[qux\]. DocMarkdownLint | ^^^^^^^^^^^^^^^ @@ -320,7 +320,7 @@ LL | /// Foo \[bar\] \[baz\] \[qux\]. `DocMarkdownLint` | ~~~~~~~~~~~~~~~~~ error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:234:22 + --> tests/ui/doc/doc-fixable.rs:235:22 | LL | /// There is no try (do() or do_not()). | ^^^^ @@ -331,7 +331,7 @@ LL | /// There is no try (`do()` or do_not()). | ~~~~~~ error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:234:30 + --> tests/ui/doc/doc-fixable.rs:235:30 | LL | /// There is no try (do() or do_not()). | ^^^^^^^^ @@ -342,7 +342,7 @@ LL | /// There is no try (do() or `do_not()`). | ~~~~~~~~~~ error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:237:5 + --> tests/ui/doc/doc-fixable.rs:238:5 | LL | /// ABes | ^^^^ @@ -353,7 +353,7 @@ LL | /// `ABes` | ~~~~~~ error: item in documentation is missing backticks - --> tests/ui/doc/doc-fixable.rs:243:9 + --> tests/ui/doc/doc-fixable.rs:244:9 | LL | /// foo() | ^^^^^ @@ -364,7 +364,7 @@ LL | /// `foo()` | ~~~~~~~ error: you should put bare URLs between `<`/`>` or make a proper Markdown link - --> tests/ui/doc/doc-fixable.rs:247:5 + --> tests/ui/doc/doc-fixable.rs:248:5 | LL | /// https://github.com/rust-lang/rust-clippy/pull/12836 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `` diff --git a/tests/ui/infinite_loops.rs b/tests/ui/infinite_loops.rs index b6cb7ff49b0a0..0613eddce2669 100644 --- a/tests/ui/infinite_loops.rs +++ b/tests/ui/infinite_loops.rs @@ -3,6 +3,7 @@ #![allow(clippy::never_loop)] #![warn(clippy::infinite_loop)] +#![feature(async_closure)] extern crate proc_macros; use proc_macros::{external, with_span}; @@ -428,4 +429,23 @@ fn continue_outer() { } } +// don't suggest adding `-> !` to async fn/closure that already returning `-> !` +mod issue_12338 { + use super::do_something; + + async fn foo() -> ! { + loop { + do_something(); + } + } + + fn bar() { + let _ = async || -> ! { + loop { + do_something(); + } + }; + } +} + fn main() {} diff --git a/tests/ui/infinite_loops.stderr b/tests/ui/infinite_loops.stderr index 7635a7442f4a2..3a3ed7d0fe8f1 100644 --- a/tests/ui/infinite_loops.stderr +++ b/tests/ui/infinite_loops.stderr @@ -1,5 +1,5 @@ error: infinite loop detected - --> tests/ui/infinite_loops.rs:13:5 + --> tests/ui/infinite_loops.rs:14:5 | LL | / loop { LL | | @@ -15,7 +15,7 @@ LL | fn no_break() -> ! { | ++++ error: infinite loop detected - --> tests/ui/infinite_loops.rs:20:5 + --> tests/ui/infinite_loops.rs:21:5 | LL | / loop { LL | | @@ -32,7 +32,7 @@ LL | fn all_inf() -> ! { | ++++ error: infinite loop detected - --> tests/ui/infinite_loops.rs:22:9 + --> tests/ui/infinite_loops.rs:23:9 | LL | / loop { LL | | @@ -49,7 +49,7 @@ LL | fn all_inf() -> ! { | ++++ error: infinite loop detected - --> tests/ui/infinite_loops.rs:24:13 + --> tests/ui/infinite_loops.rs:25:13 | LL | / loop { LL | | @@ -63,7 +63,7 @@ LL | fn all_inf() -> ! { | ++++ error: infinite loop detected - --> tests/ui/infinite_loops.rs:38:5 + --> tests/ui/infinite_loops.rs:39:5 | LL | / loop { LL | | @@ -74,7 +74,7 @@ LL | | } = help: if this is not intended, try adding a `break` or `return` condition in the loop error: infinite loop detected - --> tests/ui/infinite_loops.rs:51:5 + --> tests/ui/infinite_loops.rs:52:5 | LL | / loop { LL | | fn inner_fn() -> ! { @@ -90,7 +90,7 @@ LL | fn no_break_never_ret_noise() -> ! { | ++++ error: infinite loop detected - --> tests/ui/infinite_loops.rs:94:5 + --> tests/ui/infinite_loops.rs:95:5 | LL | / loop { LL | | @@ -107,7 +107,7 @@ LL | fn break_inner_but_not_outer_1(cond: bool) -> ! { | ++++ error: infinite loop detected - --> tests/ui/infinite_loops.rs:105:5 + --> tests/ui/infinite_loops.rs:106:5 | LL | / loop { LL | | @@ -124,7 +124,7 @@ LL | fn break_inner_but_not_outer_2(cond: bool) -> ! { | ++++ error: infinite loop detected - --> tests/ui/infinite_loops.rs:119:9 + --> tests/ui/infinite_loops.rs:120:9 | LL | / loop { LL | | @@ -138,7 +138,7 @@ LL | fn break_outer_but_not_inner() -> ! { | ++++ error: infinite loop detected - --> tests/ui/infinite_loops.rs:142:9 + --> tests/ui/infinite_loops.rs:143:9 | LL | / loop { LL | | @@ -155,7 +155,7 @@ LL | fn break_wrong_loop(cond: bool) -> ! { | ++++ error: infinite loop detected - --> tests/ui/infinite_loops.rs:182:5 + --> tests/ui/infinite_loops.rs:183:5 | LL | / loop { LL | | @@ -172,7 +172,7 @@ LL | fn match_like() -> ! { | ++++ error: infinite loop detected - --> tests/ui/infinite_loops.rs:223:5 + --> tests/ui/infinite_loops.rs:224:5 | LL | / loop { LL | | @@ -186,7 +186,7 @@ LL | fn match_like() -> ! { | ++++ error: infinite loop detected - --> tests/ui/infinite_loops.rs:228:5 + --> tests/ui/infinite_loops.rs:229:5 | LL | / loop { LL | | @@ -203,7 +203,7 @@ LL | fn match_like() -> ! { | ++++ error: infinite loop detected - --> tests/ui/infinite_loops.rs:333:9 + --> tests/ui/infinite_loops.rs:334:9 | LL | / loop { LL | | @@ -217,7 +217,7 @@ LL | fn problematic_trait_method() -> ! { | ++++ error: infinite loop detected - --> tests/ui/infinite_loops.rs:343:9 + --> tests/ui/infinite_loops.rs:344:9 | LL | / loop { LL | | @@ -231,7 +231,7 @@ LL | fn could_be_problematic() -> ! { | ++++ error: infinite loop detected - --> tests/ui/infinite_loops.rs:352:9 + --> tests/ui/infinite_loops.rs:353:9 | LL | / loop { LL | | @@ -245,7 +245,7 @@ LL | let _loop_forever = || -> ! { | ++++ error: infinite loop detected - --> tests/ui/infinite_loops.rs:366:8 + --> tests/ui/infinite_loops.rs:367:8 | LL | Ok(loop { | ________^ @@ -256,7 +256,7 @@ LL | | }) = help: if this is not intended, try adding a `break` or `return` condition in the loop error: infinite loop detected - --> tests/ui/infinite_loops.rs:408:5 + --> tests/ui/infinite_loops.rs:409:5 | LL | / 'infinite: loop { LL | | @@ -272,7 +272,7 @@ LL | fn continue_outer() -> ! { | ++++ error: infinite loop detected - --> tests/ui/infinite_loops.rs:415:5 + --> tests/ui/infinite_loops.rs:416:5 | LL | / loop { LL | | @@ -289,7 +289,7 @@ LL | fn continue_outer() -> ! { | ++++ error: infinite loop detected - --> tests/ui/infinite_loops.rs:417:9 + --> tests/ui/infinite_loops.rs:418:9 | LL | / 'inner: loop { LL | | loop { @@ -304,7 +304,7 @@ LL | fn continue_outer() -> ! { | ++++ error: infinite loop detected - --> tests/ui/infinite_loops.rs:425:5 + --> tests/ui/infinite_loops.rs:426:5 | LL | / loop { LL | | diff --git a/tests/ui/iter_without_into_iter.stderr b/tests/ui/iter_without_into_iter.stderr index 7c42fa1dd89ce..d748c85003bbf 100644 --- a/tests/ui/iter_without_into_iter.stderr +++ b/tests/ui/iter_without_into_iter.stderr @@ -13,8 +13,8 @@ help: consider implementing `IntoIterator` for `&S1` | LL + LL + impl IntoIterator for &S1 { -LL + type IntoIter = std::slice::Iter<'_, u8>; LL + type Item = &u8; +LL + type IntoIter = std::slice::Iter<'_, u8>; LL + fn into_iter(self) -> Self::IntoIter { LL + self.iter() LL + } @@ -34,8 +34,8 @@ help: consider implementing `IntoIterator` for `&mut S1` | LL + LL + impl IntoIterator for &mut S1 { -LL + type IntoIter = std::slice::IterMut<'_, u8>; LL + type Item = &mut u8; +LL + type IntoIter = std::slice::IterMut<'_, u8>; LL + fn into_iter(self) -> Self::IntoIter { LL + self.iter() LL + } @@ -55,8 +55,8 @@ help: consider implementing `IntoIterator` for `&S3<'a>` | LL + LL + impl IntoIterator for &S3<'a> { -LL + type IntoIter = std::slice::Iter<'_, u8>; LL + type Item = &u8; +LL + type IntoIter = std::slice::Iter<'_, u8>; LL + fn into_iter(self) -> Self::IntoIter { LL + self.iter() LL + } @@ -76,8 +76,8 @@ help: consider implementing `IntoIterator` for `&mut S3<'a>` | LL + LL + impl IntoIterator for &mut S3<'a> { -LL + type IntoIter = std::slice::IterMut<'_, u8>; LL + type Item = &mut u8; +LL + type IntoIter = std::slice::IterMut<'_, u8>; LL + fn into_iter(self) -> Self::IntoIter { LL + self.iter() LL + } @@ -96,8 +96,8 @@ help: consider implementing `IntoIterator` for `&S8` | LL + LL + impl IntoIterator for &S8 { -LL + type IntoIter = std::slice::Iter<'static, T>; LL + type Item = &T; +LL + type IntoIter = std::slice::Iter<'static, T>; LL + fn into_iter(self) -> Self::IntoIter { LL + self.iter() LL + } @@ -117,8 +117,8 @@ help: consider implementing `IntoIterator` for `&S9` | LL + LL + impl IntoIterator for &S9 { -LL + type IntoIter = std::slice::Iter<'_, T>; LL + type Item = &T; +LL + type IntoIter = std::slice::Iter<'_, T>; LL + fn into_iter(self) -> Self::IntoIter { LL + self.iter() LL + } @@ -138,8 +138,8 @@ help: consider implementing `IntoIterator` for `&mut S9` | LL + LL + impl IntoIterator for &mut S9 { -LL + type IntoIter = std::slice::IterMut<'_, T>; LL + type Item = &mut T; +LL + type IntoIter = std::slice::IterMut<'_, T>; LL + fn into_iter(self) -> Self::IntoIter { LL + self.iter() LL + } @@ -162,8 +162,8 @@ help: consider implementing `IntoIterator` for `&Issue12037` | LL ~ LL + impl IntoIterator for &Issue12037 { -LL + type IntoIter = std::slice::Iter<'_, u8>; LL + type Item = &u8; +LL + type IntoIter = std::slice::Iter<'_, u8>; LL + fn into_iter(self) -> Self::IntoIter { LL + self.iter() LL + } diff --git a/tests/ui/large_const_arrays.fixed b/tests/ui/large_const_arrays.fixed index 543ce460e7ba7..e5b28cb6a9ddb 100644 --- a/tests/ui/large_const_arrays.fixed +++ b/tests/ui/large_const_arrays.fixed @@ -9,11 +9,13 @@ pub struct S { // Should lint pub(crate) static FOO_PUB_CRATE: [u32; 1_000_000] = [0u32; 1_000_000]; pub static FOO_PUB: [u32; 1_000_000] = [0u32; 1_000_000]; +static FOO_COMPUTED: [u32; 1_000 * 100] = [0u32; 1_000 * 100]; static FOO: [u32; 1_000_000] = [0u32; 1_000_000]; // Good pub(crate) const G_FOO_PUB_CRATE: [u32; 250] = [0u32; 250]; pub const G_FOO_PUB: [u32; 250] = [0u32; 250]; +const G_FOO_COMPUTED: [u32; 25 * 10] = [0u32; 25 * 10]; const G_FOO: [u32; 250] = [0u32; 250]; fn main() { diff --git a/tests/ui/large_const_arrays.rs b/tests/ui/large_const_arrays.rs index e23a8081171d6..b9593225da2f7 100644 --- a/tests/ui/large_const_arrays.rs +++ b/tests/ui/large_const_arrays.rs @@ -9,11 +9,13 @@ pub struct S { // Should lint pub(crate) const FOO_PUB_CRATE: [u32; 1_000_000] = [0u32; 1_000_000]; pub const FOO_PUB: [u32; 1_000_000] = [0u32; 1_000_000]; +const FOO_COMPUTED: [u32; 1_000 * 100] = [0u32; 1_000 * 100]; const FOO: [u32; 1_000_000] = [0u32; 1_000_000]; // Good pub(crate) const G_FOO_PUB_CRATE: [u32; 250] = [0u32; 250]; pub const G_FOO_PUB: [u32; 250] = [0u32; 250]; +const G_FOO_COMPUTED: [u32; 25 * 10] = [0u32; 25 * 10]; const G_FOO: [u32; 250] = [0u32; 250]; fn main() { diff --git a/tests/ui/large_const_arrays.stderr b/tests/ui/large_const_arrays.stderr index 88f921b81f721..2711220539085 100644 --- a/tests/ui/large_const_arrays.stderr +++ b/tests/ui/large_const_arrays.stderr @@ -20,13 +20,21 @@ LL | pub const FOO_PUB: [u32; 1_000_000] = [0u32; 1_000_000]; error: large array defined as const --> tests/ui/large_const_arrays.rs:12:1 | +LL | const FOO_COMPUTED: [u32; 1_000 * 100] = [0u32; 1_000 * 100]; + | -----^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | help: make this a static item: `static` + +error: large array defined as const + --> tests/ui/large_const_arrays.rs:13:1 + | LL | const FOO: [u32; 1_000_000] = [0u32; 1_000_000]; | -----^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | | | help: make this a static item: `static` error: large array defined as const - --> tests/ui/large_const_arrays.rs:21:5 + --> tests/ui/large_const_arrays.rs:23:5 | LL | pub const BAR_PUB: [u32; 1_000_000] = [0u32; 1_000_000]; | ^^^^-----^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -34,7 +42,7 @@ LL | pub const BAR_PUB: [u32; 1_000_000] = [0u32; 1_000_000]; | help: make this a static item: `static` error: large array defined as const - --> tests/ui/large_const_arrays.rs:22:5 + --> tests/ui/large_const_arrays.rs:24:5 | LL | const BAR: [u32; 1_000_000] = [0u32; 1_000_000]; | -----^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -42,7 +50,7 @@ LL | const BAR: [u32; 1_000_000] = [0u32; 1_000_000]; | help: make this a static item: `static` error: large array defined as const - --> tests/ui/large_const_arrays.rs:23:5 + --> tests/ui/large_const_arrays.rs:25:5 | LL | pub const BAR_STRUCT_PUB: [S; 5_000] = [S { data: [0; 32] }; 5_000]; | ^^^^-----^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -50,7 +58,7 @@ LL | pub const BAR_STRUCT_PUB: [S; 5_000] = [S { data: [0; 32] }; 5_000]; | help: make this a static item: `static` error: large array defined as const - --> tests/ui/large_const_arrays.rs:24:5 + --> tests/ui/large_const_arrays.rs:26:5 | LL | const BAR_STRUCT: [S; 5_000] = [S { data: [0; 32] }; 5_000]; | -----^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -58,7 +66,7 @@ LL | const BAR_STRUCT: [S; 5_000] = [S { data: [0; 32] }; 5_000]; | help: make this a static item: `static` error: large array defined as const - --> tests/ui/large_const_arrays.rs:25:5 + --> tests/ui/large_const_arrays.rs:27:5 | LL | pub const BAR_S_PUB: [Option<&str>; 200_000] = [Some("str"); 200_000]; | ^^^^-----^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -66,12 +74,12 @@ LL | pub const BAR_S_PUB: [Option<&str>; 200_000] = [Some("str"); 200_000]; | help: make this a static item: `static` error: large array defined as const - --> tests/ui/large_const_arrays.rs:26:5 + --> tests/ui/large_const_arrays.rs:28:5 | LL | const BAR_S: [Option<&str>; 200_000] = [Some("str"); 200_000]; | -----^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | | | help: make this a static item: `static` -error: aborting due to 9 previous errors +error: aborting due to 10 previous errors diff --git a/tests/ui/manual_bits.fixed b/tests/ui/manual_bits.fixed index 4de01905e8ad1..8e5cb7d38b9ba 100644 --- a/tests/ui/manual_bits.fixed +++ b/tests/ui/manual_bits.fixed @@ -56,3 +56,14 @@ fn main() { let _ = (u128::BITS as usize).pow(5); let _ = &(u128::BITS as usize); } + +fn should_not_lint() { + macro_rules! bits_via_macro { + ($T: ty) => { + size_of::<$T>() * 8; + }; + } + + bits_via_macro!(u8); + bits_via_macro!(String); +} diff --git a/tests/ui/manual_bits.rs b/tests/ui/manual_bits.rs index d4f369fcf87d8..5e492ed15d211 100644 --- a/tests/ui/manual_bits.rs +++ b/tests/ui/manual_bits.rs @@ -56,3 +56,14 @@ fn main() { let _ = (size_of::() * 8).pow(5); let _ = &(size_of::() * 8); } + +fn should_not_lint() { + macro_rules! bits_via_macro { + ($T: ty) => { + size_of::<$T>() * 8; + }; + } + + bits_via_macro!(u8); + bits_via_macro!(String); +} diff --git a/tests/ui/map_all_any_identity.fixed b/tests/ui/map_all_any_identity.fixed new file mode 100644 index 0000000000000..c92462ab9c278 --- /dev/null +++ b/tests/ui/map_all_any_identity.fixed @@ -0,0 +1,21 @@ +#![warn(clippy::map_all_any_identity)] + +fn main() { + let _ = ["foo"].into_iter().any(|s| s == "foo"); + //~^ map_all_any_identity + let _ = ["foo"].into_iter().all(|s| s == "foo"); + //~^ map_all_any_identity + + // + // Do not lint + // + // Not identity + let _ = ["foo"].into_iter().map(|s| s.len()).any(|n| n > 0); + // Macro + macro_rules! map { + ($x:expr) => { + $x.into_iter().map(|s| s == "foo") + }; + } + map!(["foo"]).any(|a| a); +} diff --git a/tests/ui/map_all_any_identity.rs b/tests/ui/map_all_any_identity.rs new file mode 100644 index 0000000000000..0e4a564ca0467 --- /dev/null +++ b/tests/ui/map_all_any_identity.rs @@ -0,0 +1,21 @@ +#![warn(clippy::map_all_any_identity)] + +fn main() { + let _ = ["foo"].into_iter().map(|s| s == "foo").any(|a| a); + //~^ map_all_any_identity + let _ = ["foo"].into_iter().map(|s| s == "foo").all(std::convert::identity); + //~^ map_all_any_identity + + // + // Do not lint + // + // Not identity + let _ = ["foo"].into_iter().map(|s| s.len()).any(|n| n > 0); + // Macro + macro_rules! map { + ($x:expr) => { + $x.into_iter().map(|s| s == "foo") + }; + } + map!(["foo"]).any(|a| a); +} diff --git a/tests/ui/map_all_any_identity.stderr b/tests/ui/map_all_any_identity.stderr new file mode 100644 index 0000000000000..98fdcc2a9393f --- /dev/null +++ b/tests/ui/map_all_any_identity.stderr @@ -0,0 +1,26 @@ +error: usage of `.map(...).any(identity)` + --> tests/ui/map_all_any_identity.rs:4:33 + | +LL | let _ = ["foo"].into_iter().map(|s| s == "foo").any(|a| a); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::map-all-any-identity` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::map_all_any_identity)]` +help: use `.any(...)` instead + | +LL | let _ = ["foo"].into_iter().any(|s| s == "foo"); + | ~~~~~~~~~~~~~~~~~~~ + +error: usage of `.map(...).all(identity)` + --> tests/ui/map_all_any_identity.rs:6:33 + | +LL | let _ = ["foo"].into_iter().map(|s| s == "foo").all(std::convert::identity); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: use `.all(...)` instead + | +LL | let _ = ["foo"].into_iter().all(|s| s == "foo"); + | ~~~~~~~~~~~~~~~~~~~ + +error: aborting due to 2 previous errors + diff --git a/tests/ui/map_with_unused_argument_over_ranges.fixed b/tests/ui/map_with_unused_argument_over_ranges.fixed new file mode 100644 index 0000000000000..cf520e71a64fc --- /dev/null +++ b/tests/ui/map_with_unused_argument_over_ranges.fixed @@ -0,0 +1,73 @@ +#![allow( + unused, + clippy::redundant_closure, + clippy::reversed_empty_ranges, + clippy::identity_op +)] +#![warn(clippy::map_with_unused_argument_over_ranges)] + +fn do_something() -> usize { + todo!() +} + +fn do_something_interesting(x: usize, y: usize) -> usize { + todo!() +} + +macro_rules! gen { + () => { + (0..10).map(|_| do_something()); + }; +} + +fn main() { + // These should be raised + std::iter::repeat_with(|| do_something()).take(10); + std::iter::repeat_with(|| do_something()).take(10); + std::iter::repeat_with(|| do_something()).take(11); + std::iter::repeat_with(|| do_something()).take(7); + std::iter::repeat_with(|| do_something()).take(8); + std::iter::repeat_n(3, 10); + std::iter::repeat_with(|| { + let x = 3; + x + 2 + }).take(10); + std::iter::repeat_with(|| do_something()).take(10).collect::>(); + let upper = 4; + std::iter::repeat_with(|| do_something()).take(upper); + let upper_fn = || 4; + std::iter::repeat_with(|| do_something()).take(upper_fn()); + std::iter::repeat_with(|| do_something()).take(upper_fn() + 1); + std::iter::repeat_with(|| do_something()).take(upper_fn() - 2); + std::iter::repeat_with(|| do_something()).take(upper_fn() - 1); + (-3..9).map(|_| do_something()); + std::iter::repeat_with(|| do_something()).take(0); + std::iter::repeat_with(|| do_something()).take(1); + std::iter::repeat_with(|| do_something()).take((1 << 4) - 0); + // These should not be raised + gen!(); + let lower = 2; + let lower_fn = || 2; + (lower..upper_fn()).map(|_| do_something()); // Ranges not starting at zero not yet handled + (lower_fn()..upper_fn()).map(|_| do_something()); // Ranges not starting at zero not yet handled + (lower_fn()..=upper_fn()).map(|_| do_something()); // Ranges not starting at zero not yet handled + (0..10).map(|x| do_something_interesting(x, 4)); // Actual map over range + "Foobar".chars().map(|_| do_something()); // Not a map over range + // i128::MAX == 340282366920938463463374607431768211455 + (0..=340282366920938463463374607431768211455).map(|_: u128| do_something()); // Can't be replaced due to overflow +} + +#[clippy::msrv = "1.27"] +fn msrv_1_27() { + (0..10).map(|_| do_something()); +} + +#[clippy::msrv = "1.28"] +fn msrv_1_28() { + std::iter::repeat_with(|| do_something()).take(10); +} + +#[clippy::msrv = "1.81"] +fn msrv_1_82() { + std::iter::repeat(3).take(10); +} diff --git a/tests/ui/map_with_unused_argument_over_ranges.rs b/tests/ui/map_with_unused_argument_over_ranges.rs new file mode 100644 index 0000000000000..298eee9ca3fa1 --- /dev/null +++ b/tests/ui/map_with_unused_argument_over_ranges.rs @@ -0,0 +1,73 @@ +#![allow( + unused, + clippy::redundant_closure, + clippy::reversed_empty_ranges, + clippy::identity_op +)] +#![warn(clippy::map_with_unused_argument_over_ranges)] + +fn do_something() -> usize { + todo!() +} + +fn do_something_interesting(x: usize, y: usize) -> usize { + todo!() +} + +macro_rules! gen { + () => { + (0..10).map(|_| do_something()); + }; +} + +fn main() { + // These should be raised + (0..10).map(|_| do_something()); + (0..10).map(|_foo| do_something()); + (0..=10).map(|_| do_something()); + (3..10).map(|_| do_something()); + (3..=10).map(|_| do_something()); + (0..10).map(|_| 3); + (0..10).map(|_| { + let x = 3; + x + 2 + }); + (0..10).map(|_| do_something()).collect::>(); + let upper = 4; + (0..upper).map(|_| do_something()); + let upper_fn = || 4; + (0..upper_fn()).map(|_| do_something()); + (0..=upper_fn()).map(|_| do_something()); + (2..upper_fn()).map(|_| do_something()); + (2..=upper_fn()).map(|_| do_something()); + (-3..9).map(|_| do_something()); + (9..3).map(|_| do_something()); + (9..=9).map(|_| do_something()); + (1..=1 << 4).map(|_| do_something()); + // These should not be raised + gen!(); + let lower = 2; + let lower_fn = || 2; + (lower..upper_fn()).map(|_| do_something()); // Ranges not starting at zero not yet handled + (lower_fn()..upper_fn()).map(|_| do_something()); // Ranges not starting at zero not yet handled + (lower_fn()..=upper_fn()).map(|_| do_something()); // Ranges not starting at zero not yet handled + (0..10).map(|x| do_something_interesting(x, 4)); // Actual map over range + "Foobar".chars().map(|_| do_something()); // Not a map over range + // i128::MAX == 340282366920938463463374607431768211455 + (0..=340282366920938463463374607431768211455).map(|_: u128| do_something()); // Can't be replaced due to overflow +} + +#[clippy::msrv = "1.27"] +fn msrv_1_27() { + (0..10).map(|_| do_something()); +} + +#[clippy::msrv = "1.28"] +fn msrv_1_28() { + (0..10).map(|_| do_something()); +} + +#[clippy::msrv = "1.81"] +fn msrv_1_82() { + (0..10).map(|_| 3); +} diff --git a/tests/ui/map_with_unused_argument_over_ranges.stderr b/tests/ui/map_with_unused_argument_over_ranges.stderr new file mode 100644 index 0000000000000..0b56c6d952194 --- /dev/null +++ b/tests/ui/map_with_unused_argument_over_ranges.stderr @@ -0,0 +1,223 @@ +error: map of a closure that does not depend on its parameter over a range + --> tests/ui/map_with_unused_argument_over_ranges.rs:25:5 + | +LL | (0..10).map(|_| do_something()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::map-with-unused-argument-over-ranges` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::map_with_unused_argument_over_ranges)]` +help: remove the explicit range and use `repeat_with` and `take` + | +LL - (0..10).map(|_| do_something()); +LL + std::iter::repeat_with(|| do_something()).take(10); + | + +error: map of a closure that does not depend on its parameter over a range + --> tests/ui/map_with_unused_argument_over_ranges.rs:26:5 + | +LL | (0..10).map(|_foo| do_something()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: remove the explicit range and use `repeat_with` and `take` + | +LL - (0..10).map(|_foo| do_something()); +LL + std::iter::repeat_with(|| do_something()).take(10); + | + +error: map of a closure that does not depend on its parameter over a range + --> tests/ui/map_with_unused_argument_over_ranges.rs:27:5 + | +LL | (0..=10).map(|_| do_something()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: remove the explicit range and use `repeat_with` and `take` + | +LL - (0..=10).map(|_| do_something()); +LL + std::iter::repeat_with(|| do_something()).take(11); + | + +error: map of a closure that does not depend on its parameter over a range + --> tests/ui/map_with_unused_argument_over_ranges.rs:28:5 + | +LL | (3..10).map(|_| do_something()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: remove the explicit range and use `repeat_with` and `take` + | +LL - (3..10).map(|_| do_something()); +LL + std::iter::repeat_with(|| do_something()).take(7); + | + +error: map of a closure that does not depend on its parameter over a range + --> tests/ui/map_with_unused_argument_over_ranges.rs:29:5 + | +LL | (3..=10).map(|_| do_something()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: remove the explicit range and use `repeat_with` and `take` + | +LL - (3..=10).map(|_| do_something()); +LL + std::iter::repeat_with(|| do_something()).take(8); + | + +error: map of a closure that does not depend on its parameter over a range + --> tests/ui/map_with_unused_argument_over_ranges.rs:30:5 + | +LL | (0..10).map(|_| 3); + | ^^^^^^^^^^^^^^^^^^ + | +help: remove the explicit range and use `repeat_n` + | +LL | std::iter::repeat_n(3, 10); + | ~~~~~~~~~~~~~~~~~~~ ~~~~~ + +error: map of a closure that does not depend on its parameter over a range + --> tests/ui/map_with_unused_argument_over_ranges.rs:31:5 + | +LL | / (0..10).map(|_| { +LL | | let x = 3; +LL | | x + 2 +LL | | }); + | |______^ + | +help: remove the explicit range and use `repeat_with` and `take` + | +LL ~ std::iter::repeat_with(|| { +LL | let x = 3; +LL | x + 2 +LL ~ }).take(10); + | + +error: map of a closure that does not depend on its parameter over a range + --> tests/ui/map_with_unused_argument_over_ranges.rs:35:5 + | +LL | (0..10).map(|_| do_something()).collect::>(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: remove the explicit range and use `repeat_with` and `take` + | +LL - (0..10).map(|_| do_something()).collect::>(); +LL + std::iter::repeat_with(|| do_something()).take(10).collect::>(); + | + +error: map of a closure that does not depend on its parameter over a range + --> tests/ui/map_with_unused_argument_over_ranges.rs:37:5 + | +LL | (0..upper).map(|_| do_something()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: remove the explicit range and use `repeat_with` and `take` + | +LL - (0..upper).map(|_| do_something()); +LL + std::iter::repeat_with(|| do_something()).take(upper); + | + +error: map of a closure that does not depend on its parameter over a range + --> tests/ui/map_with_unused_argument_over_ranges.rs:39:5 + | +LL | (0..upper_fn()).map(|_| do_something()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: remove the explicit range and use `repeat_with` and `take` + | +LL - (0..upper_fn()).map(|_| do_something()); +LL + std::iter::repeat_with(|| do_something()).take(upper_fn()); + | + +error: map of a closure that does not depend on its parameter over a range + --> tests/ui/map_with_unused_argument_over_ranges.rs:40:5 + | +LL | (0..=upper_fn()).map(|_| do_something()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: remove the explicit range and use `repeat_with` and `take` + | +LL - (0..=upper_fn()).map(|_| do_something()); +LL + std::iter::repeat_with(|| do_something()).take(upper_fn() + 1); + | + +error: map of a closure that does not depend on its parameter over a range + --> tests/ui/map_with_unused_argument_over_ranges.rs:41:5 + | +LL | (2..upper_fn()).map(|_| do_something()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: remove the explicit range and use `repeat_with` and `take` + | +LL - (2..upper_fn()).map(|_| do_something()); +LL + std::iter::repeat_with(|| do_something()).take(upper_fn() - 2); + | + +error: map of a closure that does not depend on its parameter over a range + --> tests/ui/map_with_unused_argument_over_ranges.rs:42:5 + | +LL | (2..=upper_fn()).map(|_| do_something()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: remove the explicit range and use `repeat_with` and `take` + | +LL - (2..=upper_fn()).map(|_| do_something()); +LL + std::iter::repeat_with(|| do_something()).take(upper_fn() - 1); + | + +error: map of a closure that does not depend on its parameter over a range + --> tests/ui/map_with_unused_argument_over_ranges.rs:44:5 + | +LL | (9..3).map(|_| do_something()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: remove the explicit range and use `repeat_with` and `take` + | +LL - (9..3).map(|_| do_something()); +LL + std::iter::repeat_with(|| do_something()).take(0); + | + +error: map of a closure that does not depend on its parameter over a range + --> tests/ui/map_with_unused_argument_over_ranges.rs:45:5 + | +LL | (9..=9).map(|_| do_something()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: remove the explicit range and use `repeat_with` and `take` + | +LL - (9..=9).map(|_| do_something()); +LL + std::iter::repeat_with(|| do_something()).take(1); + | + +error: map of a closure that does not depend on its parameter over a range + --> tests/ui/map_with_unused_argument_over_ranges.rs:46:5 + | +LL | (1..=1 << 4).map(|_| do_something()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: remove the explicit range and use `repeat_with` and `take` + | +LL - (1..=1 << 4).map(|_| do_something()); +LL + std::iter::repeat_with(|| do_something()).take((1 << 4) - 0); + | + +error: map of a closure that does not depend on its parameter over a range + --> tests/ui/map_with_unused_argument_over_ranges.rs:67:5 + | +LL | (0..10).map(|_| do_something()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: remove the explicit range and use `repeat_with` and `take` + | +LL - (0..10).map(|_| do_something()); +LL + std::iter::repeat_with(|| do_something()).take(10); + | + +error: map of a closure that does not depend on its parameter over a range + --> tests/ui/map_with_unused_argument_over_ranges.rs:72:5 + | +LL | (0..10).map(|_| 3); + | ^^^^^^^^^^^^^^^^^^ + | +help: remove the explicit range and use `repeat` and `take` + | +LL | std::iter::repeat(3).take(10); + | ~~~~~~~~~~~~~~~~~ ~ +++++++++ + +error: aborting due to 18 previous errors + diff --git a/tests/ui/missing_const_for_fn/could_be_const.fixed b/tests/ui/missing_const_for_fn/could_be_const.fixed index 7c88278951152..754fe061c4aea 100644 --- a/tests/ui/missing_const_for_fn/could_be_const.fixed +++ b/tests/ui/missing_const_for_fn/could_be_const.fixed @@ -2,7 +2,6 @@ #![allow(incomplete_features, clippy::let_and_return, clippy::missing_transmute_annotations)] #![feature(const_trait_impl, abi_vectorcall)] - use std::mem::transmute; struct Game { diff --git a/tests/ui/missing_const_for_fn/could_be_const.rs b/tests/ui/missing_const_for_fn/could_be_const.rs index 48312d48ed3d0..460be0733e035 100644 --- a/tests/ui/missing_const_for_fn/could_be_const.rs +++ b/tests/ui/missing_const_for_fn/could_be_const.rs @@ -2,7 +2,6 @@ #![allow(incomplete_features, clippy::let_and_return, clippy::missing_transmute_annotations)] #![feature(const_trait_impl, abi_vectorcall)] - use std::mem::transmute; struct Game { diff --git a/tests/ui/missing_const_for_fn/could_be_const.stderr b/tests/ui/missing_const_for_fn/could_be_const.stderr index f28dec5c7f195..d553c52255653 100644 --- a/tests/ui/missing_const_for_fn/could_be_const.stderr +++ b/tests/ui/missing_const_for_fn/could_be_const.stderr @@ -1,5 +1,5 @@ error: this could be a `const fn` - --> tests/ui/missing_const_for_fn/could_be_const.rs:14:5 + --> tests/ui/missing_const_for_fn/could_be_const.rs:13:5 | LL | / pub fn new() -> Self { LL | | @@ -16,7 +16,7 @@ LL | pub const fn new() -> Self { | +++++ error: this could be a `const fn` - --> tests/ui/missing_const_for_fn/could_be_const.rs:20:5 + --> tests/ui/missing_const_for_fn/could_be_const.rs:19:5 | LL | / fn const_generic_params<'a, T, const N: usize>(&self, b: &'a [T; N]) -> &'a [T; N] { LL | | @@ -30,7 +30,7 @@ LL | const fn const_generic_params<'a, T, const N: usize>(&self, b: &'a [T; | +++++ error: this could be a `const fn` - --> tests/ui/missing_const_for_fn/could_be_const.rs:27:1 + --> tests/ui/missing_const_for_fn/could_be_const.rs:26:1 | LL | / fn one() -> i32 { LL | | @@ -44,7 +44,7 @@ LL | const fn one() -> i32 { | +++++ error: this could be a `const fn` - --> tests/ui/missing_const_for_fn/could_be_const.rs:33:1 + --> tests/ui/missing_const_for_fn/could_be_const.rs:32:1 | LL | / fn two() -> i32 { LL | | @@ -59,7 +59,7 @@ LL | const fn two() -> i32 { | +++++ error: this could be a `const fn` - --> tests/ui/missing_const_for_fn/could_be_const.rs:40:1 + --> tests/ui/missing_const_for_fn/could_be_const.rs:39:1 | LL | / fn string() -> String { LL | | @@ -73,7 +73,7 @@ LL | const fn string() -> String { | +++++ error: this could be a `const fn` - --> tests/ui/missing_const_for_fn/could_be_const.rs:46:1 + --> tests/ui/missing_const_for_fn/could_be_const.rs:45:1 | LL | / unsafe fn four() -> i32 { LL | | @@ -87,7 +87,7 @@ LL | const unsafe fn four() -> i32 { | +++++ error: this could be a `const fn` - --> tests/ui/missing_const_for_fn/could_be_const.rs:52:1 + --> tests/ui/missing_const_for_fn/could_be_const.rs:51:1 | LL | / fn generic(t: T) -> T { LL | | @@ -101,7 +101,7 @@ LL | const fn generic(t: T) -> T { | +++++ error: this could be a `const fn` - --> tests/ui/missing_const_for_fn/could_be_const.rs:61:1 + --> tests/ui/missing_const_for_fn/could_be_const.rs:60:1 | LL | / fn generic_arr(t: [T; 1]) -> T { LL | | @@ -115,7 +115,7 @@ LL | const fn generic_arr(t: [T; 1]) -> T { | +++++ error: this could be a `const fn` - --> tests/ui/missing_const_for_fn/could_be_const.rs:75:9 + --> tests/ui/missing_const_for_fn/could_be_const.rs:74:9 | LL | / pub fn b(self, a: &A) -> B { LL | | @@ -129,7 +129,7 @@ LL | pub const fn b(self, a: &A) -> B { | +++++ error: this could be a `const fn` - --> tests/ui/missing_const_for_fn/could_be_const.rs:85:5 + --> tests/ui/missing_const_for_fn/could_be_const.rs:84:5 | LL | / fn const_fn_stabilized_before_msrv(byte: u8) { LL | | @@ -143,7 +143,7 @@ LL | const fn const_fn_stabilized_before_msrv(byte: u8) { | +++++ error: this could be a `const fn` - --> tests/ui/missing_const_for_fn/could_be_const.rs:97:1 + --> tests/ui/missing_const_for_fn/could_be_const.rs:96:1 | LL | / fn msrv_1_46() -> i32 { LL | | @@ -157,7 +157,7 @@ LL | const fn msrv_1_46() -> i32 { | +++++ error: this could be a `const fn` - --> tests/ui/missing_const_for_fn/could_be_const.rs:117:1 + --> tests/ui/missing_const_for_fn/could_be_const.rs:116:1 | LL | fn d(this: D) {} | ^^^^^^^^^^^^^^^^ @@ -168,7 +168,7 @@ LL | const fn d(this: D) {} | +++++ error: this could be a `const fn` - --> tests/ui/missing_const_for_fn/could_be_const.rs:125:9 + --> tests/ui/missing_const_for_fn/could_be_const.rs:124:9 | LL | / fn deref_ptr_can_be_const(self) -> usize { LL | | @@ -182,7 +182,7 @@ LL | const fn deref_ptr_can_be_const(self) -> usize { | +++++ error: this could be a `const fn` - --> tests/ui/missing_const_for_fn/could_be_const.rs:130:9 + --> tests/ui/missing_const_for_fn/could_be_const.rs:129:9 | LL | / fn deref_copied_val(self) -> usize { LL | | @@ -196,7 +196,7 @@ LL | const fn deref_copied_val(self) -> usize { | +++++ error: this could be a `const fn` - --> tests/ui/missing_const_for_fn/could_be_const.rs:141:5 + --> tests/ui/missing_const_for_fn/could_be_const.rs:140:5 | LL | / fn union_access_can_be_const() { LL | | @@ -211,7 +211,7 @@ LL | const fn union_access_can_be_const() { | +++++ error: this could be a `const fn` - --> tests/ui/missing_const_for_fn/could_be_const.rs:149:9 + --> tests/ui/missing_const_for_fn/could_be_const.rs:148:9 | LL | extern "C" fn c() {} | ^^^^^^^^^^^^^^^^^^^^ @@ -222,7 +222,7 @@ LL | const extern "C" fn c() {} | +++++ error: this could be a `const fn` - --> tests/ui/missing_const_for_fn/could_be_const.rs:153:9 + --> tests/ui/missing_const_for_fn/could_be_const.rs:152:9 | LL | extern fn implicit_c() {} | ^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -233,7 +233,7 @@ LL | const extern fn implicit_c() {} | +++++ error: this could be a `const fn` - --> tests/ui/missing_const_for_fn/could_be_const.rs:170:9 + --> tests/ui/missing_const_for_fn/could_be_const.rs:169:9 | LL | / pub fn new(strings: Vec) -> Self { LL | | Self { strings } @@ -246,7 +246,7 @@ LL | pub const fn new(strings: Vec) -> Self { | +++++ error: this could be a `const fn` - --> tests/ui/missing_const_for_fn/could_be_const.rs:175:9 + --> tests/ui/missing_const_for_fn/could_be_const.rs:174:9 | LL | / pub fn empty() -> Self { LL | | Self { strings: Vec::new() } @@ -259,7 +259,7 @@ LL | pub const fn empty() -> Self { | +++++ error: this could be a `const fn` - --> tests/ui/missing_const_for_fn/could_be_const.rs:186:9 + --> tests/ui/missing_const_for_fn/could_be_const.rs:185:9 | LL | / pub fn new(text: String) -> Self { LL | | let vec = Vec::new(); @@ -273,7 +273,7 @@ LL | pub const fn new(text: String) -> Self { | +++++ error: this could be a `const fn` - --> tests/ui/missing_const_for_fn/could_be_const.rs:205:5 + --> tests/ui/missing_const_for_fn/could_be_const.rs:204:5 | LL | fn alias_ty_is_projection(bar: <() as FooTrait>::Foo) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -284,7 +284,7 @@ LL | const fn alias_ty_is_projection(bar: <() as FooTrait>::Foo) {} | +++++ error: this could be a `const fn` - --> tests/ui/missing_const_for_fn/could_be_const.rs:209:5 + --> tests/ui/missing_const_for_fn/could_be_const.rs:208:5 | LL | extern "C-unwind" fn c_unwind() {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -295,7 +295,7 @@ LL | const extern "C-unwind" fn c_unwind() {} | +++++ error: this could be a `const fn` - --> tests/ui/missing_const_for_fn/could_be_const.rs:211:5 + --> tests/ui/missing_const_for_fn/could_be_const.rs:210:5 | LL | extern "system" fn system() {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -306,7 +306,7 @@ LL | const extern "system" fn system() {} | +++++ error: this could be a `const fn` - --> tests/ui/missing_const_for_fn/could_be_const.rs:213:5 + --> tests/ui/missing_const_for_fn/could_be_const.rs:212:5 | LL | extern "system-unwind" fn system_unwind() {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -317,7 +317,7 @@ LL | const extern "system-unwind" fn system_unwind() {} | +++++ error: this could be a `const fn` - --> tests/ui/missing_const_for_fn/could_be_const.rs:215:5 + --> tests/ui/missing_const_for_fn/could_be_const.rs:214:5 | LL | pub extern "vectorcall" fn std_call() {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -328,7 +328,7 @@ LL | pub const extern "vectorcall" fn std_call() {} | +++++ error: this could be a `const fn` - --> tests/ui/missing_const_for_fn/could_be_const.rs:217:5 + --> tests/ui/missing_const_for_fn/could_be_const.rs:216:5 | LL | pub extern "vectorcall-unwind" fn std_call_unwind() {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/missing_doc.rs b/tests/ui/missing_doc.rs index 9c936d7fa23e1..31bc4309582ea 100644 --- a/tests/ui/missing_doc.rs +++ b/tests/ui/missing_doc.rs @@ -116,6 +116,14 @@ with_span!(span pub fn foo_pm() {}); with_span!(span pub static FOO_PM: u32 = 0;); with_span!(span pub const FOO2_PM: u32 = 0;); +// Don't lint unnamed constants +const _: () = (); + +fn issue13298() { + // Rustdoc doesn't generate documentation for items within other items like fns or consts + const MSG: &str = "Hello, world!"; +} + // issue #12197 // Undocumented field originated inside of spanned proc-macro attribute /// Some dox for struct. diff --git a/tests/ui/missing_doc.stderr b/tests/ui/missing_doc.stderr index ef0f96a5b7130..133c76ac9d438 100644 --- a/tests/ui/missing_doc.stderr +++ b/tests/ui/missing_doc.stderr @@ -88,5 +88,14 @@ error: missing documentation for a function LL | fn also_undocumented2() {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 13 previous errors +error: missing documentation for a function + --> tests/ui/missing_doc.rs:122:1 + | +LL | / fn issue13298() { +LL | | // Rustdoc doesn't generate documentation for items within other items like fns or consts +LL | | const MSG: &str = "Hello, world!"; +LL | | } + | |_^ + +error: aborting due to 14 previous errors diff --git a/tests/ui/needless_as_bytes.fixed b/tests/ui/needless_as_bytes.fixed new file mode 100644 index 0000000000000..042342311fdf1 --- /dev/null +++ b/tests/ui/needless_as_bytes.fixed @@ -0,0 +1,50 @@ +#![warn(clippy::needless_as_bytes)] +#![allow(clippy::const_is_empty)] + +struct S; + +impl S { + fn as_bytes(&self) -> &[u8] { + &[] + } +} + +fn main() { + if "some string".is_empty() { + //~^ needless_as_bytes + println!("len = {}", "some string".len()); + //~^ needless_as_bytes + } + + let s = String::from("yet another string"); + if s.is_empty() { + //~^ needless_as_bytes + println!("len = {}", s.len()); + //~^ needless_as_bytes + } + + // Do not lint + let _ = S.as_bytes().is_empty(); + let _ = S.as_bytes().len(); + let _ = (&String::new() as &dyn AsBytes).as_bytes().len(); + macro_rules! m { + (1) => { + "" + }; + (2) => { + "".as_bytes() + }; + } + m!(1).as_bytes().len(); + m!(2).len(); +} + +pub trait AsBytes { + fn as_bytes(&self) -> &[u8]; +} + +impl AsBytes for String { + fn as_bytes(&self) -> &[u8] { + &[] + } +} diff --git a/tests/ui/needless_as_bytes.rs b/tests/ui/needless_as_bytes.rs new file mode 100644 index 0000000000000..c481e041e0abf --- /dev/null +++ b/tests/ui/needless_as_bytes.rs @@ -0,0 +1,50 @@ +#![warn(clippy::needless_as_bytes)] +#![allow(clippy::const_is_empty)] + +struct S; + +impl S { + fn as_bytes(&self) -> &[u8] { + &[] + } +} + +fn main() { + if "some string".as_bytes().is_empty() { + //~^ needless_as_bytes + println!("len = {}", "some string".as_bytes().len()); + //~^ needless_as_bytes + } + + let s = String::from("yet another string"); + if s.as_bytes().is_empty() { + //~^ needless_as_bytes + println!("len = {}", s.as_bytes().len()); + //~^ needless_as_bytes + } + + // Do not lint + let _ = S.as_bytes().is_empty(); + let _ = S.as_bytes().len(); + let _ = (&String::new() as &dyn AsBytes).as_bytes().len(); + macro_rules! m { + (1) => { + "" + }; + (2) => { + "".as_bytes() + }; + } + m!(1).as_bytes().len(); + m!(2).len(); +} + +pub trait AsBytes { + fn as_bytes(&self) -> &[u8]; +} + +impl AsBytes for String { + fn as_bytes(&self) -> &[u8] { + &[] + } +} diff --git a/tests/ui/needless_as_bytes.stderr b/tests/ui/needless_as_bytes.stderr new file mode 100644 index 0000000000000..3391238a142bf --- /dev/null +++ b/tests/ui/needless_as_bytes.stderr @@ -0,0 +1,29 @@ +error: needless call to `as_bytes()` + --> tests/ui/needless_as_bytes.rs:13:8 + | +LL | if "some string".as_bytes().is_empty() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: `is_empty()` can be called directly on strings: `"some string".is_empty()` + | + = note: `-D clippy::needless-as-bytes` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::needless_as_bytes)]` + +error: needless call to `as_bytes()` + --> tests/ui/needless_as_bytes.rs:15:30 + | +LL | println!("len = {}", "some string".as_bytes().len()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: `len()` can be called directly on strings: `"some string".len()` + +error: needless call to `as_bytes()` + --> tests/ui/needless_as_bytes.rs:20:8 + | +LL | if s.as_bytes().is_empty() { + | ^^^^^^^^^^^^^^^^^^^^^^^ help: `is_empty()` can be called directly on strings: `s.is_empty()` + +error: needless call to `as_bytes()` + --> tests/ui/needless_as_bytes.rs:22:30 + | +LL | println!("len = {}", s.as_bytes().len()); + | ^^^^^^^^^^^^^^^^^^ help: `len()` can be called directly on strings: `s.len()` + +error: aborting due to 4 previous errors + diff --git a/tests/ui/needless_continue.rs b/tests/ui/needless_continue.rs index c26a292c8cb57..b6d8a8f61aeb0 100644 --- a/tests/ui/needless_continue.rs +++ b/tests/ui/needless_continue.rs @@ -151,3 +151,20 @@ mod issue_2329 { } } } + +fn issue_13641() { + 'a: while std::hint::black_box(true) { + #[allow(clippy::never_loop)] + loop { + continue 'a; + } + } + + #[allow(clippy::never_loop)] + while std::hint::black_box(true) { + 'b: loop { + continue 'b; + //~^ ERROR: this `continue` expression is redundant + } + } +} diff --git a/tests/ui/needless_continue.stderr b/tests/ui/needless_continue.stderr index ec7c9ba39a72a..0741ba6924870 100644 --- a/tests/ui/needless_continue.stderr +++ b/tests/ui/needless_continue.stderr @@ -136,5 +136,13 @@ LL | | } println!("bar-5"); } -error: aborting due to 8 previous errors +error: this `continue` expression is redundant + --> tests/ui/needless_continue.rs:166:13 + | +LL | continue 'b; + | ^^^^^^^^^^^^ + | + = help: consider dropping the `continue` expression + +error: aborting due to 9 previous errors diff --git a/tests/ui/no_lints.rs b/tests/ui/no_lints.rs deleted file mode 100644 index a8467bb6ef786..0000000000000 --- a/tests/ui/no_lints.rs +++ /dev/null @@ -1,3 +0,0 @@ -#![deny(clippy::all)] - -fn main() {} \ No newline at end of file diff --git a/tests/ui/no_mangle_with_rust_abi.rs b/tests/ui/no_mangle_with_rust_abi.rs index 8c1ea81d2ac9a..8be149d186379 100644 --- a/tests/ui/no_mangle_with_rust_abi.rs +++ b/tests/ui/no_mangle_with_rust_abi.rs @@ -2,30 +2,30 @@ #![allow(unused)] #![warn(clippy::no_mangle_with_rust_abi)] -#[no_mangle] +#[unsafe(no_mangle)] fn rust_abi_fn_one(arg_one: u32, arg_two: usize) {} -//~^ ERROR: `#[no_mangle]` set on a function with the default (`Rust`) ABI +//~^ ERROR: `#[unsafe(no_mangle)]` set on a function with the default (`Rust`) ABI //~| NOTE: `-D clippy::no-mangle-with-rust-abi` implied by `-D warnings` -#[no_mangle] +#[unsafe(no_mangle)] pub fn rust_abi_fn_two(arg_one: u32, arg_two: usize) {} -//~^ ERROR: `#[no_mangle]` set on a function with the default (`Rust`) ABI +//~^ ERROR: `#[unsafe(no_mangle)]` set on a function with the default (`Rust`) ABI /// # Safety /// This function shouldn't be called unless the horsemen are ready -#[no_mangle] +#[unsafe(no_mangle)] pub unsafe fn rust_abi_fn_three(arg_one: u32, arg_two: usize) {} -//~^ ERROR: `#[no_mangle]` set on a function with the default (`Rust`) ABI +//~^ ERROR: `#[unsafe(no_mangle)]` set on a function with the default (`Rust`) ABI /// # Safety /// This function shouldn't be called unless the horsemen are ready -#[no_mangle] +#[unsafe(no_mangle)] unsafe fn rust_abi_fn_four(arg_one: u32, arg_two: usize) {} -//~^ ERROR: `#[no_mangle]` set on a function with the default (`Rust`) ABI +//~^ ERROR: `#[unsafe(no_mangle)]` set on a function with the default (`Rust`) ABI -#[no_mangle] +#[unsafe(no_mangle)] fn rust_abi_multiline_function_really_long_name_to_overflow_args_to_multiple_lines( - //~^ ERROR: `#[no_mangle]` set on a function with the default (`Rust`) ABI + //~^ ERROR: `#[unsafe(no_mangle)]` set on a function with the default (`Rust`) ABI arg_one: u32, arg_two: usize, ) -> u32 { @@ -33,13 +33,13 @@ fn rust_abi_multiline_function_really_long_name_to_overflow_args_to_multiple_lin } // Must not run on functions that explicitly opt in to using the Rust ABI with `extern "Rust"` -#[no_mangle] +#[unsafe(no_mangle)] #[rustfmt::skip] extern "Rust" fn rust_abi_fn_explicit_opt_in(arg_one: u32, arg_two: usize) {} fn rust_abi_fn_again(arg_one: u32, arg_two: usize) {} -#[no_mangle] +#[unsafe(no_mangle)] extern "C" fn c_abi_fn(arg_one: u32, arg_two: usize) {} extern "C" fn c_abi_fn_again(arg_one: u32, arg_two: usize) {} @@ -48,6 +48,11 @@ extern "C" { fn c_abi_in_block(arg_one: u32, arg_two: usize); } +mod r#fn { + #[unsafe(no_mangle)] + pub(in super::r#fn) fn with_some_fn_around() {} +} + fn main() { // test code goes here } diff --git a/tests/ui/no_mangle_with_rust_abi.stderr b/tests/ui/no_mangle_with_rust_abi.stderr index 27b8b39a21a22..a00ebe5e1ac73 100644 --- a/tests/ui/no_mangle_with_rust_abi.stderr +++ b/tests/ui/no_mangle_with_rust_abi.stderr @@ -1,4 +1,4 @@ -error: `#[no_mangle]` set on a function with the default (`Rust`) ABI +error: `#[unsafe(no_mangle)]` set on a function with the default (`Rust`) ABI --> tests/ui/no_mangle_with_rust_abi.rs:6:1 | LL | fn rust_abi_fn_one(arg_one: u32, arg_two: usize) {} @@ -15,7 +15,7 @@ help: or explicitly set the default LL | extern "Rust" fn rust_abi_fn_one(arg_one: u32, arg_two: usize) {} | +++++++++++++ -error: `#[no_mangle]` set on a function with the default (`Rust`) ABI +error: `#[unsafe(no_mangle)]` set on a function with the default (`Rust`) ABI --> tests/ui/no_mangle_with_rust_abi.rs:11:1 | LL | pub fn rust_abi_fn_two(arg_one: u32, arg_two: usize) {} @@ -30,7 +30,7 @@ help: or explicitly set the default LL | pub extern "Rust" fn rust_abi_fn_two(arg_one: u32, arg_two: usize) {} | +++++++++++++ -error: `#[no_mangle]` set on a function with the default (`Rust`) ABI +error: `#[unsafe(no_mangle)]` set on a function with the default (`Rust`) ABI --> tests/ui/no_mangle_with_rust_abi.rs:17:1 | LL | pub unsafe fn rust_abi_fn_three(arg_one: u32, arg_two: usize) {} @@ -45,7 +45,7 @@ help: or explicitly set the default LL | pub unsafe extern "Rust" fn rust_abi_fn_three(arg_one: u32, arg_two: usize) {} | +++++++++++++ -error: `#[no_mangle]` set on a function with the default (`Rust`) ABI +error: `#[unsafe(no_mangle)]` set on a function with the default (`Rust`) ABI --> tests/ui/no_mangle_with_rust_abi.rs:23:1 | LL | unsafe fn rust_abi_fn_four(arg_one: u32, arg_two: usize) {} @@ -60,7 +60,7 @@ help: or explicitly set the default LL | unsafe extern "Rust" fn rust_abi_fn_four(arg_one: u32, arg_two: usize) {} | +++++++++++++ -error: `#[no_mangle]` set on a function with the default (`Rust`) ABI +error: `#[unsafe(no_mangle)]` set on a function with the default (`Rust`) ABI --> tests/ui/no_mangle_with_rust_abi.rs:27:1 | LL | / fn rust_abi_multiline_function_really_long_name_to_overflow_args_to_multiple_lines( @@ -79,5 +79,20 @@ help: or explicitly set the default LL | extern "Rust" fn rust_abi_multiline_function_really_long_name_to_overflow_args_to_multiple_lines( | +++++++++++++ -error: aborting due to 5 previous errors +error: `#[unsafe(no_mangle)]` set on a function with the default (`Rust`) ABI + --> tests/ui/no_mangle_with_rust_abi.rs:53:5 + | +LL | pub(in super::r#fn) fn with_some_fn_around() {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: set an ABI + | +LL | pub(in super::r#fn) extern "C" fn with_some_fn_around() {} + | ++++++++++ +help: or explicitly set the default + | +LL | pub(in super::r#fn) extern "Rust" fn with_some_fn_around() {} + | +++++++++++++ + +error: aborting due to 6 previous errors diff --git a/tests/ui/no_mangle_with_rust_abi_2021.rs b/tests/ui/no_mangle_with_rust_abi_2021.rs new file mode 100644 index 0000000000000..c7c9733534800 --- /dev/null +++ b/tests/ui/no_mangle_with_rust_abi_2021.rs @@ -0,0 +1,57 @@ +//@edition:2021 +// +// Edition 2024 requires the use of #[unsafe(no_mangle)] + +//@no-rustfix: overlapping suggestions +#![allow(unused)] +#![warn(clippy::no_mangle_with_rust_abi)] + +#[no_mangle] +fn rust_abi_fn_one(arg_one: u32, arg_two: usize) {} +//~^ ERROR: `#[no_mangle]` set on a function with the default (`Rust`) ABI +//~| NOTE: `-D clippy::no-mangle-with-rust-abi` implied by `-D warnings` + +#[no_mangle] +pub fn rust_abi_fn_two(arg_one: u32, arg_two: usize) {} +//~^ ERROR: `#[no_mangle]` set on a function with the default (`Rust`) ABI + +/// # Safety +/// This function shouldn't be called unless the horsemen are ready +#[no_mangle] +pub unsafe fn rust_abi_fn_three(arg_one: u32, arg_two: usize) {} +//~^ ERROR: `#[no_mangle]` set on a function with the default (`Rust`) ABI + +/// # Safety +/// This function shouldn't be called unless the horsemen are ready +#[no_mangle] +unsafe fn rust_abi_fn_four(arg_one: u32, arg_two: usize) {} +//~^ ERROR: `#[no_mangle]` set on a function with the default (`Rust`) ABI + +#[no_mangle] +fn rust_abi_multiline_function_really_long_name_to_overflow_args_to_multiple_lines( + //~^ ERROR: `#[no_mangle]` set on a function with the default (`Rust`) ABI + arg_one: u32, + arg_two: usize, +) -> u32 { + 0 +} + +// Must not run on functions that explicitly opt in to using the Rust ABI with `extern "Rust"` +#[no_mangle] +#[rustfmt::skip] +extern "Rust" fn rust_abi_fn_explicit_opt_in(arg_one: u32, arg_two: usize) {} + +fn rust_abi_fn_again(arg_one: u32, arg_two: usize) {} + +#[no_mangle] +extern "C" fn c_abi_fn(arg_one: u32, arg_two: usize) {} + +extern "C" fn c_abi_fn_again(arg_one: u32, arg_two: usize) {} + +extern "C" { + fn c_abi_in_block(arg_one: u32, arg_two: usize); +} + +fn main() { + // test code goes here +} diff --git a/tests/ui/no_mangle_with_rust_abi_2021.stderr b/tests/ui/no_mangle_with_rust_abi_2021.stderr new file mode 100644 index 0000000000000..15075be72d0bf --- /dev/null +++ b/tests/ui/no_mangle_with_rust_abi_2021.stderr @@ -0,0 +1,83 @@ +error: `#[no_mangle]` set on a function with the default (`Rust`) ABI + --> tests/ui/no_mangle_with_rust_abi_2021.rs:10:1 + | +LL | fn rust_abi_fn_one(arg_one: u32, arg_two: usize) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::no-mangle-with-rust-abi` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::no_mangle_with_rust_abi)]` +help: set an ABI + | +LL | extern "C" fn rust_abi_fn_one(arg_one: u32, arg_two: usize) {} + | ++++++++++ +help: or explicitly set the default + | +LL | extern "Rust" fn rust_abi_fn_one(arg_one: u32, arg_two: usize) {} + | +++++++++++++ + +error: `#[no_mangle]` set on a function with the default (`Rust`) ABI + --> tests/ui/no_mangle_with_rust_abi_2021.rs:15:1 + | +LL | pub fn rust_abi_fn_two(arg_one: u32, arg_two: usize) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: set an ABI + | +LL | pub extern "C" fn rust_abi_fn_two(arg_one: u32, arg_two: usize) {} + | ++++++++++ +help: or explicitly set the default + | +LL | pub extern "Rust" fn rust_abi_fn_two(arg_one: u32, arg_two: usize) {} + | +++++++++++++ + +error: `#[no_mangle]` set on a function with the default (`Rust`) ABI + --> tests/ui/no_mangle_with_rust_abi_2021.rs:21:1 + | +LL | pub unsafe fn rust_abi_fn_three(arg_one: u32, arg_two: usize) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: set an ABI + | +LL | pub unsafe extern "C" fn rust_abi_fn_three(arg_one: u32, arg_two: usize) {} + | ++++++++++ +help: or explicitly set the default + | +LL | pub unsafe extern "Rust" fn rust_abi_fn_three(arg_one: u32, arg_two: usize) {} + | +++++++++++++ + +error: `#[no_mangle]` set on a function with the default (`Rust`) ABI + --> tests/ui/no_mangle_with_rust_abi_2021.rs:27:1 + | +LL | unsafe fn rust_abi_fn_four(arg_one: u32, arg_two: usize) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: set an ABI + | +LL | unsafe extern "C" fn rust_abi_fn_four(arg_one: u32, arg_two: usize) {} + | ++++++++++ +help: or explicitly set the default + | +LL | unsafe extern "Rust" fn rust_abi_fn_four(arg_one: u32, arg_two: usize) {} + | +++++++++++++ + +error: `#[no_mangle]` set on a function with the default (`Rust`) ABI + --> tests/ui/no_mangle_with_rust_abi_2021.rs:31:1 + | +LL | / fn rust_abi_multiline_function_really_long_name_to_overflow_args_to_multiple_lines( +LL | | +LL | | arg_one: u32, +LL | | arg_two: usize, +LL | | ) -> u32 { + | |________^ + | +help: set an ABI + | +LL | extern "C" fn rust_abi_multiline_function_really_long_name_to_overflow_args_to_multiple_lines( + | ++++++++++ +help: or explicitly set the default + | +LL | extern "Rust" fn rust_abi_multiline_function_really_long_name_to_overflow_args_to_multiple_lines( + | +++++++++++++ + +error: aborting due to 5 previous errors + diff --git a/tests/ui/nonminimal_bool.stderr b/tests/ui/nonminimal_bool.stderr index eafffdaf8a638..578f918f01392 100644 --- a/tests/ui/nonminimal_bool.stderr +++ b/tests/ui/nonminimal_bool.stderr @@ -118,25 +118,25 @@ error: this boolean expression can be simplified --> tests/ui/nonminimal_bool.rs:161:8 | LL | if !(12 == a) {} - | ^^^^^^^^^^ help: try: `12 != a` + | ^^^^^^^^^^ help: try: `(12 != a)` error: this boolean expression can be simplified --> tests/ui/nonminimal_bool.rs:162:8 | LL | if !(a == 12) {} - | ^^^^^^^^^^ help: try: `a != 12` + | ^^^^^^^^^^ help: try: `(a != 12)` error: this boolean expression can be simplified --> tests/ui/nonminimal_bool.rs:163:8 | LL | if !(12 != a) {} - | ^^^^^^^^^^ help: try: `12 == a` + | ^^^^^^^^^^ help: try: `(12 == a)` error: this boolean expression can be simplified --> tests/ui/nonminimal_bool.rs:164:8 | LL | if !(a != 12) {} - | ^^^^^^^^^^ help: try: `a == 12` + | ^^^^^^^^^^ help: try: `(a == 12)` error: this boolean expression can be simplified --> tests/ui/nonminimal_bool.rs:168:8 diff --git a/tests/ui/nonminimal_bool_methods.fixed b/tests/ui/nonminimal_bool_methods.fixed index a23310c1ad9fd..65ccaaca89189 100644 --- a/tests/ui/nonminimal_bool_methods.fixed +++ b/tests/ui/nonminimal_bool_methods.fixed @@ -110,9 +110,33 @@ fn dont_warn_for_negated_partial_ord_comparison() { fn issue_12625() { let a = 0; let b = 0; - if (a as u64) < b {} //~ ERROR: this boolean expression can be simplified - if (a as u64) < b {} //~ ERROR: this boolean expression can be simplified - if a as u64 > b {} //~ ERROR: this boolean expression can be simplified + if ((a as u64) < b) {} //~ ERROR: this boolean expression can be simplified + if ((a as u64) < b) {} //~ ERROR: this boolean expression can be simplified + if (a as u64 > b) {} //~ ERROR: this boolean expression can be simplified +} + +fn issue_12761() { + let a = 0; + let b = 0; + let c = 0; + if (a < b) as i32 == c {} //~ ERROR: this boolean expression can be simplified + if (a < b) | (a > c) {} //~ ERROR: this boolean expression can be simplified + let opt: Option = Some(1); + let res: Result = Ok(1); + if res.is_err() as i32 == c {} //~ ERROR: this boolean expression can be simplified + if res.is_err() | opt.is_some() {} //~ ERROR: this boolean expression can be simplified + + fn a(a: bool) -> bool { + (4 <= 3).b() //~ ERROR: this boolean expression can be simplified + } + + trait B { + fn b(&self) -> bool { + true + } + } + + impl B for bool {} } fn issue_13436() { diff --git a/tests/ui/nonminimal_bool_methods.rs b/tests/ui/nonminimal_bool_methods.rs index 6c844373af709..06db3a1d4a5cf 100644 --- a/tests/ui/nonminimal_bool_methods.rs +++ b/tests/ui/nonminimal_bool_methods.rs @@ -115,6 +115,30 @@ fn issue_12625() { if !(a as u64 <= b) {} //~ ERROR: this boolean expression can be simplified } +fn issue_12761() { + let a = 0; + let b = 0; + let c = 0; + if !(a >= b) as i32 == c {} //~ ERROR: this boolean expression can be simplified + if !(a >= b) | !(a <= c) {} //~ ERROR: this boolean expression can be simplified + let opt: Option = Some(1); + let res: Result = Ok(1); + if !res.is_ok() as i32 == c {} //~ ERROR: this boolean expression can be simplified + if !res.is_ok() | !opt.is_none() {} //~ ERROR: this boolean expression can be simplified + + fn a(a: bool) -> bool { + (!(4 > 3)).b() //~ ERROR: this boolean expression can be simplified + } + + trait B { + fn b(&self) -> bool { + true + } + } + + impl B for bool {} +} + fn issue_13436() { fn not_zero(x: i32) -> bool { x != 0 diff --git a/tests/ui/nonminimal_bool_methods.stderr b/tests/ui/nonminimal_bool_methods.stderr index 52803e828aed0..66c50f9ff1e10 100644 --- a/tests/ui/nonminimal_bool_methods.stderr +++ b/tests/ui/nonminimal_bool_methods.stderr @@ -83,127 +83,169 @@ error: this boolean expression can be simplified --> tests/ui/nonminimal_bool_methods.rs:113:8 | LL | if !(a as u64 >= b) {} - | ^^^^^^^^^^^^^^^^ help: try: `(a as u64) < b` + | ^^^^^^^^^^^^^^^^ help: try: `((a as u64) < b)` error: this boolean expression can be simplified --> tests/ui/nonminimal_bool_methods.rs:114:8 | LL | if !((a as u64) >= b) {} - | ^^^^^^^^^^^^^^^^^^ help: try: `(a as u64) < b` + | ^^^^^^^^^^^^^^^^^^ help: try: `((a as u64) < b)` error: this boolean expression can be simplified --> tests/ui/nonminimal_bool_methods.rs:115:8 | LL | if !(a as u64 <= b) {} - | ^^^^^^^^^^^^^^^^ help: try: `a as u64 > b` + | ^^^^^^^^^^^^^^^^ help: try: `(a as u64 > b)` error: this boolean expression can be simplified - --> tests/ui/nonminimal_bool_methods.rs:131:9 + --> tests/ui/nonminimal_bool_methods.rs:122:8 + | +LL | if !(a >= b) as i32 == c {} + | ^^^^^^^^^ help: try: `(a < b)` + +error: this boolean expression can be simplified + --> tests/ui/nonminimal_bool_methods.rs:123:8 + | +LL | if !(a >= b) | !(a <= c) {} + | ^^^^^^^^^ help: try: `(a < b)` + +error: this boolean expression can be simplified + --> tests/ui/nonminimal_bool_methods.rs:123:20 + | +LL | if !(a >= b) | !(a <= c) {} + | ^^^^^^^^^ help: try: `(a > c)` + +error: this boolean expression can be simplified + --> tests/ui/nonminimal_bool_methods.rs:126:8 + | +LL | if !res.is_ok() as i32 == c {} + | ^^^^^^^^^^^^ help: try: `res.is_err()` + +error: this boolean expression can be simplified + --> tests/ui/nonminimal_bool_methods.rs:127:8 + | +LL | if !res.is_ok() | !opt.is_none() {} + | ^^^^^^^^^^^^ help: try: `res.is_err()` + +error: this boolean expression can be simplified + --> tests/ui/nonminimal_bool_methods.rs:127:23 + | +LL | if !res.is_ok() | !opt.is_none() {} + | ^^^^^^^^^^^^^^ help: try: `opt.is_some()` + +error: this boolean expression can be simplified + --> tests/ui/nonminimal_bool_methods.rs:130:9 + | +LL | (!(4 > 3)).b() + | ^^^^^^^^^^ help: try: `(4 <= 3)` + +error: this boolean expression can be simplified + --> tests/ui/nonminimal_bool_methods.rs:155:9 | LL | _ = !opt.is_some_and(|x| x < 1000); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `opt.is_none_or(|x| x >= 1000)` error: this boolean expression can be simplified - --> tests/ui/nonminimal_bool_methods.rs:132:9 + --> tests/ui/nonminimal_bool_methods.rs:156:9 | LL | _ = !opt.is_some_and(|x| x <= 1000); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `opt.is_none_or(|x| x > 1000)` error: this boolean expression can be simplified - --> tests/ui/nonminimal_bool_methods.rs:133:9 + --> tests/ui/nonminimal_bool_methods.rs:157:9 | LL | _ = !opt.is_some_and(|x| x > 1000); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `opt.is_none_or(|x| x <= 1000)` error: this boolean expression can be simplified - --> tests/ui/nonminimal_bool_methods.rs:134:9 + --> tests/ui/nonminimal_bool_methods.rs:158:9 | LL | _ = !opt.is_some_and(|x| x >= 1000); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `opt.is_none_or(|x| x < 1000)` error: this boolean expression can be simplified - --> tests/ui/nonminimal_bool_methods.rs:135:9 + --> tests/ui/nonminimal_bool_methods.rs:159:9 | LL | _ = !opt.is_some_and(|x| x == 1000); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `opt.is_none_or(|x| x != 1000)` error: this boolean expression can be simplified - --> tests/ui/nonminimal_bool_methods.rs:136:9 + --> tests/ui/nonminimal_bool_methods.rs:160:9 | LL | _ = !opt.is_some_and(|x| x != 1000); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `opt.is_none_or(|x| x == 1000)` error: this boolean expression can be simplified - --> tests/ui/nonminimal_bool_methods.rs:145:9 + --> tests/ui/nonminimal_bool_methods.rs:169:9 | LL | _ = !opt.is_none_or(|x| x < 1000); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `opt.is_some_and(|x| x >= 1000)` error: this boolean expression can be simplified - --> tests/ui/nonminimal_bool_methods.rs:146:9 + --> tests/ui/nonminimal_bool_methods.rs:170:9 | LL | _ = !opt.is_none_or(|x| x <= 1000); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `opt.is_some_and(|x| x > 1000)` error: this boolean expression can be simplified - --> tests/ui/nonminimal_bool_methods.rs:147:9 + --> tests/ui/nonminimal_bool_methods.rs:171:9 | LL | _ = !opt.is_none_or(|x| x > 1000); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `opt.is_some_and(|x| x <= 1000)` error: this boolean expression can be simplified - --> tests/ui/nonminimal_bool_methods.rs:148:9 + --> tests/ui/nonminimal_bool_methods.rs:172:9 | LL | _ = !opt.is_none_or(|x| x >= 1000); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `opt.is_some_and(|x| x < 1000)` error: this boolean expression can be simplified - --> tests/ui/nonminimal_bool_methods.rs:149:9 + --> tests/ui/nonminimal_bool_methods.rs:173:9 | LL | _ = !opt.is_none_or(|x| x == 1000); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `opt.is_some_and(|x| x != 1000)` error: this boolean expression can be simplified - --> tests/ui/nonminimal_bool_methods.rs:150:9 + --> tests/ui/nonminimal_bool_methods.rs:174:9 | LL | _ = !opt.is_none_or(|x| x != 1000); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `opt.is_some_and(|x| x == 1000)` error: this boolean expression can be simplified - --> tests/ui/nonminimal_bool_methods.rs:157:9 + --> tests/ui/nonminimal_bool_methods.rs:181:9 | LL | _ = !opt.is_some_and(|x| !x); | ^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `opt.is_none_or(|x| x)` error: this boolean expression can be simplified - --> tests/ui/nonminimal_bool_methods.rs:161:9 + --> tests/ui/nonminimal_bool_methods.rs:185:9 | LL | _ = !opt.is_none_or(|x| !x); | ^^^^^^^^^^^^^^^^^^^^^^^ help: try: `opt.is_some_and(|x| x)` error: this boolean expression can be simplified - --> tests/ui/nonminimal_bool_methods.rs:168:9 + --> tests/ui/nonminimal_bool_methods.rs:192:9 | LL | _ = !opt.is_some_and(|x| x.is_ok()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `opt.is_none_or(|x| x.is_err())` error: this boolean expression can be simplified - --> tests/ui/nonminimal_bool_methods.rs:169:9 + --> tests/ui/nonminimal_bool_methods.rs:193:9 | LL | _ = !opt.is_some_and(|x| x.is_err()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `opt.is_none_or(|x| x.is_ok())` error: this boolean expression can be simplified - --> tests/ui/nonminimal_bool_methods.rs:170:9 + --> tests/ui/nonminimal_bool_methods.rs:194:9 | LL | _ = !opt.is_none_or(|x| x.is_ok()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `opt.is_some_and(|x| x.is_err())` error: this boolean expression can be simplified - --> tests/ui/nonminimal_bool_methods.rs:171:9 + --> tests/ui/nonminimal_bool_methods.rs:195:9 | LL | _ = !opt.is_none_or(|x| x.is_err()); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `opt.is_some_and(|x| x.is_ok())` -error: aborting due to 34 previous errors +error: aborting due to 41 previous errors diff --git a/tests/ui/rename.rs b/tests/ui/rename.rs index e03df1658ee36..2ac59718786b6 100644 --- a/tests/ui/rename.rs +++ b/tests/ui/rename.rs @@ -54,7 +54,7 @@ #![allow(enum_intrinsics_non_enums)] #![allow(non_fmt_panics)] #![allow(named_arguments_used_positionally)] -#![allow(temporary_cstring_as_ptr)] +#![allow(dangling_pointers_from_temporaries)] #![allow(undropped_manually_drops)] #![allow(unknown_lints)] #![allow(unused_labels)] diff --git a/tests/ui/rename.stderr b/tests/ui/rename.stderr index b906079d7df3e..a5ae727b7ee82 100644 --- a/tests/ui/rename.stderr +++ b/tests/ui/rename.stderr @@ -1,17 +1,11 @@ -error: lint `temporary_cstring_as_ptr` has been renamed to `dangling_pointers_from_temporaries` - --> tests/ui/rename.rs:57:10 - | -LL | #![allow(temporary_cstring_as_ptr)] - | ^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `dangling_pointers_from_temporaries` - | - = note: `-D renamed-and-removed-lints` implied by `-D warnings` - = help: to override `-D warnings` add `#[allow(renamed_and_removed_lints)]` - error: lint `clippy::almost_complete_letter_range` has been renamed to `clippy::almost_complete_range` --> tests/ui/rename.rs:63:9 | LL | #![warn(clippy::almost_complete_letter_range)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::almost_complete_range` + | + = note: `-D renamed-and-removed-lints` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(renamed_and_removed_lints)]` error: lint `clippy::blacklisted_name` has been renamed to `clippy::disallowed_names` --> tests/ui/rename.rs:64:9 @@ -403,5 +397,5 @@ error: lint `clippy::reverse_range_loop` has been renamed to `clippy::reversed_e LL | #![warn(clippy::reverse_range_loop)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `clippy::reversed_empty_ranges` -error: aborting due to 67 previous errors +error: aborting due to 66 previous errors diff --git a/tests/ui/repeat_vec_with_capacity.fixed b/tests/ui/repeat_vec_with_capacity.fixed index 2afe2f4332587..f72b61b5f6cd8 100644 --- a/tests/ui/repeat_vec_with_capacity.fixed +++ b/tests/ui/repeat_vec_with_capacity.fixed @@ -1,3 +1,4 @@ +#![allow(clippy::map_with_unused_argument_over_ranges)] #![warn(clippy::repeat_vec_with_capacity)] fn main() { diff --git a/tests/ui/repeat_vec_with_capacity.rs b/tests/ui/repeat_vec_with_capacity.rs index 659f2a3953dde..c0cc81f784365 100644 --- a/tests/ui/repeat_vec_with_capacity.rs +++ b/tests/ui/repeat_vec_with_capacity.rs @@ -1,3 +1,4 @@ +#![allow(clippy::map_with_unused_argument_over_ranges)] #![warn(clippy::repeat_vec_with_capacity)] fn main() { diff --git a/tests/ui/repeat_vec_with_capacity.stderr b/tests/ui/repeat_vec_with_capacity.stderr index cec9c6ea84a27..43027c9cb8926 100644 --- a/tests/ui/repeat_vec_with_capacity.stderr +++ b/tests/ui/repeat_vec_with_capacity.stderr @@ -1,5 +1,5 @@ error: repeating `Vec::with_capacity` using `vec![x; n]`, which does not retain capacity - --> tests/ui/repeat_vec_with_capacity.rs:5:9 + --> tests/ui/repeat_vec_with_capacity.rs:6:9 | LL | vec![Vec::<()>::with_capacity(42); 123]; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -13,7 +13,7 @@ LL | (0..123).map(|_| Vec::<()>::with_capacity(42)).collect::>(); | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ error: repeating `Vec::with_capacity` using `vec![x; n]`, which does not retain capacity - --> tests/ui/repeat_vec_with_capacity.rs:11:9 + --> tests/ui/repeat_vec_with_capacity.rs:12:9 | LL | vec![Vec::<()>::with_capacity(42); n]; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -25,7 +25,7 @@ LL | (0..n).map(|_| Vec::<()>::with_capacity(42)).collect::>(); | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ error: repeating `Vec::with_capacity` using `iter::repeat`, which does not retain capacity - --> tests/ui/repeat_vec_with_capacity.rs:26:9 + --> tests/ui/repeat_vec_with_capacity.rs:27:9 | LL | std::iter::repeat(Vec::<()>::with_capacity(42)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/semicolon_outside_block.fixed b/tests/ui/semicolon_outside_block.fixed index 148e112e0bcb5..ac7e86631caea 100644 --- a/tests/ui/semicolon_outside_block.fixed +++ b/tests/ui/semicolon_outside_block.fixed @@ -80,5 +80,13 @@ fn main() { { unit_fn_block(); }; + unsafe { + std::arch::asm!("") + }; + + { + line!() + }; + unit_fn_block() } diff --git a/tests/ui/semicolon_outside_block.rs b/tests/ui/semicolon_outside_block.rs index c767201469ab6..68f25339e3228 100644 --- a/tests/ui/semicolon_outside_block.rs +++ b/tests/ui/semicolon_outside_block.rs @@ -80,5 +80,13 @@ fn main() { { unit_fn_block(); }; + unsafe { + std::arch::asm!(""); + } + + { + line!(); + } + unit_fn_block() } diff --git a/tests/ui/semicolon_outside_block.stderr b/tests/ui/semicolon_outside_block.stderr index 68b44c8f980f2..ff8c00048f630 100644 --- a/tests/ui/semicolon_outside_block.stderr +++ b/tests/ui/semicolon_outside_block.stderr @@ -51,5 +51,33 @@ LL - { m!(()); } LL + { m!(()) }; | -error: aborting due to 4 previous errors +error: consider moving the `;` outside the block for consistent formatting + --> tests/ui/semicolon_outside_block.rs:83:5 + | +LL | / unsafe { +LL | | std::arch::asm!(""); +LL | | } + | |_____^ + | +help: put the `;` here + | +LL ~ std::arch::asm!("") +LL ~ }; + | + +error: consider moving the `;` outside the block for consistent formatting + --> tests/ui/semicolon_outside_block.rs:87:5 + | +LL | / { +LL | | line!(); +LL | | } + | |_____^ + | +help: put the `;` here + | +LL ~ line!() +LL ~ }; + | + +error: aborting due to 6 previous errors diff --git a/tests/ui/suspicious_map.rs b/tests/ui/suspicious_map.rs index d4247fcd9265e..d4a52cb110fb1 100644 --- a/tests/ui/suspicious_map.rs +++ b/tests/ui/suspicious_map.rs @@ -1,3 +1,4 @@ +#![allow(clippy::map_with_unused_argument_over_ranges)] #![warn(clippy::suspicious_map)] fn main() { diff --git a/tests/ui/suspicious_map.stderr b/tests/ui/suspicious_map.stderr index 2a5e2bf2a3467..769adebaede7c 100644 --- a/tests/ui/suspicious_map.stderr +++ b/tests/ui/suspicious_map.stderr @@ -1,5 +1,5 @@ error: this call to `map()` won't have an effect on the call to `count()` - --> tests/ui/suspicious_map.rs:4:13 + --> tests/ui/suspicious_map.rs:5:13 | LL | let _ = (0..3).map(|x| x + 2).count(); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -9,7 +9,7 @@ LL | let _ = (0..3).map(|x| x + 2).count(); = help: to override `-D warnings` add `#[allow(clippy::suspicious_map)]` error: this call to `map()` won't have an effect on the call to `count()` - --> tests/ui/suspicious_map.rs:8:13 + --> tests/ui/suspicious_map.rs:9:13 | LL | let _ = (0..3).map(f).count(); | ^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/unnecessary_filter_map.rs b/tests/ui/unnecessary_filter_map.rs index 1e0d7d12965c2..8cf102ab0a557 100644 --- a/tests/ui/unnecessary_filter_map.rs +++ b/tests/ui/unnecessary_filter_map.rs @@ -1,26 +1,31 @@ +//@no-rustfix #![allow(dead_code)] fn main() { let _ = (0..4).filter_map(|x| if x > 1 { Some(x) } else { None }); - //~^ ERROR: this `.filter_map` can be written more simply using `.filter` + //~^ ERROR: this `.filter_map` can be written more simply //~| NOTE: `-D clippy::unnecessary-filter-map` implied by `-D warnings` let _ = (0..4).filter_map(|x| { - //~^ ERROR: this `.filter_map` can be written more simply using `.filter` + //~^ ERROR: this `.filter_map` can be written more simply if x > 1 { return Some(x); }; None }); let _ = (0..4).filter_map(|x| match x { - //~^ ERROR: this `.filter_map` can be written more simply using `.filter` + //~^ ERROR: this `.filter_map` can be written more simply 0 | 1 => None, _ => Some(x), }); let _ = (0..4).filter_map(|x| Some(x + 1)); - //~^ ERROR: this `.filter_map` can be written more simply using `.map` + //~^ ERROR: this `.filter_map` can be written more simply let _ = (0..4).filter_map(i32::checked_abs); + + let _ = (0..4).filter_map(Some); + + let _ = vec![Some(10), None].into_iter().filter_map(|x| Some(x)); } fn filter_map_none_changes_item_type() -> impl Iterator { diff --git a/tests/ui/unnecessary_filter_map.stderr b/tests/ui/unnecessary_filter_map.stderr index f32d444ba9e09..b21589c5f8445 100644 --- a/tests/ui/unnecessary_filter_map.stderr +++ b/tests/ui/unnecessary_filter_map.stderr @@ -1,14 +1,14 @@ -error: this `.filter_map` can be written more simply using `.filter` - --> tests/ui/unnecessary_filter_map.rs:4:13 +error: this `.filter_map` can be written more simply + --> tests/ui/unnecessary_filter_map.rs:5:13 | LL | let _ = (0..4).filter_map(|x| if x > 1 { Some(x) } else { None }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try instead: `filter` | = note: `-D clippy::unnecessary-filter-map` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::unnecessary_filter_map)]` -error: this `.filter_map` can be written more simply using `.filter` - --> tests/ui/unnecessary_filter_map.rs:7:13 +error: this `.filter_map` can be written more simply + --> tests/ui/unnecessary_filter_map.rs:8:13 | LL | let _ = (0..4).filter_map(|x| { | _____________^ @@ -18,10 +18,10 @@ LL | | return Some(x); LL | | }; LL | | None LL | | }); - | |______^ + | |______^ help: try instead: `filter` -error: this `.filter_map` can be written more simply using `.filter` - --> tests/ui/unnecessary_filter_map.rs:14:13 +error: this `.filter_map` can be written more simply + --> tests/ui/unnecessary_filter_map.rs:15:13 | LL | let _ = (0..4).filter_map(|x| match x { | _____________^ @@ -29,19 +29,40 @@ LL | | LL | | 0 | 1 => None, LL | | _ => Some(x), LL | | }); - | |______^ + | |______^ help: try instead: `filter` -error: this `.filter_map` can be written more simply using `.map` - --> tests/ui/unnecessary_filter_map.rs:20:13 +error: this `.filter_map` can be written more simply + --> tests/ui/unnecessary_filter_map.rs:21:13 | LL | let _ = (0..4).filter_map(|x| Some(x + 1)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try instead: `map` -error: this `.filter_map` can be written more simply using `.filter` - --> tests/ui/unnecessary_filter_map.rs:160:14 +error: redundant closure + --> tests/ui/unnecessary_filter_map.rs:28:57 + | +LL | let _ = vec![Some(10), None].into_iter().filter_map(|x| Some(x)); + | ^^^^^^^^^^^ help: replace the closure with the function itself: `Some` + | + = note: `-D clippy::redundant-closure` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::redundant_closure)]` + +error: filter_map is unnecessary + --> tests/ui/unnecessary_filter_map.rs:28:61 + | +LL | let _ = vec![Some(10), None].into_iter().filter_map(|x| Some(x)); + | ^^^^ help: try removing the filter_map + +error: this `.filter_map` can be written more simply + --> tests/ui/unnecessary_filter_map.rs:28:13 + | +LL | let _ = vec![Some(10), None].into_iter().filter_map(|x| Some(x)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try instead: `map` + +error: this `.filter_map` can be written more simply + --> tests/ui/unnecessary_filter_map.rs:165:14 | LL | let _x = std::iter::once(1).filter_map(|n| (n > 1).then_some(n)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try instead: `filter` -error: aborting due to 5 previous errors +error: aborting due to 8 previous errors diff --git a/tests/ui/unnecessary_find_map.rs b/tests/ui/unnecessary_find_map.rs index 9972b68092abf..c357d85324819 100644 --- a/tests/ui/unnecessary_find_map.rs +++ b/tests/ui/unnecessary_find_map.rs @@ -1,24 +1,25 @@ +//@no-rustfix #![allow(dead_code)] fn main() { let _ = (0..4).find_map(|x| if x > 1 { Some(x) } else { None }); - //~^ ERROR: this `.find_map` can be written more simply using `.find` + //~^ ERROR: this `.find_map` can be written more simply //~| NOTE: `-D clippy::unnecessary-find-map` implied by `-D warnings` let _ = (0..4).find_map(|x| { - //~^ ERROR: this `.find_map` can be written more simply using `.find` + //~^ ERROR: this `.find_map` can be written more simply if x > 1 { return Some(x); }; None }); let _ = (0..4).find_map(|x| match x { - //~^ ERROR: this `.find_map` can be written more simply using `.find` + //~^ ERROR: this `.find_map` can be written more simply 0 | 1 => None, _ => Some(x), }); let _ = (0..4).find_map(|x| Some(x + 1)); - //~^ ERROR: this `.find_map` can be written more simply using `.map(..).next()` + //~^ ERROR: this `.find_map` can be written more simply let _ = (0..4).find_map(i32::checked_abs); } diff --git a/tests/ui/unnecessary_find_map.stderr b/tests/ui/unnecessary_find_map.stderr index bb939a9921467..98a6c3d164a63 100644 --- a/tests/ui/unnecessary_find_map.stderr +++ b/tests/ui/unnecessary_find_map.stderr @@ -1,14 +1,14 @@ -error: this `.find_map` can be written more simply using `.find` - --> tests/ui/unnecessary_find_map.rs:4:13 +error: this `.find_map` can be written more simply + --> tests/ui/unnecessary_find_map.rs:5:13 | LL | let _ = (0..4).find_map(|x| if x > 1 { Some(x) } else { None }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try instead: `find` | = note: `-D clippy::unnecessary-find-map` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::unnecessary_find_map)]` -error: this `.find_map` can be written more simply using `.find` - --> tests/ui/unnecessary_find_map.rs:7:13 +error: this `.find_map` can be written more simply + --> tests/ui/unnecessary_find_map.rs:8:13 | LL | let _ = (0..4).find_map(|x| { | _____________^ @@ -18,10 +18,10 @@ LL | | return Some(x); LL | | }; LL | | None LL | | }); - | |______^ + | |______^ help: try instead: `find` -error: this `.find_map` can be written more simply using `.find` - --> tests/ui/unnecessary_find_map.rs:14:13 +error: this `.find_map` can be written more simply + --> tests/ui/unnecessary_find_map.rs:15:13 | LL | let _ = (0..4).find_map(|x| match x { | _____________^ @@ -29,19 +29,19 @@ LL | | LL | | 0 | 1 => None, LL | | _ => Some(x), LL | | }); - | |______^ + | |______^ help: try instead: `find` -error: this `.find_map` can be written more simply using `.map(..).next()` - --> tests/ui/unnecessary_find_map.rs:20:13 +error: this `.find_map` can be written more simply + --> tests/ui/unnecessary_find_map.rs:21:13 | LL | let _ = (0..4).find_map(|x| Some(x + 1)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try instead: `map(..).next()` -error: this `.find_map` can be written more simply using `.find` - --> tests/ui/unnecessary_find_map.rs:32:14 +error: this `.find_map` can be written more simply + --> tests/ui/unnecessary_find_map.rs:33:14 | LL | let _x = std::iter::once(1).find_map(|n| (n > 1).then_some(n)); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try instead: `find` error: aborting due to 5 previous errors diff --git a/tests/ui/unused_io_amount.rs b/tests/ui/unused_io_amount.rs index f5b200d5ffed9..175c4ca76895c 100644 --- a/tests/ui/unused_io_amount.rs +++ b/tests/ui/unused_io_amount.rs @@ -277,4 +277,18 @@ fn allow_works(mut f: F) { f.read(&mut data).unwrap(); } +struct Reader {} + +impl Read for Reader { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + todo!() + } + + fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> { + // We shouldn't recommend using Read::read_exact inside Read::read_exact! + self.read(buf).unwrap(); + Ok(()) + } +} + fn main() {} diff --git a/triagebot.toml b/triagebot.toml index cd9641eedd8f8..eadfd7107c77b 100644 --- a/triagebot.toml +++ b/triagebot.toml @@ -21,13 +21,11 @@ new_pr = true contributing_url = "https://github.com/rust-lang/rust-clippy/blob/master/CONTRIBUTING.md" users_on_vacation = [ "matthiaskrgr", - "giraffate", ] [assign.owners] "/.github" = ["@flip1995"] "/book" = ["@flip1995"] -"/util/gh-pages" = ["@xFrednet"] "*" = [ "@Manishearth", "@llogiq", diff --git a/util/gh-pages/index_template.html b/util/gh-pages/index_template.html index 2412f0fd18135..deb0ef0b49966 100644 --- a/util/gh-pages/index_template.html +++ b/util/gh-pages/index_template.html @@ -52,12 +52,12 @@

Clippy Lints

{# #} {# #}
{# #} -
{# #} +