From 430575b61a95fc559f5021e246c04c3468acd1d6 Mon Sep 17 00:00:00 2001 From: Elliot Bobrow Date: Wed, 6 Apr 2022 09:28:49 -0700 Subject: add `manual_find` lint for function return case --- CHANGELOG.md | 1 + clippy_lints/src/lib.register_all.rs | 1 + clippy_lints/src/lib.register_complexity.rs | 1 + clippy_lints/src/lib.register_lints.rs | 1 + clippy_lints/src/loops/manual_find.rs | 158 ++++++++++++++++++ clippy_lints/src/loops/mod.rs | 34 ++++ clippy_lints/src/non_expressive_names.rs | 10 +- tests/ui/manual_find.rs | 22 +++ tests/ui/manual_find.stderr | 29 ++++ tests/ui/manual_find_fixable.fixed | 182 +++++++++++++++++++++ tests/ui/manual_find_fixable.rs | 242 ++++++++++++++++++++++++++++ tests/ui/manual_find_fixable.stderr | 142 ++++++++++++++++ tests/ui/while_let_on_iterator.fixed | 3 +- tests/ui/while_let_on_iterator.rs | 3 +- tests/ui/while_let_on_iterator.stderr | 46 +++--- 15 files changed, 844 insertions(+), 31 deletions(-) create mode 100644 clippy_lints/src/loops/manual_find.rs create mode 100644 tests/ui/manual_find.rs create mode 100644 tests/ui/manual_find.stderr create mode 100644 tests/ui/manual_find_fixable.fixed create mode 100644 tests/ui/manual_find_fixable.rs create mode 100644 tests/ui/manual_find_fixable.stderr diff --git a/CHANGELOG.md b/CHANGELOG.md index fd288285532..0011f50da20 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3384,6 +3384,7 @@ Released 2018-09-13 [`manual_async_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_async_fn [`manual_bits`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_bits [`manual_filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_filter_map +[`manual_find`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_find [`manual_find_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_find_map [`manual_flatten`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_flatten [`manual_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_map diff --git a/clippy_lints/src/lib.register_all.rs b/clippy_lints/src/lib.register_all.rs index 132a4662676..844008254a8 100644 --- a/clippy_lints/src/lib.register_all.rs +++ b/clippy_lints/src/lib.register_all.rs @@ -109,6 +109,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(loops::FOR_KV_MAP), LintId::of(loops::FOR_LOOPS_OVER_FALLIBLES), LintId::of(loops::ITER_NEXT_LOOP), + LintId::of(loops::MANUAL_FIND), LintId::of(loops::MANUAL_FLATTEN), LintId::of(loops::MANUAL_MEMCPY), LintId::of(loops::MISSING_SPIN_LOOP), diff --git a/clippy_lints/src/lib.register_complexity.rs b/clippy_lints/src/lib.register_complexity.rs index a2ce69065f9..64f5b2df004 100644 --- a/clippy_lints/src/lib.register_complexity.rs +++ b/clippy_lints/src/lib.register_complexity.rs @@ -21,6 +21,7 @@ store.register_group(true, "clippy::complexity", Some("clippy_complexity"), vec! LintId::of(lifetimes::EXTRA_UNUSED_LIFETIMES), LintId::of(lifetimes::NEEDLESS_LIFETIMES), LintId::of(loops::EXPLICIT_COUNTER_LOOP), + LintId::of(loops::MANUAL_FIND), LintId::of(loops::MANUAL_FLATTEN), LintId::of(loops::SINGLE_ELEMENT_LOOP), LintId::of(loops::WHILE_LET_LOOP), diff --git a/clippy_lints/src/lib.register_lints.rs b/clippy_lints/src/lib.register_lints.rs index 21f1ef562b5..d5ddb95d25f 100644 --- a/clippy_lints/src/lib.register_lints.rs +++ b/clippy_lints/src/lib.register_lints.rs @@ -222,6 +222,7 @@ store.register_lints(&[ loops::FOR_KV_MAP, loops::FOR_LOOPS_OVER_FALLIBLES, loops::ITER_NEXT_LOOP, + loops::MANUAL_FIND, loops::MANUAL_FLATTEN, loops::MANUAL_MEMCPY, loops::MISSING_SPIN_LOOP, diff --git a/clippy_lints/src/loops/manual_find.rs b/clippy_lints/src/loops/manual_find.rs new file mode 100644 index 00000000000..33736d6d4e6 --- /dev/null +++ b/clippy_lints/src/loops/manual_find.rs @@ -0,0 +1,158 @@ +use super::utils::make_iterator_snippet; +use super::MANUAL_FIND; +use clippy_utils::{ + diagnostics::span_lint_and_then, higher, is_lang_ctor, path_res, peel_blocks_with_stmt, + source::snippet_with_applicability, ty::implements_trait, +}; +use if_chain::if_chain; +use rustc_errors::Applicability; +use rustc_hir::{ + def::Res, lang_items::LangItem, BindingAnnotation, Block, Expr, ExprKind, HirId, Node, Pat, PatKind, Stmt, StmtKind, +}; +use rustc_lint::LateContext; +use rustc_span::source_map::Span; + +pub(super) fn check<'tcx>( + cx: &LateContext<'tcx>, + pat: &'tcx Pat<'_>, + arg: &'tcx Expr<'_>, + body: &'tcx Expr<'_>, + span: Span, + expr: &'tcx Expr<'_>, +) { + let inner_expr = peel_blocks_with_stmt(body); + // Check for the specific case that the result is returned and optimize suggestion for that (more + // cases can be added later) + if_chain! { + if let Some(higher::If { cond, then, r#else: None, }) = higher::If::hir(inner_expr); + if let Some(binding_id) = get_binding(pat); + if let ExprKind::Block(block, _) = then.kind; + if let [stmt] = block.stmts; + if let StmtKind::Semi(semi) = stmt.kind; + if let ExprKind::Ret(Some(ret_value)) = semi.kind; + if let ExprKind::Call(Expr { kind: ExprKind::Path(ctor), .. }, [inner_ret]) = ret_value.kind; + if is_lang_ctor(cx, ctor, LangItem::OptionSome); + if path_res(cx, inner_ret) == Res::Local(binding_id); + if let Some((last_stmt, last_ret)) = last_stmt_and_ret(cx, expr); + then { + let mut applicability = Applicability::MachineApplicable; + let mut snippet = make_iterator_snippet(cx, arg, &mut applicability); + // Checks if `pat` is a single reference to a binding (`&x`) + let is_ref_to_binding = + matches!(pat.kind, PatKind::Ref(inner, _) if matches!(inner.kind, PatKind::Binding(..))); + // If `pat` is not a binding or a reference to a binding (`x` or `&x`) + // we need to map it to the binding returned by the function (i.e. `.map(|(x, _)| x)`) + if !(matches!(pat.kind, PatKind::Binding(..)) || is_ref_to_binding) { + snippet.push_str( + &format!( + ".map(|{}| {})", + snippet_with_applicability(cx, pat.span, "..", &mut applicability), + snippet_with_applicability(cx, inner_ret.span, "..", &mut applicability), + )[..], + ); + } + let ty = cx.typeck_results().expr_ty(inner_ret); + if cx.tcx.lang_items().copy_trait().map_or(false, |id| implements_trait(cx, ty, id, &[])) { + snippet.push_str( + &format!( + ".find(|{}{}| {})", + "&".repeat(1 + usize::from(is_ref_to_binding)), + snippet_with_applicability(cx, inner_ret.span, "..", &mut applicability), + snippet_with_applicability(cx, cond.span, "..", &mut applicability), + )[..], + ); + if is_ref_to_binding { + snippet.push_str(".copied()"); + } + } else { + applicability = Applicability::MaybeIncorrect; + snippet.push_str( + &format!( + ".find(|{}| {})", + snippet_with_applicability(cx, inner_ret.span, "..", &mut applicability), + snippet_with_applicability(cx, cond.span, "..", &mut applicability), + )[..], + ); + } + // Extends to `last_stmt` to include semicolon in case of `return None;` + let lint_span = span.to(last_stmt.span).to(last_ret.span); + span_lint_and_then( + cx, + MANUAL_FIND, + lint_span, + "manual implementation of `Iterator::find`", + |diag| { + if applicability == Applicability::MaybeIncorrect { + diag.note("you may need to dereference some variables"); + } + diag.span_suggestion( + lint_span, + "replace with an iterator", + snippet, + applicability, + ); + }, + ); + } + } +} + +fn get_binding(pat: &Pat<'_>) -> Option { + let mut hir_id = None; + let mut count = 0; + pat.each_binding(|annotation, id, _, _| { + count += 1; + if count > 1 { + hir_id = None; + return; + } + if let BindingAnnotation::Unannotated = annotation { + hir_id = Some(id); + } + }); + hir_id +} + +// Returns the last statement and last return if function fits format for lint +fn last_stmt_and_ret<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'_>, +) -> Option<(&'tcx Stmt<'tcx>, &'tcx Expr<'tcx>)> { + // Returns last non-return statement and the last return + fn extract<'tcx>(block: &Block<'tcx>) -> Option<(&'tcx Stmt<'tcx>, &'tcx Expr<'tcx>)> { + if let [.., last_stmt] = block.stmts { + if let Some(ret) = block.expr { + return Some((last_stmt, ret)); + } + if_chain! { + if let [.., snd_last, _] = block.stmts; + if let StmtKind::Semi(last_expr) = last_stmt.kind; + if let ExprKind::Ret(Some(ret)) = last_expr.kind; + then { + return Some((snd_last, ret)); + } + } + } + None + } + let mut parent_iter = cx.tcx.hir().parent_iter(expr.hir_id); + if_chain! { + // This should be the loop + if let Some((node_hir, Node::Stmt(..))) = parent_iter.next(); + // This should be the funciton body + if let Some((_, Node::Block(block))) = parent_iter.next(); + if let Some((last_stmt, last_ret)) = extract(block); + if last_stmt.hir_id == node_hir; + if let ExprKind::Path(path) = &last_ret.kind; + if is_lang_ctor(cx, path, LangItem::OptionNone); + if let Some((_, Node::Expr(_block))) = parent_iter.next(); + // This includes the function header + if let Some((_, func)) = parent_iter.next(); + if func.fn_kind().is_some(); + then { + Some((block.stmts.last().unwrap(), last_ret)) + } else { + None + } + } +} diff --git a/clippy_lints/src/loops/mod.rs b/clippy_lints/src/loops/mod.rs index f029067d367..2f5d176b2de 100644 --- a/clippy_lints/src/loops/mod.rs +++ b/clippy_lints/src/loops/mod.rs @@ -5,6 +5,7 @@ mod explicit_iter_loop; mod for_kv_map; mod for_loops_over_fallibles; mod iter_next_loop; +mod manual_find; mod manual_flatten; mod manual_memcpy; mod missing_spin_loop; @@ -597,6 +598,37 @@ declare_clippy_lint! { "An empty busy waiting loop" } +declare_clippy_lint! { + /// ### What it does + /// Check for manual implementations of Iterator::find + /// + /// ### Why is this bad? + /// It doesn't affect performance, but using `find` is shorter and easier to read. + /// + /// ### Example + /// + /// ```rust + /// fn example(arr: Vec) -> Option { + /// for el in arr { + /// if el == 1 { + /// return Some(el); + /// } + /// } + /// None + /// } + /// ``` + /// Use instead: + /// ```rust + /// fn example(arr: Vec) -> Option { + /// arr.into_iter().find(|&el| el == 1) + /// } + /// ``` + #[clippy::version = "1.61.0"] + pub MANUAL_FIND, + complexity, + "manual implementation of `Iterator::find`" +} + declare_lint_pass!(Loops => [ MANUAL_MEMCPY, MANUAL_FLATTEN, @@ -617,6 +649,7 @@ declare_lint_pass!(Loops => [ SAME_ITEM_PUSH, SINGLE_ELEMENT_LOOP, MISSING_SPIN_LOOP, + MANUAL_FIND, ]); impl<'tcx> LateLintPass<'tcx> for Loops { @@ -692,6 +725,7 @@ fn check_for_loop<'tcx>( single_element_loop::check(cx, pat, arg, body, expr); same_item_push::check(cx, pat, arg, body, expr); manual_flatten::check(cx, pat, arg, body, span); + manual_find::check(cx, pat, arg, body, span, expr); } fn check_for_loop_arg(cx: &LateContext<'_>, pat: &Pat<'_>, arg: &Expr<'_>) { diff --git a/clippy_lints/src/non_expressive_names.rs b/clippy_lints/src/non_expressive_names.rs index 0d0c88b02c7..4a3edbb9d37 100644 --- a/clippy_lints/src/non_expressive_names.rs +++ b/clippy_lints/src/non_expressive_names.rs @@ -159,12 +159,10 @@ impl<'a, 'tcx, 'b> Visitor<'tcx> for SimilarNamesNameVisitor<'a, 'tcx, 'b> { #[must_use] fn get_exemptions(interned_name: &str) -> Option<&'static [&'static str]> { - for &list in ALLOWED_TO_BE_SIMILAR { - if allowed_to_be_similar(interned_name, list) { - return Some(list); - } - } - None + ALLOWED_TO_BE_SIMILAR + .iter() + .find(|&&list| allowed_to_be_similar(interned_name, list)) + .copied() } #[must_use] diff --git a/tests/ui/manual_find.rs b/tests/ui/manual_find.rs new file mode 100644 index 00000000000..257fe045f78 --- /dev/null +++ b/tests/ui/manual_find.rs @@ -0,0 +1,22 @@ +#![allow(unused)] +#![warn(clippy::manual_find)] + +fn vec_string(strings: Vec) -> Option { + for s in strings { + if s == String::new() { + return Some(s); + } + } + None +} + +fn tuple(arr: Vec<(String, i32)>) -> Option { + for (s, _) in arr { + if s == String::new() { + return Some(s); + } + } + None +} + +fn main() {} diff --git a/tests/ui/manual_find.stderr b/tests/ui/manual_find.stderr new file mode 100644 index 00000000000..da0fd4aaef7 --- /dev/null +++ b/tests/ui/manual_find.stderr @@ -0,0 +1,29 @@ +error: manual implementation of `Iterator::find` + --> $DIR/manual_find.rs:5:5 + | +LL | / for s in strings { +LL | | if s == String::new() { +LL | | return Some(s); +LL | | } +LL | | } +LL | | None + | |________^ help: replace with an iterator: `strings.into_iter().find(|s| s == String::new())` + | + = note: `-D clippy::manual-find` implied by `-D warnings` + = note: you may need to dereference some variables + +error: manual implementation of `Iterator::find` + --> $DIR/manual_find.rs:14:5 + | +LL | / for (s, _) in arr { +LL | | if s == String::new() { +LL | | return Some(s); +LL | | } +LL | | } +LL | | None + | |________^ help: replace with an iterator: `arr.into_iter().map(|(s, _)| s).find(|s| s == String::new())` + | + = note: you may need to dereference some variables + +error: aborting due to 2 previous errors + diff --git a/tests/ui/manual_find_fixable.fixed b/tests/ui/manual_find_fixable.fixed new file mode 100644 index 00000000000..36d1644c22b --- /dev/null +++ b/tests/ui/manual_find_fixable.fixed @@ -0,0 +1,182 @@ +// run-rustfix + +#![allow(unused, clippy::needless_return)] +#![warn(clippy::manual_find)] + +use std::collections::HashMap; + +const ARRAY: &[u32; 5] = &[2, 7, 1, 9, 3]; + +fn lookup(n: u32) -> Option { + ARRAY.iter().find(|&&v| v == n).copied() +} + +fn with_pat(arr: Vec<(u32, u32)>) -> Option { + arr.into_iter().map(|(a, _)| a).find(|&a| a % 2 == 0) +} + +struct Data { + name: String, + is_true: bool, +} +fn with_struct(arr: Vec) -> Option { + arr.into_iter().find(|el| el.name.len() == 10) +} + +struct Tuple(usize, usize); +fn with_tuple_struct(arr: Vec) -> Option { + arr.into_iter().map(|Tuple(a, _)| a).find(|&a| a >= 3) +} + +struct A; +impl A { + fn should_keep(&self) -> bool { + true + } +} +fn with_method_call(arr: Vec) -> Option { + arr.into_iter().find(|el| el.should_keep()) +} + +fn with_closure(arr: Vec) -> Option { + let f = |el: u32| -> u32 { el + 10 }; + arr.into_iter().find(|&el| f(el) == 20) +} + +fn with_closure2(arr: HashMap) -> Option { + let f = |el: i32| -> bool { el == 10 }; + arr.values().find(|&&el| f(el)).copied() +} + +fn with_bool(arr: Vec) -> Option { + arr.into_iter().find(|el| el.is_true) +} + +fn with_side_effects(arr: Vec) -> Option { + for v in arr { + if v == 1 { + println!("side effect"); + return Some(v); + } + } + None +} + +fn with_else(arr: Vec) -> Option { + for el in arr { + if el % 2 == 0 { + return Some(el); + } else { + println!("{}", el); + } + } + None +} + +fn tuple_with_ref(v: [(u8, &u8); 3]) -> Option { + v.into_iter().map(|(_, &x)| x).find(|&x| x > 10) +} + +fn ref_to_tuple_with_ref(v: &[(u8, &u8)]) -> Option { + v.iter().map(|&(_, &x)| x).find(|&x| x > 10) +} + +fn explicit_ret(arr: Vec) -> Option { + arr.into_iter().find(|&x| x >= 5) +} + +fn plus_one(a: i32) -> Option { + Some(a + 1) +} +fn fn_instead_of_some(a: &[i32]) -> Option { + for &x in a { + if x == 1 { + return plus_one(x); + } + } + None +} + +fn for_in_condition(a: &[i32], b: bool) -> Option { + if b { + for &x in a { + if x == 1 { + return Some(x); + } + } + } + None +} + +fn intermediate_statements(a: &[i32]) -> Option { + for &x in a { + if x == 1 { + return Some(x); + } + } + + println!("side effect"); + + None +} + +fn mixed_binding_modes(arr: Vec<(i32, String)>) -> Option { + for (x, mut s) in arr { + if x == 1 && s.as_mut_str().len() == 2 { + return Some(x); + } + } + None +} + +fn as_closure() { + #[rustfmt::skip] + let f = |arr: Vec| -> Option { + arr.into_iter().find(|&x| x < 1) + }; +} + +fn in_block(a: &[i32]) -> Option { + let should_be_none = { + for &x in a { + if x == 1 { + return Some(x); + } + } + None + }; + + assert!(should_be_none.is_none()); + + should_be_none +} + +// Not handled yet +fn mut_binding(v: Vec) -> Option { + for mut s in v { + if s.as_mut_str().len() > 1 { + return Some(s); + } + } + None +} + +fn subpattern(v: Vec<[u32; 32]>) -> Option<[u32; 32]> { + for a @ [first, ..] in v { + if a[12] == first { + return Some(a); + } + } + None +} + +fn two_bindings(v: Vec<(u8, u8)>) -> Option { + for (a, n) in v { + if a == n { + return Some(a); + } + } + None +} + +fn main() {} diff --git a/tests/ui/manual_find_fixable.rs b/tests/ui/manual_find_fixable.rs new file mode 100644 index 00000000000..ed277ddaa72 --- /dev/null +++ b/tests/ui/manual_find_fixable.rs @@ -0,0 +1,242 @@ +// run-rustfix + +#![allow(unused, clippy::needless_return)] +#![warn(clippy::manual_find)] + +use std::collections::HashMap; + +const ARRAY: &[u32; 5] = &[2, 7, 1, 9, 3]; + +fn lookup(n: u32) -> Option { + for &v in ARRAY { + if v == n { + return Some(v); + } + } + None +} + +fn with_pat(arr: Vec<(u32, u32)>) -> Option { + for (a, _) in arr { + if a % 2 == 0 { + return Some(a); + } + } + None +} + +struct Data { + name: String, + is_true: bool, +} +fn with_struct(arr: Vec) -> Option { + for el in arr { + if el.name.len() == 10 { + return Some(el); + } + } + None +} + +struct Tuple(usize, usize); +fn with_tuple_struct(arr: Vec) -> Option { + for Tuple(a, _) in arr { + if a >= 3 { + return Some(a); + } + } + None +} + +struct A; +impl A { + fn should_keep(&self) -> bool { + true + } +} +fn with_method_call(arr: Vec) -> Option { + for el in arr { + if el.should_keep() { + return Some(el); + } + } + None +} + +fn with_closure(arr: Vec) -> Option { + let f = |el: u32| -> u32 { el + 10 }; + for el in arr { + if f(el) == 20 { + return Some(el); + } + } + None +} + +fn with_closure2(arr: HashMap) -> Option { + let f = |el: i32| -> bool { el == 10 }; + for &el in arr.values() { + if f(el) { + return Some(el); + } + } + None +} + +fn with_bool(arr: Vec) -> Option { + for el in arr { + if el.is_true { + return Some(el); + } + } + None +} + +fn with_side_effects(arr: Vec) -> Option { + for v in arr { + if v == 1 { + println!("side effect"); + return Some(v); + } + } + None +} + +fn with_else(arr: Vec) -> Option { + for el in arr { + if el % 2 == 0 { + return Some(el); + } else { + println!("{}", el); + } + } + None +} + +fn tuple_with_ref(v: [(u8, &u8); 3]) -> Option { + for (_, &x) in v { + if x > 10 { + return Some(x); + } + } + None +} + +fn ref_to_tuple_with_ref(v: &[(u8, &u8)]) -> Option { + for &(_, &x) in v { + if x > 10 { + return Some(x); + } + } + None +} + +fn explicit_ret(arr: Vec) -> Option { + for x in arr { + if x >= 5 { + return Some(x); + } + } + return None; +} + +fn plus_one(a: i32) -> Option { + Some(a + 1) +} +fn fn_instead_of_some(a: &[i32]) -> Option { + for &x in a { + if x == 1 { + return plus_one(x); + } + } + None +} + +fn for_in_condition(a: &[i32], b: bool) -> Option { + if b { + for &x in a { + if x == 1 { + return Some(x); + } + } + } + None +} + +fn intermediate_statements(a: &[i32]) -> Option { + for &x in a { + if x == 1 { + return Some(x); + } + } + + println!("side effect"); + + None +} + +fn mixed_binding_modes(arr: Vec<(i32, String)>) -> Option { + for (x, mut s) in arr { + if x == 1 && s.as_mut_str().len() == 2 { + return Some(x); + } + } + None +} + +fn as_closure() { + #[rustfmt::skip] + let f = |arr: Vec| -> Option { + for x in arr { + if x < 1 { + return Some(x); + } + } + None + }; +} + +fn in_block(a: &[i32]) -> Option { + let should_be_none = { + for &x in a { + if x == 1 { + return Some(x); + } + } + None + }; + + assert!(should_be_none.is_none()); + + should_be_none +} + +// Not handled yet +fn mut_binding(v: Vec) -> Option { + for mut s in v { + if s.as_mut_str().len() > 1 { + return Some(s); + } + } + None +} + +fn subpattern(v: Vec<[u32; 32]>) -> Option<[u32; 32]> { + for a @ [first, ..] in v { + if a[12] == first { + return Some(a); + } + } + None +} + +fn two_bindings(v: Vec<(u8, u8)>) -> Option { + for (a, n) in v { + if a == n { + return Some(a); + } + } + None +} + +fn main() {} diff --git a/tests/ui/manual_find_fixable.stderr b/tests/ui/manual_find_fixable.stderr new file mode 100644 index 00000000000..dbc4ff69a74 --- /dev/null +++ b/tests/ui/manual_find_fixable.stderr @@ -0,0 +1,142 @@ +error: manual implementation of `Iterator::find` + --> $DIR/manual_find_fixable.rs:11:5 + | +LL | / for &v in ARRAY { +LL | | if v == n { +LL | | return Some(v); +LL | | } +LL | | } +LL | | None + | |________^ help: replace with an iterator: `ARRAY.iter().find(|&&v| v == n).copied()` + | + = note: `-D clippy::manual-find` implied by `-D warnings` + +error: manual implementation of `Iterator::find` + --> $DIR/manual_find_fixable.rs:20:5 + | +LL | / for (a, _) in arr { +LL | | if a % 2 == 0 { +LL | | return Some(a); +LL | | } +LL | | } +LL | | None + | |________^ help: replace with an iterator: `arr.into_iter().map(|(a, _)| a).find(|&a| a % 2 == 0)` + +error: manual implementation of `Iterator::find` + --> $DIR/manual_find_fixable.rs:33:5 + | +LL | / for el in arr { +LL | | if el.name.len() == 10 { +LL | | return Some(el); +LL | | } +LL | | } +LL | | None + | |________^ help: replace with an iterator: `arr.into_iter().find(|el| el.name.len() == 10)` + | + = note: you may need to dereference some variables + +error: manual implementation of `Iterator::find` + --> $DIR/manual_find_fixable.rs:43:5 + | +LL | / for Tuple(a, _) in arr { +LL | | if a >= 3 { +LL | | return Some(a); +LL | | } +LL | | } +LL | | None + | |________^ help: replace with an iterator: `arr.into_iter().map(|Tuple(a, _)| a).find(|&a| a >= 3)` + +error: manual implementation of `Iterator::find` + --> $DIR/manual_find_fixable.rs:58:5 + | +LL | / for el in arr { +LL | | if el.should_keep() { +LL | | return Some(el); +LL | | } +LL | | } +LL | | None + | |________^ help: replace with an iterator: `arr.into_iter().find(|el| el.should_keep())` + | + = note: you may need to dereference some variables + +error: manual implementation of `Iterator::find` + --> $DIR/manual_find_fixable.rs:68:5 + | +LL | / for el in arr { +LL | | if f(el) == 20 { +LL | | return Some(el); +LL | | } +LL | | } +LL | | None + | |________^ help: replace with an iterator: `arr.into_iter().find(|&el| f(el) == 20)` + +error: manual implementation of `Iterator::find` + --> $DIR/manual_find_fixable.rs:78:5 + | +LL | / for &el in arr.values() { +LL | | if f(el) { +LL | | return Some(el); +LL | | } +LL | | } +LL | | None + | |________^ help: replace with an iterator: `arr.values().find(|&&el| f(el)).copied()` + +error: manual implementation of `Iterator::find` + --> $DIR/manual_find_fixable.rs:87:5 + | +LL | / for el in arr { +LL | | if el.is_true { +LL | | return Some(el); +LL | | } +LL | | } +LL | | None + | |________^ help: replace with an iterator: `arr.into_iter().find(|el| el.is_true)` + | + = note: you may need to dereference some variables + +error: manual implementation of `Iterator::find` + --> $DIR/manual_find_fixable.rs:117:5 + | +LL | / for (_, &x) in v { +LL | | if x > 10 { +LL | | return Some(x); +LL | | } +LL | | } +LL | | None + | |________^ help: replace with an iterator: `v.into_iter().map(|(_, &x)| x).find(|&x| x > 10)` + +error: manual implementation of `Iterator::find` + --> $DIR/manual_find_fixable.rs:126:5 + | +LL | / for &(_, &x) in v { +LL | | if x > 10 { +LL | | return Some(x); +LL | | } +LL | | } +LL | | None + | |________^ help: replace with an iterator: `v.iter().map(|&(_, &x)| x).find(|&x| x > 10)` + +error: manual implementation of `Iterator::find` + --> $DIR/manual_find_fixable.rs:135:5 + | +LL | / for x in arr { +LL | | if x >= 5 { +LL | | return Some(x); +LL | | } +LL | | } +LL | | return None; + | |________________^ help: replace with an iterator: `arr.into_iter().find(|&x| x >= 5)` + +error: manual implementation of `Iterator::find` + --> $DIR/manual_find_fixable.rs:190:9 + | +LL | / for x in arr { +LL | | if x < 1 { +LL | | return Some(x); +LL | | } +LL | | } +LL | | None + | |____________^ help: replace with an iterator: `arr.into_iter().find(|&x| x < 1)` + +error: aborting due to 12 previous errors + diff --git a/tests/ui/while_let_on_iterator.fixed b/tests/ui/while_let_on_iterator.fixed index cb8892a3f00..e9ff64431e1 100644 --- a/tests/ui/while_let_on_iterator.fixed +++ b/tests/ui/while_let_on_iterator.fixed @@ -6,7 +6,8 @@ unreachable_code, unused_mut, dead_code, - clippy::equatable_if_let + clippy::equatable_if_let, + clippy::manual_find )] fn base() { diff --git a/tests/ui/while_let_on_iterator.rs b/tests/ui/while_let_on_iterator.rs index d9157184495..03da39526b2 100644 --- a/tests/ui/while_let_on_iterator.rs +++ b/tests/ui/while_let_on_iterator.rs @@ -6,7 +6,8 @@ unreachable_code, unused_mut, dead_code, - clippy::equatable_if_let + clippy::equatable_if_let, + clippy::manual_find )] fn base() { diff --git a/tests/ui/while_let_on_iterator.stderr b/tests/ui/while_let_on_iterator.stderr index fb2b0f2467c..42859243855 100644 --- a/tests/ui/while_let_on_iterator.stderr +++ b/tests/ui/while_let_on_iterator.stderr @@ -1,5 +1,5 @@ error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:14:5 + --> $DIR/while_let_on_iterator.rs:15:5 | LL | while let Option::Some(x) = iter.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for x in iter` @@ -7,85 +7,85 @@ LL | while let Option::Some(x) = iter.next() { = note: `-D clippy::while-let-on-iterator` implied by `-D warnings` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:19:5 + --> $DIR/while_let_on_iterator.rs:20:5 | LL | while let Some(x) = iter.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for x in iter` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:24:5 + --> $DIR/while_let_on_iterator.rs:25:5 | LL | while let Some(_) = iter.next() {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for _ in iter` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:100:9 + --> $DIR/while_let_on_iterator.rs:101:9 | LL | while let Some([..]) = it.next() {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for [..] in it` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:107:9 + --> $DIR/while_let_on_iterator.rs:108:9 | LL | while let Some([_x]) = it.next() {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for [_x] in it` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:120:9 + --> $DIR/while_let_on_iterator.rs:121:9 | LL | while let Some(x @ [_]) = it.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for x @ [_] in it` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:140:9 + --> $DIR/while_let_on_iterator.rs:141:9 | LL | while let Some(_) = y.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for _ in y` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:197:9 + --> $DIR/while_let_on_iterator.rs:198:9 | LL | while let Some(m) = it.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for m in it.by_ref()` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:208:5 + --> $DIR/while_let_on_iterator.rs:209:5 | LL | while let Some(n) = it.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for n in it` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:210:9 + --> $DIR/while_let_on_iterator.rs:211:9 | LL | while let Some(m) = it.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for m in it` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:219:9 + --> $DIR/while_let_on_iterator.rs:220:9 | LL | while let Some(m) = it.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for m in it` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:228:9 + --> $DIR/while_let_on_iterator.rs:229:9 | LL | while let Some(m) = it.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for m in it.by_ref()` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:245:9 + --> $DIR/while_let_on_iterator.rs:246:9 | LL | while let Some(m) = it.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for m in it.by_ref()` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:260:13 + --> $DIR/while_let_on_iterator.rs:261:13 | LL | while let Some(i) = self.0.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for i in self.0.by_ref()` error: manual `!RangeInclusive::contains` implementation - --> $DIR/while_let_on_iterator.rs:261:20 + --> $DIR/while_let_on_iterator.rs:262:20 | LL | if i < 3 || i > 7 { | ^^^^^^^^^^^^^^ help: use: `!(3..=7).contains(&i)` @@ -93,49 +93,49 @@ LL | if i < 3 || i > 7 { = note: `-D clippy::manual-range-contains` implied by `-D warnings` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:292:13 + --> $DIR/while_let_on_iterator.rs:293:13 | LL | while let Some(i) = self.0.0.0.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for i in self.0.0.0.by_ref()` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:321:5 + --> $DIR/while_let_on_iterator.rs:322:5 | LL | while let Some(n) = it.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for n in it.by_ref()` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:333:9 + --> $DIR/while_let_on_iterator.rs:334:9 | LL | while let Some(x) = it.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for x in it.by_ref()` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:347:5 + --> $DIR/while_let_on_iterator.rs:348:5 | LL | while let Some(x) = it.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for x in it.by_ref()` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:358:5 + --> $DIR/while_let_on_iterator.rs:359:5 | LL | while let Some(x) = it.0.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for x in it.0.by_ref()` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:393:5 + --> $DIR/while_let_on_iterator.rs:394:5 | LL | while let Some(x) = s.x.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for x in s.x.by_ref()` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:400:5 + --> $DIR/while_let_on_iterator.rs:401:5 | LL | while let Some(x) = x[0].next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for x in x[0].by_ref()` error: this loop could be written as a `for` loop - --> $DIR/while_let_on_iterator.rs:407:5 + --> $DIR/while_let_on_iterator.rs:408:5 | LL | while let Some(..) = it.next() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `for _ in it` -- cgit 1.4.1-3-g733a5 From 5a70d88af5ff96897b7ddfd01d13e039a86c775c Mon Sep 17 00:00:00 2001 From: DevAccentor Date: Sun, 5 Jun 2022 11:01:54 +0200 Subject: add vec.capacity() to slow_vec_initialization --- clippy_lints/src/slow_vector_initialization.rs | 39 ++++++++++++++------------ tests/ui/slow_vector_initialization.rs | 6 ++++ tests/ui/slow_vector_initialization.stderr | 28 ++++++++++++++---- 3 files changed, 49 insertions(+), 24 deletions(-) diff --git a/clippy_lints/src/slow_vector_initialization.rs b/clippy_lints/src/slow_vector_initialization.rs index b4ad5dcbe3e..e5b30937d16 100644 --- a/clippy_lints/src/slow_vector_initialization.rs +++ b/clippy_lints/src/slow_vector_initialization.rs @@ -28,6 +28,9 @@ declare_clippy_lint! { /// let mut vec1 = Vec::with_capacity(len); /// vec1.resize(len, 0); /// + /// let mut vec1 = Vec::with_capacity(len); + /// vec1.resize(vec1.capacity(), 0); + /// /// let mut vec2 = Vec::with_capacity(len); /// vec2.extend(repeat(0).take(len)); /// @@ -210,23 +213,20 @@ impl<'a, 'tcx> VectorInitializationVisitor<'a, 'tcx> { /// Checks if the given expression is resizing a vector with 0 fn search_slow_resize_filling(&mut self, expr: &'tcx Expr<'_>) { - if_chain! { - if self.initialization_found; - if let ExprKind::MethodCall(path, [self_arg, len_arg, fill_arg], _) = expr.kind; - if path_to_local_id(self_arg, self.vec_alloc.local_id); - if path.ident.name == sym!(resize); - + 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) // Check that is filled with 0 - if let ExprKind::Lit(ref lit) = fill_arg.kind; - if let LitKind::Int(0, _) = lit.node; - - // Check that len expression is equals to `with_capacity` expression - if SpanlessEq::new(self.cx).eq_expr(len_arg, self.vec_alloc.len_expr); - - then { - self.slow_expression = Some(InitializationType::Resize(expr)); + && let ExprKind::Lit(ref lit) = fill_arg.kind + && let LitKind::Int(0, _) = lit.node { + // Check that len expression is equals to `with_capacity` expression + if SpanlessEq::new(self.cx).eq_expr(len_arg, self.vec_alloc.len_expr) { + self.slow_expression = Some(InitializationType::Resize(expr)); + } else if let ExprKind::MethodCall(path, _, _) = len_arg.kind && path.ident.as_str() == "capacity" { + self.slow_expression = Some(InitializationType::Resize(expr)); + } } - } } /// Returns `true` if give expression is `repeat(0).take(...)` @@ -239,12 +239,15 @@ impl<'a, 'tcx> VectorInitializationVisitor<'a, 'tcx> { if let Some(repeat_expr) = take_args.get(0); if self.is_repeat_zero(repeat_expr); - // Check that len expression is equals to `with_capacity` expression if let Some(len_arg) = take_args.get(1); - if SpanlessEq::new(self.cx).eq_expr(len_arg, self.vec_alloc.len_expr); then { - return true; + // Check that len expression is equals to `with_capacity` expression + if SpanlessEq::new(self.cx).eq_expr(len_arg, self.vec_alloc.len_expr) { + return true; + } else if let ExprKind::MethodCall(path, _, _) = len_arg.kind && path.ident.as_str() == "capacity" { + return true; + } } } diff --git a/tests/ui/slow_vector_initialization.rs b/tests/ui/slow_vector_initialization.rs index 7e3d357ae50..16be9f6d203 100644 --- a/tests/ui/slow_vector_initialization.rs +++ b/tests/ui/slow_vector_initialization.rs @@ -19,6 +19,9 @@ fn extend_vector() { // Extend with mismatching expression should not be warned let mut vec3 = Vec::with_capacity(24322); vec3.extend(repeat(0).take(2)); + + let mut vec4 = Vec::with_capacity(len); + vec4.extend(repeat(0).take(vec4.capacity())); } fn mixed_extend_resize_vector() { @@ -48,6 +51,9 @@ fn resize_vector() { let mut vec3 = Vec::with_capacity(len - 10); vec3.resize(len - 10, 0); + let mut vec4 = Vec::with_capacity(len); + vec4.resize(vec4.capacity(), 0); + // Reinitialization should be warned vec1 = Vec::with_capacity(10); vec1.resize(10, 0); diff --git a/tests/ui/slow_vector_initialization.stderr b/tests/ui/slow_vector_initialization.stderr index 5d2788ec260..cb3ce3e95a7 100644 --- a/tests/ui/slow_vector_initialization.stderr +++ b/tests/ui/slow_vector_initialization.stderr @@ -17,7 +17,15 @@ LL | vec2.extend(repeat(0).take(len - 10)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: slow zero-filling initialization - --> $DIR/slow_vector_initialization.rs:31:5 + --> $DIR/slow_vector_initialization.rs:24:5 + | +LL | let mut vec4 = Vec::with_capacity(len); + | ----------------------- help: consider replace allocation with: `vec![0; len]` +LL | vec4.extend(repeat(0).take(vec4.capacity())); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: slow zero-filling initialization + --> $DIR/slow_vector_initialization.rs:34:5 | LL | let mut resized_vec = Vec::with_capacity(30); | ---------------------- help: consider replace allocation with: `vec![0; 30]` @@ -25,7 +33,7 @@ LL | resized_vec.resize(30, 0); | ^^^^^^^^^^^^^^^^^^^^^^^^^ error: slow zero-filling initialization - --> $DIR/slow_vector_initialization.rs:34:5 + --> $DIR/slow_vector_initialization.rs:37:5 | LL | let mut extend_vec = Vec::with_capacity(30); | ---------------------- help: consider replace allocation with: `vec![0; 30]` @@ -33,7 +41,7 @@ LL | extend_vec.extend(repeat(0).take(30)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: slow zero-filling initialization - --> $DIR/slow_vector_initialization.rs:41:5 + --> $DIR/slow_vector_initialization.rs:44:5 | LL | let mut vec1 = Vec::with_capacity(len); | ----------------------- help: consider replace allocation with: `vec![0; len]` @@ -41,7 +49,7 @@ LL | vec1.resize(len, 0); | ^^^^^^^^^^^^^^^^^^^ error: slow zero-filling initialization - --> $DIR/slow_vector_initialization.rs:49:5 + --> $DIR/slow_vector_initialization.rs:52:5 | LL | let mut vec3 = Vec::with_capacity(len - 10); | ---------------------------- help: consider replace allocation with: `vec![0; len - 10]` @@ -49,12 +57,20 @@ LL | vec3.resize(len - 10, 0); | ^^^^^^^^^^^^^^^^^^^^^^^^ error: slow zero-filling initialization - --> $DIR/slow_vector_initialization.rs:53:5 + --> $DIR/slow_vector_initialization.rs:55:5 + | +LL | let mut vec4 = Vec::with_capacity(len); + | ----------------------- help: consider replace allocation with: `vec![0; len]` +LL | vec4.resize(vec4.capacity(), 0); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: slow zero-filling initialization + --> $DIR/slow_vector_initialization.rs:59:5 | LL | vec1 = Vec::with_capacity(10); | ---------------------- help: consider replace allocation with: `vec![0; 10]` LL | vec1.resize(10, 0); | ^^^^^^^^^^^^^^^^^^ -error: aborting due to 7 previous errors +error: aborting due to 9 previous errors -- cgit 1.4.1-3-g733a5 From 6d6c63ee23c627aa9299cffa738f40189126c08a Mon Sep 17 00:00:00 2001 From: Micha White Date: Thu, 9 Jun 2022 16:25:48 -0400 Subject: Lint single_match with Options, Results, and Cows --- clippy_lints/src/matches/single_match.rs | 53 +++++++++++++++++++------------- tests/ui/single_match.stderr | 11 ++++++- tests/ui/single_match_else.stderr | 42 ++++++++++++++++++++++++- 3 files changed, 82 insertions(+), 24 deletions(-) diff --git a/clippy_lints/src/matches/single_match.rs b/clippy_lints/src/matches/single_match.rs index 0c4cb45d147..b54d4e8a0ee 100644 --- a/clippy_lints/src/matches/single_match.rs +++ b/clippy_lints/src/matches/single_match.rs @@ -140,6 +140,23 @@ fn check_opt_like<'a>( ty: Ty<'a>, els: Option<&Expr<'_>>, ) { + // We want to suggest to exclude an arm that contains only wildcards or forms the exhaustive + // match with the second branch, without enum variants in matches. + if !contains_only_wilds(arms[1].pat) && !form_exhaustive_matches(cx, ty, arms[0].pat, arms[1].pat) { + return; + } + + let mut paths_and_types = Vec::new(); + if !collect_pat_paths(&mut paths_and_types, cx, arms[1].pat, ty) { + return; + } + + if paths_and_types.iter().all(|info| in_candidate_enum(cx, info)) { + report_single_pattern(cx, ex, arms, expr, els); + } +} + +fn in_candidate_enum<'a>(cx: &LateContext<'a>, path_info: &(String, Ty<'_>)) -> bool { // list of candidate `Enum`s we know will never get any more members let candidates = &[ (&paths::COW, "Borrowed"), @@ -151,29 +168,13 @@ fn check_opt_like<'a>( (&paths::RESULT, "Ok"), ]; - // We want to suggest to exclude an arm that contains only wildcards or forms the exhaustive - // match with the second branch, without enum variants in matches. - if !contains_only_wilds(arms[1].pat) && !form_exhaustive_matches(arms[0].pat, arms[1].pat) { - return; - } - - let mut paths_and_types = Vec::new(); - if !collect_pat_paths(&mut paths_and_types, cx, arms[1].pat, ty) { - return; - } - - let in_candidate_enum = |path_info: &(String, Ty<'_>)| -> bool { - let (path, ty) = path_info; - for &(ty_path, pat_path) in candidates { - if path == pat_path && match_type(cx, *ty, ty_path) { - return true; - } + let (path, ty) = path_info; + for &(ty_path, pat_path) in candidates { + if path == pat_path && match_type(cx, *ty, ty_path) { + return true; } - false - }; - if paths_and_types.iter().all(in_candidate_enum) { - report_single_pattern(cx, ex, arms, expr, els); } + false } /// Collects paths and their types from the given patterns. Returns true if the given pattern could @@ -218,7 +219,7 @@ fn contains_only_wilds(pat: &Pat<'_>) -> bool { /// Returns true if the given patterns forms only exhaustive matches that don't contain enum /// patterns without a wildcard. -fn form_exhaustive_matches(left: &Pat<'_>, right: &Pat<'_>) -> bool { +fn form_exhaustive_matches<'a>(cx: &LateContext<'a>, ty: Ty<'a>, left: &Pat<'_>, right: &Pat<'_>) -> bool { match (&left.kind, &right.kind) { (PatKind::Wild, _) | (_, PatKind::Wild) => true, (PatKind::Tuple(left_in, left_pos), PatKind::Tuple(right_in, right_pos)) => { @@ -264,6 +265,14 @@ fn form_exhaustive_matches(left: &Pat<'_>, right: &Pat<'_>) -> bool { } true }, + (PatKind::TupleStruct(..), PatKind::Path(_) | PatKind::TupleStruct(..)) => { + let mut paths_and_types = Vec::new(); + if !collect_pat_paths(&mut paths_and_types, cx, right, ty) { + return false; + } + + paths_and_types.iter().all(|info| in_candidate_enum(cx, info)) + }, _ => false, } } diff --git a/tests/ui/single_match.stderr b/tests/ui/single_match.stderr index 318faf25717..4d2b9ec5f90 100644 --- a/tests/ui/single_match.stderr +++ b/tests/ui/single_match.stderr @@ -38,6 +38,15 @@ LL | | _ => {}, LL | | }; | |_____^ help: try this: `if let (2..=3, 7..=9) = z { dummy() }` +error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` + --> $DIR/single_match.rs:54:5 + | +LL | / match x { +LL | | Some(y) => dummy(), +LL | | None => (), +LL | | }; + | |_____^ help: try this: `if let Some(y) = x { dummy() }` + error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` --> $DIR/single_match.rs:59:5 | @@ -146,5 +155,5 @@ LL | | (..) => {}, LL | | } | |_____^ help: try this: `if let (.., Some(E::V), _) = (Some(42), Some(E::V), Some(42)) {}` -error: aborting due to 15 previous errors +error: aborting due to 16 previous errors diff --git a/tests/ui/single_match_else.stderr b/tests/ui/single_match_else.stderr index 7756c6f204e..dc603578fde 100644 --- a/tests/ui/single_match_else.stderr +++ b/tests/ui/single_match_else.stderr @@ -20,5 +20,45 @@ LL + None LL ~ }; | -error: aborting due to previous error +error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` + --> $DIR/single_match_else.rs:84:5 + | +LL | / match Some(1) { +LL | | Some(a) => println!("${:?}", a), +LL | | None => { +LL | | println!("else block"); +LL | | return +LL | | }, +LL | | } + | |_____^ + | +help: try this + | +LL ~ if let Some(a) = Some(1) { println!("${:?}", a) } else { +LL + println!("else block"); +LL + return +LL + } + | + +error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` + --> $DIR/single_match_else.rs:93:5 + | +LL | / match Some(1) { +LL | | Some(a) => println!("${:?}", a), +LL | | None => { +LL | | println!("else block"); +LL | | return; +LL | | }, +LL | | } + | |_____^ + | +help: try this + | +LL ~ if let Some(a) = Some(1) { println!("${:?}", a) } else { +LL + println!("else block"); +LL + return; +LL + } + | + +error: aborting due to 3 previous errors -- cgit 1.4.1-3-g733a5 From 2967127de2b99957cb03a3af741e797710eae534 Mon Sep 17 00:00:00 2001 From: Micha White Date: Thu, 9 Jun 2022 17:44:28 -0400 Subject: Refactored the candidate type checking --- clippy_lints/src/matches/single_match.rs | 48 ++++++-------------------------- 1 file changed, 9 insertions(+), 39 deletions(-) diff --git a/clippy_lints/src/matches/single_match.rs b/clippy_lints/src/matches/single_match.rs index b54d4e8a0ee..7d1ff208729 100644 --- a/clippy_lints/src/matches/single_match.rs +++ b/clippy_lints/src/matches/single_match.rs @@ -151,26 +151,17 @@ fn check_opt_like<'a>( return; } - if paths_and_types.iter().all(|info| in_candidate_enum(cx, info)) { + if paths_and_types.iter().all(|ty| in_candidate_enum(cx, *ty)) { report_single_pattern(cx, ex, arms, expr, els); } } -fn in_candidate_enum<'a>(cx: &LateContext<'a>, path_info: &(String, Ty<'_>)) -> bool { +fn in_candidate_enum<'a>(cx: &LateContext<'a>, ty: Ty<'_>) -> bool { // list of candidate `Enum`s we know will never get any more members - let candidates = &[ - (&paths::COW, "Borrowed"), - (&paths::COW, "Cow::Borrowed"), - (&paths::COW, "Cow::Owned"), - (&paths::COW, "Owned"), - (&paths::OPTION, "None"), - (&paths::RESULT, "Err"), - (&paths::RESULT, "Ok"), - ]; + let candidates = [&paths::COW, &paths::OPTION, &paths::RESULT]; - let (path, ty) = path_info; - for &(ty_path, pat_path) in candidates { - if path == pat_path && match_type(cx, *ty, ty_path) { + for candidate_ty in candidates { + if match_type(cx, ty, candidate_ty) { return true; } } @@ -179,29 +170,15 @@ fn in_candidate_enum<'a>(cx: &LateContext<'a>, path_info: &(String, Ty<'_>)) -> /// Collects paths and their types from the given patterns. Returns true if the given pattern could /// be simplified, false otherwise. -fn collect_pat_paths<'a>(acc: &mut Vec<(String, Ty<'a>)>, cx: &LateContext<'a>, pat: &Pat<'_>, ty: Ty<'a>) -> bool { +fn collect_pat_paths<'a>(acc: &mut Vec>, cx: &LateContext<'a>, pat: &Pat<'_>, ty: Ty<'a>) -> bool { match pat.kind { PatKind::Wild => true, PatKind::Tuple(inner, _) => inner.iter().all(|p| { let p_ty = cx.typeck_results().pat_ty(p); collect_pat_paths(acc, cx, p, p_ty) }), - PatKind::TupleStruct(ref path, ..) => { - let path = rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| { - s.print_qpath(path, false); - }); - acc.push((path, ty)); - true - }, - PatKind::Binding(BindingAnnotation::Unannotated, .., ident, None) => { - acc.push((ident.to_string(), ty)); - true - }, - PatKind::Path(ref path) => { - let path = rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| { - s.print_qpath(path, false); - }); - acc.push((path, ty)); + PatKind::TupleStruct(..) | PatKind::Binding(BindingAnnotation::Unannotated, .., None) | PatKind::Path(_) => { + acc.push(ty); true }, _ => false, @@ -265,14 +242,7 @@ fn form_exhaustive_matches<'a>(cx: &LateContext<'a>, ty: Ty<'a>, left: &Pat<'_>, } true }, - (PatKind::TupleStruct(..), PatKind::Path(_) | PatKind::TupleStruct(..)) => { - let mut paths_and_types = Vec::new(); - if !collect_pat_paths(&mut paths_and_types, cx, right, ty) { - return false; - } - - paths_and_types.iter().all(|info| in_candidate_enum(cx, info)) - }, + (PatKind::TupleStruct(..), PatKind::Path(_) | PatKind::TupleStruct(..)) => in_candidate_enum(cx, ty), _ => false, } } -- cgit 1.4.1-3-g733a5 From b46f1c4a7fa5c0705cc65953e9887c4449aa8cba Mon Sep 17 00:00:00 2001 From: Micha White Date: Thu, 9 Jun 2022 20:01:22 -0400 Subject: Don't trigger if a binding is in the else pattern --- clippy_lints/src/matches/single_match.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/clippy_lints/src/matches/single_match.rs b/clippy_lints/src/matches/single_match.rs index 7d1ff208729..e67ef2a2c3a 100644 --- a/clippy_lints/src/matches/single_match.rs +++ b/clippy_lints/src/matches/single_match.rs @@ -242,7 +242,10 @@ fn form_exhaustive_matches<'a>(cx: &LateContext<'a>, ty: Ty<'a>, left: &Pat<'_>, } true }, - (PatKind::TupleStruct(..), PatKind::Path(_) | PatKind::TupleStruct(..)) => in_candidate_enum(cx, ty), + (PatKind::TupleStruct(..), PatKind::Path(_)) => in_candidate_enum(cx, ty), + (PatKind::TupleStruct(..), PatKind::TupleStruct(_, inner, _)) => { + in_candidate_enum(cx, ty) && inner.iter().all(contains_only_wilds) + }, _ => false, } } -- cgit 1.4.1-3-g733a5 From b0c20302b7a647ae362071d72b382b1bd393e4a5 Mon Sep 17 00:00:00 2001 From: Micha White Date: Thu, 9 Jun 2022 21:16:39 -0400 Subject: Some refactoring --- clippy_lints/src/matches/single_match.rs | 37 ++++++++++++++------------------ 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/clippy_lints/src/matches/single_match.rs b/clippy_lints/src/matches/single_match.rs index e67ef2a2c3a..813469be2f7 100644 --- a/clippy_lints/src/matches/single_match.rs +++ b/clippy_lints/src/matches/single_match.rs @@ -140,22 +140,20 @@ fn check_opt_like<'a>( ty: Ty<'a>, els: Option<&Expr<'_>>, ) { - // We want to suggest to exclude an arm that contains only wildcards or forms the exhaustive - // match with the second branch, without enum variants in matches. - if !contains_only_wilds(arms[1].pat) && !form_exhaustive_matches(cx, ty, arms[0].pat, arms[1].pat) { - return; + // We don't want to lint if the second arm contains an enum which could + // have more variants in the future. + if form_exhaustive_matches(cx, ty, arms[0].pat, arms[1].pat) { + report_single_pattern(cx, ex, arms, expr, els); } +} +fn pat_in_candidate_enum<'a>(cx: &LateContext<'a>, ty: Ty<'a>, pat: &Pat<'_>) -> bool { let mut paths_and_types = Vec::new(); - if !collect_pat_paths(&mut paths_and_types, cx, arms[1].pat, ty) { - return; - } - - if paths_and_types.iter().all(|ty| in_candidate_enum(cx, *ty)) { - report_single_pattern(cx, ex, arms, expr, els); - } + collect_pat_paths(&mut paths_and_types, cx, pat, ty); + paths_and_types.iter().all(|ty| in_candidate_enum(cx, *ty)) } +/// Returns `true` if the given type is an enum we know won't be expanded in the future fn in_candidate_enum<'a>(cx: &LateContext<'a>, ty: Ty<'_>) -> bool { // list of candidate `Enum`s we know will never get any more members let candidates = [&paths::COW, &paths::OPTION, &paths::RESULT]; @@ -168,20 +166,17 @@ fn in_candidate_enum<'a>(cx: &LateContext<'a>, ty: Ty<'_>) -> bool { false } -/// Collects paths and their types from the given patterns. Returns true if the given pattern could -/// be simplified, false otherwise. -fn collect_pat_paths<'a>(acc: &mut Vec>, cx: &LateContext<'a>, pat: &Pat<'_>, ty: Ty<'a>) -> bool { +/// Collects paths and their types from the given patterns +fn collect_pat_paths<'a>(acc: &mut Vec>, cx: &LateContext<'a>, pat: &Pat<'_>, ty: Ty<'a>) { match pat.kind { - PatKind::Wild => true, - PatKind::Tuple(inner, _) => inner.iter().all(|p| { + PatKind::Tuple(inner, _) => inner.iter().for_each(|p| { let p_ty = cx.typeck_results().pat_ty(p); - collect_pat_paths(acc, cx, p, p_ty) + collect_pat_paths(acc, cx, p, p_ty); }), PatKind::TupleStruct(..) | PatKind::Binding(BindingAnnotation::Unannotated, .., None) | PatKind::Path(_) => { acc.push(ty); - true }, - _ => false, + _ => {}, } } @@ -242,9 +237,9 @@ fn form_exhaustive_matches<'a>(cx: &LateContext<'a>, ty: Ty<'a>, left: &Pat<'_>, } true }, - (PatKind::TupleStruct(..), PatKind::Path(_)) => in_candidate_enum(cx, ty), + (PatKind::TupleStruct(..), PatKind::Path(_)) => pat_in_candidate_enum(cx, ty, right), (PatKind::TupleStruct(..), PatKind::TupleStruct(_, inner, _)) => { - in_candidate_enum(cx, ty) && inner.iter().all(contains_only_wilds) + pat_in_candidate_enum(cx, ty, right) && inner.iter().all(contains_only_wilds) }, _ => false, } -- cgit 1.4.1-3-g733a5 From 01c75e4b98c3fe4cb1e6340bfd3148086ba05879 Mon Sep 17 00:00:00 2001 From: Micha White Date: Thu, 9 Jun 2022 21:39:13 -0400 Subject: Added tests for Cow and Result --- tests/ui/single_match_else.rs | 19 ++++++++++++++++++ tests/ui/single_match_else.stderr | 42 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/tests/ui/single_match_else.rs b/tests/ui/single_match_else.rs index 82387f3d80b..70d6febb71f 100644 --- a/tests/ui/single_match_else.rs +++ b/tests/ui/single_match_else.rs @@ -97,4 +97,23 @@ fn main() { return; }, } + + // lint here + use std::convert::Infallible; + match Result::::Ok(1) { + Ok(a) => println!("${:?}", a), + Err(_) => { + println!("else block"); + return; + } + } + + use std::borrow::Cow; + match Cow::from("moo") { + Cow::Owned(a) => println!("${:?}", a), + Cow::Borrowed(_) => { + println!("else block"); + return; + } + } } diff --git a/tests/ui/single_match_else.stderr b/tests/ui/single_match_else.stderr index dc603578fde..38fd9c6a678 100644 --- a/tests/ui/single_match_else.stderr +++ b/tests/ui/single_match_else.stderr @@ -60,5 +60,45 @@ LL + return; LL + } | -error: aborting due to 3 previous errors +error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` + --> $DIR/single_match_else.rs:103:5 + | +LL | / match Result::::Ok(1) { +LL | | Ok(a) => println!("${:?}", a), +LL | | Err(_) => { +LL | | println!("else block"); +LL | | return; +LL | | } +LL | | } + | |_____^ + | +help: try this + | +LL ~ if let Ok(a) = Result::::Ok(1) { println!("${:?}", a) } else { +LL + println!("else block"); +LL + return; +LL + } + | + +error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let` + --> $DIR/single_match_else.rs:112:5 + | +LL | / match Cow::from("moo") { +LL | | Cow::Owned(a) => println!("${:?}", a), +LL | | Cow::Borrowed(_) => { +LL | | println!("else block"); +LL | | return; +LL | | } +LL | | } + | |_____^ + | +help: try this + | +LL ~ if let Cow::Owned(a) = Cow::from("moo") { println!("${:?}", a) } else { +LL + println!("else block"); +LL + return; +LL + } + | + +error: aborting due to 5 previous errors -- cgit 1.4.1-3-g733a5 From f3dc7ae370a67988e48a573e332247717ce9bee4 Mon Sep 17 00:00:00 2001 From: Micha White Date: Thu, 9 Jun 2022 21:46:13 -0400 Subject: Fixed up the documentation for a couple functions --- clippy_lints/src/matches/single_match.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/clippy_lints/src/matches/single_match.rs b/clippy_lints/src/matches/single_match.rs index 813469be2f7..888ace687bb 100644 --- a/clippy_lints/src/matches/single_match.rs +++ b/clippy_lints/src/matches/single_match.rs @@ -147,6 +147,7 @@ fn check_opt_like<'a>( } } +/// Returns `true` if all of the types in the pattern are candidate enums fn pat_in_candidate_enum<'a>(cx: &LateContext<'a>, ty: Ty<'a>, pat: &Pat<'_>) -> bool { let mut paths_and_types = Vec::new(); collect_pat_paths(&mut paths_and_types, cx, pat, ty); @@ -166,7 +167,7 @@ fn in_candidate_enum<'a>(cx: &LateContext<'a>, ty: Ty<'_>) -> bool { false } -/// Collects paths and their types from the given patterns +/// Collects types from the given pattern fn collect_pat_paths<'a>(acc: &mut Vec>, cx: &LateContext<'a>, pat: &Pat<'_>, ty: Ty<'a>) { match pat.kind { PatKind::Tuple(inner, _) => inner.iter().for_each(|p| { -- cgit 1.4.1-3-g733a5 From ded2bb54642507c7a037a46bce31078026078672 Mon Sep 17 00:00:00 2001 From: Micha White Date: Thu, 9 Jun 2022 21:47:24 -0400 Subject: Slight documentation change --- clippy_lints/src/matches/single_match.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/clippy_lints/src/matches/single_match.rs b/clippy_lints/src/matches/single_match.rs index 888ace687bb..92091a0c339 100644 --- a/clippy_lints/src/matches/single_match.rs +++ b/clippy_lints/src/matches/single_match.rs @@ -147,7 +147,8 @@ fn check_opt_like<'a>( } } -/// Returns `true` if all of the types in the pattern are candidate enums +/// Returns `true` if all of the types in the pattern are enums which we know +/// won't be expanded in the future fn pat_in_candidate_enum<'a>(cx: &LateContext<'a>, ty: Ty<'a>, pat: &Pat<'_>) -> bool { let mut paths_and_types = Vec::new(); collect_pat_paths(&mut paths_and_types, cx, pat, ty); -- cgit 1.4.1-3-g733a5 From 9306e9a4df2e6f8ed577fff3c82006c532ca51d2 Mon Sep 17 00:00:00 2001 From: Dany Marcoux Date: Wed, 18 May 2022 20:28:11 +0200 Subject: Ignore bodies containing `todo!()` in `clippy::if_same_then_else` --- clippy_lints/src/copies.rs | 18 +++++++++++++++++- tests/ui/if_same_then_else.rs | 3 +++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/clippy_lints/src/copies.rs b/clippy_lints/src/copies.rs index 1deff9684a1..6f92caf7391 100644 --- a/clippy_lints/src/copies.rs +++ b/clippy_lints/src/copies.rs @@ -12,7 +12,7 @@ use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::hygiene::walk_chain; use rustc_span::source_map::SourceMap; -use rustc_span::{BytePos, Span, Symbol}; +use rustc_span::{sym, BytePos, Span, Symbol}; use std::borrow::Cow; declare_clippy_lint! { @@ -365,6 +365,21 @@ fn eq_stmts( .all(|b| get_stmt(b).map_or(false, |s| eq.eq_stmt(s, stmt))) } +fn block_contains_todo_macro(cx: &LateContext<'_>, block: &Block<'_>) -> bool { + dbg!(block); + if let Some(macro_def_id) = block.span.ctxt().outer_expn_data().macro_def_id { + dbg!(macro_def_id); + if let Some(diagnostic_name) = cx.tcx.get_diagnostic_name(macro_def_id) { + dbg!(diagnostic_name); + diagnostic_name == sym::todo_macro + } else { + false + } + } else { + false + } +} + fn scan_block_for_eq(cx: &LateContext<'_>, _conds: &[&Expr<'_>], block: &Block<'_>, blocks: &[&Block<'_>]) -> BlockEq { let mut eq = SpanlessEq::new(cx); let mut eq = eq.inter_expr(); @@ -398,6 +413,7 @@ fn scan_block_for_eq(cx: &LateContext<'_>, _conds: &[&Expr<'_>], block: &Block<' moved_locals, }; } + let end_search_start = block.stmts[start_end_eq..] .iter() .rev() diff --git a/tests/ui/if_same_then_else.rs b/tests/ui/if_same_then_else.rs index ef956745500..767185c207b 100644 --- a/tests/ui/if_same_then_else.rs +++ b/tests/ui/if_same_then_else.rs @@ -126,6 +126,9 @@ fn if_same_then_else() { _ => 4, }; } + + // Issue #8836 + if true { todo!() } else { todo!() } } // Issue #2423. This was causing an ICE. -- cgit 1.4.1-3-g733a5 From 7cb4cef123a53534aaaa3956d4edf079d8482c6b Mon Sep 17 00:00:00 2001 From: kyoto7250 <50972773+kyoto7250@users.noreply.github.com> Date: Thu, 16 Jun 2022 21:29:43 +0900 Subject: feat(fix): ignore todo! and unimplemented! in if_same_then_else --- clippy_lints/src/copies.rs | 51 ++++++++++++++++++++++--------- tests/ui/if_same_then_else.rs | 64 ++++++++++++++++++++++++++++++++++++--- tests/ui/if_same_then_else.stderr | 20 ++++++------ 3 files changed, 106 insertions(+), 29 deletions(-) diff --git a/clippy_lints/src/copies.rs b/clippy_lints/src/copies.rs index 6f92caf7391..a771656c20f 100644 --- a/clippy_lints/src/copies.rs +++ b/clippy_lints/src/copies.rs @@ -1,4 +1,5 @@ use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_then}; +use clippy_utils::macros::macro_backtrace; use clippy_utils::source::{first_line_of_span, indent_of, reindent_multiline, snippet, snippet_opt}; use clippy_utils::{ eq_expr_value, get_enclosing_block, hash_expr, hash_stmt, if_sequence, is_else_clause, is_lint_allowed, @@ -7,14 +8,16 @@ use clippy_utils::{ use core::iter; use rustc_errors::Applicability; use rustc_hir::intravisit; -use rustc_hir::{BinOpKind, Block, Expr, ExprKind, HirId, Stmt, StmtKind}; +use rustc_hir::{BinOpKind, Block, Expr, ExprKind, HirId, QPath, Stmt, StmtKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::hygiene::walk_chain; use rustc_span::source_map::SourceMap; -use rustc_span::{sym, BytePos, Span, Symbol}; +use rustc_span::{BytePos, Span, Symbol}; use std::borrow::Cow; +const ACCEPTABLE_MACRO: [&str; 2] = ["todo", "unimplemented"]; + declare_clippy_lint! { /// ### What it does /// Checks for consecutive `if`s with the same condition. @@ -195,7 +198,12 @@ fn lint_if_same_then_else(cx: &LateContext<'_>, conds: &[&Expr<'_>], blocks: &[& .array_windows::<2>() .enumerate() .fold(true, |all_eq, (i, &[lhs, rhs])| { - if eq.eq_block(lhs, rhs) && !contains_let(conds[i]) && conds.get(i + 1).map_or(true, |e| !contains_let(e)) { + if eq.eq_block(lhs, rhs) + && !contains_acceptable_macro(cx, lhs) + && !contains_acceptable_macro(cx, rhs) + && !contains_let(conds[i]) + && conds.get(i + 1).map_or(true, |e| !contains_let(e)) + { span_lint_and_note( cx, IF_SAME_THEN_ELSE, @@ -365,19 +373,33 @@ fn eq_stmts( .all(|b| get_stmt(b).map_or(false, |s| eq.eq_stmt(s, stmt))) } -fn block_contains_todo_macro(cx: &LateContext<'_>, block: &Block<'_>) -> bool { - dbg!(block); - if let Some(macro_def_id) = block.span.ctxt().outer_expn_data().macro_def_id { - dbg!(macro_def_id); - if let Some(diagnostic_name) = cx.tcx.get_diagnostic_name(macro_def_id) { - dbg!(diagnostic_name); - diagnostic_name == sym::todo_macro - } else { - false +fn contains_acceptable_macro(cx: &LateContext<'_>, block: &Block<'_>) -> bool { + for stmt in block.stmts { + match stmt.kind { + StmtKind::Semi(semi_expr) if acceptable_macro(cx, semi_expr) => return true, + _ => {}, } - } else { - false } + + if let Some(block_expr) = block.expr + && acceptable_macro(cx, block_expr) + { + return true + } + + false +} + +fn acceptable_macro(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { + if let ExprKind::Call(call_expr, _) = expr.kind + && let ExprKind::Path(QPath::Resolved(None, path)) = call_expr.kind + && macro_backtrace(path.span).any(|macro_call| { + ACCEPTABLE_MACRO.contains(&cx.tcx.item_name(macro_call.def_id).as_str()) + }) { + return true; + } + + false } fn scan_block_for_eq(cx: &LateContext<'_>, _conds: &[&Expr<'_>], block: &Block<'_>, blocks: &[&Block<'_>]) -> BlockEq { @@ -413,7 +435,6 @@ fn scan_block_for_eq(cx: &LateContext<'_>, _conds: &[&Expr<'_>], block: &Block<' moved_locals, }; } - let end_search_start = block.stmts[start_end_eq..] .iter() .rev() diff --git a/tests/ui/if_same_then_else.rs b/tests/ui/if_same_then_else.rs index 767185c207b..2598c2ab426 100644 --- a/tests/ui/if_same_then_else.rs +++ b/tests/ui/if_same_then_else.rs @@ -6,7 +6,9 @@ clippy::no_effect, clippy::unused_unit, clippy::zero_divided_by_zero, - clippy::branches_sharing_code + clippy::branches_sharing_code, + dead_code, + unreachable_code )] struct Foo { @@ -126,9 +128,6 @@ fn if_same_then_else() { _ => 4, }; } - - // Issue #8836 - if true { todo!() } else { todo!() } } // Issue #2423. This was causing an ICE. @@ -158,4 +157,61 @@ mod issue_5698 { } } +mod issue_8836 { + fn do_not_lint() { + if true { + todo!() + } else { + todo!() + } + if true { + todo!(); + } else { + todo!(); + } + if true { + unimplemented!() + } else { + unimplemented!() + } + if true { + unimplemented!(); + } else { + unimplemented!(); + } + + if true { + println!("FOO"); + todo!(); + } else { + println!("FOO"); + todo!(); + } + + if true { + println!("FOO"); + unimplemented!(); + } else { + println!("FOO"); + unimplemented!(); + } + + if true { + println!("FOO"); + todo!() + } else { + println!("FOO"); + todo!() + } + + if true { + println!("FOO"); + unimplemented!() + } else { + println!("FOO"); + unimplemented!() + } + } +} + fn main() {} diff --git a/tests/ui/if_same_then_else.stderr b/tests/ui/if_same_then_else.stderr index 2f38052fc20..2cdf442486a 100644 --- a/tests/ui/if_same_then_else.stderr +++ b/tests/ui/if_same_then_else.stderr @@ -1,5 +1,5 @@ error: this `if` has identical blocks - --> $DIR/if_same_then_else.rs:21:13 + --> $DIR/if_same_then_else.rs:23:13 | LL | if true { | _____________^ @@ -13,7 +13,7 @@ LL | | } else { | = note: `-D clippy::if-same-then-else` implied by `-D warnings` note: same as this - --> $DIR/if_same_then_else.rs:29:12 + --> $DIR/if_same_then_else.rs:31:12 | LL | } else { | ____________^ @@ -26,7 +26,7 @@ LL | | } | |_____^ error: this `if` has identical blocks - --> $DIR/if_same_then_else.rs:65:21 + --> $DIR/if_same_then_else.rs:67:21 | LL | let _ = if true { | _____________________^ @@ -35,7 +35,7 @@ LL | | } else { | |_____^ | note: same as this - --> $DIR/if_same_then_else.rs:67:12 + --> $DIR/if_same_then_else.rs:69:12 | LL | } else { | ____________^ @@ -45,7 +45,7 @@ LL | | }; | |_____^ error: this `if` has identical blocks - --> $DIR/if_same_then_else.rs:72:21 + --> $DIR/if_same_then_else.rs:74:21 | LL | let _ = if true { | _____________________^ @@ -54,7 +54,7 @@ LL | | } else { | |_____^ | note: same as this - --> $DIR/if_same_then_else.rs:74:12 + --> $DIR/if_same_then_else.rs:76:12 | LL | } else { | ____________^ @@ -64,7 +64,7 @@ LL | | }; | |_____^ error: this `if` has identical blocks - --> $DIR/if_same_then_else.rs:88:21 + --> $DIR/if_same_then_else.rs:90:21 | LL | let _ = if true { | _____________________^ @@ -73,7 +73,7 @@ LL | | } else { | |_____^ | note: same as this - --> $DIR/if_same_then_else.rs:90:12 + --> $DIR/if_same_then_else.rs:92:12 | LL | } else { | ____________^ @@ -83,7 +83,7 @@ LL | | }; | |_____^ error: this `if` has identical blocks - --> $DIR/if_same_then_else.rs:95:13 + --> $DIR/if_same_then_else.rs:97:13 | LL | if true { | _____________^ @@ -96,7 +96,7 @@ LL | | } else { | |_____^ | note: same as this - --> $DIR/if_same_then_else.rs:102:12 + --> $DIR/if_same_then_else.rs:104:12 | LL | } else { | ____________^ -- cgit 1.4.1-3-g733a5 From 922ff84baf2889709c89fcd2704ba097c4b4fd35 Mon Sep 17 00:00:00 2001 From: klensy Date: Wed, 15 Jun 2022 14:15:54 +0300 Subject: bless clippy tests --- tests/ui/eprint_with_newline.stderr | 16 ++++++++-------- tests/ui/manual_split_once.stderr | 20 ++++++++++---------- tests/ui/map_unwrap_or.stderr | 8 ++++---- tests/ui/needless_late_init.stderr | 4 ++-- tests/ui/print_literal.stderr | 22 +++++++++++----------- tests/ui/print_with_newline.stderr | 16 ++++++++-------- tests/ui/unnecessary_iter_cloned.stderr | 4 ++-- tests/ui/unnecessary_to_owned.stderr | 2 +- tests/ui/write_literal.stderr | 22 +++++++++++----------- tests/ui/write_literal_2.stderr | 10 +++++----- tests/ui/write_with_newline.stderr | 16 ++++++++-------- 11 files changed, 70 insertions(+), 70 deletions(-) diff --git a/tests/ui/eprint_with_newline.stderr b/tests/ui/eprint_with_newline.stderr index 090dae3733d..f137787bff0 100644 --- a/tests/ui/eprint_with_newline.stderr +++ b/tests/ui/eprint_with_newline.stderr @@ -9,7 +9,7 @@ help: use `eprintln!` instead | LL - eprint!("Hello/n"); LL + eprintln!("Hello"); - | + | error: using `eprint!()` with a format string that ends in a single newline --> $DIR/eprint_with_newline.rs:6:5 @@ -21,7 +21,7 @@ help: use `eprintln!` instead | LL - eprint!("Hello {}/n", "world"); LL + eprintln!("Hello {}", "world"); - | + | error: using `eprint!()` with a format string that ends in a single newline --> $DIR/eprint_with_newline.rs:7:5 @@ -33,7 +33,7 @@ help: use `eprintln!` instead | LL - eprint!("Hello {} {}/n", "world", "#2"); LL + eprintln!("Hello {} {}", "world", "#2"); - | + | error: using `eprint!()` with a format string that ends in a single newline --> $DIR/eprint_with_newline.rs:8:5 @@ -45,7 +45,7 @@ help: use `eprintln!` instead | LL - eprint!("{}/n", 1265); LL + eprintln!("{}", 1265); - | + | error: using `eprint!()` with a format string that ends in a single newline --> $DIR/eprint_with_newline.rs:9:5 @@ -57,7 +57,7 @@ help: use `eprintln!` instead | LL - eprint!("/n"); LL + eprintln!(); - | + | error: using `eprint!()` with a format string that ends in a single newline --> $DIR/eprint_with_newline.rs:28:5 @@ -69,7 +69,7 @@ help: use `eprintln!` instead | LL - eprint!("//n"); // should fail LL + eprintln!("/"); // should fail - | + | error: using `eprint!()` with a format string that ends in a single newline --> $DIR/eprint_with_newline.rs:35:5 @@ -111,7 +111,7 @@ help: use `eprintln!` instead | LL - eprint!("/r/n"); //~ ERROR LL + eprintln!("/r"); //~ ERROR - | + | error: using `eprint!()` with a format string that ends in a single newline --> $DIR/eprint_with_newline.rs:48:5 @@ -123,7 +123,7 @@ help: use `eprintln!` instead | LL - eprint!("foo/rbar/n") // ~ ERROR LL + eprintln!("foo/rbar") // ~ ERROR - | + | error: aborting due to 10 previous errors diff --git a/tests/ui/manual_split_once.stderr b/tests/ui/manual_split_once.stderr index 2563a6904b7..2696694680a 100644 --- a/tests/ui/manual_split_once.stderr +++ b/tests/ui/manual_split_once.stderr @@ -96,12 +96,12 @@ help: remove the `iter` usages | LL - let l = iter.next().unwrap(); LL + - | + | help: remove the `iter` usages | LL - let r = iter.next().unwrap(); LL + - | + | error: manual implementation of `split_once` --> $DIR/manual_split_once.rs:49:5 @@ -121,12 +121,12 @@ help: remove the `iter` usages | LL - let l = iter.next()?; LL + - | + | help: remove the `iter` usages | LL - let r = iter.next()?; LL + - | + | error: manual implementation of `rsplit_once` --> $DIR/manual_split_once.rs:53:5 @@ -146,12 +146,12 @@ help: remove the `iter` usages | LL - let r = iter.next().unwrap(); LL + - | + | help: remove the `iter` usages | LL - let l = iter.next().unwrap(); LL + - | + | error: manual implementation of `rsplit_once` --> $DIR/manual_split_once.rs:57:5 @@ -171,12 +171,12 @@ help: remove the `iter` usages | LL - let r = iter.next()?; LL + - | + | help: remove the `iter` usages | LL - let l = iter.next()?; LL + - | + | error: manual implementation of `split_once` --> $DIR/manual_split_once.rs:142:13 @@ -202,12 +202,12 @@ help: remove the `iter` usages | LL - let a = iter.next().unwrap(); LL + - | + | help: remove the `iter` usages | LL - let b = iter.next().unwrap(); LL + - | + | error: aborting due to 19 previous errors diff --git a/tests/ui/map_unwrap_or.stderr b/tests/ui/map_unwrap_or.stderr index 954000b8b76..abc9c1ece32 100644 --- a/tests/ui/map_unwrap_or.stderr +++ b/tests/ui/map_unwrap_or.stderr @@ -12,7 +12,7 @@ help: use `map_or(, )` instead | LL - let _ = opt.map(|x| x + 1) LL + let _ = opt.map_or(0, |x| x + 1); - | + | error: called `map().unwrap_or()` on an `Option` value. This can be done more directly by calling `map_or(, )` instead --> $DIR/map_unwrap_or.rs:20:13 @@ -59,7 +59,7 @@ help: use `and_then()` instead | LL - let _ = opt.map(|x| Some(x + 1)).unwrap_or(None); LL + let _ = opt.and_then(|x| Some(x + 1)); - | + | error: called `map().unwrap_or(None)` on an `Option` value. This can be done more directly by calling `and_then()` instead --> $DIR/map_unwrap_or.rs:31:13 @@ -92,7 +92,7 @@ help: use `and_then()` instead | LL - .map(|x| Some(x + 1)) LL + .and_then(|x| Some(x + 1)); - | + | error: called `map().unwrap_or()` on an `Option` value. This can be done more directly by calling `map_or(, )` instead --> $DIR/map_unwrap_or.rs:46:13 @@ -104,7 +104,7 @@ help: use `map_or(, )` instead | LL - let _ = Some("prefix").map(|p| format!("{}.", p)).unwrap_or(id); LL + let _ = Some("prefix").map_or(id, |p| format!("{}.", p)); - | + | error: called `map().unwrap_or_else()` on an `Option` value. This can be done more directly by calling `map_or_else(, )` instead --> $DIR/map_unwrap_or.rs:50:13 diff --git a/tests/ui/needless_late_init.stderr b/tests/ui/needless_late_init.stderr index f320b5b9cbb..313cdbbeba1 100644 --- a/tests/ui/needless_late_init.stderr +++ b/tests/ui/needless_late_init.stderr @@ -164,7 +164,7 @@ help: remove the assignments from the `match` arms | LL - 1 => f = "three", LL + 1 => "three", - | + | error: unneeded late initialization --> $DIR/needless_late_init.rs:76:5 @@ -180,7 +180,7 @@ help: remove the assignments from the branches | LL - g = 5; LL + 5 - | + | help: add a semicolon after the `if` expression | LL | }; diff --git a/tests/ui/print_literal.stderr b/tests/ui/print_literal.stderr index a10cac04411..72aae075603 100644 --- a/tests/ui/print_literal.stderr +++ b/tests/ui/print_literal.stderr @@ -9,7 +9,7 @@ help: try this | LL - print!("Hello {}", "world"); LL + print!("Hello world"); - | + | error: literal with an empty format string --> $DIR/print_literal.rs:26:36 @@ -21,7 +21,7 @@ help: try this | LL - println!("Hello {} {}", world, "world"); LL + println!("Hello {} world", world); - | + | error: literal with an empty format string --> $DIR/print_literal.rs:27:26 @@ -33,7 +33,7 @@ help: try this | LL - println!("Hello {}", "world"); LL + println!("Hello world"); - | + | error: literal with an empty format string --> $DIR/print_literal.rs:32:25 @@ -45,7 +45,7 @@ help: try this | LL - println!("{0} {1}", "hello", "world"); LL + println!("hello {1}", "world"); - | + | error: literal with an empty format string --> $DIR/print_literal.rs:32:34 @@ -57,7 +57,7 @@ help: try this | LL - println!("{0} {1}", "hello", "world"); LL + println!("{0} world", "hello"); - | + | error: literal with an empty format string --> $DIR/print_literal.rs:33:25 @@ -69,7 +69,7 @@ help: try this | LL - println!("{1} {0}", "hello", "world"); LL + println!("{1} hello", "world"); - | + | error: literal with an empty format string --> $DIR/print_literal.rs:33:34 @@ -81,7 +81,7 @@ help: try this | LL - println!("{1} {0}", "hello", "world"); LL + println!("world {0}", "hello"); - | + | error: literal with an empty format string --> $DIR/print_literal.rs:36:29 @@ -93,7 +93,7 @@ help: try this | LL - println!("{foo} {bar}", foo = "hello", bar = "world"); LL + println!("hello {bar}", bar = "world"); - | + | error: literal with an empty format string --> $DIR/print_literal.rs:36:44 @@ -105,7 +105,7 @@ help: try this | LL - println!("{foo} {bar}", foo = "hello", bar = "world"); LL + println!("{foo} world", foo = "hello"); - | + | error: literal with an empty format string --> $DIR/print_literal.rs:37:29 @@ -117,7 +117,7 @@ help: try this | LL - println!("{bar} {foo}", foo = "hello", bar = "world"); LL + println!("{bar} hello", bar = "world"); - | + | error: literal with an empty format string --> $DIR/print_literal.rs:37:44 @@ -129,7 +129,7 @@ help: try this | LL - println!("{bar} {foo}", foo = "hello", bar = "world"); LL + println!("world {foo}", foo = "hello"); - | + | error: aborting due to 11 previous errors diff --git a/tests/ui/print_with_newline.stderr b/tests/ui/print_with_newline.stderr index d409bee30ec..edbaa1cdf97 100644 --- a/tests/ui/print_with_newline.stderr +++ b/tests/ui/print_with_newline.stderr @@ -9,7 +9,7 @@ help: use `println!` instead | LL - print!("Hello/n"); LL + println!("Hello"); - | + | error: using `print!()` with a format string that ends in a single newline --> $DIR/print_with_newline.rs:9:5 @@ -21,7 +21,7 @@ help: use `println!` instead | LL - print!("Hello {}/n", "world"); LL + println!("Hello {}", "world"); - | + | error: using `print!()` with a format string that ends in a single newline --> $DIR/print_with_newline.rs:10:5 @@ -33,7 +33,7 @@ help: use `println!` instead | LL - print!("Hello {} {}/n", "world", "#2"); LL + println!("Hello {} {}", "world", "#2"); - | + | error: using `print!()` with a format string that ends in a single newline --> $DIR/print_with_newline.rs:11:5 @@ -45,7 +45,7 @@ help: use `println!` instead | LL - print!("{}/n", 1265); LL + println!("{}", 1265); - | + | error: using `print!()` with a format string that ends in a single newline --> $DIR/print_with_newline.rs:12:5 @@ -57,7 +57,7 @@ help: use `println!` instead | LL - print!("/n"); LL + println!(); - | + | error: using `print!()` with a format string that ends in a single newline --> $DIR/print_with_newline.rs:31:5 @@ -69,7 +69,7 @@ help: use `println!` instead | LL - print!("//n"); // should fail LL + println!("/"); // should fail - | + | error: using `print!()` with a format string that ends in a single newline --> $DIR/print_with_newline.rs:38:5 @@ -111,7 +111,7 @@ help: use `println!` instead | LL - print!("/r/n"); //~ ERROR LL + println!("/r"); //~ ERROR - | + | error: using `print!()` with a format string that ends in a single newline --> $DIR/print_with_newline.rs:51:5 @@ -123,7 +123,7 @@ help: use `println!` instead | LL - print!("foo/rbar/n") // ~ ERROR LL + println!("foo/rbar") // ~ ERROR - | + | error: aborting due to 10 previous errors diff --git a/tests/ui/unnecessary_iter_cloned.stderr b/tests/ui/unnecessary_iter_cloned.stderr index e44379f8aa0..8f151e620a2 100644 --- a/tests/ui/unnecessary_iter_cloned.stderr +++ b/tests/ui/unnecessary_iter_cloned.stderr @@ -13,7 +13,7 @@ help: remove this `&` | LL - let other = match get_file_path(&t) { LL + let other = match get_file_path(t) { - | + | error: unnecessary use of `copied` --> $DIR/unnecessary_iter_cloned.rs:46:22 @@ -29,7 +29,7 @@ help: remove this `&` | LL - let other = match get_file_path(&t) { LL + let other = match get_file_path(t) { - | + | error: aborting due to 2 previous errors diff --git a/tests/ui/unnecessary_to_owned.stderr b/tests/ui/unnecessary_to_owned.stderr index af7e7b41fb0..243b4599dba 100644 --- a/tests/ui/unnecessary_to_owned.stderr +++ b/tests/ui/unnecessary_to_owned.stderr @@ -489,7 +489,7 @@ help: remove this `&` | LL - let path = match get_file_path(&t) { LL + let path = match get_file_path(t) { - | + | error: unnecessary use of `to_vec` --> $DIR/unnecessary_to_owned.rs:221:14 diff --git a/tests/ui/write_literal.stderr b/tests/ui/write_literal.stderr index 593e9493ec5..3c5ec91d3e0 100644 --- a/tests/ui/write_literal.stderr +++ b/tests/ui/write_literal.stderr @@ -9,7 +9,7 @@ help: try this | LL - write!(v, "Hello {}", "world"); LL + write!(v, "Hello world"); - | + | error: literal with an empty format string --> $DIR/write_literal.rs:31:39 @@ -21,7 +21,7 @@ help: try this | LL - writeln!(v, "Hello {} {}", world, "world"); LL + writeln!(v, "Hello {} world", world); - | + | error: literal with an empty format string --> $DIR/write_literal.rs:32:29 @@ -33,7 +33,7 @@ help: try this | LL - writeln!(v, "Hello {}", "world"); LL + writeln!(v, "Hello world"); - | + | error: literal with an empty format string --> $DIR/write_literal.rs:37:28 @@ -45,7 +45,7 @@ help: try this | LL - writeln!(v, "{0} {1}", "hello", "world"); LL + writeln!(v, "hello {1}", "world"); - | + | error: literal with an empty format string --> $DIR/write_literal.rs:37:37 @@ -57,7 +57,7 @@ help: try this | LL - writeln!(v, "{0} {1}", "hello", "world"); LL + writeln!(v, "{0} world", "hello"); - | + | error: literal with an empty format string --> $DIR/write_literal.rs:38:28 @@ -69,7 +69,7 @@ help: try this | LL - writeln!(v, "{1} {0}", "hello", "world"); LL + writeln!(v, "{1} hello", "world"); - | + | error: literal with an empty format string --> $DIR/write_literal.rs:38:37 @@ -81,7 +81,7 @@ help: try this | LL - writeln!(v, "{1} {0}", "hello", "world"); LL + writeln!(v, "world {0}", "hello"); - | + | error: literal with an empty format string --> $DIR/write_literal.rs:41:32 @@ -93,7 +93,7 @@ help: try this | LL - writeln!(v, "{foo} {bar}", foo = "hello", bar = "world"); LL + writeln!(v, "hello {bar}", bar = "world"); - | + | error: literal with an empty format string --> $DIR/write_literal.rs:41:47 @@ -105,7 +105,7 @@ help: try this | LL - writeln!(v, "{foo} {bar}", foo = "hello", bar = "world"); LL + writeln!(v, "{foo} world", foo = "hello"); - | + | error: literal with an empty format string --> $DIR/write_literal.rs:42:32 @@ -117,7 +117,7 @@ help: try this | LL - writeln!(v, "{bar} {foo}", foo = "hello", bar = "world"); LL + writeln!(v, "{bar} hello", bar = "world"); - | + | error: literal with an empty format string --> $DIR/write_literal.rs:42:47 @@ -129,7 +129,7 @@ help: try this | LL - writeln!(v, "{bar} {foo}", foo = "hello", bar = "world"); LL + writeln!(v, "world {foo}", foo = "hello"); - | + | error: aborting due to 11 previous errors diff --git a/tests/ui/write_literal_2.stderr b/tests/ui/write_literal_2.stderr index fc40fbfa9e2..9ff297069c4 100644 --- a/tests/ui/write_literal_2.stderr +++ b/tests/ui/write_literal_2.stderr @@ -9,7 +9,7 @@ help: try this | LL - writeln!(v, "{}", "{hello}"); LL + writeln!(v, "{{hello}}"); - | + | error: literal with an empty format string --> $DIR/write_literal_2.rs:10:24 @@ -21,7 +21,7 @@ help: try this | LL - writeln!(v, r"{}", r"{hello}"); LL + writeln!(v, r"{{hello}}"); - | + | error: literal with an empty format string --> $DIR/write_literal_2.rs:11:23 @@ -33,7 +33,7 @@ help: try this | LL - writeln!(v, "{}", '/''); LL + writeln!(v, "'"); - | + | error: literal with an empty format string --> $DIR/write_literal_2.rs:12:23 @@ -45,7 +45,7 @@ help: try this | LL - writeln!(v, "{}", '"'); LL + writeln!(v, "/""); - | + | error: literal with an empty format string --> $DIR/write_literal_2.rs:14:24 @@ -57,7 +57,7 @@ help: try this | LL - writeln!(v, r"{}", '/''); LL + writeln!(v, r"'"); - | + | error: literal with an empty format string --> $DIR/write_literal_2.rs:18:9 diff --git a/tests/ui/write_with_newline.stderr b/tests/ui/write_with_newline.stderr index 3314a2a6e24..5f55431be0b 100644 --- a/tests/ui/write_with_newline.stderr +++ b/tests/ui/write_with_newline.stderr @@ -9,7 +9,7 @@ help: use `writeln!()` instead | LL - write!(v, "Hello/n"); LL + writeln!(v, "Hello"); - | + | error: using `write!()` with a format string that ends in a single newline --> $DIR/write_with_newline.rs:14:5 @@ -21,7 +21,7 @@ help: use `writeln!()` instead | LL - write!(v, "Hello {}/n", "world"); LL + writeln!(v, "Hello {}", "world"); - | + | error: using `write!()` with a format string that ends in a single newline --> $DIR/write_with_newline.rs:15:5 @@ -33,7 +33,7 @@ help: use `writeln!()` instead | LL - write!(v, "Hello {} {}/n", "world", "#2"); LL + writeln!(v, "Hello {} {}", "world", "#2"); - | + | error: using `write!()` with a format string that ends in a single newline --> $DIR/write_with_newline.rs:16:5 @@ -45,7 +45,7 @@ help: use `writeln!()` instead | LL - write!(v, "{}/n", 1265); LL + writeln!(v, "{}", 1265); - | + | error: using `write!()` with a format string that ends in a single newline --> $DIR/write_with_newline.rs:17:5 @@ -57,7 +57,7 @@ help: use `writeln!()` instead | LL - write!(v, "/n"); LL + writeln!(v); - | + | error: using `write!()` with a format string that ends in a single newline --> $DIR/write_with_newline.rs:36:5 @@ -69,7 +69,7 @@ help: use `writeln!()` instead | LL - write!(v, "//n"); // should fail LL + writeln!(v, "/"); // should fail - | + | error: using `write!()` with a format string that ends in a single newline --> $DIR/write_with_newline.rs:43:5 @@ -115,7 +115,7 @@ help: use `writeln!()` instead | LL - write!(v, "/r/n"); //~ ERROR LL + writeln!(v, "/r"); //~ ERROR - | + | error: using `write!()` with a format string that ends in a single newline --> $DIR/write_with_newline.rs:58:5 @@ -127,7 +127,7 @@ help: use `writeln!()` instead | LL - write!(v, "foo/rbar/n"); LL + writeln!(v, "foo/rbar"); - | + | error: aborting due to 10 previous errors -- cgit 1.4.1-3-g733a5 From 4d889935443b7d5cd3c33b432c920807b1387340 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Thu, 16 Jun 2022 18:00:32 +0400 Subject: bless clippy ui tests --- tests/ui/bind_instead_of_map_multipart.stderr | 18 ++++++++++ .../shared_at_top_and_bottom.stderr | 3 +- tests/ui/entry.stderr | 41 ++++++++++++++++++---- tests/ui/entry_with_else.stderr | 17 ++++++--- tests/ui/let_unit.stderr | 3 +- tests/ui/manual_async_fn.stderr | 9 ++++- tests/ui/needless_for_each_unfixable.stderr | 3 +- tests/ui/new_without_default.stderr | 3 +- tests/ui/ptr_arg.stderr | 3 +- tests/ui/significant_drop_in_scrutinee.stderr | 9 +++-- tests/ui/unit_arg.stderr | 8 ++++- tests/ui/unnecessary_wraps.stderr | 6 ++-- 12 files changed, 102 insertions(+), 21 deletions(-) diff --git a/tests/ui/bind_instead_of_map_multipart.stderr b/tests/ui/bind_instead_of_map_multipart.stderr index f822b6f49fa..0152a93feee 100644 --- a/tests/ui/bind_instead_of_map_multipart.stderr +++ b/tests/ui/bind_instead_of_map_multipart.stderr @@ -56,7 +56,25 @@ LL | if s == "43" { LL ~ return 43; LL | } LL | s == "42" +LL | } { +LL ~ return 45; +LL | } +LL | match s.len() { +LL ~ 10 => 2, +LL | 20 => { ... +LL | if foo() { +LL ~ return 20; +LL | } +LL | println!("foo"); +LL ~ 3 +LL | }; +LL | } +LL ~ 20 +LL | }, +LL ~ 40 => 30, +LL ~ _ => 1, + | error: using `Option.and_then(|x| Some(y))`, which is more succinctly expressed as `map(|x| y)` --> $DIR/bind_instead_of_map_multipart.rs:61:13 diff --git a/tests/ui/branches_sharing_code/shared_at_top_and_bottom.stderr b/tests/ui/branches_sharing_code/shared_at_top_and_bottom.stderr index 1db2343d3fe..37fe2f76f4a 100644 --- a/tests/ui/branches_sharing_code/shared_at_top_and_bottom.stderr +++ b/tests/ui/branches_sharing_code/shared_at_top_and_bottom.stderr @@ -98,7 +98,8 @@ LL + id: e_id, LL + name: "Player 1".to_string(), LL + some_data: vec![0x12, 0x34, 0x56, 0x78, 0x90], LL + }; - ... +LL + process_data(pack); + | error: all if blocks contain the same code at the start and the end. Here at the start --> $DIR/shared_at_top_and_bottom.rs:94:5 diff --git a/tests/ui/entry.stderr b/tests/ui/entry.stderr index 1076500498d..2ef9966525c 100644 --- a/tests/ui/entry.stderr +++ b/tests/ui/entry.stderr @@ -28,7 +28,8 @@ LL + v LL + } else { LL + v2 LL + } - ... +LL + }); + | error: usage of `contains_key` followed by `insert` on a `HashMap` --> $DIR/entry.rs:38:5 @@ -50,7 +51,8 @@ LL + v LL + } else { LL + v2 LL + } - ... +LL + }); + | error: usage of `contains_key` followed by `insert` on a `HashMap` --> $DIR/entry.rs:47:5 @@ -72,7 +74,9 @@ LL + e.insert(v); LL + } else { LL + e.insert(v2); LL + return; - ... +LL + } +LL + } + | error: usage of `contains_key` followed by `insert` on a `HashMap` --> $DIR/entry.rs:57:5 @@ -111,7 +115,11 @@ LL + 1 if true => { LL + v LL + }, LL + _ => { - ... +LL + v2 +LL + }, +LL + } +LL + }); + | error: usage of `contains_key` followed by `insert` on a `HashMap` --> $DIR/entry.rs:75:5 @@ -133,7 +141,9 @@ LL + 0 => foo(), LL + _ => { LL + e.insert(v2); LL + }, - ... +LL + }; +LL + } + | error: usage of `contains_key` followed by `insert` on a `HashMap` --> $DIR/entry.rs:85:5 @@ -155,7 +165,26 @@ LL + match 0 { LL + 0 if false => { LL + v LL + }, - ... +LL + 1 => { +LL + foo(); +LL + v +LL + }, +LL + 2 | 3 => { +LL + for _ in 0..2 { +LL + foo(); +LL + } +LL + if true { +LL + v +LL + } else { +LL + v2 +LL + } +LL + }, +LL + _ => { +LL + v2 +LL + }, +LL + } +LL + }); + | error: usage of `contains_key` followed by `insert` on a `HashMap` --> $DIR/entry.rs:119:5 diff --git a/tests/ui/entry_with_else.stderr b/tests/ui/entry_with_else.stderr index 7279efc5959..e0f6671b460 100644 --- a/tests/ui/entry_with_else.stderr +++ b/tests/ui/entry_with_else.stderr @@ -17,7 +17,9 @@ LL + e.insert(v); LL + } LL + std::collections::hash_map::Entry::Occupied(mut e) => { LL + e.insert(v2); - ... +LL + } +LL + } + | error: usage of `contains_key` followed by `insert` on a `HashMap` --> $DIR/entry_with_else.rs:22:5 @@ -37,7 +39,9 @@ LL + e.insert(v); LL + } LL + std::collections::hash_map::Entry::Vacant(e) => { LL + e.insert(v2); - ... +LL + } +LL + } + | error: usage of `contains_key` followed by `insert` on a `HashMap` --> $DIR/entry_with_else.rs:28:5 @@ -95,7 +99,9 @@ LL + e.insert(v); LL + } LL + std::collections::hash_map::Entry::Occupied(mut e) => { LL + e.insert(v2); - ... +LL + } +LL + } + | error: usage of `contains_key` followed by `insert` on a `HashMap` --> $DIR/entry_with_else.rs:46:5 @@ -115,7 +121,10 @@ LL + if true { Some(e.insert(v)) } else { Some(e.insert(v2)) } LL + } LL + std::collections::hash_map::Entry::Vacant(e) => { LL + e.insert(v); - ... +LL + None +LL + } +LL ~ }; + | error: usage of `contains_key` followed by `insert` on a `HashMap` --> $DIR/entry_with_else.rs:52:5 diff --git a/tests/ui/let_unit.stderr b/tests/ui/let_unit.stderr index 13ec11a6d33..45bf67acdb7 100644 --- a/tests/ui/let_unit.stderr +++ b/tests/ui/let_unit.stderr @@ -32,7 +32,8 @@ LL + .map(|i| i * 2) LL + .filter(|i| i % 2 == 0) LL + .map(|_| ()) LL + .next() - ... +LL + .unwrap(); + | error: this let-binding has unit value --> $DIR/let_unit.rs:80:5 diff --git a/tests/ui/manual_async_fn.stderr b/tests/ui/manual_async_fn.stderr index 7435f46074c..0a903ed6fd4 100644 --- a/tests/ui/manual_async_fn.stderr +++ b/tests/ui/manual_async_fn.stderr @@ -122,7 +122,14 @@ LL + let a = 42; LL + let b = 21; LL + if a < b { LL + let c = 21; - ... +LL + let d = 42; +LL + if c < d { +LL + let _ = 42; +LL + } +LL + } +LL + 42 +LL + } + | error: this function can be simplified using the `async fn` syntax --> $DIR/manual_async_fn.rs:92:1 diff --git a/tests/ui/needless_for_each_unfixable.stderr b/tests/ui/needless_for_each_unfixable.stderr index f607e0a430e..7893ff31a6f 100644 --- a/tests/ui/needless_for_each_unfixable.stderr +++ b/tests/ui/needless_for_each_unfixable.stderr @@ -19,7 +19,8 @@ LL + return; LL + } else { LL + println!("{}", v); LL + } - ... +LL + } + | help: ...and replace `return` with `continue` | LL | continue; diff --git a/tests/ui/new_without_default.stderr b/tests/ui/new_without_default.stderr index 19572dfe8b0..212a69ab94e 100644 --- a/tests/ui/new_without_default.stderr +++ b/tests/ui/new_without_default.stderr @@ -117,7 +117,8 @@ LL + Self::new() LL + } LL + } LL + - ... +LL ~ impl Foo { + | error: aborting due to 7 previous errors diff --git a/tests/ui/ptr_arg.stderr b/tests/ui/ptr_arg.stderr index a9613daadde..7ec4a566ff3 100644 --- a/tests/ui/ptr_arg.stderr +++ b/tests/ui/ptr_arg.stderr @@ -56,7 +56,8 @@ LL | let f = e.clone(); // OK LL | let g = x; LL ~ let h = g.to_owned(); LL | let i = (e).clone(); - ... +LL ~ x.to_owned() + | error: writing `&String` instead of `&str` involves a new object where a slice will do --> $DIR/ptr_arg.rs:57:18 diff --git a/tests/ui/significant_drop_in_scrutinee.stderr b/tests/ui/significant_drop_in_scrutinee.stderr index 303f3c1df03..5ce95204416 100644 --- a/tests/ui/significant_drop_in_scrutinee.stderr +++ b/tests/ui/significant_drop_in_scrutinee.stderr @@ -188,7 +188,9 @@ LL + _ => mutex2.lock().unwrap(), LL + } LL + .s LL + .len() - ... +LL + > 1; +LL ~ match value + | error: temporary with significant drop in match scrutinee --> $DIR/significant_drop_in_scrutinee.rs:397:11 @@ -211,7 +213,10 @@ LL + } else { LL + mutex2.lock().unwrap() LL + } LL + .s - ... +LL + .len() +LL + > 1; +LL ~ match value + | error: temporary with significant drop in match scrutinee --> $DIR/significant_drop_in_scrutinee.rs:451:11 diff --git a/tests/ui/unit_arg.stderr b/tests/ui/unit_arg.stderr index 394dee29dc9..11cfe66a30e 100644 --- a/tests/ui/unit_arg.stderr +++ b/tests/ui/unit_arg.stderr @@ -137,7 +137,13 @@ LL + foo(1); LL + }; LL + { LL + foo(2); - ... +LL + foo(3); +LL + }; +LL + taking_multiple_units( +LL + (), +LL + (), +LL ~ ); + | error: passing a unit value to a function --> $DIR/unit_arg.rs:85:13 diff --git a/tests/ui/unnecessary_wraps.stderr b/tests/ui/unnecessary_wraps.stderr index 8e31db39502..a6a0b22cf68 100644 --- a/tests/ui/unnecessary_wraps.stderr +++ b/tests/ui/unnecessary_wraps.stderr @@ -23,7 +23,8 @@ LL | if a { LL | Some(-1); LL ~ 2 LL | } else { - ... +LL ~ return 1337; + | error: this function's return value is unnecessarily wrapped by `Option` --> $DIR/unnecessary_wraps.rs:21:1 @@ -122,7 +123,8 @@ LL | if a { LL | Some(()); LL ~ LL | } else { - ... +LL ~ return ; + | error: this function's return value is unnecessary --> $DIR/unnecessary_wraps.rs:117:1 -- cgit 1.4.1-3-g733a5 From f8f9d01c2ad0dff565bdd60feeb4cbd09dada8cd Mon Sep 17 00:00:00 2001 From: flip1995 Date: Thu, 16 Jun 2022 17:39:06 +0200 Subject: Merge commit 'd7b5cbf065b88830ca519adcb73fad4c0d24b1c7' into clippyup --- .github/workflows/clippy_bors.yml | 19 + .github/workflows/remark.yml | 9 + .gitignore | 3 + CHANGELOG.md | 2 + CONTRIBUTING.md | 168 +---- Cargo.toml | 2 +- book/README.md | 4 + book/book.toml | 28 + book/src/README.md | 34 + book/src/SUMMARY.md | 23 + book/src/configuration.md | 92 +++ book/src/continuous_integration/README.md | 18 + book/src/continuous_integration/github_actions.md | 21 + book/src/continuous_integration/travis.md | 20 + book/src/development/README.md | 43 ++ book/src/development/adding_lints.md | 711 +++++++++++++++++++++ book/src/development/basics.md | 188 ++++++ book/src/development/common_tools_writing_lints.md | 279 ++++++++ book/src/development/infrastructure/README.md | 19 + book/src/development/infrastructure/backport.md | 71 ++ book/src/development/infrastructure/book.md | 42 ++ .../development/infrastructure/changelog_update.md | 102 +++ book/src/development/infrastructure/release.md | 142 ++++ book/src/development/infrastructure/sync.md | 123 ++++ book/src/development/proposals/README.md | 11 + book/src/development/proposals/roadmap-2021.md | 235 +++++++ book/src/installation.md | 24 + book/src/lints.md | 105 +++ book/src/usage.md | 151 +++++ clippy_dev/src/lint.rs | 2 +- clippy_dev/src/main.rs | 212 +++--- clippy_dev/src/new_lint.rs | 7 +- clippy_dev/src/serve.rs | 2 +- clippy_dev/src/update_lints.rs | 10 + clippy_lints/Cargo.toml | 4 +- clippy_lints/src/assertions_on_constants.rs | 3 - clippy_lints/src/async_yields_async.rs | 6 +- clippy_lints/src/attrs.rs | 4 +- clippy_lints/src/await_holding_invalid.rs | 2 - clippy_lints/src/bool_assert_comparison.rs | 5 +- clippy_lints/src/borrow_deref_ref.rs | 13 +- clippy_lints/src/casts/mod.rs | 36 +- clippy_lints/src/checked_conversions.rs | 15 +- clippy_lints/src/collapsible_if.rs | 12 +- clippy_lints/src/comparison_chain.rs | 1 - clippy_lints/src/copies.rs | 675 ++++++++----------- clippy_lints/src/create_dir.rs | 6 +- clippy_lints/src/dbg_macro.rs | 5 +- clippy_lints/src/default.rs | 11 +- clippy_lints/src/default_numeric_fallback.rs | 15 +- clippy_lints/src/dereference.rs | 61 +- clippy_lints/src/derivable_impls.rs | 4 +- clippy_lints/src/derive.rs | 97 +-- clippy_lints/src/doc.rs | 6 +- clippy_lints/src/enum_variants.rs | 3 +- clippy_lints/src/eq_op.rs | 10 +- clippy_lints/src/eta_reduction.rs | 8 +- clippy_lints/src/excessive_bools.rs | 3 +- clippy_lints/src/float_literal.rs | 5 +- clippy_lints/src/functions/mod.rs | 5 +- clippy_lints/src/get_first.rs | 3 +- clippy_lints/src/implicit_saturating_sub.rs | 16 +- clippy_lints/src/index_refutable_slice.rs | 2 +- clippy_lints/src/indexing_slicing.rs | 43 +- clippy_lints/src/infinite_iter.rs | 1 + clippy_lints/src/inherent_to_string.rs | 12 +- clippy_lints/src/int_plus_one.rs | 3 +- clippy_lints/src/integer_division.rs | 5 +- clippy_lints/src/items_after_statements.rs | 3 +- clippy_lints/src/large_const_arrays.rs | 5 +- clippy_lints/src/large_enum_variant.rs | 4 +- clippy_lints/src/large_include_file.rs | 3 +- clippy_lints/src/let_underscore.rs | 24 +- clippy_lints/src/lib.register_all.rs | 2 + clippy_lints/src/lib.register_correctness.rs | 1 + clippy_lints/src/lib.register_lints.rs | 2 + clippy_lints/src/lib.register_style.rs | 1 + clippy_lints/src/lib.rs | 11 +- clippy_lints/src/lifetimes.rs | 12 +- clippy_lints/src/literal_representation.rs | 29 +- clippy_lints/src/loops/for_loops_over_fallibles.rs | 40 +- clippy_lints/src/loops/mod.rs | 34 +- clippy_lints/src/loops/never_loop.rs | 12 +- clippy_lints/src/matches/match_same_arms.rs | 11 +- clippy_lints/src/matches/mod.rs | 68 +- clippy_lints/src/methods/filter_map.rs | 27 +- clippy_lints/src/methods/iter_overeager_cloned.rs | 97 ++- clippy_lints/src/methods/mod.rs | 230 ++++--- clippy_lints/src/misc.rs | 30 +- clippy_lints/src/misc_early/mod.rs | 71 +- clippy_lints/src/mixed_read_write_in_expression.rs | 6 +- clippy_lints/src/mut_reference.rs | 15 +- clippy_lints/src/mutex_atomic.rs | 11 +- clippy_lints/src/needless_bool.rs | 11 +- clippy_lints/src/needless_borrowed_ref.rs | 9 +- clippy_lints/src/needless_late_init.rs | 16 +- .../src/needless_parens_on_range_literals.rs | 87 +++ clippy_lints/src/needless_update.rs | 7 +- clippy_lints/src/neg_cmp_op_on_partial_ord.rs | 14 +- clippy_lints/src/neg_multiply.rs | 9 +- clippy_lints/src/non_copy_const.rs | 14 +- clippy_lints/src/octal_escapes.rs | 7 +- clippy_lints/src/only_used_in_recursion.rs | 2 +- clippy_lints/src/pass_by_ref_or_value.rs | 12 +- clippy_lints/src/ptr.rs | 22 +- clippy_lints/src/ranges.rs | 45 +- clippy_lints/src/read_zero_byte_vec.rs | 142 ++++ clippy_lints/src/redundant_closure_call.rs | 11 +- clippy_lints/src/redundant_slicing.rs | 2 +- clippy_lints/src/reference.rs | 5 +- clippy_lints/src/return_self_not_must_use.rs | 14 +- clippy_lints/src/same_name_method.rs | 18 +- clippy_lints/src/shadow.rs | 15 +- clippy_lints/src/single_char_lifetime_names.rs | 2 +- clippy_lints/src/slow_vector_initialization.rs | 7 +- clippy_lints/src/strings.rs | 16 +- clippy_lints/src/trait_bounds.rs | 3 +- clippy_lints/src/transmute/useless_transmute.rs | 7 +- clippy_lints/src/unicode.rs | 3 +- clippy_lints/src/unused_async.rs | 5 +- clippy_lints/src/useless_conversion.rs | 5 +- clippy_lints/src/utils/conf.rs | 74 ++- clippy_lints/src/utils/internal_lints.rs | 26 +- .../src/utils/internal_lints/metadata_collector.rs | 232 ++++++- clippy_lints/src/vec.rs | 8 +- clippy_lints/src/wildcard_imports.rs | 14 +- clippy_lints/src/write.rs | 27 +- clippy_lints/src/zero_div_zero.rs | 5 +- clippy_utils/src/consts.rs | 4 +- clippy_utils/src/hir_utils.rs | 14 +- clippy_utils/src/lib.rs | 4 +- clippy_utils/src/ty.rs | 4 +- doc/adding_lints.md | 697 -------------------- doc/backport.md | 71 -- doc/basics.md | 174 ----- doc/changelog_update.md | 97 --- doc/common_tools_writing_lints.md | 266 -------- doc/release.md | 145 ----- doc/roadmap-2021.md | 235 ------- lintcheck/src/config.rs | 64 +- rust-toolchain | 2 +- tests/dogfood.rs | 4 +- tests/ui-toml/bad_toml/conf_bad_toml.rs | 2 - tests/ui-toml/bad_toml_type/conf_bad_type.rs | 3 - .../blacklisted_names_append/blacklisted_names.rs | 10 + .../blacklisted_names.stderr | 16 + tests/ui-toml/blacklisted_names_append/clippy.toml | 1 + .../blacklisted_names_replace/blacklisted_names.rs | 10 + .../blacklisted_names.stderr | 10 + .../ui-toml/blacklisted_names_replace/clippy.toml | 1 + .../conf_deprecated_key/conf_deprecated_key.rs | 3 - tests/ui-toml/doc_valid_idents_append/clippy.toml | 1 + .../doc_valid_idents_append/doc_markdown.rs | 12 + .../doc_valid_idents_append/doc_markdown.stderr | 14 + tests/ui-toml/doc_valid_idents_replace/clippy.toml | 1 + .../doc_valid_idents_replace/doc_markdown.rs | 12 + .../doc_valid_idents_replace/doc_markdown.stderr | 36 ++ .../conf_no_false_negatives.rs | 2 - tests/ui-toml/toml_unknown_key/conf_unknown_key.rs | 2 - tests/ui/almost_complete_letter_range.fixed | 1 + tests/ui/almost_complete_letter_range.rs | 1 + tests/ui/almost_complete_letter_range.stderr | 24 +- tests/ui/async_yields_async.fixed | 13 +- tests/ui/async_yields_async.rs | 13 +- tests/ui/branches_sharing_code/false_positives.rs | 15 +- .../branches_sharing_code/shared_at_bottom.stderr | 28 +- .../ui/branches_sharing_code/shared_at_top.stderr | 18 +- .../shared_at_top_and_bottom.stderr | 48 +- tests/ui/checked_conversions.fixed | 5 + tests/ui/checked_conversions.rs | 5 + tests/ui/default_numeric_fallback_i32.fixed | 6 + tests/ui/default_numeric_fallback_i32.rs | 6 + tests/ui/default_numeric_fallback_i32.stderr | 50 +- tests/ui/derive_partial_eq_without_eq.fixed | 60 +- tests/ui/derive_partial_eq_without_eq.rs | 56 +- tests/ui/derive_partial_eq_without_eq.stderr | 26 +- tests/ui/eta.fixed | 12 + tests/ui/eta.rs | 12 + tests/ui/for_loops_over_fallibles.rs | 17 +- tests/ui/for_loops_over_fallibles.stderr | 40 +- tests/ui/iter_overeager_cloned.fixed | 6 +- tests/ui/iter_overeager_cloned.rs | 3 + tests/ui/iter_overeager_cloned.stderr | 42 +- tests/ui/manual_filter_map.fixed | 50 ++ tests/ui/manual_filter_map.rs | 59 ++ tests/ui/manual_filter_map.stderr | 74 ++- tests/ui/manual_find_map.fixed | 50 ++ tests/ui/manual_find_map.rs | 59 ++ tests/ui/manual_find_map.stderr | 74 ++- tests/ui/manual_map_option.fixed | 1 + tests/ui/manual_map_option.rs | 1 + tests/ui/manual_map_option.stderr | 42 +- tests/ui/needless_borrow.fixed | 9 + tests/ui/needless_borrow.rs | 9 + tests/ui/needless_borrow.stderr | 32 +- tests/ui/needless_parens_on_range_literals.fixed | 14 + tests/ui/needless_parens_on_range_literals.rs | 14 + tests/ui/needless_parens_on_range_literals.stderr | 40 ++ tests/ui/never_loop.rs | 17 + tests/ui/read_zero_byte_vec.rs | 87 +++ tests/ui/read_zero_byte_vec.stderr | 64 ++ tests/ui/ref_binding_to_reference.rs | 10 + tests/ui/ref_binding_to_reference.stderr | 14 +- tests/ui/same_name_method.rs | 16 + tests/ui/same_name_method.stderr | 20 +- util/gh-pages/index.html | 20 + 206 files changed, 5547 insertions(+), 3320 deletions(-) create mode 100644 book/README.md create mode 100644 book/book.toml create mode 100644 book/src/README.md create mode 100644 book/src/SUMMARY.md create mode 100644 book/src/configuration.md create mode 100644 book/src/continuous_integration/README.md create mode 100644 book/src/continuous_integration/github_actions.md create mode 100644 book/src/continuous_integration/travis.md create mode 100644 book/src/development/README.md create mode 100644 book/src/development/adding_lints.md create mode 100644 book/src/development/basics.md create mode 100644 book/src/development/common_tools_writing_lints.md create mode 100644 book/src/development/infrastructure/README.md create mode 100644 book/src/development/infrastructure/backport.md create mode 100644 book/src/development/infrastructure/book.md create mode 100644 book/src/development/infrastructure/changelog_update.md create mode 100644 book/src/development/infrastructure/release.md create mode 100644 book/src/development/infrastructure/sync.md create mode 100644 book/src/development/proposals/README.md create mode 100644 book/src/development/proposals/roadmap-2021.md create mode 100644 book/src/installation.md create mode 100644 book/src/lints.md create mode 100644 book/src/usage.md create mode 100644 clippy_lints/src/needless_parens_on_range_literals.rs create mode 100644 clippy_lints/src/read_zero_byte_vec.rs delete mode 100644 doc/adding_lints.md delete mode 100644 doc/backport.md delete mode 100644 doc/basics.md delete mode 100644 doc/changelog_update.md delete mode 100644 doc/common_tools_writing_lints.md delete mode 100644 doc/release.md delete mode 100644 doc/roadmap-2021.md create mode 100644 tests/ui-toml/blacklisted_names_append/blacklisted_names.rs create mode 100644 tests/ui-toml/blacklisted_names_append/blacklisted_names.stderr create mode 100644 tests/ui-toml/blacklisted_names_append/clippy.toml create mode 100644 tests/ui-toml/blacklisted_names_replace/blacklisted_names.rs create mode 100644 tests/ui-toml/blacklisted_names_replace/blacklisted_names.stderr create mode 100644 tests/ui-toml/blacklisted_names_replace/clippy.toml create mode 100644 tests/ui-toml/doc_valid_idents_append/clippy.toml create mode 100644 tests/ui-toml/doc_valid_idents_append/doc_markdown.rs create mode 100644 tests/ui-toml/doc_valid_idents_append/doc_markdown.stderr create mode 100644 tests/ui-toml/doc_valid_idents_replace/clippy.toml create mode 100644 tests/ui-toml/doc_valid_idents_replace/doc_markdown.rs create mode 100644 tests/ui-toml/doc_valid_idents_replace/doc_markdown.stderr create mode 100644 tests/ui/needless_parens_on_range_literals.fixed create mode 100644 tests/ui/needless_parens_on_range_literals.rs create mode 100644 tests/ui/needless_parens_on_range_literals.stderr create mode 100644 tests/ui/read_zero_byte_vec.rs create mode 100644 tests/ui/read_zero_byte_vec.stderr diff --git a/.github/workflows/clippy_bors.yml b/.github/workflows/clippy_bors.yml index 9b3fd3ddfeb..b8ea424ef34 100644 --- a/.github/workflows/clippy_bors.yml +++ b/.github/workflows/clippy_bors.yml @@ -143,6 +143,25 @@ jobs: env: OS: ${{ runner.os }} + metadata_collection: + needs: base + runs-on: ubuntu-latest + + steps: + # Setup + - uses: rust-lang/simpleinfra/github-actions/cancel-outdated-builds@master + with: + github_token: "${{ secrets.github_token }}" + + - name: Checkout + uses: actions/checkout@v3.0.2 + + - name: Install toolchain + run: rustup show active-toolchain + + - name: Test metadata collection + run: cargo collect-metadata + integration_build: needs: changelog runs-on: ubuntu-latest diff --git a/.github/workflows/remark.yml b/.github/workflows/remark.yml index a179bfa7261..ff471207b65 100644 --- a/.github/workflows/remark.yml +++ b/.github/workflows/remark.yml @@ -26,10 +26,19 @@ jobs: - name: Install remark run: npm install remark-cli remark-lint remark-lint-maximum-line-length remark-preset-lint-recommended remark-gfm + - name: Install mdbook + run: | + mkdir mdbook + curl -Lf https://github.com/rust-lang/mdBook/releases/download/v0.4.18/mdbook-v0.4.18-x86_64-unknown-linux-gnu.tar.gz | tar -xz --directory=./mdbook + echo `pwd`/mdbook >> $GITHUB_PATH + # Run - name: Check *.md files run: git ls-files -z '*.md' | xargs -0 -n 1 -I {} ./node_modules/.bin/remark {} -u lint -f > /dev/null + - 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. diff --git a/.gitignore b/.gitignore index 3e50c45a9b6..503ae3c5090 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,6 @@ helper.txt *.iml .vscode .idea + +# mdbook generated output +/book/book diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ef338b819d..6aaf12ed932 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3605,6 +3605,7 @@ Released 2018-09-13 [`needless_match`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_match [`needless_option_as_deref`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_option_as_deref [`needless_option_take`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_option_take +[`needless_parens_on_range_literals`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_parens_on_range_literals [`needless_pass_by_value`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_pass_by_value [`needless_question_mark`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_question_mark [`needless_range_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#needless_range_loop @@ -3677,6 +3678,7 @@ Released 2018-09-13 [`rc_buffer`]: https://rust-lang.github.io/rust-clippy/master/index.html#rc_buffer [`rc_clone_in_vec_init`]: https://rust-lang.github.io/rust-clippy/master/index.html#rc_clone_in_vec_init [`rc_mutex`]: https://rust-lang.github.io/rust-clippy/master/index.html#rc_mutex +[`read_zero_byte_vec`]: https://rust-lang.github.io/rust-clippy/master/index.html#read_zero_byte_vec [`recursive_format_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#recursive_format_impl [`redundant_allocation`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_allocation [`redundant_clone`]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_clone diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6ab2bd59137..e81e7ceedcb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -13,19 +13,14 @@ anything, feel free to ask questions on issues or visit the `#clippy` on [Zulip] All contributors are expected to follow the [Rust Code of Conduct]. - [Contributing to Clippy](#contributing-to-clippy) - - [Getting started](#getting-started) - - [High level approach](#high-level-approach) - - [Finding something to fix/improve](#finding-something-to-fiximprove) + - [The Clippy book](#the-clippy-book) + - [High level approach](#high-level-approach) + - [Finding something to fix/improve](#finding-something-to-fiximprove) - [Writing code](#writing-code) - [Getting code-completion for rustc internals to work](#getting-code-completion-for-rustc-internals-to-work) - [IntelliJ Rust](#intellij-rust) - [Rust Analyzer](#rust-analyzer) - [How Clippy works](#how-clippy-works) - - [Syncing changes between Clippy and `rust-lang/rust`](#syncing-changes-between-clippy-and-rust-langrust) - - [Patching git-subtree to work with big repos](#patching-git-subtree-to-work-with-big-repos) - - [Performing the sync from `rust-lang/rust` to Clippy](#performing-the-sync-from-rust-langrust-to-clippy) - - [Performing the sync from Clippy to `rust-lang/rust`](#performing-the-sync-from-clippy-to-rust-langrust) - - [Defining remotes](#defining-remotes) - [Issue and PR triage](#issue-and-pr-triage) - [Bors and Homu](#bors-and-homu) - [Contributions](#contributions) @@ -33,24 +28,29 @@ All contributors are expected to follow the [Rust Code of Conduct]. [Zulip]: https://rust-lang.zulipchat.com/#narrow/stream/clippy [Rust Code of Conduct]: https://www.rust-lang.org/policies/code-of-conduct -## Getting started +## The Clippy book -**Note: If this is your first time contributing to Clippy, you should -first read the [Basics docs](doc/basics.md).** +If you're new to Clippy and don't know where to start the [Clippy book] includes +a developer guide and is a good place to start your journey. -### High level approach + +[Clippy book]: book/src + +## High level approach 1. Find something to fix/improve 2. Change code (likely some file in `clippy_lints/src/`) -3. Follow the instructions in the [Basics docs](doc/basics.md) to get set up +3. Follow the instructions in the [Basics docs](book/src/development/basics.md) + to get set up 4. Run `cargo test` in the root directory and wiggle code until it passes 5. Open a PR (also can be done after 2. if you run into problems) -### Finding something to fix/improve +## Finding something to fix/improve -All issues on Clippy are mentored, if you want help simply ask @Manishearth, @flip1995, @phansch -or @llogiq directly by mentioning them in the issue or over on [Zulip]. This list may be out of date. -All currently active mentors can be found [here](https://github.com/rust-lang/highfive/blob/master/highfive/configs/rust-lang/rust-clippy.json#L3) +All issues on Clippy are mentored, if you want help simply ask someone from the +Clippy team directly by mentioning them in the issue or over on [Zulip]. All +currently active team members can be found +[here](https://github.com/rust-lang/highfive/blob/master/highfive/configs/rust-lang/rust-clippy.json#L3) Some issues are easier than others. The [`good-first-issue`] label can be used to find the easy issues. You can use `@rustbot claim` to assign the issue to yourself. @@ -91,20 +91,6 @@ an AST expression). `match_def_path()` in Clippy's `utils` module can also be us [let chains]: https://github.com/rust-lang/rust/pull/94927 [nest-less]: https://github.com/rust-lang/rust-clippy/blob/5e4f0922911536f80d9591180fa604229ac13939/clippy_lints/src/bit_mask.rs#L133-L159 -## Writing code - -Have a look at the [docs for writing lints][adding_lints] for more details. - -If you want to add a new lint or change existing ones apart from bugfixing, it's -also a good idea to give the [stability guarantees][rfc_stability] and -[lint categories][rfc_lint_cats] sections of the [Clippy 1.0 RFC][clippy_rfc] a -quick read. - -[adding_lints]: https://github.com/rust-lang/rust-clippy/blob/master/doc/adding_lints.md -[clippy_rfc]: https://github.com/rust-lang/rfcs/blob/master/text/2476-clippy-uno.md -[rfc_stability]: https://github.com/rust-lang/rfcs/blob/master/text/2476-clippy-uno.md#stability-guarantees -[rfc_lint_cats]: https://github.com/rust-lang/rfcs/blob/master/text/2476-clippy-uno.md#lint-audit-and-categories - ## Getting code-completion for rustc internals to work ### IntelliJ Rust @@ -205,126 +191,6 @@ That's why the `else_if_without_else` example uses the `register_early_pass` fun [early_lint_pass]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/trait.EarlyLintPass.html [late_lint_pass]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/trait.LateLintPass.html -## Syncing changes between Clippy and [`rust-lang/rust`] - -Clippy currently gets built with a pinned nightly version. - -In the `rust-lang/rust` repository, where rustc resides, there's a copy of Clippy -that compiler hackers modify from time to time to adapt to changes in the unstable -API of the compiler. - -We need to sync these changes back to this repository periodically, and the changes -made to this repository in the meantime also need to be synced to the `rust-lang/rust` repository. - -To avoid flooding the `rust-lang/rust` PR queue, this two-way sync process is done -in a bi-weekly basis if there's no urgent changes. This is done starting on the day of -the Rust stable release and then every other week. That way we guarantee that we keep -this repo up to date with the latest compiler API, and every feature in Clippy is available -for 2 weeks in nightly, before it can get to beta. For reference, the first sync -following this cadence was performed the 2020-08-27. - -This process is described in detail in the following sections. For general information -about `subtree`s in the Rust repository see [Rust's `CONTRIBUTING.md`][subtree]. - -### Patching git-subtree to work with big repos - -Currently, there's a bug in `git-subtree` that prevents it from working properly -with the [`rust-lang/rust`] repo. There's an open PR to fix that, but it's stale. -Before continuing with the following steps, we need to manually apply that fix to -our local copy of `git-subtree`. - -You can get the patched version of `git-subtree` from [here][gitgitgadget-pr]. -Put this file under `/usr/lib/git-core` (taking a backup of the previous file) -and make sure it has the proper permissions: - -```bash -sudo cp --backup /path/to/patched/git-subtree.sh /usr/lib/git-core/git-subtree -sudo chmod --reference=/usr/lib/git-core/git-subtree~ /usr/lib/git-core/git-subtree -sudo chown --reference=/usr/lib/git-core/git-subtree~ /usr/lib/git-core/git-subtree -``` - -_Note:_ The first time running `git subtree push` a cache has to be built. This -involves going through the complete Clippy history once. For this you have to -increase the stack limit though, which you can do with `ulimit -s 60000`. -Make sure to run the `ulimit` command from the same session you call git subtree. - -_Note:_ If you are a Debian user, `dash` is the shell used by default for scripts instead of `sh`. -This shell has a hardcoded recursion limit set to 1000. In order to make this process work, -you need to force the script to run `bash` instead. You can do this by editing the first -line of the `git-subtree` script and changing `sh` to `bash`. - -### Performing the sync from [`rust-lang/rust`] to Clippy - -Here is a TL;DR version of the sync process (all of the following commands have -to be run inside the `rust` directory): - -1. Clone the [`rust-lang/rust`] repository or make sure it is up to date. -2. Checkout the commit from the latest available nightly. You can get it using `rustup check`. -3. Sync the changes to the rust-copy of Clippy to your Clippy fork: - ```bash - # Make sure to change `your-github-name` to your github name in the following command. Also be - # sure to either use a net-new branch, e.g. `sync-from-rust`, or delete the branch beforehand - # because changes cannot be fast forwarded - git subtree push -P src/tools/clippy git@github.com:your-github-name/rust-clippy sync-from-rust - ``` - - _Note:_ This will directly push to the remote repository. You can also push - to your local copy by replacing the remote address with `/path/to/rust-clippy` - directory. - - _Note:_ Most of the time you have to create a merge commit in the - `rust-clippy` repo (this has to be done in the Clippy repo, not in the - rust-copy of Clippy): - ```bash - git fetch origin && git fetch upstream - git checkout sync-from-rust - git merge upstream/master - ``` -4. Open a PR to `rust-lang/rust-clippy` and wait for it to get merged (to - accelerate the process ping the `@rust-lang/clippy` team in your PR and/or - ~~annoy~~ ask them in the [Zulip] stream.) - -### Performing the sync from Clippy to [`rust-lang/rust`] - -All of the following commands have to be run inside the `rust` directory. - -1. Make sure Clippy itself is up-to-date by following the steps outlined in the previous -section if necessary. - -2. Sync the `rust-lang/rust-clippy` master to the rust-copy of Clippy: - ```bash - git checkout -b sync-from-clippy - git subtree pull -P src/tools/clippy https://github.com/rust-lang/rust-clippy master - ``` -3. Open a PR to [`rust-lang/rust`] - -### Defining remotes - -You may want to define remotes, so you don't have to type out the remote -addresses on every sync. You can do this with the following commands (these -commands still have to be run inside the `rust` directory): - -```bash -# Set clippy-upstream remote for pulls -$ git remote add clippy-upstream https://github.com/rust-lang/rust-clippy -# Make sure to not push to the upstream repo -$ git remote set-url --push clippy-upstream DISABLED -# Set clippy-origin remote to your fork for pushes -$ git remote add clippy-origin git@github.com:your-github-name/rust-clippy -# Set a local remote -$ git remote add clippy-local /path/to/rust-clippy -``` - -You can then sync with the remote names from above, e.g.: - -```bash -$ git subtree push -P src/tools/clippy clippy-local sync-from-rust -``` - -[gitgitgadget-pr]: https://github.com/gitgitgadget/git/pull/493 -[subtree]: https://rustc-dev-guide.rust-lang.org/contributing.html#external-dependencies-subtree -[`rust-lang/rust`]: https://github.com/rust-lang/rust - ## Issue and PR triage Clippy is following the [Rust triage procedure][triage] for issues and pull diff --git a/Cargo.toml b/Cargo.toml index 3c8b758d53d..e4060ce29a7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,7 +58,7 @@ rustc_tools_util = { version = "0.2", path = "rustc_tools_util" } [features] deny-warnings = ["clippy_lints/deny-warnings"] integration = ["tempfile"] -internal = ["clippy_lints/internal"] +internal = ["clippy_lints/internal", "tempfile"] [package.metadata.rust-analyzer] # This package uses #[feature(rustc_private)] diff --git a/book/README.md b/book/README.md new file mode 100644 index 00000000000..b652194d0d1 --- /dev/null +++ b/book/README.md @@ -0,0 +1,4 @@ +# Clippy Book + +This is the source for the Clippy Book. See the +[book](src/infrastructure/book.md) for more information. diff --git a/book/book.toml b/book/book.toml new file mode 100644 index 00000000000..93b6641f7e1 --- /dev/null +++ b/book/book.toml @@ -0,0 +1,28 @@ +[book] +authors = ["The Rust Clippy Developers"] +language = "en" +multilingual = false +src = "src" +title = "Clippy Documentation" + +[rust] +edition = "2018" + +[output.html] +edit-url-template = "https://github.com/rust-lang/rust-clippy/edit/master/book/{path}" +git-repository-url = "https://github.com/rust-lang/rust-clippy/tree/master/book" +mathjax-support = true +site-url = "/rust-clippy/" + +[output.html.playground] +editable = true +line-numbers = true + +[output.html.search] +boost-hierarchy = 2 +boost-paragraph = 1 +boost-title = 2 +expand = true +heading-split-level = 2 +limit-results = 20 +use-boolean-and = true diff --git a/book/src/README.md b/book/src/README.md new file mode 100644 index 00000000000..de1f70d7e96 --- /dev/null +++ b/book/src/README.md @@ -0,0 +1,34 @@ +# Clippy + +[![Clippy Test](https://github.com/rust-lang/rust-clippy/workflows/Clippy%20Test/badge.svg?branch=auto&event=push)](https://github.com/rust-lang/rust-clippy/actions?query=workflow%3A%22Clippy+Test%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 500 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. + +| Category | Description | Default level | +| --------------------- | ----------------------------------------------------------------------------------- | ------------- | +| `clippy::all` | all lints that are on by default (correctness, suspicious, style, complexity, perf) | **warn/deny** | +| `clippy::correctness` | code that is outright wrong or useless | **deny** | +| `clippy::suspicious` | code that is most likely wrong or useless | **warn** | +| `clippy::complexity` | code that does something simple but in a complex way | **warn** | +| `clippy::perf` | code that can be written to run faster | **warn** | +| `clippy::style` | code that should be written in a more idiomatic way | **warn** | +| `clippy::pedantic` | lints which are rather strict or might have false positives | allow | +| `clippy::nursery` | new lints that are still under development | allow | +| `clippy::cargo` | lints for the cargo manifest | allow | | allow | + +More to come, please [file an +issue](https://github.com/rust-lang/rust-clippy/issues) if you have ideas! + +The [lint list](https://rust-lang.github.io/rust-clippy/master/index.html) also +contains "restriction lints", which are for things which are usually not +considered "bad", but may be useful to turn on in specific cases. These should +be used very selectively, if at all. diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md new file mode 100644 index 00000000000..0b945faf9b7 --- /dev/null +++ b/book/src/SUMMARY.md @@ -0,0 +1,23 @@ +# Summary + +[Introduction](README.md) + +- [Installation](installation.md) +- [Usage](usage.md) +- [Configuration](configuration.md) +- [Clippy's Lints](lints.md) +- [Continuous Integration](continuous_integration/README.md) + - [GitHub Actions](continuous_integration/github_actions.md) + - [Travis CI](continuous_integration/travis.md) +- [Development](development/README.md) + - [Basics](development/basics.md) + - [Adding Lints](development/adding_lints.md) + - [Common Tools](development/common_tools_writing_lints.md) + - [Infrastructure](development/infrastructure/README.md) + - [Syncing changes between Clippy and rust-lang/rust](development/infrastructure/sync.md) + - [Backporting Changes](development/infrastructure/backport.md) + - [Updating the Changelog](development/infrastructure/changelog_update.md) + - [Release a New Version](development/infrastructure/release.md) + - [The Clippy Book](development/infrastructure/book.md) + - [Proposals](development/proposals/README.md) + - [Roadmap 2021](development/proposals/roadmap-2021.md) diff --git a/book/src/configuration.md b/book/src/configuration.md new file mode 100644 index 00000000000..6e295ac3181 --- /dev/null +++ b/book/src/configuration.md @@ -0,0 +1,92 @@ +# Configuring Clippy + +> **Note:** The configuration file is unstable and may be deprecated in the future. + +Some lints can be configured in a TOML file named `clippy.toml` or `.clippy.toml`. It contains a +basic `variable = value` mapping eg. + +```toml +avoid-breaking-exported-api = false +blacklisted-names = ["toto", "tata", "titi"] +cognitive-complexity-threshold = 30 +``` + +See the [list of lints](https://rust-lang.github.io/rust-clippy/master/index.html) for more information about which +lints can be configured and the meaning of the variables. + +To deactivate the "for further information visit *lint-link*" message you can define the `CLIPPY_DISABLE_DOCS_LINKS` +environment variable. + +### Allowing/denying lints + +You can add options to your code to `allow`/`warn`/`deny` Clippy lints: + +* the whole set of `Warn` lints using the `clippy` lint group (`#![deny(clippy::all)]`) + +* all lints using both the `clippy` and `clippy::pedantic` lint groups (`#![deny(clippy::all)]`, + `#![deny(clippy::pedantic)]`). Note that `clippy::pedantic` contains some very aggressive lints prone to false + positives. + +* only some lints (`#![deny(clippy::single_match, clippy::box_vec)]`, etc.) + +* `allow`/`warn`/`deny` can be limited to a single function or module using `#[allow(...)]`, etc. + +Note: `allow` means to suppress the lint for your code. With `warn` the lint will only emit a warning, while with `deny` +the lint will emit an error, when triggering for your code. An error causes clippy to exit with an error code, so is +useful in scripts like CI/CD. + +If you do not want to include your lint levels in your code, you can globally enable/disable lints by passing extra +flags to Clippy during the run: + +To allow `lint_name`, run + +```terminal +cargo clippy -- -A clippy::lint_name +``` + +And to warn on `lint_name`, run + +```terminal +cargo clippy -- -W clippy::lint_name +``` + +This also works with lint groups. For example you can run Clippy with warnings for all lints enabled: + +```terminal +cargo clippy -- -W clippy::pedantic +``` + +If you care only about a single lint, you can allow all others and then explicitly warn on the lint(s) you are +interested in: + +```terminal +cargo clippy -- -A clippy::all -W clippy::useless_format -W clippy::... +``` + +### Specifying the minimum supported Rust version + +Projects that intend to support old versions of Rust can disable lints pertaining to newer features by specifying the +minimum supported Rust version (MSRV) in the clippy configuration file. + +```toml +msrv = "1.30.0" +``` + +The MSRV can also be specified as an inner attribute, like below. + +```rust +#![feature(custom_inner_attributes)] +#![clippy::msrv = "1.30.0"] + +fn main() { + ... +} +``` + +You can also omit the patch version when specifying the MSRV, so `msrv = 1.30` +is equivalent to `msrv = 1.30.0`. + +Note: `custom_inner_attributes` is an unstable feature so it has to be enabled explicitly. + +Lints that recognize this configuration option can be +found [here](https://rust-lang.github.io/rust-clippy/master/index.html#msrv) diff --git a/book/src/continuous_integration/README.md b/book/src/continuous_integration/README.md new file mode 100644 index 00000000000..e5c3673bde4 --- /dev/null +++ b/book/src/continuous_integration/README.md @@ -0,0 +1,18 @@ +# Continuous Integration + +It is recommended to run Clippy on CI with `-Dwarnings`, so that Clippy lints +prevent CI from passing. To enforce errors on warnings on all `cargo` commands +not just `cargo clippy`, you can set the env var `RUSTFLAGS="-Dwarnings"`. + +We recommend to use Clippy from the same toolchain, that you use for compiling +your crate for maximum compatibility. E.g. if your crate is compiled with the +`stable` toolchain, you should also use `stable` Clippy. + +> _Note:_ New Clippy lints are first added to the `nightly` toolchain. If you +> want to help with improving Clippy and have CI resources left, please consider +> adding a `nightly` Clippy check to your CI and report problems like false +> positives back to us. With that we can fix bugs early, before they can get to +> stable. + +This chapter will give an overview on how to use Clippy on different popular CI +providers. diff --git a/book/src/continuous_integration/github_actions.md b/book/src/continuous_integration/github_actions.md new file mode 100644 index 00000000000..42a43ef1380 --- /dev/null +++ b/book/src/continuous_integration/github_actions.md @@ -0,0 +1,21 @@ +# GitHub Actions + +On the GitHub hosted runners, Clippy from the latest stable Rust version comes +pre-installed. So all you have to do is to run `cargo clippy`. + +```yml +on: push +name: Clippy check + +# Make sure CI fails on all warnings, including Clippy lints +env: + RUSTFLAGS: "-Dwarnings" + +jobs: + clippy_check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Run Clippy + run: cargo clippy --all-targets --all-features +``` diff --git a/book/src/continuous_integration/travis.md b/book/src/continuous_integration/travis.md new file mode 100644 index 00000000000..85b9ed53dae --- /dev/null +++ b/book/src/continuous_integration/travis.md @@ -0,0 +1,20 @@ +# Travis CI + +You can add Clippy to Travis CI in the same way you use it locally: + +```yml +language: rust +rust: + - stable + - beta +before_script: + - rustup component add clippy +script: + - cargo clippy + # if you want the build job to fail when encountering warnings, use + - cargo clippy -- -D warnings + # in order to also check tests and non-default crate features, use + - cargo clippy --all-targets --all-features -- -D warnings + - cargo test + # etc. +``` diff --git a/book/src/development/README.md b/book/src/development/README.md new file mode 100644 index 00000000000..5cf7201cffa --- /dev/null +++ b/book/src/development/README.md @@ -0,0 +1,43 @@ +# Clippy Development + +Hello fellow Rustacean! If you made it here, you're probably interested in +making Clippy better by contributing to it. In that case, welcome to the +project! + +> _Note:_ If you're just interested in using Clippy, there's nothing to see from +> this point onward and you should return to one of the earlier chapters. + +## Getting started + +If this is your first time contributing to Clippy, you should first read the +[Basics docs](basics.md). This will explain the basics on how to get the source +code and how to compile and test the code. + +## Writing code + +If you have done the basic setup, it's time to start hacking. + +The [Adding lints](adding_lints.md) chapter is a walk through on how to add a +new lint to Clippy. This is also interesting if you just want to fix a lint, +because it also covers how to test lints and gives an overview of the bigger +picture. + +If you want to add a new lint or change existing ones apart from bugfixing, it's +also a good idea to give the [stability guarantees][rfc_stability] and +[lint categories][rfc_lint_cats] sections of the [Clippy 1.0 RFC][clippy_rfc] a +quick read. The lint categories are also described [earlier in this +book](../lints.md). + +> _Note:_ Some higher level things about contributing to Clippy are still +> covered in the [`CONTRIBUTING.md`] document. Some of those will be moved to +> the book over time, like: +> - Finding something to fix +> - IDE setup +> - High level overview on how Clippy works +> - Triage procedure +> - Bors and Homu + +[clippy_rfc]: https://github.com/rust-lang/rfcs/blob/master/text/2476-clippy-uno.md +[rfc_stability]: https://github.com/rust-lang/rfcs/blob/master/text/2476-clippy-uno.md#stability-guarantees +[rfc_lint_cats]: https://github.com/rust-lang/rfcs/blob/master/text/2476-clippy-uno.md#lint-audit-and-categories +[`CONTRIBUTING.md`]: https://github.com/rust-lang/rust-clippy/blob/master/CONTRIBUTING.md diff --git a/book/src/development/adding_lints.md b/book/src/development/adding_lints.md new file mode 100644 index 00000000000..3da07fcb968 --- /dev/null +++ b/book/src/development/adding_lints.md @@ -0,0 +1,711 @@ +# Adding a new lint + +You are probably here because you want to add a new lint to Clippy. If this is +the first time you're contributing to Clippy, this document guides you through +creating an example lint from scratch. + +To get started, we will create a lint that detects functions called `foo`, +because that's clearly a non-descriptive name. + +- [Adding a new lint](#adding-a-new-lint) + - [Setup](#setup) + - [Getting Started](#getting-started) + - [Testing](#testing) + - [Cargo lints](#cargo-lints) + - [Rustfix tests](#rustfix-tests) + - [Edition 2018 tests](#edition-2018-tests) + - [Testing manually](#testing-manually) + - [Lint declaration](#lint-declaration) + - [Lint registration](#lint-registration) + - [Lint passes](#lint-passes) + - [Emitting a lint](#emitting-a-lint) + - [Adding the lint logic](#adding-the-lint-logic) + - [Specifying the lint's minimum supported Rust version (MSRV)](#specifying-the-lints-minimum-supported-rust-version-msrv) + - [Author lint](#author-lint) + - [Print HIR lint](#print-hir-lint) + - [Documentation](#documentation) + - [Running rustfmt](#running-rustfmt) + - [Debugging](#debugging) + - [PR Checklist](#pr-checklist) + - [Adding configuration to a lint](#adding-configuration-to-a-lint) + - [Cheat Sheet](#cheat-sheet) + +## Setup + +See the [Basics](basics.md#get-the-code) documentation. + +## Getting Started + +There is a bit of boilerplate code that needs to be set up when creating a new +lint. Fortunately, you can use the clippy dev tools to handle this for you. We +are naming our new lint `foo_functions` (lints are generally written in snake +case), and we don't need type information so it will have an early pass type +(more on this later on). If you're not sure if the name you chose fits the lint, +take a look at our [lint naming guidelines][lint_naming]. To get started on this +lint you can run `cargo dev new_lint --name=foo_functions --pass=early +--category=pedantic` (category will default to nursery if not provided). This +command will create two files: `tests/ui/foo_functions.rs` and +`clippy_lints/src/foo_functions.rs`, as well as [registering the +lint](#lint-registration). For cargo lints, two project hierarchies (fail/pass) +will be created by default under `tests/ui-cargo`. + +Next, we'll open up these files and add our lint! + +## Testing + +Let's write some tests first that we can execute while we iterate on our lint. + +Clippy uses UI tests for testing. UI tests check that the output of Clippy is +exactly as expected. Each test is just a plain Rust file that contains the code +we want to check. The output of Clippy is compared against a `.stderr` file. +Note that you don't have to create this file yourself, we'll get to generating +the `.stderr` files further down. + +We start by opening the test file created at `tests/ui/foo_functions.rs`. + +Update the file with some examples to get started: + +```rust +#![warn(clippy::foo_functions)] + +// Impl methods +struct A; +impl A { + pub fn fo(&self) {} + pub fn foo(&self) {} + pub fn food(&self) {} +} + +// Default trait methods +trait B { + fn fo(&self) {} + fn foo(&self) {} + fn food(&self) {} +} + +// Plain functions +fn fo() {} +fn foo() {} +fn food() {} + +fn main() { + // We also don't want to lint method calls + foo(); + let a = A; + a.foo(); +} +``` + +Now we can run the test with `TESTNAME=foo_functions cargo uitest`, currently +this test is meaningless though. + +While we are working on implementing our lint, we can keep running the UI test. +That allows us to check if the output is turning into what we want. + +Once we are satisfied with the output, we need to run `cargo dev bless` to +update the `.stderr` file for our lint. Please note that, we should run +`TESTNAME=foo_functions cargo uitest` every time before running `cargo dev +bless`. Running `TESTNAME=foo_functions cargo uitest` should pass then. When we +commit our lint, we need to commit the generated `.stderr` files, too. In +general, you should only commit files changed by `cargo dev bless` for the +specific lint you are creating/editing. Note that if the generated files are +empty, they should be removed. + +> _Note:_ you can run multiple test files by specifying a comma separated list: +> `TESTNAME=foo_functions,test2,test3`. + +### Cargo lints + +For cargo lints, the process of testing differs in that we are interested in the +`Cargo.toml` manifest file. We also need a minimal crate associated with that +manifest. + +If our new lint is named e.g. `foo_categories`, after running `cargo dev +new_lint` we will find by default two new crates, each with its manifest file: + +* `tests/ui-cargo/foo_categories/fail/Cargo.toml`: this file should cause the + new lint to raise an error. +* `tests/ui-cargo/foo_categories/pass/Cargo.toml`: this file should not trigger + the lint. + +If you need more cases, you can copy one of those crates (under +`foo_categories`) and rename it. + +The process of generating the `.stderr` file is the same, and prepending the +`TESTNAME` variable to `cargo uitest` works too. + +## Rustfix tests + +If the lint you are working on is making use of structured suggestions, the test +file should include a `// run-rustfix` comment at the top. This will +additionally run [rustfix] for that test. Rustfix will apply the suggestions +from the lint to the code of the test file and compare that to the contents of a +`.fixed` file. + +Use `cargo dev bless` to automatically generate the `.fixed` file after running +the tests. + +[rustfix]: https://github.com/rust-lang/rustfix + +## Testing manually + +Manually testing against an example file can be useful if you have added some +`println!`s and the test suite output becomes unreadable. To try Clippy with +your local modifications, run + +``` +cargo dev lint input.rs +``` + +from the working copy root. With tests in place, let's have a look at +implementing our lint now. + +## Lint declaration + +Let's start by opening the new file created in the `clippy_lints` crate at +`clippy_lints/src/foo_functions.rs`. That's the crate where all the lint code +is. This file has already imported some initial things we will need: + +```rust +use rustc_lint::{EarlyLintPass, EarlyContext}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; +use rustc_ast::ast::*; +``` + +The next step is to update the lint declaration. Lints are declared using the +[`declare_clippy_lint!`][declare_clippy_lint] macro, and we just need to update +the auto-generated lint declaration to have a real description, something like +this: + +```rust +declare_clippy_lint! { + /// ### What it does + /// + /// ### Why is this bad? + /// + /// ### Example + /// ```rust + /// // example code + /// ``` + #[clippy::version = "1.29.0"] + pub FOO_FUNCTIONS, + pedantic, + "function named `foo`, which is not a descriptive name" +} +``` + +* The section of lines prefixed with `///` constitutes the lint documentation + section. This is the default documentation style and will be displayed [like + this][example_lint_page]. To render and open this documentation locally in a + browser, run `cargo dev serve`. +* The `#[clippy::version]` attribute will be rendered as part of the lint + documentation. The value should be set to the current Rust version that the + lint is developed in, it can be retrieved by running `rustc -vV` in the + rust-clippy directory. The version is listed under *release*. (Use the version + without the `-nightly`) suffix. +* `FOO_FUNCTIONS` is the name of our lint. Be sure to follow the [lint naming + guidelines][lint_naming] here when naming your lint. In short, the name should + state the thing that is being checked for and read well when used with + `allow`/`warn`/`deny`. +* `pedantic` sets the lint level to `Allow`. The exact mapping can be found + [here][category_level_mapping] +* The last part should be a text that explains what exactly is wrong with the + code + +The rest of this file contains an empty implementation for our lint pass, which +in this case is `EarlyLintPass` and should look like this: + +```rust +// clippy_lints/src/foo_functions.rs + +// .. imports and lint declaration .. + +declare_lint_pass!(FooFunctions => [FOO_FUNCTIONS]); + +impl EarlyLintPass for FooFunctions {} +``` + +[declare_clippy_lint]: https://github.com/rust-lang/rust-clippy/blob/557f6848bd5b7183f55c1e1522a326e9e1df6030/clippy_lints/src/lib.rs#L60 +[example_lint_page]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure +[lint_naming]: https://rust-lang.github.io/rfcs/0344-conventions-galore.html#lints +[category_level_mapping]: https://github.com/rust-lang/rust-clippy/blob/557f6848bd5b7183f55c1e1522a326e9e1df6030/clippy_lints/src/lib.rs#L110 + +## Lint registration + +When using `cargo dev new_lint`, the lint is automatically registered and +nothing more has to be done. + +When declaring a new lint by hand and `cargo dev update_lints` is used, the lint +pass may have to be registered manually in the `register_plugins` function in +`clippy_lints/src/lib.rs`: + +```rust +store.register_early_pass(|| Box::new(foo_functions::FooFunctions)); +``` + +As one may expect, there is a corresponding `register_late_pass` method +available as well. Without a call to one of `register_early_pass` or +`register_late_pass`, the lint pass in question will not be run. + +One reason that `cargo dev update_lints` does not automate this step is that +multiple lints can use the same lint pass, so registering the lint pass may +already be done when adding a new lint. Another reason that this step is not +automated is that the order that the passes are registered determines the order +the passes actually run, which in turn affects the order that any emitted lints +are output in. + +## Lint passes + +Writing a lint that only checks for the name of a function means that we only +have to deal with the AST and don't have to deal with the type system at all. +This is good, because it makes writing this particular lint less complicated. + +We have to make this decision with every new Clippy lint. It boils down to using +either [`EarlyLintPass`][early_lint_pass] or [`LateLintPass`][late_lint_pass]. + +In short, the `LateLintPass` has access to type information while the +`EarlyLintPass` doesn't. If you don't need access to type information, use the +`EarlyLintPass`. The `EarlyLintPass` is also faster. However linting speed +hasn't really been a concern with Clippy so far. + +Since we don't need type information for checking the function name, we used +`--pass=early` when running the new lint automation and all the imports were +added accordingly. + +[early_lint_pass]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/trait.EarlyLintPass.html +[late_lint_pass]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/trait.LateLintPass.html + +## Emitting a lint + +With UI tests and the lint declaration in place, we can start working on the +implementation of the lint logic. + +Let's start by implementing the `EarlyLintPass` for our `FooFunctions`: + +```rust +impl EarlyLintPass for FooFunctions { + fn check_fn(&mut self, cx: &EarlyContext<'_>, fn_kind: FnKind<'_>, span: Span, _: NodeId) { + // TODO: Emit lint here + } +} +``` + +We implement the [`check_fn`][check_fn] method from the +[`EarlyLintPass`][early_lint_pass] trait. This gives us access to various +information about the function that is currently being checked. More on that in +the next section. Let's worry about the details later and emit our lint for +*every* function definition first. + +Depending on how complex we want our lint message to be, we can choose from a +variety of lint emission functions. They can all be found in +[`clippy_utils/src/diagnostics.rs`][diagnostics]. + +`span_lint_and_help` seems most appropriate in this case. It allows us to +provide an extra help message and we can't really suggest a better name +automatically. This is how it looks: + +```rust +impl EarlyLintPass for FooFunctions { + fn check_fn(&mut self, cx: &EarlyContext<'_>, fn_kind: FnKind<'_>, span: Span, _: NodeId) { + span_lint_and_help( + cx, + FOO_FUNCTIONS, + span, + "function named `foo`", + None, + "consider using a more meaningful name" + ); + } +} +``` + +Running our UI test should now produce output that contains the lint message. + +According to [the rustc-dev-guide], the text should be matter of fact and avoid +capitalization and periods, unless multiple sentences are needed. When code or +an identifier must appear in a message or label, it should be surrounded with +single grave accents \`. + +[check_fn]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/trait.EarlyLintPass.html#method.check_fn +[diagnostics]: https://github.com/rust-lang/rust-clippy/blob/master/clippy_utils/src/diagnostics.rs +[the rustc-dev-guide]: https://rustc-dev-guide.rust-lang.org/diagnostics.html + +## Adding the lint logic + +Writing the logic for your lint will most likely be different from our example, +so this section is kept rather short. + +Using the [`check_fn`][check_fn] method gives us access to [`FnKind`][fn_kind] +that has the [`FnKind::Fn`] variant. It provides access to the name of the +function/method via an [`Ident`][ident]. + +With that we can expand our `check_fn` method to: + +```rust +impl EarlyLintPass for FooFunctions { + fn check_fn(&mut self, cx: &EarlyContext<'_>, fn_kind: FnKind<'_>, span: Span, _: NodeId) { + if is_foo_fn(fn_kind) { + span_lint_and_help( + cx, + FOO_FUNCTIONS, + span, + "function named `foo`", + None, + "consider using a more meaningful name" + ); + } + } +} +``` + +We separate the lint conditional from the lint emissions because it makes the +code a bit easier to read. In some cases this separation would also allow to +write some unit tests (as opposed to only UI tests) for the separate function. + +In our example, `is_foo_fn` looks like: + +```rust +// use statements, impl EarlyLintPass, check_fn, .. + +fn is_foo_fn(fn_kind: FnKind<'_>) -> bool { + match fn_kind { + FnKind::Fn(_, ident, ..) => { + // check if `fn` name is `foo` + ident.name.as_str() == "foo" + } + // ignore closures + FnKind::Closure(..) => false + } +} +``` + +Now we should also run the full test suite with `cargo test`. At this point +running `cargo test` should produce the expected output. Remember to run `cargo +dev bless` to update the `.stderr` file. + +`cargo test` (as opposed to `cargo uitest`) will also ensure that our lint +implementation is not violating any Clippy lints itself. + +That should be it for the lint implementation. Running `cargo test` should now +pass. + +[fn_kind]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_ast/visit/enum.FnKind.html +[`FnKind::Fn`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_ast/visit/enum.FnKind.html#variant.Fn +[ident]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_span/symbol/struct.Ident.html + +## Specifying the lint's minimum supported Rust version (MSRV) + +Sometimes a lint makes suggestions that require a certain version of Rust. For +example, the `manual_strip` lint suggests using `str::strip_prefix` and +`str::strip_suffix` which is only available after Rust 1.45. In such cases, you +need to ensure that the MSRV configured for the project is >= the MSRV of the +required Rust feature. If multiple features are required, just use the one with +a lower MSRV. + +First, add an MSRV alias for the required feature in +[`clippy_utils::msrvs`](/clippy_utils/src/msrvs.rs). This can be accessed later +as `msrvs::STR_STRIP_PREFIX`, for example. + +```rust +msrv_aliases! { + .. + 1,45,0 { STR_STRIP_PREFIX } +} +``` + +In order to access the project-configured MSRV, you need to have an `msrv` field +in the LintPass struct, and a constructor to initialize the field. The `msrv` +value is passed to the constructor in `clippy_lints/lib.rs`. + +```rust +pub struct ManualStrip { + msrv: Option, +} + +impl ManualStrip { + #[must_use] + pub fn new(msrv: Option) -> Self { + Self { msrv } + } +} +``` + +The project's MSRV can then be matched against the feature MSRV in the LintPass +using the `meets_msrv` utility function. + +``` rust +if !meets_msrv(self.msrv, msrvs::STR_STRIP_PREFIX) { + return; +} +``` + +The project's MSRV can also be specified as an inner attribute, which overrides +the value from `clippy.toml`. This can be accounted for using the +`extract_msrv_attr!(LintContext)` macro and passing +`LateContext`/`EarlyContext`. + +```rust +impl<'tcx> LateLintPass<'tcx> for ManualStrip { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + ... + } + extract_msrv_attr!(LateContext); +} +``` + +Once the `msrv` is added to the lint, a relevant test case should be added to +`tests/ui/min_rust_version_attr.rs` which verifies that the lint isn't emitted +if the project's MSRV is lower. + +As a last step, the lint should be added to the lint documentation. This is done +in `clippy_lints/src/utils/conf.rs`: + +```rust +define_Conf! { + /// Lint: LIST, OF, LINTS, . The minimum rust version that the project supports + (msrv: Option = None), + ... +} +``` + +## Author lint + +If you have trouble implementing your lint, there is also the internal `author` +lint to generate Clippy code that detects the offending pattern. It does not +work for all of the Rust syntax, but can give a good starting point. + +The quickest way to use it, is the [Rust playground: +play.rust-lang.org][author_example]. Put the code you want to lint into the +editor and add the `#[clippy::author]` attribute above the item. Then run Clippy +via `Tools -> Clippy` and you should see the generated code in the output below. + +[Here][author_example] is an example on the playground. + +If the command was executed successfully, you can copy the code over to where +you are implementing your lint. + +[author_example]: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=9a12cb60e5c6ad4e3003ac6d5e63cf55 + +## Print HIR lint + +To implement a lint, it's helpful to first understand the internal +representation that rustc uses. Clippy has the `#[clippy::dump]` attribute that +prints the [_High-Level Intermediate Representation (HIR)_] of the item, +statement, or expression that the attribute is attached to. To attach the +attribute to expressions you often need to enable +`#![feature(stmt_expr_attributes)]`. + +[Here][print_hir_example] you can find an example, just select _Tools_ and run +_Clippy_. + +[_High-Level Intermediate Representation (HIR)_]: https://rustc-dev-guide.rust-lang.org/hir.html +[print_hir_example]: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=daf14db3a7f39ca467cd1b86c34b9afb + +## Documentation + +The final thing before submitting our PR is to add some documentation to our +lint declaration. + +Please document your lint with a doc comment akin to the following: + +```rust +declare_clippy_lint! { + /// ### What it does + /// Checks for ... (describe what the lint matches). + /// + /// ### Why is this bad? + /// Supply the reason for linting the code. + /// + /// ### Example + /// + /// ```rust,ignore + /// // A short example of code that triggers the lint + /// ``` + /// + /// Use instead: + /// ```rust,ignore + /// // A short example of improved code that doesn't trigger the lint + /// ``` + #[clippy::version = "1.29.0"] + pub FOO_FUNCTIONS, + pedantic, + "function named `foo`, which is not a descriptive name" +} +``` + +Once your lint is merged, this documentation will show up in the [lint +list][lint_list]. + +[lint_list]: https://rust-lang.github.io/rust-clippy/master/index.html + +## Running rustfmt + +[Rustfmt] is a tool for formatting Rust code according to style guidelines. Your +code has to be formatted by `rustfmt` before a PR can be merged. Clippy uses +nightly `rustfmt` in the CI. + +It can be installed via `rustup`: + +```bash +rustup component add rustfmt --toolchain=nightly +``` + +Use `cargo dev fmt` to format the whole codebase. Make sure that `rustfmt` is +installed for the nightly toolchain. + +[Rustfmt]: https://github.com/rust-lang/rustfmt + +## Debugging + +If you want to debug parts of your lint implementation, you can use the [`dbg!`] +macro anywhere in your code. Running the tests should then include the debug +output in the `stdout` part. + +[`dbg!`]: https://doc.rust-lang.org/std/macro.dbg.html + +## PR Checklist + +Before submitting your PR make sure you followed all of the basic requirements: + + + +- \[ ] Followed [lint naming conventions][lint_naming] +- \[ ] Added passing UI tests (including committed `.stderr` file) +- \[ ] `cargo test` passes locally +- \[ ] Executed `cargo dev update_lints` +- \[ ] Added lint documentation +- \[ ] Run `cargo dev fmt` + +## Adding configuration to a lint + +Clippy supports the configuration of lints values using a `clippy.toml` file in +the workspace directory. Adding a configuration to a lint can be useful for +thresholds or to constrain some behavior that can be seen as a false positive +for some users. Adding a configuration is done in the following steps: + +1. Adding a new configuration entry to + [clippy_lints::utils::conf](/clippy_lints/src/utils/conf.rs) like this: + + ```rust + /// Lint: LINT_NAME. + /// + /// + (configuration_ident: Type = DefaultValue), + ``` + + The doc comment is automatically added to the documentation of the listed + lints. The default value will be formatted using the `Debug` implementation + of the type. +2. Adding the configuration value to the lint impl struct: + 1. This first requires the definition of a lint impl struct. Lint impl + structs are usually generated with the `declare_lint_pass!` macro. This + struct needs to be defined manually to add some kind of metadata to it: + ```rust + // Generated struct definition + declare_lint_pass!(StructName => [ + LINT_NAME + ]); + + // New manual definition struct + #[derive(Copy, Clone)] + pub struct StructName {} + + impl_lint_pass!(StructName => [ + LINT_NAME + ]); + ``` + + 2. Next add the configuration value and a corresponding creation method like + this: + ```rust + #[derive(Copy, Clone)] + pub struct StructName { + configuration_ident: Type, + } + + // ... + + impl StructName { + pub fn new(configuration_ident: Type) -> Self { + Self { + configuration_ident, + } + } + } + ``` +3. Passing the configuration value to the lint impl struct: + + First find the struct construction in the [clippy_lints lib + file](/clippy_lints/src/lib.rs). The configuration value is now cloned or + copied into a local value that is then passed to the impl struct like this: + + ```rust + // Default generated registration: + store.register_*_pass(|| box module::StructName); + + // New registration with configuration value + let configuration_ident = conf.configuration_ident.clone(); + store.register_*_pass(move || box module::StructName::new(configuration_ident)); + ``` + + Congratulations the work is almost done. The configuration value can now be + accessed in the linting code via `self.configuration_ident`. + +4. Adding tests: + 1. The default configured value can be tested like any normal lint in + [`tests/ui`](/tests/ui). + 2. The configuration itself will be tested separately in + [`tests/ui-toml`](/tests/ui-toml). Simply add a new subfolder with a + fitting name. This folder contains a `clippy.toml` file with the + configuration value and a rust file that should be linted by Clippy. The + test can otherwise be written as usual. + +## Cheat Sheet + +Here are some pointers to things you are likely going to need for every lint: + +* [Clippy utils][utils] - Various helper functions. Maybe the function you need + is already in here ([`is_type_diagnostic_item`], [`implements_trait`], + [`snippet`], etc) +* [Clippy diagnostics][diagnostics] +* [Let chains][let-chains] +* [`from_expansion`][from_expansion] and + [`in_external_macro`][in_external_macro] +* [`Span`][span] +* [`Applicability`][applicability] +* [Common tools for writing lints](common_tools_writing_lints.md) helps with + common operations +* [The rustc-dev-guide][rustc-dev-guide] explains a lot of internal compiler + concepts +* [The nightly rustc docs][nightly_docs] which has been linked to throughout + this guide + +For `EarlyLintPass` lints: + +* [`EarlyLintPass`][early_lint_pass] +* [`rustc_ast::ast`][ast] + +For `LateLintPass` lints: + +* [`LateLintPass`][late_lint_pass] +* [`Ty::TyKind`][ty] + +While most of Clippy's lint utils are documented, most of rustc's internals lack +documentation currently. This is unfortunate, but in most cases you can probably +get away with copying things from existing similar lints. If you are stuck, +don't hesitate to ask on [Zulip] or in the issue/PR. + +[utils]: https://doc.rust-lang.org/nightly/nightly-rustc/clippy_utils/index.html +[`is_type_diagnostic_item`]: https://doc.rust-lang.org/nightly/nightly-rustc/clippy_utils/ty/fn.is_type_diagnostic_item.html +[`implements_trait`]: https://doc.rust-lang.org/nightly/nightly-rustc/clippy_utils/ty/fn.implements_trait.html +[`snippet`]: https://doc.rust-lang.org/nightly/nightly-rustc/clippy_utils/source/fn.snippet.html +[let-chains]: https://github.com/rust-lang/rust/pull/94927 +[from_expansion]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_span/struct.Span.html#method.from_expansion +[in_external_macro]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/lint/fn.in_external_macro.html +[span]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_span/struct.Span.html +[applicability]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_errors/enum.Applicability.html +[rustc-dev-guide]: https://rustc-dev-guide.rust-lang.org/ +[nightly_docs]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ +[ast]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_ast/ast/index.html +[ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/sty/index.html +[Zulip]: https://rust-lang.zulipchat.com/#narrow/stream/clippy diff --git a/book/src/development/basics.md b/book/src/development/basics.md new file mode 100644 index 00000000000..78c429ea013 --- /dev/null +++ b/book/src/development/basics.md @@ -0,0 +1,188 @@ +# Basics for hacking on Clippy + +This document explains the basics for hacking on Clippy. Besides others, this +includes how to build and test Clippy. For a more in depth description on the +codebase take a look at [Adding Lints] or [Common Tools]. + +[Adding Lints]: https://github.com/rust-lang/rust-clippy/blob/master/doc/adding_lints.md +[Common Tools]: https://github.com/rust-lang/rust-clippy/blob/master/doc/common_tools_writing_lints.md + +- [Basics for hacking on Clippy](#basics-for-hacking-on-clippy) + - [Get the Code](#get-the-code) + - [Building and Testing](#building-and-testing) + - [`cargo dev`](#cargo-dev) + - [lintcheck](#lintcheck) + - [PR](#pr) + - [Common Abbreviations](#common-abbreviations) + - [Install from source](#install-from-source) + +## Get the Code + +First, make sure you have checked out the latest version of Clippy. If this is +your first time working on Clippy, create a fork of the repository and clone it +afterwards with the following command: + +```bash +git clone git@github.com:/rust-clippy +``` + +If you've already cloned Clippy in the past, update it to the latest version: + +```bash +# If the upstream remote has not been added yet +git remote add upstream https://github.com/rust-lang/rust-clippy +# upstream has to be the remote of the rust-lang/rust-clippy repo +git fetch upstream +# make sure that you are on the master branch +git checkout master +# rebase your master branch on the upstream master +git rebase upstream/master +# push to the master branch of your fork +git push +``` + +## Building and Testing + +You can build and test Clippy like every other Rust project: + +```bash +cargo build # builds Clippy +cargo test # tests Clippy +``` + +Since Clippy's test suite is pretty big, there are some commands that only run a +subset of Clippy's tests: + +```bash +# only run UI tests +cargo uitest +# only run UI tests starting with `test_` +TESTNAME="test_" cargo uitest +# only run dogfood tests +cargo test --test dogfood +``` + +If the output of a [UI test] differs from the expected output, you can update +the reference file with: + +```bash +cargo dev bless +``` + +For example, this is necessary, if you fix a typo in an error message of a lint +or if you modify a test file to add a test case. + +> _Note:_ This command may update more files than you intended. In that case +> only commit the files you wanted to update. + +[UI test]: https://rustc-dev-guide.rust-lang.org/tests/adding.html#guide-to-the-ui-tests + +## `cargo dev` + +Clippy has some dev tools to make working on Clippy more convenient. These tools +can be accessed through the `cargo dev` command. Available tools are listed +below. To get more information about these commands, just call them with +`--help`. + +```bash +# formats the whole Clippy codebase and all tests +cargo dev fmt +# register or update lint names/groups/... +cargo dev update_lints +# create a new lint and register it +cargo dev new_lint +# automatically formatting all code before each commit +cargo dev setup git-hook +# (experimental) Setup Clippy to work with IntelliJ-Rust +cargo dev setup intellij +``` + +More about intellij command usage and reasons +[here](../CONTRIBUTING.md#intellij-rust) + +## lintcheck + +`cargo lintcheck` will build and run clippy on a fixed set of crates and +generate a log of the results. You can `git diff` the updated log against its +previous version and see what impact your lint made on a small set of crates. +If you add a new lint, please audit the resulting warnings and make sure there +are no false positives and that the suggestions are valid. + +Refer to the tools [README] for more details. + +[README]: https://github.com/rust-lang/rust-clippy/blob/master/lintcheck/README.md + +## PR + +We follow a rustc no merge-commit policy. See +. + +## Common Abbreviations + +| Abbreviation | Meaning | +| ------------ | -------------------------------------- | +| UB | Undefined Behavior | +| FP | False Positive | +| FN | False Negative | +| ICE | Internal Compiler Error | +| AST | Abstract Syntax Tree | +| MIR | Mid-Level Intermediate Representation | +| HIR | High-Level Intermediate Representation | +| TCX | Type context | + +This is a concise list of abbreviations that can come up during Clippy +development. An extensive general list can be found in the [rustc-dev-guide +glossary][glossary]. Always feel free to ask if an abbreviation or meaning is +unclear to you. + +## Install from source + +If you are hacking on Clippy and want to install it from source, do the +following: + +First, take note of the toolchain +[override](https://rust-lang.github.io/rustup/overrides.html) in +`/rust-toolchain`. We will use this override to install Clippy into the right +toolchain. + +> Tip: You can view the active toolchain for the current directory with `rustup +> show active-toolchain`. + +From the Clippy project root, run the following command to build the Clippy +binaries and copy them into the toolchain directory. This will override the +currently installed Clippy component. + +```terminal +cargo build --release --bin cargo-clippy --bin clippy-driver -Zunstable-options --out-dir "$(rustc --print=sysroot)/bin" +``` + +Now you may run `cargo clippy` in any project, using the toolchain where you +just installed Clippy. + +```terminal +cd my-project +cargo +nightly-2021-07-01 clippy +``` + +...or `clippy-driver` + +```terminal +clippy-driver +nightly-2021-07-01 +``` + +If you need to restore the default Clippy installation, run the following (from +the Clippy project root). + +```terminal +rustup component remove clippy +rustup component add clippy +``` + +> **DO NOT** install using `cargo install --path . --force` since this will +> overwrite rustup +> [proxies](https://rust-lang.github.io/rustup/concepts/proxies.html). That is, +> `~/.cargo/bin/cargo-clippy` and `~/.cargo/bin/clippy-driver` should be hard or +> soft links to `~/.cargo/bin/rustup`. You can repair these by running `rustup +> update`. + +[glossary]: https://rustc-dev-guide.rust-lang.org/appendix/glossary.html diff --git a/book/src/development/common_tools_writing_lints.md b/book/src/development/common_tools_writing_lints.md new file mode 100644 index 00000000000..e1ed89262f6 --- /dev/null +++ b/book/src/development/common_tools_writing_lints.md @@ -0,0 +1,279 @@ +# Common tools for writing lints + +You may need following tooltips to catch up with common operations. + +- [Common tools for writing lints](#common-tools-for-writing-lints) + - [Retrieving the type of an expression](#retrieving-the-type-of-an-expression) + - [Checking if an expr is calling a specific method](#checking-if-an-expr-is-calling-a-specific-method) + - [Checking for a specific type](#checking-for-a-specific-type) + - [Checking if a type implements a specific trait](#checking-if-a-type-implements-a-specific-trait) + - [Checking if a type defines a specific method](#checking-if-a-type-defines-a-specific-method) + - [Dealing with macros](#dealing-with-macros-and-expansions) + +Useful Rustc dev guide links: +- [Stages of compilation](https://rustc-dev-guide.rust-lang.org/compiler-src.html#the-main-stages-of-compilation) +- [Diagnostic items](https://rustc-dev-guide.rust-lang.org/diagnostics/diagnostic-items.html) +- [Type checking](https://rustc-dev-guide.rust-lang.org/type-checking.html) +- [Ty module](https://rustc-dev-guide.rust-lang.org/ty.html) + +## Retrieving the type of an expression + +Sometimes you may want to retrieve the type `Ty` of an expression `Expr`, for +example to answer following questions: + +- which type does this expression correspond to (using its [`TyKind`][TyKind])? +- is it a sized type? +- is it a primitive type? +- does it implement a trait? + +This operation is performed using the [`expr_ty()`][expr_ty] method from the +[`TypeckResults`][TypeckResults] struct, that gives you access to the underlying +structure [`Ty`][Ty]. + +Example of use: +```rust +impl LateLintPass<'_> for MyStructLint { + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { + // Get type of `expr` + let ty = cx.typeck_results().expr_ty(expr); + // Match its kind to enter its type + match ty.kind { + ty::Adt(adt_def, _) if adt_def.is_struct() => println!("Our `expr` is a struct!"), + _ => () + } + } +} +``` + +Similarly in [`TypeckResults`][TypeckResults] methods, you have the +[`pat_ty()`][pat_ty] method to retrieve a type from a pattern. + +Two noticeable items here: +- `cx` is the lint context [`LateContext`][LateContext]. The two most useful + data structures in this context are `tcx` and the `TypeckResults` returned by + `LateContext::typeck_results`, allowing us to jump to type definitions and + other compilation stages such as HIR. +- `typeck_results`'s return value is [`TypeckResults`][TypeckResults] and is + created by type checking step, it includes useful information such as types of + expressions, ways to resolve methods and so on. + +## Checking if an expr is calling a specific method + +Starting with an `expr`, you can check whether it is calling a specific method +`some_method`: + +```rust +impl<'tcx> LateLintPass<'tcx> for MyStructLint { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { + // 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) + // Optionally, check the type of the self argument. + // - See "Checking for a specific type" + { + // ... + } + } +} +``` + +## Checking for a specific type + +There are three ways to check if an expression type is a specific type we want +to check for. All of these methods only check for the base type, generic +arguments have to be checked separately. + +```rust +use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item}; +use clippy_utils::{paths, match_def_path}; +use rustc_span::symbol::sym; +use rustc_hir::LangItem; + +impl LateLintPass<'_> for MyStructLint { + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { + // Getting the expression type + let ty = cx.typeck_results().expr_ty(expr); + + // 1. Using diagnostic items + // The last argument is the diagnostic item to check for + if is_type_diagnostic_item(cx, ty, sym::Option) { + // The type is an `Option` + } + + // 2. Using lang items + if is_type_lang_item(cx, ty, LangItem::RangeFull) { + // The type is a full range like `.drain(..)` + } + + // 3. Using the type path + // This method should be avoided if possible + if match_def_path(cx, def_id, &paths::RESULT) { + // The type is a `core::result::Result` + } + } +} +``` + +Prefer using diagnostic items and lang items where possible. + +## Checking if a type implements a specific trait + +There are three ways to do this, depending on if the target trait has a +diagnostic item, lang item or neither. + +```rust +use clippy_utils::{implements_trait, is_trait_method, match_trait_method, paths}; +use rustc_span::symbol::sym; + +impl LateLintPass<'_> for MyStructLint { + fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { + // 1. Using diagnostic items with the expression + // we use `is_trait_method` function from Clippy's utils + if is_trait_method(cx, expr, sym::Iterator) { + // method call in `expr` belongs to `Iterator` trait + } + + // 2. Using lang items with the expression type + let ty = cx.typeck_results().expr_ty(expr); + if cx.tcx.lang_items() + // we are looking for the `DefId` of `Drop` trait in lang items + .drop_trait() + // then we use it with our type `ty` by calling `implements_trait` from Clippy's utils + .map_or(false, |id| implements_trait(cx, ty, id, &[])) { + // `expr` implements `Drop` trait + } + + // 3. Using the type path with the expression + // we use `match_trait_method` function from Clippy's utils + // (This method should be avoided if possible) + if match_trait_method(cx, expr, &paths::INTO) { + // `expr` implements `Into` trait + } + } +} +``` + +> Prefer using diagnostic and lang items, if the target trait has one. + +We access lang items through the type context `tcx`. `tcx` is of type +[`TyCtxt`][TyCtxt] and is defined in the `rustc_middle` crate. A list of defined +paths for Clippy can be found in [paths.rs][paths] + +## Checking if a type defines a specific method + +To check if our type defines a method called `some_method`: + +```rust +use clippy_utils::ty::is_type_diagnostic_item; +use clippy_utils::return_ty; + +impl<'tcx> LateLintPass<'tcx> for MyTypeImpl { + fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx ImplItem<'_>) { + // 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) + // 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` + && is_type_diagnostic_item(cx, return_ty(cx, impl_item.hir_id), sym!(string_type)) + { + // ... + } + } +} +``` + +## Dealing with macros and expansions + +Keep in mind that macros are already expanded and desugaring is already applied +to the code representation that you are working with in Clippy. This +unfortunately causes a lot of false positives because macro expansions are +"invisible" unless you actively check for them. Generally speaking, code with +macro expansions should just be ignored by Clippy because that code can be +dynamic in ways that are difficult or impossible to see. Use the following +functions to deal with macros: + +- `span.from_expansion()`: detects if a span is from macro expansion or + desugaring. Checking this is a common first step in a lint. + + ```rust + if expr.span.from_expansion() { + // just forget it + return; + } + ``` + +- `span.ctxt()`: the span's context represents whether it is from expansion, and + if so, which macro call expanded it. It is sometimes useful to check if the + context of two spans are equal. + + ```rust + // expands to `1 + 0`, but don't lint + 1 + mac!() + ``` + ```rust + if left.span.ctxt() != right.span.ctxt() { + // the coder most likely cannot modify this expression + return; + } + ``` + > Note: Code that is not from expansion is in the "root" context. So any spans + > where `from_expansion` returns `true` can be assumed to have the same + > context. And so just using `span.from_expansion()` is often good enough. + + +- `in_external_macro(span)`: detect if the given span is from a macro defined in + a foreign crate. If you want the lint to work with macro-generated code, this + is the next line of defense to avoid macros not defined in the current crate. + It doesn't make sense to lint code that the coder can't change. + + You may want to use it for example to not start linting in macros from other + crates + + ```rust + #[macro_use] + extern crate a_crate_with_macros; + + // `foo` is defined in `a_crate_with_macros` + foo!("bar"); + + // if we lint the `match` of `foo` call and test its span + assert_eq!(in_external_macro(cx.sess(), match_span), true); + ``` + +- `span.ctxt()`: the span's context represents whether it is from expansion, and + if so, what expanded it + + One thing `SpanContext` is useful for is to check if two spans are in the same + context. For example, in `a == b`, `a` and `b` have the same context. In a + `macro_rules!` with `a == $b`, `$b` is expanded to some expression with a + different context from `a`. + + ```rust + macro_rules! m { + ($a:expr, $b:expr) => { + if $a.is_some() { + $b; + } + } + } + + let x: Option = Some(42); + m!(x, x.unwrap()); + + // These spans are not from the same context + // x.is_some() is from inside the macro + // x.unwrap() is from outside the macro + assert_eq!(x_is_some_span.ctxt(), x_unwrap_span.ctxt()); + ``` + +[Ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.Ty.html +[TyKind]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/enum.TyKind.html +[TypeckResults]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TypeckResults.html +[expr_ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TypeckResults.html#method.expr_ty +[LateContext]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/struct.LateContext.html +[TyCtxt]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/context/struct.TyCtxt.html +[pat_ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/context/struct.TypeckResults.html#method.pat_ty +[paths]: ../clippy_utils/src/paths.rs diff --git a/book/src/development/infrastructure/README.md b/book/src/development/infrastructure/README.md new file mode 100644 index 00000000000..3b2a2539996 --- /dev/null +++ b/book/src/development/infrastructure/README.md @@ -0,0 +1,19 @@ +# Infrastructure + +In order to deploy Clippy over `rustup`, some infrastructure is necessary. This +chapter describes the different parts of the Clippy infrastructure that need to +be maintained to make this possible. + +The most important part is the sync between the `rust-lang/rust` repository and +the Clippy repository that takes place every two weeks. This process is +described in the [Syncing changes between Clippy and `rust-lang/rust`](sync.md) +section. + +A new Clippy release is done together with every Rust release, so every six +weeks. The release process is described in the [Release a new Clippy +Version](release.md) section. During a release cycle a changelog entry for the +next release has to be written. The format of that and how to do that is +documented in the [Changelog Update](changelog_update.md) section. + +> _Note:_ The Clippy CI should also be described in this chapter, but for now is +> left as a TODO. diff --git a/book/src/development/infrastructure/backport.md b/book/src/development/infrastructure/backport.md new file mode 100644 index 00000000000..15f3d1f0806 --- /dev/null +++ b/book/src/development/infrastructure/backport.md @@ -0,0 +1,71 @@ +# Backport Changes + +Sometimes it is necessary to backport changes to the beta release of Clippy. +Backports in Clippy are rare and should be approved by the Clippy team. For +example, a backport is done, if a crucial ICE was fixed or a lint is broken to a +point, that it has to be disabled, before landing on stable. + +Backports are done to the `beta` branch of Clippy. Backports to stable Clippy +releases basically don't exist, since this would require a Rust point release, +which is almost never justifiable for a Clippy fix. + + +## Backport the changes + +Backports are done on the beta branch of the Clippy repository. + +```bash +# Assuming the current directory corresponds to the Clippy repository +$ git checkout beta +$ git checkout -b backport +$ git cherry-pick # `` is the commit hash of the commit(s), that should be backported +$ git push origin backport +``` + +Now you should test that the backport passes all the tests in the Rust +repository. You can do this with: + +```bash +# Assuming the current directory corresponds to the Rust repository +$ git checkout beta +$ git subtree pull -p src/tools/clippy https://github.com//rust-clippy backport +$ ./x.py test src/tools/clippy +``` + +Should the test fail, you can fix Clippy directly in the Rust repository. This +has to be first applied to the Clippy beta branch and then again synced to the +Rust repository, though. The easiest way to do this is: + +```bash +# In the Rust repository +$ git diff --patch --relative=src/tools/clippy > clippy.patch +# In the Clippy repository +$ git apply /path/to/clippy.patch +$ git add -u +$ git commit -m "Fix rustup fallout" +$ git push origin backport +``` + +After this, you can open a PR to the `beta` branch of the Clippy repository. + + +## Update Clippy in the Rust Repository + +This step must be done, **after** the PR of the previous step was merged. + +After the backport landed in the Clippy repository, the branch has to be synced +back to the beta branch of the Rust repository. + +```bash +# Assuming the current directory corresponds to the Rust repository +$ git checkout beta +$ git checkout -b clippy_backport +$ git subtree pull -p src/tools/clippy https://github.com/rust-lang/rust-clippy beta +$ git push origin clippy_backport +``` + +Make sure to test the backport in the Rust repository before opening a PR. This +is done with `./x.py test src/tools/clippy`. If that passes all tests, open a PR +to the `beta` branch of the Rust repository. In this PR you should tag the +Clippy team member, that agreed to the backport or the `@rust-lang/clippy` team. +Make sure to add `[beta]` to the title of the PR. diff --git a/book/src/development/infrastructure/book.md b/book/src/development/infrastructure/book.md new file mode 100644 index 00000000000..b62314c6735 --- /dev/null +++ b/book/src/development/infrastructure/book.md @@ -0,0 +1,42 @@ +# The Clippy Book + +This document explains how to make additions and changes to the Clippy book, the +guide to Clippy that you're reading right now. The Clippy book is formatted with +[Markdown](https://www.markdownguide.org) and generated by +[mdbook](https://github.com/rust-lang/mdBook). + +- [Get mdbook](#get-mdbook) +- [Make changes](#make-changes) + +## Get mdbook + +While not strictly necessary since the book source is simply Markdown text +files, having mdbook locally will allow you to build, test and serve the book +locally to view changes before you commit them to the repository. You likely +already have `cargo` installed, so the easiest option is to simply: + +```shell +cargo install mdbook +``` + +See the mdbook [installation](https://github.com/rust-lang/mdBook#installation) +instructions for other options. + +## Make changes + +The book's +[src](https://github.com/joshrotenberg/rust-clippy/tree/clippy_guide/book/src) +directory contains all of the markdown files used to generate the book. If you +want to see your changes in real time, you can use the mdbook `serve` command to +run a web server locally that will automatically update changes as they are +made. From the top level of your `rust-clippy` directory: + +```shell +mdbook serve book --open +``` + +Then navigate to `http://localhost:3000` to see the generated book. While the +server is running, changes you make will automatically be updated. + +For more information, see the mdbook +[guide](https://rust-lang.github.io/mdBook/). diff --git a/book/src/development/infrastructure/changelog_update.md b/book/src/development/infrastructure/changelog_update.md new file mode 100644 index 00000000000..e560f4c6a3e --- /dev/null +++ b/book/src/development/infrastructure/changelog_update.md @@ -0,0 +1,102 @@ +# Changelog Update + +If you want to help with updating the [changelog], you're in the right place. + +## When to update + +Typos and other small fixes/additions are _always_ welcome. + +Special care needs to be taken when it comes to updating the changelog for a new +Rust release. For that purpose, the changelog is ideally updated during the week +before an upcoming stable release. You can find the release dates on the [Rust +Forge][forge]. + +Most of the time we only need to update the changelog for minor Rust releases. +It's been very rare that Clippy changes were included in a patch release. + +## Changelog update walkthrough + +### 1. Finding the relevant Clippy commits + +Each Rust release ships with its own version of Clippy. The Clippy subtree can +be found in the `tools` directory of the Rust repository. + +Depending on the current time and what exactly you want to update, the following +bullet points might be helpful: + +* When writing the release notes for the **upcoming stable release** you need to + check out the Clippy commit of the current Rust `beta` branch. + [Link][rust_beta_tools] +* When writing the release notes for the **upcoming beta release**, you need to + check out the Clippy commit of the current Rust `master`. + [Link][rust_master_tools] +* When writing the (forgotten) release notes for a **past stable release**, you + need to check out the Rust release tag of the stable release. + [Link][rust_stable_tools] + +Usually you want to write the changelog of the **upcoming stable release**. Make +sure though, that `beta` was already branched in the Rust repository. + +To find the commit hash, issue the following command when in a `rust-lang/rust` +checkout: +``` +git log --oneline -- src/tools/clippy/ | grep -o "Merge commit '[a-f0-9]*' into .*" | head -1 | sed -e "s/Merge commit '\([a-f0-9]*\)' into .*/\1/g" +``` + +### 2. Fetching the PRs between those commits + +Once you've got the correct commit range, run + +``` +util/fetch_prs_between.sh commit1 commit2 > changes.txt +``` + +and open that file in your editor of choice. + +When updating the changelog it's also a good idea to make sure that `commit1` is +already correct in the current changelog. + +### 3. Authoring the final changelog + +The above script should have dumped all the relevant PRs to the file you +specified. It should have filtered out most of the irrelevant PRs already, but +it's a good idea to do a manual cleanup pass where you look for more irrelevant +PRs. If you're not sure about some PRs, just leave them in for the review and +ask for feedback. + +With the PRs filtered, you can start to take each PR and move the `changelog: ` +content to `CHANGELOG.md`. Adapt the wording as you see fit but try to keep it +somewhat coherent. + +The order should roughly be: + +1. New lints +2. Moves or deprecations of lints +3. Changes that expand what code existing lints cover +4. False positive fixes +5. Suggestion fixes/improvements +6. ICE fixes +7. Documentation improvements +8. Others + +As section headers, we use: + +``` +### New Lints +### Moves and Deprecations +### Enhancements +### False Positive Fixes +### Suggestion Fixes/Improvements +### ICE Fixes +### Documentation Improvements +### Others +``` + +Please also be sure to update the Beta/Unreleased sections at the top with the +relevant commit ranges. + +[changelog]: https://github.com/rust-lang/rust-clippy/blob/master/CHANGELOG.md +[forge]: https://forge.rust-lang.org/ +[rust_master_tools]: https://github.com/rust-lang/rust/tree/master/src/tools/clippy +[rust_beta_tools]: https://github.com/rust-lang/rust/tree/beta/src/tools/clippy +[rust_stable_tools]: https://github.com/rust-lang/rust/releases diff --git a/book/src/development/infrastructure/release.md b/book/src/development/infrastructure/release.md new file mode 100644 index 00000000000..0572281803e --- /dev/null +++ b/book/src/development/infrastructure/release.md @@ -0,0 +1,142 @@ +# Release a new Clippy Version + +> _NOTE:_ This document is probably only relevant to you, if you're a member of +> the Clippy team. + +Clippy is released together with stable Rust releases. The dates for these +releases can be found at the [Rust Forge]. This document explains the necessary +steps to create a Clippy release. + +1. [Remerge the `beta` branch](#remerge-the-beta-branch) +2. [Update the `beta` branch](#update-the-beta-branch) +3. [Find the Clippy commit](#find-the-clippy-commit) +4. [Tag the stable commit](#tag-the-stable-commit) +5. [Update `CHANGELOG.md`](#update-changelogmd) + +> _NOTE:_ This document is for stable Rust releases, not for point releases. For +> point releases, step 1. and 2. should be enough. + +[Rust Forge]: https://forge.rust-lang.org/ + +## Remerge the `beta` branch + +This step is only necessary, if since the last release something was backported +to the beta Rust release. The remerge is then necessary, to make sure that the +Clippy commit, that was used by the now stable Rust release, persists in the +tree of the Clippy repository. + +To find out if this step is necessary run + +```bash +# Assumes that the local master branch of rust-lang/rust-clippy is up-to-date +$ git fetch upstream +$ git branch master --contains upstream/beta +``` + +If this command outputs `master`, this step is **not** necessary. + +```bash +# Assuming `HEAD` is the current `master` branch of rust-lang/rust-clippy +$ git checkout -b backport_remerge +$ git merge upstream/beta +$ git diff # This diff has to be empty, otherwise something with the remerge failed +$ git push origin backport_remerge # This can be pushed to your fork +``` + +After this, open a PR to the master branch. In this PR, the commit hash of the +`HEAD` of the `beta` branch must exists. In addition to that, no files should be +changed by this PR. + +## Update the `beta` branch + +This step must be done **after** the PR of the previous step was merged. + +First, the Clippy commit of the `beta` branch of the Rust repository has to be +determined. + +```bash +# Assuming the current directory corresponds to the Rust repository +$ git fetch upstream +$ git checkout upstream/beta +$ BETA_SHA=$(git log --oneline -- src/tools/clippy/ | grep -o "Merge commit '[a-f0-9]*' into .*" | head -1 | sed -e "s/Merge commit '\([a-f0-9]*\)' into .*/\1/g") +``` + +After finding the Clippy commit, the `beta` branch in the Clippy repository can +be updated. + +```bash +# Assuming the current directory corresponds to the Clippy repository +$ git checkout beta +$ git reset --hard $BETA_SHA +$ git push upstream beta +``` + +## Find the Clippy commit + +The first step is to tag the Clippy commit, that is included in the stable Rust +release. This commit can be found in the Rust repository. + +```bash +# Assuming the current directory corresponds to the Rust repository +$ git fetch upstream # `upstream` is the `rust-lang/rust` remote +$ git checkout 1.XX.0 # XX should be exchanged with the corresponding version +$ SHA=$(git log --oneline -- src/tools/clippy/ | grep -o "Merge commit '[a-f0-9]*' into .*" | head -1 | sed -e "s/Merge commit '\([a-f0-9]*\)' into .*/\1/g") +``` + +## Tag the stable commit + +After finding the Clippy commit, it can be tagged with the release number. + +```bash +# Assuming the current directory corresponds to the Clippy repository +$ git checkout $SHA +$ git tag rust-1.XX.0 # XX should be exchanged with the corresponding version +$ git push upstream rust-1.XX.0 # `upstream` is the `rust-lang/rust-clippy` remote +``` + +After this, the release should be available on the Clippy [release page]. + +[release page]: https://github.com/rust-lang/rust-clippy/releases + +## Update the `stable` branch + +At this step you should have already checked out the commit of the `rust-1.XX.0` +tag. Updating the stable branch from here is as easy as: + +```bash +# Assuming the current directory corresponds to the Clippy repository and the +# commit of the just created rust-1.XX.0 tag is checked out. +$ git push upstream rust-1.XX.0:stable # `upstream` is the `rust-lang/rust-clippy` remote +``` + +> _NOTE:_ Usually there are no stable backports for Clippy, so this update +> should be possible without force pushing or anything like this. If there +> should have happened a stable backport, make sure to re-merge those changes +> just as with the `beta` branch. + +## Update `CHANGELOG.md` + +For this see the document on [how to update the changelog]. + +If you don't have time to do a complete changelog update right away, just update +the following parts: + +- Remove the `(beta)` from the new stable version: + + ```markdown + ## Rust 1.XX (beta) -> ## Rust 1.XX + ``` + +- Update the release date line of the new stable version: + + ```markdown + Current beta, release 20YY-MM-DD -> Current stable, released 20YY-MM-DD + ``` + +- Update the release date line of the previous stable version: + + ```markdown + Current stable, released 20YY-MM-DD -> Released 20YY-MM-DD + ``` + +[how to update the changelog]: changelog_update.md diff --git a/book/src/development/infrastructure/sync.md b/book/src/development/infrastructure/sync.md new file mode 100644 index 00000000000..5a0f7409a2e --- /dev/null +++ b/book/src/development/infrastructure/sync.md @@ -0,0 +1,123 @@ +# Syncing changes between Clippy and [`rust-lang/rust`] + +Clippy currently gets built with a pinned nightly version. + +In the `rust-lang/rust` repository, where rustc resides, there's a copy of +Clippy that compiler hackers modify from time to time to adapt to changes in the +unstable API of the compiler. + +We need to sync these changes back to this repository periodically, and the +changes made to this repository in the meantime also need to be synced to the +`rust-lang/rust` repository. + +To avoid flooding the `rust-lang/rust` PR queue, this two-way sync process is +done in a bi-weekly basis if there's no urgent changes. This is done starting on +the day of the Rust stable release and then every other week. That way we +guarantee that we keep this repo up to date with the latest compiler API, and +every feature in Clippy is available for 2 weeks in nightly, before it can get +to beta. For reference, the first sync following this cadence was performed the +2020-08-27. + +This process is described in detail in the following sections. For general +information about `subtree`s in the Rust repository see [Rust's +`CONTRIBUTING.md`][subtree]. + +## Patching git-subtree to work with big repos + +Currently, there's a bug in `git-subtree` that prevents it from working properly +with the [`rust-lang/rust`] repo. There's an open PR to fix that, but it's +stale. Before continuing with the following steps, we need to manually apply +that fix to our local copy of `git-subtree`. + +You can get the patched version of `git-subtree` from [here][gitgitgadget-pr]. +Put this file under `/usr/lib/git-core` (making a backup of the previous file) +and make sure it has the proper permissions: + +```bash +sudo cp --backup /path/to/patched/git-subtree.sh /usr/lib/git-core/git-subtree +sudo chmod --reference=/usr/lib/git-core/git-subtree~ /usr/lib/git-core/git-subtree +sudo chown --reference=/usr/lib/git-core/git-subtree~ /usr/lib/git-core/git-subtree +``` + +> _Note:_ The first time running `git subtree push` a cache has to be built. +> This involves going through the complete Clippy history once. For this you +> have to increase the stack limit though, which you can do with `ulimit -s +> 60000`. Make sure to run the `ulimit` command from the same session you call +> git subtree. + +> _Note:_ If you are a Debian user, `dash` is the shell used by default for +> scripts instead of `sh`. This shell has a hardcoded recursion limit set to +> 1000. In order to make this process work, you need to force the script to run +> `bash` instead. You can do this by editing the first line of the `git-subtree` +> script and changing `sh` to `bash`. + +## Defining remotes + +You may want to define remotes, so you don't have to type out the remote +addresses on every sync. You can do this with the following commands (these +commands still have to be run inside the `rust` directory): + +```bash +# Set clippy-upstream remote for pulls +$ git remote add clippy-upstream https://github.com/rust-lang/rust-clippy +# Make sure to not push to the upstream repo +$ git remote set-url --push clippy-upstream DISABLED +# Set a local remote +$ git remote add clippy-local /path/to/rust-clippy +``` + +> Note: The following sections assume that you have set those remotes with the +> above remote names. + +## Performing the sync from [`rust-lang/rust`] to Clippy + +Here is a TL;DR version of the sync process (all of the following commands have +to be run inside the `rust` directory): + +1. Clone the [`rust-lang/rust`] repository or make sure it is up to date. +2. Checkout the commit from the latest available nightly. You can get it using + `rustup check`. +3. Sync the changes to the rust-copy of Clippy to your Clippy fork: + ```bash + # Make sure to change `your-github-name` to your github name in the following command. Also be + # sure to either use a net-new branch, e.g. `sync-from-rust`, or delete the branch beforehand + # because changes cannot be fast forwarded and you have to run this command again. + git subtree push -P src/tools/clippy clippy-local sync-from-rust + ``` + + > _Note:_ Most of the time you have to create a merge commit in the + > `rust-clippy` repo (this has to be done in the Clippy repo, not in the + > rust-copy of Clippy): + ```bash + git fetch upstream # assuming upstream is the rust-lang/rust remote + git checkout sync-from-rust + git merge upstream/master --no-ff + ``` + > Note: This is one of the few instances where a merge commit is allowed in + > a PR. +4. Bump the nightly version in the Clippy repository by changing the date in the + rust-toolchain file to the current date and committing it with the message: + ```bash + git commit -m "Bump nightly version -> YYYY-MM-DD" + ``` +5. Open a PR to `rust-lang/rust-clippy` and wait for it to get merged (to + accelerate the process ping the `@rust-lang/clippy` team in your PR and/or + ask them in the [Zulip] stream.) + +[Zulip]: https://rust-lang.zulipchat.com/#narrow/stream/clippy + +## Performing the sync from Clippy to [`rust-lang/rust`] + +All of the following commands have to be run inside the `rust` directory. + +1. Make sure you have checked out the latest `master` of `rust-lang/rust`. +2. Sync the `rust-lang/rust-clippy` master to the rust-copy of Clippy: + ```bash + git checkout -b sync-from-clippy + git subtree pull -P src/tools/clippy clippy-upstream master + ``` +3. Open a PR to [`rust-lang/rust`] + +[gitgitgadget-pr]: https://github.com/gitgitgadget/git/pull/493 +[subtree]: https://rustc-dev-guide.rust-lang.org/contributing.html#external-dependencies-subtree +[`rust-lang/rust`]: https://github.com/rust-lang/rust diff --git a/book/src/development/proposals/README.md b/book/src/development/proposals/README.md new file mode 100644 index 00000000000..78fe34ebf8f --- /dev/null +++ b/book/src/development/proposals/README.md @@ -0,0 +1,11 @@ +# Proposals + +This chapter is about accepted proposals for changes that should be worked on in +or around Clippy in the long run. + +Besides adding more and more lints and improve the lints that Clippy already +has, Clippy is also interested in making the experience of its users, developers +and maintainers better over time. Projects that address bigger picture things +like this usually take more time and it is useful to have a proposal for those +first. This is the place where such proposals are collected, so that we can +refer to them when working on them. diff --git a/book/src/development/proposals/roadmap-2021.md b/book/src/development/proposals/roadmap-2021.md new file mode 100644 index 00000000000..fe8b080f56f --- /dev/null +++ b/book/src/development/proposals/roadmap-2021.md @@ -0,0 +1,235 @@ +# Roadmap 2021 + +# Summary + +This Roadmap lays out the plans for Clippy in 2021: + +- Improving usability and reliability +- Improving experience of contributors and maintainers +- Develop and specify processes + +Members of the Clippy team will be assigned tasks from one or more of these +topics. The team member is then responsible to complete the assigned tasks. This +can either be done by implementing them or by providing mentorship to interested +contributors. + +# Motivation + +With the ongoing growth of the Rust language and with that of the whole +ecosystem, also Clippy gets more and more users and contributors. This is good +for the project, but also brings challenges along. Some of these challenges are: + +- More issues about reliability or usability are popping up +- Traffic is hard to handle for a small team +- Bigger projects don't get completed due to the lack of processes and/or time + of the team members + +Additionally, according to the [Rust Roadmap 2021], clear processes should be +defined by every team and unified across teams. This Roadmap is the first step +towards this. + +[Rust Roadmap 2021]: https://github.com/rust-lang/rfcs/pull/3037 + +# Explanation + +This section will explain the things that should be done in 2021. It is +important to note, that this document focuses on the "What?", not the "How?". +The later will be addressed in follow-up tracking issue, with an assigned team +member. + +The following is split up in two major sections. The first section covers the +user facing plans, the second section the internal plans. + +## User Facing + +Clippy should be as pleasant to use and configure as possible. This section +covers plans that should be implemented to improve the situation of Clippy in +this regard. + +### Usability + +In the following, plans to improve the usability are covered. + +#### No Output After `cargo check` + +Currently when `cargo clippy` is run after `cargo check`, it does not produce +any output. This is especially problematic since `rust-analyzer` is on the rise +and it uses `cargo check` for checking code. A fix is already implemented, but +it still has to be pushed over the finish line. This also includes the +stabilization of the `cargo clippy --fix` command or the support of multi-span +suggestions in `rustfix`. + +- [#4612](https://github.com/rust-lang/rust-clippy/issues/4612) + +#### `lints.toml` Configuration + +This is something that comes up every now and then: a reusable configuration +file, where lint levels can be defined. Discussions about this often lead to +nothing specific or to "we need an RFC for this". And this is exactly what needs +to be done. Get together with the cargo team and write an RFC and implement such +a configuration file somehow and somewhere. + +- [#3164](https://github.com/rust-lang/rust-clippy/issues/3164) +- [cargo#5034](https://github.com/rust-lang/cargo/issues/5034) +- [IRLO](https://internals.rust-lang.org/t/proposal-cargo-lint-configuration/9135/8) + +#### Lint Groups + +There are more and more issues about managing lints in Clippy popping up. Lints +are hard to implement with a guarantee of no/few false positives (FPs). One way +to address this might be to introduce more lint groups to give users the ability +to better manage lints, or improve the process of classifying lints, so that +disabling lints due to FPs becomes rare. It is important to note, that Clippy +lints are less conservative than `rustc` lints, which won't change in the +future. + +- [#5537](https://github.com/rust-lang/rust-clippy/issues/5537) +- [#6366](https://github.com/rust-lang/rust-clippy/issues/6366) + +### Reliability + +In the following, plans to improve the reliability are covered. + +#### False Positive Rate + +In the worst case, new lints are only available in nightly for 2 weeks, before +hitting beta and ultimately stable. This and the fact that fewer people use +nightly Rust nowadays makes it more probable that a lint with many FPs hits +stable. This leads to annoyed users, that will disable these new lints in the +best case and to more annoyed users, that will stop using Clippy in the worst. +A process should be developed and implemented to prevent this from happening. + +- [#6429](https://github.com/rust-lang/rust-clippy/issues/6429) + +## Internal + +(The end of) 2020 has shown, that Clippy has to think about the available +resources, especially regarding management and maintenance of the project. This +section address issues affecting team members and contributors. + +### Management + +In 2020 Clippy achieved over 1000 open issues with regularly between 25-35 open +PRs. This is simultaneously a win and a loss. More issues and PRs means more +people are interested in Clippy and in contributing to it. On the other hand, it +means for team members more work and for contributors longer wait times for +reviews. The following will describe plans how to improve the situation for both +team members and contributors. + +#### Clear Expectations for Team Members + +According to the [Rust Roadmap 2021], a document specifying what it means to be +a member of the team should be produced. This should not put more pressure on +the team members, but rather help them and interested folks to know what the +expectations are. With this it should also be easier to recruit new team members +and may encourage people to get in touch, if they're interested to join. + +#### Scaling up the Team + +More people means less work for each individual. Together with the document +about expectations for team members, a document defining the process of how to +join the team should be produced. This can also increase the stability of the +team, in case of current members dropping out (temporarily). There can also be +different roles in the team, like people triaging vs. people reviewing. + +#### Regular Meetings + +Other teams have regular meetings. Clippy is big enough that it might be worth +to also do them. Especially if more people join the team, this can be important +for sync-ups. Besides the asynchronous communication, that works well for +working on separate lints, a meeting adds a synchronous alternative at a known +time. This is especially helpful if there are bigger things that need to be +discussed (like the projects in this roadmap). For starters bi-weekly meetings +before Rust syncs might make sense. + +#### Triaging + +To get a handle on the influx of open issues, a process for triaging issues and +PRs should be developed. Officially, Clippy follows the Rust triage process, but +currently no one enforces it. This can be improved by sharing triage teams +across projects or by implementing dashboards / tools which simplify triaging. + +### Development + +Improving the developer and contributor experience is something the Clippy team +works on regularly. Though, some things might need special attention and +planing. These topics are listed in the following. + +#### Process for New and Existing Lints + +As already mentioned above, classifying new lints gets quite hard, because the +probability of a buggy lint getting into stable is quite high. A process should +be implemented on how to classify lints. In addition, a test system should be +developed to find out which lints are currently problematic in real world code +to fix or disable them. + +- [#6429 (comment)](https://github.com/rust-lang/rust-clippy/issues/6429#issuecomment-741056379) +- [#6429 (comment)](https://github.com/rust-lang/rust-clippy/issues/6429#issuecomment-741153345) + +#### Processes + +Related to the point before, a process for suggesting and discussing major +changes should be implemented. It's also not clearly defined when a lint should +be enabled or disabled by default. This can also be improved by the test system +mentioned above. + +#### Dev-Tools + +There's already `cargo dev` which makes Clippy development easier and more +pleasant. This can still be expanded, so that it covers more areas of the +development process. + +- [#5394](https://github.com/rust-lang/rust-clippy/issues/5394) + +#### Contributor Guide + +Similar to a Clippy Book, which describes how to use Clippy, a book about how to +contribute to Clippy might be helpful for new and existing contributors. There's +already the `doc` directory in the Clippy repo, this can be turned into a +`mdbook`. + +#### `rustc` integration + +Recently Clippy was integrated with `git subtree` into the `rust-lang/rust` +repository. This made syncing between the two repositories easier. A +`#[non_exhaustive]` list of things that still can be improved is: + +1. Use the same `rustfmt` version and configuration as `rustc`. +2. Make `cargo dev` work in the Rust repo, just as it works in the Clippy repo. + E.g. `cargo dev bless` or `cargo dev update_lints`. And even add more things + to it that might be useful for the Rust repo, e.g. `cargo dev deprecate`. +3. Easier sync process. The `subtree` situation is not ideal. + +## Prioritization + +The most pressing issues for users of Clippy are of course the user facing +issues. So there should be a priority on those issues, but without losing track +of the internal issues listed in this document. + +Getting the FP rate of warn/deny-by-default lints under control should have the +highest priority. Other user facing issues should also get a high priority, but +shouldn't be in the way of addressing internal issues. + +To better manage the upcoming projects, the basic internal processes, like +meetings, tracking issues and documentation, should be established as soon as +possible. They might even be necessary to properly manage the projects, +regarding the user facing issues. + +# Prior Art + +## Rust Roadmap + +Rust's roadmap process was established by [RFC 1728] in 2016. Since then every +year a roadmap was published, that defined the bigger plans for the coming +years. This years roadmap can be found [here][Rust Roadmap 2021]. + +[RFC 1728]: https://rust-lang.github.io/rfcs/1728-north-star.html + +# Drawbacks + +## Big Roadmap + +This roadmap is pretty big and not all items listed in this document might be +addressed during 2021. Because this is the first roadmap for Clippy, having open +tasks at the end of 2021 is fine, but they should be revisited in the 2022 +roadmap. diff --git a/book/src/installation.md b/book/src/installation.md new file mode 100644 index 00000000000..b2a28d0be62 --- /dev/null +++ b/book/src/installation.md @@ -0,0 +1,24 @@ +# Installation + +If you're using `rustup` to install and manage you're Rust toolchains, Clippy is +usually **already installed**. In that case you can skip this chapter and go to +the [Usage] chapter. + +> Note: If you used the `minimal` profile when installing a Rust toolchain, +> Clippy is not automatically installed. + +## Using Rustup + +If Clippy was not installed for a toolchain, it can be installed with + +``` +$ rustup component add clippy [--toolchain=] +``` + +## From Source + +Take a look at the [Basics] chapter in the Clippy developer guide to find step +by step instructions on how to build and install Clippy from source. + +[Basics]: development/basics.md#install-from-source +[Usage]: usage.md diff --git a/book/src/lints.md b/book/src/lints.md new file mode 100644 index 00000000000..35e30960b56 --- /dev/null +++ b/book/src/lints.md @@ -0,0 +1,105 @@ +# Clippy's Lints + +Clippy offers a bunch of additional lints, to help its users write more correct +and idiomatic Rust code. A full list of all lints, that can be filtered by +category, lint level or keywords, can be found in the [Clippy lint +documentation]. + +This chapter will give an overview of the different lint categories, which kind +of lints they offer and recommended actions when you should see a lint out of +that category. For examples, see the [Clippy lint documentation] and filter by +category. + +The different lint groups were defined in the [Clippy 1.0 RFC]. + +## Correctness + +The `clippy::correctness` group is the only lint group in Clippy which lints are +deny-by-default and abort the compilation when triggered. This is for good +reason: If you see a `correctness` lint, it means that your code is outright +wrong or useless and you should try to fix it. + +Lints in this category are carefully picked and should be free of false +positives. So just `#[allow]`ing those lints is not recommended. + +## Suspicious + +The `clippy::suspicious` group is similar to the correctness lints in that it +contains lints that trigger on code that is really _sus_ and should be fixed. As +opposed to correctness lints, it might be possible that the linted code is +intentionally written like it is. + +It is still recommended to fix code that is linted by lints out of this group +instead of `#[allow]`ing the lint. In case you intentionally have written code +that offends the lint you should specifically and locally `#[allow]` the lint +and add give a reason why the code is correct as written. + +## Complexity + +The `clippy::complexity` group offers lints that give you suggestions on how to +simplify your code. It mostly focuses on code that can be written in a shorter +and more readable way, while preserving the semantics. + +If you should see a complexity lint, it usually means that you can remove or +replace some code and it is recommended to do so. However, if you need the more +complex code for some expressiveness reason, it is recommended to allow +complexity lints on a case-by-case basis. + +## Perf + +The `clippy::perf` group gives you suggestions on how you can increase the +performance of your code. Those lints are mostly about code that the compiler +can't trivially optimize, but has to be written in a slightly different way to +make the optimizer's job easier. + +Perf lints are usually easy to apply and it is recommended to do so. + +## Style + +The `clippy::style` group is mostly about writing idiomatic code. Because style +is subjective, this lint group is the most opinionated warn-by-default group in +Clippy. + +If you see a style lint, applying the suggestion usually makes your code more +readable and idiomatic. But because we know that this is opinionated, feel free +to sprinkle `#[allow]`s for style lints in your code or `#![allow]` a style lint +on your whole crate if you disagree with the suggested style completely. + +## Pedantic + +The `clippy::pedantic` group makes Clippy even more _pedantic_. You can enable +the whole group with `#![warn(clippy::pedantic)]` in the `lib.rs`/`main.rs` of +your crate. This lint group is for Clippy power users that want an in depth +check of their code. + +> _Note:_ Instead of enabling the whole group (like Clippy itself does), you may +> want to cherry-pick lints out of the pedantic group. + +If you enable this group, expect to also use `#[allow]` attributes generously +throughout your code. Lints in this group are designed to be pedantic and false +positives sometimes are intentional in order to prevent false negatives. + +## Restriction + +The `clippy::restriction` group contains lints that will _restrict_ you from +using certain parts of the Rust language. It is **not** recommended to enable +the whole group, but rather cherry-pick lints that are useful for your code base +and your use case. + +> _Note:_ Clippy will produce a warning if it finds a +> `#![warn(clippy::restriction)]` attribute in your code! + +Lints from this group will restrict you in some way. If you enable a restriction +lint for your crate it is recommended to also fix code that this lint triggers +on. However, those lints are really strict by design and you might want to +`#[allow]` them in some special cases, with a comment justifying that. + +## Cargo + +The `clippy::cargo` group gives you suggestions on how to improve your +`Cargo.toml` file. This might be especially interesting if you want to publish +your crate and are not sure if you have all useful information in your +`Cargo.toml`. + +[Clippy lint documentation]: https://rust-lang.github.io/rust-clippy/ +[Clippy 1.0 RFC]: https://github.com/rust-lang/rfcs/blob/master/text/2476-clippy-uno.md#lint-audit-and-categories diff --git a/book/src/usage.md b/book/src/usage.md new file mode 100644 index 00000000000..337680aa313 --- /dev/null +++ b/book/src/usage.md @@ -0,0 +1,151 @@ +# Usage + +This chapter describes how to use Clippy to get the most out of it. Clippy can +be used as a `cargo` subcommand or, like `rustc`, directly with the +`clippy-driver` binary. + +> _Note:_ This chapter assumes that you have Clippy installed already. If you're +> not sure, take a look at the [Installation] chapter. + +## Cargo subcommand + +The easiest and most common way to run Clippy is through `cargo`. To do that, +just run + +```bash +cargo clippy +``` + +### Lint configuration + +The above command will run the default set of lints, which are included in the +lint group `clippy::all`. You might want to use even more lints or you might not +agree with every Clippy lint, and for that there are ways to configure lint +levels. + +> _Note:_ Clippy is meant to be used with a generous sprinkling of +> `#[allow(..)]`s through your code. So if you disagree with a lint, don't feel +> bad disabling them for parts of your code or the whole project. + +#### Command line + +You can configure lint levels on the command line by adding +`-A/W/D clippy::lint_name` like this: + +```bash +cargo clippy -- -Aclippy::style -Wclippy::double_neg -Dclippy::perf +``` + +For [CI] all warnings can be elevated to errors which will inturn fail +the build and cause Clippy to exit with a code other than `0`. + +``` +cargo clippy -- -Dwarnings +``` + +> _Note:_ Adding `-D warnings` will cause your build to fail if **any** warnings +> are found in your code. That includes warnings found by rustc (e.g. +> `dead_code`, etc.). + +For more information on configuring lint levels, see the [rustc documentation]. + +[rustc documentation]: https://doc.rust-lang.org/rustc/lints/levels.html#configuring-warning-levels + +#### Even more lints + +Clippy has lint groups which are allow-by-default. This means, that you will +have to enable the lints in those groups manually. + +For a full list of all lints with their description and examples, please refere +to [Clippy's lint list]. The two most important allow-by-default groups are +described below: + +[Clippy's lint list]: https://rust-lang.github.io/rust-clippy/master/index.html + +##### `clippy::pedantic` + +The first group is the `pedantic` group. This group contains really opinionated +lints, that may have some intentional false positives in order to prevent false +negatives. So while this group is ready to be used in production, you can expect +to sprinkle multiple `#[allow(..)]`s in your code. If you find any false +positives, you're still welcome to report them to us for future improvements. + +> FYI: Clippy uses the whole group to lint itself. + +##### `clippy::restriction` + +The second group is the `restriction` group. This group contains lints that +"restrict" the language in some way. For example the `clippy::unwrap` lint from +this group won't allow you to use `.unwrap()` in your code. You may want to look +through the lints in this group and enable the ones that fit your need. + +> _Note:_ You shouldn't enable the whole lint group, but cherry-pick lints from +> this group. Some lints in this group will even contradict other Clippy lints! + +#### Too many lints + +The most opinionated warn-by-default group of Clippy is the `clippy::style` +group. Some people prefer to disable this group completely and then cherry-pick +some lints they like from this group. The same is of course possible with every +other of Clippy's lint groups. + +> _Note:_ We try to keep the warn-by-default groups free from false positives +> (FP). If you find that a lint wrongly triggers, please report it in an issue +> (if there isn't an issue for that FP already) + +#### Source Code + +You can configure lint levels in source code the same way you can configure +`rustc` lints: + +```rust +#![allow(clippy::style)] + +#[warn(clippy::double_neg)] +fn main() { + let x = 1; + let y = --x; + // ^^ warning: double negation +} +``` + +### Automatically applying Clippy suggestions + +Clippy can automatically apply some lint suggestions, just like the compiler. + +```terminal +cargo clippy --fix +``` + +### Workspaces + +All the usual workspace options should work with Clippy. For example the +following command will run Clippy on the `example` crate in your workspace: + +```terminal +cargo clippy -p example +``` + +As with `cargo check`, this includes dependencies that are members of the +workspace, like path dependencies. If you want to run Clippy **only** on the +given crate, use the `--no-deps` option like this: + +```terminal +cargo clippy -p example -- --no-deps +``` + +## Using Clippy without `cargo`: `clippy-driver` + +Clippy can also be used in projects that do not use cargo. To do so, run +`clippy-driver` with the same arguments you use for `rustc`. For example: + +```terminal +clippy-driver --edition 2018 -Cpanic=abort foo.rs +``` + +> _Note:_ `clippy-driver` is designed for running Clippy and should not be used +> as a general replacement for `rustc`. `clippy-driver` may produce artifacts +> that are not optimized as expected, for example. + +[Installation]: installation.md +[CI]: continuous_integration diff --git a/clippy_dev/src/lint.rs b/clippy_dev/src/lint.rs index 9e463aa741c..71005449b4d 100644 --- a/clippy_dev/src/lint.rs +++ b/clippy_dev/src/lint.rs @@ -13,7 +13,7 @@ fn exit_if_err(status: io::Result) { } } -pub fn run<'a>(path: &str, args: impl Iterator) { +pub fn run<'a>(path: &str, args: impl Iterator) { let is_file = match fs::metadata(path) { Ok(metadata) => metadata.is_file(), Err(e) => { diff --git a/clippy_dev/src/main.rs b/clippy_dev/src/main.rs index ee535b1d3be..2c27a0bcaf9 100644 --- a/clippy_dev/src/main.rs +++ b/clippy_dev/src/main.rs @@ -2,7 +2,7 @@ // warn on lints, that are included in `rust-lang/rust`s bootstrap #![warn(rust_2018_idioms, unused_lifetimes)] -use clap::{Arg, ArgMatches, Command}; +use clap::{Arg, ArgAction, ArgMatches, Command, PossibleValue}; use clippy_dev::{bless, fmt, lint, new_lint, serve, setup, update_lints}; use indoc::indoc; fn main() { @@ -10,15 +10,15 @@ fn main() { match matches.subcommand() { Some(("bless", matches)) => { - bless::bless(matches.is_present("ignore-timestamp")); + bless::bless(matches.contains_id("ignore-timestamp")); }, Some(("fmt", matches)) => { - fmt::run(matches.is_present("check"), matches.is_present("verbose")); + fmt::run(matches.contains_id("check"), matches.contains_id("verbose")); }, Some(("update_lints", matches)) => { - if matches.is_present("print-only") { + if matches.contains_id("print-only") { update_lints::print_lints(); - } else if matches.is_present("check") { + } else if matches.contains_id("check") { update_lints::update(update_lints::UpdateMode::Check); } else { update_lints::update(update_lints::UpdateMode::Change); @@ -26,10 +26,10 @@ fn main() { }, Some(("new_lint", matches)) => { match new_lint::create( - matches.value_of("pass"), - matches.value_of("name"), - matches.value_of("category"), - matches.is_present("msrv"), + matches.get_one::("pass"), + matches.get_one::("name"), + matches.get_one::("category"), + matches.contains_id("msrv"), ) { Ok(_) => update_lints::update(update_lints::UpdateMode::Change), Err(e) => eprintln!("Unable to create lint: {}", e), @@ -37,28 +37,28 @@ fn main() { }, Some(("setup", sub_command)) => match sub_command.subcommand() { Some(("intellij", matches)) => { - if matches.is_present("remove") { + if matches.contains_id("remove") { setup::intellij::remove_rustc_src(); } else { setup::intellij::setup_rustc_src( matches - .value_of("rustc-repo-path") + .get_one::("rustc-repo-path") .expect("this field is mandatory and therefore always valid"), ); } }, Some(("git-hook", matches)) => { - if matches.is_present("remove") { + if matches.contains_id("remove") { setup::git_hook::remove_hook(); } else { - setup::git_hook::install_hook(matches.is_present("force-override")); + setup::git_hook::install_hook(matches.contains_id("force-override")); } }, Some(("vscode-tasks", matches)) => { - if matches.is_present("remove") { + if matches.contains_id("remove") { setup::vscode::remove_tasks(); } else { - setup::vscode::install_tasks(matches.is_present("force-override")); + setup::vscode::install_tasks(matches.contains_id("force-override")); } }, _ => {}, @@ -70,19 +70,19 @@ fn main() { _ => {}, }, Some(("serve", matches)) => { - let port = matches.value_of("port").unwrap().parse().unwrap(); - let lint = matches.value_of("lint"); + let port = *matches.get_one::("port").unwrap(); + let lint = matches.get_one::("lint"); serve::run(port, lint); }, Some(("lint", matches)) => { - let path = matches.value_of("path").unwrap(); - let args = matches.values_of("args").into_iter().flatten(); + let path = matches.get_one::("path").unwrap(); + let args = matches.get_many::("args").into_iter().flatten(); lint::run(path, args); }, Some(("rename_lint", matches)) => { - let old_name = matches.value_of("old_name").unwrap(); - let new_name = matches.value_of("new_name").unwrap_or(old_name); - let uplift = matches.is_present("uplift"); + let old_name = matches.get_one::("old_name").unwrap(); + let new_name = matches.get_one::("new_name").unwrap_or(old_name); + let uplift = matches.contains_id("uplift"); update_lints::rename(old_name, new_name, uplift); }, _ => {}, @@ -92,98 +92,86 @@ fn main() { fn get_clap_config() -> ArgMatches { Command::new("Clippy developer tooling") .arg_required_else_help(true) - .subcommand( + .subcommands([ Command::new("bless").about("bless the test output changes").arg( Arg::new("ignore-timestamp") .long("ignore-timestamp") .help("Include files updated before clippy was built"), ), - ) - .subcommand( Command::new("fmt") .about("Run rustfmt on all projects and tests") - .arg(Arg::new("check").long("check").help("Use the rustfmt --check option")) - .arg(Arg::new("verbose").short('v').long("verbose").help("Echo commands run")), - ) - .subcommand( + .args([ + Arg::new("check").long("check").help("Use the rustfmt --check option"), + Arg::new("verbose").short('v').long("verbose").help("Echo commands run"), + ]), Command::new("update_lints") .about("Updates lint registration and information from the source code") .long_about( "Makes sure that:\n \ - * the lint count in README.md is correct\n \ - * the changelog contains markdown link references at the bottom\n \ - * all lint groups include the correct lints\n \ - * lint modules in `clippy_lints/*` are visible in `src/lib.rs` via `pub mod`\n \ - * all lints are registered in the lint store", + * the lint count in README.md is correct\n \ + * the changelog contains markdown link references at the bottom\n \ + * all lint groups include the correct lints\n \ + * lint modules in `clippy_lints/*` are visible in `src/lib.rs` via `pub mod`\n \ + * all lints are registered in the lint store", ) - .arg(Arg::new("print-only").long("print-only").help( - "Print a table of lints to STDOUT. \ - This does not include deprecated and internal lints. \ - (Does not modify any files)", - )) - .arg( + .args([ + Arg::new("print-only").long("print-only").help( + "Print a table of lints to STDOUT. \ + This does not include deprecated and internal lints. \ + (Does not modify any files)", + ), Arg::new("check") .long("check") .help("Checks that `cargo dev update_lints` has been run. Used on CI."), - ), - ) - .subcommand( + ]), Command::new("new_lint") .about("Create new lint and run `cargo dev update_lints`") - .arg( + .args([ Arg::new("pass") .short('p') .long("pass") .help("Specify whether the lint runs during the early or late pass") .takes_value(true) - .possible_values(&["early", "late"]) + .value_parser([PossibleValue::new("early"), PossibleValue::new("late")]) .required(true), - ) - .arg( Arg::new("name") .short('n') .long("name") .help("Name of the new lint in snake case, ex: fn_too_long") .takes_value(true) .required(true), - ) - .arg( Arg::new("category") .short('c') .long("category") .help("What category the lint belongs to") .default_value("nursery") - .possible_values(&[ - "style", - "correctness", - "suspicious", - "complexity", - "perf", - "pedantic", - "restriction", - "cargo", - "nursery", - "internal", - "internal_warn", + .value_parser([ + PossibleValue::new("style"), + PossibleValue::new("correctness"), + PossibleValue::new("suspicious"), + PossibleValue::new("complexity"), + PossibleValue::new("perf"), + PossibleValue::new("pedantic"), + PossibleValue::new("restriction"), + PossibleValue::new("cargo"), + PossibleValue::new("nursery"), + PossibleValue::new("internal"), + PossibleValue::new("internal_warn"), ]) .takes_value(true), - ) - .arg(Arg::new("msrv").long("msrv").help("Add MSRV config code to the lint")), - ) - .subcommand( + Arg::new("msrv").long("msrv").help("Add MSRV config code to the lint"), + ]), Command::new("setup") .about("Support for setting up your personal development environment") .arg_required_else_help(true) - .subcommand( + .subcommands([ Command::new("intellij") .about("Alter dependencies so Intellij Rust can find rustc internals") - .arg( + .args([ Arg::new("remove") .long("remove") .help("Remove the dependencies added with 'cargo dev setup intellij'") .required(false), - ) - .arg( Arg::new("rustc-repo-path") .long("repo-path") .short('r') @@ -192,67 +180,53 @@ fn get_clap_config() -> ArgMatches { .value_name("path") .conflicts_with("remove") .required(true), - ), - ) - .subcommand( + ]), Command::new("git-hook") .about("Add a pre-commit git hook that formats your code to make it look pretty") - .arg( + .args([ Arg::new("remove") .long("remove") .help("Remove the pre-commit hook added with 'cargo dev setup git-hook'") .required(false), - ) - .arg( Arg::new("force-override") .long("force-override") .short('f') .help("Forces the override of an existing git pre-commit hook") .required(false), - ), - ) - .subcommand( + ]), Command::new("vscode-tasks") .about("Add several tasks to vscode for formatting, validation and testing") - .arg( + .args([ Arg::new("remove") .long("remove") .help("Remove the tasks added with 'cargo dev setup vscode-tasks'") .required(false), - ) - .arg( Arg::new("force-override") .long("force-override") .short('f') .help("Forces the override of existing vscode tasks") .required(false), - ), - ), - ) - .subcommand( + ]), + ]), Command::new("remove") .about("Support for undoing changes done by the setup command") .arg_required_else_help(true) - .subcommand(Command::new("git-hook").about("Remove any existing pre-commit git hook")) - .subcommand(Command::new("vscode-tasks").about("Remove any existing vscode tasks")) - .subcommand( + .subcommands([ + Command::new("git-hook").about("Remove any existing pre-commit git hook"), + Command::new("vscode-tasks").about("Remove any existing vscode tasks"), Command::new("intellij").about("Removes rustc source paths added via `cargo dev setup intellij`"), - ), - ) - .subcommand( + ]), Command::new("serve") .about("Launch a local 'ALL the Clippy Lints' website in a browser") - .arg( + .args([ Arg::new("port") .long("port") .short('p') .help("Local port for the http server") .default_value("8000") - .validator_os(serve::validate_port), - ) - .arg(Arg::new("lint").help("Which lint's page to load initially (optional)")), - ) - .subcommand( + .value_parser(clap::value_parser!(u16)), + Arg::new("lint").help("Which lint's page to load initially (optional)"), + ]), Command::new("lint") .about("Manually run clippy on a file or package") .after_help(indoc! {" @@ -271,37 +245,27 @@ fn get_clap_config() -> ArgMatches { cargo dev lint file.rs -- -W clippy::pedantic cargo dev lint ~/my-project -- -- -W clippy::pedantic "}) - .arg( + .args([ Arg::new("path") .required(true) .help("The path to a file or package directory to lint"), - ) - .arg( Arg::new("args") - .multiple_occurrences(true) + .action(ArgAction::Append) .help("Pass extra arguments to cargo/clippy-driver"), - ), - ) - .subcommand( - Command::new("rename_lint") - .about("Renames the given lint") - .arg( - Arg::new("old_name") - .index(1) - .required(true) - .help("The name of the lint to rename"), - ) - .arg( - Arg::new("new_name") - .index(2) - .required_unless_present("uplift") - .help("The new name of the lint"), - ) - .arg( - Arg::new("uplift") - .long("uplift") - .help("This lint will be uplifted into rustc"), - ), - ) + ]), + Command::new("rename_lint").about("Renames the given lint").args([ + Arg::new("old_name") + .index(1) + .required(true) + .help("The name of the lint to rename"), + Arg::new("new_name") + .index(2) + .required_unless_present("uplift") + .help("The new name of the lint"), + Arg::new("uplift") + .long("uplift") + .help("This lint will be uplifted into rustc"), + ]), + ]) .get_matches() } diff --git a/clippy_dev/src/new_lint.rs b/clippy_dev/src/new_lint.rs index 07d19638788..748d73c0801 100644 --- a/clippy_dev/src/new_lint.rs +++ b/clippy_dev/src/new_lint.rs @@ -34,7 +34,12 @@ impl Context for io::Result { /// # Errors /// /// This function errors out if the files couldn't be created or written to. -pub fn create(pass: Option<&str>, lint_name: Option<&str>, category: Option<&str>, msrv: bool) -> io::Result<()> { +pub fn create( + pass: Option<&String>, + lint_name: Option<&String>, + category: Option<&String>, + msrv: bool, +) -> io::Result<()> { let lint = LintData { pass: pass.expect("`pass` argument is validated by clap"), name: lint_name.expect("`name` argument is validated by clap"), diff --git a/clippy_dev/src/serve.rs b/clippy_dev/src/serve.rs index d55b1a354d0..f15f24da946 100644 --- a/clippy_dev/src/serve.rs +++ b/clippy_dev/src/serve.rs @@ -8,7 +8,7 @@ use std::time::{Duration, SystemTime}; /// # Panics /// /// Panics if the python commands could not be spawned -pub fn run(port: u16, lint: Option<&str>) -> ! { +pub fn run(port: u16, lint: Option<&String>) -> ! { let mut url = Some(match lint { None => format!("http://localhost:{}", port), Some(lint) => format!("http://localhost:{}/#{}", port, lint), diff --git a/clippy_dev/src/update_lints.rs b/clippy_dev/src/update_lints.rs index 5024e63bfa7..1bbd9a45b61 100644 --- a/clippy_dev/src/update_lints.rs +++ b/clippy_dev/src/update_lints.rs @@ -58,6 +58,16 @@ fn generate_lint_files( }, ); + replace_region_in_file( + update_mode, + Path::new("book/src/README.md"), + "[There are over ", + " lints included in this crate!]", + |res| { + write!(res, "{}", round_to_fifty(usable_lints.len())).unwrap(); + }, + ); + replace_region_in_file( update_mode, Path::new("CHANGELOG.md"), diff --git a/clippy_lints/Cargo.toml b/clippy_lints/Cargo.toml index 0a3f04da357..4d5bf47833f 100644 --- a/clippy_lints/Cargo.toml +++ b/clippy_lints/Cargo.toml @@ -10,6 +10,7 @@ edition = "2021" [dependencies] cargo_metadata = "0.14" +clippy_dev = { path = "../clippy_dev", optional = true } clippy_utils = { path = "../clippy_utils" } if_chain = "1.0" itertools = "0.10.1" @@ -18,6 +19,7 @@ quine-mc_cluskey = "0.2" regex-syntax = "0.6" serde = { version = "1.0", features = ["derive"] } serde_json = { version = "1.0", optional = true } +tempfile = { version = "3.2", optional = true } toml = "0.5" unicode-normalization = "0.1" unicode-script = { version = "0.5", default-features = false } @@ -30,7 +32,7 @@ url = { version = "2.2", features = ["serde"] } [features] deny-warnings = ["clippy_utils/deny-warnings"] # build clippy with internal lints enabled, off by default -internal = ["clippy_utils/internal", "serde_json"] +internal = ["clippy_utils/internal", "serde_json", "tempfile", "clippy_dev"] [package.metadata.rust-analyzer] # This crate uses #[feature(rustc_private)] diff --git a/clippy_lints/src/assertions_on_constants.rs b/clippy_lints/src/assertions_on_constants.rs index c82837746bd..2705ffffdcb 100644 --- a/clippy_lints/src/assertions_on_constants.rs +++ b/clippy_lints/src/assertions_on_constants.rs @@ -14,9 +14,6 @@ declare_clippy_lint! { /// Will be optimized out by the compiler or should probably be replaced by a /// `panic!()` or `unreachable!()` /// - /// ### Known problems - /// None - /// /// ### Example /// ```rust,ignore /// assert!(false) diff --git a/clippy_lints/src/async_yields_async.rs b/clippy_lints/src/async_yields_async.rs index 0619490e73c..27c2896e1e5 100644 --- a/clippy_lints/src/async_yields_async.rs +++ b/clippy_lints/src/async_yields_async.rs @@ -1,4 +1,4 @@ -use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::source::snippet; use clippy_utils::ty::implements_trait; use rustc_errors::Applicability; @@ -24,6 +24,7 @@ declare_clippy_lint! { /// }; /// } /// ``` + /// /// Use instead: /// ```rust /// async fn foo() {} @@ -63,9 +64,10 @@ impl<'tcx> LateLintPass<'tcx> for AsyncYieldsAsync { _ => None, }; if let Some(return_expr_span) = return_expr_span { - span_lint_and_then( + span_lint_hir_and_then( cx, ASYNC_YIELDS_ASYNC, + body.value.hir_id, return_expr_span, "an async construct yields a type which is itself awaitable", |db| { diff --git a/clippy_lints/src/attrs.rs b/clippy_lints/src/attrs.rs index 770cb6a3d7b..ed12ad9c367 100644 --- a/clippy_lints/src/attrs.rs +++ b/clippy_lints/src/attrs.rs @@ -340,7 +340,7 @@ impl<'tcx> LateLintPass<'tcx> for Attributes { for lint in lint_list { match item.kind { ItemKind::Use(..) => { - if is_word(lint, sym!(unused_imports)) + if is_word(lint, sym::unused_imports) || is_word(lint, sym::deprecated) || is_word(lint, sym!(unreachable_pub)) || is_word(lint, sym!(unused)) @@ -355,7 +355,7 @@ impl<'tcx> LateLintPass<'tcx> for Attributes { } }, ItemKind::ExternCrate(..) => { - if is_word(lint, sym!(unused_imports)) && skip_unused_imports { + if is_word(lint, sym::unused_imports) && skip_unused_imports { return; } if is_word(lint, sym!(unused_extern_crates)) { diff --git a/clippy_lints/src/await_holding_invalid.rs b/clippy_lints/src/await_holding_invalid.rs index 5b7c4591504..eee5f90d178 100644 --- a/clippy_lints/src/await_holding_invalid.rs +++ b/clippy_lints/src/await_holding_invalid.rs @@ -140,8 +140,6 @@ declare_clippy_lint! { /// from a memory access perspective but will cause bugs at runtime if they /// are held in such a way. /// - /// ### Known problems - /// /// ### Example /// /// ```toml diff --git a/clippy_lints/src/bool_assert_comparison.rs b/clippy_lints/src/bool_assert_comparison.rs index c50e214be28..95abe8aa59f 100644 --- a/clippy_lints/src/bool_assert_comparison.rs +++ b/clippy_lints/src/bool_assert_comparison.rs @@ -17,11 +17,12 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// // Bad /// assert_eq!("a".is_empty(), false); /// assert_ne!("a".is_empty(), true); + /// ``` /// - /// // Good + /// Use instead: + /// ```rust /// assert!(!"a".is_empty()); /// ``` #[clippy::version = "1.53.0"] diff --git a/clippy_lints/src/borrow_deref_ref.rs b/clippy_lints/src/borrow_deref_ref.rs index ec2f31cf673..1582ec9ee5c 100644 --- a/clippy_lints/src/borrow_deref_ref.rs +++ b/clippy_lints/src/borrow_deref_ref.rs @@ -18,7 +18,7 @@ declare_clippy_lint! { /// Dereferencing and then borrowing a reference value has no effect in most cases. /// /// ### Known problems - /// false negative on such code: + /// False negative on such code: /// ``` /// let x = &12; /// let addr_x = &x as *const _ as usize; @@ -29,17 +29,20 @@ declare_clippy_lint! { /// /// ### Example /// ```rust + /// fn foo(_x: &str) {} + /// /// let s = &String::new(); /// - /// // Bad /// let a: &String = &* s; /// foo(&*s); + /// ``` /// - /// // Good + /// Use instead: + /// ```rust + /// # fn foo(_x: &str) {} + /// # let s = &String::new(); /// let a: &String = s; /// foo(&**s); - /// - /// fn foo(_: &str){ } /// ``` #[clippy::version = "1.59.0"] pub BORROW_DEREF_REF, diff --git a/clippy_lints/src/casts/mod.rs b/clippy_lints/src/casts/mod.rs index daf3b7b4ce4..02c2f30a4dd 100644 --- a/clippy_lints/src/casts/mod.rs +++ b/clippy_lints/src/casts/mod.rs @@ -219,13 +219,14 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// // Bad /// fn fun() -> i32 { 1 } - /// let a = fun as i64; + /// let _ = fun as i64; + /// ``` /// - /// // Good - /// fn fun2() -> i32 { 1 } - /// let a = fun2 as usize; + /// Use instead: + /// ```rust + /// # fn fun() -> i32 { 1 } + /// let _ = fun as usize; /// ``` #[clippy::version = "pre 1.29.0"] pub FN_TO_NUMERIC_CAST, @@ -245,17 +246,19 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// // Bad /// fn fn1() -> i16 { /// 1 /// }; /// let _ = fn1 as i32; + /// ``` /// - /// // Better: Cast to usize first, then comment with the reason for the truncation - /// fn fn2() -> i16 { + /// Use instead: + /// ```rust + /// // Cast to usize first, then comment with the reason for the truncation + /// fn fn1() -> i16 { /// 1 /// }; - /// let fn_ptr = fn2 as usize; + /// let fn_ptr = fn1 as usize; /// let fn_ptr_truncated = fn_ptr as i32; /// ``` #[clippy::version = "pre 1.29.0"] @@ -277,19 +280,24 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// // Bad: fn1 is cast as `usize` + /// // fn1 is cast as `usize` /// fn fn1() -> u16 { /// 1 /// }; /// let _ = fn1 as usize; + /// ``` /// - /// // Good: maybe you intended to call the function? + /// Use instead: + /// ```rust + /// // maybe you intended to call the function? /// fn fn2() -> u16 { /// 1 /// }; /// let _ = fn2() as usize; /// - /// // Good: maybe you intended to cast it to a function type? + /// // or + /// + /// // maybe you intended to cast it to a function type? /// fn fn3() -> u16 { /// 1 /// } @@ -406,7 +414,7 @@ declare_clippy_lint! { /// enum E { X = 256 }; /// let _ = E::X as u8; /// ``` - #[clippy::version = "1.60.0"] + #[clippy::version = "1.61.0"] pub CAST_ENUM_TRUNCATION, suspicious, "casts from an enum type to an integral type which will truncate the value" @@ -451,7 +459,7 @@ declare_clippy_lint! { /// println!("{:?}", &*new_ptr); /// } /// ``` - #[clippy::version = "1.60.0"] + #[clippy::version = "1.61.0"] pub CAST_SLICE_DIFFERENT_SIZES, correctness, "casting using `as` between raw pointers to slices of types with different sizes" diff --git a/clippy_lints/src/checked_conversions.rs b/clippy_lints/src/checked_conversions.rs index 1010340c712..17fc81951f9 100644 --- a/clippy_lints/src/checked_conversions.rs +++ b/clippy_lints/src/checked_conversions.rs @@ -2,7 +2,7 @@ use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet_with_applicability; -use clippy_utils::{meets_msrv, msrvs, SpanlessEq}; +use clippy_utils::{in_constant, meets_msrv, msrvs, SpanlessEq}; use if_chain::if_chain; use rustc_ast::ast::LitKind; use rustc_errors::Applicability; @@ -22,18 +22,14 @@ declare_clippy_lint! { /// ### Example /// ```rust /// # let foo: u32 = 5; - /// # let _ = - /// foo <= i32::MAX as u32 - /// # ; + /// foo <= i32::MAX as u32; /// ``` /// - /// Could be written: - /// + /// Use instead: /// ```rust /// # let foo = 1; - /// # let _ = - /// i32::try_from(foo).is_ok() - /// # ; + /// # #[allow(unused)] + /// i32::try_from(foo).is_ok(); /// ``` #[clippy::version = "1.37.0"] pub CHECKED_CONVERSIONS, @@ -61,6 +57,7 @@ impl<'tcx> LateLintPass<'tcx> for CheckedConversions { } let result = if_chain! { + if !in_constant(cx, item.hir_id); if !in_external_macro(cx.sess(), item.span); if let ExprKind::Binary(op, left, right) = &item.kind; diff --git a/clippy_lints/src/collapsible_if.rs b/clippy_lints/src/collapsible_if.rs index 3eceb848822..90430b71a0e 100644 --- a/clippy_lints/src/collapsible_if.rs +++ b/clippy_lints/src/collapsible_if.rs @@ -32,20 +32,20 @@ declare_clippy_lint! { /// makes code look more complex than it really is. /// /// ### Example - /// ```rust,ignore + /// ```rust + /// # let (x, y) = (true, true); /// if x { /// if y { - /// … + /// // … /// } /// } - /// /// ``` /// /// Use instead: - /// - /// ```rust,ignore + /// ```rust + /// # let (x, y) = (true, true); /// if x && y { - /// … + /// // … /// } /// ``` #[clippy::version = "pre 1.29.0"] diff --git a/clippy_lints/src/comparison_chain.rs b/clippy_lints/src/comparison_chain.rs index 913e081af3b..a05b41eb3ab 100644 --- a/clippy_lints/src/comparison_chain.rs +++ b/clippy_lints/src/comparison_chain.rs @@ -35,7 +35,6 @@ declare_clippy_lint! { /// ``` /// /// Use instead: - /// /// ```rust,ignore /// use std::cmp::Ordering; /// # fn a() {} diff --git a/clippy_lints/src/copies.rs b/clippy_lints/src/copies.rs index 1e9a1153011..1deff9684a1 100644 --- a/clippy_lints/src/copies.rs +++ b/clippy_lints/src/copies.rs @@ -1,18 +1,18 @@ use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_then}; use clippy_utils::source::{first_line_of_span, indent_of, reindent_multiline, snippet, snippet_opt}; use clippy_utils::{ - both, count_eq, eq_expr_value, get_enclosing_block, get_parent_expr, if_sequence, is_else_clause, is_lint_allowed, - search_same, ContainsName, SpanlessEq, SpanlessHash, + eq_expr_value, get_enclosing_block, hash_expr, hash_stmt, if_sequence, is_else_clause, is_lint_allowed, + search_same, ContainsName, HirEqInterExpr, SpanlessEq, }; -use if_chain::if_chain; -use rustc_data_structures::fx::FxHashSet; -use rustc_errors::{Applicability, Diagnostic}; -use rustc_hir::intravisit::{self, Visitor}; -use rustc_hir::{Block, Expr, ExprKind, HirId}; -use rustc_lint::{LateContext, LateLintPass, LintContext}; -use rustc_middle::hir::nested_filter; +use core::iter; +use rustc_errors::Applicability; +use rustc_hir::intravisit; +use rustc_hir::{BinOpKind, Block, Expr, ExprKind, HirId, Stmt, StmtKind}; +use rustc_lint::{LateContext, LateLintPass}; use rustc_session::{declare_lint_pass, declare_tool_lint}; -use rustc_span::{source_map::Span, symbol::Symbol, BytePos}; +use rustc_span::hygiene::walk_chain; +use rustc_span::source_map::SourceMap; +use rustc_span::{BytePos, Span, Symbol}; use std::borrow::Cow; declare_clippy_lint! { @@ -165,243 +165,315 @@ declare_lint_pass!(CopyAndPaste => [ impl<'tcx> LateLintPass<'tcx> for CopyAndPaste { fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - if !expr.span.from_expansion() { - if let ExprKind::If(_, _, _) = expr.kind { - // skip ifs directly in else, it will be checked in the parent if - if let Some(&Expr { - kind: ExprKind::If(_, _, Some(else_expr)), - .. - }) = get_parent_expr(cx, expr) - { - if else_expr.hir_id == expr.hir_id { - return; - } - } - - let (conds, blocks) = if_sequence(expr); - // Conditions - lint_same_cond(cx, &conds); - lint_same_fns_in_if_cond(cx, &conds); - // Block duplication - lint_same_then_else(cx, &conds, &blocks, conds.len() == blocks.len(), expr); + if !expr.span.from_expansion() && matches!(expr.kind, ExprKind::If(..)) && !is_else_clause(cx.tcx, expr) { + let (conds, blocks) = if_sequence(expr); + lint_same_cond(cx, &conds); + lint_same_fns_in_if_cond(cx, &conds); + let all_same = + !is_lint_allowed(cx, IF_SAME_THEN_ELSE, expr.hir_id) && lint_if_same_then_else(cx, &conds, &blocks); + if !all_same && conds.len() != blocks.len() { + lint_branches_sharing_code(cx, &conds, &blocks, expr); } } } } -/// Implementation of `BRANCHES_SHARING_CODE` and `IF_SAME_THEN_ELSE` if the blocks are equal. -fn lint_same_then_else<'tcx>( +/// Checks if the given expression is a let chain. +fn contains_let(e: &Expr<'_>) -> bool { + match e.kind { + ExprKind::Let(..) => true, + ExprKind::Binary(op, lhs, rhs) if op.node == BinOpKind::And => { + matches!(lhs.kind, ExprKind::Let(..)) || contains_let(rhs) + }, + _ => false, + } +} + +fn lint_if_same_then_else(cx: &LateContext<'_>, conds: &[&Expr<'_>], blocks: &[&Block<'_>]) -> bool { + let mut eq = SpanlessEq::new(cx); + blocks + .array_windows::<2>() + .enumerate() + .fold(true, |all_eq, (i, &[lhs, rhs])| { + if eq.eq_block(lhs, rhs) && !contains_let(conds[i]) && conds.get(i + 1).map_or(true, |e| !contains_let(e)) { + span_lint_and_note( + cx, + IF_SAME_THEN_ELSE, + lhs.span, + "this `if` has identical blocks", + Some(rhs.span), + "same as this", + ); + all_eq + } else { + false + } + }) +} + +fn lint_branches_sharing_code<'tcx>( cx: &LateContext<'tcx>, conds: &[&'tcx Expr<'_>], blocks: &[&Block<'tcx>], - has_conditional_else: bool, expr: &'tcx Expr<'_>, ) { // We only lint ifs with multiple blocks - if blocks.len() < 2 || is_else_clause(cx.tcx, expr) { - return; - } - - // Check if each block has shared code - let has_expr = blocks[0].expr.is_some(); - - let (start_eq, mut end_eq, expr_eq) = if let Some(block_eq) = scan_block_for_eq(cx, conds, blocks) { - (block_eq.start_eq, block_eq.end_eq, block_eq.expr_eq) - } else { + let &[first_block, ref blocks @ ..] = blocks else { return; }; - - // BRANCHES_SHARING_CODE prerequisites - if has_conditional_else || (start_eq == 0 && end_eq == 0 && (has_expr && !expr_eq)) { + let &[.., last_block] = blocks else { return; - } - - // Only the start is the same - if start_eq != 0 && end_eq == 0 && (!has_expr || !expr_eq) { - let block = blocks[0]; - let start_stmts = block.stmts.split_at(start_eq).0; - - let mut start_walker = UsedValueFinderVisitor::new(cx); - for stmt in start_stmts { - intravisit::walk_stmt(&mut start_walker, stmt); - } + }; - emit_branches_sharing_code_lint( - cx, - start_eq, - 0, - false, - check_for_warn_of_moved_symbol(cx, &start_walker.def_symbols, expr), - blocks, - expr, - ); - } else if end_eq != 0 || (has_expr && expr_eq) { - let block = blocks[blocks.len() - 1]; - let (start_stmts, block_stmts) = block.stmts.split_at(start_eq); - let (block_stmts, end_stmts) = block_stmts.split_at(block_stmts.len() - end_eq); + let res = scan_block_for_eq(cx, conds, first_block, blocks); + let sm = cx.tcx.sess.source_map(); + let start_suggestion = res.start_span(first_block, sm).map(|span| { + let first_line_span = first_line_of_span(cx, expr.span); + let replace_span = first_line_span.with_hi(span.hi()); + let cond_span = first_line_span.until(first_block.span); + let cond_snippet = reindent_multiline(snippet(cx, cond_span, "_"), false, None); + let cond_indent = indent_of(cx, cond_span); + let moved_snippet = reindent_multiline(snippet(cx, span, "_"), true, None); + let suggestion = moved_snippet.to_string() + "\n" + &cond_snippet + "{"; + let suggestion = reindent_multiline(Cow::Borrowed(&suggestion), true, cond_indent); + (replace_span, suggestion.to_string()) + }); + let end_suggestion = res.end_span(last_block, sm).map(|span| { + let moved_snipped = reindent_multiline(snippet(cx, span, "_"), true, None); + let indent = indent_of(cx, expr.span.shrink_to_hi()); + let suggestion = "}\n".to_string() + &moved_snipped; + let suggestion = reindent_multiline(Cow::Borrowed(&suggestion), true, indent); - // Scan start - let mut start_walker = UsedValueFinderVisitor::new(cx); - for stmt in start_stmts { - intravisit::walk_stmt(&mut start_walker, stmt); + let span = span.with_hi(last_block.span.hi()); + // Improve formatting if the inner block has indention (i.e. normal Rust formatting) + let test_span = Span::new(span.lo() - BytePos(4), span.lo(), span.ctxt(), span.parent()); + let span = if snippet_opt(cx, test_span).map_or(false, |snip| snip == " ") { + span.with_lo(test_span.lo()) + } else { + span + }; + (span, suggestion.to_string()) + }); + + let (span, msg, end_span) = match (&start_suggestion, &end_suggestion) { + (&Some((span, _)), &Some((end_span, _))) => ( + span, + "all if blocks contain the same code at both the start and the end", + Some(end_span), + ), + (&Some((span, _)), None) => (span, "all if blocks contain the same code at the start", None), + (None, &Some((span, _))) => (span, "all if blocks contain the same code at the end", None), + (None, None) => return, + }; + span_lint_and_then(cx, BRANCHES_SHARING_CODE, span, msg, |diag| { + if let Some(span) = end_span { + diag.span_note(span, "this code is shared at the end"); } - let mut moved_syms = start_walker.def_symbols; - - // Scan block - let mut block_walker = UsedValueFinderVisitor::new(cx); - for stmt in block_stmts { - intravisit::walk_stmt(&mut block_walker, stmt); + if let Some((span, sugg)) = start_suggestion { + diag.span_suggestion( + span, + "consider moving these statements before the if", + sugg, + Applicability::Unspecified, + ); } - let mut block_defs = block_walker.defs; - - // Scan moved stmts - let mut moved_start: Option = None; - let mut end_walker = UsedValueFinderVisitor::new(cx); - for (index, stmt) in end_stmts.iter().enumerate() { - intravisit::walk_stmt(&mut end_walker, stmt); - - for value in &end_walker.uses { - // Well we can't move this and all prev statements. So reset - if block_defs.contains(value) { - moved_start = Some(index + 1); - end_walker.defs.drain().for_each(|x| { - block_defs.insert(x); - }); - - end_walker.def_symbols.clear(); - } + if let Some((span, sugg)) = end_suggestion { + diag.span_suggestion( + span, + "consider moving these statements after the if", + sugg, + Applicability::Unspecified, + ); + if !cx.typeck_results().expr_ty(expr).is_unit() { + diag.note("the end suggestion probably needs some adjustments to use the expression result correctly"); } - - end_walker.uses.clear(); } - - if let Some(moved_start) = moved_start { - end_eq -= moved_start; + if check_for_warn_of_moved_symbol(cx, &res.moved_locals, expr) { + diag.warn("some moved values might need to be renamed to avoid wrong references"); } + }); +} - let end_linable = block.expr.map_or_else( - || end_eq != 0, - |expr| { - intravisit::walk_expr(&mut end_walker, expr); - end_walker.uses.iter().any(|x| !block_defs.contains(x)) - }, - ); - - if end_linable { - end_walker.def_symbols.drain().for_each(|x| { - moved_syms.insert(x); - }); +struct BlockEq { + /// The end of the range of equal stmts at the start. + start_end_eq: usize, + /// The start of the range of equal stmts at the end. + end_begin_eq: Option, + /// The name and id of every local which can be moved at the beginning and the end. + moved_locals: Vec<(HirId, Symbol)>, +} +impl BlockEq { + fn start_span(&self, b: &Block<'_>, sm: &SourceMap) -> Option { + match &b.stmts[..self.start_end_eq] { + [first, .., last] => Some(sm.stmt_span(first.span, b.span).to(sm.stmt_span(last.span, b.span))), + [s] => Some(sm.stmt_span(s.span, b.span)), + [] => None, } + } - emit_branches_sharing_code_lint( - cx, - start_eq, - end_eq, - end_linable, - check_for_warn_of_moved_symbol(cx, &moved_syms, expr), - blocks, - expr, - ); + fn end_span(&self, b: &Block<'_>, sm: &SourceMap) -> Option { + match (&b.stmts[b.stmts.len() - self.end_begin_eq?..], b.expr) { + ([first, .., last], None) => Some(sm.stmt_span(first.span, b.span).to(sm.stmt_span(last.span, b.span))), + ([first, ..], Some(last)) => Some(sm.stmt_span(first.span, b.span).to(sm.stmt_span(last.span, b.span))), + ([s], None) => Some(sm.stmt_span(s.span, b.span)), + ([], Some(e)) => Some(walk_chain(e.span, b.span.ctxt())), + ([], None) => None, + } } } -struct BlockEqual { - /// The amount statements that are equal from the start - start_eq: usize, - /// The amount statements that are equal from the end - end_eq: usize, - /// An indication if the block expressions are the same. This will also be true if both are - /// `None` - expr_eq: bool, +/// If the statement is a local, checks if the bound names match the expected list of names. +fn eq_binding_names(s: &Stmt<'_>, names: &[(HirId, Symbol)]) -> bool { + if let StmtKind::Local(l) = s.kind { + let mut i = 0usize; + let mut res = true; + l.pat.each_binding_or_first(&mut |_, _, _, name| { + if names.get(i).map_or(false, |&(_, n)| n == name.name) { + i += 1; + } else { + res = false; + } + }); + res && i == names.len() + } else { + false + } } -/// This function can also trigger the `IF_SAME_THEN_ELSE` in which case it'll return `None` to -/// abort any further processing and avoid duplicate lint triggers. -fn scan_block_for_eq(cx: &LateContext<'_>, conds: &[&Expr<'_>], blocks: &[&Block<'_>]) -> Option { - let mut start_eq = usize::MAX; - let mut end_eq = usize::MAX; - let mut expr_eq = true; - let mut iter = blocks.windows(2).enumerate(); - while let Some((i, &[block0, block1])) = iter.next() { - let l_stmts = block0.stmts; - let r_stmts = block1.stmts; - - // `SpanlessEq` now keeps track of the locals and is therefore context sensitive clippy#6752. - // The comparison therefore needs to be done in a way that builds the correct context. - let mut evaluator = SpanlessEq::new(cx); - let mut evaluator = evaluator.inter_expr(); - - let current_start_eq = count_eq(&mut l_stmts.iter(), &mut r_stmts.iter(), |l, r| evaluator.eq_stmt(l, r)); +/// Checks if the given statement should be considered equal to the statement in the same position +/// for each block. +fn eq_stmts( + stmt: &Stmt<'_>, + blocks: &[&Block<'_>], + get_stmt: impl for<'a> Fn(&'a Block<'a>) -> Option<&'a Stmt<'a>>, + eq: &mut HirEqInterExpr<'_, '_, '_>, + moved_bindings: &mut Vec<(HirId, Symbol)>, +) -> bool { + (if let StmtKind::Local(l) = stmt.kind { + let old_count = moved_bindings.len(); + l.pat.each_binding_or_first(&mut |_, id, _, name| { + moved_bindings.push((id, name.name)); + }); + let new_bindings = &moved_bindings[old_count..]; + blocks + .iter() + .all(|b| get_stmt(b).map_or(false, |s| eq_binding_names(s, new_bindings))) + } else { + true + }) && blocks + .iter() + .all(|b| get_stmt(b).map_or(false, |s| eq.eq_stmt(s, stmt))) +} - let current_end_eq = { - // We skip the middle statements which can't be equal - let end_comparison_count = l_stmts.len().min(r_stmts.len()) - current_start_eq; - let it1 = l_stmts.iter().skip(l_stmts.len() - end_comparison_count); - let it2 = r_stmts.iter().skip(r_stmts.len() - end_comparison_count); - it1.zip(it2) - .fold(0, |acc, (l, r)| if evaluator.eq_stmt(l, r) { acc + 1 } else { 0 }) +fn scan_block_for_eq(cx: &LateContext<'_>, _conds: &[&Expr<'_>], block: &Block<'_>, blocks: &[&Block<'_>]) -> BlockEq { + let mut eq = SpanlessEq::new(cx); + let mut eq = eq.inter_expr(); + let mut moved_locals = Vec::new(); + + let start_end_eq = block + .stmts + .iter() + .enumerate() + .find(|&(i, stmt)| !eq_stmts(stmt, blocks, |b| b.stmts.get(i), &mut eq, &mut moved_locals)) + .map_or(block.stmts.len(), |(i, _)| i); + + // Walk backwards through the final expression/statements so long as their hashes are equal. Note + // `SpanlessHash` treats all local references as equal allowing locals declared earlier in the block + // to match those in other blocks. e.g. If each block ends with the following the hash value will be + // the same even though each `x` binding will have a different `HirId`: + // let x = foo(); + // x + 50 + let expr_hash_eq = if let Some(e) = block.expr { + let hash = hash_expr(cx, e); + blocks + .iter() + .all(|b| b.expr.map_or(false, |e| hash_expr(cx, e) == hash)) + } else { + blocks.iter().all(|b| b.expr.is_none()) + }; + if !expr_hash_eq { + return BlockEq { + start_end_eq, + end_begin_eq: None, + moved_locals, }; - let block_expr_eq = both(&block0.expr, &block1.expr, |l, r| evaluator.eq_expr(l, r)); - - // IF_SAME_THEN_ELSE - if_chain! { - if block_expr_eq; - if l_stmts.len() == r_stmts.len(); - if l_stmts.len() == current_start_eq; - // `conds` may have one last item than `blocks`. - // Any `i` from `blocks.windows(2)` will exist in `conds`, but `i+1` may not exist on the last iteration. - if !matches!(conds[i].kind, ExprKind::Let(..)); - if !matches!(conds.get(i + 1).map(|e| &e.kind), Some(ExprKind::Let(..))); - if !is_lint_allowed(cx, IF_SAME_THEN_ELSE, block0.hir_id); - if !is_lint_allowed(cx, IF_SAME_THEN_ELSE, block1.hir_id); - then { - span_lint_and_note( - cx, - IF_SAME_THEN_ELSE, - block0.span, - "this `if` has identical blocks", - Some(block1.span), - "same as this", - ); - - return None; + } + let end_search_start = block.stmts[start_end_eq..] + .iter() + .rev() + .enumerate() + .find(|&(offset, stmt)| { + let hash = hash_stmt(cx, stmt); + blocks.iter().any(|b| { + b.stmts + // the bounds check will catch the underflow + .get(b.stmts.len().wrapping_sub(offset + 1)) + .map_or(true, |s| hash != hash_stmt(cx, s)) + }) + }) + .map_or(block.stmts.len() - start_end_eq, |(i, _)| i); + + let moved_locals_at_start = moved_locals.len(); + let mut i = end_search_start; + let end_begin_eq = block.stmts[block.stmts.len() - end_search_start..] + .iter() + .zip(iter::repeat_with(move || { + let x = i; + i -= 1; + x + })) + .fold(end_search_start, |init, (stmt, offset)| { + if eq_stmts( + stmt, + blocks, + |b| b.stmts.get(b.stmts.len() - offset), + &mut eq, + &mut moved_locals, + ) { + init + } else { + // Clear out all locals seen at the end so far. None of them can be moved. + let stmts = &blocks[0].stmts; + for stmt in &stmts[stmts.len() - init..=stmts.len() - offset] { + if let StmtKind::Local(l) = stmt.kind { + l.pat.each_binding_or_first(&mut |_, id, _, _| { + eq.locals.remove(&id); + }); + } + } + moved_locals.truncate(moved_locals_at_start); + offset - 1 + } + }); + if let Some(e) = block.expr { + for block in blocks { + if block.expr.map_or(false, |expr| !eq.eq_expr(expr, e)) { + moved_locals.truncate(moved_locals_at_start); + return BlockEq { + start_end_eq, + end_begin_eq: None, + moved_locals, + }; } } - - start_eq = start_eq.min(current_start_eq); - end_eq = end_eq.min(current_end_eq); - expr_eq &= block_expr_eq; - } - - if !expr_eq { - end_eq = 0; } - // Check if the regions are overlapping. Set `end_eq` to prevent the overlap - let min_block_size = blocks.iter().map(|x| x.stmts.len()).min().unwrap(); - if (start_eq + end_eq) > min_block_size { - end_eq = min_block_size - start_eq; + BlockEq { + start_end_eq, + end_begin_eq: Some(end_begin_eq), + moved_locals, } - - Some(BlockEqual { - start_eq, - end_eq, - expr_eq, - }) } -fn check_for_warn_of_moved_symbol(cx: &LateContext<'_>, symbols: &FxHashSet, if_expr: &Expr<'_>) -> bool { +fn check_for_warn_of_moved_symbol(cx: &LateContext<'_>, symbols: &[(HirId, Symbol)], if_expr: &Expr<'_>) -> bool { get_enclosing_block(cx, if_expr.hir_id).map_or(false, |block| { let ignore_span = block.span.shrink_to_lo().to(if_expr.span); symbols .iter() - .filter(|sym| !sym.as_str().starts_with('_')) - .any(move |sym| { - let mut walker = ContainsName { - name: *sym, - result: false, - }; + .filter(|&&(_, name)| !name.as_str().starts_with('_')) + .any(|&(_, name)| { + let mut walker = ContainsName { name, result: false }; // Scan block block @@ -419,194 +491,9 @@ fn check_for_warn_of_moved_symbol(cx: &LateContext<'_>, symbols: &FxHashSet, - start_stmts: usize, - end_stmts: usize, - lint_end: bool, - warn_about_moved_symbol: bool, - blocks: &[&Block<'_>], - if_expr: &Expr<'_>, -) { - if start_stmts == 0 && !lint_end { - return; - } - - // (help, span, suggestion) - let mut suggestions: Vec<(&str, Span, String)> = vec![]; - let mut add_expr_note = false; - - // Construct suggestions - let sm = cx.sess().source_map(); - if start_stmts > 0 { - let block = blocks[0]; - let span_start = first_line_of_span(cx, if_expr.span).shrink_to_lo(); - let span_end = sm.stmt_span(block.stmts[start_stmts - 1].span, block.span); - - let cond_span = first_line_of_span(cx, if_expr.span).until(block.span); - let cond_snippet = reindent_multiline(snippet(cx, cond_span, "_"), false, None); - let cond_indent = indent_of(cx, cond_span); - let moved_span = block.stmts[0].span.source_callsite().to(span_end); - let moved_snippet = reindent_multiline(snippet(cx, moved_span, "_"), true, None); - let suggestion = moved_snippet.to_string() + "\n" + &cond_snippet + "{"; - let suggestion = reindent_multiline(Cow::Borrowed(&suggestion), true, cond_indent); - - let span = span_start.to(span_end); - suggestions.push(("start", span, suggestion.to_string())); - } - - if lint_end { - let block = blocks[blocks.len() - 1]; - let span_end = block.span.shrink_to_hi(); - - let moved_start = if end_stmts == 0 && block.expr.is_some() { - block.expr.unwrap().span.source_callsite() - } else { - sm.stmt_span(block.stmts[block.stmts.len() - end_stmts].span, block.span) - }; - let moved_end = block.expr.map_or_else( - || sm.stmt_span(block.stmts[block.stmts.len() - 1].span, block.span), - |expr| expr.span.source_callsite(), - ); - - let moved_span = moved_start.to(moved_end); - let moved_snipped = reindent_multiline(snippet(cx, moved_span, "_"), true, None); - let indent = indent_of(cx, if_expr.span.shrink_to_hi()); - let suggestion = "}\n".to_string() + &moved_snipped; - let suggestion = reindent_multiline(Cow::Borrowed(&suggestion), true, indent); - - let mut span = moved_start.to(span_end); - // Improve formatting if the inner block has indention (i.e. normal Rust formatting) - let test_span = Span::new(span.lo() - BytePos(4), span.lo(), span.ctxt(), span.parent()); - if snippet_opt(cx, test_span) - .map(|snip| snip == " ") - .unwrap_or_default() - { - span = span.with_lo(test_span.lo()); - } - - suggestions.push(("end", span, suggestion.to_string())); - add_expr_note = !cx.typeck_results().expr_ty(if_expr).is_unit(); - } - - let add_optional_msgs = |diag: &mut Diagnostic| { - if add_expr_note { - diag.note("The end suggestion probably needs some adjustments to use the expression result correctly"); - } - - if warn_about_moved_symbol { - diag.warn("Some moved values might need to be renamed to avoid wrong references"); - } - }; - - // Emit lint - if suggestions.len() == 1 { - let (place_str, span, sugg) = suggestions.pop().unwrap(); - let msg = format!("all if blocks contain the same code at the {}", place_str); - let help = format!("consider moving the {} statements out like this", place_str); - span_lint_and_then(cx, BRANCHES_SHARING_CODE, span, msg.as_str(), |diag| { - diag.span_suggestion(span, help.as_str(), sugg, Applicability::Unspecified); - - add_optional_msgs(diag); - }); - } else if suggestions.len() == 2 { - let (_, end_span, end_sugg) = suggestions.pop().unwrap(); - let (_, start_span, start_sugg) = suggestions.pop().unwrap(); - span_lint_and_then( - cx, - BRANCHES_SHARING_CODE, - start_span, - "all if blocks contain the same code at the start and the end. Here at the start", - move |diag| { - diag.span_note(end_span, "and here at the end"); - - diag.span_suggestion( - start_span, - "consider moving the start statements out like this", - start_sugg, - Applicability::Unspecified, - ); - - diag.span_suggestion( - end_span, - "and consider moving the end statements out like this", - end_sugg, - Applicability::Unspecified, - ); - - add_optional_msgs(diag); - }, - ); - } -} - -/// This visitor collects `HirId`s and Symbols of defined symbols and `HirId`s of used values. -struct UsedValueFinderVisitor<'a, 'tcx> { - cx: &'a LateContext<'tcx>, - - /// The `HirId`s of defined values in the scanned statements - defs: FxHashSet, - - /// The Symbols of the defined symbols in the scanned statements - def_symbols: FxHashSet, - - /// The `HirId`s of the used values - uses: FxHashSet, -} - -impl<'a, 'tcx> UsedValueFinderVisitor<'a, 'tcx> { - fn new(cx: &'a LateContext<'tcx>) -> Self { - UsedValueFinderVisitor { - cx, - defs: FxHashSet::default(), - def_symbols: FxHashSet::default(), - uses: FxHashSet::default(), - } - } -} - -impl<'a, 'tcx> Visitor<'tcx> for UsedValueFinderVisitor<'a, 'tcx> { - type NestedFilter = nested_filter::All; - - fn nested_visit_map(&mut self) -> Self::Map { - self.cx.tcx.hir() - } - - fn visit_local(&mut self, l: &'tcx rustc_hir::Local<'tcx>) { - let local_id = l.pat.hir_id; - self.defs.insert(local_id); - - if let Some(sym) = l.pat.simple_ident() { - self.def_symbols.insert(sym.name); - } - - if let Some(expr) = l.init { - intravisit::walk_expr(self, expr); - } - } - - fn visit_qpath(&mut self, qpath: &'tcx rustc_hir::QPath<'tcx>, id: HirId, _span: rustc_span::Span) { - if let rustc_hir::QPath::Resolved(_, path) = *qpath { - if path.segments.len() == 1 { - if let rustc_hir::def::Res::Local(var) = self.cx.qpath_res(qpath, id) { - self.uses.insert(var); - } - } - } - } -} - /// Implementation of `IFS_SAME_COND`. fn lint_same_cond(cx: &LateContext<'_>, conds: &[&Expr<'_>]) { - let hash: &dyn Fn(&&Expr<'_>) -> u64 = &|expr| -> u64 { - let mut h = SpanlessHash::new(cx); - h.hash_expr(expr); - h.finish() - }; - - let eq: &dyn Fn(&&Expr<'_>, &&Expr<'_>) -> bool = &|&lhs, &rhs| -> bool { eq_expr_value(cx, lhs, rhs) }; - - for (i, j) in search_same(conds, hash, eq) { + for (i, j) in search_same(conds, |e| hash_expr(cx, e), |lhs, rhs| eq_expr_value(cx, lhs, rhs)) { span_lint_and_note( cx, IFS_SAME_COND, @@ -620,12 +507,6 @@ fn lint_same_cond(cx: &LateContext<'_>, conds: &[&Expr<'_>]) { /// Implementation of `SAME_FUNCTIONS_IN_IF_CONDITION`. fn lint_same_fns_in_if_cond(cx: &LateContext<'_>, conds: &[&Expr<'_>]) { - let hash: &dyn Fn(&&Expr<'_>) -> u64 = &|expr| -> u64 { - let mut h = SpanlessHash::new(cx); - h.hash_expr(expr); - h.finish() - }; - let eq: &dyn Fn(&&Expr<'_>, &&Expr<'_>) -> bool = &|&lhs, &rhs| -> bool { // Do not lint if any expr originates from a macro if lhs.span.from_expansion() || rhs.span.from_expansion() { @@ -638,7 +519,7 @@ fn lint_same_fns_in_if_cond(cx: &LateContext<'_>, conds: &[&Expr<'_>]) { SpanlessEq::new(cx).eq_expr(lhs, rhs) }; - for (i, j) in search_same(conds, hash, eq) { + for (i, j) in search_same(conds, |e| hash_expr(cx, e), eq) { span_lint_and_note( cx, SAME_FUNCTIONS_IN_IF_CONDITION, diff --git a/clippy_lints/src/create_dir.rs b/clippy_lints/src/create_dir.rs index 6bc4054a5ab..18d34370a7b 100644 --- a/clippy_lints/src/create_dir.rs +++ b/clippy_lints/src/create_dir.rs @@ -15,12 +15,12 @@ declare_clippy_lint! { /// Sometimes `std::fs::create_dir` is mistakenly chosen over `std::fs::create_dir_all`. /// /// ### Example - /// - /// ```rust + /// ```rust,ignore /// std::fs::create_dir("foo"); /// ``` + /// /// Use instead: - /// ```rust + /// ```rust,ignore /// std::fs::create_dir_all("foo"); /// ``` #[clippy::version = "1.48.0"] diff --git a/clippy_lints/src/dbg_macro.rs b/clippy_lints/src/dbg_macro.rs index 17deccf8c39..fe9f4f9ae3c 100644 --- a/clippy_lints/src/dbg_macro.rs +++ b/clippy_lints/src/dbg_macro.rs @@ -18,10 +18,11 @@ declare_clippy_lint! { /// /// ### Example /// ```rust,ignore - /// // Bad /// dbg!(true) + /// ``` /// - /// // Good + /// Use instead: + /// ```rust,ignore /// true /// ``` #[clippy::version = "1.34.0"] diff --git a/clippy_lints/src/default.rs b/clippy_lints/src/default.rs index 243dfd3a461..d99a1aa2969 100644 --- a/clippy_lints/src/default.rs +++ b/clippy_lints/src/default.rs @@ -18,15 +18,16 @@ declare_clippy_lint! { /// Checks for literal calls to `Default::default()`. /// /// ### Why is this bad? - /// It's more clear to the reader to use the name of the type whose default is - /// being gotten than the generic `Default`. + /// It's easier for the reader if the name of the type is used, rather than the + /// generic `Default`. /// /// ### Example /// ```rust - /// // Bad /// let s: String = Default::default(); + /// ``` /// - /// // Good + /// Use instead: + /// ```rust /// let s = String::default(); /// ``` #[clippy::version = "pre 1.29.0"] @@ -47,13 +48,13 @@ declare_clippy_lint! { /// Assignments to patterns that are of tuple type are not linted. /// /// ### Example - /// Bad: /// ``` /// # #[derive(Default)] /// # struct A { i: i32 } /// let mut a: A = Default::default(); /// a.i = 42; /// ``` + /// /// Use instead: /// ``` /// # #[derive(Default)] diff --git a/clippy_lints/src/default_numeric_fallback.rs b/clippy_lints/src/default_numeric_fallback.rs index 3d9f9ed41ce..fb418a3251f 100644 --- a/clippy_lints/src/default_numeric_fallback.rs +++ b/clippy_lints/src/default_numeric_fallback.rs @@ -1,4 +1,4 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; +use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::numeric_literal; use clippy_utils::source::snippet_opt; use if_chain::if_chain; @@ -76,7 +76,7 @@ impl<'a, 'tcx> NumericFallbackVisitor<'a, 'tcx> { } /// Check whether a passed literal has potential to cause fallback or not. - fn check_lit(&self, lit: &Lit, lit_ty: Ty<'tcx>) { + fn check_lit(&self, lit: &Lit, lit_ty: Ty<'tcx>, emit_hir_id: HirId) { if_chain! { if !in_external_macro(self.cx.sess(), lit.span); if let Some(ty_bound) = self.ty_bounds.last(); @@ -101,14 +101,15 @@ impl<'a, 'tcx> NumericFallbackVisitor<'a, 'tcx> { } }; let sugg = numeric_literal::format(&src, Some(suffix), is_float); - span_lint_and_sugg( + span_lint_hir_and_then( self.cx, DEFAULT_NUMERIC_FALLBACK, + emit_hir_id, lit.span, "default numeric fallback might occur", - "consider adding suffix", - sugg, - Applicability::MaybeIncorrect, + |diag| { + diag.span_suggestion(lit.span, "consider adding suffix", sugg, Applicability::MaybeIncorrect); + } ); } } @@ -179,7 +180,7 @@ impl<'a, 'tcx> Visitor<'tcx> for NumericFallbackVisitor<'a, 'tcx> { ExprKind::Lit(lit) => { let ty = self.cx.typeck_results().expr_ty(expr); - self.check_lit(lit, ty); + self.check_lit(lit, ty, expr.hir_id); return; }, diff --git a/clippy_lints/src/dereference.rs b/clippy_lints/src/dereference.rs index 527529965a9..b47441eff37 100644 --- a/clippy_lints/src/dereference.rs +++ b/clippy_lints/src/dereference.rs @@ -1,4 +1,4 @@ -use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; +use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then}; use clippy_utils::source::{snippet_with_applicability, snippet_with_context}; use clippy_utils::sugg::has_enclosing_paren; use clippy_utils::ty::peel_mid_ty_refs; @@ -30,13 +30,14 @@ declare_clippy_lint! { /// let a: &mut String = &mut String::from("foo"); /// let b: &str = a.deref(); /// ``` - /// Could be written as: + /// + /// Use instead: /// ```rust /// let a: &mut String = &mut String::from("foo"); /// let b = &*a; /// ``` /// - /// This lint excludes + /// This lint excludes: /// ```rust,ignore /// let _ = d.unwrap().deref(); /// ``` @@ -59,11 +60,13 @@ declare_clippy_lint! { /// ```rust /// fn fun(_a: &i32) {} /// - /// // Bad /// let x: &i32 = &&&&&&5; /// fun(&x); + /// ``` /// - /// // Good + /// Use instead: + /// ```rust + /// # fn fun(_a: &i32) {} /// let x: &i32 = &5; /// fun(x); /// ``` @@ -82,13 +85,14 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// // Bad /// let x = Some(""); /// if let Some(ref x) = x { /// // use `x` here /// } + /// ``` /// - /// // Good + /// Use instead: + /// ```rust /// let x = Some(""); /// if let Some(x) = x { /// // use `&x` here @@ -131,6 +135,7 @@ pub struct Dereferencing { struct StateData { /// Span of the top level expression span: Span, + hir_id: HirId, } enum State { @@ -165,6 +170,8 @@ struct RefPat { app: Applicability, /// All the replacements which need to be made. replacements: Vec<(Span, String)>, + /// The [`HirId`] that the lint should be emitted at. + hir_id: HirId, } impl<'tcx> LateLintPass<'tcx> for Dereferencing { @@ -218,7 +225,10 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing { is_final_ufcs: matches!(expr.kind, ExprKind::Call(..)), target_mut, }, - StateData { span: expr.span }, + StateData { + span: expr.span, + hir_id: expr.hir_id, + }, )); }, RefOp::AddrOf => { @@ -290,7 +300,10 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing { required_precedence, msg, }, - StateData { span: expr.span }, + StateData { + span: expr.span, + hir_id: expr.hir_id, + }, )); } }, @@ -383,6 +396,7 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing { spans: vec![pat.span], app, replacements: vec![(pat.span, snip.into())], + hir_id: pat.hir_id }), ); } @@ -395,13 +409,15 @@ impl<'tcx> LateLintPass<'tcx> for Dereferencing { for pat in self.ref_locals.drain(..).filter_map(|(_, x)| x) { let replacements = pat.replacements; let app = pat.app; - span_lint_and_then( + let lint = if pat.always_deref { + NEEDLESS_BORROW + } else { + REF_BINDING_TO_REFERENCE + }; + span_lint_hir_and_then( cx, - if pat.always_deref { - NEEDLESS_BORROW - } else { - REF_BINDING_TO_REFERENCE - }, + lint, + pat.hir_id, pat.spans, "this pattern creates a reference to a reference", |diag| { @@ -638,19 +654,14 @@ fn report<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, state: State, data: S } => { let mut app = Applicability::MachineApplicable; let snip = snippet_with_context(cx, expr.span, data.span.ctxt(), "..", &mut app).0; - span_lint_and_sugg( - cx, - NEEDLESS_BORROW, - data.span, - msg, - "change this to", - if required_precedence > expr.precedence().order() && !has_enclosing_paren(&snip) { + span_lint_hir_and_then(cx, NEEDLESS_BORROW, data.hir_id, data.span, msg, |diag| { + let sugg = if required_precedence > expr.precedence().order() && !has_enclosing_paren(&snip) { format!("({})", snip) } else { snip.into() - }, - app, - ); + }; + diag.span_suggestion(data.span, "change this to", sugg, app); + }); }, } } diff --git a/clippy_lints/src/derivable_impls.rs b/clippy_lints/src/derivable_impls.rs index e98691fd5bb..4d7f4076d7b 100644 --- a/clippy_lints/src/derivable_impls.rs +++ b/clippy_lints/src/derivable_impls.rs @@ -30,8 +30,7 @@ declare_clippy_lint! { /// } /// ``` /// - /// Could be written as: - /// + /// Use instead: /// ```rust /// #[derive(Default)] /// struct Foo { @@ -45,7 +44,6 @@ declare_clippy_lint! { /// specialized than what derive will produce. This lint can't detect the manual `impl` /// has exactly equal bounds, and therefore this lint is disabled for types with /// generic parameters. - /// #[clippy::version = "1.57.0"] pub DERIVABLE_IMPLS, complexity, diff --git a/clippy_lints/src/derive.rs b/clippy_lints/src/derive.rs index 99347ebadc6..28f218a8e34 100644 --- a/clippy_lints/src/derive.rs +++ b/clippy_lints/src/derive.rs @@ -4,14 +4,19 @@ use clippy_utils::ty::{implements_trait, implements_trait_with_env, is_copy}; use clippy_utils::{is_lint_allowed, match_def_path}; use if_chain::if_chain; use rustc_errors::Applicability; +use rustc_hir::def_id::DefId; use rustc_hir::intravisit::{walk_expr, walk_fn, walk_item, FnKind, Visitor}; use rustc_hir::{ - self as hir, BlockCheckMode, BodyId, Expr, ExprKind, FnDecl, HirId, Impl, Item, ItemKind, UnsafeSource, Unsafety, + self as hir, BlockCheckMode, BodyId, Constness, Expr, ExprKind, FnDecl, HirId, Impl, Item, ItemKind, UnsafeSource, + Unsafety, }; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::hir::nested_filter; -use rustc_middle::ty::subst::GenericArg; -use rustc_middle::ty::{self, BoundConstness, ImplPolarity, ParamEnv, PredicateKind, TraitPredicate, TraitRef, Ty}; +use rustc_middle::traits::Reveal; +use rustc_middle::ty::{ + self, Binder, BoundConstness, GenericParamDefKind, ImplPolarity, ParamEnv, PredicateKind, TraitPredicate, TraitRef, + Ty, TyCtxt, Visibility, +}; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::source_map::Span; use rustc_span::sym; @@ -459,50 +464,18 @@ impl<'tcx> Visitor<'tcx> for UnsafeVisitor<'_, 'tcx> { fn check_partial_eq_without_eq<'tcx>(cx: &LateContext<'tcx>, span: Span, trait_ref: &hir::TraitRef<'_>, ty: Ty<'tcx>) { if_chain! { if let ty::Adt(adt, substs) = ty.kind(); + if cx.tcx.visibility(adt.did()) == Visibility::Public; if let Some(eq_trait_def_id) = cx.tcx.get_diagnostic_item(sym::Eq); - if let Some(peq_trait_def_id) = cx.tcx.get_diagnostic_item(sym::PartialEq); if let Some(def_id) = trait_ref.trait_def_id(); if cx.tcx.is_diagnostic_item(sym::PartialEq, def_id); - // New `ParamEnv` replacing `T: PartialEq` with `T: Eq` - let param_env = ParamEnv::new( - cx.tcx.mk_predicates(cx.param_env.caller_bounds().iter().map(|p| { - let kind = p.kind(); - match kind.skip_binder() { - PredicateKind::Trait(p) - if p.trait_ref.def_id == peq_trait_def_id - && p.trait_ref.substs.get(0) == p.trait_ref.substs.get(1) - && matches!(p.trait_ref.self_ty().kind(), ty::Param(_)) - && p.constness == BoundConstness::NotConst - && p.polarity == ImplPolarity::Positive => - { - cx.tcx.mk_predicate(kind.rebind(PredicateKind::Trait(TraitPredicate { - trait_ref: TraitRef::new( - eq_trait_def_id, - cx.tcx.mk_substs([GenericArg::from(p.trait_ref.self_ty())].into_iter()), - ), - constness: BoundConstness::NotConst, - polarity: ImplPolarity::Positive, - }))) - }, - _ => p, - } - })), - cx.param_env.reveal(), - cx.param_env.constness(), - ); - if !implements_trait_with_env(cx.tcx, param_env, ty, eq_trait_def_id, substs); + let param_env = param_env_for_derived_eq(cx.tcx, adt.did(), eq_trait_def_id); + if !implements_trait_with_env(cx.tcx, param_env, ty, eq_trait_def_id, &[]); + // If all of our fields implement `Eq`, we can implement `Eq` too + if adt + .all_fields() + .map(|f| f.ty(cx.tcx, substs)) + .all(|ty| implements_trait_with_env(cx.tcx, param_env, ty, eq_trait_def_id, &[])); then { - // If all of our fields implement `Eq`, we can implement `Eq` too - for variant in adt.variants() { - for field in &variant.fields { - let ty = field.ty(cx.tcx, substs); - - if !implements_trait(cx, ty, eq_trait_def_id, substs) { - return; - } - } - } - span_lint_and_sugg( cx, DERIVE_PARTIAL_EQ_WITHOUT_EQ, @@ -515,3 +488,41 @@ fn check_partial_eq_without_eq<'tcx>(cx: &LateContext<'tcx>, span: Span, trait_r } } } + +/// Creates the `ParamEnv` used for the give type's derived `Eq` impl. +fn param_env_for_derived_eq(tcx: TyCtxt<'_>, did: DefId, eq_trait_id: DefId) -> ParamEnv<'_> { + // Initial map from generic index to param def. + // Vec<(param_def, needs_eq)> + let mut params = tcx + .generics_of(did) + .params + .iter() + .map(|p| (p, matches!(p.kind, GenericParamDefKind::Type { .. }))) + .collect::>(); + + let ty_predicates = tcx.predicates_of(did).predicates; + for (p, _) in ty_predicates { + if let PredicateKind::Trait(p) = p.kind().skip_binder() + && p.trait_ref.def_id == eq_trait_id + && let ty::Param(self_ty) = p.trait_ref.self_ty().kind() + && p.constness == BoundConstness::NotConst + { + // Flag types which already have an `Eq` bound. + params[self_ty.index as usize].1 = false; + } + } + + ParamEnv::new( + tcx.mk_predicates(ty_predicates.iter().map(|&(p, _)| p).chain( + params.iter().filter(|&&(_, needs_eq)| needs_eq).map(|&(param, _)| { + tcx.mk_predicate(Binder::dummy(PredicateKind::Trait(TraitPredicate { + trait_ref: TraitRef::new(eq_trait_id, tcx.mk_substs([tcx.mk_param_from_def(param)].into_iter())), + constness: BoundConstness::NotConst, + polarity: ImplPolarity::Positive, + }))) + }), + )), + Reveal::UserFacing, + Constness::NotConst, + ) +} diff --git a/clippy_lints/src/doc.rs b/clippy_lints/src/doc.rs index aaec88f50c7..da111e7378e 100644 --- a/clippy_lints/src/doc.rs +++ b/clippy_lints/src/doc.rs @@ -163,7 +163,7 @@ declare_clippy_lint! { /// } /// } /// ``` - #[clippy::version = "1.52.0"] + #[clippy::version = "1.51.0"] pub MISSING_PANICS_DOC, pedantic, "`pub fn` may panic without `# Panics` in doc comment" @@ -178,7 +178,7 @@ declare_clippy_lint! { /// if the `fn main()` is left implicit. /// /// ### Examples - /// ``````rust + /// ```rust /// /// An example of a doctest with a `main()` function /// /// /// /// # Examples @@ -191,7 +191,7 @@ declare_clippy_lint! { /// fn needless_main() { /// unimplemented!(); /// } - /// `````` + /// ``` #[clippy::version = "1.40.0"] pub NEEDLESS_DOCTEST_MAIN, style, diff --git a/clippy_lints/src/enum_variants.rs b/clippy_lints/src/enum_variants.rs index 263a5b573c9..23b75104570 100644 --- a/clippy_lints/src/enum_variants.rs +++ b/clippy_lints/src/enum_variants.rs @@ -60,7 +60,8 @@ declare_clippy_lint! { /// struct BlackForestCake; /// } /// ``` - /// Could be written as: + /// + /// Use instead: /// ```rust /// mod cake { /// struct BlackForest; diff --git a/clippy_lints/src/eq_op.rs b/clippy_lints/src/eq_op.rs index c3176d987c6..2f4c90d07cf 100644 --- a/clippy_lints/src/eq_op.rs +++ b/clippy_lints/src/eq_op.rs @@ -52,15 +52,13 @@ declare_clippy_lint! { /// ### Why is this bad? /// It is more idiomatic to dereference the other argument. /// - /// ### Known problems - /// None - /// /// ### Example - /// ```ignore - /// // Bad + /// ```rust,ignore /// &x == y + /// ``` /// - /// // Good + /// Use instead: + /// ```rust,ignore /// x == *y /// ``` #[clippy::version = "pre 1.29.0"] diff --git a/clippy_lints/src/eta_reduction.rs b/clippy_lints/src/eta_reduction.rs index 197cac86a57..a5a763c37d1 100644 --- a/clippy_lints/src/eta_reduction.rs +++ b/clippy_lints/src/eta_reduction.rs @@ -34,14 +34,14 @@ declare_clippy_lint! { /// /// ### Example /// ```rust,ignore - /// // Bad /// xs.map(|x| foo(x)) + /// ``` /// - /// // Good + /// Use instead: + /// ```rust,ignore + /// // where `foo(_)` is a plain function that takes the exact argument type of `x`. /// xs.map(foo) /// ``` - /// where `foo(_)` is a plain function that takes the exact argument type of - /// `x`. #[clippy::version = "pre 1.29.0"] pub REDUNDANT_CLOSURE, style, diff --git a/clippy_lints/src/excessive_bools.rs b/clippy_lints/src/excessive_bools.rs index a2af10e2ba5..f7a92bc0795 100644 --- a/clippy_lints/src/excessive_bools.rs +++ b/clippy_lints/src/excessive_bools.rs @@ -54,12 +54,11 @@ declare_clippy_lint! { /// API easier to use. /// /// ### Example - /// Bad: /// ```rust,ignore /// fn f(is_round: bool, is_hot: bool) { ... } /// ``` /// - /// Good: + /// Use instead: /// ```rust,ignore /// enum Shape { /// Round, diff --git a/clippy_lints/src/float_literal.rs b/clippy_lints/src/float_literal.rs index f850ea31f4d..f2e07980963 100644 --- a/clippy_lints/src/float_literal.rs +++ b/clippy_lints/src/float_literal.rs @@ -45,10 +45,11 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// // Bad /// let _: f32 = 16_777_217.0; // 16_777_216.0 + /// ``` /// - /// // Good + /// Use instead: + /// ```rust /// let _: f32 = 16_777_216.0; /// let _: f64 = 16_777_217.0; /// ``` diff --git a/clippy_lints/src/functions/mod.rs b/clippy_lints/src/functions/mod.rs index ad031cbc09d..73261fb8a44 100644 --- a/clippy_lints/src/functions/mod.rs +++ b/clippy_lints/src/functions/mod.rs @@ -76,12 +76,13 @@ declare_clippy_lint! { /// /// ### Example /// ```rust,ignore - /// // Bad /// pub fn foo(x: *const u8) { /// println!("{}", unsafe { *x }); /// } + /// ``` /// - /// // Good + /// Use instead: + /// ```rust,ignore /// pub unsafe fn foo(x: *const u8) { /// println!("{}", unsafe { *x }); /// } diff --git a/clippy_lints/src/get_first.rs b/clippy_lints/src/get_first.rs index 0748ab45252..529f7babaa5 100644 --- a/clippy_lints/src/get_first.rs +++ b/clippy_lints/src/get_first.rs @@ -20,13 +20,12 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// // Bad /// let x = vec![2, 3, 5]; /// let first_element = x.get(0); /// ``` + /// /// Use instead: /// ```rust - /// // Good /// let x = vec![2, 3, 5]; /// let first_element = x.first(); /// ``` diff --git a/clippy_lints/src/implicit_saturating_sub.rs b/clippy_lints/src/implicit_saturating_sub.rs index ae4158662d4..46654bc61e0 100644 --- a/clippy_lints/src/implicit_saturating_sub.rs +++ b/clippy_lints/src/implicit_saturating_sub.rs @@ -16,17 +16,21 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// let end: u32 = 10; - /// let start: u32 = 5; - /// + /// # let end: u32 = 10; + /// # let start: u32 = 5; /// let mut i: u32 = end - start; /// - /// // Bad /// if i != 0 { /// i -= 1; /// } + /// ``` + /// + /// Use instead: + /// ```rust + /// # let end: u32 = 10; + /// # let start: u32 = 5; + /// let mut i: u32 = end - start; /// - /// // Good /// i = i.saturating_sub(1); /// ``` #[clippy::version = "1.44.0"] @@ -48,7 +52,7 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitSaturatingSub { // Check if the conditional expression is a binary operation if let ExprKind::Binary(ref cond_op, cond_left, cond_right) = cond.kind; - // Ensure that the binary operator is >, != and < + // Ensure that the binary operator is >, !=, or < if BinOpKind::Ne == cond_op.node || BinOpKind::Gt == cond_op.node || BinOpKind::Lt == cond_op.node; // Check if assign operation is done diff --git a/clippy_lints/src/index_refutable_slice.rs b/clippy_lints/src/index_refutable_slice.rs index 9ce5b8e17a9..d0c6495e35a 100644 --- a/clippy_lints/src/index_refutable_slice.rs +++ b/clippy_lints/src/index_refutable_slice.rs @@ -45,7 +45,7 @@ declare_clippy_lint! { /// println!("{}", first); /// } /// ``` - #[clippy::version = "1.58.0"] + #[clippy::version = "1.59.0"] pub INDEX_REFUTABLE_SLICE, nursery, "avoid indexing on slices which could be destructed" diff --git a/clippy_lints/src/indexing_slicing.rs b/clippy_lints/src/indexing_slicing.rs index 4ba7477add8..4a375752e1d 100644 --- a/clippy_lints/src/indexing_slicing.rs +++ b/clippy_lints/src/indexing_slicing.rs @@ -17,19 +17,20 @@ declare_clippy_lint! { /// ### Why is this bad? /// This will always panic at runtime. /// - /// ### Known problems - /// Hopefully none. - /// /// ### Example - /// ```no_run + /// ```rust,no_run /// # #![allow(const_err)] /// let x = [1, 2, 3, 4]; /// - /// // Bad /// x[9]; /// &x[2..9]; + /// ``` + /// + /// Use instead: + /// ```rust + /// # let x = [1, 2, 3, 4]; + /// // Index within bounds /// - /// // Good /// x[0]; /// x[3]; /// ``` @@ -49,42 +50,32 @@ declare_clippy_lint! { /// Indexing and slicing can panic at runtime and there are /// safe alternatives. /// - /// ### Known problems - /// Hopefully none. - /// /// ### Example /// ```rust,no_run /// // Vector /// let x = vec![0; 5]; /// - /// // Bad /// x[2]; /// &x[2..100]; - /// &x[2..]; - /// &x[..100]; - /// - /// // Good - /// x.get(2); - /// x.get(2..100); - /// x.get(2..); - /// x.get(..100); /// /// // Array /// let y = [0, 1, 2, 3]; /// - /// // Bad /// &y[10..100]; /// &y[10..]; - /// &y[..100]; + /// ``` + /// + /// Use instead: + /// ```rust + /// # #![allow(unused)] + /// + /// # let x = vec![0; 5]; + /// # let y = [0, 1, 2, 3]; + /// x.get(2); + /// x.get(2..100); /// - /// // Good - /// &y[2..]; - /// &y[..2]; - /// &y[0..3]; /// y.get(10); /// y.get(10..100); - /// y.get(10..); - /// y.get(..100); /// ``` #[clippy::version = "pre 1.29.0"] pub INDEXING_SLICING, diff --git a/clippy_lints/src/infinite_iter.rs b/clippy_lints/src/infinite_iter.rs index 41e1fc4e3c2..78b5ec8ec1e 100644 --- a/clippy_lints/src/infinite_iter.rs +++ b/clippy_lints/src/infinite_iter.rs @@ -41,6 +41,7 @@ declare_clippy_lint! { /// ### Example /// ```rust /// let infinite_iter = 0..; + /// # #[allow(unused)] /// [0..].iter().zip(infinite_iter.take_while(|x| *x > 5)); /// ``` #[clippy::version = "pre 1.29.0"] diff --git a/clippy_lints/src/inherent_to_string.rs b/clippy_lints/src/inherent_to_string.rs index 55c04a1186f..39f68a8a1b4 100644 --- a/clippy_lints/src/inherent_to_string.rs +++ b/clippy_lints/src/inherent_to_string.rs @@ -14,12 +14,8 @@ declare_clippy_lint! { /// ### Why is this bad? /// This method is also implicitly defined if a type implements the `Display` trait. As the functionality of `Display` is much more versatile, it should be preferred. /// - /// ### Known problems - /// None - /// /// ### Example /// ```rust - /// // Bad /// pub struct A; /// /// impl A { @@ -29,8 +25,8 @@ declare_clippy_lint! { /// } /// ``` /// + /// Use instead: /// ```rust - /// // Good /// use std::fmt; /// /// pub struct A; @@ -54,12 +50,8 @@ declare_clippy_lint! { /// ### Why is this bad? /// This method is also implicitly defined if a type implements the `Display` trait. The less versatile inherent method will then shadow the implementation introduced by `Display`. /// - /// ### Known problems - /// None - /// /// ### Example /// ```rust - /// // Bad /// use std::fmt; /// /// pub struct A; @@ -77,8 +69,8 @@ declare_clippy_lint! { /// } /// ``` /// + /// Use instead: /// ```rust - /// // Good /// use std::fmt; /// /// pub struct A; diff --git a/clippy_lints/src/int_plus_one.rs b/clippy_lints/src/int_plus_one.rs index 8db7b307ddb..9a944def3eb 100644 --- a/clippy_lints/src/int_plus_one.rs +++ b/clippy_lints/src/int_plus_one.rs @@ -21,8 +21,7 @@ declare_clippy_lint! { /// if x >= y + 1 {} /// ``` /// - /// Could be written as: - /// + /// Use instead: /// ```rust /// # let x = 1; /// # let y = 1; diff --git a/clippy_lints/src/integer_division.rs b/clippy_lints/src/integer_division.rs index fa786205678..3effba56826 100644 --- a/clippy_lints/src/integer_division.rs +++ b/clippy_lints/src/integer_division.rs @@ -15,11 +15,12 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// // Bad /// let x = 3 / 2; /// println!("{}", x); + /// ``` /// - /// // Good + /// Use instead: + /// ```rust /// let x = 3f32 / 2f32; /// println!("{}", x); /// ``` diff --git a/clippy_lints/src/items_after_statements.rs b/clippy_lints/src/items_after_statements.rs index cdefe627efd..46d439b4497 100644 --- a/clippy_lints/src/items_after_statements.rs +++ b/clippy_lints/src/items_after_statements.rs @@ -17,7 +17,6 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// // Bad /// fn foo() { /// println!("cake"); /// } @@ -31,8 +30,8 @@ declare_clippy_lint! { /// } /// ``` /// + /// Use instead: /// ```rust - /// // Good /// fn foo() { /// println!("cake"); /// } diff --git a/clippy_lints/src/large_const_arrays.rs b/clippy_lints/src/large_const_arrays.rs index e10993ba7dd..289755bfec6 100644 --- a/clippy_lints/src/large_const_arrays.rs +++ b/clippy_lints/src/large_const_arrays.rs @@ -20,10 +20,11 @@ declare_clippy_lint! { /// /// ### Example /// ```rust,ignore - /// // Bad /// pub const a = [0u32; 1_000_000]; + /// ``` /// - /// // Good + /// Use instead: + /// ```rust.ignore /// pub static a = [0u32; 1_000_000]; /// ``` #[clippy::version = "1.44.0"] diff --git a/clippy_lints/src/large_enum_variant.rs b/clippy_lints/src/large_enum_variant.rs index 63ac092dfaf..9be057bcf90 100644 --- a/clippy_lints/src/large_enum_variant.rs +++ b/clippy_lints/src/large_enum_variant.rs @@ -38,12 +38,14 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// // Bad /// enum Test { /// A(i32), /// B([i32; 8000]), /// } + /// ``` /// + /// Use instead: + /// ```rust /// // Possibly better /// enum Test2 { /// A(i32), diff --git a/clippy_lints/src/large_include_file.rs b/clippy_lints/src/large_include_file.rs index 8bef13c682d..84dd61a1e4b 100644 --- a/clippy_lints/src/large_include_file.rs +++ b/clippy_lints/src/large_include_file.rs @@ -22,10 +22,11 @@ declare_clippy_lint! { /// let included_bytes = include_bytes!("very_large_file.txt"); /// ``` /// - /// Instead, you can load the file at runtime: + /// Use instead: /// ```rust,ignore /// use std::fs; /// + /// // You can load the file at runtime /// let string = fs::read_to_string("very_large_file.txt")?; /// let bytes = fs::read("very_large_file.txt")?; /// ``` diff --git a/clippy_lints/src/let_underscore.rs b/clippy_lints/src/let_underscore.rs index cb1ef01f5ba..26c540e2223 100644 --- a/clippy_lints/src/let_underscore.rs +++ b/clippy_lints/src/let_underscore.rs @@ -45,13 +45,11 @@ declare_clippy_lint! { /// `std::mem::drop` conveys your intention better and is less error-prone. /// /// ### Example - /// - /// Bad: /// ```rust,ignore /// let _ = mutex.lock(); /// ``` /// - /// Good: + /// Use instead: /// ```rust,ignore /// let _lock = mutex.lock(); /// ``` @@ -75,24 +73,20 @@ declare_clippy_lint! { /// better and is less error-prone. /// /// ### Example - /// - /// Bad: - /// ```rust,ignore - /// struct Droppable; - /// impl Drop for Droppable { - /// fn drop(&mut self) {} - /// } + /// ```rust + /// # struct DroppableItem; /// { - /// let _ = Droppable; - /// // ^ dropped here + /// let _ = DroppableItem; + /// // ^ dropped here /// /* more code */ /// } /// ``` /// - /// Good: - /// ```rust,ignore + /// Use instead: + /// ```rust + /// # struct DroppableItem; /// { - /// let _droppable = Droppable; + /// let _droppable = DroppableItem; /// /* more code */ /// // dropped at end of scope /// } diff --git a/clippy_lints/src/lib.register_all.rs b/clippy_lints/src/lib.register_all.rs index d4ec046d0bb..8a2cfbff953 100644 --- a/clippy_lints/src/lib.register_all.rs +++ b/clippy_lints/src/lib.register_all.rs @@ -242,6 +242,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(needless_bool::NEEDLESS_BOOL), LintId::of(needless_borrowed_ref::NEEDLESS_BORROWED_REFERENCE), LintId::of(needless_late_init::NEEDLESS_LATE_INIT), + LintId::of(needless_parens_on_range_literals::NEEDLESS_PARENS_ON_RANGE_LITERALS), LintId::of(needless_question_mark::NEEDLESS_QUESTION_MARK), LintId::of(needless_update::NEEDLESS_UPDATE), LintId::of(neg_cmp_op_on_partial_ord::NEG_CMP_OP_ON_PARTIAL_ORD), @@ -270,6 +271,7 @@ store.register_group(true, "clippy::all", Some("clippy_all"), vec![ LintId::of(ranges::RANGE_ZIP_WITH_LEN), LintId::of(ranges::REVERSED_EMPTY_RANGES), LintId::of(rc_clone_in_vec_init::RC_CLONE_IN_VEC_INIT), + LintId::of(read_zero_byte_vec::READ_ZERO_BYTE_VEC), LintId::of(redundant_clone::REDUNDANT_CLONE), LintId::of(redundant_closure_call::REDUNDANT_CLOSURE_CALL), LintId::of(redundant_field_names::REDUNDANT_FIELD_NAMES), diff --git a/clippy_lints/src/lib.register_correctness.rs b/clippy_lints/src/lib.register_correctness.rs index 50cdd0af923..92a3a0aabf1 100644 --- a/clippy_lints/src/lib.register_correctness.rs +++ b/clippy_lints/src/lib.register_correctness.rs @@ -55,6 +55,7 @@ store.register_group(true, "clippy::correctness", Some("clippy_correctness"), ve LintId::of(ptr::INVALID_NULL_PTR_USAGE), LintId::of(ptr::MUT_FROM_REF), LintId::of(ranges::REVERSED_EMPTY_RANGES), + LintId::of(read_zero_byte_vec::READ_ZERO_BYTE_VEC), LintId::of(regex::INVALID_REGEX), LintId::of(self_assignment::SELF_ASSIGNMENT), LintId::of(serde_api::SERDE_API_MISUSE), diff --git a/clippy_lints/src/lib.register_lints.rs b/clippy_lints/src/lib.register_lints.rs index b927ba3b17c..8ad984c68b8 100644 --- a/clippy_lints/src/lib.register_lints.rs +++ b/clippy_lints/src/lib.register_lints.rs @@ -408,6 +408,7 @@ store.register_lints(&[ needless_continue::NEEDLESS_CONTINUE, needless_for_each::NEEDLESS_FOR_EACH, needless_late_init::NEEDLESS_LATE_INIT, + needless_parens_on_range_literals::NEEDLESS_PARENS_ON_RANGE_LITERALS, needless_pass_by_value::NEEDLESS_PASS_BY_VALUE, needless_question_mark::NEEDLESS_QUESTION_MARK, needless_update::NEEDLESS_UPDATE, @@ -458,6 +459,7 @@ store.register_lints(&[ ranges::RANGE_ZIP_WITH_LEN, ranges::REVERSED_EMPTY_RANGES, rc_clone_in_vec_init::RC_CLONE_IN_VEC_INIT, + read_zero_byte_vec::READ_ZERO_BYTE_VEC, redundant_clone::REDUNDANT_CLONE, redundant_closure_call::REDUNDANT_CLOSURE_CALL, redundant_else::REDUNDANT_ELSE, diff --git a/clippy_lints/src/lib.register_style.rs b/clippy_lints/src/lib.register_style.rs index 35575351784..b6992ae0ad2 100644 --- a/clippy_lints/src/lib.register_style.rs +++ b/clippy_lints/src/lib.register_style.rs @@ -91,6 +91,7 @@ store.register_group(true, "clippy::style", Some("clippy_style"), vec![ LintId::of(mut_mutex_lock::MUT_MUTEX_LOCK), LintId::of(mut_reference::UNNECESSARY_MUT_PASSED), LintId::of(needless_late_init::NEEDLESS_LATE_INIT), + LintId::of(needless_parens_on_range_literals::NEEDLESS_PARENS_ON_RANGE_LITERALS), LintId::of(neg_multiply::NEG_MULTIPLY), LintId::of(new_without_default::NEW_WITHOUT_DEFAULT), LintId::of(non_copy_const::BORROW_INTERIOR_MUTABLE_CONST), diff --git a/clippy_lints/src/lib.rs b/clippy_lints/src/lib.rs index ee0416fc0ff..84898eae05a 100644 --- a/clippy_lints/src/lib.rs +++ b/clippy_lints/src/lib.rs @@ -1,5 +1,3 @@ -// error-pattern:cargo-clippy - #![feature(array_windows)] #![feature(binary_heap_into_iter_sorted)] #![feature(box_patterns)] @@ -88,10 +86,11 @@ use rustc_session::Session; /// /// /// /// ### Example /// /// ```rust -/// /// // Bad /// /// Insert a short example of code that triggers the lint +/// /// ``` /// /// -/// /// // Good +/// /// Use instead: +/// /// ```rust /// /// Insert a short example of improved code that doesn't trigger the lint /// /// ``` /// pub LINT_NAME, @@ -315,6 +314,7 @@ mod needless_borrowed_ref; mod needless_continue; mod needless_for_each; mod needless_late_init; +mod needless_parens_on_range_literals; mod needless_pass_by_value; mod needless_question_mark; mod needless_update; @@ -348,6 +348,7 @@ mod pub_use; mod question_mark; mod ranges; mod rc_clone_in_vec_init; +mod read_zero_byte_vec; mod redundant_clone; mod redundant_closure_call; mod redundant_else; @@ -746,6 +747,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_early_pass(|| Box::new(collapsible_if::CollapsibleIf)); store.register_early_pass(|| Box::new(items_after_statements::ItemsAfterStatements)); store.register_early_pass(|| Box::new(precedence::Precedence)); + store.register_late_pass(|| Box::new(needless_parens_on_range_literals::NeedlessParensOnRangeLiterals)); store.register_early_pass(|| Box::new(needless_continue::NeedlessContinue)); store.register_early_pass(|| Box::new(redundant_else::RedundantElse)); store.register_late_pass(|| Box::new(create_dir::CreateDir)); @@ -907,6 +909,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf: store.register_late_pass(|| Box::new(swap_ptr_to_ref::SwapPtrToRef)); store.register_late_pass(|| Box::new(mismatching_type_param_order::TypeParamMismatch)); store.register_late_pass(|| Box::new(as_underscore::AsUnderscore)); + store.register_late_pass(|| Box::new(read_zero_byte_vec::ReadZeroByteVec)); // add lints here, do not remove this comment, it's used in `new_lint` } diff --git a/clippy_lints/src/lifetimes.rs b/clippy_lints/src/lifetimes.rs index 070c7e59142..93f5663312f 100644 --- a/clippy_lints/src/lifetimes.rs +++ b/clippy_lints/src/lifetimes.rs @@ -36,12 +36,14 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// // Bad: unnecessary lifetime annotations + /// // Unnecessary lifetime annotations /// fn in_and_out<'a>(x: &'a u8, y: u8) -> &'a u8 { /// x /// } + /// ``` /// - /// // Good + /// Use instead: + /// ```rust /// fn elided(x: &u8, y: u8) -> &u8 { /// x /// } @@ -65,12 +67,14 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// // Bad: unnecessary lifetimes + /// // unnecessary lifetimes /// fn unused_lifetime<'a>(x: u8) { /// // .. /// } + /// ``` /// - /// // Good + /// Use instead: + /// ```rust /// fn no_lifetime(x: u8) { /// // ... /// } diff --git a/clippy_lints/src/literal_representation.rs b/clippy_lints/src/literal_representation.rs index 9998712b852..fb2104861c8 100644 --- a/clippy_lints/src/literal_representation.rs +++ b/clippy_lints/src/literal_representation.rs @@ -22,11 +22,16 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// // Bad - /// let x: u64 = 61864918973511; + /// # let _: u64 = + /// 61864918973511 + /// # ; + /// ``` /// - /// // Good - /// let x: u64 = 61_864_918_973_511; + /// Use instead: + /// ```rust + /// # let _: u64 = + /// 61_864_918_973_511 + /// # ; /// ``` #[clippy::version = "pre 1.29.0"] pub UNREADABLE_LITERAL, @@ -46,6 +51,7 @@ declare_clippy_lint! { /// - Does not match on `_127` since that is a valid grouping for decimal and octal numbers /// /// ### Example + /// ```ignore /// `2_32` => `2_i32` /// `250_8 => `250_u8` /// ``` @@ -66,11 +72,16 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// // Bad - /// let x: u64 = 618_64_9189_73_511; + /// # let _: u64 = + /// 618_64_9189_73_511 + /// # ; + /// ``` /// - /// // Good - /// let x: u64 = 61_864_918_973_511; + /// Use instead: + /// ```rust + /// # let _: u64 = + /// 61_864_918_973_511 + /// # ; /// ``` #[clippy::version = "pre 1.29.0"] pub INCONSISTENT_DIGIT_GROUPING, @@ -125,9 +136,11 @@ declare_clippy_lint! { /// readable than a decimal representation. /// /// ### Example + /// ```text /// `255` => `0xFF` /// `65_535` => `0xFFFF` /// `4_042_322_160` => `0xF0F0_F0F0` + /// ``` #[clippy::version = "pre 1.29.0"] pub DECIMAL_LITERAL_REPRESENTATION, restriction, diff --git a/clippy_lints/src/loops/for_loops_over_fallibles.rs b/clippy_lints/src/loops/for_loops_over_fallibles.rs index 90530ebf003..77de90fd7b9 100644 --- a/clippy_lints/src/loops/for_loops_over_fallibles.rs +++ b/clippy_lints/src/loops/for_loops_over_fallibles.rs @@ -7,9 +7,22 @@ use rustc_lint::LateContext; use rustc_span::symbol::sym; /// Checks for `for` loops over `Option`s and `Result`s. -pub(super) fn check(cx: &LateContext<'_>, pat: &Pat<'_>, arg: &Expr<'_>) { +pub(super) fn check(cx: &LateContext<'_>, pat: &Pat<'_>, arg: &Expr<'_>, method_name: Option<&str>) { let ty = cx.typeck_results().expr_ty(arg); if is_type_diagnostic_item(cx, ty, sym::Option) { + let help_string = if let Some(method_name) = method_name { + format!( + "consider replacing `for {0} in {1}.{method_name}()` with `if let Some({0}) = {1}`", + snippet(cx, pat.span, "_"), + snippet(cx, arg.span, "_") + ) + } else { + format!( + "consider replacing `for {0} in {1}` with `if let Some({0}) = {1}`", + snippet(cx, pat.span, "_"), + snippet(cx, arg.span, "_") + ) + }; span_lint_and_help( cx, FOR_LOOPS_OVER_FALLIBLES, @@ -20,13 +33,22 @@ pub(super) fn check(cx: &LateContext<'_>, pat: &Pat<'_>, arg: &Expr<'_>) { snippet(cx, arg.span, "_") ), None, - &format!( - "consider replacing `for {0} in {1}` with `if let Some({0}) = {1}`", - snippet(cx, pat.span, "_"), - snippet(cx, arg.span, "_") - ), + &help_string, ); } else if is_type_diagnostic_item(cx, ty, sym::Result) { + let help_string = if let Some(method_name) = method_name { + format!( + "consider replacing `for {0} in {1}.{method_name}()` with `if let Ok({0}) = {1}`", + snippet(cx, pat.span, "_"), + snippet(cx, arg.span, "_") + ) + } else { + format!( + "consider replacing `for {0} in {1}` with `if let Ok({0}) = {1}`", + snippet(cx, pat.span, "_"), + snippet(cx, arg.span, "_") + ) + }; span_lint_and_help( cx, FOR_LOOPS_OVER_FALLIBLES, @@ -37,11 +59,7 @@ pub(super) fn check(cx: &LateContext<'_>, pat: &Pat<'_>, arg: &Expr<'_>) { snippet(cx, arg.span, "_") ), None, - &format!( - "consider replacing `for {0} in {1}` with `if let Ok({0}) = {1}`", - snippet(cx, pat.span, "_"), - snippet(cx, arg.span, "_") - ), + &help_string, ); } } diff --git a/clippy_lints/src/loops/mod.rs b/clippy_lints/src/loops/mod.rs index d61be78895f..391de922e1e 100644 --- a/clippy_lints/src/loops/mod.rs +++ b/clippy_lints/src/loops/mod.rs @@ -42,7 +42,8 @@ declare_clippy_lint! { /// dst[i + 64] = src[i]; /// } /// ``` - /// Could be written as: + /// + /// Use instead: /// ```rust /// # let src = vec![1]; /// # let mut dst = vec![0; 65]; @@ -70,7 +71,8 @@ declare_clippy_lint! { /// println!("{}", vec[i]); /// } /// ``` - /// Could be written as: + /// + /// Use instead: /// ```rust /// let vec = vec!['a', 'b', 'c']; /// for i in vec { @@ -103,7 +105,8 @@ declare_clippy_lint! { /// // .. /// } /// ``` - /// can be rewritten to + /// + /// Use instead: /// ```rust /// # let y = vec![1]; /// for x in &y { @@ -188,6 +191,10 @@ declare_clippy_lint! { /// for x in &res { /// // .. /// } + /// + /// for x in res.iter() { + /// // .. + /// } /// ``` /// /// Use instead: @@ -282,7 +289,8 @@ declare_clippy_lint! { /// i += 1; /// } /// ``` - /// Could be written as + /// + /// Use instead: /// ```rust /// # let v = vec![1]; /// # fn bar(bar: usize, baz: usize) {} @@ -469,7 +477,7 @@ declare_clippy_lint! { /// /// ### Why is this bad? /// This kind of operation can be expressed more succinctly with - /// `vec![item;SIZE]` or `vec.resize(NEW_SIZE, item)` and using these alternatives may also + /// `vec![item; SIZE]` or `vec.resize(NEW_SIZE, item)` and using these alternatives may also /// have better performance. /// /// ### Example @@ -484,7 +492,8 @@ declare_clippy_lint! { /// vec.push(item2); /// } /// ``` - /// could be written as + /// + /// Use instead: /// ```rust /// let item1 = 2; /// let item2 = 3; @@ -512,7 +521,8 @@ declare_clippy_lint! { /// println!("{}", item); /// } /// ``` - /// could be written as + /// + /// Use instead: /// ```rust /// let item1 = 2; /// let item = &item1; @@ -586,7 +596,7 @@ declare_clippy_lint! { /// std::hint::spin_loop() /// } /// ``` - #[clippy::version = "1.59.0"] + #[clippy::version = "1.61.0"] pub MISSING_SPIN_LOOP, perf, "An empty busy waiting loop" @@ -695,10 +705,14 @@ fn check_for_loop_arg(cx: &LateContext<'_>, pat: &Pat<'_>, arg: &Expr<'_>) { let method_name = method.ident.as_str(); // check for looping over x.iter() or x.iter_mut(), could use &x or &mut x match method_name { - "iter" | "iter_mut" => explicit_iter_loop::check(cx, self_arg, arg, method_name), + "iter" | "iter_mut" => { + explicit_iter_loop::check(cx, self_arg, arg, method_name); + for_loops_over_fallibles::check(cx, pat, self_arg, Some(method_name)); + }, "into_iter" => { explicit_iter_loop::check(cx, self_arg, arg, method_name); explicit_into_iter_loop::check(cx, self_arg, arg); + for_loops_over_fallibles::check(cx, pat, self_arg, Some(method_name)); }, "next" => { next_loop_linted = iter_next_loop::check(cx, arg); @@ -708,6 +722,6 @@ fn check_for_loop_arg(cx: &LateContext<'_>, pat: &Pat<'_>, arg: &Expr<'_>) { } if !next_loop_linted { - for_loops_over_fallibles::check(cx, pat, arg); + for_loops_over_fallibles::check(cx, pat, arg, None); } } diff --git a/clippy_lints/src/loops/never_loop.rs b/clippy_lints/src/loops/never_loop.rs index 99d21466935..32de20f6531 100644 --- a/clippy_lints/src/loops/never_loop.rs +++ b/clippy_lints/src/loops/never_loop.rs @@ -117,13 +117,20 @@ fn never_loop_expr(expr: &Expr<'_>, main_loop_id: HirId) -> NeverLoopResult { | ExprKind::Type(e, _) | ExprKind::Field(e, _) | ExprKind::AddrOf(_, _, e) - | ExprKind::Struct(_, _, Some(e)) | ExprKind::Repeat(e, _) | ExprKind::DropTemps(e) => never_loop_expr(e, main_loop_id), ExprKind::Let(let_expr) => never_loop_expr(let_expr.init, main_loop_id), ExprKind::Array(es) | ExprKind::MethodCall(_, es, _) | ExprKind::Tup(es) => { never_loop_expr_all(&mut es.iter(), main_loop_id) }, + ExprKind::Struct(_, fields, base) => { + let fields = never_loop_expr_all(&mut fields.iter().map(|f| f.expr), main_loop_id); + if let Some(base) = base { + combine_both(fields, never_loop_expr(base, main_loop_id)) + } else { + fields + } + }, ExprKind::Call(e, es) => never_loop_expr_all(&mut once(e).chain(es.iter()), main_loop_id), ExprKind::Binary(_, e1, e2) | ExprKind::Assign(e1, e2, _) @@ -180,8 +187,7 @@ fn never_loop_expr(expr: &Expr<'_>, main_loop_id: HirId) -> NeverLoopResult { | InlineAsmOperand::SymStatic { .. } => NeverLoopResult::Otherwise, }) .fold(NeverLoopResult::Otherwise, combine_both), - ExprKind::Struct(_, _, None) - | ExprKind::Yield(_, _) + ExprKind::Yield(_, _) | ExprKind::Closure { .. } | ExprKind::Path(_) | ExprKind::ConstBlock(_) diff --git a/clippy_lints/src/matches/match_same_arms.rs b/clippy_lints/src/matches/match_same_arms.rs index 4f8baf7efb0..16fefea5520 100644 --- a/clippy_lints/src/matches/match_same_arms.rs +++ b/clippy_lints/src/matches/match_same_arms.rs @@ -110,14 +110,9 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, arms: &'tcx [Arm<'_>]) { arm1.span, "this match arm has an identical body to the `_` wildcard arm", |diag| { - diag.span_suggestion( - arm1.span, - "try removing the arm", - "", - Applicability::MaybeIncorrect, - ) - .help("or try changing either arm body") - .span_note(arm2.span, "`_` wildcard arm here"); + diag.span_suggestion(arm1.span, "try removing the arm", "", Applicability::MaybeIncorrect) + .help("or try changing either arm body") + .span_note(arm2.span, "`_` wildcard arm here"); }, ); } else { diff --git a/clippy_lints/src/matches/mod.rs b/clippy_lints/src/matches/mod.rs index d1e42f39e47..3e765173fb9 100644 --- a/clippy_lints/src/matches/mod.rs +++ b/clippy_lints/src/matches/mod.rs @@ -43,13 +43,16 @@ declare_clippy_lint! { /// ```rust /// # fn bar(stool: &str) {} /// # let x = Some("abc"); - /// // Bad /// match x { /// Some(ref foo) => bar(foo), /// _ => (), /// } + /// ``` /// - /// // Good + /// Use instead: + /// ```rust + /// # fn bar(stool: &str) {} + /// # let x = Some("abc"); /// if let Some(ref foo) = x { /// bar(foo); /// } @@ -114,14 +117,15 @@ declare_clippy_lint! { /// /// ### Example /// ```rust,ignore - /// // Bad /// match x { /// &A(ref y) => foo(y), /// &B => bar(), /// _ => frob(&x), /// } + /// ``` /// - /// // Good + /// Use instead: + /// ```rust,ignore /// match *x { /// A(ref y) => foo(y), /// B => bar(), @@ -227,13 +231,16 @@ declare_clippy_lint! { /// ```rust /// let x: Option<()> = None; /// - /// // Bad /// let r: Option<&()> = match x { /// None => None, /// Some(ref v) => Some(v), /// }; + /// ``` + /// + /// Use instead: + /// ```rust + /// let x: Option<()> = None; /// - /// // Good /// let r: Option<&()> = x.as_ref(); /// ``` #[clippy::version = "pre 1.29.0"] @@ -257,13 +264,16 @@ declare_clippy_lint! { /// ```rust /// # enum Foo { A(usize), B(usize) } /// # let x = Foo::B(1); - /// // Bad /// match x { /// Foo::A(_) => {}, /// _ => {}, /// } + /// ``` /// - /// // Good + /// Use instead: + /// ```rust + /// # enum Foo { A(usize), B(usize) } + /// # let x = Foo::B(1); /// match x { /// Foo::A(_) => {}, /// Foo::B(_) => {}, @@ -290,14 +300,17 @@ declare_clippy_lint! { /// ```rust /// # enum Foo { A, B, C } /// # let x = Foo::B; - /// // Bad /// match x { /// Foo::A => {}, /// Foo::B => {}, /// _ => {}, /// } + /// ``` /// - /// // Good + /// Use instead: + /// ```rust + /// # enum Foo { A, B, C } + /// # let x = Foo::B; /// match x { /// Foo::A => {}, /// Foo::B => {}, @@ -320,14 +333,17 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// // Bad - /// match "foo" { + /// # let s = "foo"; + /// match s { /// "a" => {}, /// "bar" | _ => {}, /// } + /// ``` /// - /// // Good - /// match "foo" { + /// Use instead: + /// ```rust + /// # let s = "foo"; + /// match s { /// "a" => {}, /// _ => {}, /// } @@ -389,15 +405,17 @@ declare_clippy_lint! { /// ```rust /// # let a = 1; /// # let b = 2; - /// - /// // Bad /// match (a, b) { /// (c, d) => { /// // useless match /// } /// } + /// ``` /// - /// // Good + /// Use instead: + /// ```rust + /// # let a = 1; + /// # let b = 2; /// let (c, d) = (a, b); /// ``` #[clippy::version = "1.43.0"] @@ -419,13 +437,16 @@ declare_clippy_lint! { /// # struct A { a: i32 } /// let a = A { a: 5 }; /// - /// // Bad /// match a { /// A { a: 5, .. } => {}, /// _ => {}, /// } + /// ``` /// - /// // Good + /// Use instead: + /// ```rust + /// # struct A { a: i32 } + /// # let a = A { a: 5 }; /// match a { /// A { a: 5 } => {}, /// _ => {}, @@ -509,7 +530,6 @@ declare_clippy_lint! { /// ```rust /// let x = Some(5); /// - /// // Bad /// let a = match x { /// Some(0) => true, /// _ => false, @@ -520,8 +540,11 @@ declare_clippy_lint! { /// } else { /// false /// }; + /// ``` /// - /// // Good + /// Use instead: + /// ```rust + /// let x = Some(5); /// let a = matches!(x, Some(0)); /// ``` #[clippy::version = "1.47.0"] @@ -695,19 +718,18 @@ declare_clippy_lint! { /// let arr = vec![0, 1, 2, 3]; /// let idx = 1; /// - /// // Bad /// match arr[idx] { /// 0 => println!("{}", 0), /// 1 => println!("{}", 3), /// _ => {}, /// } /// ``` + /// /// Use instead: /// ```rust, no_run /// let arr = vec![0, 1, 2, 3]; /// let idx = 1; /// - /// // Good /// match arr.get(idx) { /// Some(0) => println!("{}", 0), /// Some(1) => println!("{}", 3), diff --git a/clippy_lints/src/methods/filter_map.rs b/clippy_lints/src/methods/filter_map.rs index 3efccd703a6..58c3e52e138 100644 --- a/clippy_lints/src/methods/filter_map.rs +++ b/clippy_lints/src/methods/filter_map.rs @@ -6,7 +6,7 @@ use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_hir::def::Res; -use rustc_hir::{Expr, ExprKind, PatKind, QPath, UnOp}; +use rustc_hir::{Expr, ExprKind, PatKind, PathSegment, QPath, UnOp}; use rustc_lint::LateContext; use rustc_span::source_map::Span; use rustc_span::symbol::{sym, Symbol}; @@ -155,7 +155,15 @@ pub(super) fn check<'tcx>( } false }; - if SpanlessEq::new(cx).expr_fallback(eq_fallback).eq_expr(filter_arg, map_arg); + + if match map_arg.kind { + ExprKind::MethodCall(method, [original_arg], _) => { + acceptable_methods(method) + && SpanlessEq::new(cx).expr_fallback(eq_fallback).eq_expr(filter_arg, original_arg) + }, + _ => SpanlessEq::new(cx).expr_fallback(eq_fallback).eq_expr(filter_arg, map_arg) + }; + then { let span = filter_span.with_hi(expr.span.hi()); let (filter_name, lint) = if is_find { @@ -171,3 +179,18 @@ pub(super) fn check<'tcx>( } } } + +fn acceptable_methods(method: &PathSegment<'_>) -> bool { + let methods: [Symbol; 8] = [ + sym::clone, + sym::as_ref, + sym!(copied), + sym!(cloned), + sym!(as_deref), + sym!(as_mut), + sym!(as_deref_mut), + sym!(to_owned), + ]; + + methods.contains(&method.ident.name) +} diff --git a/clippy_lints/src/methods/iter_overeager_cloned.rs b/clippy_lints/src/methods/iter_overeager_cloned.rs index 54c9ca435a4..06a39c5997e 100644 --- a/clippy_lints/src/methods/iter_overeager_cloned.rs +++ b/clippy_lints/src/methods/iter_overeager_cloned.rs @@ -1,70 +1,59 @@ -use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::source::snippet; -use clippy_utils::ty::{get_iterator_item_ty, implements_trait, is_copy}; -use itertools::Itertools; +use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::source::snippet_opt; +use clippy_utils::ty::{get_associated_type, implements_trait, is_copy}; use rustc_errors::Applicability; -use rustc_hir as hir; +use rustc_hir::Expr; use rustc_lint::LateContext; use rustc_middle::ty; use rustc_span::sym; -use std::ops::Not; use super::ITER_OVEREAGER_CLONED; use crate::redundant_clone::REDUNDANT_CLONE; -/// lint overeager use of `cloned()` for `Iterator`s pub(super) fn check<'tcx>( cx: &LateContext<'tcx>, - expr: &'tcx hir::Expr<'_>, - recv: &'tcx hir::Expr<'_>, - name: &str, - map_arg: &[hir::Expr<'_>], + expr: &'tcx Expr<'_>, + cloned_call: &'tcx Expr<'_>, + cloned_recv: &'tcx Expr<'_>, + is_count: bool, + needs_into_iter: bool, ) { - // Check if it's iterator and get type associated with `Item`. - let inner_ty = if_chain! { - if let Some(iterator_trait_id) = cx.tcx.get_diagnostic_item(sym::Iterator); - let recv_ty = cx.typeck_results().expr_ty(recv); - if implements_trait(cx, recv_ty, iterator_trait_id, &[]); - if let Some(inner_ty) = get_iterator_item_ty(cx, cx.typeck_results().expr_ty_adjusted(recv)); - then { - inner_ty - } else { + let typeck = cx.typeck_results(); + if let Some(iter_id) = cx.tcx.get_diagnostic_item(sym::Iterator) + && let Some(method_id) = typeck.type_dependent_def_id(expr.hir_id) + && cx.tcx.trait_of_item(method_id) == Some(iter_id) + && let Some(method_id) = typeck.type_dependent_def_id(cloned_call.hir_id) + && cx.tcx.trait_of_item(method_id) == Some(iter_id) + && let cloned_recv_ty = typeck.expr_ty_adjusted(cloned_recv) + && let Some(iter_assoc_ty) = get_associated_type(cx, cloned_recv_ty, iter_id, "Item") + && matches!(*iter_assoc_ty.kind(), ty::Ref(_, ty, _) if !is_copy(cx, ty)) + { + if needs_into_iter + && let Some(into_iter_id) = cx.tcx.get_diagnostic_item(sym::IntoIterator) + && !implements_trait(cx, iter_assoc_ty, into_iter_id, &[]) + { return; } - }; - - match inner_ty.kind() { - ty::Ref(_, ty, _) if !is_copy(cx, *ty) => {}, - _ => return, - }; - let (lint, preserve_cloned) = match name { - "count" => (REDUNDANT_CLONE, false), - _ => (ITER_OVEREAGER_CLONED, true), - }; - let wildcard_params = map_arg.is_empty().not().then(|| "...").unwrap_or_default(); - let msg = format!( - "called `cloned().{}({})` on an `Iterator`. It may be more efficient to call `{}({}){}` instead", - name, - wildcard_params, - name, - wildcard_params, - preserve_cloned.then(|| ".cloned()").unwrap_or_default(), - ); + let (lint, msg, trailing_clone) = if is_count { + (REDUNDANT_CLONE, "unneeded cloning of iterator items", "") + } else { + (ITER_OVEREAGER_CLONED, "unnecessarily eager cloning of iterator items", ".cloned()") + }; - span_lint_and_sugg( - cx, - lint, - expr.span, - &msg, - "try this", - format!( - "{}.{}({}){}", - snippet(cx, recv.span, ".."), - name, - map_arg.iter().map(|a| snippet(cx, a.span, "..")).join(", "), - preserve_cloned.then(|| ".cloned()").unwrap_or_default(), - ), - Applicability::MachineApplicable, - ); + span_lint_and_then( + cx, + lint, + expr.span, + msg, + |diag| { + let method_span = expr.span.with_lo(cloned_call.span.hi()); + if let Some(mut snip) = snippet_opt(cx, method_span) { + snip.push_str(trailing_clone); + let replace_span = expr.span.with_lo(cloned_recv.span.hi()); + diag.span_suggestion(replace_span, "try this", snip, Applicability::MachineApplicable); + } + } + ); + } } diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 7308e74c323..9bb7bb7a7ab 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -124,28 +124,24 @@ declare_clippy_lint! { /// It's often inefficient to clone all elements of an iterator, when eventually, only some /// of them will be consumed. /// + /// ### Known Problems + /// This `lint` removes the side of effect of cloning items in the iterator. + /// A code that relies on that side-effect could fail. + /// /// ### Examples /// ```rust /// # let vec = vec!["string".to_string()]; - /// - /// // Bad /// vec.iter().cloned().take(10); - /// - /// // Good - /// vec.iter().take(10).cloned(); - /// - /// // Bad /// vec.iter().cloned().last(); + /// ``` /// - /// // Good + /// Use instead: + /// ```rust + /// # let vec = vec!["string".to_string()]; + /// vec.iter().take(10).cloned(); /// vec.iter().last().cloned(); - /// /// ``` - /// ### Known Problems - /// This `lint` removes the side of effect of cloning items in the iterator. - /// A code that relies on that side-effect could fail. - /// - #[clippy::version = "1.59.0"] + #[clippy::version = "1.60.0"] pub ITER_OVEREAGER_CLONED, perf, "using `cloned()` early with `Iterator::iter()` can lead to some performance inefficiencies" @@ -342,11 +338,12 @@ declare_clippy_lint! { /// ### Example /// ```rust /// # let x = Ok::<_, ()>(()); - /// - /// // Bad /// x.ok().expect("why did I do this again?"); + /// ``` /// - /// // Good + /// Use instead: + /// ```rust + /// # let x = Ok::<_, ()>(()); /// x.expect("why did I do this again?"); /// ``` #[clippy::version = "pre 1.29.0"] @@ -390,12 +387,13 @@ declare_clippy_lint! { /// ### Examples /// ```rust /// # let x = Some(1); - /// - /// // Bad /// x.unwrap_or_else(Default::default); /// x.unwrap_or_else(u32::default); + /// ``` /// - /// // Good + /// Use instead: + /// ```rust + /// # let x = Some(1); /// x.unwrap_or_default(); /// ``` #[clippy::version = "1.56.0"] @@ -453,11 +451,12 @@ declare_clippy_lint! { /// ### Example /// ```rust /// # let opt = Some(1); - /// - /// // Bad /// opt.map_or(None, |a| Some(a + 1)); + /// ``` /// - /// // Good + /// Use instead: + /// ```rust + /// # let opt = Some(1); /// opt.and_then(|a| Some(a + 1)); /// ``` #[clippy::version = "pre 1.29.0"] @@ -475,13 +474,12 @@ declare_clippy_lint! { /// `_.ok()`. /// /// ### Example - /// Bad: /// ```rust /// # let r: Result = Ok(1); /// assert_eq!(Some(1), r.map_or(None, Some)); /// ``` /// - /// Good: + /// Use instead: /// ```rust /// # let r: Result = Ok(1); /// assert_eq!(Some(1), r.ok()); @@ -538,7 +536,8 @@ declare_clippy_lint! { /// # let vec = vec![1]; /// vec.iter().filter(|x| **x == 0).next(); /// ``` - /// Could be written as + /// + /// Use instead: /// ```rust /// # let vec = vec![1]; /// vec.iter().find(|x| **x == 0); @@ -562,7 +561,8 @@ declare_clippy_lint! { /// # let vec = vec![1]; /// vec.iter().skip_while(|x| **x == 0).next(); /// ``` - /// Could be written as + /// + /// Use instead: /// ```rust /// # let vec = vec![1]; /// vec.iter().find(|x| **x != 0); @@ -586,11 +586,14 @@ declare_clippy_lint! { /// let vec = vec![vec![1]]; /// let opt = Some(5); /// - /// // Bad /// vec.iter().map(|x| x.iter()).flatten(); /// opt.map(|x| Some(x * 2)).flatten(); + /// ``` /// - /// // Good + /// Use instead: + /// ```rust + /// # let vec = vec![vec![1]]; + /// # let opt = Some(5); /// vec.iter().flat_map(|x| x.iter()); /// opt.and_then(|x| Some(x * 2)); /// ``` @@ -610,15 +613,16 @@ declare_clippy_lint! { /// less performant. /// /// ### Example - /// Bad: /// ```rust + /// # #![allow(unused)] /// (0_i32..10) /// .filter(|n| n.checked_add(1).is_some()) /// .map(|n| n.checked_add(1).unwrap()); /// ``` /// - /// Good: + /// Use instead: /// ```rust + /// # #[allow(unused)] /// (0_i32..10).filter_map(|n| n.checked_add(1)); /// ``` #[clippy::version = "1.51.0"] @@ -637,14 +641,13 @@ declare_clippy_lint! { /// less performant. /// /// ### Example - /// Bad: /// ```rust /// (0_i32..10) /// .find(|n| n.checked_add(1).is_some()) /// .map(|n| n.checked_add(1).unwrap()); /// ``` /// - /// Good: + /// Use instead: /// ```rust /// (0_i32..10).find_map(|n| n.checked_add(1)); /// ``` @@ -712,17 +715,20 @@ declare_clippy_lint! { /// /// ### Example /// ```rust + /// # #![allow(unused)] /// let vec = vec![1]; /// vec.iter().find(|x| **x == 0).is_some(); /// - /// let _ = "hello world".find("world").is_none(); + /// "hello world".find("world").is_none(); /// ``` - /// Could be written as + /// + /// Use instead: /// ```rust /// let vec = vec![1]; /// vec.iter().any(|x| *x == 0); /// - /// let _ = !"hello world".contains("world"); + /// # #[allow(unused)] + /// !"hello world".contains("world"); /// ``` #[clippy::version = "pre 1.29.0"] pub SEARCH_IS_SOME, @@ -744,7 +750,8 @@ declare_clippy_lint! { /// let name = "foo"; /// if name.chars().next() == Some('_') {}; /// ``` - /// Could be written as + /// + /// Use instead: /// ```rust /// let name = "foo"; /// if name.starts_with('_') {}; @@ -899,10 +906,13 @@ declare_clippy_lint! { /// # use std::rc::Rc; /// let x = Rc::new(1); /// - /// // Bad /// x.clone(); + /// ``` /// - /// // Good + /// Use instead: + /// ```rust + /// # use std::rc::Rc; + /// # let x = Rc::new(1); /// Rc::clone(&x); /// ``` #[clippy::version = "pre 1.29.0"] @@ -1034,11 +1044,13 @@ declare_clippy_lint! { /// /// ### Example /// ```rust,ignore - /// // Bad /// _.split("x"); + /// ``` /// - /// // Good + /// Use instead: + /// ```rust,ignore /// _.split('x'); + /// ``` #[clippy::version = "pre 1.29.0"] pub SINGLE_CHAR_PATTERN, perf, @@ -1099,12 +1111,14 @@ declare_clippy_lint! { /// ### Example /// ```rust /// # use std::collections::HashSet; - /// // Bad /// # let mut s = HashSet::new(); /// # s.insert(1); /// let x = s.iter().nth(0); + /// ``` /// - /// // Good + /// Use instead: + /// ```rust + /// # use std::collections::HashSet; /// # let mut s = HashSet::new(); /// # s.insert(1); /// let x = s.iter().next(); @@ -1210,11 +1224,12 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// // Bad /// let x = vec![2, 3, 5]; /// let last_element = x.get(x.len() - 1); + /// ``` /// - /// // Good + /// Use instead: + /// ```rust /// let x = vec![2, 3, 5]; /// let last_element = x.last(); /// ``` @@ -1273,10 +1288,14 @@ declare_clippy_lint! { /// let mut a = vec![1, 2, 3]; /// let mut b = vec![4, 5, 6]; /// - /// // Bad /// a.extend(b.drain(..)); + /// ``` + /// + /// Use instead: + /// ```rust + /// let mut a = vec![1, 2, 3]; + /// let mut b = vec![4, 5, 6]; /// - /// // Good /// a.append(&mut b); /// ``` #[clippy::version = "1.55.0"] @@ -1351,11 +1370,12 @@ declare_clippy_lint! { /// ### Example /// ```rust /// # let name = "_"; - /// - /// // Bad /// name.chars().last() == Some('_') || name.chars().next_back() == Some('-'); + /// ``` /// - /// // Good + /// Use instead: + /// ```rust + /// # let name = "_"; /// name.ends_with('_') || name.ends_with('-'); /// ``` #[clippy::version = "pre 1.29.0"] @@ -1401,11 +1421,13 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// let _ = (0..3).fold(false, |acc, x| acc || x > 2); + /// # #[allow(unused)] + /// (0..3).fold(false, |acc, x| acc || x > 2); /// ``` - /// This could be written as: + /// + /// Use instead: /// ```rust - /// let _ = (0..3).any(|x| x > 2); + /// (0..3).any(|x| x > 2); /// ``` #[clippy::version = "pre 1.29.0"] pub UNNECESSARY_FOLD, @@ -1485,11 +1507,14 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// // Bad - /// let _ = (&vec![3, 4, 5]).into_iter(); + /// # let vec = vec![3, 4, 5]; + /// (&vec).into_iter(); + /// ``` /// - /// // Good - /// let _ = (&vec![3, 4, 5]).iter(); + /// Use instead: + /// ```rust + /// # let vec = vec![3, 4, 5]; + /// (&vec).iter(); /// ``` #[clippy::version = "1.32.0"] pub INTO_ITER_ON_REF, @@ -1704,13 +1729,14 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// let mut string = String::new(); + /// # let mut string = String::new(); /// string.insert_str(0, "R"); /// string.push_str("R"); /// ``` - /// Could be written as + /// + /// Use instead: /// ```rust - /// let mut string = String::new(); + /// # let mut string = String::new(); /// string.insert(0, 'R'); /// string.push('R'); /// ``` @@ -1881,7 +1907,7 @@ declare_clippy_lint! { /// let x = [1, 2, 3]; /// let y: Vec<_> = x.iter().map(|x| 2*x).collect(); /// ``` - #[clippy::version = "1.52.0"] + #[clippy::version = "1.47.0"] pub MAP_IDENTITY, complexity, "using iterator.map(|x| x)" @@ -1897,11 +1923,14 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// // Bad - /// let _ = "Hello".bytes().nth(3); + /// # #[allow(unused)] + /// "Hello".bytes().nth(3); + /// ``` /// - /// // Good - /// let _ = "Hello".as_bytes().get(3); + /// Use instead: + /// ```rust + /// # #[allow(unused)] + /// "Hello".as_bytes().get(3); /// ``` #[clippy::version = "1.52.0"] pub BYTES_NTH, @@ -1945,15 +1974,19 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// // Bad + /// # #![allow(unused)] /// let some_vec = vec![0, 1, 2, 3]; - /// let _ = some_vec.iter().count(); - /// let _ = &some_vec[..].iter().count(); /// - /// // Good + /// some_vec.iter().count(); + /// &some_vec[..].iter().count(); + /// ``` + /// + /// Use instead: + /// ```rust /// let some_vec = vec![0, 1, 2, 3]; - /// let _ = some_vec.len(); - /// let _ = &some_vec[..].len(); + /// + /// some_vec.len(); + /// &some_vec[..].len(); /// ``` #[clippy::version = "1.52.0"] pub ITER_COUNT, @@ -1973,16 +2006,17 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// // Bad - /// let s = ""; + /// # let s = ""; /// for x in s.splitn(1, ":") { - /// // use x + /// // .. /// } + /// ``` /// - /// // Good - /// let s = ""; + /// Use instead: + /// ```rust + /// # let s = ""; /// for x in s.splitn(2, ":") { - /// // use x + /// // .. /// } /// ``` #[clippy::version = "1.54.0"] @@ -2000,10 +2034,11 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// // Bad /// let x: String = std::iter::repeat('x').take(10).collect(); + /// ``` /// - /// // Good + /// Use instead: + /// ```rust /// let x: String = "x".repeat(10); /// ``` #[clippy::version = "1.54.0"] @@ -2021,7 +2056,6 @@ declare_clippy_lint! { /// /// ### Example /// ```rust,ignore - /// // Bad /// let s = "key=value=add"; /// let (key, value) = s.splitn(2, '=').next_tuple()?; /// let value = s.splitn(2, '=').nth(1)?; @@ -2030,9 +2064,9 @@ declare_clippy_lint! { /// let key = parts.next()?; /// let value = parts.next()?; /// ``` + /// /// Use instead: /// ```rust,ignore - /// // Good /// let s = "key=value=add"; /// let (key, value) = s.split_once('=')?; /// let value = s.split_once('=')?.1; @@ -2057,17 +2091,16 @@ declare_clippy_lint! { /// that both functions return a lazy iterator. /// ### Example /// ```rust - /// // Bad /// let str = "key=value=add"; /// let _ = str.splitn(3, '=').next().unwrap(); /// ``` + /// /// Use instead: /// ```rust - /// // Good /// let str = "key=value=add"; /// let _ = str.split('=').next().unwrap(); /// ``` - #[clippy::version = "1.58.0"] + #[clippy::version = "1.59.0"] pub NEEDLESS_SPLITN, complexity, "usages of `str::splitn` that can be replaced with `str::split`" @@ -2098,7 +2131,7 @@ declare_clippy_lint! { /// foo(&path.to_string_lossy()); /// fn foo(s: &str) {} /// ``` - #[clippy::version = "1.58.0"] + #[clippy::version = "1.59.0"] pub UNNECESSARY_TO_OWNED, perf, "unnecessary calls to `to_owned`-like functions" @@ -2149,7 +2182,8 @@ declare_clippy_lint! { /// let a = Some(&1); /// let b = a.as_deref(); // goes from Option<&i32> to Option<&i32> /// ``` - /// Could be written as: + /// + /// Use instead: /// ```rust /// let a = Some(&1); /// let b = a; @@ -2583,8 +2617,8 @@ impl Methods { }, _ => {}, }, - (name @ "count", args @ []) => match method_call(recv) { - Some(("cloned", [recv2], _)) => iter_overeager_cloned::check(cx, expr, recv2, name, args), + ("count", []) => match method_call(recv) { + Some(("cloned", [recv2], _)) => iter_overeager_cloned::check(cx, expr, recv, recv2, true, false), Some((name2 @ ("into_iter" | "iter" | "iter_mut"), [recv2], _)) => { iter_count::check(cx, expr, recv2, name2); }, @@ -2614,9 +2648,9 @@ impl Methods { flat_map_identity::check(cx, expr, arg, span); flat_map_option::check(cx, expr, arg, span); }, - (name @ "flatten", args @ []) => match method_call(recv) { + ("flatten", []) => match method_call(recv) { Some(("map", [recv, map_arg], map_span)) => map_flatten::check(cx, expr, recv, map_arg, map_span), - Some(("cloned", [recv2], _)) => iter_overeager_cloned::check(cx, expr, recv2, name, args), + Some(("cloned", [recv2], _)) => iter_overeager_cloned::check(cx, expr, recv, recv2, false, true), _ => {}, }, ("fold", [init, acc]) => unnecessary_fold::check(cx, expr, init, acc, span), @@ -2636,10 +2670,10 @@ impl Methods { unnecessary_join::check(cx, expr, recv, join_arg, span); } }, - ("last", args @ []) | ("skip", args @ [_]) => { + ("last", []) | ("skip", [_]) => { if let Some((name2, [recv2, args2 @ ..], _span2)) = method_call(recv) { if let ("cloned", []) = (name2, args2) { - iter_overeager_cloned::check(cx, expr, recv2, name, args); + iter_overeager_cloned::check(cx, expr, recv, recv2, false, false); } } }, @@ -2660,10 +2694,10 @@ impl Methods { map_identity::check(cx, expr, recv, m_arg, name, span); }, ("map_or", [def, map]) => option_map_or_none::check(cx, expr, recv, def, map), - (name @ "next", args @ []) => { + ("next", []) => { if let Some((name2, [recv2, args2 @ ..], _)) = method_call(recv) { match (name2, args2) { - ("cloned", []) => iter_overeager_cloned::check(cx, expr, recv2, name, args), + ("cloned", []) => iter_overeager_cloned::check(cx, expr, recv, recv2, false, false), ("filter", [arg]) => filter_next::check(cx, expr, recv2, arg), ("filter_map", [arg]) => filter_map_next::check(cx, expr, recv2, arg, self.msrv), ("iter", []) => iter_next_slice::check(cx, expr, recv2), @@ -2673,9 +2707,9 @@ impl Methods { } } }, - ("nth", args @ [n_arg]) => match method_call(recv) { + ("nth", [n_arg]) => match method_call(recv) { Some(("bytes", [recv2], _)) => bytes_nth::check(cx, expr, recv2, n_arg), - Some(("cloned", [recv2], _)) => iter_overeager_cloned::check(cx, expr, recv2, name, args), + Some(("cloned", [recv2], _)) => iter_overeager_cloned::check(cx, expr, recv, recv2, false, false), Some(("iter", [recv2], _)) => iter_nth::check(cx, expr, recv2, recv, n_arg, false), Some(("iter_mut", [recv2], _)) => iter_nth::check(cx, expr, recv2, recv, n_arg, true), _ => iter_nth_zero::check(cx, expr, recv, n_arg), @@ -2698,10 +2732,10 @@ impl Methods { } }, ("step_by", [arg]) => iterator_step_by_zero::check(cx, expr, arg), - ("take", args @ [_arg]) => { + ("take", [_arg]) => { if let Some((name2, [recv2, args2 @ ..], _span2)) = method_call(recv) { if let ("cloned", []) = (name2, args2) { - iter_overeager_cloned::check(cx, expr, recv2, name, args); + iter_overeager_cloned::check(cx, expr, recv, recv2, false, false); } } }, diff --git a/clippy_lints/src/misc.rs b/clippy_lints/src/misc.rs index 55665699453..01bf871198a 100644 --- a/clippy_lints/src/misc.rs +++ b/clippy_lints/src/misc.rs @@ -45,16 +45,13 @@ declare_clippy_lint! { /// dereferences, e.g., changing `*x` to `x` within the function. /// /// ### Example - /// ```rust,ignore - /// // Bad - /// fn foo(ref x: u8) -> bool { - /// true - /// } + /// ```rust + /// fn foo(ref _x: u8) {} + /// ``` /// - /// // Good - /// fn foo(x: &u8) -> bool { - /// true - /// } + /// Use instead: + /// ```rust + /// fn foo(_x: &u8) {} /// ``` #[clippy::version = "pre 1.29.0"] pub TOPLEVEL_REF_ARG, @@ -73,11 +70,12 @@ declare_clippy_lint! { /// ### Example /// ```rust /// # let x = 1.0; - /// - /// // Bad /// if x == f32::NAN { } + /// ``` /// - /// // Good + /// Use instead: + /// ```rust + /// # let x = 1.0f32; /// if x.is_nan() { } /// ``` #[clippy::version = "pre 1.29.0"] @@ -139,7 +137,8 @@ declare_clippy_lint! { /// # let y = String::from("foo"); /// if x.to_owned() == y {} /// ``` - /// Could be written as + /// + /// Use instead: /// ```rust /// # let x = "foo"; /// # let y = String::from("foo"); @@ -232,10 +231,11 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// // Bad /// let a = 0 as *const u32; + /// ``` /// - /// // Good + /// Use instead: + /// ```rust /// let a = std::ptr::null::(); /// ``` #[clippy::version = "pre 1.29.0"] diff --git a/clippy_lints/src/misc_early/mod.rs b/clippy_lints/src/misc_early/mod.rs index 6860b60acbd..704918c0b97 100644 --- a/clippy_lints/src/misc_early/mod.rs +++ b/clippy_lints/src/misc_early/mod.rs @@ -34,13 +34,21 @@ declare_clippy_lint! { /// # } /// let f = Foo { a: 0, b: 0, c: 0 }; /// - /// // Bad /// match f { /// Foo { a: _, b: 0, .. } => {}, /// Foo { a: _, b: _, c: _ } => {}, /// } + /// ``` + /// + /// Use instead: + /// ```rust + /// # struct Foo { + /// # a: i32, + /// # b: i32, + /// # c: i32, + /// # } + /// let f = Foo { a: 0, b: 0, c: 0 }; /// - /// // Good /// match f { /// Foo { b: 0, .. } => {}, /// Foo { .. } => {}, @@ -62,10 +70,11 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// // Bad /// fn foo(a: i32, _a: i32) {} + /// ``` /// - /// // Good + /// Use instead: + /// ```rust /// fn bar(a: i32, _b: i32) {} /// ``` #[clippy::version = "pre 1.29.0"] @@ -103,11 +112,16 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// // Bad - /// let y = 0x1a9BAcD; + /// # let _ = + /// 0x1a9BAcD + /// # ; + /// ``` /// - /// // Good - /// let y = 0x1A9BACD; + /// Use instead: + /// ```rust + /// # let _ = + /// 0x1A9BACD + /// # ; /// ``` #[clippy::version = "pre 1.29.0"] pub MIXED_CASE_HEX_LITERALS, @@ -127,11 +141,16 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// // Bad - /// let y = 123832i32; + /// # let _ = + /// 123832i32 + /// # ; + /// ``` /// - /// // Good - /// let y = 123832_i32; + /// Use instead: + /// ```rust + /// # let _ = + /// 123832_i32 + /// # ; /// ``` #[clippy::version = "pre 1.29.0"] pub UNSEPARATED_LITERAL_SUFFIX, @@ -150,11 +169,16 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// // Bad - /// let y = 123832_i32; + /// # let _ = + /// 123832_i32 + /// # ; + /// ``` /// - /// // Good - /// let y = 123832i32; + /// Use instead: + /// ```rust + /// # let _ = + /// 123832i32 + /// # ; /// ``` #[clippy::version = "1.58.0"] pub SEPARATED_LITERAL_SUFFIX, @@ -234,14 +258,15 @@ declare_clippy_lint! { /// ### Example /// ```rust /// # let v = Some("abc"); - /// - /// // Bad /// match v { /// Some(x) => (), /// y @ _ => (), /// } + /// ``` /// - /// // Good + /// Use instead: + /// ```rust + /// # let v = Some("abc"); /// match v { /// Some(x) => (), /// y => (), @@ -262,6 +287,7 @@ declare_clippy_lint! { /// means there are 0 or more elements left. This can make a difference /// when refactoring, but shouldn't result in errors in the refactored code, /// since the wildcard pattern isn't used anyway. + /// /// ### Why is this bad? /// The wildcard pattern is unneeded as the rest pattern /// can match that element as well. @@ -270,13 +296,16 @@ declare_clippy_lint! { /// ```rust /// # struct TupleStruct(u32, u32, u32); /// # let t = TupleStruct(1, 2, 3); - /// // Bad /// match t { /// TupleStruct(0, .., _) => (), /// _ => (), /// } + /// ``` /// - /// // Good + /// Use instead: + /// ```rust + /// # struct TupleStruct(u32, u32, u32); + /// # let t = TupleStruct(1, 2, 3); /// match t { /// TupleStruct(0, ..) => (), /// _ => (), diff --git a/clippy_lints/src/mixed_read_write_in_expression.rs b/clippy_lints/src/mixed_read_write_in_expression.rs index c3b850fbb9d..a2419c277e9 100644 --- a/clippy_lints/src/mixed_read_write_in_expression.rs +++ b/clippy_lints/src/mixed_read_write_in_expression.rs @@ -25,14 +25,16 @@ declare_clippy_lint! { /// ```rust /// let mut x = 0; /// - /// // Bad /// let a = { /// x = 1; /// 1 /// } + x; /// // Unclear whether a is 1 or 2. + /// ``` /// - /// // Good + /// Use instead: + /// ```rust + /// # let mut x = 0; /// let tmp = { /// x = 1; /// 1 diff --git a/clippy_lints/src/mut_reference.rs b/clippy_lints/src/mut_reference.rs index 9d8f8999ce4..f434a655f8a 100644 --- a/clippy_lints/src/mut_reference.rs +++ b/clippy_lints/src/mut_reference.rs @@ -16,12 +16,17 @@ declare_clippy_lint! { /// the value. Also the code misleads about the intent of the call site. /// /// ### Example - /// ```ignore - /// // Bad - /// my_vec.push(&mut value) + /// ```rust + /// # let mut vec = Vec::new(); + /// # let mut value = 5; + /// vec.push(&mut value); + /// ``` /// - /// // Good - /// my_vec.push(&value) + /// Use instead: + /// ```rust + /// # let mut vec = Vec::new(); + /// # let value = 5; + /// vec.push(&value); /// ``` #[clippy::version = "pre 1.29.0"] pub UNNECESSARY_MUT_PASSED, diff --git a/clippy_lints/src/mutex_atomic.rs b/clippy_lints/src/mutex_atomic.rs index 73823779e49..a98577093ed 100644 --- a/clippy_lints/src/mutex_atomic.rs +++ b/clippy_lints/src/mutex_atomic.rs @@ -27,12 +27,13 @@ declare_clippy_lint! { /// ### Example /// ```rust /// # let y = true; - /// - /// // Bad /// # use std::sync::Mutex; /// let x = Mutex::new(&y); + /// ``` /// - /// // Good + /// Use instead: + /// ```rust + /// # let y = true; /// # use std::sync::atomic::AtomicBool; /// let x = AtomicBool::new(y); /// ``` @@ -60,8 +61,10 @@ declare_clippy_lint! { /// ```rust /// # use std::sync::Mutex; /// let x = Mutex::new(0usize); + /// ``` /// - /// // Good + /// Use instead: + /// ```rust /// # use std::sync::atomic::AtomicUsize; /// let x = AtomicUsize::new(0usize); /// ``` diff --git a/clippy_lints/src/needless_bool.rs b/clippy_lints/src/needless_bool.rs index 778d49cb4b6..a4eec95b371 100644 --- a/clippy_lints/src/needless_bool.rs +++ b/clippy_lints/src/needless_bool.rs @@ -30,16 +30,21 @@ declare_clippy_lint! { /// shorter code. /// /// ### Example - /// ```rust,ignore + /// ```rust + /// # let x = true; /// if x { /// false /// } else { /// true /// } + /// # ; /// ``` - /// Could be written as - /// ```rust,ignore + /// + /// Use instead: + /// ```rust + /// # let x = true; /// !x + /// # ; /// ``` #[clippy::version = "pre 1.29.0"] pub NEEDLESS_BOOL, diff --git a/clippy_lints/src/needless_borrowed_ref.rs b/clippy_lints/src/needless_borrowed_ref.rs index 0fcc419e722..05c012b92e8 100644 --- a/clippy_lints/src/needless_borrowed_ref.rs +++ b/clippy_lints/src/needless_borrowed_ref.rs @@ -27,16 +27,17 @@ declare_clippy_lint! { /// ``` /// /// ### Example - /// Bad: /// ```rust /// let mut v = Vec::::new(); - /// let _ = v.iter_mut().filter(|&ref a| a.is_empty()); + /// # #[allow(unused)] + /// v.iter_mut().filter(|&ref a| a.is_empty()); /// ``` /// - /// Good: + /// Use instead: /// ```rust /// let mut v = Vec::::new(); - /// let _ = v.iter_mut().filter(|a| a.is_empty()); + /// # #[allow(unused)] + /// v.iter_mut().filter(|a| a.is_empty()); /// ``` #[clippy::version = "pre 1.29.0"] pub NEEDLESS_BORROWED_REFERENCE, diff --git a/clippy_lints/src/needless_late_init.rs b/clippy_lints/src/needless_late_init.rs index 1f8c4c85cc2..ff2999b1f4a 100644 --- a/clippy_lints/src/needless_late_init.rs +++ b/clippy_lints/src/needless_late_init.rs @@ -56,7 +56,7 @@ declare_clippy_lint! { /// -1 /// }; /// ``` - #[clippy::version = "1.58.0"] + #[clippy::version = "1.59.0"] pub NEEDLESS_LATE_INIT, style, "late initializations that can be replaced by a `let` statement with an initializer" @@ -185,14 +185,14 @@ fn assignment_suggestions<'tcx>( let suggestions = assignments .iter() - .map(|assignment| Some((assignment.span.until(assignment.rhs_span), String::new()))) - .chain(assignments.iter().map(|assignment| { - Some(( + .flat_map(|assignment| { + [ + assignment.span.until(assignment.rhs_span), assignment.rhs_span.shrink_to_hi().with_hi(assignment.span.hi()), - String::new(), - )) - })) - .collect::>>()?; + ] + }) + .map(|span| (span, String::new())) + .collect::>(); match suggestions.len() { // All of `exprs` are never types diff --git a/clippy_lints/src/needless_parens_on_range_literals.rs b/clippy_lints/src/needless_parens_on_range_literals.rs new file mode 100644 index 00000000000..6e54b243c03 --- /dev/null +++ b/clippy_lints/src/needless_parens_on_range_literals.rs @@ -0,0 +1,87 @@ +use clippy_utils::{ + diagnostics::span_lint_and_then, + higher, + source::{snippet, snippet_with_applicability}, +}; + +use rustc_ast::ast; +use rustc_errors::Applicability; +use rustc_hir::{Expr, ExprKind}; + +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// ### What it does + /// The lint checks for parenthesis on literals in range statements that are + /// superfluous. + /// + /// ### Why is this bad? + /// Having superfluous parenthesis makes the code less readable + /// overhead when reading. + /// + /// ### Example + /// + /// ```rust + /// for i in (0)..10 { + /// println!("{i}"); + /// } + /// ``` + /// + /// Use instead: + /// + /// ```rust + /// for i in 0..10 { + /// println!("{i}"); + /// } + /// ``` + #[clippy::version = "1.63.0"] + pub NEEDLESS_PARENS_ON_RANGE_LITERALS, + style, + "needless parenthesis on range literals can be removed" +} + +declare_lint_pass!(NeedlessParensOnRangeLiterals => [NEEDLESS_PARENS_ON_RANGE_LITERALS]); + +fn snippet_enclosed_in_parenthesis(snippet: &str) -> bool { + snippet.starts_with('(') && snippet.ends_with(')') +} + +fn check_for_parens(cx: &LateContext<'_>, e: &Expr<'_>, is_start: bool) { + if is_start && + let ExprKind::Lit(ref literal) = e.kind && + let ast::LitKind::Float(_sym, ast::LitFloatType::Unsuffixed) = literal.node + { + // don't check floating point literals on the start expression of a range + return; + } + if_chain! { + if let ExprKind::Lit(ref literal) = e.kind; + // the indicator that parenthesis surround the literal is that the span of the expression and the literal differ + if (literal.span.data().hi - literal.span.data().lo) != (e.span.data().hi - e.span.data().lo); + // inspect the source code of the expression for parenthesis + if snippet_enclosed_in_parenthesis(&snippet(cx, e.span, "")); + then { + let mut applicability = Applicability::MachineApplicable; + span_lint_and_then(cx, NEEDLESS_PARENS_ON_RANGE_LITERALS, e.span, + "needless parenthesis on range literals can be removed", + |diag| { + let suggestion = snippet_with_applicability(cx, literal.span, "_", &mut applicability); + diag.span_suggestion(e.span, "try", suggestion, applicability); + }); + } + } +} + +impl<'tcx> LateLintPass<'tcx> for NeedlessParensOnRangeLiterals { + fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { + if let Some(higher::Range { start, end, .. }) = higher::Range::hir(expr) { + if let Some(start) = start { + check_for_parens(cx, start, true); + } + if let Some(end) = end { + check_for_parens(cx, end, false); + } + } + } +} diff --git a/clippy_lints/src/needless_update.rs b/clippy_lints/src/needless_update.rs index c87c174ef73..0bd29d1776b 100644 --- a/clippy_lints/src/needless_update.rs +++ b/clippy_lints/src/needless_update.rs @@ -24,16 +24,17 @@ declare_clippy_lint! { /// # z: i32, /// # } /// # let zero_point = Point { x: 0, y: 0, z: 0 }; - /// - /// // Bad /// Point { /// x: 1, /// y: 1, /// z: 1, /// ..zero_point /// }; + /// ``` /// - /// // Ok + /// Use instead: + /// ```rust,ignore + /// // Missing field `z` /// Point { /// x: 1, /// y: 1, diff --git a/clippy_lints/src/neg_cmp_op_on_partial_ord.rs b/clippy_lints/src/neg_cmp_op_on_partial_ord.rs index efe31a15441..a7e0e35787c 100644 --- a/clippy_lints/src/neg_cmp_op_on_partial_ord.rs +++ b/clippy_lints/src/neg_cmp_op_on_partial_ord.rs @@ -19,17 +19,17 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// use std::cmp::Ordering; - /// - /// // Bad /// let a = 1.0; /// let b = f64::NAN; /// - /// let _not_less_or_equal = !(a <= b); + /// let not_less_or_equal = !(a <= b); + /// ``` /// - /// // Good - /// let a = 1.0; - /// let b = f64::NAN; + /// Use instead: + /// ```rust + /// use std::cmp::Ordering; + /// # let a = 1.0; + /// # let b = f64::NAN; /// /// let _not_less_or_equal = match a.partial_cmp(&b) { /// None | Some(Ordering::Greater) => true, diff --git a/clippy_lints/src/neg_multiply.rs b/clippy_lints/src/neg_multiply.rs index 707f3b2181a..ce6bb38b7c0 100644 --- a/clippy_lints/src/neg_multiply.rs +++ b/clippy_lints/src/neg_multiply.rs @@ -19,12 +19,13 @@ declare_clippy_lint! { /// This only catches integers (for now). /// /// ### Example - /// ```ignore - /// // Bad + /// ```rust,ignore /// let a = x * -1; + /// ``` /// - /// // Good - /// let b = -x; + /// Use instead: + /// ```rust,ignore + /// let a = -x; /// ``` #[clippy::version = "pre 1.29.0"] pub NEG_MULTIPLY, diff --git a/clippy_lints/src/non_copy_const.rs b/clippy_lints/src/non_copy_const.rs index 7163cfe5e3a..1727275a4e0 100644 --- a/clippy_lints/src/non_copy_const.rs +++ b/clippy_lints/src/non_copy_const.rs @@ -59,12 +59,14 @@ declare_clippy_lint! { /// ```rust /// use std::sync::atomic::{AtomicUsize, Ordering::SeqCst}; /// - /// // Bad. /// const CONST_ATOM: AtomicUsize = AtomicUsize::new(12); /// CONST_ATOM.store(6, SeqCst); // the content of the atomic is unchanged /// assert_eq!(CONST_ATOM.load(SeqCst), 12); // because the CONST_ATOM in these lines are distinct + /// ``` /// - /// // Good. + /// Use instead: + /// ```rust + /// # use std::sync::atomic::{AtomicUsize, Ordering::SeqCst}; /// static STATIC_ATOM: AtomicUsize = AtomicUsize::new(15); /// STATIC_ATOM.store(9, SeqCst); /// assert_eq!(STATIC_ATOM.load(SeqCst), 9); // use a `static` item to refer to the same instance @@ -105,11 +107,15 @@ declare_clippy_lint! { /// use std::sync::atomic::{AtomicUsize, Ordering::SeqCst}; /// const CONST_ATOM: AtomicUsize = AtomicUsize::new(12); /// - /// // Bad. /// CONST_ATOM.store(6, SeqCst); // the content of the atomic is unchanged /// assert_eq!(CONST_ATOM.load(SeqCst), 12); // because the CONST_ATOM in these lines are distinct + /// ``` + /// + /// Use instead: + /// ```rust + /// use std::sync::atomic::{AtomicUsize, Ordering::SeqCst}; + /// const CONST_ATOM: AtomicUsize = AtomicUsize::new(12); /// - /// // Good. /// static STATIC_ATOM: AtomicUsize = CONST_ATOM; /// STATIC_ATOM.store(9, SeqCst); /// assert_eq!(STATIC_ATOM.load(SeqCst), 9); // use a `static` item to refer to the same instance diff --git a/clippy_lints/src/octal_escapes.rs b/clippy_lints/src/octal_escapes.rs index e8532db4f71..3ab4b6c4f6f 100644 --- a/clippy_lints/src/octal_escapes.rs +++ b/clippy_lints/src/octal_escapes.rs @@ -33,15 +33,16 @@ declare_clippy_lint! { /// /// # Example /// ```rust - /// // Bad /// let one = "\033[1m Bold? \033[0m"; // \033 intended as escape /// let two = "\033\0"; // \033 intended as null-3-3 + /// ``` /// - /// // Good + /// Use instead: + /// ```rust /// let one = "\x1b[1mWill this be bold?\x1b[0m"; /// let two = "\x0033\x00"; /// ``` - #[clippy::version = "1.58.0"] + #[clippy::version = "1.59.0"] pub OCTAL_ESCAPES, suspicious, "string escape sequences looking like octal characters" diff --git a/clippy_lints/src/only_used_in_recursion.rs b/clippy_lints/src/only_used_in_recursion.rs index de5f77f3ad9..677ac998b56 100644 --- a/clippy_lints/src/only_used_in_recursion.rs +++ b/clippy_lints/src/only_used_in_recursion.rs @@ -87,7 +87,7 @@ declare_clippy_lint! { /// # print!("{}", f(1)); /// # } /// ``` - #[clippy::version = "1.60.0"] + #[clippy::version = "1.61.0"] pub ONLY_USED_IN_RECURSION, nursery, "arguments that is only used in recursion can be removed" diff --git a/clippy_lints/src/pass_by_ref_or_value.rs b/clippy_lints/src/pass_by_ref_or_value.rs index 5a93431f25a..05ab62786f4 100644 --- a/clippy_lints/src/pass_by_ref_or_value.rs +++ b/clippy_lints/src/pass_by_ref_or_value.rs @@ -57,12 +57,11 @@ declare_clippy_lint! { /// ### Example /// /// ```rust - /// // Bad /// fn foo(v: &u32) {} /// ``` /// + /// Use instead: /// ```rust - /// // Better /// fn foo(v: u32) {} /// ``` #[clippy::version = "pre 1.29.0"] @@ -89,14 +88,13 @@ declare_clippy_lint! { /// #[derive(Clone, Copy)] /// struct TooLarge([u8; 2048]); /// - /// // Bad /// fn foo(v: TooLarge) {} /// ``` - /// ```rust - /// #[derive(Clone, Copy)] - /// struct TooLarge([u8; 2048]); /// - /// // Good + /// Use instead: + /// ```rust + /// # #[derive(Clone, Copy)] + /// # struct TooLarge([u8; 2048]); /// fn foo(v: &TooLarge) {} /// ``` #[clippy::version = "1.49.0"] diff --git a/clippy_lints/src/ptr.rs b/clippy_lints/src/ptr.rs index 0b96f6ff683..b06eba13d2f 100644 --- a/clippy_lints/src/ptr.rs +++ b/clippy_lints/src/ptr.rs @@ -48,10 +48,11 @@ declare_clippy_lint! { /// /// ### Example /// ```ignore - /// // Bad /// fn foo(&Vec) { .. } + /// ``` /// - /// // Good + /// Use instead: + /// ```ignore /// fn foo(&[u32]) { .. } /// ``` #[clippy::version = "pre 1.29.0"] @@ -70,15 +71,18 @@ declare_clippy_lint! { /// method instead /// /// ### Example - /// ```ignore - /// // Bad + /// ```rust,ignore + /// use std::ptr; + /// /// if x == ptr::null { - /// .. + /// // .. /// } + /// ``` /// - /// // Good + /// Use instead: + /// ```rust,ignore /// if x.is_null() { - /// .. + /// // .. /// } /// ``` #[clippy::version = "pre 1.29.0"] @@ -129,12 +133,12 @@ declare_clippy_lint! { /// /// ### Example /// ```ignore - /// // Bad. Undefined behavior + /// // Undefined behavior /// unsafe { std::slice::from_raw_parts(ptr::null(), 0); } /// ``` /// + /// Use instead: /// ```ignore - /// // Good /// unsafe { std::slice::from_raw_parts(NonNull::dangling().as_ptr(), 0); } /// ``` #[clippy::version = "1.53.0"] diff --git a/clippy_lints/src/ranges.rs b/clippy_lints/src/ranges.rs index eea036178b8..547d4da8187 100644 --- a/clippy_lints/src/ranges.rs +++ b/clippy_lints/src/ranges.rs @@ -27,12 +27,13 @@ declare_clippy_lint! { /// ### Example /// ```rust /// # let x = vec![1]; - /// x.iter().zip(0..x.len()); + /// let _ = x.iter().zip(0..x.len()); /// ``` - /// Could be written as + /// + /// Use instead: /// ```rust /// # let x = vec![1]; - /// x.iter().enumerate(); + /// let _ = x.iter().enumerate(); /// ``` #[clippy::version = "pre 1.29.0"] pub RANGE_ZIP_WITH_LEN, @@ -65,12 +66,21 @@ declare_clippy_lint! { /// ([#3307](https://github.com/rust-lang/rust-clippy/issues/3307)). /// /// ### Example - /// ```rust,ignore - /// for x..(y+1) { .. } + /// ```rust + /// # let x = 0; + /// # let y = 1; + /// for i in x..(y+1) { + /// // .. + /// } /// ``` - /// Could be written as - /// ```rust,ignore - /// for x..=y { .. } + /// + /// Use instead: + /// ```rust + /// # let x = 0; + /// # let y = 1; + /// for i in x..=y { + /// // .. + /// } /// ``` #[clippy::version = "pre 1.29.0"] pub RANGE_PLUS_ONE, @@ -94,12 +104,21 @@ declare_clippy_lint! { /// ([#3307](https://github.com/rust-lang/rust-clippy/issues/3307)). /// /// ### Example - /// ```rust,ignore - /// for x..=(y-1) { .. } + /// ```rust + /// # let x = 0; + /// # let y = 1; + /// for i in x..=(y-1) { + /// // .. + /// } /// ``` - /// Could be written as - /// ```rust,ignore - /// for x..y { .. } + /// + /// Use instead: + /// ```rust + /// # let x = 0; + /// # let y = 1; + /// for i in x..y { + /// // .. + /// } /// ``` #[clippy::version = "pre 1.29.0"] pub RANGE_MINUS_ONE, diff --git a/clippy_lints/src/read_zero_byte_vec.rs b/clippy_lints/src/read_zero_byte_vec.rs new file mode 100644 index 00000000000..9538a810473 --- /dev/null +++ b/clippy_lints/src/read_zero_byte_vec.rs @@ -0,0 +1,142 @@ +use clippy_utils::{ + diagnostics::{span_lint, span_lint_and_sugg}, + higher::{get_vec_init_kind, VecInitKind}, + source::snippet, + visitors::expr_visitor_no_bodies, +}; +use hir::{intravisit::Visitor, ExprKind, Local, PatKind, PathSegment, QPath, StmtKind}; +use rustc_errors::Applicability; +use rustc_hir as hir; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_session::{declare_lint_pass, declare_tool_lint}; + +declare_clippy_lint! { + /// ### What it does + /// This lint catches reads into a zero-length `Vec`. + /// Especially in the case of a call to `with_capacity`, this lint warns that read + /// gets the number of bytes from the `Vec`'s length, not its capacity. + /// + /// ### Why is this bad? + /// Reading zero bytes is almost certainly not the intended behavior. + /// + /// ### Known problems + /// In theory, a very unusual read implementation could assign some semantic meaning + /// to zero-byte reads. But it seems exceptionally unlikely that code intending to do + /// a zero-byte read would allocate a `Vec` for it. + /// + /// ### Example + /// ```rust + /// use std::io; + /// fn foo(mut f: F) { + /// let mut data = Vec::with_capacity(100); + /// f.read(&mut data).unwrap(); + /// } + /// ``` + /// Use instead: + /// ```rust + /// use std::io; + /// fn foo(mut f: F) { + /// let mut data = Vec::with_capacity(100); + /// data.resize(100, 0); + /// f.read(&mut data).unwrap(); + /// } + /// ``` + #[clippy::version = "1.63.0"] + pub READ_ZERO_BYTE_VEC, + correctness, + "checks for reads into a zero-length `Vec`" +} +declare_lint_pass!(ReadZeroByteVec => [READ_ZERO_BYTE_VEC]); + +impl<'tcx> LateLintPass<'tcx> for ReadZeroByteVec { + fn check_block(&mut self, cx: &LateContext<'tcx>, block: &hir::Block<'tcx>) { + for (idx, stmt) in block.stmts.iter().enumerate() { + if !stmt.span.from_expansion() + // matches `let v = Vec::new();` + && let StmtKind::Local(local) = stmt.kind + && let Local { pat, init: Some(init), .. } = local + && let PatKind::Binding(_, _, ident, _) = pat.kind + && let Some(vec_init_kind) = get_vec_init_kind(cx, init) + { + // finds use of `_.read(&mut v)` + let mut read_found = false; + let mut visitor = expr_visitor_no_bodies(|expr| { + if let ExprKind::MethodCall(path, [_self, arg], _) = expr.kind + && let PathSegment { ident: read_or_read_exact, .. } = *path + && matches!(read_or_read_exact.as_str(), "read" | "read_exact") + && let ExprKind::AddrOf(_, hir::Mutability::Mut, inner) = arg.kind + && let ExprKind::Path(QPath::Resolved(None, inner_path)) = inner.kind + && let [inner_seg] = inner_path.segments + && ident.name == inner_seg.ident.name + { + read_found = true; + } + !read_found + }); + + let next_stmt_span; + if idx == block.stmts.len() - 1 { + // case { .. stmt; expr } + if let Some(e) = block.expr { + visitor.visit_expr(e); + next_stmt_span = e.span; + } else { + return; + } + } else { + // case { .. stmt; stmt; .. } + let next_stmt = &block.stmts[idx + 1]; + visitor.visit_stmt(next_stmt); + next_stmt_span = next_stmt.span; + } + drop(visitor); + + if read_found && !next_stmt_span.from_expansion() { + let applicability = Applicability::MaybeIncorrect; + match vec_init_kind { + VecInitKind::WithConstCapacity(len) => { + span_lint_and_sugg( + cx, + READ_ZERO_BYTE_VEC, + next_stmt_span, + "reading zero byte data to `Vec`", + "try", + format!("{}.resize({}, 0); {}", + ident.as_str(), + len, + snippet(cx, next_stmt_span, "..") + ), + applicability, + ); + } + VecInitKind::WithExprCapacity(hir_id) => { + let e = cx.tcx.hir().expect_expr(hir_id); + span_lint_and_sugg( + cx, + READ_ZERO_BYTE_VEC, + next_stmt_span, + "reading zero byte data to `Vec`", + "try", + format!("{}.resize({}, 0); {}", + ident.as_str(), + snippet(cx, e.span, ".."), + snippet(cx, next_stmt_span, "..") + ), + applicability, + ); + } + _ => { + span_lint( + cx, + READ_ZERO_BYTE_VEC, + next_stmt_span, + "reading zero byte data to `Vec`", + ); + + } + } + } + } + } + } +} diff --git a/clippy_lints/src/redundant_closure_call.rs b/clippy_lints/src/redundant_closure_call.rs index 4c2016fe3f7..65ed798867d 100644 --- a/clippy_lints/src/redundant_closure_call.rs +++ b/clippy_lints/src/redundant_closure_call.rs @@ -23,12 +23,13 @@ declare_clippy_lint! { /// complexity. /// /// ### Example - /// ```rust,ignore - /// // Bad - /// let a = (|| 42)() + /// ```rust + /// let a = (|| 42)(); + /// ``` /// - /// // Good - /// let a = 42 + /// Use instead: + /// ```rust + /// let a = 42; /// ``` #[clippy::version = "pre 1.29.0"] pub REDUNDANT_CLOSURE_CALL, diff --git a/clippy_lints/src/redundant_slicing.rs b/clippy_lints/src/redundant_slicing.rs index 25a9072ef6e..db6c97f3739 100644 --- a/clippy_lints/src/redundant_slicing.rs +++ b/clippy_lints/src/redundant_slicing.rs @@ -60,7 +60,7 @@ declare_clippy_lint! { /// let vec = vec![1, 2, 3]; /// let slice = &*vec; /// ``` - #[clippy::version = "1.60.0"] + #[clippy::version = "1.61.0"] pub DEREF_BY_SLICING, restriction, "slicing instead of dereferencing" diff --git a/clippy_lints/src/reference.rs b/clippy_lints/src/reference.rs index f789cec6d6a..a642e2da3ba 100644 --- a/clippy_lints/src/reference.rs +++ b/clippy_lints/src/reference.rs @@ -21,11 +21,12 @@ declare_clippy_lint! { /// /// ### Example /// ```rust,ignore - /// // Bad /// let a = f(*&mut b); /// let c = *&d; + /// ``` /// - /// // Good + /// Use instead: + /// ```rust,ignore /// let a = f(b); /// let c = d; /// ``` diff --git a/clippy_lints/src/return_self_not_must_use.rs b/clippy_lints/src/return_self_not_must_use.rs index 91e5e1e8b28..60be6bd335f 100644 --- a/clippy_lints/src/return_self_not_must_use.rs +++ b/clippy_lints/src/return_self_not_must_use.rs @@ -26,19 +26,20 @@ declare_clippy_lint! { /// if it was added on constructors for example. /// /// ### Example - /// Missing attribute /// ```rust /// pub struct Bar; /// impl Bar { - /// // Bad + /// // Missing attribute /// pub fn bar(&self) -> Self { /// Self /// } /// } /// ``` /// - /// It's better to have the `#[must_use]` attribute on the method like this: + /// Use instead: /// ```rust + /// # { + /// // It's better to have the `#[must_use]` attribute on the method like this: /// pub struct Bar; /// impl Bar { /// #[must_use] @@ -46,10 +47,10 @@ declare_clippy_lint! { /// Self /// } /// } - /// ``` + /// # } /// - /// Or on the type definition like this: - /// ```rust + /// # { + /// // Or on the type definition like this: /// #[must_use] /// pub struct Bar; /// impl Bar { @@ -57,6 +58,7 @@ declare_clippy_lint! { /// Self /// } /// } + /// # } /// ``` #[clippy::version = "1.59.0"] pub RETURN_SELF_NOT_MUST_USE, diff --git a/clippy_lints/src/same_name_method.rs b/clippy_lints/src/same_name_method.rs index c5c174cc8f6..20184d54b76 100644 --- a/clippy_lints/src/same_name_method.rs +++ b/clippy_lints/src/same_name_method.rs @@ -1,7 +1,7 @@ -use clippy_utils::diagnostics::span_lint_and_then; +use clippy_utils::diagnostics::span_lint_hir_and_then; use rustc_data_structures::fx::FxHashMap; use rustc_hir::def::{DefKind, Res}; -use rustc_hir::{Impl, ItemKind, Node, Path, QPath, TraitRef, TyKind}; +use rustc_hir::{HirId, Impl, ItemKind, Node, Path, QPath, TraitRef, TyKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::AssocKind; use rustc_session::{declare_lint_pass, declare_tool_lint}; @@ -42,11 +42,12 @@ declare_clippy_lint! { declare_lint_pass!(SameNameMethod => [SAME_NAME_METHOD]); struct ExistingName { - impl_methods: BTreeMap, + impl_methods: BTreeMap, trait_methods: BTreeMap>, } impl<'tcx> LateLintPass<'tcx> for SameNameMethod { + #[expect(clippy::too_many_lines)] fn check_crate_post(&mut self, cx: &LateContext<'tcx>) { let mut map = FxHashMap::::default(); @@ -97,10 +98,11 @@ impl<'tcx> LateLintPass<'tcx> for SameNameMethod { }; let mut check_trait_method = |method_name: Symbol, trait_method_span: Span| { - if let Some(impl_span) = existing_name.impl_methods.get(&method_name) { - span_lint_and_then( + if let Some((impl_span, hir_id)) = existing_name.impl_methods.get(&method_name) { + span_lint_hir_and_then( cx, SAME_NAME_METHOD, + *hir_id, *impl_span, "method's name is the same as an existing method in a trait", |diag| { @@ -136,10 +138,12 @@ impl<'tcx> LateLintPass<'tcx> for SameNameMethod { }) { let method_name = impl_item_ref.ident.name; let impl_span = impl_item_ref.span; + let hir_id = impl_item_ref.id.hir_id(); if let Some(trait_spans) = existing_name.trait_methods.get(&method_name) { - span_lint_and_then( + span_lint_hir_and_then( cx, SAME_NAME_METHOD, + hir_id, impl_span, "method's name is the same as an existing method in a trait", |diag| { @@ -152,7 +156,7 @@ impl<'tcx> LateLintPass<'tcx> for SameNameMethod { }, ); } - existing_name.impl_methods.insert(method_name, impl_span); + existing_name.impl_methods.insert(method_name, (impl_span, hir_id)); } }, } diff --git a/clippy_lints/src/shadow.rs b/clippy_lints/src/shadow.rs index 4f74c1e44c2..bf318c055da 100644 --- a/clippy_lints/src/shadow.rs +++ b/clippy_lints/src/shadow.rs @@ -23,10 +23,12 @@ declare_clippy_lint! { /// ### Example /// ```rust /// # let x = 1; - /// // Bad /// let x = &x; + /// ``` /// - /// // Good + /// Use instead: + /// ```rust + /// # let x = 1; /// let y = &x; // use different variable name /// ``` #[clippy::version = "pre 1.29.0"] @@ -79,11 +81,14 @@ declare_clippy_lint! { /// # let y = 1; /// # let z = 2; /// let x = y; - /// - /// // Bad /// let x = z; // shadows the earlier binding + /// ``` /// - /// // Good + /// Use instead: + /// ```rust + /// # let y = 1; + /// # let z = 2; + /// let x = y; /// let w = z; // use different variable name /// ``` #[clippy::version = "pre 1.29.0"] diff --git a/clippy_lints/src/single_char_lifetime_names.rs b/clippy_lints/src/single_char_lifetime_names.rs index aa306a630c4..3dc995e2fa5 100644 --- a/clippy_lints/src/single_char_lifetime_names.rs +++ b/clippy_lints/src/single_char_lifetime_names.rs @@ -33,7 +33,7 @@ declare_clippy_lint! { /// source: &'src str, /// } /// ``` - #[clippy::version = "1.59.0"] + #[clippy::version = "1.60.0"] pub SINGLE_CHAR_LIFETIME_NAMES, restriction, "warns against single-character lifetime names" diff --git a/clippy_lints/src/slow_vector_initialization.rs b/clippy_lints/src/slow_vector_initialization.rs index b4ad5dcbe3e..975a0a06e38 100644 --- a/clippy_lints/src/slow_vector_initialization.rs +++ b/clippy_lints/src/slow_vector_initialization.rs @@ -23,15 +23,16 @@ declare_clippy_lint! { /// ```rust /// # use core::iter::repeat; /// # let len = 4; - /// - /// // Bad /// let mut vec1 = Vec::with_capacity(len); /// vec1.resize(len, 0); /// /// let mut vec2 = Vec::with_capacity(len); /// vec2.extend(repeat(0).take(len)); + /// ``` /// - /// // Good + /// Use instead: + /// ```rust + /// # let len = 4; /// let mut vec1 = vec![0; len]; /// let mut vec2 = vec![0; len]; /// ``` diff --git a/clippy_lints/src/strings.rs b/clippy_lints/src/strings.rs index 7c196ccaa8c..71f3e6b6a6e 100644 --- a/clippy_lints/src/strings.rs +++ b/clippy_lints/src/strings.rs @@ -99,11 +99,12 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// // Bad - /// let bs = "a byte string".as_bytes(); + /// let bstr = "a byte string".as_bytes(); + /// ``` /// - /// // Good - /// let bs = b"a byte string"; + /// Use instead: + /// ```rust + /// let bstr = b"a byte string"; /// ``` #[clippy::version = "pre 1.29.0"] pub STRING_LIT_AS_BYTES, @@ -223,11 +224,12 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// let _ = std::str::from_utf8(&"Hello World!".as_bytes()[6..11]).unwrap(); + /// std::str::from_utf8(&"Hello World!".as_bytes()[6..11]).unwrap(); /// ``` - /// could be written as + /// + /// Use instead: /// ```rust - /// let _ = &"Hello World!"[6..11]; + /// &"Hello World!"[6..11]; /// ``` #[clippy::version = "1.50.0"] pub STRING_FROM_UTF8_AS_BYTES, diff --git a/clippy_lints/src/trait_bounds.rs b/clippy_lints/src/trait_bounds.rs index 1e5b646f5f0..ac63d182337 100644 --- a/clippy_lints/src/trait_bounds.rs +++ b/clippy_lints/src/trait_bounds.rs @@ -29,8 +29,7 @@ declare_clippy_lint! { /// pub fn foo(t: T) where T: Copy, T: Clone {} /// ``` /// - /// Could be written as: - /// + /// Use instead: /// ```rust /// pub fn foo(t: T) where T: Copy + Clone {} /// ``` diff --git a/clippy_lints/src/transmute/useless_transmute.rs b/clippy_lints/src/transmute/useless_transmute.rs index fc9227b76f0..8ea985a8984 100644 --- a/clippy_lints/src/transmute/useless_transmute.rs +++ b/clippy_lints/src/transmute/useless_transmute.rs @@ -61,12 +61,7 @@ pub(super) fn check<'tcx>( "transmute from an integer to a pointer", |diag| { if let Some(arg) = sugg::Sugg::hir_opt(cx, arg) { - diag.span_suggestion( - e.span, - "try", - arg.as_ty(&to_ty.to_string()), - Applicability::Unspecified, - ); + diag.span_suggestion(e.span, "try", arg.as_ty(&to_ty.to_string()), Applicability::Unspecified); } }, ); diff --git a/clippy_lints/src/unicode.rs b/clippy_lints/src/unicode.rs index afd7be89a4e..cc64d17be05 100644 --- a/clippy_lints/src/unicode.rs +++ b/clippy_lints/src/unicode.rs @@ -41,7 +41,8 @@ declare_clippy_lint! { /// ```rust /// let x = String::from("€"); /// ``` - /// Could be written as: + /// + /// Use instead: /// ```rust /// let x = String::from("\u{20ac}"); /// ``` diff --git a/clippy_lints/src/unused_async.rs b/clippy_lints/src/unused_async.rs index 41333bb2add..c8ec4442ab1 100644 --- a/clippy_lints/src/unused_async.rs +++ b/clippy_lints/src/unused_async.rs @@ -17,13 +17,14 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// // Bad /// async fn get_random_number() -> i64 { /// 4 // Chosen by fair dice roll. Guaranteed to be random. /// } /// let number_future = get_random_number(); + /// ``` /// - /// // Good + /// Use instead: + /// ```rust /// fn get_random_number_improved() -> i64 { /// 4 // Chosen by fair dice roll. Guaranteed to be random. /// } diff --git a/clippy_lints/src/useless_conversion.rs b/clippy_lints/src/useless_conversion.rs index 4a3b5383c89..fe29bf29d0c 100644 --- a/clippy_lints/src/useless_conversion.rs +++ b/clippy_lints/src/useless_conversion.rs @@ -21,11 +21,12 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// // Bad /// // format!() returns a `String` /// let s: String = format!("hello").into(); + /// ``` /// - /// // Good + /// Use instead: + /// ```rust /// let s: String = format!("hello"); /// ``` #[clippy::version = "1.45.0"] diff --git a/clippy_lints/src/utils/conf.rs b/clippy_lints/src/utils/conf.rs index b5c5d35135f..38e5c5e5b73 100644 --- a/clippy_lints/src/utils/conf.rs +++ b/clippy_lints/src/utils/conf.rs @@ -9,6 +9,29 @@ use std::path::{Path, PathBuf}; use std::str::FromStr; use std::{cmp, env, fmt, fs, io, iter}; +#[rustfmt::skip] +const DEFAULT_DOC_VALID_IDENTS: &[&str] = &[ + "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", + "DirectX", + "ECMAScript", + "GPLv2", "GPLv3", + "GitHub", "GitLab", + "IPv4", "IPv6", + "ClojureScript", "CoffeeScript", "JavaScript", "PureScript", "TypeScript", + "NaN", "NaNs", + "OAuth", "GraphQL", + "OCaml", + "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenDNS", + "WebGL", + "TensorFlow", + "TrueType", + "iOS", "macOS", "FreeBSD", + "TeX", "LaTeX", "BibTeX", "BibLaTeX", + "MinGW", + "CamelCase", +]; +const DEFAULT_BLACKLISTED_NAMES: &[&str] = &["foo", "baz", "quux"]; + /// Holds information used by `MISSING_ENFORCED_IMPORT_RENAMES` lint. #[derive(Clone, Debug, Deserialize)] pub struct Rename { @@ -178,8 +201,10 @@ define_Conf! { (msrv: Option = None), /// Lint: BLACKLISTED_NAME. /// - /// The list of blacklisted names to lint about. NB: `bar` is not here since it has legitimate uses - (blacklisted_names: Vec = ["foo", "baz", "quux"].iter().map(ToString::to_string).collect()), + /// The list of blacklisted names to lint about. NB: `bar` is not here since it has legitimate uses. The value + /// `".."` can be used as part of the list to indicate, that the configured values should be appended to the + /// default configuration of Clippy. By default any configuraction will replace the default value. + (blacklisted_names: Vec = super::DEFAULT_BLACKLISTED_NAMES.iter().map(ToString::to_string).collect()), /// Lint: COGNITIVE_COMPLEXITY. /// /// The maximum cognitive complexity a function can have @@ -191,27 +216,14 @@ define_Conf! { (cyclomatic_complexity_threshold: Option = None), /// Lint: DOC_MARKDOWN. /// - /// The list of words this lint should not consider as identifiers needing ticks - (doc_valid_idents: Vec = [ - "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", - "DirectX", - "ECMAScript", - "GPLv2", "GPLv3", - "GitHub", "GitLab", - "IPv4", "IPv6", - "ClojureScript", "CoffeeScript", "JavaScript", "PureScript", "TypeScript", - "NaN", "NaNs", - "OAuth", "GraphQL", - "OCaml", - "OpenGL", "OpenMP", "OpenSSH", "OpenSSL", "OpenStreetMap", "OpenDNS", - "WebGL", - "TensorFlow", - "TrueType", - "iOS", "macOS", "FreeBSD", - "TeX", "LaTeX", "BibTeX", "BibLaTeX", - "MinGW", - "CamelCase", - ].iter().map(ToString::to_string).collect()), + /// The list of words this lint should not consider as identifiers needing ticks. The value + /// `".."` can be used as part of the list to indicate, that the configured values should be appended to the + /// default configuration of Clippy. By default any configuraction will replace the default value. For example: + /// * `doc-valid-idents = ["ClipPy"]` would replace the default list with `["ClipPy"]`. + /// * `doc-valid-idents = ["ClipPy", ".."]` would append `ClipPy` to the default list. + /// + /// Default list: + (doc_valid_idents: Vec = super::DEFAULT_DOC_VALID_IDENTS.iter().map(ToString::to_string).collect()), /// Lint: TOO_MANY_ARGUMENTS. /// /// The maximum number of argument a function or method can have @@ -401,7 +413,21 @@ pub fn read(path: &Path) -> TryConf { Err(e) => return TryConf::from_error(e), Ok(content) => content, }; - toml::from_str(&content).unwrap_or_else(TryConf::from_error) + match toml::from_str::(&content) { + Ok(mut conf) => { + extend_vec_if_indicator_present(&mut conf.conf.doc_valid_idents, DEFAULT_DOC_VALID_IDENTS); + extend_vec_if_indicator_present(&mut conf.conf.blacklisted_names, DEFAULT_BLACKLISTED_NAMES); + + conf + }, + Err(e) => TryConf::from_error(e), + } +} + +fn extend_vec_if_indicator_present(vec: &mut Vec, default: &[&str]) { + if vec.contains(&"..".to_string()) { + vec.extend(default.iter().map(ToString::to_string)); + } } const SEPARATOR_WIDTH: usize = 4; diff --git a/clippy_lints/src/utils/internal_lints.rs b/clippy_lints/src/utils/internal_lints.rs index 60f98876994..b885e5132f1 100644 --- a/clippy_lints/src/utils/internal_lints.rs +++ b/clippy_lints/src/utils/internal_lints.rs @@ -89,12 +89,11 @@ declare_clippy_lint! { /// warning/error messages. /// /// ### Example - /// Bad: /// ```rust,ignore /// cx.span_lint(LINT_NAME, "message"); /// ``` /// - /// Good: + /// Use instead: /// ```rust,ignore /// utils::span_lint(cx, LINT_NAME, "message"); /// ``` @@ -112,12 +111,11 @@ declare_clippy_lint! { /// `cx.outer_expn_data()` is faster and more concise. /// /// ### Example - /// Bad: /// ```rust,ignore /// expr.span.ctxt().outer().expn_data() /// ``` /// - /// Good: + /// Use instead: /// ```rust,ignore /// expr.span.ctxt().outer_expn_data() /// ``` @@ -135,7 +133,6 @@ declare_clippy_lint! { /// ICE in large quantities can damage your teeth /// /// ### Example - /// Bad: /// ```rust,ignore /// 🍦🍦🍦🍦🍦 /// ``` @@ -153,12 +150,11 @@ declare_clippy_lint! { /// Indicates that the lint is not finished. /// /// ### Example - /// Bad: /// ```rust,ignore /// declare_lint! { pub COOL_LINT, nursery, "default lint description" } /// ``` /// - /// Good: + /// Use instead: /// ```rust,ignore /// declare_lint! { pub COOL_LINT, nursery, "a great new lint" } /// ``` @@ -183,7 +179,6 @@ declare_clippy_lint! { /// convenient, readable and less error prone. /// /// ### Example - /// Bad: /// ```rust,ignore /// span_lint_and_then(cx, TEST_LINT, expr.span, lint_msg, |diag| { /// diag.span_suggestion( @@ -207,7 +202,7 @@ declare_clippy_lint! { /// }); /// ``` /// - /// Good: + /// Use instead: /// ```rust,ignore /// span_lint_and_sugg( /// cx, @@ -237,12 +232,11 @@ declare_clippy_lint! { /// `utils::is_type_diagnostic_item()` does not require hardcoded paths. /// /// ### Example - /// Bad: /// ```rust,ignore /// utils::match_type(cx, ty, &paths::VEC) /// ``` /// - /// Good: + /// Use instead: /// ```rust,ignore /// utils::is_type_diagnostic_item(cx, ty, sym::Vec) /// ``` @@ -273,12 +267,11 @@ declare_clippy_lint! { /// It's faster and easier to use the symbol constant. /// /// ### Example - /// Bad: /// ```rust,ignore /// let _ = sym!(f32); /// ``` /// - /// Good: + /// Use instead: /// ```rust,ignore /// let _ = sym::f32; /// ``` @@ -295,12 +288,11 @@ declare_clippy_lint! { /// It's faster use symbols directly instead of strings. /// /// ### Example - /// Bad: /// ```rust,ignore /// symbol.as_str() == "clippy"; /// ``` /// - /// Good: + /// Use instead: /// ```rust,ignore /// symbol == sym::clippy; /// ``` @@ -672,8 +664,8 @@ impl<'tcx> LateLintPass<'tcx> for CollapsibleCalls { if let ExprKind::Call(func, and_then_args) = expr.kind; if is_expr_path_def_path(cx, func, &["clippy_utils", "diagnostics", "span_lint_and_then"]); if and_then_args.len() == 5; - if let ExprKind::Closure(_, _, body_id, _, _) = &and_then_args[4].kind; - let body = cx.tcx.hir().body(*body_id); + if let ExprKind::Closure { body, .. } = &and_then_args[4].kind; + let body = cx.tcx.hir().body(*body); let only_expr = peel_blocks_with_stmt(&body.value); if let ExprKind::MethodCall(ps, span_call_args, _) = &only_expr.kind; then { diff --git a/clippy_lints/src/utils/internal_lints/metadata_collector.rs b/clippy_lints/src/utils/internal_lints/metadata_collector.rs index cf2de6a42af..99e9e3275ab 100644 --- a/clippy_lints/src/utils/internal_lints/metadata_collector.rs +++ b/clippy_lints/src/utils/internal_lints/metadata_collector.rs @@ -31,6 +31,8 @@ use std::fmt::Write as _; use std::fs::{self, OpenOptions}; use std::io::prelude::*; use std::path::Path; +use std::path::PathBuf; +use std::process::Command; /// This is the output file of the lint collector. const OUTPUT_FILE: &str = "../util/gh-pages/lints.json"; @@ -180,6 +182,7 @@ pub struct MetadataCollector { lints: BinaryHeap, applicability_info: FxHashMap, config: Vec, + clippy_project_root: PathBuf, } impl MetadataCollector { @@ -188,6 +191,7 @@ impl MetadataCollector { lints: BinaryHeap::::default(), applicability_info: FxHashMap::::default(), config: collect_configs(), + clippy_project_root: clippy_dev::clippy_project_root(), } } @@ -215,11 +219,13 @@ impl Drop for MetadataCollector { // Mapping the final data let mut lints = std::mem::take(&mut self.lints).into_sorted_vec(); - collect_renames(&mut lints); for x in &mut lints { x.applicability = Some(applicability_info.remove(&x.id).unwrap_or_default()); + replace_produces(&x.id, &mut x.docs, &self.clippy_project_root); } + collect_renames(&mut lints); + // Outputting if Path::new(OUTPUT_FILE).exists() { fs::remove_file(OUTPUT_FILE).unwrap(); @@ -263,14 +269,193 @@ impl LintMetadata { } } +fn replace_produces(lint_name: &str, docs: &mut String, clippy_project_root: &Path) { + let mut doc_lines = docs.lines().map(ToString::to_string).collect::>(); + let mut lines = doc_lines.iter_mut(); + + 'outer: loop { + // Find the start of the example + + // ```rust + loop { + match lines.next() { + Some(line) if line.trim_start().starts_with("```rust") => { + if line.contains("ignore") || line.contains("no_run") { + // A {{produces}} marker may have been put on a ignored code block by mistake, + // just seek to the end of the code block and continue checking. + if lines.any(|line| line.trim_start().starts_with("```")) { + continue; + } + + panic!("lint `{}` has an unterminated code block", lint_name) + } + + break; + }, + Some(line) if line.trim_start() == "{{produces}}" => { + panic!( + "lint `{}` has marker {{{{produces}}}} with an ignored or missing code block", + lint_name + ) + }, + Some(line) => { + let line = line.trim(); + // These are the two most common markers of the corrections section + if line.eq_ignore_ascii_case("Use instead:") || line.eq_ignore_ascii_case("Could be written as:") { + break 'outer; + } + }, + None => break 'outer, + } + } + + // Collect the example + let mut example = Vec::new(); + loop { + match lines.next() { + Some(line) if line.trim_start() == "```" => break, + Some(line) => example.push(line), + None => panic!("lint `{}` has an unterminated code block", lint_name), + } + } + + // Find the {{produces}} and attempt to generate the output + loop { + match lines.next() { + Some(line) if line.is_empty() => {}, + Some(line) if line.trim() == "{{produces}}" => { + let output = get_lint_output(lint_name, &example, clippy_project_root); + line.replace_range( + .., + &format!( + "
\ + Produces\n\ + \n\ + ```text\n\ + {}\n\ + ```\n\ +
", + output + ), + ); + + break; + }, + // No {{produces}}, we can move on to the next example + Some(_) => break, + None => break 'outer, + } + } + } + + *docs = cleanup_docs(&doc_lines); +} + +fn get_lint_output(lint_name: &str, example: &[&mut String], clippy_project_root: &Path) -> String { + let dir = tempfile::tempdir().unwrap_or_else(|e| panic!("failed to create temp dir: {e}")); + let file = dir.path().join("lint_example.rs"); + + let mut source = String::new(); + let unhidden = example + .iter() + .map(|line| line.trim_start().strip_prefix("# ").unwrap_or(line)); + + // Get any attributes + let mut lines = unhidden.peekable(); + while let Some(line) = lines.peek() { + if line.starts_with("#!") { + source.push_str(line); + source.push('\n'); + lines.next(); + } else { + break; + } + } + + let needs_main = !example.iter().any(|line| line.contains("fn main")); + if needs_main { + source.push_str("fn main() {\n"); + } + + for line in lines { + source.push_str(line); + source.push('\n'); + } + + if needs_main { + source.push_str("}\n"); + } + + if let Err(e) = fs::write(&file, &source) { + panic!("failed to write to `{}`: {e}", file.as_path().to_string_lossy()); + } + + let prefixed_name = format!("{}{lint_name}", CLIPPY_LINT_GROUP_PREFIX); + + let mut cmd = Command::new("cargo"); + + cmd.current_dir(clippy_project_root) + .env("CARGO_INCREMENTAL", "0") + .env("CLIPPY_ARGS", "") + .env("CLIPPY_DISABLE_DOCS_LINKS", "1") + // We need to disable this to enable all lints + .env("ENABLE_METADATA_COLLECTION", "0") + .args(["run", "--bin", "clippy-driver"]) + .args(["--target-dir", "./clippy_lints/target"]) + .args(["--", "--error-format=json"]) + .args(["--edition", "2021"]) + .arg("-Cdebuginfo=0") + .args(["-A", "clippy::all"]) + .args(["-W", &prefixed_name]) + .args(["-L", "./target/debug"]) + .args(["-Z", "no-codegen"]); + + let output = cmd + .arg(file.as_path()) + .output() + .unwrap_or_else(|e| panic!("failed to run `{:?}`: {e}", cmd)); + + let tmp_file_path = file.to_string_lossy(); + let stderr = std::str::from_utf8(&output.stderr).unwrap(); + let msgs = stderr + .lines() + .filter(|line| line.starts_with('{')) + .map(|line| serde_json::from_str(line).unwrap()) + .collect::>(); + + let mut rendered = String::new(); + let iter = msgs + .iter() + .filter(|msg| matches!(&msg["code"]["code"], serde_json::Value::String(s) if s == &prefixed_name)); + + for message in iter { + let rendered_part = message["rendered"].as_str().expect("rendered field should exist"); + rendered.push_str(rendered_part); + } + + if rendered.is_empty() { + let rendered: Vec<&str> = msgs.iter().filter_map(|msg| msg["rendered"].as_str()).collect(); + let non_json: Vec<&str> = stderr.lines().filter(|line| !line.starts_with('{')).collect(); + panic!( + "did not find lint `{}` in output of example, got:\n{}\n{}", + lint_name, + non_json.join("\n"), + rendered.join("\n") + ); + } + + // The reader doesn't need to see `/tmp/.tmpfiy2Qd/lint_example.rs` :) + rendered.trim_end().replace(&*tmp_file_path, "lint_example.rs") +} + #[derive(Debug, Clone, Serialize, PartialEq, Eq, PartialOrd, Ord)] struct SerializableSpan { path: String, line: usize, } -impl std::fmt::Display for SerializableSpan { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +impl fmt::Display for SerializableSpan { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}:{}", self.path.rsplit('/').next().unwrap_or_default(), self.line) } } @@ -435,10 +620,10 @@ impl<'hir> LateLintPass<'hir> for MetadataCollector { if !BLACK_LISTED_LINTS.contains(&lint_name.as_str()); // metadata extraction if let Some((group, level)) = get_lint_group_and_level_or_lint(cx, &lint_name, item); - if let Some(mut docs) = extract_attr_docs_or_lint(cx, item); + if let Some(mut raw_docs) = extract_attr_docs_or_lint(cx, item); then { if let Some(configuration_section) = self.get_lint_configs(&lint_name) { - docs.push_str(&configuration_section); + raw_docs.push_str(&configuration_section); } let version = get_lint_version(cx, item); @@ -448,7 +633,7 @@ impl<'hir> LateLintPass<'hir> for MetadataCollector { group, level, version, - docs, + raw_docs, )); } } @@ -459,7 +644,7 @@ impl<'hir> LateLintPass<'hir> for MetadataCollector { let lint_name = sym_to_string(item.ident.name).to_ascii_lowercase(); if !BLACK_LISTED_LINTS.contains(&lint_name.as_str()); // Metadata the little we can get from a deprecated lint - if let Some(docs) = extract_attr_docs_or_lint(cx, item); + if let Some(raw_docs) = extract_attr_docs_or_lint(cx, item); then { let version = get_lint_version(cx, item); @@ -469,7 +654,7 @@ impl<'hir> LateLintPass<'hir> for MetadataCollector { DEPRECATED_LINT_GROUP_STR.to_string(), DEPRECATED_LINT_LEVEL, version, - docs, + raw_docs, )); } } @@ -535,22 +720,28 @@ fn extract_attr_docs_or_lint(cx: &LateContext<'_>, item: &Item<'_>) -> Option, item: &Item<'_>) -> Option { + let attrs = cx.tcx.hir().attrs(item.hir_id()); + let mut lines = attrs.iter().filter_map(ast::Attribute::doc_str); + + if let Some(line) = lines.next() { + let raw_docs = lines.fold(String::from(line.as_str()) + "\n", |s, line| s + line.as_str() + "\n"); + return Some(raw_docs); + } + + None +} + /// This function may modify the doc comment to ensure that the string can be displayed using a /// markdown viewer in Clippy's lint list. The following modifications could be applied: /// * Removal of leading space after a new line. (Important to display tables) /// * Ensures that code blocks only contain language information -fn extract_attr_docs(cx: &LateContext<'_>, item: &Item<'_>) -> Option { - let attrs = cx.tcx.hir().attrs(item.hir_id()); - let mut lines = attrs.iter().filter_map(ast::Attribute::doc_str); - let mut docs = String::from(lines.next()?.as_str()); +fn cleanup_docs(docs_collection: &Vec) -> String { let mut in_code_block = false; let mut is_code_block_rust = false; - for line in lines { - let line = line.as_str(); + let mut docs = String::new(); + for line in docs_collection { // Rustdoc hides code lines starting with `# ` and this removes them from Clippy's lint list :) if is_code_block_rust && line.trim_start().starts_with("# ") { continue; @@ -583,7 +774,8 @@ fn extract_attr_docs(cx: &LateContext<'_>, item: &Item<'_>) -> Option { docs.push_str(line); } } - Some(docs) + + docs } fn get_lint_version(cx: &LateContext<'_>, item: &Item<'_>) -> String { @@ -762,9 +954,9 @@ fn resolve_applicability<'hir>(cx: &LateContext<'hir>, expr: &'hir hir::Expr<'hi } fn check_is_multi_part<'hir>(cx: &LateContext<'hir>, closure_expr: &'hir hir::Expr<'hir>) -> bool { - if let ExprKind::Closure(_, _, body_id, _, _) = closure_expr.kind { + if let ExprKind::Closure { body, .. } = closure_expr.kind { let mut scanner = IsMultiSpanScanner::new(cx); - intravisit::walk_body(&mut scanner, cx.tcx.hir().body(body_id)); + intravisit::walk_body(&mut scanner, cx.tcx.hir().body(body)); return scanner.is_multi_part(); } else if let Some(local) = get_parent_local(cx, closure_expr) { if let Some(local_init) = local.init { diff --git a/clippy_lints/src/vec.rs b/clippy_lints/src/vec.rs index ba1ff65479d..297a80e5767 100644 --- a/clippy_lints/src/vec.rs +++ b/clippy_lints/src/vec.rs @@ -28,12 +28,14 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// # fn foo(my_vec: &[u8]) {} + /// fn foo(_x: &[u8]) {} /// - /// // Bad /// foo(&vec![1, 2]); + /// ``` /// - /// // Good + /// Use instead: + /// ```rust + /// # fn foo(_x: &[u8]) {} /// foo(&[1, 2]); /// ``` #[clippy::version = "pre 1.29.0"] diff --git a/clippy_lints/src/wildcard_imports.rs b/clippy_lints/src/wildcard_imports.rs index 2f74eaf3cf5..5418eca382d 100644 --- a/clippy_lints/src/wildcard_imports.rs +++ b/clippy_lints/src/wildcard_imports.rs @@ -26,13 +26,18 @@ declare_clippy_lint! { /// still around. /// /// ### Example - /// ```rust,ignore - /// // Bad + /// ```rust /// use std::cmp::Ordering::*; + /// + /// # fn foo(_: std::cmp::Ordering) {} /// foo(Less); + /// ``` /// - /// // Good + /// Use instead: + /// ```rust /// use std::cmp::Ordering; + /// + /// # fn foo(_: Ordering) {} /// foo(Ordering::Less) /// ``` #[clippy::version = "pre 1.29.0"] @@ -76,14 +81,13 @@ declare_clippy_lint! { /// /// ### Example /// ```rust,ignore - /// // Bad /// use crate1::*; /// /// foo(); /// ``` /// + /// Use instead: /// ```rust,ignore - /// // Good /// use crate1::foo; /// /// foo(); diff --git a/clippy_lints/src/write.rs b/clippy_lints/src/write.rs index d2493c055a5..67b2bc8c3f3 100644 --- a/clippy_lints/src/write.rs +++ b/clippy_lints/src/write.rs @@ -25,10 +25,11 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// // Bad /// println!(""); + /// ``` /// - /// // Good + /// Use instead: + /// ```rust /// println!(); /// ``` #[clippy::version = "pre 1.29.0"] @@ -177,10 +178,13 @@ declare_clippy_lint! { /// ```rust /// # use std::fmt::Write; /// # let mut buf = String::new(); - /// // Bad /// writeln!(buf, ""); + /// ``` /// - /// // Good + /// Use instead: + /// ```rust + /// # use std::fmt::Write; + /// # let mut buf = String::new(); /// writeln!(buf); /// ``` #[clippy::version = "pre 1.29.0"] @@ -204,10 +208,14 @@ declare_clippy_lint! { /// # use std::fmt::Write; /// # let mut buf = String::new(); /// # let name = "World"; - /// // Bad /// write!(buf, "Hello {}!\n", name); + /// ``` /// - /// // Good + /// Use instead: + /// ```rust + /// # use std::fmt::Write; + /// # let mut buf = String::new(); + /// # let name = "World"; /// writeln!(buf, "Hello {}!", name); /// ``` #[clippy::version = "pre 1.29.0"] @@ -233,10 +241,13 @@ declare_clippy_lint! { /// ```rust /// # use std::fmt::Write; /// # let mut buf = String::new(); - /// // Bad /// writeln!(buf, "{}", "foo"); + /// ``` /// - /// // Good + /// Use instead: + /// ```rust + /// # use std::fmt::Write; + /// # let mut buf = String::new(); /// writeln!(buf, "foo"); /// ``` #[clippy::version = "pre 1.29.0"] diff --git a/clippy_lints/src/zero_div_zero.rs b/clippy_lints/src/zero_div_zero.rs index 641681185a2..50d3c079fe6 100644 --- a/clippy_lints/src/zero_div_zero.rs +++ b/clippy_lints/src/zero_div_zero.rs @@ -14,10 +14,11 @@ declare_clippy_lint! { /// /// ### Example /// ```rust - /// // Bad /// let nan = 0.0f32 / 0.0; + /// ``` /// - /// // Good + /// Use instead: + /// ```rust /// let nan = f32::NAN; /// ``` #[clippy::version = "pre 1.29.0"] diff --git a/clippy_utils/src/consts.rs b/clippy_utils/src/consts.rs index 5d0ce6cc620..6d4a48b53de 100644 --- a/clippy_utils/src/consts.rs +++ b/clippy_utils/src/consts.rs @@ -622,7 +622,7 @@ pub fn miri_to_const<'tcx>(tcx: TyCtxt<'tcx>, result: mir::ConstantKind<'tcx>) - ty::Float(FloatTy::F32) => match len.to_valtree().try_to_machine_usize(tcx) { Some(len) => alloc .inner() - .inspect_with_uninit_and_ptr_outside_interpreter(0..(4 * len as usize)) + .inspect_with_uninit_and_ptr_outside_interpreter(0..(4 * usize::try_from(len).unwrap())) .to_owned() .chunks(4) .map(|chunk| { @@ -637,7 +637,7 @@ pub fn miri_to_const<'tcx>(tcx: TyCtxt<'tcx>, result: mir::ConstantKind<'tcx>) - ty::Float(FloatTy::F64) => match len.to_valtree().try_to_machine_usize(tcx) { Some(len) => alloc .inner() - .inspect_with_uninit_and_ptr_outside_interpreter(0..(8 * len as usize)) + .inspect_with_uninit_and_ptr_outside_interpreter(0..(8 * usize::try_from(len).unwrap())) .to_owned() .chunks(8) .map(|chunk| { diff --git a/clippy_utils/src/hir_utils.rs b/clippy_utils/src/hir_utils.rs index 12931c56df6..af62c4afd5a 100644 --- a/clippy_utils/src/hir_utils.rs +++ b/clippy_utils/src/hir_utils.rs @@ -91,7 +91,7 @@ pub struct HirEqInterExpr<'a, 'b, 'tcx> { // When binding are declared, the binding ID in the left expression is mapped to the one on the // right. For example, when comparing `{ let x = 1; x + 2 }` and `{ let y = 1; y + 2 }`, // these blocks are considered equal since `x` is mapped to `y`. - locals: HirIdMap, + pub locals: HirIdMap, } impl HirEqInterExpr<'_, '_, '_> { @@ -998,3 +998,15 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { } } } + +pub fn hash_stmt(cx: &LateContext<'_>, s: &Stmt<'_>) -> u64 { + let mut h = SpanlessHash::new(cx); + h.hash_stmt(s); + h.finish() +} + +pub fn hash_expr(cx: &LateContext<'_>, e: &Expr<'_>) -> u64 { + let mut h = SpanlessHash::new(cx); + h.hash_expr(e); + h.finish() +} diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 0cf23ca626c..73c1bdd0e3f 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -58,7 +58,9 @@ pub mod usage; pub mod visitors; pub use self::attrs::*; -pub use self::hir_utils::{both, count_eq, eq_expr_value, over, SpanlessEq, SpanlessHash}; +pub use self::hir_utils::{ + both, count_eq, eq_expr_value, hash_expr, hash_stmt, over, HirEqInterExpr, SpanlessEq, SpanlessHash, +}; use std::collections::hash_map::Entry; use std::hash::BuildHasherDefault; diff --git a/clippy_utils/src/ty.rs b/clippy_utils/src/ty.rs index a10515d2fec..227e97d37ec 100644 --- a/clippy_utils/src/ty.rs +++ b/clippy_utils/src/ty.rs @@ -78,9 +78,9 @@ pub fn get_associated_type<'tcx>( cx.tcx .associated_items(trait_id) .find_by_name_and_kind(cx.tcx, Ident::from_str(name), ty::AssocKind::Type, trait_id) - .map(|assoc| { + .and_then(|assoc| { let proj = cx.tcx.mk_projection(assoc.def_id, cx.tcx.mk_substs_trait(ty, &[])); - cx.tcx.normalize_erasing_regions(cx.param_env, proj) + cx.tcx.try_normalize_erasing_regions(cx.param_env, proj).ok() }) } diff --git a/doc/adding_lints.md b/doc/adding_lints.md deleted file mode 100644 index 3e0b1c5c4f7..00000000000 --- a/doc/adding_lints.md +++ /dev/null @@ -1,697 +0,0 @@ -# Adding a new lint - -You are probably here because you want to add a new lint to Clippy. If this is -the first time you're contributing to Clippy, this document guides you through -creating an example lint from scratch. - -To get started, we will create a lint that detects functions called `foo`, -because that's clearly a non-descriptive name. - -- [Adding a new lint](#adding-a-new-lint) - - [Setup](#setup) - - [Getting Started](#getting-started) - - [Testing](#testing) - - [Cargo lints](#cargo-lints) - - [Rustfix tests](#rustfix-tests) - - [Edition 2018 tests](#edition-2018-tests) - - [Testing manually](#testing-manually) - - [Lint declaration](#lint-declaration) - - [Lint registration](#lint-registration) - - [Lint passes](#lint-passes) - - [Emitting a lint](#emitting-a-lint) - - [Adding the lint logic](#adding-the-lint-logic) - - [Specifying the lint's minimum supported Rust version (MSRV)](#specifying-the-lints-minimum-supported-rust-version-msrv) - - [Author lint](#author-lint) - - [Print HIR lint](#print-hir-lint) - - [Documentation](#documentation) - - [Running rustfmt](#running-rustfmt) - - [Debugging](#debugging) - - [PR Checklist](#pr-checklist) - - [Adding configuration to a lint](#adding-configuration-to-a-lint) - - [Cheat Sheet](#cheat-sheet) - -## Setup - -See the [Basics](basics.md#get-the-code) documentation. - -## Getting Started - -There is a bit of boilerplate code that needs to be set up when creating a new -lint. Fortunately, you can use the clippy dev tools to handle this for you. We -are naming our new lint `foo_functions` (lints are generally written in snake -case), and we don't need type information so it will have an early pass type -(more on this later on). If you're not sure if the name you chose fits the lint, -take a look at our [lint naming guidelines][lint_naming]. To get started on this -lint you can run `cargo dev new_lint --name=foo_functions --pass=early ---category=pedantic` (category will default to nursery if not provided). This -command will create two files: `tests/ui/foo_functions.rs` and -`clippy_lints/src/foo_functions.rs`, as well as -[registering the lint](#lint-registration). For cargo lints, two project -hierarchies (fail/pass) will be created by default under `tests/ui-cargo`. - -Next, we'll open up these files and add our lint! - -## Testing - -Let's write some tests first that we can execute while we iterate on our lint. - -Clippy uses UI tests for testing. UI tests check that the output of Clippy is -exactly as expected. Each test is just a plain Rust file that contains the code -we want to check. The output of Clippy is compared against a `.stderr` file. -Note that you don't have to create this file yourself, we'll get to -generating the `.stderr` files further down. - -We start by opening the test file created at `tests/ui/foo_functions.rs`. - -Update the file with some examples to get started: - -```rust -#![warn(clippy::foo_functions)] - -// Impl methods -struct A; -impl A { - pub fn fo(&self) {} - pub fn foo(&self) {} - pub fn food(&self) {} -} - -// Default trait methods -trait B { - fn fo(&self) {} - fn foo(&self) {} - fn food(&self) {} -} - -// Plain functions -fn fo() {} -fn foo() {} -fn food() {} - -fn main() { - // We also don't want to lint method calls - foo(); - let a = A; - a.foo(); -} -``` - -Now we can run the test with `TESTNAME=foo_functions cargo uitest`, -currently this test is meaningless though. - -While we are working on implementing our lint, we can keep running the UI -test. That allows us to check if the output is turning into what we want. - -Once we are satisfied with the output, we need to run -`cargo dev bless` to update the `.stderr` file for our lint. -Please note that, we should run `TESTNAME=foo_functions cargo uitest` -every time before running `cargo dev bless`. -Running `TESTNAME=foo_functions cargo uitest` should pass then. When we commit -our lint, we need to commit the generated `.stderr` files, too. In general, you -should only commit files changed by `cargo dev bless` for the -specific lint you are creating/editing. Note that if the generated files are -empty, they should be removed. - -Note that you can run multiple test files by specifying a comma separated list: -`TESTNAME=foo_functions,test2,test3`. - -### Cargo lints - -For cargo lints, the process of testing differs in that we are interested in -the `Cargo.toml` manifest file. We also need a minimal crate associated -with that manifest. - -If our new lint is named e.g. `foo_categories`, after running `cargo dev new_lint` -we will find by default two new crates, each with its manifest file: - -* `tests/ui-cargo/foo_categories/fail/Cargo.toml`: this file should cause the new lint to raise an error. -* `tests/ui-cargo/foo_categories/pass/Cargo.toml`: this file should not trigger the lint. - -If you need more cases, you can copy one of those crates (under `foo_categories`) and rename it. - -The process of generating the `.stderr` file is the same, and prepending the `TESTNAME` -variable to `cargo uitest` works too. - -## Rustfix tests - -If the lint you are working on is making use of structured suggestions, the -test file should include a `// run-rustfix` comment at the top. This will -additionally run [rustfix] for that test. Rustfix will apply the suggestions -from the lint to the code of the test file and compare that to the contents of -a `.fixed` file. - -Use `cargo dev bless` to automatically generate the -`.fixed` file after running the tests. - -[rustfix]: https://github.com/rust-lang/rustfix - -## Edition 2018 tests - -Some features require the 2018 edition to work (e.g. `async_await`), but -compile-test tests run on the 2015 edition by default. To change this behavior -add `// edition:2018` at the top of the test file (note that it's space-sensitive). - -## Testing manually - -Manually testing against an example file can be useful if you have added some -`println!`s and the test suite output becomes unreadable. To try Clippy with -your local modifications, run - -``` -cargo dev lint input.rs -``` - -from the working copy root. With tests in place, let's have a look at -implementing our lint now. - -## Lint declaration - -Let's start by opening the new file created in the `clippy_lints` crate -at `clippy_lints/src/foo_functions.rs`. That's the crate where all the -lint code is. This file has already imported some initial things we will need: - -```rust -use rustc_lint::{EarlyLintPass, EarlyContext}; -use rustc_session::{declare_lint_pass, declare_tool_lint}; -use rustc_ast::ast::*; -``` - -The next step is to update the lint declaration. Lints are declared using the -[`declare_clippy_lint!`][declare_clippy_lint] macro, and we just need to update -the auto-generated lint declaration to have a real description, something like this: - -```rust -declare_clippy_lint! { - /// ### What it does - /// - /// ### Why is this bad? - /// - /// ### Example - /// ```rust - /// // example code - /// ``` - #[clippy::version = "1.29.0"] - pub FOO_FUNCTIONS, - pedantic, - "function named `foo`, which is not a descriptive name" -} -``` - -* The section of lines prefixed with `///` constitutes the lint documentation - section. This is the default documentation style and will be displayed - [like this][example_lint_page]. To render and open this documentation locally - in a browser, run `cargo dev serve`. -* The `#[clippy::version]` attribute will be rendered as part of the lint documentation. - The value should be set to the current Rust version that the lint is developed in, - it can be retrieved by running `rustc -vV` in the rust-clippy directory. The version - is listed under *release*. (Use the version without the `-nightly`) suffix. -* `FOO_FUNCTIONS` is the name of our lint. Be sure to follow the - [lint naming guidelines][lint_naming] here when naming your lint. - In short, the name should state the thing that is being checked for and - read well when used with `allow`/`warn`/`deny`. -* `pedantic` sets the lint level to `Allow`. - The exact mapping can be found [here][category_level_mapping] -* The last part should be a text that explains what exactly is wrong with the - code - -The rest of this file contains an empty implementation for our lint pass, -which in this case is `EarlyLintPass` and should look like this: - -```rust -// clippy_lints/src/foo_functions.rs - -// .. imports and lint declaration .. - -declare_lint_pass!(FooFunctions => [FOO_FUNCTIONS]); - -impl EarlyLintPass for FooFunctions {} -``` - -[declare_clippy_lint]: https://github.com/rust-lang/rust-clippy/blob/557f6848bd5b7183f55c1e1522a326e9e1df6030/clippy_lints/src/lib.rs#L60 -[example_lint_page]: https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure -[lint_naming]: https://rust-lang.github.io/rfcs/0344-conventions-galore.html#lints -[category_level_mapping]: https://github.com/rust-lang/rust-clippy/blob/557f6848bd5b7183f55c1e1522a326e9e1df6030/clippy_lints/src/lib.rs#L110 - -## Lint registration - -When using `cargo dev new_lint`, the lint is automatically registered and -nothing more has to be done. - -When declaring a new lint by hand and `cargo dev update_lints` is used, the lint -pass may have to be registered manually in the `register_plugins` function in -`clippy_lints/src/lib.rs`: - -```rust -store.register_early_pass(|| Box::new(foo_functions::FooFunctions)); -``` - -As one may expect, there is a corresponding `register_late_pass` method -available as well. Without a call to one of `register_early_pass` or -`register_late_pass`, the lint pass in question will not be run. - -One reason that `cargo dev update_lints` does not automate this step is that -multiple lints can use the same lint pass, so registering the lint pass may -already be done when adding a new lint. Another reason that this step is not -automated is that the order that the passes are registered determines the order -the passes actually run, which in turn affects the order that any emitted lints -are output in. - -## Lint passes - -Writing a lint that only checks for the name of a function means that we only -have to deal with the AST and don't have to deal with the type system at all. -This is good, because it makes writing this particular lint less complicated. - -We have to make this decision with every new Clippy lint. It boils down to using -either [`EarlyLintPass`][early_lint_pass] or [`LateLintPass`][late_lint_pass]. - -In short, the `LateLintPass` has access to type information while the -`EarlyLintPass` doesn't. If you don't need access to type information, use the -`EarlyLintPass`. The `EarlyLintPass` is also faster. However linting speed -hasn't really been a concern with Clippy so far. - -Since we don't need type information for checking the function name, we used -`--pass=early` when running the new lint automation and all the imports were -added accordingly. - -[early_lint_pass]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/trait.EarlyLintPass.html -[late_lint_pass]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/trait.LateLintPass.html - -## Emitting a lint - -With UI tests and the lint declaration in place, we can start working on the -implementation of the lint logic. - -Let's start by implementing the `EarlyLintPass` for our `FooFunctions`: - -```rust -impl EarlyLintPass for FooFunctions { - fn check_fn(&mut self, cx: &EarlyContext<'_>, fn_kind: FnKind<'_>, span: Span, _: NodeId) { - // TODO: Emit lint here - } -} -``` - -We implement the [`check_fn`][check_fn] method from the -[`EarlyLintPass`][early_lint_pass] trait. This gives us access to various -information about the function that is currently being checked. More on that in -the next section. Let's worry about the details later and emit our lint for -*every* function definition first. - -Depending on how complex we want our lint message to be, we can choose from a -variety of lint emission functions. They can all be found in -[`clippy_utils/src/diagnostics.rs`][diagnostics]. - -`span_lint_and_help` seems most appropriate in this case. It allows us to -provide an extra help message and we can't really suggest a better name -automatically. This is how it looks: - -```rust -impl EarlyLintPass for FooFunctions { - fn check_fn(&mut self, cx: &EarlyContext<'_>, fn_kind: FnKind<'_>, span: Span, _: NodeId) { - span_lint_and_help( - cx, - FOO_FUNCTIONS, - span, - "function named `foo`", - None, - "consider using a more meaningful name" - ); - } -} -``` - -Running our UI test should now produce output that contains the lint message. - -According to [the rustc-dev-guide], the text should be matter of fact and avoid -capitalization and periods, unless multiple sentences are needed. -When code or an identifier must appear in a message or label, it should be -surrounded with single grave accents \`. - -[check_fn]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/trait.EarlyLintPass.html#method.check_fn -[diagnostics]: https://github.com/rust-lang/rust-clippy/blob/master/clippy_utils/src/diagnostics.rs -[the rustc-dev-guide]: https://rustc-dev-guide.rust-lang.org/diagnostics.html - -## Adding the lint logic - -Writing the logic for your lint will most likely be different from our example, -so this section is kept rather short. - -Using the [`check_fn`][check_fn] method gives us access to [`FnKind`][fn_kind] -that has the [`FnKind::Fn`] variant. It provides access to the name of the -function/method via an [`Ident`][ident]. - -With that we can expand our `check_fn` method to: - -```rust -impl EarlyLintPass for FooFunctions { - fn check_fn(&mut self, cx: &EarlyContext<'_>, fn_kind: FnKind<'_>, span: Span, _: NodeId) { - if is_foo_fn(fn_kind) { - span_lint_and_help( - cx, - FOO_FUNCTIONS, - span, - "function named `foo`", - None, - "consider using a more meaningful name" - ); - } - } -} -``` - -We separate the lint conditional from the lint emissions because it makes the -code a bit easier to read. In some cases this separation would also allow to -write some unit tests (as opposed to only UI tests) for the separate function. - -In our example, `is_foo_fn` looks like: - -```rust -// use statements, impl EarlyLintPass, check_fn, .. - -fn is_foo_fn(fn_kind: FnKind<'_>) -> bool { - match fn_kind { - FnKind::Fn(_, ident, ..) => { - // check if `fn` name is `foo` - ident.name.as_str() == "foo" - } - // ignore closures - FnKind::Closure(..) => false - } -} -``` - -Now we should also run the full test suite with `cargo test`. At this point -running `cargo test` should produce the expected output. Remember to run -`cargo dev bless` to update the `.stderr` file. - -`cargo test` (as opposed to `cargo uitest`) will also ensure that our lint -implementation is not violating any Clippy lints itself. - -That should be it for the lint implementation. Running `cargo test` should now -pass. - -[fn_kind]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_ast/visit/enum.FnKind.html -[`FnKind::Fn`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_ast/visit/enum.FnKind.html#variant.Fn -[ident]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_span/symbol/struct.Ident.html - -## Specifying the lint's minimum supported Rust version (MSRV) - -Sometimes a lint makes suggestions that require a certain version of Rust. For example, the `manual_strip` lint suggests -using `str::strip_prefix` and `str::strip_suffix` which is only available after Rust 1.45. In such cases, you need to -ensure that the MSRV configured for the project is >= the MSRV of the required Rust feature. If multiple features are -required, just use the one with a lower MSRV. - -First, add an MSRV alias for the required feature in [`clippy_utils::msrvs`](/clippy_utils/src/msrvs.rs). This can be -accessed later as `msrvs::STR_STRIP_PREFIX`, for example. - -```rust -msrv_aliases! { - .. - 1,45,0 { STR_STRIP_PREFIX } -} -``` - -In order to access the project-configured MSRV, you need to have an `msrv` field in the LintPass struct, and a -constructor to initialize the field. The `msrv` value is passed to the constructor in `clippy_lints/lib.rs`. - -```rust -pub struct ManualStrip { - msrv: Option, -} - -impl ManualStrip { - #[must_use] - pub fn new(msrv: Option) -> Self { - Self { msrv } - } -} -``` - -The project's MSRV can then be matched against the feature MSRV in the LintPass -using the `meets_msrv` utility function. - -``` rust -if !meets_msrv(self.msrv, msrvs::STR_STRIP_PREFIX) { - return; -} -``` - -The project's MSRV can also be specified as an inner attribute, which overrides -the value from `clippy.toml`. This can be accounted for using the -`extract_msrv_attr!(LintContext)` macro and passing -`LateContext`/`EarlyContext`. - -```rust -impl<'tcx> LateLintPass<'tcx> for ManualStrip { - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) { - ... - } - extract_msrv_attr!(LateContext); -} -``` - -Once the `msrv` is added to the lint, a relevant test case should be added to -`tests/ui/min_rust_version_attr.rs` which verifies that the lint isn't emitted -if the project's MSRV is lower. - -As a last step, the lint should be added to the lint documentation. This is done -in `clippy_lints/src/utils/conf.rs`: - -```rust -define_Conf! { - /// Lint: LIST, OF, LINTS, . The minimum rust version that the project supports - (msrv: Option = None), - ... -} -``` - -## Author lint - -If you have trouble implementing your lint, there is also the internal `author` -lint to generate Clippy code that detects the offending pattern. It does not -work for all of the Rust syntax, but can give a good starting point. - -The quickest way to use it, is the -[Rust playground: play.rust-lang.org][author_example]. -Put the code you want to lint into the editor and add the `#[clippy::author]` -attribute above the item. Then run Clippy via `Tools -> Clippy` and you should -see the generated code in the output below. - -[Here][author_example] is an example on the playground. - -If the command was executed successfully, you can copy the code over to where -you are implementing your lint. - -[author_example]: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=9a12cb60e5c6ad4e3003ac6d5e63cf55 - -## Print HIR lint - -To implement a lint, it's helpful to first understand the internal representation -that rustc uses. Clippy has the `#[clippy::dump]` attribute that prints the -[_High-Level Intermediate Representation (HIR)_] of the item, statement, or -expression that the attribute is attached to. To attach the attribute to expressions -you often need to enable `#![feature(stmt_expr_attributes)]`. - -[Here][print_hir_example] you can find an example, just select _Tools_ and run _Clippy_. - -[_High-Level Intermediate Representation (HIR)_]: https://rustc-dev-guide.rust-lang.org/hir.html -[print_hir_example]: https://play.rust-lang.org/?version=nightly&mode=debug&edition=2021&gist=daf14db3a7f39ca467cd1b86c34b9afb - -## Documentation - -The final thing before submitting our PR is to add some documentation to our -lint declaration. - -Please document your lint with a doc comment akin to the following: - -```rust -declare_clippy_lint! { - /// ### What it does - /// Checks for ... (describe what the lint matches). - /// - /// ### Why is this bad? - /// Supply the reason for linting the code. - /// - /// ### Example - /// - /// ```rust,ignore - /// // A short example of code that triggers the lint - /// ``` - /// - /// Use instead: - /// ```rust,ignore - /// // A short example of improved code that doesn't trigger the lint - /// ``` - #[clippy::version = "1.29.0"] - pub FOO_FUNCTIONS, - pedantic, - "function named `foo`, which is not a descriptive name" -} -``` - -Once your lint is merged, this documentation will show up in the [lint -list][lint_list]. - -[lint_list]: https://rust-lang.github.io/rust-clippy/master/index.html - -## Running rustfmt - -[Rustfmt] is a tool for formatting Rust code according to style guidelines. -Your code has to be formatted by `rustfmt` before a PR can be merged. -Clippy uses nightly `rustfmt` in the CI. - -It can be installed via `rustup`: - -```bash -rustup component add rustfmt --toolchain=nightly -``` - -Use `cargo dev fmt` to format the whole codebase. Make sure that `rustfmt` is -installed for the nightly toolchain. - -[Rustfmt]: https://github.com/rust-lang/rustfmt - -## Debugging - -If you want to debug parts of your lint implementation, you can use the [`dbg!`] -macro anywhere in your code. Running the tests should then include the debug -output in the `stdout` part. - -[`dbg!`]: https://doc.rust-lang.org/std/macro.dbg.html - -## PR Checklist - -Before submitting your PR make sure you followed all of the basic requirements: - - - -- \[ ] Followed [lint naming conventions][lint_naming] -- \[ ] Added passing UI tests (including committed `.stderr` file) -- \[ ] `cargo test` passes locally -- \[ ] Executed `cargo dev update_lints` -- \[ ] Added lint documentation -- \[ ] Run `cargo dev fmt` - -## Adding configuration to a lint - -Clippy supports the configuration of lints values using a `clippy.toml` file in the workspace -directory. Adding a configuration to a lint can be useful for thresholds or to constrain some -behavior that can be seen as a false positive for some users. Adding a configuration is done -in the following steps: - -1. Adding a new configuration entry to [clippy_lints::utils::conf](/clippy_lints/src/utils/conf.rs) - like this: - ```rust - /// Lint: LINT_NAME. - /// - /// - (configuration_ident: Type = DefaultValue), - ``` - The doc comment is automatically added to the documentation of the listed lints. The default - value will be formatted using the `Debug` implementation of the type. -2. Adding the configuration value to the lint impl struct: - 1. This first requires the definition of a lint impl struct. Lint impl structs are usually - generated with the `declare_lint_pass!` macro. This struct needs to be defined manually - to add some kind of metadata to it: - ```rust - // Generated struct definition - declare_lint_pass!(StructName => [ - LINT_NAME - ]); - - // New manual definition struct - #[derive(Copy, Clone)] - pub struct StructName {} - - impl_lint_pass!(StructName => [ - LINT_NAME - ]); - ``` - - 2. Next add the configuration value and a corresponding creation method like this: - ```rust - #[derive(Copy, Clone)] - pub struct StructName { - configuration_ident: Type, - } - - // ... - - impl StructName { - pub fn new(configuration_ident: Type) -> Self { - Self { - configuration_ident, - } - } - } - ``` -3. Passing the configuration value to the lint impl struct: - - First find the struct construction in the [clippy_lints lib file](/clippy_lints/src/lib.rs). - The configuration value is now cloned or copied into a local value that is then passed to the - impl struct like this: - ```rust - // Default generated registration: - store.register_*_pass(|| box module::StructName); - - // New registration with configuration value - let configuration_ident = conf.configuration_ident.clone(); - store.register_*_pass(move || box module::StructName::new(configuration_ident)); - ``` - - Congratulations the work is almost done. The configuration value can now be accessed - in the linting code via `self.configuration_ident`. - -4. Adding tests: - 1. The default configured value can be tested like any normal lint in [`tests/ui`](/tests/ui). - 2. The configuration itself will be tested separately in [`tests/ui-toml`](/tests/ui-toml). - Simply add a new subfolder with a fitting name. This folder contains a `clippy.toml` file - with the configuration value and a rust file that should be linted by Clippy. The test can - otherwise be written as usual. - -## Cheat Sheet - -Here are some pointers to things you are likely going to need for every lint: - -* [Clippy utils][utils] - Various helper functions. Maybe the function you need - is already in here ([`is_type_diagnostic_item`], [`implements_trait`], [`snippet`], etc) -* [Clippy diagnostics][diagnostics] -* [Let chains][let-chains] -* [`from_expansion`][from_expansion] and [`in_external_macro`][in_external_macro] -* [`Span`][span] -* [`Applicability`][applicability] -* [Common tools for writing lints](common_tools_writing_lints.md) helps with common operations -* [The rustc-dev-guide][rustc-dev-guide] explains a lot of internal compiler concepts -* [The nightly rustc docs][nightly_docs] which has been linked to throughout - this guide - -For `EarlyLintPass` lints: - -* [`EarlyLintPass`][early_lint_pass] -* [`rustc_ast::ast`][ast] - -For `LateLintPass` lints: - -* [`LateLintPass`][late_lint_pass] -* [`Ty::TyKind`][ty] - -While most of Clippy's lint utils are documented, most of rustc's internals lack -documentation currently. This is unfortunate, but in most cases you can probably -get away with copying things from existing similar lints. If you are stuck, -don't hesitate to ask on [Zulip] or in the issue/PR. - -[utils]: https://doc.rust-lang.org/nightly/nightly-rustc/clippy_utils/index.html -[`is_type_diagnostic_item`]: https://doc.rust-lang.org/nightly/nightly-rustc/clippy_utils/ty/fn.is_type_diagnostic_item.html -[`implements_trait`]: https://doc.rust-lang.org/nightly/nightly-rustc/clippy_utils/ty/fn.implements_trait.html -[`snippet`]: https://doc.rust-lang.org/nightly/nightly-rustc/clippy_utils/source/fn.snippet.html -[let-chains]: https://github.com/rust-lang/rust/pull/94927 -[from_expansion]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_span/struct.Span.html#method.from_expansion -[in_external_macro]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/lint/fn.in_external_macro.html -[span]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_span/struct.Span.html -[applicability]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_errors/enum.Applicability.html -[rustc-dev-guide]: https://rustc-dev-guide.rust-lang.org/ -[nightly_docs]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ -[ast]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_ast/ast/index.html -[ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/sty/index.html -[Zulip]: https://rust-lang.zulipchat.com/#narrow/stream/clippy diff --git a/doc/backport.md b/doc/backport.md deleted file mode 100644 index 15f3d1f0806..00000000000 --- a/doc/backport.md +++ /dev/null @@ -1,71 +0,0 @@ -# Backport Changes - -Sometimes it is necessary to backport changes to the beta release of Clippy. -Backports in Clippy are rare and should be approved by the Clippy team. For -example, a backport is done, if a crucial ICE was fixed or a lint is broken to a -point, that it has to be disabled, before landing on stable. - -Backports are done to the `beta` branch of Clippy. Backports to stable Clippy -releases basically don't exist, since this would require a Rust point release, -which is almost never justifiable for a Clippy fix. - - -## Backport the changes - -Backports are done on the beta branch of the Clippy repository. - -```bash -# Assuming the current directory corresponds to the Clippy repository -$ git checkout beta -$ git checkout -b backport -$ git cherry-pick # `` is the commit hash of the commit(s), that should be backported -$ git push origin backport -``` - -Now you should test that the backport passes all the tests in the Rust -repository. You can do this with: - -```bash -# Assuming the current directory corresponds to the Rust repository -$ git checkout beta -$ git subtree pull -p src/tools/clippy https://github.com//rust-clippy backport -$ ./x.py test src/tools/clippy -``` - -Should the test fail, you can fix Clippy directly in the Rust repository. This -has to be first applied to the Clippy beta branch and then again synced to the -Rust repository, though. The easiest way to do this is: - -```bash -# In the Rust repository -$ git diff --patch --relative=src/tools/clippy > clippy.patch -# In the Clippy repository -$ git apply /path/to/clippy.patch -$ git add -u -$ git commit -m "Fix rustup fallout" -$ git push origin backport -``` - -After this, you can open a PR to the `beta` branch of the Clippy repository. - - -## Update Clippy in the Rust Repository - -This step must be done, **after** the PR of the previous step was merged. - -After the backport landed in the Clippy repository, the branch has to be synced -back to the beta branch of the Rust repository. - -```bash -# Assuming the current directory corresponds to the Rust repository -$ git checkout beta -$ git checkout -b clippy_backport -$ git subtree pull -p src/tools/clippy https://github.com/rust-lang/rust-clippy beta -$ git push origin clippy_backport -``` - -Make sure to test the backport in the Rust repository before opening a PR. This -is done with `./x.py test src/tools/clippy`. If that passes all tests, open a PR -to the `beta` branch of the Rust repository. In this PR you should tag the -Clippy team member, that agreed to the backport or the `@rust-lang/clippy` team. -Make sure to add `[beta]` to the title of the PR. diff --git a/doc/basics.md b/doc/basics.md deleted file mode 100644 index 57a90a924ec..00000000000 --- a/doc/basics.md +++ /dev/null @@ -1,174 +0,0 @@ -# Basics for hacking on Clippy - -This document explains the basics for hacking on Clippy. Besides others, this -includes how to build and test Clippy. For a more in depth description on -the codebase take a look at [Adding Lints] or [Common Tools]. - -[Adding Lints]: https://github.com/rust-lang/rust-clippy/blob/master/doc/adding_lints.md -[Common Tools]: https://github.com/rust-lang/rust-clippy/blob/master/doc/common_tools_writing_lints.md - -- [Basics for hacking on Clippy](#basics-for-hacking-on-clippy) - - [Get the Code](#get-the-code) - - [Building and Testing](#building-and-testing) - - [`cargo dev`](#cargo-dev) - - [lintcheck](#lintcheck) - - [PR](#pr) - - [Common Abbreviations](#common-abbreviations) - - [Install from source](#install-from-source) - -## Get the Code - -First, make sure you have checked out the latest version of Clippy. If this is -your first time working on Clippy, create a fork of the repository and clone it -afterwards with the following command: - -```bash -git clone git@github.com:/rust-clippy -``` - -If you've already cloned Clippy in the past, update it to the latest version: - -```bash -# If the upstream remote has not been added yet -git remote add upstream https://github.com/rust-lang/rust-clippy -# upstream has to be the remote of the rust-lang/rust-clippy repo -git fetch upstream -# make sure that you are on the master branch -git checkout master -# rebase your master branch on the upstream master -git rebase upstream/master -# push to the master branch of your fork -git push -``` - -## Building and Testing - -You can build and test Clippy like every other Rust project: - -```bash -cargo build # builds Clippy -cargo test # tests Clippy -``` - -Since Clippy's test suite is pretty big, there are some commands that only run a -subset of Clippy's tests: - -```bash -# only run UI tests -cargo uitest -# only run UI tests starting with `test_` -TESTNAME="test_" cargo uitest -# only run dogfood tests -cargo test --test dogfood -``` - -If the output of a [UI test] differs from the expected output, you can update the -reference file with: - -```bash -cargo dev bless -``` - -For example, this is necessary, if you fix a typo in an error message of a lint -or if you modify a test file to add a test case. - -_Note:_ This command may update more files than you intended. In that case only -commit the files you wanted to update. - -[UI test]: https://rustc-dev-guide.rust-lang.org/tests/adding.html#guide-to-the-ui-tests - -## `cargo dev` - -Clippy has some dev tools to make working on Clippy more convenient. These tools -can be accessed through the `cargo dev` command. Available tools are listed -below. To get more information about these commands, just call them with -`--help`. - -```bash -# formats the whole Clippy codebase and all tests -cargo dev fmt -# register or update lint names/groups/... -cargo dev update_lints -# create a new lint and register it -cargo dev new_lint -# automatically formatting all code before each commit -cargo dev setup git-hook -# (experimental) Setup Clippy to work with IntelliJ-Rust -cargo dev setup intellij -``` -More about intellij command usage and reasons [here](../CONTRIBUTING.md#intellij-rust) - -## lintcheck -`cargo lintcheck` will build and run clippy on a fixed set of crates and generate a log of the results. -You can `git diff` the updated log against its previous version and -see what impact your lint made on a small set of crates. -If you add a new lint, please audit the resulting warnings and make sure -there are no false positives and that the suggestions are valid. - -Refer to the tools [README] for more details. - -[README]: https://github.com/rust-lang/rust-clippy/blob/master/lintcheck/README.md -## PR - -We follow a rustc no merge-commit policy. -See . - -## Common Abbreviations - -| Abbreviation | Meaning | -| ------------ | -------------------------------------- | -| UB | Undefined Behavior | -| FP | False Positive | -| FN | False Negative | -| ICE | Internal Compiler Error | -| AST | Abstract Syntax Tree | -| MIR | Mid-Level Intermediate Representation | -| HIR | High-Level Intermediate Representation | -| TCX | Type context | - -This is a concise list of abbreviations that can come up during Clippy development. An extensive -general list can be found in the [rustc-dev-guide glossary][glossary]. Always feel free to ask if -an abbreviation or meaning is unclear to you. - -## Install from source - -If you are hacking on Clippy and want to install it from source, do the following: - -First, take note of the toolchain [override](https://rust-lang.github.io/rustup/overrides.html) in `/rust-toolchain`. -We will use this override to install Clippy into the right toolchain. - -> Tip: You can view the active toolchain for the current directory with `rustup show active-toolchain`. - -From the Clippy project root, run the following command to build the Clippy binaries and copy them into the -toolchain directory. This will override the currently installed Clippy component. - -```terminal -cargo build --release --bin cargo-clippy --bin clippy-driver -Zunstable-options --out-dir "$(rustc --print=sysroot)/bin" -``` - -Now you may run `cargo clippy` in any project, using the toolchain where you just installed Clippy. - -```terminal -cd my-project -cargo +nightly-2021-07-01 clippy -``` - -...or `clippy-driver` - -```terminal -clippy-driver +nightly-2021-07-01 -``` - -If you need to restore the default Clippy installation, run the following (from the Clippy project root). - -```terminal -rustup component remove clippy -rustup component add clippy -``` - -> **DO NOT** install using `cargo install --path . --force` since this will overwrite rustup -> [proxies](https://rust-lang.github.io/rustup/concepts/proxies.html). That is, `~/.cargo/bin/cargo-clippy` and -> `~/.cargo/bin/clippy-driver` should be hard or soft links to `~/.cargo/bin/rustup`. You can repair these by running -> `rustup update`. - -[glossary]: https://rustc-dev-guide.rust-lang.org/appendix/glossary.html diff --git a/doc/changelog_update.md b/doc/changelog_update.md deleted file mode 100644 index 0cbad2c0924..00000000000 --- a/doc/changelog_update.md +++ /dev/null @@ -1,97 +0,0 @@ -# Changelog Update - -If you want to help with updating the [changelog][changelog], you're in the right place. - -## When to update - -Typos and other small fixes/additions are _always_ welcome. - -Special care needs to be taken when it comes to updating the changelog for a new -Rust release. For that purpose, the changelog is ideally updated during the week -before an upcoming stable release. You can find the release dates on the [Rust -Forge][forge]. - -Most of the time we only need to update the changelog for minor Rust releases. It's -been very rare that Clippy changes were included in a patch release. - -## Changelog update walkthrough - -### 1. Finding the relevant Clippy commits - -Each Rust release ships with its own version of Clippy. The Clippy subtree can -be found in the `tools` directory of the Rust repository. - -Depending on the current time and what exactly you want to update, the following -bullet points might be helpful: - -* When writing the release notes for the **upcoming stable release** you need to check - out the Clippy commit of the current Rust `beta` branch. [Link][rust_beta_tools] -* When writing the release notes for the **upcoming beta release**, you need to check - out the Clippy commit of the current Rust `master`. [Link][rust_master_tools] -* When writing the (forgotten) release notes for a **past stable release**, you - need to check out the Rust release tag of the stable release. - [Link][rust_stable_tools] - -Usually you want to write the changelog of the **upcoming stable release**. Make -sure though, that `beta` was already branched in the Rust repository. - -To find the commit hash, issue the following command when in a `rust-lang/rust` checkout: -``` -git log --oneline -- src/tools/clippy/ | grep -o "Merge commit '[a-f0-9]*' into .*" | head -1 | sed -e "s/Merge commit '\([a-f0-9]*\)' into .*/\1/g" -``` - -### 2. Fetching the PRs between those commits - -Once you've got the correct commit range, run - - util/fetch_prs_between.sh commit1 commit2 > changes.txt - -and open that file in your editor of choice. - -When updating the changelog it's also a good idea to make sure that `commit1` is -already correct in the current changelog. - -### 3. Authoring the final changelog - -The above script should have dumped all the relevant PRs to the file you -specified. It should have filtered out most of the irrelevant PRs -already, but it's a good idea to do a manual cleanup pass where you look for -more irrelevant PRs. If you're not sure about some PRs, just leave them in for -the review and ask for feedback. - -With the PRs filtered, you can start to take each PR and move the -`changelog: ` content to `CHANGELOG.md`. Adapt the wording as you see fit but -try to keep it somewhat coherent. - -The order should roughly be: - -1. New lints -2. Moves or deprecations of lints -3. Changes that expand what code existing lints cover -4. False positive fixes -5. Suggestion fixes/improvements -6. ICE fixes -7. Documentation improvements -8. Others - -As section headers, we use: - -``` -### New Lints -### Moves and Deprecations -### Enhancements -### False Positive Fixes -### Suggestion Fixes/Improvements -### ICE Fixes -### Documentation Improvements -### Others -``` - -Please also be sure to update the Beta/Unreleased sections at the top with the -relevant commit ranges. - -[changelog]: https://github.com/rust-lang/rust-clippy/blob/master/CHANGELOG.md -[forge]: https://forge.rust-lang.org/ -[rust_master_tools]: https://github.com/rust-lang/rust/tree/master/src/tools/clippy -[rust_beta_tools]: https://github.com/rust-lang/rust/tree/beta/src/tools/clippy -[rust_stable_tools]: https://github.com/rust-lang/rust/releases diff --git a/doc/common_tools_writing_lints.md b/doc/common_tools_writing_lints.md deleted file mode 100644 index 1d1aee0da2c..00000000000 --- a/doc/common_tools_writing_lints.md +++ /dev/null @@ -1,266 +0,0 @@ -# Common tools for writing lints - -You may need following tooltips to catch up with common operations. - -- [Common tools for writing lints](#common-tools-for-writing-lints) - - [Retrieving the type of an expression](#retrieving-the-type-of-an-expression) - - [Checking if an expr is calling a specific method](#checking-if-an-expr-is-calling-a-specific-method) - - [Checking for a specific type](#checking-for-a-specific-type) - - [Checking if a type implements a specific trait](#checking-if-a-type-implements-a-specific-trait) - - [Checking if a type defines a specific method](#checking-if-a-type-defines-a-specific-method) - - [Dealing with macros](#dealing-with-macros-and-expansions) - -Useful Rustc dev guide links: -- [Stages of compilation](https://rustc-dev-guide.rust-lang.org/compiler-src.html#the-main-stages-of-compilation) -- [Diagnostic items](https://rustc-dev-guide.rust-lang.org/diagnostics/diagnostic-items.html) -- [Type checking](https://rustc-dev-guide.rust-lang.org/type-checking.html) -- [Ty module](https://rustc-dev-guide.rust-lang.org/ty.html) - -## Retrieving the type of an expression - -Sometimes you may want to retrieve the type `Ty` of an expression `Expr`, for example to answer following questions: - -- which type does this expression correspond to (using its [`TyKind`][TyKind])? -- is it a sized type? -- is it a primitive type? -- does it implement a trait? - -This operation is performed using the [`expr_ty()`][expr_ty] method from the [`TypeckResults`][TypeckResults] struct, -that gives you access to the underlying structure [`Ty`][Ty]. - -Example of use: -```rust -impl LateLintPass<'_> for MyStructLint { - fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { - // Get type of `expr` - let ty = cx.typeck_results().expr_ty(expr); - // Match its kind to enter its type - match ty.kind { - ty::Adt(adt_def, _) if adt_def.is_struct() => println!("Our `expr` is a struct!"), - _ => () - } - } -} -``` - -Similarly in [`TypeckResults`][TypeckResults] methods, you have the [`pat_ty()`][pat_ty] method -to retrieve a type from a pattern. - -Two noticeable items here: -- `cx` is the lint context [`LateContext`][LateContext]. The two most useful - data structures in this context are `tcx` and the `TypeckResults` returned by - `LateContext::typeck_results`, allowing us to jump to type definitions and - other compilation stages such as HIR. -- `typeck_results`'s return value is [`TypeckResults`][TypeckResults] and is - created by type checking step, it includes useful information such as types - of expressions, ways to resolve methods and so on. - -## Checking if an expr is calling a specific method - -Starting with an `expr`, you can check whether it is calling a specific method `some_method`: - -```rust -impl<'tcx> LateLintPass<'tcx> for MyStructLint { - fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) { - // 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) - // Optionally, check the type of the self argument. - // - See "Checking for a specific type" - { - // ... - } - } -} -``` - -## Checking for a specific type - -There are three ways to check if an expression type is a specific type we want to check for. -All of these methods only check for the base type, generic arguments have to be checked separately. - -```rust -use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item}; -use clippy_utils::{paths, match_def_path}; -use rustc_span::symbol::sym; -use rustc_hir::LangItem; - -impl LateLintPass<'_> for MyStructLint { - fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { - // Getting the expression type - let ty = cx.typeck_results().expr_ty(expr); - - // 1. Using diagnostic items - // The last argument is the diagnostic item to check for - if is_type_diagnostic_item(cx, ty, sym::Option) { - // The type is an `Option` - } - - // 2. Using lang items - if is_type_lang_item(cx, ty, LangItem::RangeFull) { - // The type is a full range like `.drain(..)` - } - - // 3. Using the type path - // This method should be avoided if possible - if match_def_path(cx, def_id, &paths::RESULT) { - // The type is a `core::result::Result` - } - } -} -``` - -Prefer using diagnostic items and lang items where possible. - -## Checking if a type implements a specific trait - -There are three ways to do this, depending on if the target trait has a diagnostic item, lang item or neither. - -```rust -use clippy_utils::{implements_trait, is_trait_method, match_trait_method, paths}; -use rustc_span::symbol::sym; - -impl LateLintPass<'_> for MyStructLint { - fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) { - // 1. Using diagnostic items with the expression - // we use `is_trait_method` function from Clippy's utils - if is_trait_method(cx, expr, sym::Iterator) { - // method call in `expr` belongs to `Iterator` trait - } - - // 2. Using lang items with the expression type - let ty = cx.typeck_results().expr_ty(expr); - if cx.tcx.lang_items() - // we are looking for the `DefId` of `Drop` trait in lang items - .drop_trait() - // then we use it with our type `ty` by calling `implements_trait` from Clippy's utils - .map_or(false, |id| implements_trait(cx, ty, id, &[])) { - // `expr` implements `Drop` trait - } - - // 3. Using the type path with the expression - // we use `match_trait_method` function from Clippy's utils - // (This method should be avoided if possible) - if match_trait_method(cx, expr, &paths::INTO) { - // `expr` implements `Into` trait - } - } -} -``` - -> Prefer using diagnostic and lang items, if the target trait has one. - -We access lang items through the type context `tcx`. `tcx` is of type [`TyCtxt`][TyCtxt] and is defined in the `rustc_middle` crate. -A list of defined paths for Clippy can be found in [paths.rs][paths] - -## Checking if a type defines a specific method - -To check if our type defines a method called `some_method`: - -```rust -use clippy_utils::ty::is_type_diagnostic_item; -use clippy_utils::return_ty; - -impl<'tcx> LateLintPass<'tcx> for MyTypeImpl { - fn check_impl_item(&mut self, cx: &LateContext<'tcx>, impl_item: &'tcx ImplItem<'_>) { - // 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) - // 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` - && is_type_diagnostic_item(cx, return_ty(cx, impl_item.hir_id), sym!(string_type)) - { - // ... - } - } -} -``` - -## Dealing with macros and expansions - -Keep in mind that macros are already expanded and desugaring is already applied -to the code representation that you are working with in Clippy. This unfortunately causes a lot of -false positives because macro expansions are "invisible" unless you actively check for them. -Generally speaking, code with macro expansions should just be ignored by Clippy because that code can be -dynamic in ways that are difficult or impossible to see. -Use the following functions to deal with macros: - -- `span.from_expansion()`: detects if a span is from macro expansion or desugaring. - Checking this is a common first step in a lint. - - ```rust - if expr.span.from_expansion() { - // just forget it - return; - } - ``` - -- `span.ctxt()`: the span's context represents whether it is from expansion, and if so, which macro call expanded it. - It is sometimes useful to check if the context of two spans are equal. - - ```rust - // expands to `1 + 0`, but don't lint - 1 + mac!() - ``` - ```rust - if left.span.ctxt() != right.span.ctxt() { - // the coder most likely cannot modify this expression - return; - } - ``` - Note: Code that is not from expansion is in the "root" context. So any spans where `from_expansion` returns `true` can - be assumed to have the same context. And so just using `span.from_expansion()` is often good enough. - - -- `in_external_macro(span)`: detect if the given span is from a macro defined in a foreign crate. - If you want the lint to work with macro-generated code, this is the next line of defense to avoid macros - not defined in the current crate. It doesn't make sense to lint code that the coder can't change. - - You may want to use it for example to not start linting in macros from other crates - - ```rust - #[macro_use] - extern crate a_crate_with_macros; - - // `foo` is defined in `a_crate_with_macros` - foo!("bar"); - - // if we lint the `match` of `foo` call and test its span - assert_eq!(in_external_macro(cx.sess(), match_span), true); - ``` - -- `span.ctxt()`: the span's context represents whether it is from expansion, and if so, what expanded it - -One thing `SpanContext` is useful for is to check if two spans are in the same context. For example, -in `a == b`, `a` and `b` have the same context. In a `macro_rules!` with `a == $b`, `$b` is expanded to some -expression with a different context from `a`. - - ```rust - macro_rules! m { - ($a:expr, $b:expr) => { - if $a.is_some() { - $b; - } - } - } - - let x: Option = Some(42); - m!(x, x.unwrap()); - - // These spans are not from the same context - // x.is_some() is from inside the macro - // x.unwrap() is from outside the macro - assert_eq!(x_is_some_span.ctxt(), x_unwrap_span.ctxt()); - ``` - -[Ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.Ty.html -[TyKind]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/enum.TyKind.html -[TypeckResults]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TypeckResults.html -[expr_ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/struct.TypeckResults.html#method.expr_ty -[LateContext]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint/struct.LateContext.html -[TyCtxt]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/context/struct.TyCtxt.html -[pat_ty]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_middle/ty/context/struct.TypeckResults.html#method.pat_ty -[paths]: ../clippy_utils/src/paths.rs diff --git a/doc/release.md b/doc/release.md deleted file mode 100644 index c4f8f989384..00000000000 --- a/doc/release.md +++ /dev/null @@ -1,145 +0,0 @@ -# Release a new Clippy Version - -_NOTE: This document is probably only relevant to you, if you're a member of the -Clippy team._ - -Clippy is released together with stable Rust releases. The dates for these -releases can be found at the [Rust Forge]. This document explains the necessary -steps to create a Clippy release. - -1. [Remerge the `beta` branch](#remerge-the-beta-branch) -2. [Update the `beta` branch](#update-the-beta-branch) -3. [Find the Clippy commit](#find-the-clippy-commit) -4. [Tag the stable commit](#tag-the-stable-commit) -5. [Update `CHANGELOG.md`](#update-changelogmd) - -_NOTE: This document is for stable Rust releases, not for point releases. For -point releases, step 1. and 2. should be enough._ - -[Rust Forge]: https://forge.rust-lang.org/ - - -## Remerge the `beta` branch - -This step is only necessary, if since the last release something was backported -to the beta Rust release. The remerge is then necessary, to make sure that the -Clippy commit, that was used by the now stable Rust release, persists in the -tree of the Clippy repository. - -To find out if this step is necessary run - -```bash -# Assumes that the local master branch is up-to-date -$ git fetch upstream -$ git branch master --contains upstream/beta -``` - -If this command outputs `master`, this step is **not** necessary. - -```bash -# Assuming `HEAD` is the current `master` branch of rust-lang/rust-clippy -$ git checkout -b backport_remerge -$ git merge upstream/beta -$ git diff # This diff has to be empty, otherwise something with the remerge failed -$ git push origin backport_remerge # This can be pushed to your fork -``` - -After this, open a PR to the master branch. In this PR, the commit hash of the -`HEAD` of the `beta` branch must exists. In addition to that, no files should -be changed by this PR. - - -## Update the `beta` branch - -This step must be done **after** the PR of the previous step was merged. - -First, the Clippy commit of the `beta` branch of the Rust repository has to be -determined. - -```bash -# Assuming the current directory corresponds to the Rust repository -$ git checkout beta -$ BETA_SHA=$(git log --oneline -- src/tools/clippy/ | grep -o "Merge commit '[a-f0-9]*' into .*" | head -1 | sed -e "s/Merge commit '\([a-f0-9]*\)' into .*/\1/g") -``` - -After finding the Clippy commit, the `beta` branch in the Clippy repository can -be updated. - -```bash -# Assuming the current directory corresponds to the Clippy repository -$ git checkout beta -$ git reset --hard $BETA_SHA -$ git push upstream beta -``` - - -## Find the Clippy commit - -The first step is to tag the Clippy commit, that is included in the stable Rust -release. This commit can be found in the Rust repository. - -```bash -# Assuming the current directory corresponds to the Rust repository -$ git fetch upstream # `upstream` is the `rust-lang/rust` remote -$ git checkout 1.XX.0 # XX should be exchanged with the corresponding version -$ SHA=$(git log --oneline -- src/tools/clippy/ | grep -o "Merge commit '[a-f0-9]*' into .*" | head -1 | sed -e "s/Merge commit '\([a-f0-9]*\)' into .*/\1/g") -``` - - -## Tag the stable commit - -After finding the Clippy commit, it can be tagged with the release number. - -```bash -# Assuming the current directory corresponds to the Clippy repository -$ git checkout $SHA -$ git tag rust-1.XX.0 # XX should be exchanged with the corresponding version -$ git push upstream rust-1.XX.0 # `upstream` is the `rust-lang/rust-clippy` remote -``` - -After this, the release should be available on the Clippy [release page]. - -[release page]: https://github.com/rust-lang/rust-clippy/releases - -## Update the `stable` branch - -At this step you should have already checked out the commit of the `rust-1.XX.0` -tag. Updating the stable branch from here is as easy as: - -```bash -# Assuming the current directory corresponds to the Clippy repository and the -# commit of the just created rust-1.XX.0 tag is checked out. -$ git push upstream rust-1.XX.0:stable # `upstream` is the `rust-lang/rust-clippy` remote -``` - -_NOTE: Usually there are no stable backports for Clippy, so this update should -be possible without force pushing or anything like this. If there should have -happened a stable backport, make sure to re-merge those changes just as with the -`beta` branch._ - -## Update `CHANGELOG.md` - -For this see the document on [how to update the changelog]. - -If you don't have time to do a complete changelog update right away, just update -the following parts: - -- Remove the `(beta)` from the new stable version: - - ```markdown - ## Rust 1.XX (beta) -> ## Rust 1.XX - ``` - -- Update the release date line of the new stable version: - - ```markdown - Current beta, release 20YY-MM-DD -> Current stable, released 20YY-MM-DD - ``` - -- Update the release date line of the previous stable version: - - ```markdown - Current stable, released 20YY-MM-DD -> Released 20YY-MM-DD - ``` - -[how to update the changelog]: https://github.com/rust-lang/rust-clippy/blob/master/doc/changelog_update.md diff --git a/doc/roadmap-2021.md b/doc/roadmap-2021.md deleted file mode 100644 index fe8b080f56f..00000000000 --- a/doc/roadmap-2021.md +++ /dev/null @@ -1,235 +0,0 @@ -# Roadmap 2021 - -# Summary - -This Roadmap lays out the plans for Clippy in 2021: - -- Improving usability and reliability -- Improving experience of contributors and maintainers -- Develop and specify processes - -Members of the Clippy team will be assigned tasks from one or more of these -topics. The team member is then responsible to complete the assigned tasks. This -can either be done by implementing them or by providing mentorship to interested -contributors. - -# Motivation - -With the ongoing growth of the Rust language and with that of the whole -ecosystem, also Clippy gets more and more users and contributors. This is good -for the project, but also brings challenges along. Some of these challenges are: - -- More issues about reliability or usability are popping up -- Traffic is hard to handle for a small team -- Bigger projects don't get completed due to the lack of processes and/or time - of the team members - -Additionally, according to the [Rust Roadmap 2021], clear processes should be -defined by every team and unified across teams. This Roadmap is the first step -towards this. - -[Rust Roadmap 2021]: https://github.com/rust-lang/rfcs/pull/3037 - -# Explanation - -This section will explain the things that should be done in 2021. It is -important to note, that this document focuses on the "What?", not the "How?". -The later will be addressed in follow-up tracking issue, with an assigned team -member. - -The following is split up in two major sections. The first section covers the -user facing plans, the second section the internal plans. - -## User Facing - -Clippy should be as pleasant to use and configure as possible. This section -covers plans that should be implemented to improve the situation of Clippy in -this regard. - -### Usability - -In the following, plans to improve the usability are covered. - -#### No Output After `cargo check` - -Currently when `cargo clippy` is run after `cargo check`, it does not produce -any output. This is especially problematic since `rust-analyzer` is on the rise -and it uses `cargo check` for checking code. A fix is already implemented, but -it still has to be pushed over the finish line. This also includes the -stabilization of the `cargo clippy --fix` command or the support of multi-span -suggestions in `rustfix`. - -- [#4612](https://github.com/rust-lang/rust-clippy/issues/4612) - -#### `lints.toml` Configuration - -This is something that comes up every now and then: a reusable configuration -file, where lint levels can be defined. Discussions about this often lead to -nothing specific or to "we need an RFC for this". And this is exactly what needs -to be done. Get together with the cargo team and write an RFC and implement such -a configuration file somehow and somewhere. - -- [#3164](https://github.com/rust-lang/rust-clippy/issues/3164) -- [cargo#5034](https://github.com/rust-lang/cargo/issues/5034) -- [IRLO](https://internals.rust-lang.org/t/proposal-cargo-lint-configuration/9135/8) - -#### Lint Groups - -There are more and more issues about managing lints in Clippy popping up. Lints -are hard to implement with a guarantee of no/few false positives (FPs). One way -to address this might be to introduce more lint groups to give users the ability -to better manage lints, or improve the process of classifying lints, so that -disabling lints due to FPs becomes rare. It is important to note, that Clippy -lints are less conservative than `rustc` lints, which won't change in the -future. - -- [#5537](https://github.com/rust-lang/rust-clippy/issues/5537) -- [#6366](https://github.com/rust-lang/rust-clippy/issues/6366) - -### Reliability - -In the following, plans to improve the reliability are covered. - -#### False Positive Rate - -In the worst case, new lints are only available in nightly for 2 weeks, before -hitting beta and ultimately stable. This and the fact that fewer people use -nightly Rust nowadays makes it more probable that a lint with many FPs hits -stable. This leads to annoyed users, that will disable these new lints in the -best case and to more annoyed users, that will stop using Clippy in the worst. -A process should be developed and implemented to prevent this from happening. - -- [#6429](https://github.com/rust-lang/rust-clippy/issues/6429) - -## Internal - -(The end of) 2020 has shown, that Clippy has to think about the available -resources, especially regarding management and maintenance of the project. This -section address issues affecting team members and contributors. - -### Management - -In 2020 Clippy achieved over 1000 open issues with regularly between 25-35 open -PRs. This is simultaneously a win and a loss. More issues and PRs means more -people are interested in Clippy and in contributing to it. On the other hand, it -means for team members more work and for contributors longer wait times for -reviews. The following will describe plans how to improve the situation for both -team members and contributors. - -#### Clear Expectations for Team Members - -According to the [Rust Roadmap 2021], a document specifying what it means to be -a member of the team should be produced. This should not put more pressure on -the team members, but rather help them and interested folks to know what the -expectations are. With this it should also be easier to recruit new team members -and may encourage people to get in touch, if they're interested to join. - -#### Scaling up the Team - -More people means less work for each individual. Together with the document -about expectations for team members, a document defining the process of how to -join the team should be produced. This can also increase the stability of the -team, in case of current members dropping out (temporarily). There can also be -different roles in the team, like people triaging vs. people reviewing. - -#### Regular Meetings - -Other teams have regular meetings. Clippy is big enough that it might be worth -to also do them. Especially if more people join the team, this can be important -for sync-ups. Besides the asynchronous communication, that works well for -working on separate lints, a meeting adds a synchronous alternative at a known -time. This is especially helpful if there are bigger things that need to be -discussed (like the projects in this roadmap). For starters bi-weekly meetings -before Rust syncs might make sense. - -#### Triaging - -To get a handle on the influx of open issues, a process for triaging issues and -PRs should be developed. Officially, Clippy follows the Rust triage process, but -currently no one enforces it. This can be improved by sharing triage teams -across projects or by implementing dashboards / tools which simplify triaging. - -### Development - -Improving the developer and contributor experience is something the Clippy team -works on regularly. Though, some things might need special attention and -planing. These topics are listed in the following. - -#### Process for New and Existing Lints - -As already mentioned above, classifying new lints gets quite hard, because the -probability of a buggy lint getting into stable is quite high. A process should -be implemented on how to classify lints. In addition, a test system should be -developed to find out which lints are currently problematic in real world code -to fix or disable them. - -- [#6429 (comment)](https://github.com/rust-lang/rust-clippy/issues/6429#issuecomment-741056379) -- [#6429 (comment)](https://github.com/rust-lang/rust-clippy/issues/6429#issuecomment-741153345) - -#### Processes - -Related to the point before, a process for suggesting and discussing major -changes should be implemented. It's also not clearly defined when a lint should -be enabled or disabled by default. This can also be improved by the test system -mentioned above. - -#### Dev-Tools - -There's already `cargo dev` which makes Clippy development easier and more -pleasant. This can still be expanded, so that it covers more areas of the -development process. - -- [#5394](https://github.com/rust-lang/rust-clippy/issues/5394) - -#### Contributor Guide - -Similar to a Clippy Book, which describes how to use Clippy, a book about how to -contribute to Clippy might be helpful for new and existing contributors. There's -already the `doc` directory in the Clippy repo, this can be turned into a -`mdbook`. - -#### `rustc` integration - -Recently Clippy was integrated with `git subtree` into the `rust-lang/rust` -repository. This made syncing between the two repositories easier. A -`#[non_exhaustive]` list of things that still can be improved is: - -1. Use the same `rustfmt` version and configuration as `rustc`. -2. Make `cargo dev` work in the Rust repo, just as it works in the Clippy repo. - E.g. `cargo dev bless` or `cargo dev update_lints`. And even add more things - to it that might be useful for the Rust repo, e.g. `cargo dev deprecate`. -3. Easier sync process. The `subtree` situation is not ideal. - -## Prioritization - -The most pressing issues for users of Clippy are of course the user facing -issues. So there should be a priority on those issues, but without losing track -of the internal issues listed in this document. - -Getting the FP rate of warn/deny-by-default lints under control should have the -highest priority. Other user facing issues should also get a high priority, but -shouldn't be in the way of addressing internal issues. - -To better manage the upcoming projects, the basic internal processes, like -meetings, tracking issues and documentation, should be established as soon as -possible. They might even be necessary to properly manage the projects, -regarding the user facing issues. - -# Prior Art - -## Rust Roadmap - -Rust's roadmap process was established by [RFC 1728] in 2016. Since then every -year a roadmap was published, that defined the bigger plans for the coming -years. This years roadmap can be found [here][Rust Roadmap 2021]. - -[RFC 1728]: https://rust-lang.github.io/rfcs/1728-north-star.html - -# Drawbacks - -## Big Roadmap - -This roadmap is pretty big and not all items listed in this document might be -addressed during 2021. Because this is the first roadmap for Clippy, having open -tasks at the end of 2021 is fine, but they should be revisited in the 2022 -roadmap. diff --git a/lintcheck/src/config.rs b/lintcheck/src/config.rs index a6f93d2a1c0..1742cf677c0 100644 --- a/lintcheck/src/config.rs +++ b/lintcheck/src/config.rs @@ -1,50 +1,40 @@ -use clap::{Arg, ArgMatches, Command}; +use clap::{Arg, ArgAction, ArgMatches, Command}; use std::env; use std::path::PathBuf; fn get_clap_config() -> ArgMatches { Command::new("lintcheck") .about("run clippy on a set of crates and check output") - .arg( + .args([ Arg::new("only") - .takes_value(true) + .action(ArgAction::Set) .value_name("CRATE") .long("only") .help("Only process a single crate of the list"), - ) - .arg( Arg::new("crates-toml") - .takes_value(true) + .action(ArgAction::Set) .value_name("CRATES-SOURCES-TOML-PATH") .long("crates-toml") .help("Set the path for a crates.toml where lintcheck should read the sources from"), - ) - .arg( Arg::new("threads") - .takes_value(true) + .action(ArgAction::Set) .value_name("N") + .value_parser(clap::value_parser!(usize)) .short('j') .long("jobs") .help("Number of threads to use, 0 automatic choice"), - ) - .arg( Arg::new("fix") - .long("--fix") + .long("fix") .help("Runs cargo clippy --fix and checks if all suggestions apply"), - ) - .arg( Arg::new("filter") - .long("--filter") - .takes_value(true) - .multiple_occurrences(true) + .long("filter") + .action(ArgAction::Append) .value_name("clippy_lint_name") .help("Apply a filter to only collect specified lints, this also overrides `allow` attributes"), - ) - .arg( Arg::new("markdown") - .long("--markdown") + .long("markdown") .help("Change the reports table to use markdown links"), - ) + ]) .get_matches() } @@ -75,13 +65,13 @@ impl LintcheckConfig { // if not, use the default "lintcheck/lintcheck_crates.toml" let sources_toml = env::var("LINTCHECK_TOML").unwrap_or_else(|_| { clap_config - .value_of("crates-toml") - .clone() + .get_one::("crates-toml") + .map(|s| &**s) .unwrap_or("lintcheck/lintcheck_crates.toml") - .to_string() + .into() }); - let markdown = clap_config.is_present("markdown"); + let markdown = clap_config.contains_id("markdown"); let sources_toml_path = PathBuf::from(sources_toml); // for the path where we save the lint results, get the filename without extension (so for @@ -96,25 +86,19 @@ impl LintcheckConfig { // look at the --threads arg, if 0 is passed, ask rayon rayon how many threads it would spawn and // use half of that for the physical core count // by default use a single thread - let max_jobs = match clap_config.value_of("threads") { - Some(threads) => { - let threads: usize = threads - .parse() - .unwrap_or_else(|_| panic!("Failed to parse '{}' to a digit", threads)); - if threads == 0 { - // automatic choice - // Rayon seems to return thread count so half that for core count - (rayon::current_num_threads() / 2) as usize - } else { - threads - } + let max_jobs = match clap_config.get_one::("threads") { + Some(&0) => { + // automatic choice + // Rayon seems to return thread count so half that for core count + (rayon::current_num_threads() / 2) as usize }, + Some(&threads) => threads, // no -j passed, use a single thread None => 1, }; let lint_filter: Vec = clap_config - .values_of("filter") + .get_many::("filter") .map(|iter| { iter.map(|lint_name| { let mut filter = lint_name.replace('_', "-"); @@ -131,8 +115,8 @@ impl LintcheckConfig { max_jobs, sources_toml_path, lintcheck_results_path, - only: clap_config.value_of("only").map(String::from), - fix: clap_config.is_present("fix"), + only: clap_config.get_one::("only").map(String::from), + fix: clap_config.contains_id("fix"), lint_filter, markdown, } diff --git a/rust-toolchain b/rust-toolchain index 2386a751f04..6ad56aacf8c 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1,3 +1,3 @@ [toolchain] -channel = "nightly-2022-06-04" +channel = "nightly-2022-06-16" components = ["cargo", "llvm-tools-preview", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"] diff --git a/tests/dogfood.rs b/tests/dogfood.rs index 9da80518ce9..fffc5360342 100644 --- a/tests/dogfood.rs +++ b/tests/dogfood.rs @@ -21,7 +21,7 @@ fn dogfood_clippy() { // "" is the root package for package in &["", "clippy_dev", "clippy_lints", "clippy_utils", "rustc_tools_util"] { - run_clippy_for_package(package, &[]); + run_clippy_for_package(package, &["-D", "clippy::all", "-D", "clippy::pedantic"]); } } @@ -77,8 +77,6 @@ fn run_clippy_for_package(project: &str, args: &[&str]) { .arg("--all-features") .arg("--") .args(args) - .args(&["-D", "clippy::all"]) - .args(&["-D", "clippy::pedantic"]) .arg("-Cdebuginfo=0"); // disable debuginfo to generate less data in the target dir if cfg!(feature = "internal") { diff --git a/tests/ui-toml/bad_toml/conf_bad_toml.rs b/tests/ui-toml/bad_toml/conf_bad_toml.rs index 3b9458fc284..f328e4d9d04 100644 --- a/tests/ui-toml/bad_toml/conf_bad_toml.rs +++ b/tests/ui-toml/bad_toml/conf_bad_toml.rs @@ -1,3 +1 @@ -// error-pattern: error reading Clippy's configuration file - fn main() {} diff --git a/tests/ui-toml/bad_toml_type/conf_bad_type.rs b/tests/ui-toml/bad_toml_type/conf_bad_type.rs index 8a0062423ad..f328e4d9d04 100644 --- a/tests/ui-toml/bad_toml_type/conf_bad_type.rs +++ b/tests/ui-toml/bad_toml_type/conf_bad_type.rs @@ -1,4 +1 @@ -// error-pattern: error reading Clippy's configuration file: `blacklisted-names` is expected to be a -// `Vec < String >` but is a `integer` - fn main() {} diff --git a/tests/ui-toml/blacklisted_names_append/blacklisted_names.rs b/tests/ui-toml/blacklisted_names_append/blacklisted_names.rs new file mode 100644 index 00000000000..fb2395cf90b --- /dev/null +++ b/tests/ui-toml/blacklisted_names_append/blacklisted_names.rs @@ -0,0 +1,10 @@ +#[warn(clippy::blacklisted_name)] + +fn main() { + // `foo` is part of the default configuration + let foo = "bar"; + // `ducks` was unrightfully blacklisted + let ducks = ["quack", "quack"]; + // `fox` is okay + let fox = ["what", "does", "the", "fox", "say", "?"]; +} diff --git a/tests/ui-toml/blacklisted_names_append/blacklisted_names.stderr b/tests/ui-toml/blacklisted_names_append/blacklisted_names.stderr new file mode 100644 index 00000000000..9169bb0e866 --- /dev/null +++ b/tests/ui-toml/blacklisted_names_append/blacklisted_names.stderr @@ -0,0 +1,16 @@ +error: use of a blacklisted/placeholder name `foo` + --> $DIR/blacklisted_names.rs:5:9 + | +LL | let foo = "bar"; + | ^^^ + | + = note: `-D clippy::blacklisted-name` implied by `-D warnings` + +error: use of a blacklisted/placeholder name `ducks` + --> $DIR/blacklisted_names.rs:7:9 + | +LL | let ducks = ["quack", "quack"]; + | ^^^^^ + +error: aborting due to 2 previous errors + diff --git a/tests/ui-toml/blacklisted_names_append/clippy.toml b/tests/ui-toml/blacklisted_names_append/clippy.toml new file mode 100644 index 00000000000..0e052ef50f0 --- /dev/null +++ b/tests/ui-toml/blacklisted_names_append/clippy.toml @@ -0,0 +1 @@ +blacklisted-names = ["ducks", ".."] diff --git a/tests/ui-toml/blacklisted_names_replace/blacklisted_names.rs b/tests/ui-toml/blacklisted_names_replace/blacklisted_names.rs new file mode 100644 index 00000000000..fb2395cf90b --- /dev/null +++ b/tests/ui-toml/blacklisted_names_replace/blacklisted_names.rs @@ -0,0 +1,10 @@ +#[warn(clippy::blacklisted_name)] + +fn main() { + // `foo` is part of the default configuration + let foo = "bar"; + // `ducks` was unrightfully blacklisted + let ducks = ["quack", "quack"]; + // `fox` is okay + let fox = ["what", "does", "the", "fox", "say", "?"]; +} diff --git a/tests/ui-toml/blacklisted_names_replace/blacklisted_names.stderr b/tests/ui-toml/blacklisted_names_replace/blacklisted_names.stderr new file mode 100644 index 00000000000..ec6f7f084f2 --- /dev/null +++ b/tests/ui-toml/blacklisted_names_replace/blacklisted_names.stderr @@ -0,0 +1,10 @@ +error: use of a blacklisted/placeholder name `ducks` + --> $DIR/blacklisted_names.rs:7:9 + | +LL | let ducks = ["quack", "quack"]; + | ^^^^^ + | + = note: `-D clippy::blacklisted-name` implied by `-D warnings` + +error: aborting due to previous error + diff --git a/tests/ui-toml/blacklisted_names_replace/clippy.toml b/tests/ui-toml/blacklisted_names_replace/clippy.toml new file mode 100644 index 00000000000..4582f1c0667 --- /dev/null +++ b/tests/ui-toml/blacklisted_names_replace/clippy.toml @@ -0,0 +1 @@ +blacklisted-names = ["ducks"] diff --git a/tests/ui-toml/conf_deprecated_key/conf_deprecated_key.rs b/tests/ui-toml/conf_deprecated_key/conf_deprecated_key.rs index 2577c1eef92..f328e4d9d04 100644 --- a/tests/ui-toml/conf_deprecated_key/conf_deprecated_key.rs +++ b/tests/ui-toml/conf_deprecated_key/conf_deprecated_key.rs @@ -1,4 +1 @@ -// error-pattern: error reading Clippy's configuration file: found deprecated field -// `cyclomatic-complexity-threshold`. Please use `cognitive-complexity-threshold` instead. - fn main() {} diff --git a/tests/ui-toml/doc_valid_idents_append/clippy.toml b/tests/ui-toml/doc_valid_idents_append/clippy.toml new file mode 100644 index 00000000000..daf3276854b --- /dev/null +++ b/tests/ui-toml/doc_valid_idents_append/clippy.toml @@ -0,0 +1 @@ +doc-valid-idents = ["ClipPy", ".."] diff --git a/tests/ui-toml/doc_valid_idents_append/doc_markdown.rs b/tests/ui-toml/doc_valid_idents_append/doc_markdown.rs new file mode 100644 index 00000000000..327a592e9ca --- /dev/null +++ b/tests/ui-toml/doc_valid_idents_append/doc_markdown.rs @@ -0,0 +1,12 @@ +#![warn(clippy::doc_markdown)] + +/// This is a special interface for ClipPy which doesn't require backticks +fn allowed_name() {} + +/// OAuth and LaTeX are inside Clippy's default list. +fn default_name() {} + +/// TestItemThingyOfCoolness might sound cool but is not on the list and should be linted. +fn unknown_name() {} + +fn main() {} diff --git a/tests/ui-toml/doc_valid_idents_append/doc_markdown.stderr b/tests/ui-toml/doc_valid_idents_append/doc_markdown.stderr new file mode 100644 index 00000000000..0f767c9b855 --- /dev/null +++ b/tests/ui-toml/doc_valid_idents_append/doc_markdown.stderr @@ -0,0 +1,14 @@ +error: item in documentation is missing backticks + --> $DIR/doc_markdown.rs:9:5 + | +LL | /// TestItemThingyOfCoolness might sound cool but is not on the list and should be linted. + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D clippy::doc-markdown` implied by `-D warnings` +help: try + | +LL | /// `TestItemThingyOfCoolness` might sound cool but is not on the list and should be linted. + | ~~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: aborting due to previous error + diff --git a/tests/ui-toml/doc_valid_idents_replace/clippy.toml b/tests/ui-toml/doc_valid_idents_replace/clippy.toml new file mode 100644 index 00000000000..70bc477b08c --- /dev/null +++ b/tests/ui-toml/doc_valid_idents_replace/clippy.toml @@ -0,0 +1 @@ +doc-valid-idents = ["ClipPy"] diff --git a/tests/ui-toml/doc_valid_idents_replace/doc_markdown.rs b/tests/ui-toml/doc_valid_idents_replace/doc_markdown.rs new file mode 100644 index 00000000000..327a592e9ca --- /dev/null +++ b/tests/ui-toml/doc_valid_idents_replace/doc_markdown.rs @@ -0,0 +1,12 @@ +#![warn(clippy::doc_markdown)] + +/// This is a special interface for ClipPy which doesn't require backticks +fn allowed_name() {} + +/// OAuth and LaTeX are inside Clippy's default list. +fn default_name() {} + +/// TestItemThingyOfCoolness might sound cool but is not on the list and should be linted. +fn unknown_name() {} + +fn main() {} diff --git a/tests/ui-toml/doc_valid_idents_replace/doc_markdown.stderr b/tests/ui-toml/doc_valid_idents_replace/doc_markdown.stderr new file mode 100644 index 00000000000..e0613eb863b --- /dev/null +++ b/tests/ui-toml/doc_valid_idents_replace/doc_markdown.stderr @@ -0,0 +1,36 @@ +error: item in documentation is missing backticks + --> $DIR/doc_markdown.rs:6:5 + | +LL | /// OAuth and LaTeX are inside Clippy's default list. + | ^^^^^ + | + = note: `-D clippy::doc-markdown` implied by `-D warnings` +help: try + | +LL | /// `OAuth` and LaTeX are inside Clippy's default list. + | ~~~~~~~ + +error: item in documentation is missing backticks + --> $DIR/doc_markdown.rs:6:15 + | +LL | /// OAuth and LaTeX are inside Clippy's default list. + | ^^^^^ + | +help: try + | +LL | /// OAuth and `LaTeX` are inside Clippy's default list. + | ~~~~~~~ + +error: item in documentation is missing backticks + --> $DIR/doc_markdown.rs:9:5 + | +LL | /// TestItemThingyOfCoolness might sound cool but is not on the list and should be linted. + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: try + | +LL | /// `TestItemThingyOfCoolness` might sound cool but is not on the list and should be linted. + | ~~~~~~~~~~~~~~~~~~~~~~~~~~ + +error: aborting due to 3 previous errors + diff --git a/tests/ui-toml/good_toml_no_false_negatives/conf_no_false_negatives.rs b/tests/ui-toml/good_toml_no_false_negatives/conf_no_false_negatives.rs index 270b9c5c43c..f328e4d9d04 100644 --- a/tests/ui-toml/good_toml_no_false_negatives/conf_no_false_negatives.rs +++ b/tests/ui-toml/good_toml_no_false_negatives/conf_no_false_negatives.rs @@ -1,3 +1 @@ -// error-pattern: should give absolutely no error - fn main() {} diff --git a/tests/ui-toml/toml_unknown_key/conf_unknown_key.rs b/tests/ui-toml/toml_unknown_key/conf_unknown_key.rs index a47569f62a3..f328e4d9d04 100644 --- a/tests/ui-toml/toml_unknown_key/conf_unknown_key.rs +++ b/tests/ui-toml/toml_unknown_key/conf_unknown_key.rs @@ -1,3 +1 @@ -// error-pattern: error reading Clippy's configuration file: unknown key `foobar` - fn main() {} diff --git a/tests/ui/almost_complete_letter_range.fixed b/tests/ui/almost_complete_letter_range.fixed index 39f8f0c2949..e69b40f35f4 100644 --- a/tests/ui/almost_complete_letter_range.fixed +++ b/tests/ui/almost_complete_letter_range.fixed @@ -6,6 +6,7 @@ #![feature(stmt_expr_attributes)] #![warn(clippy::almost_complete_letter_range)] #![allow(ellipsis_inclusive_range_patterns)] +#![allow(clippy::needless_parens_on_range_literals)] macro_rules! a { () => { diff --git a/tests/ui/almost_complete_letter_range.rs b/tests/ui/almost_complete_letter_range.rs index 3dc02199257..f2240981d45 100644 --- a/tests/ui/almost_complete_letter_range.rs +++ b/tests/ui/almost_complete_letter_range.rs @@ -6,6 +6,7 @@ #![feature(stmt_expr_attributes)] #![warn(clippy::almost_complete_letter_range)] #![allow(ellipsis_inclusive_range_patterns)] +#![allow(clippy::needless_parens_on_range_literals)] macro_rules! a { () => { diff --git a/tests/ui/almost_complete_letter_range.stderr b/tests/ui/almost_complete_letter_range.stderr index 74980ec1a92..5b5dc40ee54 100644 --- a/tests/ui/almost_complete_letter_range.stderr +++ b/tests/ui/almost_complete_letter_range.stderr @@ -1,5 +1,5 @@ error: almost complete ascii letter range - --> $DIR/almost_complete_letter_range.rs:19:17 + --> $DIR/almost_complete_letter_range.rs:20:17 | LL | let _ = ('a') ..'z'; | ^^^^^^--^^^ @@ -9,7 +9,7 @@ LL | let _ = ('a') ..'z'; = note: `-D clippy::almost-complete-letter-range` implied by `-D warnings` error: almost complete ascii letter range - --> $DIR/almost_complete_letter_range.rs:20:17 + --> $DIR/almost_complete_letter_range.rs:21:17 | LL | let _ = 'A' .. ('Z'); | ^^^^--^^^^^^ @@ -17,7 +17,7 @@ LL | let _ = 'A' .. ('Z'); | help: use an inclusive range: `..=` error: almost complete ascii letter range - --> $DIR/almost_complete_letter_range.rs:26:13 + --> $DIR/almost_complete_letter_range.rs:27:13 | LL | let _ = (b'a')..(b'z'); | ^^^^^^--^^^^^^ @@ -25,7 +25,7 @@ LL | let _ = (b'a')..(b'z'); | help: use an inclusive range: `..=` error: almost complete ascii letter range - --> $DIR/almost_complete_letter_range.rs:27:13 + --> $DIR/almost_complete_letter_range.rs:28:13 | LL | let _ = b'A'..b'Z'; | ^^^^--^^^^ @@ -33,7 +33,7 @@ LL | let _ = b'A'..b'Z'; | help: use an inclusive range: `..=` error: almost complete ascii letter range - --> $DIR/almost_complete_letter_range.rs:32:13 + --> $DIR/almost_complete_letter_range.rs:33:13 | LL | let _ = a!()..'z'; | ^^^^--^^^ @@ -41,7 +41,7 @@ LL | let _ = a!()..'z'; | help: use an inclusive range: `..=` error: almost complete ascii letter range - --> $DIR/almost_complete_letter_range.rs:35:9 + --> $DIR/almost_complete_letter_range.rs:36:9 | LL | b'a'..b'z' if true => 1, | ^^^^--^^^^ @@ -49,7 +49,7 @@ LL | b'a'..b'z' if true => 1, | help: use an inclusive range: `..=` error: almost complete ascii letter range - --> $DIR/almost_complete_letter_range.rs:36:9 + --> $DIR/almost_complete_letter_range.rs:37:9 | LL | b'A'..b'Z' if true => 2, | ^^^^--^^^^ @@ -57,7 +57,7 @@ LL | b'A'..b'Z' if true => 2, | help: use an inclusive range: `..=` error: almost complete ascii letter range - --> $DIR/almost_complete_letter_range.rs:43:9 + --> $DIR/almost_complete_letter_range.rs:44:9 | LL | 'a'..'z' if true => 1, | ^^^--^^^ @@ -65,7 +65,7 @@ LL | 'a'..'z' if true => 1, | help: use an inclusive range: `..=` error: almost complete ascii letter range - --> $DIR/almost_complete_letter_range.rs:44:9 + --> $DIR/almost_complete_letter_range.rs:45:9 | LL | 'A'..'Z' if true => 2, | ^^^--^^^ @@ -73,7 +73,7 @@ LL | 'A'..'Z' if true => 2, | help: use an inclusive range: `..=` error: almost complete ascii letter range - --> $DIR/almost_complete_letter_range.rs:54:9 + --> $DIR/almost_complete_letter_range.rs:55:9 | LL | 'a'..'z' => 1, | ^^^--^^^ @@ -81,7 +81,7 @@ LL | 'a'..'z' => 1, | help: use an inclusive range: `...` error: almost complete ascii letter range - --> $DIR/almost_complete_letter_range.rs:61:13 + --> $DIR/almost_complete_letter_range.rs:62:13 | LL | let _ = 'a'..'z'; | ^^^--^^^ @@ -89,7 +89,7 @@ LL | let _ = 'a'..'z'; | help: use an inclusive range: `..=` error: almost complete ascii letter range - --> $DIR/almost_complete_letter_range.rs:63:9 + --> $DIR/almost_complete_letter_range.rs:64:9 | LL | 'a'..'z' => 1, | ^^^--^^^ diff --git a/tests/ui/async_yields_async.fixed b/tests/ui/async_yields_async.fixed index e20b58269b9..3cf380d2b95 100644 --- a/tests/ui/async_yields_async.fixed +++ b/tests/ui/async_yields_async.fixed @@ -1,5 +1,5 @@ // run-rustfix - +#![feature(lint_reasons)] #![feature(async_closure)] #![warn(clippy::async_yields_async)] @@ -65,3 +65,14 @@ fn main() { let _n = async || custom_future_type_ctor(); let _o = async || f(); } + +#[rustfmt::skip] +#[allow(dead_code)] +fn check_expect_suppression() { + #[expect(clippy::async_yields_async)] + let _j = async || { + async { + 3 + } + }; +} diff --git a/tests/ui/async_yields_async.rs b/tests/ui/async_yields_async.rs index c1dfa398450..dd4131b60ab 100644 --- a/tests/ui/async_yields_async.rs +++ b/tests/ui/async_yields_async.rs @@ -1,5 +1,5 @@ // run-rustfix - +#![feature(lint_reasons)] #![feature(async_closure)] #![warn(clippy::async_yields_async)] @@ -65,3 +65,14 @@ fn main() { let _n = async || custom_future_type_ctor(); let _o = async || f(); } + +#[rustfmt::skip] +#[allow(dead_code)] +fn check_expect_suppression() { + #[expect(clippy::async_yields_async)] + let _j = async || { + async { + 3 + } + }; +} diff --git a/tests/ui/branches_sharing_code/false_positives.rs b/tests/ui/branches_sharing_code/false_positives.rs index 7f42df46341..06448200951 100644 --- a/tests/ui/branches_sharing_code/false_positives.rs +++ b/tests/ui/branches_sharing_code/false_positives.rs @@ -25,4 +25,17 @@ impl FooBar { fn baz(&mut self) {} } -fn main() {} +fn foo(x: u32, y: u32) -> u32 { + x / y +} + +fn main() { + let x = (1, 2); + let _ = if true { + let (x, y) = x; + foo(x, y) + } else { + let (y, x) = x; + foo(x, y) + }; +} diff --git a/tests/ui/branches_sharing_code/shared_at_bottom.stderr b/tests/ui/branches_sharing_code/shared_at_bottom.stderr index e3c1bbee994..5e1a68d216e 100644 --- a/tests/ui/branches_sharing_code/shared_at_bottom.stderr +++ b/tests/ui/branches_sharing_code/shared_at_bottom.stderr @@ -12,8 +12,8 @@ note: the lint level is defined here | LL | #![deny(clippy::if_same_then_else, clippy::branches_sharing_code)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - = note: The end suggestion probably needs some adjustments to use the expression result correctly -help: consider moving the end statements out like this + = note: the end suggestion probably needs some adjustments to use the expression result correctly +help: consider moving these statements after the if | LL ~ } LL + let result = false; @@ -28,7 +28,7 @@ LL | / println!("Same end of block"); LL | | } | |_____^ | -help: consider moving the end statements out like this +help: consider moving these statements after the if | LL ~ } LL + println!("Same end of block"); @@ -44,7 +44,7 @@ LL | | ); LL | | } | |_____^ | -help: consider moving the end statements out like this +help: consider moving these statements after the if | LL ~ } LL + println!( @@ -60,7 +60,7 @@ LL | / println!("Hello World"); LL | | } | |_________^ | -help: consider moving the end statements out like this +help: consider moving these statements after the if | LL ~ } LL + println!("Hello World"); @@ -75,8 +75,8 @@ LL | | // I'm expecting a note about this LL | | } | |_____^ | - = warning: Some moved values might need to be renamed to avoid wrong references -help: consider moving the end statements out like this + = warning: some moved values might need to be renamed to avoid wrong references +help: consider moving these statements after the if | LL ~ } LL + let later_used_value = "A string value"; @@ -91,8 +91,8 @@ LL | | println!("This is the new simple_example: {}", simple_examples); LL | | } | |_____^ | - = warning: Some moved values might need to be renamed to avoid wrong references -help: consider moving the end statements out like this + = warning: some moved values might need to be renamed to avoid wrong references +help: consider moving these statements after the if | LL ~ } LL + let simple_examples = "I now identify as a &str :)"; @@ -106,8 +106,8 @@ LL | / x << 2 LL | | }; | |_____^ | - = note: The end suggestion probably needs some adjustments to use the expression result correctly -help: consider moving the end statements out like this + = note: the end suggestion probably needs some adjustments to use the expression result correctly +help: consider moving these statements after the if | LL ~ } LL ~ x << 2; @@ -120,8 +120,8 @@ LL | / x * 4 LL | | } | |_____^ | - = note: The end suggestion probably needs some adjustments to use the expression result correctly -help: consider moving the end statements out like this + = note: the end suggestion probably needs some adjustments to use the expression result correctly +help: consider moving these statements after the if | LL ~ } LL + x * 4 @@ -133,7 +133,7 @@ error: all if blocks contain the same code at the end LL | if x == 17 { b = 1; a = 0x99; } else { a = 0x99; } | ^^^^^^^^^^^ | -help: consider moving the end statements out like this +help: consider moving these statements after the if | LL ~ if x == 17 { b = 1; a = 0x99; } else { } LL + a = 0x99; diff --git a/tests/ui/branches_sharing_code/shared_at_top.stderr b/tests/ui/branches_sharing_code/shared_at_top.stderr index 8d78fa5de7e..d890b12ecbb 100644 --- a/tests/ui/branches_sharing_code/shared_at_top.stderr +++ b/tests/ui/branches_sharing_code/shared_at_top.stderr @@ -10,7 +10,7 @@ note: the lint level is defined here | LL | #![deny(clippy::if_same_then_else, clippy::branches_sharing_code)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -help: consider moving the start statements out like this +help: consider moving these statements before the if | LL ~ println!("Hello World!"); LL + if true { @@ -25,8 +25,8 @@ LL | | println!("The value y was set to: `{}`", y); LL | | let _z = y; | |___________________^ | - = warning: Some moved values might need to be renamed to avoid wrong references -help: consider moving the start statements out like this + = warning: some moved values might need to be renamed to avoid wrong references +help: consider moving these statements before the if | LL ~ let y = 9; LL + println!("The value y was set to: `{}`", y); @@ -41,7 +41,7 @@ LL | / let _ = if x == 7 { LL | | let y = 16; | |___________________^ | -help: consider moving the start statements out like this +help: consider moving these statements before the if | LL ~ let y = 16; LL + let _ = if x == 7 { @@ -55,8 +55,8 @@ LL | | let used_value_name = "Different type"; LL | | println!("Str: {}", used_value_name); | |_____________________________________________^ | - = warning: Some moved values might need to be renamed to avoid wrong references -help: consider moving the start statements out like this + = warning: some moved values might need to be renamed to avoid wrong references +help: consider moving these statements before the if | LL ~ let used_value_name = "Different type"; LL + println!("Str: {}", used_value_name); @@ -71,8 +71,8 @@ LL | | let can_be_overridden = "Move me"; LL | | println!("I'm also moveable"); | |______________________________________^ | - = warning: Some moved values might need to be renamed to avoid wrong references -help: consider moving the start statements out like this + = warning: some moved values might need to be renamed to avoid wrong references +help: consider moving these statements before the if | LL ~ let can_be_overridden = "Move me"; LL + println!("I'm also moveable"); @@ -87,7 +87,7 @@ LL | | println!("This should trigger the `SHARED_CODE_IN_IF_BLOCKS` lint LL | | println!("Because `IF_SAME_THEN_ELSE` is allowed here"); | |________________________________________________________________^ | -help: consider moving the start statements out like this +help: consider moving these statements before the if | LL ~ println!("This should trigger the `SHARED_CODE_IN_IF_BLOCKS` lint."); LL + println!("Because `IF_SAME_THEN_ELSE` is allowed here"); diff --git a/tests/ui/branches_sharing_code/shared_at_top_and_bottom.stderr b/tests/ui/branches_sharing_code/shared_at_top_and_bottom.stderr index 1db2343d3fe..11843cc03d8 100644 --- a/tests/ui/branches_sharing_code/shared_at_top_and_bottom.stderr +++ b/tests/ui/branches_sharing_code/shared_at_top_and_bottom.stderr @@ -1,4 +1,4 @@ -error: all if blocks contain the same code at the start and the end. Here at the start +error: all if blocks contain the same code at both the start and the end --> $DIR/shared_at_top_and_bottom.rs:16:5 | LL | / if x == 7 { @@ -12,26 +12,26 @@ note: the lint level is defined here | LL | #![deny(clippy::if_same_then_else, clippy::branches_sharing_code)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -note: and here at the end +note: this code is shared at the end --> $DIR/shared_at_top_and_bottom.rs:28:5 | LL | / let _u = 9; LL | | } | |_____^ -help: consider moving the start statements out like this +help: consider moving these statements before the if | LL ~ let t = 7; LL + let _overlap_start = t * 2; LL + let _overlap_end = 2 * t; LL + if x == 7 { | -help: and consider moving the end statements out like this +help: consider moving these statements after the if | LL ~ } LL + let _u = 9; | -error: all if blocks contain the same code at the start and the end. Here at the start +error: all if blocks contain the same code at both the start and the end --> $DIR/shared_at_top_and_bottom.rs:32:5 | LL | / if x == 99 { @@ -40,29 +40,29 @@ LL | | let _overlap_start = r; LL | | let _overlap_middle = r * r; | |____________________________________^ | -note: and here at the end +note: this code is shared at the end --> $DIR/shared_at_top_and_bottom.rs:43:5 | LL | / let _overlap_end = r * r * r; LL | | let z = "end"; LL | | } | |_____^ - = warning: Some moved values might need to be renamed to avoid wrong references -help: consider moving the start statements out like this + = warning: some moved values might need to be renamed to avoid wrong references +help: consider moving these statements before the if | LL ~ let r = 7; LL + let _overlap_start = r; LL + let _overlap_middle = r * r; LL + if x == 99 { | -help: and consider moving the end statements out like this +help: consider moving these statements after the if | LL ~ } LL + let _overlap_end = r * r * r; LL + let z = "end"; | -error: all if blocks contain the same code at the start and the end. Here at the start +error: all if blocks contain the same code at both the start and the end --> $DIR/shared_at_top_and_bottom.rs:61:5 | LL | / if (x > 7 && y < 13) || (x + y) % 2 == 1 { @@ -71,7 +71,7 @@ LL | | let b = 0xffff00ff; LL | | let e_id = gen_id(a, b); | |________________________________^ | -note: and here at the end +note: this code is shared at the end --> $DIR/shared_at_top_and_bottom.rs:81:5 | LL | / let pack = DataPack { @@ -82,15 +82,15 @@ LL | | }; LL | | process_data(pack); LL | | } | |_____^ - = warning: Some moved values might need to be renamed to avoid wrong references -help: consider moving the start statements out like this + = warning: some moved values might need to be renamed to avoid wrong references +help: consider moving these statements before the if | LL ~ let a = 0xcafe; LL + let b = 0xffff00ff; LL + let e_id = gen_id(a, b); LL + if (x > 7 && y < 13) || (x + y) % 2 == 1 { | -help: and consider moving the end statements out like this +help: consider moving these statements after the if | LL ~ } LL + let pack = DataPack { @@ -100,51 +100,51 @@ LL + some_data: vec![0x12, 0x34, 0x56, 0x78, 0x90], LL + }; ... -error: all if blocks contain the same code at the start and the end. Here at the start +error: all if blocks contain the same code at both the start and the end --> $DIR/shared_at_top_and_bottom.rs:94:5 | LL | / let _ = if x == 7 { LL | | let _ = 19; | |___________________^ | -note: and here at the end +note: this code is shared at the end --> $DIR/shared_at_top_and_bottom.rs:103:5 | LL | / x << 2 LL | | }; | |_____^ - = note: The end suggestion probably needs some adjustments to use the expression result correctly -help: consider moving the start statements out like this + = note: the end suggestion probably needs some adjustments to use the expression result correctly +help: consider moving these statements before the if | LL ~ let _ = 19; LL + let _ = if x == 7 { | -help: and consider moving the end statements out like this +help: consider moving these statements after the if | LL ~ } LL ~ x << 2; | -error: all if blocks contain the same code at the start and the end. Here at the start +error: all if blocks contain the same code at both the start and the end --> $DIR/shared_at_top_and_bottom.rs:106:5 | LL | / if x == 9 { LL | | let _ = 17; | |___________________^ | -note: and here at the end +note: this code is shared at the end --> $DIR/shared_at_top_and_bottom.rs:115:5 | LL | / x * 4 LL | | } | |_____^ - = note: The end suggestion probably needs some adjustments to use the expression result correctly -help: consider moving the start statements out like this + = note: the end suggestion probably needs some adjustments to use the expression result correctly +help: consider moving these statements before the if | LL ~ let _ = 17; LL + if x == 9 { | -help: and consider moving the end statements out like this +help: consider moving these statements after the if | LL ~ } LL + x * 4 diff --git a/tests/ui/checked_conversions.fixed b/tests/ui/checked_conversions.fixed index 0983d393b56..cb7100bc9ef 100644 --- a/tests/ui/checked_conversions.fixed +++ b/tests/ui/checked_conversions.fixed @@ -71,4 +71,9 @@ pub fn i8_to_u8(value: i8) { let _ = value >= 0; } +// Do not lint +pub const fn issue_8898(i: u32) -> bool { + i <= i32::MAX as u32 +} + fn main() {} diff --git a/tests/ui/checked_conversions.rs b/tests/ui/checked_conversions.rs index 7d26ace47fd..ed4e0692388 100644 --- a/tests/ui/checked_conversions.rs +++ b/tests/ui/checked_conversions.rs @@ -71,4 +71,9 @@ pub fn i8_to_u8(value: i8) { let _ = value >= 0; } +// Do not lint +pub const fn issue_8898(i: u32) -> bool { + i <= i32::MAX as u32 +} + fn main() {} diff --git a/tests/ui/default_numeric_fallback_i32.fixed b/tests/ui/default_numeric_fallback_i32.fixed index fa85d278c8f..55451cf2f7d 100644 --- a/tests/ui/default_numeric_fallback_i32.fixed +++ b/tests/ui/default_numeric_fallback_i32.fixed @@ -1,6 +1,7 @@ // run-rustfix // aux-build:macro_rules.rs +#![feature(lint_reasons)] #![warn(clippy::default_numeric_fallback)] #![allow( unused, @@ -173,4 +174,9 @@ mod in_macro { } } +fn check_expect_suppression() { + #[expect(clippy::default_numeric_fallback)] + let x = 21; +} + fn main() {} diff --git a/tests/ui/default_numeric_fallback_i32.rs b/tests/ui/default_numeric_fallback_i32.rs index 71acccd702b..62d72f2feba 100644 --- a/tests/ui/default_numeric_fallback_i32.rs +++ b/tests/ui/default_numeric_fallback_i32.rs @@ -1,6 +1,7 @@ // run-rustfix // aux-build:macro_rules.rs +#![feature(lint_reasons)] #![warn(clippy::default_numeric_fallback)] #![allow( unused, @@ -173,4 +174,9 @@ mod in_macro { } } +fn check_expect_suppression() { + #[expect(clippy::default_numeric_fallback)] + let x = 21; +} + fn main() {} diff --git a/tests/ui/default_numeric_fallback_i32.stderr b/tests/ui/default_numeric_fallback_i32.stderr index 3cc84ff1132..f7c5e724c40 100644 --- a/tests/ui/default_numeric_fallback_i32.stderr +++ b/tests/ui/default_numeric_fallback_i32.stderr @@ -1,5 +1,5 @@ error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_i32.rs:20:17 + --> $DIR/default_numeric_fallback_i32.rs:21:17 | LL | let x = 22; | ^^ help: consider adding suffix: `22_i32` @@ -7,145 +7,145 @@ LL | let x = 22; = note: `-D clippy::default-numeric-fallback` implied by `-D warnings` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_i32.rs:21:18 + --> $DIR/default_numeric_fallback_i32.rs:22:18 | LL | let x = [1, 2, 3]; | ^ help: consider adding suffix: `1_i32` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_i32.rs:21:21 + --> $DIR/default_numeric_fallback_i32.rs:22:21 | LL | let x = [1, 2, 3]; | ^ help: consider adding suffix: `2_i32` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_i32.rs:21:24 + --> $DIR/default_numeric_fallback_i32.rs:22:24 | LL | let x = [1, 2, 3]; | ^ help: consider adding suffix: `3_i32` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_i32.rs:22:28 + --> $DIR/default_numeric_fallback_i32.rs:23:28 | LL | let x = if true { (1, 2) } else { (3, 4) }; | ^ help: consider adding suffix: `1_i32` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_i32.rs:22:31 + --> $DIR/default_numeric_fallback_i32.rs:23:31 | LL | let x = if true { (1, 2) } else { (3, 4) }; | ^ help: consider adding suffix: `2_i32` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_i32.rs:22:44 + --> $DIR/default_numeric_fallback_i32.rs:23:44 | LL | let x = if true { (1, 2) } else { (3, 4) }; | ^ help: consider adding suffix: `3_i32` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_i32.rs:22:47 + --> $DIR/default_numeric_fallback_i32.rs:23:47 | LL | let x = if true { (1, 2) } else { (3, 4) }; | ^ help: consider adding suffix: `4_i32` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_i32.rs:23:23 + --> $DIR/default_numeric_fallback_i32.rs:24:23 | LL | let x = match 1 { | ^ help: consider adding suffix: `1_i32` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_i32.rs:24:13 + --> $DIR/default_numeric_fallback_i32.rs:25:13 | LL | 1 => 1, | ^ help: consider adding suffix: `1_i32` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_i32.rs:24:18 + --> $DIR/default_numeric_fallback_i32.rs:25:18 | LL | 1 => 1, | ^ help: consider adding suffix: `1_i32` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_i32.rs:25:18 + --> $DIR/default_numeric_fallback_i32.rs:26:18 | LL | _ => 2, | ^ help: consider adding suffix: `2_i32` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_i32.rs:42:21 + --> $DIR/default_numeric_fallback_i32.rs:43:21 | LL | let y = 1; | ^ help: consider adding suffix: `1_i32` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_i32.rs:50:21 + --> $DIR/default_numeric_fallback_i32.rs:51:21 | LL | let y = 1; | ^ help: consider adding suffix: `1_i32` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_i32.rs:56:21 + --> $DIR/default_numeric_fallback_i32.rs:57:21 | LL | let y = 1; | ^ help: consider adding suffix: `1_i32` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_i32.rs:68:9 + --> $DIR/default_numeric_fallback_i32.rs:69:9 | LL | 1 | ^ help: consider adding suffix: `1_i32` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_i32.rs:74:27 + --> $DIR/default_numeric_fallback_i32.rs:75:27 | LL | let f = || -> _ { 1 }; | ^ help: consider adding suffix: `1_i32` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_i32.rs:78:29 + --> $DIR/default_numeric_fallback_i32.rs:79:29 | LL | let f = || -> i32 { 1 }; | ^ help: consider adding suffix: `1_i32` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_i32.rs:92:21 + --> $DIR/default_numeric_fallback_i32.rs:93:21 | LL | generic_arg(1); | ^ help: consider adding suffix: `1_i32` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_i32.rs:95:32 + --> $DIR/default_numeric_fallback_i32.rs:96:32 | LL | let x: _ = generic_arg(1); | ^ help: consider adding suffix: `1_i32` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_i32.rs:113:28 + --> $DIR/default_numeric_fallback_i32.rs:114:28 | LL | GenericStruct { x: 1 }; | ^ help: consider adding suffix: `1_i32` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_i32.rs:116:36 + --> $DIR/default_numeric_fallback_i32.rs:117:36 | LL | let _ = GenericStruct { x: 1 }; | ^ help: consider adding suffix: `1_i32` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_i32.rs:134:24 + --> $DIR/default_numeric_fallback_i32.rs:135:24 | LL | GenericEnum::X(1); | ^ help: consider adding suffix: `1_i32` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_i32.rs:154:23 + --> $DIR/default_numeric_fallback_i32.rs:155:23 | LL | s.generic_arg(1); | ^ help: consider adding suffix: `1_i32` error: default numeric fallback might occur - --> $DIR/default_numeric_fallback_i32.rs:161:21 + --> $DIR/default_numeric_fallback_i32.rs:162:21 | LL | let x = 22; | ^^ help: consider adding suffix: `22_i32` diff --git a/tests/ui/derive_partial_eq_without_eq.fixed b/tests/ui/derive_partial_eq_without_eq.fixed index 012780258fc..bbbe467590f 100644 --- a/tests/ui/derive_partial_eq_without_eq.fixed +++ b/tests/ui/derive_partial_eq_without_eq.fixed @@ -4,28 +4,28 @@ #![warn(clippy::derive_partial_eq_without_eq)] // Don't warn on structs that aren't PartialEq -struct NotPartialEq { +pub struct NotPartialEq { foo: u32, bar: String, } // Eq can be derived but is missing #[derive(Debug, PartialEq, Eq)] -struct MissingEq { +pub struct MissingEq { foo: u32, bar: String, } // Eq is derived #[derive(PartialEq, Eq)] -struct NotMissingEq { +pub struct NotMissingEq { foo: u32, bar: String, } // Eq is manually implemented #[derive(PartialEq)] -struct ManualEqImpl { +pub struct ManualEqImpl { foo: u32, bar: String, } @@ -34,13 +34,13 @@ impl Eq for ManualEqImpl {} // Cannot be Eq because f32 isn't Eq #[derive(PartialEq)] -struct CannotBeEq { +pub struct CannotBeEq { foo: u32, bar: f32, } // Don't warn if PartialEq is manually implemented -struct ManualPartialEqImpl { +pub struct ManualPartialEqImpl { foo: u32, bar: String, } @@ -52,53 +52,75 @@ impl PartialEq for ManualPartialEqImpl { } // Generic fields should be properly checked for Eq-ness -#[derive(PartialEq)] -struct GenericNotEq { +#[derive(PartialEq, Eq)] +pub struct GenericNotEq { foo: T, bar: U, } #[derive(PartialEq, Eq)] -struct GenericEq { +pub struct GenericEq { foo: T, bar: U, } #[derive(PartialEq, Eq)] -struct TupleStruct(u32); +pub struct TupleStruct(u32); #[derive(PartialEq, Eq)] -struct GenericTupleStruct(T); +pub struct GenericTupleStruct(T); #[derive(PartialEq)] -struct TupleStructNotEq(f32); +pub struct TupleStructNotEq(f32); #[derive(PartialEq, Eq)] -enum Enum { +pub enum Enum { Foo(u32), Bar { a: String, b: () }, } #[derive(PartialEq, Eq)] -enum GenericEnum { +pub enum GenericEnum { Foo(T), Bar { a: U, b: V }, } #[derive(PartialEq)] -enum EnumNotEq { +pub enum EnumNotEq { Foo(u32), Bar { a: String, b: f32 }, } // Ensure that rustfix works properly when `PartialEq` has other derives on either side #[derive(Debug, PartialEq, Eq, Clone)] -struct RustFixWithOtherDerives; +pub struct RustFixWithOtherDerives; -#[derive(PartialEq)] -struct Generic(T); +#[derive(PartialEq, Eq)] +pub struct Generic(T); #[derive(PartialEq, Eq)] -struct GenericPhantom(core::marker::PhantomData); +pub struct GenericPhantom(core::marker::PhantomData); + +mod _hidden { + #[derive(PartialEq, Eq)] + pub struct Reexported; + + #[derive(PartialEq, Eq)] + pub struct InPubFn; + + #[derive(PartialEq)] + pub(crate) struct PubCrate; + + #[derive(PartialEq)] + pub(super) struct PubSuper; +} + +pub use _hidden::Reexported; +pub fn _from_mod() -> _hidden::InPubFn { + _hidden::InPubFn +} + +#[derive(PartialEq)] +struct InternalTy; fn main() {} diff --git a/tests/ui/derive_partial_eq_without_eq.rs b/tests/ui/derive_partial_eq_without_eq.rs index fc8285b0c6b..88d6fbd1af7 100644 --- a/tests/ui/derive_partial_eq_without_eq.rs +++ b/tests/ui/derive_partial_eq_without_eq.rs @@ -4,28 +4,28 @@ #![warn(clippy::derive_partial_eq_without_eq)] // Don't warn on structs that aren't PartialEq -struct NotPartialEq { +pub struct NotPartialEq { foo: u32, bar: String, } // Eq can be derived but is missing #[derive(Debug, PartialEq)] -struct MissingEq { +pub struct MissingEq { foo: u32, bar: String, } // Eq is derived #[derive(PartialEq, Eq)] -struct NotMissingEq { +pub struct NotMissingEq { foo: u32, bar: String, } // Eq is manually implemented #[derive(PartialEq)] -struct ManualEqImpl { +pub struct ManualEqImpl { foo: u32, bar: String, } @@ -34,13 +34,13 @@ impl Eq for ManualEqImpl {} // Cannot be Eq because f32 isn't Eq #[derive(PartialEq)] -struct CannotBeEq { +pub struct CannotBeEq { foo: u32, bar: f32, } // Don't warn if PartialEq is manually implemented -struct ManualPartialEqImpl { +pub struct ManualPartialEqImpl { foo: u32, bar: String, } @@ -53,52 +53,74 @@ impl PartialEq for ManualPartialEqImpl { // Generic fields should be properly checked for Eq-ness #[derive(PartialEq)] -struct GenericNotEq { +pub struct GenericNotEq { foo: T, bar: U, } #[derive(PartialEq)] -struct GenericEq { +pub struct GenericEq { foo: T, bar: U, } #[derive(PartialEq)] -struct TupleStruct(u32); +pub struct TupleStruct(u32); #[derive(PartialEq)] -struct GenericTupleStruct(T); +pub struct GenericTupleStruct(T); #[derive(PartialEq)] -struct TupleStructNotEq(f32); +pub struct TupleStructNotEq(f32); #[derive(PartialEq)] -enum Enum { +pub enum Enum { Foo(u32), Bar { a: String, b: () }, } #[derive(PartialEq)] -enum GenericEnum { +pub enum GenericEnum { Foo(T), Bar { a: U, b: V }, } #[derive(PartialEq)] -enum EnumNotEq { +pub enum EnumNotEq { Foo(u32), Bar { a: String, b: f32 }, } // Ensure that rustfix works properly when `PartialEq` has other derives on either side #[derive(Debug, PartialEq, Clone)] -struct RustFixWithOtherDerives; +pub struct RustFixWithOtherDerives; #[derive(PartialEq)] -struct Generic(T); +pub struct Generic(T); #[derive(PartialEq, Eq)] -struct GenericPhantom(core::marker::PhantomData); +pub struct GenericPhantom(core::marker::PhantomData); + +mod _hidden { + #[derive(PartialEq)] + pub struct Reexported; + + #[derive(PartialEq)] + pub struct InPubFn; + + #[derive(PartialEq)] + pub(crate) struct PubCrate; + + #[derive(PartialEq)] + pub(super) struct PubSuper; +} + +pub use _hidden::Reexported; +pub fn _from_mod() -> _hidden::InPubFn { + _hidden::InPubFn +} + +#[derive(PartialEq)] +struct InternalTy; fn main() {} diff --git a/tests/ui/derive_partial_eq_without_eq.stderr b/tests/ui/derive_partial_eq_without_eq.stderr index bf55165890a..794c5dab844 100644 --- a/tests/ui/derive_partial_eq_without_eq.stderr +++ b/tests/ui/derive_partial_eq_without_eq.stderr @@ -6,6 +6,12 @@ LL | #[derive(Debug, PartialEq)] | = note: `-D clippy::derive-partial-eq-without-eq` implied by `-D warnings` +error: you are deriving `PartialEq` and can implement `Eq` + --> $DIR/derive_partial_eq_without_eq.rs:55:10 + | +LL | #[derive(PartialEq)] + | ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq` + error: you are deriving `PartialEq` and can implement `Eq` --> $DIR/derive_partial_eq_without_eq.rs:61:10 | @@ -42,5 +48,23 @@ error: you are deriving `PartialEq` and can implement `Eq` LL | #[derive(Debug, PartialEq, Clone)] | ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq` -error: aborting due to 7 previous errors +error: you are deriving `PartialEq` and can implement `Eq` + --> $DIR/derive_partial_eq_without_eq.rs:98:10 + | +LL | #[derive(PartialEq)] + | ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq` + +error: you are deriving `PartialEq` and can implement `Eq` + --> $DIR/derive_partial_eq_without_eq.rs:105:14 + | +LL | #[derive(PartialEq)] + | ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq` + +error: you are deriving `PartialEq` and can implement `Eq` + --> $DIR/derive_partial_eq_without_eq.rs:108:14 + | +LL | #[derive(PartialEq)] + | ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq` + +error: aborting due to 11 previous errors diff --git a/tests/ui/eta.fixed b/tests/ui/eta.fixed index 6c2272f4dff..f8d559bf226 100644 --- a/tests/ui/eta.fixed +++ b/tests/ui/eta.fixed @@ -291,3 +291,15 @@ fn coerced_closure() { fn slice_fn(_: impl FnOnce() -> &'static [u8]) {} slice_fn(|| arr()); } + +// https://github.com/rust-lang/rust-clippy/issues/7861 +fn box_dyn() { + fn f(_: impl Fn(usize) -> Box) {} + f(|x| Box::new(x)); +} + +// https://github.com/rust-lang/rust-clippy/issues/5939 +fn not_general_enough() { + fn f(_: impl FnMut(&Path) -> std::io::Result<()>) {} + f(|path| std::fs::remove_file(path)); +} diff --git a/tests/ui/eta.rs b/tests/ui/eta.rs index a1a9c0dfbf3..f0fb55a1e5f 100644 --- a/tests/ui/eta.rs +++ b/tests/ui/eta.rs @@ -291,3 +291,15 @@ fn coerced_closure() { fn slice_fn(_: impl FnOnce() -> &'static [u8]) {} slice_fn(|| arr()); } + +// https://github.com/rust-lang/rust-clippy/issues/7861 +fn box_dyn() { + fn f(_: impl Fn(usize) -> Box) {} + f(|x| Box::new(x)); +} + +// https://github.com/rust-lang/rust-clippy/issues/5939 +fn not_general_enough() { + fn f(_: impl FnMut(&Path) -> std::io::Result<()>) {} + f(|path| std::fs::remove_file(path)); +} diff --git a/tests/ui/for_loops_over_fallibles.rs b/tests/ui/for_loops_over_fallibles.rs index 1b9dde87cd5..3390111d0a8 100644 --- a/tests/ui/for_loops_over_fallibles.rs +++ b/tests/ui/for_loops_over_fallibles.rs @@ -2,7 +2,7 @@ fn for_loops_over_fallibles() { let option = Some(1); - let result = option.ok_or("x not found"); + let mut result = option.ok_or("x not found"); let v = vec![0, 1, 2]; // check over an `Option` @@ -10,11 +10,26 @@ fn for_loops_over_fallibles() { println!("{}", x); } + // check over an `Option` + for x in option.iter() { + println!("{}", x); + } + // check over a `Result` for x in result { println!("{}", x); } + // check over a `Result` + for x in result.iter_mut() { + println!("{}", x); + } + + // check over a `Result` + for x in result.into_iter() { + println!("{}", x); + } + for x in option.ok_or("x not found") { println!("{}", x); } diff --git a/tests/ui/for_loops_over_fallibles.stderr b/tests/ui/for_loops_over_fallibles.stderr index 52b94875aec..8c8c022243a 100644 --- a/tests/ui/for_loops_over_fallibles.stderr +++ b/tests/ui/for_loops_over_fallibles.stderr @@ -7,16 +7,40 @@ LL | for x in option { = note: `-D clippy::for-loops-over-fallibles` implied by `-D warnings` = help: consider replacing `for x in option` with `if let Some(x) = option` -error: for loop over `result`, which is a `Result`. This is more readably written as an `if let` statement +error: for loop over `option`, which is an `Option`. This is more readably written as an `if let` statement --> $DIR/for_loops_over_fallibles.rs:14:14 | +LL | for x in option.iter() { + | ^^^^^^ + | + = help: consider replacing `for x in option.iter()` with `if let Some(x) = option` + +error: for loop over `result`, which is a `Result`. This is more readably written as an `if let` statement + --> $DIR/for_loops_over_fallibles.rs:19:14 + | LL | for x in result { | ^^^^^^ | = help: consider replacing `for x in result` with `if let Ok(x) = result` +error: for loop over `result`, which is a `Result`. This is more readably written as an `if let` statement + --> $DIR/for_loops_over_fallibles.rs:24:14 + | +LL | for x in result.iter_mut() { + | ^^^^^^ + | + = help: consider replacing `for x in result.iter_mut()` with `if let Ok(x) = result` + +error: for loop over `result`, which is a `Result`. This is more readably written as an `if let` statement + --> $DIR/for_loops_over_fallibles.rs:29:14 + | +LL | for x in result.into_iter() { + | ^^^^^^ + | + = help: consider replacing `for x in result.into_iter()` with `if let Ok(x) = result` + error: for loop over `option.ok_or("x not found")`, which is a `Result`. This is more readably written as an `if let` statement - --> $DIR/for_loops_over_fallibles.rs:18:14 + --> $DIR/for_loops_over_fallibles.rs:33:14 | LL | for x in option.ok_or("x not found") { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -24,7 +48,7 @@ LL | for x in option.ok_or("x not found") { = help: consider replacing `for x in option.ok_or("x not found")` with `if let Ok(x) = option.ok_or("x not found")` error: you are iterating over `Iterator::next()` which is an Option; this will compile but is probably not what you want - --> $DIR/for_loops_over_fallibles.rs:24:14 + --> $DIR/for_loops_over_fallibles.rs:39:14 | LL | for x in v.iter().next() { | ^^^^^^^^^^^^^^^ @@ -32,7 +56,7 @@ LL | for x in v.iter().next() { = note: `#[deny(clippy::iter_next_loop)]` on by default error: for loop over `v.iter().next().and(Some(0))`, which is an `Option`. This is more readably written as an `if let` statement - --> $DIR/for_loops_over_fallibles.rs:29:14 + --> $DIR/for_loops_over_fallibles.rs:44:14 | LL | for x in v.iter().next().and(Some(0)) { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -40,7 +64,7 @@ LL | for x in v.iter().next().and(Some(0)) { = help: consider replacing `for x in v.iter().next().and(Some(0))` with `if let Some(x) = v.iter().next().and(Some(0))` error: for loop over `v.iter().next().ok_or("x not found")`, which is a `Result`. This is more readably written as an `if let` statement - --> $DIR/for_loops_over_fallibles.rs:33:14 + --> $DIR/for_loops_over_fallibles.rs:48:14 | LL | for x in v.iter().next().ok_or("x not found") { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -48,7 +72,7 @@ LL | for x in v.iter().next().ok_or("x not found") { = help: consider replacing `for x in v.iter().next().ok_or("x not found")` with `if let Ok(x) = v.iter().next().ok_or("x not found")` error: this loop never actually loops - --> $DIR/for_loops_over_fallibles.rs:45:5 + --> $DIR/for_loops_over_fallibles.rs:60:5 | LL | / while let Some(x) = option { LL | | println!("{}", x); @@ -59,7 +83,7 @@ LL | | } = note: `#[deny(clippy::never_loop)]` on by default error: this loop never actually loops - --> $DIR/for_loops_over_fallibles.rs:51:5 + --> $DIR/for_loops_over_fallibles.rs:66:5 | LL | / while let Ok(x) = result { LL | | println!("{}", x); @@ -67,5 +91,5 @@ LL | | break; LL | | } | |_____^ -error: aborting due to 8 previous errors +error: aborting due to 11 previous errors diff --git a/tests/ui/iter_overeager_cloned.fixed b/tests/ui/iter_overeager_cloned.fixed index 7c2b05d837b..c100705d017 100644 --- a/tests/ui/iter_overeager_cloned.fixed +++ b/tests/ui/iter_overeager_cloned.fixed @@ -18,7 +18,8 @@ fn main() { let _ = vec.iter().filter(|x| x == &"2").nth(2).cloned(); let _ = [Some(Some("str".to_string())), Some(Some("str".to_string()))] - .iter().flatten().cloned(); + .iter() + .flatten().cloned(); // Not implemented yet let _ = vec.iter().cloned().filter(|x| x.starts_with('2')); @@ -43,6 +44,9 @@ fn main() { // Should probably stay as it is. let _ = [0, 1, 2, 3, 4].iter().cloned().take(10); + + // `&Range<_>` doesn't implement `IntoIterator` + let _ = [0..1, 2..5].iter().cloned().flatten(); } // #8527 diff --git a/tests/ui/iter_overeager_cloned.rs b/tests/ui/iter_overeager_cloned.rs index f2d0b155d2c..2caa8802066 100644 --- a/tests/ui/iter_overeager_cloned.rs +++ b/tests/ui/iter_overeager_cloned.rs @@ -45,6 +45,9 @@ fn main() { // Should probably stay as it is. let _ = [0, 1, 2, 3, 4].iter().cloned().take(10); + + // `&Range<_>` doesn't implement `IntoIterator` + let _ = [0..1, 2..5].iter().cloned().flatten(); } // #8527 diff --git a/tests/ui/iter_overeager_cloned.stderr b/tests/ui/iter_overeager_cloned.stderr index 0582700fd16..dcae7cecd33 100644 --- a/tests/ui/iter_overeager_cloned.stderr +++ b/tests/ui/iter_overeager_cloned.stderr @@ -1,44 +1,56 @@ -error: called `cloned().last()` on an `Iterator`. It may be more efficient to call `last().cloned()` instead +error: unnecessarily eager cloning of iterator items --> $DIR/iter_overeager_cloned.rs:8:29 | LL | let _: Option = vec.iter().cloned().last(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `vec.iter().last().cloned()` + | ^^^^^^^^^^---------------- + | | + | help: try this: `.last().cloned()` | = note: `-D clippy::iter-overeager-cloned` implied by `-D warnings` -error: called `cloned().next()` on an `Iterator`. It may be more efficient to call `next().cloned()` instead +error: unnecessarily eager cloning of iterator items --> $DIR/iter_overeager_cloned.rs:10:29 | LL | let _: Option = vec.iter().chain(vec.iter()).cloned().next(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `vec.iter().chain(vec.iter()).next().cloned()` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^---------------- + | | + | help: try this: `.next().cloned()` -error: called `cloned().count()` on an `Iterator`. It may be more efficient to call `count()` instead +error: unneeded cloning of iterator items --> $DIR/iter_overeager_cloned.rs:12:20 | LL | let _: usize = vec.iter().filter(|x| x == &"2").cloned().count(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `vec.iter().filter(|x| x == &"2").count()` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^----------------- + | | + | help: try this: `.count()` | = note: `-D clippy::redundant-clone` implied by `-D warnings` -error: called `cloned().take(...)` on an `Iterator`. It may be more efficient to call `take(...).cloned()` instead +error: unnecessarily eager cloning of iterator items --> $DIR/iter_overeager_cloned.rs:14:21 | LL | let _: Vec<_> = vec.iter().cloned().take(2).collect(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `vec.iter().take(2).cloned()` + | ^^^^^^^^^^----------------- + | | + | help: try this: `.take(2).cloned()` -error: called `cloned().skip(...)` on an `Iterator`. It may be more efficient to call `skip(...).cloned()` instead +error: unnecessarily eager cloning of iterator items --> $DIR/iter_overeager_cloned.rs:16:21 | LL | let _: Vec<_> = vec.iter().cloned().skip(2).collect(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `vec.iter().skip(2).cloned()` + | ^^^^^^^^^^----------------- + | | + | help: try this: `.skip(2).cloned()` -error: called `cloned().nth(...)` on an `Iterator`. It may be more efficient to call `nth(...).cloned()` instead +error: unnecessarily eager cloning of iterator items --> $DIR/iter_overeager_cloned.rs:18:13 | LL | let _ = vec.iter().filter(|x| x == &"2").cloned().nth(2); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try this: `vec.iter().filter(|x| x == &"2").nth(2).cloned()` + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^---------------- + | | + | help: try this: `.nth(2).cloned()` -error: called `cloned().flatten()` on an `Iterator`. It may be more efficient to call `flatten().cloned()` instead +error: unnecessarily eager cloning of iterator items --> $DIR/iter_overeager_cloned.rs:20:13 | LL | let _ = [Some(Some("str".to_string())), Some(Some("str".to_string()))] @@ -50,8 +62,8 @@ LL | | .flatten(); | help: try this | -LL ~ let _ = [Some(Some("str".to_string())), Some(Some("str".to_string()))] -LL ~ .iter().flatten().cloned(); +LL ~ .iter() +LL ~ .flatten().cloned(); | error: aborting due to 7 previous errors diff --git a/tests/ui/manual_filter_map.fixed b/tests/ui/manual_filter_map.fixed index fc8f58f8ea5..de0d8614889 100644 --- a/tests/ui/manual_filter_map.fixed +++ b/tests/ui/manual_filter_map.fixed @@ -35,3 +35,53 @@ fn to_opt(_: T) -> Option { fn to_res(_: T) -> Result { unimplemented!() } + +struct Issue8920<'a> { + option_field: Option, + result_field: Result, + ref_field: Option<&'a usize>, +} + +fn issue_8920() { + let mut vec = vec![Issue8920 { + option_field: Some(String::from("str")), + result_field: Ok(String::from("str")), + ref_field: Some(&1), + }]; + + let _ = vec + .iter() + .filter_map(|f| f.option_field.clone()); + + let _ = vec + .iter() + .filter_map(|f| f.ref_field.cloned()); + + let _ = vec + .iter() + .filter_map(|f| f.ref_field.copied()); + + let _ = vec + .iter() + .filter_map(|f| f.result_field.clone().ok()); + + let _ = vec + .iter() + .filter_map(|f| f.result_field.as_ref().ok()); + + let _ = vec + .iter() + .filter_map(|f| f.result_field.as_deref().ok()); + + let _ = vec + .iter_mut() + .filter_map(|f| f.result_field.as_mut().ok()); + + let _ = vec + .iter_mut() + .filter_map(|f| f.result_field.as_deref_mut().ok()); + + let _ = vec + .iter() + .filter_map(|f| f.result_field.to_owned().ok()); +} diff --git a/tests/ui/manual_filter_map.rs b/tests/ui/manual_filter_map.rs index 3af4bbee3bf..bd6516f038b 100644 --- a/tests/ui/manual_filter_map.rs +++ b/tests/ui/manual_filter_map.rs @@ -35,3 +35,62 @@ fn to_opt(_: T) -> Option { fn to_res(_: T) -> Result { unimplemented!() } + +struct Issue8920<'a> { + option_field: Option, + result_field: Result, + ref_field: Option<&'a usize>, +} + +fn issue_8920() { + let mut vec = vec![Issue8920 { + option_field: Some(String::from("str")), + result_field: Ok(String::from("str")), + ref_field: Some(&1), + }]; + + let _ = vec + .iter() + .filter(|f| f.option_field.is_some()) + .map(|f| f.option_field.clone().unwrap()); + + let _ = vec + .iter() + .filter(|f| f.ref_field.is_some()) + .map(|f| f.ref_field.cloned().unwrap()); + + let _ = vec + .iter() + .filter(|f| f.ref_field.is_some()) + .map(|f| f.ref_field.copied().unwrap()); + + let _ = vec + .iter() + .filter(|f| f.result_field.is_ok()) + .map(|f| f.result_field.clone().unwrap()); + + let _ = vec + .iter() + .filter(|f| f.result_field.is_ok()) + .map(|f| f.result_field.as_ref().unwrap()); + + let _ = vec + .iter() + .filter(|f| f.result_field.is_ok()) + .map(|f| f.result_field.as_deref().unwrap()); + + let _ = vec + .iter_mut() + .filter(|f| f.result_field.is_ok()) + .map(|f| f.result_field.as_mut().unwrap()); + + let _ = vec + .iter_mut() + .filter(|f| f.result_field.is_ok()) + .map(|f| f.result_field.as_deref_mut().unwrap()); + + let _ = vec + .iter() + .filter(|f| f.result_field.is_ok()) + .map(|f| f.result_field.to_owned().unwrap()); +} diff --git a/tests/ui/manual_filter_map.stderr b/tests/ui/manual_filter_map.stderr index 4d4e2d5c12f..465f1b19110 100644 --- a/tests/ui/manual_filter_map.stderr +++ b/tests/ui/manual_filter_map.stderr @@ -18,5 +18,77 @@ error: `filter(..).map(..)` can be simplified as `filter_map(..)` LL | let _ = (0..).filter(|&n| to_res(n).is_ok()).map(|a| to_res(a).unwrap_or(1)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `filter_map(|a| to_res(a).ok())` -error: aborting due to 3 previous errors +error: `filter(..).map(..)` can be simplified as `filter_map(..)` + --> $DIR/manual_filter_map.rs:54:10 + | +LL | .filter(|f| f.option_field.is_some()) + | __________^ +LL | | .map(|f| f.option_field.clone().unwrap()); + | |_________________________________________________^ help: try: `filter_map(|f| f.option_field.clone())` + +error: `filter(..).map(..)` can be simplified as `filter_map(..)` + --> $DIR/manual_filter_map.rs:59:10 + | +LL | .filter(|f| f.ref_field.is_some()) + | __________^ +LL | | .map(|f| f.ref_field.cloned().unwrap()); + | |_______________________________________________^ help: try: `filter_map(|f| f.ref_field.cloned())` + +error: `filter(..).map(..)` can be simplified as `filter_map(..)` + --> $DIR/manual_filter_map.rs:64:10 + | +LL | .filter(|f| f.ref_field.is_some()) + | __________^ +LL | | .map(|f| f.ref_field.copied().unwrap()); + | |_______________________________________________^ help: try: `filter_map(|f| f.ref_field.copied())` + +error: `filter(..).map(..)` can be simplified as `filter_map(..)` + --> $DIR/manual_filter_map.rs:69:10 + | +LL | .filter(|f| f.result_field.is_ok()) + | __________^ +LL | | .map(|f| f.result_field.clone().unwrap()); + | |_________________________________________________^ help: try: `filter_map(|f| f.result_field.clone().ok())` + +error: `filter(..).map(..)` can be simplified as `filter_map(..)` + --> $DIR/manual_filter_map.rs:74:10 + | +LL | .filter(|f| f.result_field.is_ok()) + | __________^ +LL | | .map(|f| f.result_field.as_ref().unwrap()); + | |__________________________________________________^ help: try: `filter_map(|f| f.result_field.as_ref().ok())` + +error: `filter(..).map(..)` can be simplified as `filter_map(..)` + --> $DIR/manual_filter_map.rs:79:10 + | +LL | .filter(|f| f.result_field.is_ok()) + | __________^ +LL | | .map(|f| f.result_field.as_deref().unwrap()); + | |____________________________________________________^ help: try: `filter_map(|f| f.result_field.as_deref().ok())` + +error: `filter(..).map(..)` can be simplified as `filter_map(..)` + --> $DIR/manual_filter_map.rs:84:10 + | +LL | .filter(|f| f.result_field.is_ok()) + | __________^ +LL | | .map(|f| f.result_field.as_mut().unwrap()); + | |__________________________________________________^ help: try: `filter_map(|f| f.result_field.as_mut().ok())` + +error: `filter(..).map(..)` can be simplified as `filter_map(..)` + --> $DIR/manual_filter_map.rs:89:10 + | +LL | .filter(|f| f.result_field.is_ok()) + | __________^ +LL | | .map(|f| f.result_field.as_deref_mut().unwrap()); + | |________________________________________________________^ help: try: `filter_map(|f| f.result_field.as_deref_mut().ok())` + +error: `filter(..).map(..)` can be simplified as `filter_map(..)` + --> $DIR/manual_filter_map.rs:94:10 + | +LL | .filter(|f| f.result_field.is_ok()) + | __________^ +LL | | .map(|f| f.result_field.to_owned().unwrap()); + | |____________________________________________________^ help: try: `filter_map(|f| f.result_field.to_owned().ok())` + +error: aborting due to 12 previous errors diff --git a/tests/ui/manual_find_map.fixed b/tests/ui/manual_find_map.fixed index 95e97c4fd1f..d69b6c1dcf3 100644 --- a/tests/ui/manual_find_map.fixed +++ b/tests/ui/manual_find_map.fixed @@ -35,3 +35,53 @@ fn to_opt(_: T) -> Option { fn to_res(_: T) -> Result { unimplemented!() } + +struct Issue8920<'a> { + option_field: Option, + result_field: Result, + ref_field: Option<&'a usize>, +} + +fn issue_8920() { + let mut vec = vec![Issue8920 { + option_field: Some(String::from("str")), + result_field: Ok(String::from("str")), + ref_field: Some(&1), + }]; + + let _ = vec + .iter() + .find_map(|f| f.option_field.clone()); + + let _ = vec + .iter() + .find_map(|f| f.ref_field.cloned()); + + let _ = vec + .iter() + .find_map(|f| f.ref_field.copied()); + + let _ = vec + .iter() + .find_map(|f| f.result_field.clone().ok()); + + let _ = vec + .iter() + .find_map(|f| f.result_field.as_ref().ok()); + + let _ = vec + .iter() + .find_map(|f| f.result_field.as_deref().ok()); + + let _ = vec + .iter_mut() + .find_map(|f| f.result_field.as_mut().ok()); + + let _ = vec + .iter_mut() + .find_map(|f| f.result_field.as_deref_mut().ok()); + + let _ = vec + .iter() + .find_map(|f| f.result_field.to_owned().ok()); +} diff --git a/tests/ui/manual_find_map.rs b/tests/ui/manual_find_map.rs index cd3c82e3b25..1c4e18e31c8 100644 --- a/tests/ui/manual_find_map.rs +++ b/tests/ui/manual_find_map.rs @@ -35,3 +35,62 @@ fn to_opt(_: T) -> Option { fn to_res(_: T) -> Result { unimplemented!() } + +struct Issue8920<'a> { + option_field: Option, + result_field: Result, + ref_field: Option<&'a usize>, +} + +fn issue_8920() { + let mut vec = vec![Issue8920 { + option_field: Some(String::from("str")), + result_field: Ok(String::from("str")), + ref_field: Some(&1), + }]; + + let _ = vec + .iter() + .find(|f| f.option_field.is_some()) + .map(|f| f.option_field.clone().unwrap()); + + let _ = vec + .iter() + .find(|f| f.ref_field.is_some()) + .map(|f| f.ref_field.cloned().unwrap()); + + let _ = vec + .iter() + .find(|f| f.ref_field.is_some()) + .map(|f| f.ref_field.copied().unwrap()); + + let _ = vec + .iter() + .find(|f| f.result_field.is_ok()) + .map(|f| f.result_field.clone().unwrap()); + + let _ = vec + .iter() + .find(|f| f.result_field.is_ok()) + .map(|f| f.result_field.as_ref().unwrap()); + + let _ = vec + .iter() + .find(|f| f.result_field.is_ok()) + .map(|f| f.result_field.as_deref().unwrap()); + + let _ = vec + .iter_mut() + .find(|f| f.result_field.is_ok()) + .map(|f| f.result_field.as_mut().unwrap()); + + let _ = vec + .iter_mut() + .find(|f| f.result_field.is_ok()) + .map(|f| f.result_field.as_deref_mut().unwrap()); + + let _ = vec + .iter() + .find(|f| f.result_field.is_ok()) + .map(|f| f.result_field.to_owned().unwrap()); +} diff --git a/tests/ui/manual_find_map.stderr b/tests/ui/manual_find_map.stderr index 9e7f798df45..9dea42b7686 100644 --- a/tests/ui/manual_find_map.stderr +++ b/tests/ui/manual_find_map.stderr @@ -18,5 +18,77 @@ error: `find(..).map(..)` can be simplified as `find_map(..)` LL | let _ = (0..).find(|&n| to_res(n).is_ok()).map(|a| to_res(a).unwrap_or(1)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `find_map(|a| to_res(a).ok())` -error: aborting due to 3 previous errors +error: `find(..).map(..)` can be simplified as `find_map(..)` + --> $DIR/manual_find_map.rs:54:10 + | +LL | .find(|f| f.option_field.is_some()) + | __________^ +LL | | .map(|f| f.option_field.clone().unwrap()); + | |_________________________________________________^ help: try: `find_map(|f| f.option_field.clone())` + +error: `find(..).map(..)` can be simplified as `find_map(..)` + --> $DIR/manual_find_map.rs:59:10 + | +LL | .find(|f| f.ref_field.is_some()) + | __________^ +LL | | .map(|f| f.ref_field.cloned().unwrap()); + | |_______________________________________________^ help: try: `find_map(|f| f.ref_field.cloned())` + +error: `find(..).map(..)` can be simplified as `find_map(..)` + --> $DIR/manual_find_map.rs:64:10 + | +LL | .find(|f| f.ref_field.is_some()) + | __________^ +LL | | .map(|f| f.ref_field.copied().unwrap()); + | |_______________________________________________^ help: try: `find_map(|f| f.ref_field.copied())` + +error: `find(..).map(..)` can be simplified as `find_map(..)` + --> $DIR/manual_find_map.rs:69:10 + | +LL | .find(|f| f.result_field.is_ok()) + | __________^ +LL | | .map(|f| f.result_field.clone().unwrap()); + | |_________________________________________________^ help: try: `find_map(|f| f.result_field.clone().ok())` + +error: `find(..).map(..)` can be simplified as `find_map(..)` + --> $DIR/manual_find_map.rs:74:10 + | +LL | .find(|f| f.result_field.is_ok()) + | __________^ +LL | | .map(|f| f.result_field.as_ref().unwrap()); + | |__________________________________________________^ help: try: `find_map(|f| f.result_field.as_ref().ok())` + +error: `find(..).map(..)` can be simplified as `find_map(..)` + --> $DIR/manual_find_map.rs:79:10 + | +LL | .find(|f| f.result_field.is_ok()) + | __________^ +LL | | .map(|f| f.result_field.as_deref().unwrap()); + | |____________________________________________________^ help: try: `find_map(|f| f.result_field.as_deref().ok())` + +error: `find(..).map(..)` can be simplified as `find_map(..)` + --> $DIR/manual_find_map.rs:84:10 + | +LL | .find(|f| f.result_field.is_ok()) + | __________^ +LL | | .map(|f| f.result_field.as_mut().unwrap()); + | |__________________________________________________^ help: try: `find_map(|f| f.result_field.as_mut().ok())` + +error: `find(..).map(..)` can be simplified as `find_map(..)` + --> $DIR/manual_find_map.rs:89:10 + | +LL | .find(|f| f.result_field.is_ok()) + | __________^ +LL | | .map(|f| f.result_field.as_deref_mut().unwrap()); + | |________________________________________________________^ help: try: `find_map(|f| f.result_field.as_deref_mut().ok())` + +error: `find(..).map(..)` can be simplified as `find_map(..)` + --> $DIR/manual_find_map.rs:94:10 + | +LL | .find(|f| f.result_field.is_ok()) + | __________^ +LL | | .map(|f| f.result_field.to_owned().unwrap()); + | |____________________________________________________^ help: try: `find_map(|f| f.result_field.to_owned().ok())` + +error: aborting due to 12 previous errors diff --git a/tests/ui/manual_map_option.fixed b/tests/ui/manual_map_option.fixed index fc6a7abca0e..a59da4ae10b 100644 --- a/tests/ui/manual_map_option.fixed +++ b/tests/ui/manual_map_option.fixed @@ -7,6 +7,7 @@ clippy::unit_arg, clippy::match_ref_pats, clippy::redundant_pattern_matching, + clippy::for_loops_over_fallibles, dead_code )] diff --git a/tests/ui/manual_map_option.rs b/tests/ui/manual_map_option.rs index 16508270f64..0bdbefa51e8 100644 --- a/tests/ui/manual_map_option.rs +++ b/tests/ui/manual_map_option.rs @@ -7,6 +7,7 @@ clippy::unit_arg, clippy::match_ref_pats, clippy::redundant_pattern_matching, + clippy::for_loops_over_fallibles, dead_code )] diff --git a/tests/ui/manual_map_option.stderr b/tests/ui/manual_map_option.stderr index 0036b8151de..cdc2c0e62a9 100644 --- a/tests/ui/manual_map_option.stderr +++ b/tests/ui/manual_map_option.stderr @@ -1,5 +1,5 @@ error: manual implementation of `Option::map` - --> $DIR/manual_map_option.rs:14:5 + --> $DIR/manual_map_option.rs:15:5 | LL | / match Some(0) { LL | | Some(_) => Some(2), @@ -10,7 +10,7 @@ LL | | }; = note: `-D clippy::manual-map` implied by `-D warnings` error: manual implementation of `Option::map` - --> $DIR/manual_map_option.rs:19:5 + --> $DIR/manual_map_option.rs:20:5 | LL | / match Some(0) { LL | | Some(x) => Some(x + 1), @@ -19,7 +19,7 @@ LL | | }; | |_____^ help: try this: `Some(0).map(|x| x + 1)` error: manual implementation of `Option::map` - --> $DIR/manual_map_option.rs:24:5 + --> $DIR/manual_map_option.rs:25:5 | LL | / match Some("") { LL | | Some(x) => Some(x.is_empty()), @@ -28,7 +28,7 @@ LL | | }; | |_____^ help: try this: `Some("").map(|x| x.is_empty())` error: manual implementation of `Option::map` - --> $DIR/manual_map_option.rs:29:5 + --> $DIR/manual_map_option.rs:30:5 | LL | / if let Some(x) = Some(0) { LL | | Some(!x) @@ -38,7 +38,7 @@ LL | | }; | |_____^ help: try this: `Some(0).map(|x| !x)` error: manual implementation of `Option::map` - --> $DIR/manual_map_option.rs:36:5 + --> $DIR/manual_map_option.rs:37:5 | LL | / match Some(0) { LL | | Some(x) => { Some(std::convert::identity(x)) } @@ -47,7 +47,7 @@ LL | | }; | |_____^ help: try this: `Some(0).map(std::convert::identity)` error: manual implementation of `Option::map` - --> $DIR/manual_map_option.rs:41:5 + --> $DIR/manual_map_option.rs:42:5 | LL | / match Some(&String::new()) { LL | | Some(x) => Some(str::len(x)), @@ -56,7 +56,7 @@ LL | | }; | |_____^ help: try this: `Some(&String::new()).map(|x| str::len(x))` error: manual implementation of `Option::map` - --> $DIR/manual_map_option.rs:51:5 + --> $DIR/manual_map_option.rs:52:5 | LL | / match &Some([0, 1]) { LL | | Some(x) => Some(x[0]), @@ -65,7 +65,7 @@ LL | | }; | |_____^ help: try this: `Some([0, 1]).as_ref().map(|x| x[0])` error: manual implementation of `Option::map` - --> $DIR/manual_map_option.rs:56:5 + --> $DIR/manual_map_option.rs:57:5 | LL | / match &Some(0) { LL | | &Some(x) => Some(x * 2), @@ -74,7 +74,7 @@ LL | | }; | |_____^ help: try this: `Some(0).map(|x| x * 2)` error: manual implementation of `Option::map` - --> $DIR/manual_map_option.rs:61:5 + --> $DIR/manual_map_option.rs:62:5 | LL | / match Some(String::new()) { LL | | Some(ref x) => Some(x.is_empty()), @@ -83,7 +83,7 @@ LL | | }; | |_____^ help: try this: `Some(String::new()).as_ref().map(|x| x.is_empty())` error: manual implementation of `Option::map` - --> $DIR/manual_map_option.rs:66:5 + --> $DIR/manual_map_option.rs:67:5 | LL | / match &&Some(String::new()) { LL | | Some(x) => Some(x.len()), @@ -92,7 +92,7 @@ LL | | }; | |_____^ help: try this: `Some(String::new()).as_ref().map(|x| x.len())` error: manual implementation of `Option::map` - --> $DIR/manual_map_option.rs:71:5 + --> $DIR/manual_map_option.rs:72:5 | LL | / match &&Some(0) { LL | | &&Some(x) => Some(x + x), @@ -101,7 +101,7 @@ LL | | }; | |_____^ help: try this: `Some(0).map(|x| x + x)` error: manual implementation of `Option::map` - --> $DIR/manual_map_option.rs:84:9 + --> $DIR/manual_map_option.rs:85:9 | LL | / match &mut Some(String::new()) { LL | | Some(x) => Some(x.push_str("")), @@ -110,7 +110,7 @@ LL | | }; | |_________^ help: try this: `Some(String::new()).as_mut().map(|x| x.push_str(""))` error: manual implementation of `Option::map` - --> $DIR/manual_map_option.rs:90:5 + --> $DIR/manual_map_option.rs:91:5 | LL | / match &mut Some(String::new()) { LL | | Some(ref x) => Some(x.len()), @@ -119,7 +119,7 @@ LL | | }; | |_____^ help: try this: `Some(String::new()).as_ref().map(|x| x.len())` error: manual implementation of `Option::map` - --> $DIR/manual_map_option.rs:95:5 + --> $DIR/manual_map_option.rs:96:5 | LL | / match &mut &Some(String::new()) { LL | | Some(x) => Some(x.is_empty()), @@ -128,7 +128,7 @@ LL | | }; | |_____^ help: try this: `Some(String::new()).as_ref().map(|x| x.is_empty())` error: manual implementation of `Option::map` - --> $DIR/manual_map_option.rs:100:5 + --> $DIR/manual_map_option.rs:101:5 | LL | / match Some((0, 1, 2)) { LL | | Some((x, y, z)) => Some(x + y + z), @@ -137,7 +137,7 @@ LL | | }; | |_____^ help: try this: `Some((0, 1, 2)).map(|(x, y, z)| x + y + z)` error: manual implementation of `Option::map` - --> $DIR/manual_map_option.rs:105:5 + --> $DIR/manual_map_option.rs:106:5 | LL | / match Some([1, 2, 3]) { LL | | Some([first, ..]) => Some(first), @@ -146,7 +146,7 @@ LL | | }; | |_____^ help: try this: `Some([1, 2, 3]).map(|[first, ..]| first)` error: manual implementation of `Option::map` - --> $DIR/manual_map_option.rs:110:5 + --> $DIR/manual_map_option.rs:111:5 | LL | / match &Some((String::new(), "test")) { LL | | Some((x, y)) => Some((y, x)), @@ -155,7 +155,7 @@ LL | | }; | |_____^ help: try this: `Some((String::new(), "test")).as_ref().map(|(x, y)| (y, x))` error: manual implementation of `Option::map` - --> $DIR/manual_map_option.rs:168:5 + --> $DIR/manual_map_option.rs:169:5 | LL | / match Some(0) { LL | | Some(x) => Some(vec![x]), @@ -164,7 +164,7 @@ LL | | }; | |_____^ help: try this: `Some(0).map(|x| vec![x])` error: manual implementation of `Option::map` - --> $DIR/manual_map_option.rs:173:5 + --> $DIR/manual_map_option.rs:174:5 | LL | / match option_env!("") { LL | | Some(x) => Some(String::from(x)), @@ -173,7 +173,7 @@ LL | | }; | |_____^ help: try this: `option_env!("").map(String::from)` error: manual implementation of `Option::map` - --> $DIR/manual_map_option.rs:193:12 + --> $DIR/manual_map_option.rs:194:12 | LL | } else if let Some(x) = Some(0) { | ____________^ @@ -184,7 +184,7 @@ LL | | }; | |_____^ help: try this: `{ Some(0).map(|x| x + 1) }` error: manual implementation of `Option::map` - --> $DIR/manual_map_option.rs:201:12 + --> $DIR/manual_map_option.rs:202:12 | LL | } else if let Some(x) = Some(0) { | ____________^ diff --git a/tests/ui/needless_borrow.fixed b/tests/ui/needless_borrow.fixed index efeb5cf5b2b..e7a483c0582 100644 --- a/tests/ui/needless_borrow.fixed +++ b/tests/ui/needless_borrow.fixed @@ -1,5 +1,7 @@ // run-rustfix +#![feature(lint_reasons)] + #[warn(clippy::all, clippy::needless_borrow)] #[allow(unused_variables, clippy::unnecessary_mut_passed)] fn main() { @@ -96,3 +98,10 @@ trait Trait {} impl<'a> Trait for &'a str {} fn h(_: &dyn Trait) {} + +#[allow(dead_code)] +fn check_expect_suppression() { + let a = 5; + #[expect(clippy::needless_borrow)] + let _ = x(&&a); +} diff --git a/tests/ui/needless_borrow.rs b/tests/ui/needless_borrow.rs index 3e416a0eb84..1d6bf46405a 100644 --- a/tests/ui/needless_borrow.rs +++ b/tests/ui/needless_borrow.rs @@ -1,5 +1,7 @@ // run-rustfix +#![feature(lint_reasons)] + #[warn(clippy::all, clippy::needless_borrow)] #[allow(unused_variables, clippy::unnecessary_mut_passed)] fn main() { @@ -96,3 +98,10 @@ trait Trait {} impl<'a> Trait for &'a str {} fn h(_: &dyn Trait) {} + +#[allow(dead_code)] +fn check_expect_suppression() { + let a = 5; + #[expect(clippy::needless_borrow)] + let _ = x(&&a); +} diff --git a/tests/ui/needless_borrow.stderr b/tests/ui/needless_borrow.stderr index 05591ce4117..be59d8f546d 100644 --- a/tests/ui/needless_borrow.stderr +++ b/tests/ui/needless_borrow.stderr @@ -1,5 +1,5 @@ error: this expression creates a reference which is immediately dereferenced by the compiler - --> $DIR/needless_borrow.rs:9:15 + --> $DIR/needless_borrow.rs:11:15 | LL | let _ = x(&&a); // warn | ^^^ help: change this to: `&a` @@ -7,91 +7,91 @@ LL | let _ = x(&&a); // warn = note: `-D clippy::needless-borrow` implied by `-D warnings` error: this expression creates a reference which is immediately dereferenced by the compiler - --> $DIR/needless_borrow.rs:13:13 + --> $DIR/needless_borrow.rs:15:13 | LL | mut_ref(&mut &mut b); // warn | ^^^^^^^^^^^ help: change this to: `&mut b` error: this expression creates a reference which is immediately dereferenced by the compiler - --> $DIR/needless_borrow.rs:25:13 + --> $DIR/needless_borrow.rs:27:13 | LL | &&a | ^^^ help: change this to: `&a` error: this expression creates a reference which is immediately dereferenced by the compiler - --> $DIR/needless_borrow.rs:27:15 + --> $DIR/needless_borrow.rs:29:15 | LL | 46 => &&a, | ^^^ help: change this to: `&a` error: this expression creates a reference which is immediately dereferenced by the compiler - --> $DIR/needless_borrow.rs:33:27 + --> $DIR/needless_borrow.rs:35:27 | LL | break &ref_a; | ^^^^^^ help: change this to: `ref_a` error: this expression creates a reference which is immediately dereferenced by the compiler - --> $DIR/needless_borrow.rs:40:15 + --> $DIR/needless_borrow.rs:42:15 | LL | let _ = x(&&&a); | ^^^^ help: change this to: `&a` error: this expression creates a reference which is immediately dereferenced by the compiler - --> $DIR/needless_borrow.rs:41:15 + --> $DIR/needless_borrow.rs:43:15 | LL | let _ = x(&mut &&a); | ^^^^^^^^ help: change this to: `&a` error: this expression creates a reference which is immediately dereferenced by the compiler - --> $DIR/needless_borrow.rs:42:15 + --> $DIR/needless_borrow.rs:44:15 | LL | let _ = x(&&&mut b); | ^^^^^^^^ help: change this to: `&mut b` error: this expression creates a reference which is immediately dereferenced by the compiler - --> $DIR/needless_borrow.rs:43:15 + --> $DIR/needless_borrow.rs:45:15 | LL | let _ = x(&&ref_a); | ^^^^^^^ help: change this to: `ref_a` error: this expression creates a reference which is immediately dereferenced by the compiler - --> $DIR/needless_borrow.rs:46:11 + --> $DIR/needless_borrow.rs:48:11 | LL | x(&b); | ^^ help: change this to: `b` error: this expression creates a reference which is immediately dereferenced by the compiler - --> $DIR/needless_borrow.rs:53:13 + --> $DIR/needless_borrow.rs:55:13 | LL | mut_ref(&mut x); | ^^^^^^ help: change this to: `x` error: this expression creates a reference which is immediately dereferenced by the compiler - --> $DIR/needless_borrow.rs:54:13 + --> $DIR/needless_borrow.rs:56:13 | LL | mut_ref(&mut &mut x); | ^^^^^^^^^^^ help: change this to: `x` error: this expression creates a reference which is immediately dereferenced by the compiler - --> $DIR/needless_borrow.rs:55:23 + --> $DIR/needless_borrow.rs:57:23 | LL | let y: &mut i32 = &mut x; | ^^^^^^ help: change this to: `x` error: this expression creates a reference which is immediately dereferenced by the compiler - --> $DIR/needless_borrow.rs:56:23 + --> $DIR/needless_borrow.rs:58:23 | LL | let y: &mut i32 = &mut &mut x; | ^^^^^^^^^^^ help: change this to: `x` error: this expression borrows a value the compiler would automatically borrow - --> $DIR/needless_borrow.rs:72:13 + --> $DIR/needless_borrow.rs:74:13 | LL | let _ = (&x).0; | ^^^^ help: change this to: `x` error: this expression borrows a value the compiler would automatically borrow - --> $DIR/needless_borrow.rs:74:22 + --> $DIR/needless_borrow.rs:76:22 | LL | let _ = unsafe { (&*x).0 }; | ^^^^^ help: change this to: `(*x)` diff --git a/tests/ui/needless_parens_on_range_literals.fixed b/tests/ui/needless_parens_on_range_literals.fixed new file mode 100644 index 00000000000..1bd75c806bc --- /dev/null +++ b/tests/ui/needless_parens_on_range_literals.fixed @@ -0,0 +1,14 @@ +// run-rustfix +// edition:2018 + +#![warn(clippy::needless_parens_on_range_literals)] +#![allow(clippy::almost_complete_letter_range)] + +fn main() { + let _ = 'a'..='z'; + let _ = 'a'..'z'; + let _ = (1.)..2.; + let _ = (1.)..2.; + let _ = 'a'..; + let _ = ..'z'; +} diff --git a/tests/ui/needless_parens_on_range_literals.rs b/tests/ui/needless_parens_on_range_literals.rs new file mode 100644 index 00000000000..7abb8a1adc1 --- /dev/null +++ b/tests/ui/needless_parens_on_range_literals.rs @@ -0,0 +1,14 @@ +// run-rustfix +// edition:2018 + +#![warn(clippy::needless_parens_on_range_literals)] +#![allow(clippy::almost_complete_letter_range)] + +fn main() { + let _ = ('a')..=('z'); + let _ = 'a'..('z'); + let _ = (1.)..2.; + let _ = (1.)..(2.); + let _ = ('a')..; + let _ = ..('z'); +} diff --git a/tests/ui/needless_parens_on_range_literals.stderr b/tests/ui/needless_parens_on_range_literals.stderr new file mode 100644 index 00000000000..505f7ac916d --- /dev/null +++ b/tests/ui/needless_parens_on_range_literals.stderr @@ -0,0 +1,40 @@ +error: needless parenthesis on range literals can be removed + --> $DIR/needless_parens_on_range_literals.rs:8:13 + | +LL | let _ = ('a')..=('z'); + | ^^^^^ help: try: `'a'` + | + = note: `-D clippy::needless-parens-on-range-literals` implied by `-D warnings` + +error: needless parenthesis on range literals can be removed + --> $DIR/needless_parens_on_range_literals.rs:8:21 + | +LL | let _ = ('a')..=('z'); + | ^^^^^ help: try: `'z'` + +error: needless parenthesis on range literals can be removed + --> $DIR/needless_parens_on_range_literals.rs:9:18 + | +LL | let _ = 'a'..('z'); + | ^^^^^ help: try: `'z'` + +error: needless parenthesis on range literals can be removed + --> $DIR/needless_parens_on_range_literals.rs:11:19 + | +LL | let _ = (1.)..(2.); + | ^^^^ help: try: `2.` + +error: needless parenthesis on range literals can be removed + --> $DIR/needless_parens_on_range_literals.rs:12:13 + | +LL | let _ = ('a')..; + | ^^^^^ help: try: `'a'` + +error: needless parenthesis on range literals can be removed + --> $DIR/needless_parens_on_range_literals.rs:13:15 + | +LL | let _ = ..('z'); + | ^^^^^ help: try: `'z'` + +error: aborting due to 6 previous errors + diff --git a/tests/ui/never_loop.rs b/tests/ui/never_loop.rs index 2770eb2b2ab..0a21589dd0d 100644 --- a/tests/ui/never_loop.rs +++ b/tests/ui/never_loop.rs @@ -186,6 +186,23 @@ pub fn test16() { } } +// Issue #9001: `continue` in struct expression fields +pub fn test17() { + struct Foo { + f: (), + } + + let mut n = 0; + let _ = loop { + break Foo { + f: if n < 5 { + n += 1; + continue; + }, + }; + }; +} + fn main() { test1(); test2(); diff --git a/tests/ui/read_zero_byte_vec.rs b/tests/ui/read_zero_byte_vec.rs new file mode 100644 index 00000000000..30807e0f8b9 --- /dev/null +++ b/tests/ui/read_zero_byte_vec.rs @@ -0,0 +1,87 @@ +#![warn(clippy::read_zero_byte_vec)] +#![allow(clippy::unused_io_amount)] +use std::fs::File; +use std::io; +use std::io::prelude::*; + +extern crate futures; +use futures::io::{AsyncRead, AsyncReadExt}; +use tokio::io::{AsyncRead as TokioAsyncRead, AsyncReadExt as _, AsyncWrite as TokioAsyncWrite, AsyncWriteExt as _}; + +fn test() -> io::Result<()> { + let cap = 1000; + let mut f = File::open("foo.txt").unwrap(); + + // should lint + let mut data = Vec::with_capacity(20); + f.read_exact(&mut data).unwrap(); + + // should lint + let mut data2 = Vec::with_capacity(cap); + f.read_exact(&mut data2)?; + + // should lint + let mut data3 = Vec::new(); + f.read_exact(&mut data3)?; + + // should lint + let mut data4 = vec![]; + let _ = f.read(&mut data4)?; + + // should lint + let _ = { + let mut data5 = Vec::new(); + f.read(&mut data5) + }; + + // should lint + let _ = { + let mut data6: Vec = Default::default(); + f.read(&mut data6) + }; + + // should not lint + let mut buf = [0u8; 100]; + f.read(&mut buf)?; + + // should not lint + let mut empty = vec![]; + let mut data7 = vec![]; + f.read(&mut empty); + + // should not lint + f.read(&mut data7); + + // should not lint + let mut data8 = Vec::new(); + data8.resize(100, 0); + f.read_exact(&mut data8)?; + + // should not lint + let mut data9 = vec![1, 2, 3]; + f.read_exact(&mut data9)?; + + Ok(()) +} + +async fn test_futures(r: &mut R) { + // should lint + let mut data = Vec::new(); + r.read(&mut data).await.unwrap(); + + // should lint + let mut data2 = Vec::new(); + r.read_exact(&mut data2).await.unwrap(); +} + +async fn test_tokio(r: &mut R) { + // should lint + let mut data = Vec::new(); + r.read(&mut data).await.unwrap(); + + // should lint + let mut data2 = Vec::new(); + r.read_exact(&mut data2).await.unwrap(); +} + +fn main() {} diff --git a/tests/ui/read_zero_byte_vec.stderr b/tests/ui/read_zero_byte_vec.stderr new file mode 100644 index 00000000000..08ba9753d7c --- /dev/null +++ b/tests/ui/read_zero_byte_vec.stderr @@ -0,0 +1,64 @@ +error: reading zero byte data to `Vec` + --> $DIR/read_zero_byte_vec.rs:17:5 + | +LL | f.read_exact(&mut data).unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `data.resize(20, 0); f.read_exact(&mut data).unwrap();` + | + = note: `-D clippy::read-zero-byte-vec` implied by `-D warnings` + +error: reading zero byte data to `Vec` + --> $DIR/read_zero_byte_vec.rs:21:5 + | +LL | f.read_exact(&mut data2)?; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `data2.resize(cap, 0); f.read_exact(&mut data2)?;` + +error: reading zero byte data to `Vec` + --> $DIR/read_zero_byte_vec.rs:25:5 + | +LL | f.read_exact(&mut data3)?; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: reading zero byte data to `Vec` + --> $DIR/read_zero_byte_vec.rs:29:5 + | +LL | let _ = f.read(&mut data4)?; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: reading zero byte data to `Vec` + --> $DIR/read_zero_byte_vec.rs:34:9 + | +LL | f.read(&mut data5) + | ^^^^^^^^^^^^^^^^^^ + +error: reading zero byte data to `Vec` + --> $DIR/read_zero_byte_vec.rs:40:9 + | +LL | f.read(&mut data6) + | ^^^^^^^^^^^^^^^^^^ + +error: reading zero byte data to `Vec` + --> $DIR/read_zero_byte_vec.rs:70:5 + | +LL | r.read(&mut data).await.unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: reading zero byte data to `Vec` + --> $DIR/read_zero_byte_vec.rs:74:5 + | +LL | r.read_exact(&mut data2).await.unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: reading zero byte data to `Vec` + --> $DIR/read_zero_byte_vec.rs:80:5 + | +LL | r.read(&mut data).await.unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: reading zero byte data to `Vec` + --> $DIR/read_zero_byte_vec.rs:84:5 + | +LL | r.read_exact(&mut data2).await.unwrap(); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 10 previous errors + diff --git a/tests/ui/ref_binding_to_reference.rs b/tests/ui/ref_binding_to_reference.rs index fe742a4c2f4..570ef406e4a 100644 --- a/tests/ui/ref_binding_to_reference.rs +++ b/tests/ui/ref_binding_to_reference.rs @@ -1,5 +1,6 @@ // FIXME: run-rustfix waiting on multi-span suggestions +#![feature(lint_reasons)] #![warn(clippy::ref_binding_to_reference)] #![allow(clippy::needless_borrowed_reference)] @@ -73,3 +74,12 @@ impl T1 for S { let _: &&String = x; } } + +fn check_expect_suppression() { + let x = String::new(); + #[expect(clippy::ref_binding_to_reference)] + let _: &&String = match Some(&x) { + Some(ref x) => x, + None => return, + }; +} diff --git a/tests/ui/ref_binding_to_reference.stderr b/tests/ui/ref_binding_to_reference.stderr index c5856e15fa9..eb36cd516a2 100644 --- a/tests/ui/ref_binding_to_reference.stderr +++ b/tests/ui/ref_binding_to_reference.stderr @@ -1,5 +1,5 @@ error: this pattern creates a reference to a reference - --> $DIR/ref_binding_to_reference.rs:30:14 + --> $DIR/ref_binding_to_reference.rs:31:14 | LL | Some(ref x) => x, | ^^^^^ @@ -11,7 +11,7 @@ LL | Some(x) => &x, | ~ ~~ error: this pattern creates a reference to a reference - --> $DIR/ref_binding_to_reference.rs:36:14 + --> $DIR/ref_binding_to_reference.rs:37:14 | LL | Some(ref x) => { | ^^^^^ @@ -25,7 +25,7 @@ LL ~ &x | error: this pattern creates a reference to a reference - --> $DIR/ref_binding_to_reference.rs:46:14 + --> $DIR/ref_binding_to_reference.rs:47:14 | LL | Some(ref x) => m2!(x), | ^^^^^ @@ -36,7 +36,7 @@ LL | Some(x) => m2!(&x), | ~ ~~ error: this pattern creates a reference to a reference - --> $DIR/ref_binding_to_reference.rs:51:15 + --> $DIR/ref_binding_to_reference.rs:52:15 | LL | let _ = |&ref x: &&String| { | ^^^^^ @@ -48,7 +48,7 @@ LL ~ let _: &&String = &x; | error: this pattern creates a reference to a reference - --> $DIR/ref_binding_to_reference.rs:57:12 + --> $DIR/ref_binding_to_reference.rs:58:12 | LL | fn f2<'a>(&ref x: &&'a String) -> &'a String { | ^^^^^ @@ -61,7 +61,7 @@ LL ~ x | error: this pattern creates a reference to a reference - --> $DIR/ref_binding_to_reference.rs:64:11 + --> $DIR/ref_binding_to_reference.rs:65:11 | LL | fn f(&ref x: &&String) { | ^^^^^ @@ -73,7 +73,7 @@ LL ~ let _: &&String = &x; | error: this pattern creates a reference to a reference - --> $DIR/ref_binding_to_reference.rs:72:11 + --> $DIR/ref_binding_to_reference.rs:73:11 | LL | fn f(&ref x: &&String) { | ^^^^^ diff --git a/tests/ui/same_name_method.rs b/tests/ui/same_name_method.rs index 12e10ba6c49..9562b47f0c4 100644 --- a/tests/ui/same_name_method.rs +++ b/tests/ui/same_name_method.rs @@ -1,3 +1,4 @@ +#![feature(lint_reasons)] #![warn(clippy::same_name_method)] #![allow(dead_code, non_camel_case_types)] @@ -108,4 +109,19 @@ mod should_not_lint { } } +mod check_expect_suppression { + use crate::T1; + + struct S; + + impl S { + #[expect(clippy::same_name_method)] + fn foo() {} + } + + impl T1 for S { + fn foo() {} + } +} + fn main() {} diff --git a/tests/ui/same_name_method.stderr b/tests/ui/same_name_method.stderr index cf06eb32e0c..f55ec9f3cc6 100644 --- a/tests/ui/same_name_method.stderr +++ b/tests/ui/same_name_method.stderr @@ -1,61 +1,61 @@ error: method's name is the same as an existing method in a trait - --> $DIR/same_name_method.rs:20:13 + --> $DIR/same_name_method.rs:21:13 | LL | fn foo() {} | ^^^^^^^^^^^ | = note: `-D clippy::same-name-method` implied by `-D warnings` note: existing `foo` defined here - --> $DIR/same_name_method.rs:24:13 + --> $DIR/same_name_method.rs:25:13 | LL | fn foo() {} | ^^^^^^^^^^^ error: method's name is the same as an existing method in a trait - --> $DIR/same_name_method.rs:34:13 + --> $DIR/same_name_method.rs:35:13 | LL | fn clone() {} | ^^^^^^^^^^^^^ | note: existing `clone` defined here - --> $DIR/same_name_method.rs:30:18 + --> $DIR/same_name_method.rs:31:18 | LL | #[derive(Clone)] | ^^^^^ = note: this error originates in the derive macro `Clone` (in Nightly builds, run with -Z macro-backtrace for more info) error: method's name is the same as an existing method in a trait - --> $DIR/same_name_method.rs:44:13 + --> $DIR/same_name_method.rs:45:13 | LL | fn foo() {} | ^^^^^^^^^^^ | note: existing `foo` defined here - --> $DIR/same_name_method.rs:48:13 + --> $DIR/same_name_method.rs:49:13 | LL | fn foo() {} | ^^^^^^^^^^^ error: method's name is the same as an existing method in a trait - --> $DIR/same_name_method.rs:58:13 + --> $DIR/same_name_method.rs:59:13 | LL | fn foo() {} | ^^^^^^^^^^^ | note: existing `foo` defined here - --> $DIR/same_name_method.rs:61:9 + --> $DIR/same_name_method.rs:62:9 | LL | impl T1 for S {} | ^^^^^^^^^^^^^^^^ error: method's name is the same as an existing method in a trait - --> $DIR/same_name_method.rs:70:13 + --> $DIR/same_name_method.rs:71:13 | LL | fn foo() {} | ^^^^^^^^^^^ | note: existing `foo` defined here - --> $DIR/same_name_method.rs:73:9 + --> $DIR/same_name_method.rs:74:9 | LL | impl T1 for S {} | ^^^^^^^^^^^^^^^^ diff --git a/util/gh-pages/index.html b/util/gh-pages/index.html index 2076d129978..4999cce7511 100644 --- a/util/gh-pages/index.html +++ b/util/gh-pages/index.html @@ -206,6 +206,26 @@ Otherwise, have a great day =^.^= margin: auto 5px; font-family: monospace; } + + details { + border-radius: 4px; + padding: .5em .5em 0; + } + + code { + white-space: pre !important; + } + + summary { + font-weight: bold; + margin: -.5em -.5em 0; + padding: .5em; + display: revert; + } + + details[open] { + padding: .5em; + }