diff --git a/book/src/configuration.md b/book/src/configuration.md index 9eb067abd..ea549e4df 100644 --- a/book/src/configuration.md +++ b/book/src/configuration.md @@ -133,7 +133,7 @@ Very rarely, you may wish to prevent Clippy from evaluating certain sections of `clippy` cfg is not set. You may need to provide a stub so that the code compiles: ```rust -#[cfg(not(clippy)] +#[cfg(not(clippy))] include!(concat!(env!("OUT_DIR"), "/my_big_function-generated.rs")); #[cfg(clippy)] diff --git a/book/src/development/adding_lints.md b/book/src/development/adding_lints.md index 415022612..48c00bcbf 100644 --- a/book/src/development/adding_lints.md +++ b/book/src/development/adding_lints.md @@ -587,6 +587,11 @@ declare_clippy_lint! { } ``` +If the lint is in the `restriction` group because it lints things that are not +necessarily “bad” but are more of a style choice, then replace the +“Why is this bad?” section heading with “Why restrict this?”, to avoid writing +“Why is this bad? It isn't, but ...”. + Once your lint is merged, this documentation will show up in the [lint list][lint_list]. diff --git a/clippy_dev/src/new_lint.rs b/clippy_dev/src/new_lint.rs index b6481dde4..2e56eb8ec 100644 --- a/clippy_dev/src/new_lint.rs +++ b/clippy_dev/src/new_lint.rs @@ -331,12 +331,17 @@ fn get_lint_file_contents(lint: &LintData<'_>, enable_msrv: bool) -> String { } fn get_lint_declaration(name_upper: &str, category: &str) -> String { + let justification_heading = if category == "restriction" { + "Why restrict this?" + } else { + "Why is this bad?" + }; formatdoc!( r#" declare_clippy_lint! {{ /// ### What it does /// - /// ### Why is this bad? + /// ### {justification_heading} /// /// ### Example /// ```no_run diff --git a/clippy_lints/src/absolute_paths.rs b/clippy_lints/src/absolute_paths.rs index 8ba661afe..461117cf9 100644 --- a/clippy_lints/src/absolute_paths.rs +++ b/clippy_lints/src/absolute_paths.rs @@ -12,7 +12,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for usage of items through absolute paths, like `std::env::current_dir`. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Many codebases have their own style when it comes to importing, but one that is seldom used /// is using absolute paths *everywhere*. This is generally considered unidiomatic, and you /// should add a `use` statement. diff --git a/clippy_lints/src/allow_attributes.rs b/clippy_lints/src/allow_attributes.rs index 39fc49dee..123d0e51e 100644 --- a/clippy_lints/src/allow_attributes.rs +++ b/clippy_lints/src/allow_attributes.rs @@ -19,10 +19,11 @@ declare_clippy_lint! { /// This lint only warns outer attributes (`#[allow]`), as inner attributes /// (`#![allow]`) are usually used to enable or disable lints on a global scale. /// - /// ### Why is this bad? - /// `#[expect]` attributes suppress the lint emission, but emit a warning, if + /// ### Why restrict this? + /// `#[allow]` attributes can linger after their reason for existence is gone. + /// `#[expect]` attributes suppress the lint emission, but emit a warning if /// the expectation is unfulfilled. This can be useful to be notified when the - /// lint is no longer triggered. + /// lint is no longer triggered, which may indicate the attribute can be removed. /// /// ### Example /// ```rust,ignore diff --git a/clippy_lints/src/as_conversions.rs b/clippy_lints/src/as_conversions.rs index e3daf75c3..cfa25005a 100644 --- a/clippy_lints/src/as_conversions.rs +++ b/clippy_lints/src/as_conversions.rs @@ -17,7 +17,7 @@ declare_clippy_lint! { /// There is a good explanation the reason why this lint should work in this way and how it is useful /// [in this issue](https://github.com/rust-lang/rust-clippy/issues/5122). /// - /// ### Why is this bad? + /// ### Why restrict this? /// `as` conversions will perform many kinds of /// conversions, including silently lossy conversions and dangerous coercions. /// There are cases when it makes sense to use `as`, so the lint is diff --git a/clippy_lints/src/asm_syntax.rs b/clippy_lints/src/asm_syntax.rs index 7c88bfc97..0db1456d4 100644 --- a/clippy_lints/src/asm_syntax.rs +++ b/clippy_lints/src/asm_syntax.rs @@ -65,9 +65,8 @@ declare_clippy_lint! { /// ### What it does /// Checks for usage of Intel x86 assembly syntax. /// - /// ### Why is this bad? - /// The lint has been enabled to indicate a preference - /// for AT&T x86 assembly syntax. + /// ### Why restrict this? + /// To enforce consistent use of AT&T x86 assembly syntax. /// /// ### Example /// @@ -114,9 +113,8 @@ declare_clippy_lint! { /// ### What it does /// Checks for usage of AT&T x86 assembly syntax. /// - /// ### Why is this bad? - /// The lint has been enabled to indicate a preference - /// for Intel x86 assembly syntax. + /// ### Why restrict this? + /// To enforce consistent use of Intel x86 assembly syntax. /// /// ### Example /// diff --git a/clippy_lints/src/assertions_on_result_states.rs b/clippy_lints/src/assertions_on_result_states.rs index aec22965b..7217686dc 100644 --- a/clippy_lints/src/assertions_on_result_states.rs +++ b/clippy_lints/src/assertions_on_result_states.rs @@ -16,23 +16,33 @@ declare_clippy_lint! { /// ### What it does /// Checks for `assert!(r.is_ok())` or `assert!(r.is_err())` calls. /// - /// ### Why is this bad? - /// An assertion failure cannot output an useful message of the error. + /// ### Why restrict this? + /// This form of assertion does not show any of the information present in the `Result` + /// other than which variant it isn’t. /// /// ### Known problems /// The suggested replacement decreases the readability of code and log output. /// /// ### Example - /// ```rust,ignore + /// ```rust,no_run /// # let r = Ok::<_, ()>(()); /// assert!(r.is_ok()); - /// # let r = Err::<_, ()>(()); + /// # let r = Err::<(), _>(()); /// assert!(r.is_err()); /// ``` + /// + /// Use instead: + /// + /// ```rust,no_run + /// # let r = Ok::<_, ()>(()); + /// r.unwrap(); + /// # let r = Err::<(), _>(()); + /// r.unwrap_err(); + /// ``` #[clippy::version = "1.64.0"] pub ASSERTIONS_ON_RESULT_STATES, restriction, - "`assert!(r.is_ok())`/`assert!(r.is_err())` gives worse error message than directly calling `r.unwrap()`/`r.unwrap_err()`" + "`assert!(r.is_ok())` or `assert!(r.is_err())` gives worse panic messages than directly calling `r.unwrap()` or `r.unwrap_err()`" } declare_lint_pass!(AssertionsOnResultStates => [ASSERTIONS_ON_RESULT_STATES]); diff --git a/clippy_lints/src/attrs/mod.rs b/clippy_lints/src/attrs/mod.rs index 39f406077..a24bd5ed4 100644 --- a/clippy_lints/src/attrs/mod.rs +++ b/clippy_lints/src/attrs/mod.rs @@ -309,9 +309,9 @@ declare_clippy_lint! { /// /// (This requires the `lint_reasons` feature) /// - /// ### Why is this bad? - /// Allowing a lint should always have a reason. This reason should be documented to - /// ensure that others understand the reasoning + /// ### Why restrict this? + /// Justifying each `allow` helps readers understand the reasoning, + /// and may allow removing `allow` attributes if their purpose is obsolete. /// /// ### Example /// ```no_run diff --git a/clippy_lints/src/casts/mod.rs b/clippy_lints/src/casts/mod.rs index bd2c96f01..e60c36ced 100644 --- a/clippy_lints/src/casts/mod.rs +++ b/clippy_lints/src/casts/mod.rs @@ -303,7 +303,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for casts of a function pointer to any integer type. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Casting a function pointer to an integer can have surprising results and can occur /// accidentally if parentheses are omitted from a function call. If you aren't doing anything /// low-level with function pointers then you can opt-out of casting functions to integers in @@ -535,8 +535,8 @@ declare_clippy_lint! { /// ### What it does /// Checks for the usage of `as _` conversion using inferred type. /// - /// ### Why is this bad? - /// The conversion might include lossy conversion and dangerous cast that might go + /// ### Why restrict this? + /// The conversion might include lossy conversion or a dangerous cast that might go /// undetected due to the type being inferred. /// /// The lint is allowed by default as using `_` is less wordy than always specifying the type. diff --git a/clippy_lints/src/create_dir.rs b/clippy_lints/src/create_dir.rs index 7a3d5a070..27c00948a 100644 --- a/clippy_lints/src/create_dir.rs +++ b/clippy_lints/src/create_dir.rs @@ -10,8 +10,10 @@ declare_clippy_lint! { /// ### What it does /// Checks usage of `std::fs::create_dir` and suggest using `std::fs::create_dir_all` instead. /// - /// ### Why is this bad? - /// Sometimes `std::fs::create_dir` is mistakenly chosen over `std::fs::create_dir_all`. + /// ### Why restrict this? + /// Sometimes `std::fs::create_dir` is mistakenly chosen over `std::fs::create_dir_all`, + /// resulting in failure when more than one directory needs to be created or when the directory already exists. + /// Crates which never need to specifically create a single directory may wish to prevent this mistake. /// /// ### Example /// ```rust,ignore diff --git a/clippy_lints/src/dbg_macro.rs b/clippy_lints/src/dbg_macro.rs index db5937266..b0590b0a7 100644 --- a/clippy_lints/src/dbg_macro.rs +++ b/clippy_lints/src/dbg_macro.rs @@ -14,7 +14,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for usage of the [`dbg!`](https://doc.rust-lang.org/std/macro.dbg.html) macro. /// - /// ### Why is this bad? + /// ### Why restrict this? /// The `dbg!` macro is intended as a debugging tool. It should not be present in released /// software or committed to a version control system. /// diff --git a/clippy_lints/src/default_numeric_fallback.rs b/clippy_lints/src/default_numeric_fallback.rs index 4d8c066fe..ff631909b 100644 --- a/clippy_lints/src/default_numeric_fallback.rs +++ b/clippy_lints/src/default_numeric_fallback.rs @@ -22,9 +22,8 @@ declare_clippy_lint! { /// /// See [RFC0212](https://github.com/rust-lang/rfcs/blob/master/text/0212-restore-int-fallback.md) for more information about the fallback. /// - /// ### Why is this bad? - /// For those who are very careful about types, default numeric fallback - /// can be a pitfall that cause unexpected runtime behavior. + /// ### Why restrict this? + /// To ensure that every numeric type is chosen explicitly rather than implicitly. /// /// ### Known problems /// This lint can only be allowed at the function level or above. diff --git a/clippy_lints/src/default_union_representation.rs b/clippy_lints/src/default_union_representation.rs index b4290b643..3fa9bad0d 100644 --- a/clippy_lints/src/default_union_representation.rs +++ b/clippy_lints/src/default_union_representation.rs @@ -10,7 +10,7 @@ declare_clippy_lint! { /// ### What it does /// Displays a warning when a union is declared with the default representation (without a `#[repr(C)]` attribute). /// - /// ### Why is this bad? + /// ### Why restrict this? /// Unions in Rust have unspecified layout by default, despite many people thinking that they /// lay out each field at the start of the union (like C does). That is, there are no guarantees /// about the offset of the fields for unions with multiple non-ZST fields without an explicitly diff --git a/clippy_lints/src/dereference.rs b/clippy_lints/src/dereference.rs index 297082354..d60320d82 100644 --- a/clippy_lints/src/dereference.rs +++ b/clippy_lints/src/dereference.rs @@ -2,7 +2,9 @@ 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::{implements_trait, is_manually_drop, peel_mid_ty_refs}; -use clippy_utils::{expr_use_ctxt, get_parent_expr, is_lint_allowed, path_to_local, DefinedTy, ExprUseNode}; +use clippy_utils::{ + expr_use_ctxt, get_parent_expr, is_block_like, is_lint_allowed, path_to_local, DefinedTy, ExprUseNode, +}; use core::mem; use rustc_ast::util::parser::{PREC_POSTFIX, PREC_PREFIX}; use rustc_data_structures::fx::FxIndexMap; @@ -1038,14 +1040,8 @@ fn report<'tcx>( ); }, State::ExplicitDeref { mutability } => { - if matches!( - expr.kind, - ExprKind::Block(..) - | ExprKind::ConstBlock(_) - | ExprKind::If(..) - | ExprKind::Loop(..) - | ExprKind::Match(..) - ) && let ty::Ref(_, ty, _) = data.adjusted_ty.kind() + if is_block_like(expr) + && let ty::Ref(_, ty, _) = data.adjusted_ty.kind() && ty.is_sized(cx.tcx, cx.param_env) { // Rustc bug: auto deref doesn't work on block expression when targeting sized types. diff --git a/clippy_lints/src/derive.rs b/clippy_lints/src/derive.rs index 2a644922f..636698e96 100644 --- a/clippy_lints/src/derive.rs +++ b/clippy_lints/src/derive.rs @@ -1,17 +1,17 @@ -use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_note, span_lint_and_sugg, span_lint_and_then}; +use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_then, span_lint_hir_and_then}; use clippy_utils::ty::{implements_trait, implements_trait_with_env, is_copy}; use clippy_utils::{has_non_exhaustive_attr, is_lint_allowed, match_def_path, paths}; 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, Safety, Impl, Item, ItemKind, UnsafeSource, + self as hir, BlockCheckMode, BodyId, Expr, ExprKind, FnDecl, Impl, Item, ItemKind, Safety, UnsafeSource, }; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::hir::nested_filter; use rustc_middle::traits::Reveal; use rustc_middle::ty::{ - self, ClauseKind, GenericArgKind, GenericParamDefKind, ParamEnv, Upcast, TraitPredicate, Ty, TyCtxt, + self, ClauseKind, GenericArgKind, GenericParamDefKind, ParamEnv, TraitPredicate, Ty, TyCtxt, Upcast, }; use rustc_session::declare_lint_pass; use rustc_span::def_id::LocalDefId; @@ -390,13 +390,17 @@ fn check_unsafe_derive_deserialize<'tcx>( .map(|imp_did| cx.tcx.hir().expect_item(imp_did.expect_local())) .any(|imp| has_unsafe(cx, imp)) { - span_lint_and_help( + span_lint_hir_and_then( cx, UNSAFE_DERIVE_DESERIALIZE, + adt_hir_id, item.span, "you are deriving `serde::Deserialize` on a type that has methods using `unsafe`", - None, - "consider implementing `serde::Deserialize` manually. See https://serde.rs/impl-deserialize.html", + |diag| { + diag.help( + "consider implementing `serde::Deserialize` manually. See https://serde.rs/impl-deserialize.html", + ); + }, ); } } @@ -452,20 +456,27 @@ fn check_partial_eq_without_eq<'tcx>(cx: &LateContext<'tcx>, span: Span, trait_r && !has_non_exhaustive_attr(cx.tcx, *adt) && !ty_implements_eq_trait(cx.tcx, ty, eq_trait_def_id) && let param_env = param_env_for_derived_eq(cx.tcx, adt.did(), eq_trait_def_id) + && let Some(local_def_id) = adt.did().as_local() // If all of our fields implement `Eq`, we can implement `Eq` too && adt .all_fields() .map(|f| f.ty(cx.tcx, args)) .all(|ty| implements_trait_with_env(cx.tcx, param_env, ty, eq_trait_def_id, None, &[])) { - span_lint_and_sugg( + span_lint_hir_and_then( cx, DERIVE_PARTIAL_EQ_WITHOUT_EQ, + cx.tcx.local_def_id_to_hir_id(local_def_id), span.ctxt().outer_expn_data().call_site, "you are deriving `PartialEq` and can implement `Eq`", - "consider deriving `Eq` as well", - "PartialEq, Eq".to_string(), - Applicability::MachineApplicable, + |diag| { + diag.span_suggestion( + span.ctxt().outer_expn_data().call_site, + "consider deriving `Eq` as well", + "PartialEq, Eq", + Applicability::MachineApplicable, + ); + }, ); } } diff --git a/clippy_lints/src/disallowed_script_idents.rs b/clippy_lints/src/disallowed_script_idents.rs index def4b5932..a995f06fb 100644 --- a/clippy_lints/src/disallowed_script_idents.rs +++ b/clippy_lints/src/disallowed_script_idents.rs @@ -20,11 +20,11 @@ declare_clippy_lint! { /// [aliases]: http://www.unicode.org/reports/tr24/tr24-31.html#Script_Value_Aliases /// [supported_scripts]: https://www.unicode.org/iso15924/iso15924-codes.html /// - /// ### Why is this bad? + /// ### Why restrict this? /// It may be not desired to have many different scripts for /// identifiers in the codebase. /// - /// Note that if you only want to allow plain English, you might want to use + /// Note that if you only want to allow typical English, you might want to use /// built-in [`non_ascii_idents`] lint instead. /// /// [`non_ascii_idents`]: https://doc.rust-lang.org/rustc/lints/listing/allowed-by-default.html#non-ascii-idents diff --git a/clippy_lints/src/doc/markdown.rs b/clippy_lints/src/doc/markdown.rs index 1add02af3..41c0bcd55 100644 --- a/clippy_lints/src/doc/markdown.rs +++ b/clippy_lints/src/doc/markdown.rs @@ -1,4 +1,4 @@ -use clippy_utils::diagnostics::{span_lint, span_lint_and_then}; +use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then}; use clippy_utils::source::snippet_with_applicability; use rustc_data_structures::fx::FxHashSet; use rustc_errors::{Applicability, SuggestionStyle}; @@ -30,6 +30,7 @@ pub fn check( word = tmp_word; } + let original_len = word.len(); word = word.trim_start_matches(trim_pattern); // Remove leading or trailing single `:` which may be part of a sentence. @@ -44,6 +45,25 @@ pub fn check( continue; } + // Ensure that all reachable matching closing parens are included as well. + let size_diff = original_len - word.len(); + let mut open_parens = 0; + let mut close_parens = 0; + for c in word.chars() { + if c == '(' { + open_parens += 1; + } else if c == ')' { + close_parens += 1; + } + } + while close_parens < open_parens + && let Some(tmp_word) = orig_word.get(size_diff..=(word.len() + size_diff)) + && tmp_word.ends_with(')') + { + word = tmp_word; + close_parens += 1; + } + // Adjust for the current word let offset = word.as_ptr() as usize - text.as_ptr() as usize; let span = Span::new( @@ -92,13 +112,15 @@ fn check_word(cx: &LateContext<'_>, word: &str, span: Span, code_level: isize, b if let Ok(url) = Url::parse(word) { // try to get around the fact that `foo::bar` parses as a valid URL if !url.cannot_be_a_base() { - span_lint( + span_lint_and_sugg( cx, DOC_MARKDOWN, span, "you should put bare URLs between `<`/`>` or make a proper Markdown link", + "try", + format!("<{word}>"), + Applicability::MachineApplicable, ); - return; } } diff --git a/clippy_lints/src/doc/mod.rs b/clippy_lints/src/doc/mod.rs index 9f0897394..3d875e7ac 100644 --- a/clippy_lints/src/doc/mod.rs +++ b/clippy_lints/src/doc/mod.rs @@ -261,7 +261,7 @@ declare_clippy_lint! { /// Checks for the doc comments of publicly visible /// safe functions and traits and warns if there is a `# Safety` section. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Safe functions and traits are safe to implement and therefore do not /// need to describe safety preconditions that users are required to uphold. /// diff --git a/clippy_lints/src/drop_forget_ref.rs b/clippy_lints/src/drop_forget_ref.rs index 119473c24..4a6ffcd9a 100644 --- a/clippy_lints/src/drop_forget_ref.rs +++ b/clippy_lints/src/drop_forget_ref.rs @@ -52,9 +52,10 @@ declare_clippy_lint! { /// Checks for usage of `std::mem::forget(t)` where `t` is /// `Drop` or has a field that implements `Drop`. /// - /// ### Why is this bad? - /// `std::mem::forget(t)` prevents `t` from running its - /// destructor, possibly causing leaks. + /// ### Why restrict this? + /// `std::mem::forget(t)` prevents `t` from running its destructor, possibly causing leaks. + /// It is not possible to detect all means of creating leaks, but it may be desirable to + /// prohibit the simple ones. /// /// ### Example /// ```no_run diff --git a/clippy_lints/src/else_if_without_else.rs b/clippy_lints/src/else_if_without_else.rs index a6ca7fe9e..bb6f9aac2 100644 --- a/clippy_lints/src/else_if_without_else.rs +++ b/clippy_lints/src/else_if_without_else.rs @@ -11,7 +11,7 @@ declare_clippy_lint! { /// Checks for usage of if expressions with an `else if` branch, /// but without a final `else` branch. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Some coding guidelines require this (e.g., MISRA-C:2004 Rule 14.10). /// /// ### Example diff --git a/clippy_lints/src/empty_drop.rs b/clippy_lints/src/empty_drop.rs index 74db250b3..c5fc72b5e 100644 --- a/clippy_lints/src/empty_drop.rs +++ b/clippy_lints/src/empty_drop.rs @@ -9,7 +9,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for empty `Drop` implementations. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Empty `Drop` implementations have no effect when dropping an instance of the type. They are /// most likely useless. However, an empty `Drop` implementation prevents a type from being /// destructured, which might be the intention behind adding the implementation as a marker. diff --git a/clippy_lints/src/empty_enum.rs b/clippy_lints/src/empty_enum.rs index 420888b6c..d16714695 100644 --- a/clippy_lints/src/empty_enum.rs +++ b/clippy_lints/src/empty_enum.rs @@ -7,32 +7,53 @@ use rustc_session::declare_lint_pass; declare_clippy_lint! { /// ### What it does - /// Checks for `enum`s with no variants. + /// Checks for `enum`s with no variants, which therefore are uninhabited types + /// (cannot be instantiated). /// - /// As of this writing, the `never_type` is still a - /// nightly-only experimental API. Therefore, this lint is only triggered - /// if the `never_type` is enabled. + /// As of this writing, the `never_type` is still a nightly-only experimental API. + /// Therefore, this lint is only triggered if `#![feature(never_type)]` is enabled. /// /// ### Why is this bad? - /// If you want to introduce a type which - /// can't be instantiated, you should use `!` (the primitive type "never"), - /// or a wrapper around it, because `!` has more extensive - /// compiler support (type inference, etc...) and wrappers - /// around it are the conventional way to define an uninhabited type. - /// For further information visit [never type documentation](https://doc.rust-lang.org/std/primitive.never.html) + /// * If you only want a type which can’t be instantiated, you should use [`!`] + /// (the primitive type "never"), because [`!`] has more extensive compiler support + /// (type inference, etc.) and implementations of common traits. /// + /// * If you need to introduce a distinct type, consider using a [newtype] `struct` + /// containing [`!`] instead (`struct MyType(pub !)`), because it is more idiomatic + /// to use a `struct` rather than an `enum` when an `enum` is unnecessary. + /// + /// If you do this, note that the [visibility] of the [`!`] field determines whether + /// the uninhabitedness is visible in documentation, and whether it can be pattern + /// matched to mark code unreachable. If the field is not visible, then the struct + /// acts like any other struct with private fields. + /// + /// * If the enum has no variants only because all variants happen to be + /// [disabled by conditional compilation][cfg], then it would be appropriate + /// to allow the lint, with `#[allow(empty_enum)]`. + /// + /// For further information, visit + /// [the never type’s documentation][`!`]. /// /// ### Example /// ```no_run - /// enum Test {} + /// enum CannotExist {} /// ``` /// /// Use instead: /// ```no_run /// #![feature(never_type)] /// - /// struct Test(!); + /// /// Use the `!` type directly... + /// type CannotExist = !; + /// + /// /// ...or define a newtype which is distinct. + /// struct CannotExist2(pub !); /// ``` + /// + /// [`!`]: https://doc.rust-lang.org/std/primitive.never.html + /// [cfg]: https://doc.rust-lang.org/reference/conditional-compilation.html + /// [newtype]: https://doc.rust-lang.org/book/ch19-04-advanced-types.html#using-the-newtype-pattern-for-type-safety-and-abstraction + /// [visibility]: https://doc.rust-lang.org/reference/visibility-and-privacy.html #[clippy::version = "pre 1.29.0"] pub EMPTY_ENUM, pedantic, diff --git a/clippy_lints/src/empty_with_brackets.rs b/clippy_lints/src/empty_with_brackets.rs index 969df6d85..745599b0e 100644 --- a/clippy_lints/src/empty_with_brackets.rs +++ b/clippy_lints/src/empty_with_brackets.rs @@ -11,16 +11,23 @@ declare_clippy_lint! { /// ### What it does /// Finds structs without fields (a so-called "empty struct") that are declared with brackets. /// - /// ### Why is this bad? - /// Empty brackets after a struct declaration can be omitted. + /// ### Why restrict this? + /// Empty brackets after a struct declaration can be omitted, + /// and it may be desirable to do so consistently for style. + /// + /// However, removing the brackets also introduces a public constant named after the struct, + /// so this is not just a syntactic simplification but an an API change, and adding them back + /// is a *breaking* API change. /// /// ### Example /// ```no_run /// struct Cookie {} + /// struct Biscuit(); /// ``` /// Use instead: /// ```no_run /// struct Cookie; + /// struct Biscuit; /// ``` #[clippy::version = "1.62.0"] pub EMPTY_STRUCTS_WITH_BRACKETS, @@ -32,14 +39,20 @@ declare_clippy_lint! { /// ### What it does /// Finds enum variants without fields that are declared with empty brackets. /// - /// ### Why is this bad? - /// Empty brackets while defining enum variants are redundant and can be omitted. + /// ### Why restrict this? + /// Empty brackets after a enum variant declaration are redundant and can be omitted, + /// and it may be desirable to do so consistently for style. + /// + /// However, removing the brackets also introduces a public constant named after the variant, + /// so this is not just a syntactic simplification but an an API change, and adding them back + /// is a *breaking* API change. /// /// ### Example /// ```no_run /// enum MyEnum { /// HasData(u8), - /// HasNoData(), // redundant parentheses + /// HasNoData(), // redundant parentheses + /// NoneHereEither {}, // redundant braces /// } /// ``` /// @@ -48,6 +61,7 @@ declare_clippy_lint! { /// enum MyEnum { /// HasData(u8), /// HasNoData, + /// NoneHereEither, /// } /// ``` #[clippy::version = "1.77.0"] diff --git a/clippy_lints/src/endian_bytes.rs b/clippy_lints/src/endian_bytes.rs index dd03df797..bb766e963 100644 --- a/clippy_lints/src/endian_bytes.rs +++ b/clippy_lints/src/endian_bytes.rs @@ -13,8 +13,9 @@ declare_clippy_lint! { /// ### What it does /// Checks for the usage of the `to_ne_bytes` method and/or the function `from_ne_bytes`. /// - /// ### Why is this bad? - /// It's not, but some may prefer to specify the target endianness explicitly. + /// ### Why restrict this? + /// To ensure use of explicitly chosen endianness rather than the target’s endianness, + /// such as when implementing network protocols or file formats rather than FFI. /// /// ### Example /// ```rust,ignore @@ -31,9 +32,8 @@ declare_clippy_lint! { /// ### What it does /// Checks for the usage of the `to_le_bytes` method and/or the function `from_le_bytes`. /// - /// ### Why is this bad? - /// It's not, but some may wish to lint usage of this method, either to suggest using the host - /// endianness or big endian. + /// ### Why restrict this? + /// To ensure use of big endian or the target’s endianness rather than little endian. /// /// ### Example /// ```rust,ignore @@ -50,9 +50,8 @@ declare_clippy_lint! { /// ### What it does /// Checks for the usage of the `to_be_bytes` method and/or the function `from_be_bytes`. /// - /// ### Why is this bad? - /// It's not, but some may wish to lint usage of this method, either to suggest using the host - /// endianness or little endian. + /// ### Why restrict this? + /// To ensure use of little endian or the target’s endianness rather than big endian. /// /// ### Example /// ```rust,ignore diff --git a/clippy_lints/src/error_impl_error.rs b/clippy_lints/src/error_impl_error.rs index 8dbb47fad..8e49138cd 100644 --- a/clippy_lints/src/error_impl_error.rs +++ b/clippy_lints/src/error_impl_error.rs @@ -12,7 +12,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for types named `Error` that implement `Error`. /// - /// ### Why is this bad? + /// ### Why restrict this? /// It can become confusing when a codebase has 20 types all named `Error`, requiring either /// aliasing them in the `use` statement or qualifying them like `my_module::Error`. This /// hinders comprehension, as it requires you to memorize every variation of importing `Error` diff --git a/clippy_lints/src/exhaustive_items.rs b/clippy_lints/src/exhaustive_items.rs index 9ffda6457..436dc8611 100644 --- a/clippy_lints/src/exhaustive_items.rs +++ b/clippy_lints/src/exhaustive_items.rs @@ -10,10 +10,10 @@ declare_clippy_lint! { /// ### What it does /// Warns on any exported `enum`s that are not tagged `#[non_exhaustive]` /// - /// ### Why is this bad? - /// Exhaustive enums are typically fine, but a project which does - /// not wish to make a stability commitment around exported enums may wish to - /// disable them by default. + /// ### Why restrict this? + /// Making an `enum` exhaustive is a stability commitment: adding a variant is a breaking change. + /// A project may wish to ensure that there are no exhaustive enums or that every exhaustive + /// `enum` is explicitly `#[allow]`ed. /// /// ### Example /// ```no_run @@ -40,10 +40,10 @@ declare_clippy_lint! { /// ### What it does /// Warns on any exported `struct`s that are not tagged `#[non_exhaustive]` /// - /// ### Why is this bad? - /// Exhaustive structs are typically fine, but a project which does - /// not wish to make a stability commitment around exported structs may wish to - /// disable them by default. + /// ### Why restrict this? + /// Making a `struct` exhaustive is a stability commitment: adding a field is a breaking change. + /// A project may wish to ensure that there are no exhaustive structs or that every exhaustive + /// `struct` is explicitly `#[allow]`ed. /// /// ### Example /// ```no_run diff --git a/clippy_lints/src/exit.rs b/clippy_lints/src/exit.rs index 106844dd4..91c94d664 100644 --- a/clippy_lints/src/exit.rs +++ b/clippy_lints/src/exit.rs @@ -9,11 +9,13 @@ declare_clippy_lint! { /// ### What it does /// Detects calls to the `exit()` function which terminates the program. /// - /// ### Why is this bad? - /// Exit terminates the program at the location it is called. For unrecoverable - /// errors `panics` should be used to provide a stacktrace and potentially other - /// information. A normal termination or one with an error code should happen in - /// the main function. + /// ### Why restrict this? + /// `exit()` immediately terminates the program with no information other than an exit code. + /// This provides no means to troubleshoot a problem, and may be an unexpected side effect. + /// + /// Codebases may use this lint to require that all exits are performed either by panicking + /// (which produces a message, a code location, and optionally a backtrace) + /// or by returning from `main()` (which is a single place to look). /// /// ### Example /// ```no_run diff --git a/clippy_lints/src/float_literal.rs b/clippy_lints/src/float_literal.rs index 2cd4e9e99..4ec9bd757 100644 --- a/clippy_lints/src/float_literal.rs +++ b/clippy_lints/src/float_literal.rs @@ -38,9 +38,9 @@ declare_clippy_lint! { /// Checks for whole number float literals that /// cannot be represented as the underlying type without loss. /// - /// ### Why is this bad? - /// Rust will silently lose precision during - /// conversion to a float. + /// ### Why restrict this? + /// If the value was intended to be exact, it will not be. + /// This may be especially surprising when the lost precision is to the left of the decimal point. /// /// ### Example /// ```no_run diff --git a/clippy_lints/src/format_push_string.rs b/clippy_lints/src/format_push_string.rs index 2b08437d8..a75538dd3 100644 --- a/clippy_lints/src/format_push_string.rs +++ b/clippy_lints/src/format_push_string.rs @@ -11,7 +11,7 @@ declare_clippy_lint! { /// Detects cases where the result of a `format!` call is /// appended to an existing `String`. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Introduces an extra, avoidable heap allocation. /// /// ### Known problems diff --git a/clippy_lints/src/functions/misnamed_getters.rs b/clippy_lints/src/functions/misnamed_getters.rs index 7729c556e..2cbc4b072 100644 --- a/clippy_lints/src/functions/misnamed_getters.rs +++ b/clippy_lints/src/functions/misnamed_getters.rs @@ -2,7 +2,7 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::snippet; use rustc_errors::Applicability; use rustc_hir::intravisit::FnKind; -use rustc_hir::{Body, ExprKind, FnDecl, Safety, ImplicitSelfKind}; +use rustc_hir::{Body, ExprKind, FnDecl, ImplicitSelfKind, Safety}; use rustc_lint::LateContext; use rustc_middle::ty; use rustc_span::Span; diff --git a/clippy_lints/src/functions/mod.rs b/clippy_lints/src/functions/mod.rs index dfcaac9ab..26534492d 100644 --- a/clippy_lints/src/functions/mod.rs +++ b/clippy_lints/src/functions/mod.rs @@ -338,8 +338,10 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does /// Lints when `impl Trait` is being used in a function's parameters. - /// ### Why is this bad? - /// Turbofish syntax (`::<>`) cannot be used when `impl Trait` is being used, making `impl Trait` less powerful. Readability may also be a factor. + /// + /// ### Why restrict this? + /// Turbofish syntax (`::<>`) cannot be used to specify the type of an `impl Trait` parameter, + /// making `impl Trait` less powerful. Readability may also be a factor. /// /// ### Example /// ```no_run @@ -366,9 +368,8 @@ declare_clippy_lint! { /// Lints when the name of function parameters from trait impl is /// different than its default implementation. /// - /// ### Why is this bad? - /// Using the default name for parameters of a trait method is often - /// more desirable for consistency's sake. + /// ### Why restrict this? + /// Using the default name for parameters of a trait method is more consistent. /// /// ### Example /// ```rust diff --git a/clippy_lints/src/if_then_some_else_none.rs b/clippy_lints/src/if_then_some_else_none.rs index f5ba62ae4..0b2008152 100644 --- a/clippy_lints/src/if_then_some_else_none.rs +++ b/clippy_lints/src/if_then_some_else_none.rs @@ -15,7 +15,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for if-else that could be written using either `bool::then` or `bool::then_some`. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Looks a little redundant. Using `bool::then` is more concise and incurs no loss of clarity. /// For simple calculations and known values, use `bool::then_some`, which is eagerly evaluated /// in comparison to `bool::then`. diff --git a/clippy_lints/src/implicit_return.rs b/clippy_lints/src/implicit_return.rs index 5288efd8d..cc342007e 100644 --- a/clippy_lints/src/implicit_return.rs +++ b/clippy_lints/src/implicit_return.rs @@ -16,12 +16,13 @@ declare_clippy_lint! { /// ### What it does /// Checks for missing return statements at the end of a block. /// - /// ### Why is this bad? - /// Actually omitting the return keyword is idiomatic Rust code. Programmers - /// coming from other languages might prefer the expressiveness of `return`. It's possible to miss - /// the last returning statement because the only difference is a missing `;`. Especially in bigger - /// code with multiple return paths having a `return` keyword makes it easier to find the - /// corresponding statements. + /// ### Why restrict this? + /// Omitting the return keyword whenever possible is idiomatic Rust code, but: + /// + /// * Programmers coming from other languages might prefer the expressiveness of `return`. + /// * It's possible to miss the last returning statement because the only difference is a missing `;`. + /// * Especially in bigger code with multiple return paths, having a `return` keyword makes it easier to find the + /// corresponding statements. /// /// ### Example /// ```no_run diff --git a/clippy_lints/src/indexing_slicing.rs b/clippy_lints/src/indexing_slicing.rs index 35fcd8cdd..e3e79749b 100644 --- a/clippy_lints/src/indexing_slicing.rs +++ b/clippy_lints/src/indexing_slicing.rs @@ -45,9 +45,10 @@ declare_clippy_lint! { /// does report on arrays if we can tell that slicing operations are in bounds and does not /// lint on constant `usize` indexing on arrays because that is handled by rustc's `const_err` lint. /// - /// ### Why is this bad? - /// Indexing and slicing can panic at runtime and there are - /// safe alternatives. + /// ### Why restrict this? + /// To avoid implicit panics from indexing and slicing. + /// There are “checked” alternatives which do not panic, and can be used with `unwrap()` to make + /// an explicit panic when it is desired. /// /// ### Example /// ```rust,no_run diff --git a/clippy_lints/src/inherent_impl.rs b/clippy_lints/src/inherent_impl.rs index 1127f00ab..95ae59188 100644 --- a/clippy_lints/src/inherent_impl.rs +++ b/clippy_lints/src/inherent_impl.rs @@ -14,7 +14,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for multiple inherent implementations of a struct /// - /// ### Why is this bad? + /// ### Why restrict this? /// Splitting the implementation of a type makes the code harder to navigate. /// /// ### Example diff --git a/clippy_lints/src/init_numbered_fields.rs b/clippy_lints/src/init_numbered_fields.rs index e48656380..1c8fd0a27 100644 --- a/clippy_lints/src/init_numbered_fields.rs +++ b/clippy_lints/src/init_numbered_fields.rs @@ -1,5 +1,5 @@ use clippy_utils::diagnostics::span_lint_and_sugg; -use clippy_utils::source::snippet_with_applicability; +use clippy_utils::source::{snippet_with_applicability, snippet_with_context}; use rustc_errors::Applicability; use rustc_hir::def::{DefKind, Res}; use rustc_hir::{Expr, ExprKind}; @@ -62,7 +62,7 @@ impl<'tcx> LateLintPass<'tcx> for NumberedFields { snippet_with_applicability(cx, path.span(), "..", &mut appl), expr_spans .into_iter_sorted() - .map(|(_, span)| snippet_with_applicability(cx, span, "..", &mut appl)) + .map(|(_, span)| snippet_with_context(cx, span, path.span().ctxt(), "..", &mut appl).0) .intersperse(Cow::Borrowed(", ")) .collect::() ); diff --git a/clippy_lints/src/integer_division_remainder_used.rs b/clippy_lints/src/integer_division_remainder_used.rs index a3577b765..cf598d504 100644 --- a/clippy_lints/src/integer_division_remainder_used.rs +++ b/clippy_lints/src/integer_division_remainder_used.rs @@ -7,10 +7,10 @@ use rustc_session::declare_lint_pass; declare_clippy_lint! { /// ### What it does - /// Checks for the usage of division (/) and remainder (%) operations - /// when performed on any integer types using the default Div and Rem trait implementations. + /// Checks for the usage of division (`/`) and remainder (`%`) operations + /// when performed on any integer types using the default `Div` and `Rem` trait implementations. /// - /// ### Why is this bad? + /// ### Why restrict this? /// In cryptographic contexts, division can result in timing sidechannel vulnerabilities, /// and needs to be replaced with constant-time code instead (e.g. Barrett reduction). /// diff --git a/clippy_lints/src/iter_over_hash_type.rs b/clippy_lints/src/iter_over_hash_type.rs index 6c6eff9ba..fb29d9824 100644 --- a/clippy_lints/src/iter_over_hash_type.rs +++ b/clippy_lints/src/iter_over_hash_type.rs @@ -14,8 +14,8 @@ declare_clippy_lint! { /// ### What it does /// This is a restriction lint which prevents the use of hash types (i.e., `HashSet` and `HashMap`) in for loops. /// - /// ### Why is this bad? - /// Because hash types are unordered, when iterated through such as in a for loop, the values are returned in + /// ### Why restrict this? + /// Because hash types are unordered, when iterated through such as in a `for` loop, the values are returned in /// an undefined order. As a result, on redundant systems this may cause inconsistencies and anomalies. /// In addition, the unknown order of the elements may reduce readability or introduce other undesired /// side effects. diff --git a/clippy_lints/src/large_include_file.rs b/clippy_lints/src/large_include_file.rs index 790bed580..07efee159 100644 --- a/clippy_lints/src/large_include_file.rs +++ b/clippy_lints/src/large_include_file.rs @@ -10,10 +10,12 @@ use rustc_span::sym; declare_clippy_lint! { /// ### What it does /// Checks for the inclusion of large files via `include_bytes!()` - /// and `include_str!()` + /// or `include_str!()`. /// - /// ### Why is this bad? - /// Including large files can increase the size of the binary + /// ### Why restrict this? + /// Including large files can undesirably increase the size of the binary produced by the compiler. + /// This lint may be used to catch mistakes where an unexpectedly large file is included, or + /// temporarily to obtain a list of all large files. /// /// ### Example /// ```rust,ignore diff --git a/clippy_lints/src/let_underscore.rs b/clippy_lints/src/let_underscore.rs index 619e933b4..9fd4f509a 100644 --- a/clippy_lints/src/let_underscore.rs +++ b/clippy_lints/src/let_underscore.rs @@ -12,9 +12,8 @@ declare_clippy_lint! { /// ### What it does /// Checks for `let _ = ` where expr is `#[must_use]` /// - /// ### Why is this bad? - /// It's better to explicitly handle the value of a `#[must_use]` - /// expr + /// ### Why restrict this? + /// To ensure that all `#[must_use]` types are used rather than ignored. /// /// ### Example /// ```no_run @@ -96,8 +95,8 @@ declare_clippy_lint! { /// Checks for `let _ = ` without a type annotation, and suggests to either provide one, /// or remove the `let` keyword altogether. /// - /// ### Why is this bad? - /// The `let _ = ` expression ignores the value of `` but will remain doing so even + /// ### Why restrict this? + /// The `let _ = ` expression ignores the value of ``, but will continue to do so even /// if the type were to change, thus potentially introducing subtle bugs. By supplying a type /// annotation, one will be forced to re-visit the decision to ignore the value in such cases. /// diff --git a/clippy_lints/src/literal_representation.rs b/clippy_lints/src/literal_representation.rs index 2348dd182..d2a140a36 100644 --- a/clippy_lints/src/literal_representation.rs +++ b/clippy_lints/src/literal_representation.rs @@ -132,8 +132,8 @@ declare_clippy_lint! { /// ### What it does /// Warns if there is a better representation for a numeric literal. /// - /// ### Why is this bad? - /// Especially for big powers of 2 a hexadecimal representation is more + /// ### Why restrict this? + /// Especially for big powers of 2, a hexadecimal representation is usually more /// readable than a decimal representation. /// /// ### Example diff --git a/clippy_lints/src/loops/mod.rs b/clippy_lints/src/loops/mod.rs index 3dcb050d7..086829421 100644 --- a/clippy_lints/src/loops/mod.rs +++ b/clippy_lints/src/loops/mod.rs @@ -675,9 +675,9 @@ declare_clippy_lint! { /// Checks for infinite loops in a function where the return type is not `!` /// and lint accordingly. /// - /// ### Why is this bad? - /// A loop should be gently exited somewhere, or at least mark its parent function as - /// never return (`!`). + /// ### Why restrict this? + /// Making the return type `!` serves as documentation that the function does not return. + /// If the function is not intended to loop infinitely, then this lint may detect a bug. /// /// ### Example /// ```no_run,ignore diff --git a/clippy_lints/src/matches/mod.rs b/clippy_lints/src/matches/mod.rs index ee9f48d71..691ecd575 100644 --- a/clippy_lints/src/matches/mod.rs +++ b/clippy_lints/src/matches/mod.rs @@ -260,7 +260,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for wildcard enum matches using `_`. /// - /// ### Why is this bad? + /// ### Why restrict this? /// New enum variants added by library updates can be missed. /// /// ### Known problems @@ -435,7 +435,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for unnecessary '..' pattern binding on struct when all fields are explicitly matched. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Correctness and readability. It's like having a wildcard pattern after /// matching all enum variants explicitly. /// @@ -861,7 +861,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for usage of `Err(x)?`. /// - /// ### Why is this bad? + /// ### Why restrict this? /// The `?` operator is designed to allow calls that /// can fail to be easily chained. For example, `foo()?.bar()` or /// `foo(bar()?)`. Because `Err(x)?` can't be used that way (it will diff --git a/clippy_lints/src/matches/significant_drop_in_scrutinee.rs b/clippy_lints/src/matches/significant_drop_in_scrutinee.rs index 6ac0705ab..2f72e5983 100644 --- a/clippy_lints/src/matches/significant_drop_in_scrutinee.rs +++ b/clippy_lints/src/matches/significant_drop_in_scrutinee.rs @@ -1,12 +1,17 @@ +use std::ops::ControlFlow; + use crate::FxHashSet; use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::{indent_of, snippet}; +use clippy_utils::ty::{for_each_top_level_late_bound_region, is_copy}; use clippy_utils::{get_attr, is_lint_allowed}; +use itertools::Itertools; +use rustc_ast::Mutability; use rustc_errors::{Applicability, Diag}; use rustc_hir::intravisit::{walk_expr, Visitor}; use rustc_hir::{Arm, Expr, ExprKind, MatchSource}; use rustc_lint::{LateContext, LintContext}; -use rustc_middle::ty::{GenericArgKind, Ty}; +use rustc_middle::ty::{GenericArgKind, Region, RegionKind, Ty, TyCtxt, TypeVisitable, TypeVisitor}; use rustc_span::Span; use super::SIGNIFICANT_DROP_IN_SCRUTINEE; @@ -22,43 +27,34 @@ pub(super) fn check<'tcx>( return; } - if let Some((suggestions, message)) = has_significant_drop_in_scrutinee(cx, scrutinee, source) { - for found in suggestions { - span_lint_and_then(cx, SIGNIFICANT_DROP_IN_SCRUTINEE, found.found_span, message, |diag| { - set_diagnostic(diag, cx, expr, found); - let s = Span::new(expr.span.hi(), expr.span.hi(), expr.span.ctxt(), None); - diag.span_label(s, "temporary lives until here"); - for span in has_significant_drop_in_arms(cx, arms) { - diag.span_label(span, "another value with significant `Drop` created here"); - } - diag.note("this might lead to deadlocks or other unexpected behavior"); - }); - } + let (suggestions, message) = has_significant_drop_in_scrutinee(cx, scrutinee, source); + for found in suggestions { + span_lint_and_then(cx, SIGNIFICANT_DROP_IN_SCRUTINEE, found.found_span, message, |diag| { + set_diagnostic(diag, cx, expr, found); + let s = Span::new(expr.span.hi(), expr.span.hi(), expr.span.ctxt(), None); + diag.span_label(s, "temporary lives until here"); + for span in has_significant_drop_in_arms(cx, arms) { + diag.span_label(span, "another value with significant `Drop` created here"); + } + diag.note("this might lead to deadlocks or other unexpected behavior"); + }); } } fn set_diagnostic<'tcx>(diag: &mut Diag<'_, ()>, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, found: FoundSigDrop) { - if found.lint_suggestion == LintSuggestion::MoveAndClone { - // If our suggestion is to move and clone, then we want to leave it to the user to - // decide how to address this lint, since it may be that cloning is inappropriate. - // Therefore, we won't to emit a suggestion. - return; - } - let original = snippet(cx, found.found_span, ".."); let trailing_indent = " ".repeat(indent_of(cx, found.found_span).unwrap_or(0)); - let replacement = if found.lint_suggestion == LintSuggestion::MoveAndDerefToCopy { - format!("let value = *{original};\n{trailing_indent}") - } else if found.is_unit_return_val { - // If the return value of the expression to be moved is unit, then we don't need to - // capture the result in a temporary -- we can just replace it completely with `()`. - format!("{original};\n{trailing_indent}") - } else { - format!("let value = {original};\n{trailing_indent}") + let replacement = { + let (def_part, deref_part) = if found.is_unit_return_val { + ("", String::new()) + } else { + ("let value = ", "*".repeat(found.peel_ref_times)) + }; + format!("{def_part}{deref_part}{original};\n{trailing_indent}") }; - let suggestion_message = if found.lint_suggestion == LintSuggestion::MoveOnly { + let suggestion_message = if found.peel_ref_times == 0 { "try moving the temporary above the match" } else { "try moving the temporary above the match and create a copy" @@ -66,8 +62,11 @@ fn set_diagnostic<'tcx>(diag: &mut Diag<'_, ()>, cx: &LateContext<'tcx>, expr: & let scrutinee_replacement = if found.is_unit_return_val { "()".to_owned() - } else { + } else if found.peel_ref_times == 0 { "value".to_owned() + } else { + let ref_part = "&".repeat(found.peel_ref_times); + format!("({ref_part}value)") }; diag.multipart_suggestion( @@ -86,20 +85,18 @@ fn has_significant_drop_in_scrutinee<'tcx>( cx: &LateContext<'tcx>, scrutinee: &'tcx Expr<'tcx>, source: MatchSource, -) -> Option<(Vec, &'static str)> { +) -> (Vec, &'static str) { let mut helper = SigDropHelper::new(cx); let scrutinee = match (source, &scrutinee.kind) { (MatchSource::ForLoopDesugar, ExprKind::Call(_, [e])) => e, _ => scrutinee, }; - helper.find_sig_drop(scrutinee).map(|drops| { - let message = if source == MatchSource::Normal { - "temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression" - } else { - "temporary with significant `Drop` in `for` loop condition will live until the end of the `for` expression" - }; - (drops, message) - }) + let message = if source == MatchSource::Normal { + "temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression" + } else { + "temporary with significant `Drop` in `for` loop condition will live until the end of the `for` expression" + }; + (helper.find_sig_drop(scrutinee), message) } struct SigDropChecker<'a, 'tcx> { @@ -172,205 +169,248 @@ impl<'a, 'tcx> SigDropChecker<'a, 'tcx> { } } +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug)] +enum SigDropHolder { + /// No values with significant drop present in this expression. + /// + /// Expressions that we've emited lints do not count. + None, + /// Some field in this expression references to values with significant drop. + /// + /// Example: `(1, &data.lock().field)`. + PackedRef, + /// The value of this expression references to values with significant drop. + /// + /// Example: `data.lock().field`. + DirectRef, + /// This expression should be moved out to avoid significant drop in scrutinee. + Moved, +} + +impl Default for SigDropHolder { + fn default() -> Self { + Self::None + } +} + struct SigDropHelper<'a, 'tcx> { cx: &'a LateContext<'tcx>, - is_chain_end: bool, - has_significant_drop: bool, - current_sig_drop: Option, - sig_drop_spans: Option>, - special_handling_for_binary_op: bool, + parent_expr: Option<&'tcx Expr<'tcx>>, + sig_drop_holder: SigDropHolder, + sig_drop_spans: Vec, sig_drop_checker: SigDropChecker<'a, 'tcx>, } -#[expect(clippy::enum_variant_names)] -#[derive(Debug, PartialEq, Eq, Clone, Copy)] -enum LintSuggestion { - MoveOnly, - MoveAndDerefToCopy, - MoveAndClone, -} - -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug)] struct FoundSigDrop { found_span: Span, is_unit_return_val: bool, - lint_suggestion: LintSuggestion, + peel_ref_times: usize, } impl<'a, 'tcx> SigDropHelper<'a, 'tcx> { fn new(cx: &'a LateContext<'tcx>) -> SigDropHelper<'a, 'tcx> { SigDropHelper { cx, - is_chain_end: true, - has_significant_drop: false, - current_sig_drop: None, - sig_drop_spans: None, - special_handling_for_binary_op: false, + parent_expr: None, + sig_drop_holder: SigDropHolder::None, + sig_drop_spans: Vec::new(), sig_drop_checker: SigDropChecker::new(cx), } } - fn find_sig_drop(&mut self, match_expr: &'tcx Expr<'_>) -> Option> { + fn find_sig_drop(&mut self, match_expr: &'tcx Expr<'_>) -> Vec { self.visit_expr(match_expr); - // If sig drop spans is empty but we found a significant drop, it means that we didn't find - // a type that was trivially copyable as we moved up the chain after finding a significant - // drop, so move the entire scrutinee. - if self.has_significant_drop && self.sig_drop_spans.is_none() { - self.try_setting_current_suggestion(match_expr, true); - self.move_current_suggestion(); - } - - self.sig_drop_spans.take() + core::mem::take(&mut self.sig_drop_spans) } - fn replace_current_sig_drop( - &mut self, - found_span: Span, - is_unit_return_val: bool, - lint_suggestion: LintSuggestion, - ) { - self.current_sig_drop.replace(FoundSigDrop { + fn replace_current_sig_drop(&mut self, found_span: Span, is_unit_return_val: bool, peel_ref_times: usize) { + self.sig_drop_spans.clear(); + self.sig_drop_spans.push(FoundSigDrop { found_span, is_unit_return_val, - lint_suggestion, + peel_ref_times, }); } - /// This will try to set the current suggestion (so it can be moved into the suggestions vec - /// later). If `allow_move_and_clone` is false, the suggestion *won't* be set -- this gives us - /// an opportunity to look for another type in the chain that will be trivially copyable. - /// However, if we are at the end of the chain, we want to accept whatever is there. (The - /// suggestion won't actually be output, but the diagnostic message will be output, so the user - /// can determine the best way to handle the lint.) - fn try_setting_current_suggestion(&mut self, expr: &'tcx Expr<'_>, allow_move_and_clone: bool) { - if self.current_sig_drop.is_some() { - return; + fn try_move_sig_drop(&mut self, expr: &'tcx Expr<'_>, parent_expr: &'tcx Expr<'_>) { + if self.sig_drop_holder == SigDropHolder::Moved { + self.sig_drop_holder = SigDropHolder::None; } - let ty = self.cx.typeck_results().expr_ty(expr); - if ty.is_ref() { - // We checked that the type was ref, so builtin_deref will return Some, - // but let's avoid any chance of an ICE. - if let Some(ty) = ty.builtin_deref(true) { - if ty.is_trivially_pure_clone_copy() { - self.replace_current_sig_drop(expr.span, false, LintSuggestion::MoveAndDerefToCopy); - } else if allow_move_and_clone { - self.replace_current_sig_drop(expr.span, false, LintSuggestion::MoveAndClone); - } + + if self.sig_drop_holder == SigDropHolder::DirectRef { + self.sig_drop_holder = SigDropHolder::PackedRef; + self.try_move_sig_drop_direct_ref(expr, parent_expr); + } else if self.sig_drop_checker.is_sig_drop_expr(expr) { + // The values with significant drop can be moved to some other functions. For example, consider + // `drop(data.lock())`. We use `SigDropHolder::None` here to avoid emitting lints in such scenarios. + self.sig_drop_holder = SigDropHolder::None; + self.try_move_sig_drop_direct_ref(expr, parent_expr); + } + + if self.sig_drop_holder != SigDropHolder::None { + let parent_ty = self.cx.typeck_results().expr_ty(parent_expr); + if !ty_has_erased_regions(parent_ty) && !parent_expr.is_syntactic_place_expr() { + self.replace_current_sig_drop(parent_expr.span, parent_ty.is_unit(), 0); + self.sig_drop_holder = SigDropHolder::Moved; + } + + let (peel_ref_ty, peel_ref_times) = ty_peel_refs(parent_ty); + if !ty_has_erased_regions(peel_ref_ty) && is_copy(self.cx, peel_ref_ty) { + self.replace_current_sig_drop(parent_expr.span, peel_ref_ty.is_unit(), peel_ref_times); + self.sig_drop_holder = SigDropHolder::Moved; } - } else if ty.is_trivially_pure_clone_copy() { - self.replace_current_sig_drop(expr.span, false, LintSuggestion::MoveOnly); - } else if allow_move_and_clone { - self.replace_current_sig_drop(expr.span, false, LintSuggestion::MoveAndClone); } } - fn move_current_suggestion(&mut self) { - if let Some(current) = self.current_sig_drop.take() { - self.sig_drop_spans.get_or_insert_with(Vec::new).push(current); + fn try_move_sig_drop_direct_ref(&mut self, expr: &'tcx Expr<'_>, parent_expr: &'tcx Expr<'_>) { + let arg_idx = match parent_expr.kind { + ExprKind::MethodCall(_, receiver, exprs, _) => std::iter::once(receiver) + .chain(exprs.iter()) + .find_position(|ex| ex.hir_id == expr.hir_id) + .map(|(idx, _)| idx), + ExprKind::Call(_, exprs) => exprs + .iter() + .find_position(|ex| ex.hir_id == expr.hir_id) + .map(|(idx, _)| idx), + ExprKind::Binary(_, lhs, rhs) | ExprKind::AssignOp(_, lhs, rhs) => [lhs, rhs] + .iter() + .find_position(|ex| ex.hir_id == expr.hir_id) + .map(|(idx, _)| idx), + ExprKind::Unary(_, ex) => (ex.hir_id == expr.hir_id).then_some(0), + _ => { + // Here we assume that all other expressions create or propagate the reference to the value with + // significant drop. + self.sig_drop_holder = SigDropHolder::DirectRef; + return; + }, + }; + let Some(arg_idx) = arg_idx else { + return; + }; + + let fn_sig = if let Some(def_id) = self.cx.typeck_results().type_dependent_def_id(parent_expr.hir_id) { + self.cx.tcx.fn_sig(def_id).instantiate_identity() + } else { + return; + }; + + let input_re = if let Some(input_ty) = fn_sig.skip_binder().inputs().get(arg_idx) + && let rustc_middle::ty::Ref(input_re, _, _) = input_ty.kind() + { + input_re + } else { + return; + }; + + // Late bound lifetime parameters are not related to any constraints, so we can track them in a very + // simple manner. For other lifetime parameters, we give up and update the state to `PackedRef`. + let RegionKind::ReBound(_, input_re_bound) = input_re.kind() else { + self.sig_drop_holder = SigDropHolder::PackedRef; + return; + }; + let contains_input_re = |re_bound| { + if re_bound == input_re_bound { + ControlFlow::Break(()) + } else { + ControlFlow::Continue(()) + } + }; + + let output_ty = fn_sig.skip_binder().output(); + if let rustc_middle::ty::Ref(output_re, peel_ref_ty, _) = output_ty.kind() + && input_re == output_re + && for_each_top_level_late_bound_region(*peel_ref_ty, contains_input_re).is_continue() + { + // We're lucky! The output type is still a direct reference to the value with significant drop. + self.sig_drop_holder = SigDropHolder::DirectRef; + } else if for_each_top_level_late_bound_region(output_ty, contains_input_re).is_continue() { + // The lifetime to the value with significant drop goes away. So we can emit a lint that suggests to + // move the expression out. + self.replace_current_sig_drop(parent_expr.span, output_ty.is_unit(), 0); + self.sig_drop_holder = SigDropHolder::Moved; + } else { + // TODO: The lifetime is still there but it's for a inner type. For instance, consider + // `Some(&mutex.lock().field)`, which has a type of `Option<&u32>`. How to address this scenario? + self.sig_drop_holder = SigDropHolder::PackedRef; + } + } +} + +fn ty_peel_refs(mut ty: Ty<'_>) -> (Ty<'_>, usize) { + let mut n = 0; + while let rustc_middle::ty::Ref(_, new_ty, Mutability::Not) = ty.kind() { + ty = *new_ty; + n += 1; + } + (ty, n) +} + +fn ty_has_erased_regions(ty: Ty<'_>) -> bool { + struct V; + + impl<'tcx> TypeVisitor> for V { + type Result = ControlFlow<()>; + + fn visit_region(&mut self, region: Region<'tcx>) -> Self::Result { + if region.is_erased() { + ControlFlow::Break(()) + } else { + ControlFlow::Continue(()) + } } } - fn visit_exprs_for_binary_ops( - &mut self, - left: &'tcx Expr<'_>, - right: &'tcx Expr<'_>, - is_unit_return_val: bool, - span: Span, - ) { - self.special_handling_for_binary_op = true; - self.visit_expr(left); - self.visit_expr(right); - - // If either side had a significant drop, suggest moving the entire scrutinee to avoid - // unnecessary copies and to simplify cases where both sides have significant drops. - if self.has_significant_drop { - self.replace_current_sig_drop(span, is_unit_return_val, LintSuggestion::MoveOnly); - } - - self.special_handling_for_binary_op = false; - } + ty.visit_with(&mut V).is_break() } impl<'a, 'tcx> Visitor<'tcx> for SigDropHelper<'a, 'tcx> { fn visit_expr(&mut self, ex: &'tcx Expr<'_>) { - if !self.is_chain_end && self.sig_drop_checker.is_sig_drop_expr(ex) { - self.has_significant_drop = true; + // We've emited a lint on some neighborhood expression. That lint will suggest to move out the + // _parent_ expression (not the expression itself). Since we decide to move out the parent + // expression, it is pointless to continue to process the current expression. + if self.sig_drop_holder == SigDropHolder::Moved { return; } - self.is_chain_end = false; + + // These states are of neighborhood expressions. We save and clear them here, and we'll later merge + // the states of the current expression with them at the end of the method. + let sig_drop_holder_before = core::mem::take(&mut self.sig_drop_holder); + let sig_drop_spans_before = core::mem::take(&mut self.sig_drop_spans); + let parent_expr_before = self.parent_expr.replace(ex); match ex.kind { - ExprKind::MethodCall(_, expr, ..) => { - self.visit_expr(expr); - } - ExprKind::Binary(_, left, right) => { - self.visit_exprs_for_binary_ops(left, right, false, ex.span); - } - ExprKind::Assign(left, right, _) | ExprKind::AssignOp(_, left, right) => { - self.visit_exprs_for_binary_ops(left, right, true, ex.span); - } - ExprKind::Tup(exprs) => { - for expr in exprs { - self.visit_expr(expr); - if self.has_significant_drop { - // We may have not have set current_sig_drop if all the suggestions were - // MoveAndClone, so add this tuple item's full expression in that case. - if self.current_sig_drop.is_none() { - self.try_setting_current_suggestion(expr, true); - } + // Skip blocks because values in blocks will be dropped as usual. + ExprKind::Block(..) => (), + _ => walk_expr(self, ex), + } - // Now we are guaranteed to have something, so add it to the final vec. - self.move_current_suggestion(); - } - // Reset `has_significant_drop` after each tuple expression so we can look for - // additional cases. - self.has_significant_drop = false; - } - if self.sig_drop_spans.is_some() { - self.has_significant_drop = true; - } - } - ExprKind::Array(..) | - ExprKind::Call(..) | - ExprKind::Unary(..) | - ExprKind::If(..) | - ExprKind::Match(..) | - ExprKind::Field(..) | - ExprKind::Index(..) | - ExprKind::Ret(..) | - ExprKind::Become(..) | - ExprKind::Repeat(..) | - ExprKind::Yield(..) => walk_expr(self, ex), - ExprKind::AddrOf(_, _, _) | - ExprKind::Block(_, _) | - ExprKind::Break(_, _) | - ExprKind::Cast(_, _) | - // Don't want to check the closure itself, only invocation, which is covered by MethodCall - ExprKind::Closure { .. } | - ExprKind::ConstBlock(_) | - ExprKind::Continue(_) | - ExprKind::DropTemps(_) | - ExprKind::Err(_) | - ExprKind::InlineAsm(_) | - ExprKind::OffsetOf(_, _) | - ExprKind::Let(_) | - ExprKind::Lit(_) | - ExprKind::Loop(_, _, _, _) | - ExprKind::Path(_) | - ExprKind::Struct(_, _, _) | - ExprKind::Type(_, _) => { - return; + if let Some(parent_ex) = parent_expr_before { + match parent_ex.kind { + ExprKind::Assign(lhs, _, _) | ExprKind::AssignOp(_, lhs, _) + if lhs.hir_id == ex.hir_id && self.sig_drop_holder == SigDropHolder::Moved => + { + // Never move out only the assignee. Instead, we should always move out the whole assigment. + self.replace_current_sig_drop(parent_ex.span, true, 0); + }, + _ => { + self.try_move_sig_drop(ex, parent_ex); + }, } } - // Once a significant temporary has been found, we need to go back up at least 1 level to - // find the span to extract for replacement, so the temporary gets dropped. However, for - // binary ops, we want to move the whole scrutinee so we avoid unnecessary copies and to - // simplify cases where both sides have significant drops. - if self.has_significant_drop && !self.special_handling_for_binary_op { - self.try_setting_current_suggestion(ex, false); + self.sig_drop_holder = std::cmp::max(self.sig_drop_holder, sig_drop_holder_before); + + // We do not need those old spans in neighborhood expressions if we emit a lint that suggests to + // move out the _parent_ expression (i.e., `self.sig_drop_holder == SigDropHolder::Moved`). + if self.sig_drop_holder != SigDropHolder::Moved { + let mut sig_drop_spans = sig_drop_spans_before; + sig_drop_spans.append(&mut self.sig_drop_spans); + self.sig_drop_spans = sig_drop_spans; } + + self.parent_expr = parent_expr_before; } } diff --git a/clippy_lints/src/methods/iter_on_single_or_empty_collections.rs b/clippy_lints/src/methods/iter_on_single_or_empty_collections.rs index 6c9bdcff8..f4397212c 100644 --- a/clippy_lints/src/methods/iter_on_single_or_empty_collections.rs +++ b/clippy_lints/src/methods/iter_on_single_or_empty_collections.rs @@ -1,8 +1,12 @@ +use std::iter::once; + use clippy_utils::diagnostics::span_lint_and_sugg; use clippy_utils::source::snippet; use clippy_utils::{get_expr_use_or_unification_node, is_res_lang_ctor, path_res, std_or_core}; use rustc_errors::Applicability; +use rustc_hir::def_id::DefId; +use rustc_hir::hir_id::HirId; use rustc_hir::LangItem::{OptionNone, OptionSome}; use rustc_hir::{Expr, ExprKind, Node}; use rustc_lint::LateContext; @@ -25,7 +29,29 @@ impl IterType { } } -pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, method_name: &str, recv: &Expr<'_>) { +fn is_arg_ty_unified_in_fn<'tcx>( + cx: &LateContext<'tcx>, + fn_id: DefId, + arg_id: HirId, + args: impl IntoIterator>, +) -> bool { + let fn_sig = cx.tcx.fn_sig(fn_id).instantiate_identity(); + let arg_id_in_args = args.into_iter().position(|e| e.hir_id == arg_id).unwrap(); + let arg_ty_in_args = fn_sig.input(arg_id_in_args).skip_binder(); + + cx.tcx.predicates_of(fn_id).predicates.iter().any(|(clause, _)| { + clause + .as_projection_clause() + .and_then(|p| p.map_bound(|p| p.term.ty()).transpose()) + .is_some_and(|ty| ty.skip_binder() == arg_ty_in_args) + }) || fn_sig + .inputs() + .iter() + .enumerate() + .any(|(i, ty)| i != arg_id_in_args && ty.skip_binder().walk().any(|arg| arg.as_type() == Some(arg_ty_in_args))) +} + +pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, method_name: &str, recv: &'tcx Expr<'tcx>) { let item = match recv.kind { ExprKind::Array([]) => None, ExprKind::Array([e]) => Some(e), @@ -43,6 +69,25 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, method_name: &str, re let is_unified = match get_expr_use_or_unification_node(cx.tcx, expr) { Some((Node::Expr(parent), child_id)) => match parent.kind { ExprKind::If(e, _, _) | ExprKind::Match(e, _, _) if e.hir_id == child_id => false, + ExprKind::Call( + Expr { + kind: ExprKind::Path(path), + hir_id, + .. + }, + args, + ) => cx + .typeck_results() + .qpath_res(path, *hir_id) + .opt_def_id() + .filter(|fn_id| cx.tcx.def_kind(fn_id).is_fn_like()) + .is_some_and(|fn_id| is_arg_ty_unified_in_fn(cx, fn_id, child_id, args)), + ExprKind::MethodCall(_name, recv, args, _span) => is_arg_ty_unified_in_fn( + cx, + cx.typeck_results().type_dependent_def_id(parent.hir_id).unwrap(), + child_id, + once(recv).chain(args.iter()), + ), ExprKind::If(_, _, _) | ExprKind::Match(_, _, _) | ExprKind::Closure(_) diff --git a/clippy_lints/src/methods/mod.rs b/clippy_lints/src/methods/mod.rs index 9d67aa233..75a86c0c8 100644 --- a/clippy_lints/src/methods/mod.rs +++ b/clippy_lints/src/methods/mod.rs @@ -257,7 +257,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for `.unwrap()` or `.unwrap_err()` calls on `Result`s and `.unwrap()` call on `Option`s. /// - /// ### Why is this bad? + /// ### Why restrict this? /// It is better to handle the `None` or `Err` case, /// or at least call `.expect(_)` with a more helpful message. Still, for a lot of /// quick-and-dirty code, `unwrap` is a good choice, which is why this lint is @@ -333,7 +333,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for `.expect()` or `.expect_err()` calls on `Result`s and `.expect()` call on `Option`s. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Usually it is better to handle the `None` or `Err` case. /// Still, for a lot of quick-and-dirty code, `expect` is a good choice, which is why /// this lint is `Allow` by default. @@ -1029,8 +1029,8 @@ declare_clippy_lint! { /// (`Rc`, `Arc`, `rc::Weak`, or `sync::Weak`), and suggests calling Clone via unified /// function syntax instead (e.g., `Rc::clone(foo)`). /// - /// ### Why is this bad? - /// Calling '.clone()' on an Rc, Arc, or Weak + /// ### Why restrict this? + /// Calling `.clone()` on an `Rc`, `Arc`, or `Weak` /// can obscure the fact that only the pointer is being cloned, not the underlying /// data. /// @@ -1051,7 +1051,7 @@ declare_clippy_lint! { #[clippy::version = "pre 1.29.0"] pub CLONE_ON_REF_PTR, restriction, - "using 'clone' on a ref-counted pointer" + "using `clone` on a ref-counted pointer" } declare_clippy_lint! { @@ -1359,7 +1359,7 @@ declare_clippy_lint! { /// Checks for usage of `.get().unwrap()` (or /// `.get_mut().unwrap`) on a standard library type which implements `Index` /// - /// ### Why is this bad? + /// ### Why restrict this? /// Using the Index trait (`[]`) is more clear and more /// concise. /// @@ -1743,7 +1743,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for `FileType::is_file()`. /// - /// ### Why is this bad? + /// ### Why restrict this? /// When people testing a file type with `FileType::is_file` /// they are testing whether a path is something they can get bytes from. But /// `is_file` doesn't cover special file types in unix-like systems, and doesn't cover @@ -2688,8 +2688,9 @@ declare_clippy_lint! { /// ### What it does /// Checks for instances of `map_err(|_| Some::Enum)` /// - /// ### Why is this bad? - /// This `map_err` throws away the original error rather than allowing the enum to contain and report the cause of the error + /// ### Why restrict this? + /// This `map_err` throws away the original error rather than allowing the enum to + /// contain and report the cause of the error. /// /// ### Example /// Before: @@ -3145,7 +3146,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for usage of File::read_to_end and File::read_to_string. /// - /// ### Why is this bad? + /// ### Why restrict this? /// `fs::{read, read_to_string}` provide the same functionality when `buf` is empty with fewer imports and no intermediate values. /// See also: [fs::read docs](https://doc.rust-lang.org/std/fs/fn.read.html), [fs::read_to_string docs](https://doc.rust-lang.org/std/fs/fn.read_to_string.html) /// diff --git a/clippy_lints/src/methods/unnecessary_iter_cloned.rs b/clippy_lints/src/methods/unnecessary_iter_cloned.rs index 7431dc1cf..d1300dd43 100644 --- a/clippy_lints/src/methods/unnecessary_iter_cloned.rs +++ b/clippy_lints/src/methods/unnecessary_iter_cloned.rs @@ -38,7 +38,7 @@ pub fn check_for_loop_iter( ) -> bool { if let Some(grandparent) = get_parent_expr(cx, expr).and_then(|parent| get_parent_expr(cx, parent)) && let Some(ForLoop { pat, body, .. }) = ForLoop::hir(grandparent) - && let (clone_or_copy_needed, addr_of_exprs) = clone_or_copy_needed(cx, pat, body) + && let (clone_or_copy_needed, references_to_binding) = clone_or_copy_needed(cx, pat, body) && !clone_or_copy_needed && let Some(receiver_snippet) = snippet_opt(cx, receiver.span) { @@ -123,14 +123,12 @@ pub fn check_for_loop_iter( Applicability::MachineApplicable }; diag.span_suggestion(expr.span, "use", snippet, applicability); - for addr_of_expr in addr_of_exprs { - match addr_of_expr.kind { - ExprKind::AddrOf(_, _, referent) => { - let span = addr_of_expr.span.with_hi(referent.span.lo()); - diag.span_suggestion(span, "remove this `&`", "", applicability); - }, - _ => unreachable!(), - } + if !references_to_binding.is_empty() { + diag.multipart_suggestion( + "remove any references to the binding", + references_to_binding, + applicability, + ); } }, ); diff --git a/clippy_lints/src/methods/utils.rs b/clippy_lints/src/methods/utils.rs index 34d7b9acb..1a55b7160 100644 --- a/clippy_lints/src/methods/utils.rs +++ b/clippy_lints/src/methods/utils.rs @@ -9,6 +9,7 @@ use rustc_lint::LateContext; use rustc_middle::hir::nested_filter; use rustc_middle::ty::{self, Ty}; use rustc_span::symbol::sym; +use rustc_span::Span; pub(super) fn derefs_to_slice<'tcx>( cx: &LateContext<'tcx>, @@ -96,15 +97,15 @@ pub(super) fn clone_or_copy_needed<'tcx>( cx: &LateContext<'tcx>, pat: &Pat<'tcx>, body: &'tcx Expr<'tcx>, -) -> (bool, Vec<&'tcx Expr<'tcx>>) { +) -> (bool, Vec<(Span, String)>) { let mut visitor = CloneOrCopyVisitor { cx, binding_hir_ids: pat_bindings(pat), clone_or_copy_needed: false, - addr_of_exprs: Vec::new(), + references_to_binding: Vec::new(), }; visitor.visit_expr(body); - (visitor.clone_or_copy_needed, visitor.addr_of_exprs) + (visitor.clone_or_copy_needed, visitor.references_to_binding) } /// Returns a vector of all `HirId`s bound by the pattern. @@ -127,7 +128,7 @@ struct CloneOrCopyVisitor<'cx, 'tcx> { cx: &'cx LateContext<'tcx>, binding_hir_ids: Vec, clone_or_copy_needed: bool, - addr_of_exprs: Vec<&'tcx Expr<'tcx>>, + references_to_binding: Vec<(Span, String)>, } impl<'cx, 'tcx> Visitor<'tcx> for CloneOrCopyVisitor<'cx, 'tcx> { @@ -142,8 +143,11 @@ impl<'cx, 'tcx> Visitor<'tcx> for CloneOrCopyVisitor<'cx, 'tcx> { if self.is_binding(expr) { if let Some(parent) = get_parent_expr(self.cx, expr) { match parent.kind { - ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, _) => { - self.addr_of_exprs.push(parent); + ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, referent) => { + if !parent.span.from_expansion() { + self.references_to_binding + .push((parent.span.until(referent.span), String::new())); + } return; }, ExprKind::MethodCall(.., args, _) => { diff --git a/clippy_lints/src/min_ident_chars.rs b/clippy_lints/src/min_ident_chars.rs index c6b7f5b0c..e43b71202 100644 --- a/clippy_lints/src/min_ident_chars.rs +++ b/clippy_lints/src/min_ident_chars.rs @@ -12,14 +12,13 @@ use std::borrow::Cow; declare_clippy_lint! { /// ### What it does - /// Checks for idents which comprise of a single letter. + /// Checks for identifiers which consist of a single character (or fewer than the configured threshold). /// /// Note: This lint can be very noisy when enabled; it may be desirable to only enable it /// temporarily. /// - /// ### Why is this bad? - /// In many cases it's not, but at times it can severely hinder readability. Some codebases may - /// wish to disallow this to improve readability. + /// ### Why restrict this? + /// To improve readability by requiring that every variable has a name more specific than a single letter can be. /// /// ### Example /// ```rust,ignore diff --git a/clippy_lints/src/misc_early/mod.rs b/clippy_lints/src/misc_early/mod.rs index 2f5499d76..fedcfd11f 100644 --- a/clippy_lints/src/misc_early/mod.rs +++ b/clippy_lints/src/misc_early/mod.rs @@ -23,7 +23,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for structure field patterns bound to wildcards. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Using `..` instead is shorter and leaves the focus on /// the fields that are actually bound. /// @@ -138,7 +138,7 @@ declare_clippy_lint! { /// To enforce unseparated literal suffix style, /// see the `separated_literal_suffix` lint. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Suffix style should be consistent. /// /// ### Example @@ -166,7 +166,7 @@ declare_clippy_lint! { /// To enforce separated literal suffix style, /// see the `unseparated_literal_suffix` lint. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Suffix style should be consistent. /// /// ### Example diff --git a/clippy_lints/src/missing_assert_message.rs b/clippy_lints/src/missing_assert_message.rs index 04df7b7a7..dd98352da 100644 --- a/clippy_lints/src/missing_assert_message.rs +++ b/clippy_lints/src/missing_assert_message.rs @@ -10,7 +10,7 @@ declare_clippy_lint! { /// ### What it does /// Checks assertions without a custom panic message. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Without a good custom message, it'd be hard to understand what went wrong when the assertion fails. /// A good custom message should be more about why the failure of the assertion is problematic /// and not what is failed because the assertion already conveys that. diff --git a/clippy_lints/src/missing_asserts_for_indexing.rs b/clippy_lints/src/missing_asserts_for_indexing.rs index c29e46b94..752723a0c 100644 --- a/clippy_lints/src/missing_asserts_for_indexing.rs +++ b/clippy_lints/src/missing_asserts_for_indexing.rs @@ -21,7 +21,7 @@ declare_clippy_lint! { /// Checks for repeated slice indexing without asserting beforehand that the length /// is greater than the largest index used to index into the slice. /// - /// ### Why is this bad? + /// ### Why restrict this? /// In the general case where the compiler does not have a lot of information /// about the length of a slice, indexing it repeatedly will generate a bounds check /// for every single index. diff --git a/clippy_lints/src/missing_doc.rs b/clippy_lints/src/missing_doc.rs index 2fb784dae..ca344dc5c 100644 --- a/clippy_lints/src/missing_doc.rs +++ b/clippy_lints/src/missing_doc.rs @@ -20,9 +20,9 @@ use rustc_span::{sym, Span}; declare_clippy_lint! { /// ### What it does - /// Warns if there is missing doc for any private documentable item + /// Warns if there is missing documentation for any private documentable item. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Doc is good. *rustc* has a `MISSING_DOCS` /// allowed-by-default lint for /// public members, but has no way to enforce documentation of private items. diff --git a/clippy_lints/src/missing_inline.rs b/clippy_lints/src/missing_inline.rs index c6a764788..33a14d8b7 100644 --- a/clippy_lints/src/missing_inline.rs +++ b/clippy_lints/src/missing_inline.rs @@ -10,13 +10,16 @@ declare_clippy_lint! { /// It lints if an exported function, method, trait method with default impl, /// or trait method impl is not `#[inline]`. /// - /// ### Why is this bad? - /// In general, it is not. Functions can be inlined across - /// crates when that's profitable as long as any form of LTO is used. When LTO is disabled, - /// functions that are not `#[inline]` cannot be inlined across crates. Certain types of crates - /// might intend for most of the methods in their public API to be able to be inlined across - /// crates even when LTO is disabled. For these types of crates, enabling this lint might make - /// sense. It allows the crate to require all exported methods to be `#[inline]` by default, and + /// ### Why restrict this? + /// When a function is not marked `#[inline]`, it is not + /// [a “small” candidate for automatic inlining][small], and LTO is not in use, then it is not + /// possible for the function to be inlined into the code of any crate other than the one in + /// which it is defined. Depending on the role of the function and the relationship of the crates, + /// this could significantly reduce performance. + /// + /// Certain types of crates might intend for most of the methods in their public API to be able + /// to be inlined across crates even when LTO is disabled. + /// This lint allows those crates to require all exported methods to be `#[inline]` by default, and /// then opt out for specific methods where this might not make sense. /// /// ### Example @@ -51,6 +54,8 @@ declare_clippy_lint! { /// fn def_bar() {} // missing #[inline] /// } /// ``` + /// + /// [small]: https://github.com/rust-lang/rust/pull/116505 #[clippy::version = "pre 1.29.0"] pub MISSING_INLINE_IN_PUBLIC_ITEMS, restriction, diff --git a/clippy_lints/src/missing_trait_methods.rs b/clippy_lints/src/missing_trait_methods.rs index 6f844bc64..85029a5e6 100644 --- a/clippy_lints/src/missing_trait_methods.rs +++ b/clippy_lints/src/missing_trait_methods.rs @@ -10,16 +10,16 @@ use rustc_session::declare_lint_pass; declare_clippy_lint! { /// ### What it does /// Checks if a provided method is used implicitly by a trait - /// implementation. A usage example would be a wrapper where every method - /// should perform some operation before delegating to the inner type's /// implementation. /// + /// ### Why restrict this? + /// To ensure that a certain implementation implements every method; for example, + /// a wrapper type where every method should delegate to the corresponding method of + /// the inner type's implementation. + /// /// This lint should typically be enabled on a specific trait `impl` item /// rather than globally. /// - /// ### Why is this bad? - /// Indicates that a method is missing. - /// /// ### Example /// ```no_run /// trait Trait { diff --git a/clippy_lints/src/mixed_read_write_in_expression.rs b/clippy_lints/src/mixed_read_write_in_expression.rs index 181351910..9c5a8a0cf 100644 --- a/clippy_lints/src/mixed_read_write_in_expression.rs +++ b/clippy_lints/src/mixed_read_write_in_expression.rs @@ -12,9 +12,10 @@ declare_clippy_lint! { /// whether the read occurs before or after the write depends on the evaluation /// order of sub-expressions. /// - /// ### Why is this bad? - /// It is often confusing to read. As described [here](https://doc.rust-lang.org/reference/expressions.html?highlight=subexpression#evaluation-order-of-operands), - /// the operands of these expressions are evaluated before applying the effects of the expression. + /// ### Why restrict this? + /// While [the evaluation order of sub-expressions] is fully specified in Rust, + /// it still may be confusing to read an expression where the evaluation order + /// affects its behavior. /// /// ### Known problems /// Code which intentionally depends on the evaluation @@ -40,6 +41,8 @@ declare_clippy_lint! { /// }; /// let a = tmp + x; /// ``` + /// + /// [order]: (https://doc.rust-lang.org/reference/expressions.html?highlight=subexpression#evaluation-order-of-operands) #[clippy::version = "pre 1.29.0"] pub MIXED_READ_WRITE_IN_EXPRESSION, restriction, diff --git a/clippy_lints/src/module_style.rs b/clippy_lints/src/module_style.rs index 6c031c081..305499f9d 100644 --- a/clippy_lints/src/module_style.rs +++ b/clippy_lints/src/module_style.rs @@ -10,9 +10,9 @@ use std::path::{Component, Path}; declare_clippy_lint! { /// ### What it does - /// Checks that module layout uses only self named module files, bans `mod.rs` files. + /// Checks that module layout uses only self named module files; bans `mod.rs` files. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Having multiple module layout styles in a project can be confusing. /// /// ### Example @@ -41,7 +41,7 @@ declare_clippy_lint! { /// ### What it does /// Checks that module layout uses only `mod.rs` files. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Having multiple module layout styles in a project can be confusing. /// /// ### Example diff --git a/clippy_lints/src/multiple_unsafe_ops_per_block.rs b/clippy_lints/src/multiple_unsafe_ops_per_block.rs index 5306205ae..5b4ef852f 100644 --- a/clippy_lints/src/multiple_unsafe_ops_per_block.rs +++ b/clippy_lints/src/multiple_unsafe_ops_per_block.rs @@ -2,7 +2,7 @@ use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::visitors::{for_each_expr_with_closures, Descend, Visitable}; use core::ops::ControlFlow::Continue; use hir::def::{DefKind, Res}; -use hir::{BlockCheckMode, ExprKind, Safety, QPath, UnOp}; +use hir::{BlockCheckMode, ExprKind, QPath, Safety, UnOp}; use rustc_ast::Mutability; use rustc_hir as hir; use rustc_lint::{LateContext, LateLintPass}; @@ -15,7 +15,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for `unsafe` blocks that contain more than one unsafe operation. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Combined with `undocumented_unsafe_blocks`, /// this lint ensures that each unsafe operation must be independently justified. /// Combined with `unused_unsafe`, this lint also ensures diff --git a/clippy_lints/src/mutex_atomic.rs b/clippy_lints/src/mutex_atomic.rs index 7ecc86176..853e476a0 100644 --- a/clippy_lints/src/mutex_atomic.rs +++ b/clippy_lints/src/mutex_atomic.rs @@ -14,7 +14,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for usage of `Mutex` where an atomic will do. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Using a mutex just to make access to a plain bool or /// reference sequential is shooting flies with cannons. /// `std::sync::atomic::AtomicBool` and `std::sync::atomic::AtomicPtr` are leaner and diff --git a/clippy_lints/src/needless_bool.rs b/clippy_lints/src/needless_bool.rs index f9ee4a3dc..e1866eaa1 100644 --- a/clippy_lints/src/needless_bool.rs +++ b/clippy_lints/src/needless_bool.rs @@ -6,8 +6,8 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg}; use clippy_utils::source::snippet_with_applicability; use clippy_utils::sugg::Sugg; use clippy_utils::{ - higher, is_else_clause, is_expn_of, is_parent_stmt, peel_blocks, peel_blocks_with_stmt, span_extract_comment, - SpanlessEq, + higher, is_block_like, is_else_clause, is_expn_of, is_parent_stmt, peel_blocks, peel_blocks_with_stmt, + span_extract_comment, SpanlessEq, }; use rustc_ast::ast::LitKind; use rustc_errors::Applicability; @@ -121,14 +121,7 @@ fn condition_needs_parentheses(e: &Expr<'_>) -> bool { | ExprKind::Type(i, _) | ExprKind::Index(i, _, _) = inner.kind { - if matches!( - i.kind, - ExprKind::Block(..) - | ExprKind::ConstBlock(..) - | ExprKind::If(..) - | ExprKind::Loop(..) - | ExprKind::Match(..) - ) { + if is_block_like(i) { return true; } inner = i; diff --git a/clippy_lints/src/non_expressive_names.rs b/clippy_lints/src/non_expressive_names.rs index 7b2623529..eacfe9ff3 100644 --- a/clippy_lints/src/non_expressive_names.rs +++ b/clippy_lints/src/non_expressive_names.rs @@ -98,6 +98,10 @@ struct SimilarNamesLocalVisitor<'a, 'tcx> { impl<'a, 'tcx> SimilarNamesLocalVisitor<'a, 'tcx> { fn check_single_char_names(&self) { + if self.single_char_names.last().map(Vec::len) == Some(0) { + return; + } + let num_single_char_names = self.single_char_names.iter().flatten().count(); let threshold = self.lint.single_char_binding_names_threshold; if num_single_char_names as u64 > threshold { diff --git a/clippy_lints/src/operators/mod.rs b/clippy_lints/src/operators/mod.rs index 9e77d0e09..48a442705 100644 --- a/clippy_lints/src/operators/mod.rs +++ b/clippy_lints/src/operators/mod.rs @@ -70,7 +70,7 @@ declare_clippy_lint! { /// Known safe built-in types like `Wrapping` or `Saturating`, floats, operations in constant /// environments, allowed types and non-constant operations that won't overflow are ignored. /// - /// ### Why is this bad? + /// ### Why restrict this? /// For integers, overflow will trigger a panic in debug builds or wrap the result in /// release mode; division by zero will cause a panic in either mode. As a result, it is /// desirable to explicitly call checked, wrapping or saturating arithmetic methods. @@ -100,7 +100,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for float arithmetic. /// - /// ### Why is this bad? + /// ### Why restrict this? /// For some embedded systems or kernel development, it /// can be useful to rule out floating-point numbers. /// @@ -502,7 +502,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for division of integers /// - /// ### Why is this bad? + /// ### Why restrict this? /// When outside of some very specific algorithms, /// integer division is very often a mistake because it discards the /// remainder. @@ -596,7 +596,7 @@ declare_clippy_lint! { /// value and constant, except in functions called `*eq*` (which probably /// implement equality for a type involving floats). /// - /// ### Why is this bad? + /// ### Why restrict this? /// Floating point calculations are usually imprecise, so /// asking if two values are *exactly* equal is asking for trouble. For a good /// guide on what to do, see [the floating point @@ -653,8 +653,8 @@ declare_clippy_lint! { /// ### What it does /// Checks for modulo arithmetic. /// - /// ### Why is this bad? - /// The results of modulo (%) operation might differ + /// ### Why restrict this? + /// The results of modulo (`%`) operation might differ /// depending on the language, when negative numbers are involved. /// If you interop with different languages it might be beneficial /// to double check all places that use modulo arithmetic. diff --git a/clippy_lints/src/panic_in_result_fn.rs b/clippy_lints/src/panic_in_result_fn.rs index f821a4efe..806638c05 100644 --- a/clippy_lints/src/panic_in_result_fn.rs +++ b/clippy_lints/src/panic_in_result_fn.rs @@ -13,9 +13,9 @@ use rustc_span::{sym, Span}; declare_clippy_lint! { /// ### What it does - /// Checks for usage of `panic!` or assertions in a function of type result. + /// Checks for usage of `panic!` or assertions in a function whose return type is `Result`. /// - /// ### Why is this bad? + /// ### Why restrict this? /// For some codebases, it is desirable for functions of type result to return an error instead of crashing. Hence panicking macros should be avoided. /// /// ### Known problems diff --git a/clippy_lints/src/panic_unimplemented.rs b/clippy_lints/src/panic_unimplemented.rs index 75066c1f0..80ef76190 100644 --- a/clippy_lints/src/panic_unimplemented.rs +++ b/clippy_lints/src/panic_unimplemented.rs @@ -14,8 +14,8 @@ declare_clippy_lint! { /// ### What it does /// Checks for usage of `panic!`. /// - /// ### Why is this bad? - /// `panic!` will stop the execution of the executable. + /// ### Why restrict this? + /// This macro, or panics in general, may be unwanted in production code. /// /// ### Example /// ```no_run @@ -31,8 +31,8 @@ declare_clippy_lint! { /// ### What it does /// Checks for usage of `unimplemented!`. /// - /// ### Why is this bad? - /// This macro should not be present in production code. + /// ### Why restrict this? + /// This macro, or panics in general, may be unwanted in production code. /// /// ### Example /// ```no_run @@ -48,9 +48,9 @@ declare_clippy_lint! { /// ### What it does /// Checks for usage of `todo!`. /// - /// ### Why is this bad? - /// The `todo!` macro is often used for unfinished code, and it causes - /// code to panic. It should not be present in production code. + /// ### Why restrict this? + /// The `todo!` macro indicates the presence of unfinished code, + /// so it should not be present in production code. /// /// ### Example /// ```no_run @@ -70,8 +70,8 @@ declare_clippy_lint! { /// ### What it does /// Checks for usage of `unreachable!`. /// - /// ### Why is this bad? - /// This macro can cause code to panic. + /// ### Why restrict this? + /// This macro, or panics in general, may be unwanted in production code. /// /// ### Example /// ```no_run diff --git a/clippy_lints/src/partial_pub_fields.rs b/clippy_lints/src/partial_pub_fields.rs index ffa403e27..2d20cbea6 100644 --- a/clippy_lints/src/partial_pub_fields.rs +++ b/clippy_lints/src/partial_pub_fields.rs @@ -5,15 +5,16 @@ use rustc_session::declare_lint_pass; declare_clippy_lint! { /// ### What it does - /// Checks whether partial fields of a struct are public. + /// Checks whether some but not all fields of a `struct` are public. /// /// Either make all fields of a type public, or make none of them public /// - /// ### Why is this bad? + /// ### Why restrict this? /// Most types should either be: /// * Abstract data types: complex objects with opaque implementation which guard - /// interior invariants and expose intentionally limited API to the outside world. - /// * Data: relatively simple objects which group a bunch of related attributes together. + /// interior invariants and expose intentionally limited API to the outside world. + /// * Data: relatively simple objects which group a bunch of related attributes together, + /// but have no invariants. /// /// ### Example /// ```no_run diff --git a/clippy_lints/src/pattern_type_mismatch.rs b/clippy_lints/src/pattern_type_mismatch.rs index 44db061b8..9661a57b8 100644 --- a/clippy_lints/src/pattern_type_mismatch.rs +++ b/clippy_lints/src/pattern_type_mismatch.rs @@ -30,9 +30,8 @@ declare_clippy_lint! { /// this lint can still be used to highlight areas of interest and ensure a good understanding /// of ownership semantics. /// - /// ### Why is this bad? - /// It isn't bad in general. But in some contexts it can be desirable - /// because it increases ownership hints in the code, and will guard against some changes + /// ### Why restrict this? + /// It increases ownership hints in the code, and will guard against some changes /// in ownership. /// /// ### Example diff --git a/clippy_lints/src/pub_use.rs b/clippy_lints/src/pub_use.rs index c0e999e76..ab8f8a168 100644 --- a/clippy_lints/src/pub_use.rs +++ b/clippy_lints/src/pub_use.rs @@ -5,13 +5,11 @@ use rustc_session::declare_lint_pass; declare_clippy_lint! { /// ### What it does - /// /// Restricts the usage of `pub use ...` /// - /// ### Why is this bad? - /// - /// `pub use` is usually fine, but a project may wish to limit `pub use` instances to prevent - /// unintentional exports or to encourage placing exported items directly in public modules + /// ### Why restrict this? + /// A project may wish to limit `pub use` instances to prevent + /// unintentional exports, or to encourage placing exported items directly in public modules. /// /// ### Example /// ```no_run diff --git a/clippy_lints/src/question_mark_used.rs b/clippy_lints/src/question_mark_used.rs index ddfc53083..f5e6cb804 100644 --- a/clippy_lints/src/question_mark_used.rs +++ b/clippy_lints/src/question_mark_used.rs @@ -9,7 +9,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for expressions that use the question mark operator and rejects them. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Sometimes code wants to avoid the question mark operator because for instance a local /// block requires a macro to re-throw errors to attach additional information to the /// error. diff --git a/clippy_lints/src/raw_strings.rs b/clippy_lints/src/raw_strings.rs index 7e71f48c6..3a0042454 100644 --- a/clippy_lints/src/raw_strings.rs +++ b/clippy_lints/src/raw_strings.rs @@ -15,8 +15,10 @@ declare_clippy_lint! { /// ### What it does /// Checks for raw string literals where a string literal can be used instead. /// - /// ### Why is this bad? - /// It's just unnecessary, but there are many cases where using a raw string literal is more + /// ### Why restrict this? + /// For consistent style by using simpler string literals whenever possible. + /// + /// However, there are many cases where using a raw string literal is more /// idiomatic than a string literal, so it's opt-in. /// /// ### Example diff --git a/clippy_lints/src/redundant_slicing.rs b/clippy_lints/src/redundant_slicing.rs index c99b657c2..7f87d18e5 100644 --- a/clippy_lints/src/redundant_slicing.rs +++ b/clippy_lints/src/redundant_slicing.rs @@ -46,7 +46,7 @@ declare_clippy_lint! { /// Checks for slicing expressions which are equivalent to dereferencing the /// value. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Some people may prefer to dereference rather than slice. /// /// ### Example diff --git a/clippy_lints/src/redundant_type_annotations.rs b/clippy_lints/src/redundant_type_annotations.rs index 11b95ee3a..81556f396 100644 --- a/clippy_lints/src/redundant_type_annotations.rs +++ b/clippy_lints/src/redundant_type_annotations.rs @@ -11,7 +11,7 @@ declare_clippy_lint! { /// ### What it does /// Warns about needless / redundant type annotations. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Code without type annotations is shorter and in most cases /// more idiomatic and easier to modify. /// diff --git a/clippy_lints/src/ref_patterns.rs b/clippy_lints/src/ref_patterns.rs index 607a0740b..467038523 100644 --- a/clippy_lints/src/ref_patterns.rs +++ b/clippy_lints/src/ref_patterns.rs @@ -6,9 +6,11 @@ use rustc_session::declare_lint_pass; declare_clippy_lint! { /// ### What it does /// Checks for usages of the `ref` keyword. - /// ### Why is this bad? + /// + /// ### Why restrict this? /// The `ref` keyword can be confusing for people unfamiliar with it, and often /// it is more concise to use `&` instead. + /// /// ### Example /// ```no_run /// let opt = Some(5); diff --git a/clippy_lints/src/returns.rs b/clippy_lints/src/returns.rs index e8f9d4381..48d6fb3c0 100644 --- a/clippy_lints/src/returns.rs +++ b/clippy_lints/src/returns.rs @@ -3,8 +3,8 @@ use clippy_utils::source::{snippet_opt, snippet_with_context}; use clippy_utils::sugg::has_enclosing_paren; use clippy_utils::visitors::{for_each_expr_with_closures, Descend}; use clippy_utils::{ - fn_def_id, is_from_proc_macro, is_inside_let_else, is_res_lang_ctor, path_res, path_to_local_id, span_contains_cfg, - span_find_starting_semi, + binary_expr_needs_parentheses, fn_def_id, is_from_proc_macro, is_inside_let_else, is_res_lang_ctor, path_res, + path_to_local_id, span_contains_cfg, span_find_starting_semi, }; use core::ops::ControlFlow; use rustc_errors::Applicability; @@ -129,7 +129,7 @@ enum RetReplacement<'tcx> { Empty, Block, Unit, - IfSequence(Cow<'tcx, str>, Applicability), + NeedsPar(Cow<'tcx, str>, Applicability), Expr(Cow<'tcx, str>, Applicability), } @@ -139,13 +139,13 @@ impl<'tcx> RetReplacement<'tcx> { Self::Empty | Self::Expr(..) => "remove `return`", Self::Block => "replace `return` with an empty block", Self::Unit => "replace `return` with a unit value", - Self::IfSequence(..) => "remove `return` and wrap the sequence with parentheses", + Self::NeedsPar(..) => "remove `return` and wrap the sequence with parentheses", } } fn applicability(&self) -> Applicability { match self { - Self::Expr(_, ap) | Self::IfSequence(_, ap) => *ap, + Self::Expr(_, ap) | Self::NeedsPar(_, ap) => *ap, _ => Applicability::MachineApplicable, } } @@ -157,7 +157,7 @@ impl<'tcx> Display for RetReplacement<'tcx> { Self::Empty => write!(f, ""), Self::Block => write!(f, "{{}}"), Self::Unit => write!(f, "()"), - Self::IfSequence(inner, _) => write!(f, "({inner})"), + Self::NeedsPar(inner, _) => write!(f, "({inner})"), Self::Expr(inner, _) => write!(f, "{inner}"), } } @@ -244,7 +244,11 @@ impl<'tcx> LateLintPass<'tcx> for Return { err.span_label(local.span, "unnecessary `let` binding"); if let Some(mut snippet) = snippet_opt(cx, initexpr.span) { - if !cx.typeck_results().expr_adjustments(retexpr).is_empty() { + if binary_expr_needs_parentheses(initexpr) { + if !has_enclosing_paren(&snippet) { + snippet = format!("({snippet})"); + } + } else if !cx.typeck_results().expr_adjustments(retexpr).is_empty() { if !has_enclosing_paren(&snippet) { snippet = format!("({snippet})"); } @@ -349,8 +353,8 @@ fn check_final_expr<'tcx>( let mut applicability = Applicability::MachineApplicable; let (snippet, _) = snippet_with_context(cx, inner_expr.span, ret_span.ctxt(), "..", &mut applicability); - if expr_contains_conjunctive_ifs(inner_expr) { - RetReplacement::IfSequence(snippet, applicability) + if binary_expr_needs_parentheses(inner_expr) { + RetReplacement::NeedsPar(snippet, applicability) } else { RetReplacement::Expr(snippet, applicability) } @@ -404,18 +408,6 @@ fn check_final_expr<'tcx>( } } -fn expr_contains_conjunctive_ifs<'tcx>(expr: &'tcx Expr<'tcx>) -> bool { - fn contains_if(expr: &Expr<'_>, on_if: bool) -> bool { - match expr.kind { - ExprKind::If(..) => on_if, - ExprKind::Binary(_, left, right) => contains_if(left, true) || contains_if(right, true), - _ => false, - } - } - - contains_if(expr, false) -} - fn emit_return_lint( cx: &LateContext<'_>, ret_span: Span, diff --git a/clippy_lints/src/same_name_method.rs b/clippy_lints/src/same_name_method.rs index 7e27f70bc..8fdd19c54 100644 --- a/clippy_lints/src/same_name_method.rs +++ b/clippy_lints/src/same_name_method.rs @@ -14,7 +14,7 @@ declare_clippy_lint! { /// It lints if a struct has two methods with the same name: /// one from a trait, another not from trait. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Confusing. /// /// ### Example diff --git a/clippy_lints/src/semicolon_block.rs b/clippy_lints/src/semicolon_block.rs index 0b3adfb7a..0e77acdfd 100644 --- a/clippy_lints/src/semicolon_block.rs +++ b/clippy_lints/src/semicolon_block.rs @@ -11,8 +11,7 @@ declare_clippy_lint! { /// Suggests moving the semicolon after a block to the inside of the block, after its last /// expression. /// - /// ### Why is this bad? - /// + /// ### Why restrict this? /// For consistency it's best to have the semicolon inside/outside the block. Either way is fine /// and this lint suggests inside the block. /// Take a look at `semicolon_outside_block` for the other alternative. @@ -40,8 +39,7 @@ declare_clippy_lint! { /// /// Suggests moving the semicolon from a block's final expression outside of the block. /// - /// ### Why is this bad? - /// + /// ### Why restrict this? /// For consistency it's best to have the semicolon inside/outside the block. Either way is fine /// and this lint suggests outside the block. /// Take a look at `semicolon_inside_block` for the other alternative. diff --git a/clippy_lints/src/shadow.rs b/clippy_lints/src/shadow.rs index 9db08acb0..80f5fd0b4 100644 --- a/clippy_lints/src/shadow.rs +++ b/clippy_lints/src/shadow.rs @@ -15,10 +15,10 @@ declare_clippy_lint! { /// Checks for bindings that shadow other bindings already in /// scope, while just changing reference level or mutability. /// - /// ### Why is this bad? - /// Not much, in fact it's a very common pattern in Rust - /// code. Still, some may opt to avoid it in their code base, they can set this - /// lint to `Warn`. + /// ### Why restrict this? + /// To require that what are formally distinct variables be given distinct names. + /// + /// See also `shadow_reuse` and `shadow_unrelated` for other restrictions on shadowing. /// /// ### Example /// ```no_run @@ -42,12 +42,13 @@ declare_clippy_lint! { /// Checks for bindings that shadow other bindings already in /// scope, while reusing the original value. /// - /// ### Why is this bad? - /// Not too much, in fact it's a common pattern in Rust - /// code. Still, some argue that name shadowing like this hurts readability, + /// ### Why restrict this? + /// Some argue that name shadowing like this hurts readability, /// because a value may be bound to different things depending on position in /// the code. /// + /// See also `shadow_same` and `shadow_unrelated` for other restrictions on shadowing. + /// /// ### Example /// ```no_run /// let x = 2; @@ -70,11 +71,14 @@ declare_clippy_lint! { /// scope, either without an initialization or with one that does not even use /// the original value. /// - /// ### Why is this bad? - /// Name shadowing can hurt readability, especially in + /// ### Why restrict this? + /// Shadowing a binding with a closely related one is part of idiomatic Rust, + /// but shadowing a binding by accident with an unrelated one may indicate a mistake. + /// + /// Additionally, name shadowing in general can hurt readability, especially in /// large code bases, because it is easy to lose track of the active binding at - /// any place in the code. This can be alleviated by either giving more specific - /// names to bindings or introducing more scopes to contain the bindings. + /// any place in the code. If linting against all shadowing is desired, you may wish + /// to use the `shadow_same` and `shadow_reuse` lints as well. /// /// ### Example /// ```no_run diff --git a/clippy_lints/src/single_call_fn.rs b/clippy_lints/src/single_call_fn.rs index f8e09d517..23bbeae0e 100644 --- a/clippy_lints/src/single_call_fn.rs +++ b/clippy_lints/src/single_call_fn.rs @@ -13,13 +13,20 @@ declare_clippy_lint! { /// ### What it does /// Checks for functions that are only used once. Does not lint tests. /// - /// ### Why is this bad? - /// It's usually not, splitting a function into multiple parts often improves readability and in - /// the case of generics, can prevent the compiler from duplicating the function dozens of - /// time; instead, only duplicating a thunk. But this can prevent segmentation across a - /// codebase, where many small functions are used only once. + /// ### Why restrict this? + /// If a function is only used once (perhaps because it used to be used more widely), + /// then the code could be simplified by moving that function's code into its caller. /// - /// Note: If this lint is used, prepare to allow this a lot. + /// However, there are reasons not to do this everywhere: + /// + /// * Splitting a large function into multiple parts often improves readability + /// by giving names to its parts. + /// * A function’s signature might serve a necessary purpose, such as constraining + /// the type of a closure passed to it. + /// * Generic functions might call non-generic functions to reduce duplication + /// in the produced machine code. + /// + /// If this lint is used, prepare to `#[allow]` it a lot. /// /// ### Example /// ```no_run diff --git a/clippy_lints/src/single_char_lifetime_names.rs b/clippy_lints/src/single_char_lifetime_names.rs index 42f1564db..72feb977c 100644 --- a/clippy_lints/src/single_char_lifetime_names.rs +++ b/clippy_lints/src/single_char_lifetime_names.rs @@ -9,11 +9,10 @@ declare_clippy_lint! { /// Checks for lifetimes with names which are one character /// long. /// - /// ### Why is this bad? + /// ### Why restrict this? /// A single character is likely not enough to express the /// purpose of a lifetime. Using a longer name can make code - /// easier to understand, especially for those who are new to - /// Rust. + /// easier to understand. /// /// ### Known problems /// Rust programmers and learning resources tend to use single diff --git a/clippy_lints/src/std_instead_of_core.rs b/clippy_lints/src/std_instead_of_core.rs index 926c56332..12b70075a 100644 --- a/clippy_lints/src/std_instead_of_core.rs +++ b/clippy_lints/src/std_instead_of_core.rs @@ -12,11 +12,9 @@ use rustc_span::{sym, Span}; declare_clippy_lint! { /// ### What it does - /// /// Finds items imported through `std` when available through `core`. /// - /// ### Why is this bad? - /// + /// ### Why restrict this? /// Crates which have `no_std` compatibility may wish to ensure types are imported from core to ensure /// disabling `std` does not cause the crate to fail to compile. This lint is also useful for crates /// migrating to become `no_std` compatible. @@ -37,11 +35,9 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does - /// /// Finds items imported through `std` when available through `alloc`. /// - /// ### Why is this bad? - /// + /// ### Why restrict this? /// Crates which have `no_std` compatibility and require alloc may wish to ensure types are imported from /// alloc to ensure disabling `std` does not cause the crate to fail to compile. This lint is also useful /// for crates migrating to become `no_std` compatible. @@ -63,11 +59,9 @@ declare_clippy_lint! { declare_clippy_lint! { /// ### What it does - /// /// Finds items imported through `alloc` when available through `core`. /// - /// ### Why is this bad? - /// + /// ### Why restrict this? /// Crates which have `no_std` compatibility and may optionally require alloc may wish to ensure types are /// imported from core to ensure disabling `alloc` does not cause the crate to fail to compile. This lint /// is also useful for crates migrating to become `no_std` compatible. diff --git a/clippy_lints/src/strings.rs b/clippy_lints/src/strings.rs index 292124196..f8d36d6b9 100644 --- a/clippy_lints/src/strings.rs +++ b/clippy_lints/src/strings.rs @@ -45,10 +45,10 @@ declare_clippy_lint! { /// `String`, but only if [`string_add_assign`](#string_add_assign) does *not* /// match. /// - /// ### Why is this bad? - /// It's not bad in and of itself. However, this particular + /// ### Why restrict this? + /// This particular /// `Add` implementation is asymmetric (the other operand need not be `String`, - /// but `x` does), while addition as mathematically defined is symmetric, also + /// but `x` does), while addition as mathematically defined is symmetric, and /// the `String::push_str(_)` function is a perfectly good replacement. /// Therefore, some dislike it and wish not to have it in their code. /// @@ -123,7 +123,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for slice operations on strings /// - /// ### Why is this bad? + /// ### Why restrict this? /// UTF-8 characters span multiple bytes, and it is easy to inadvertently confuse character /// counts and string indices. This may lead to panics, and should warrant some test cases /// containing wide UTF-8 characters. This lint is most useful in code that should avoid @@ -364,10 +364,10 @@ declare_clippy_lint! { /// ### What it does /// This lint checks for `.to_string()` method calls on values of type `&str`. /// - /// ### Why is this bad? + /// ### Why restrict this? /// The `to_string` method is also used on other types to convert them to a string. - /// When called on a `&str` it turns the `&str` into the owned variant `String`, which can be better - /// expressed with `.to_owned()`. + /// When called on a `&str` it turns the `&str` into the owned variant `String`, which can be + /// more specifically expressed with `.to_owned()`. /// /// ### Example /// ```no_run @@ -415,9 +415,10 @@ declare_clippy_lint! { /// ### What it does /// This lint checks for `.to_string()` method calls on values of type `String`. /// - /// ### Why is this bad? + /// ### Why restrict this? /// The `to_string` method is also used on other types to convert them to a string. - /// When called on a `String` it only clones the `String`, which can be better expressed with `.clone()`. + /// When called on a `String` it only clones the `String`, which can be more specifically + /// expressed with `.clone()`. /// /// ### Example /// ```no_run diff --git a/clippy_lints/src/suspicious_xor_used_as_pow.rs b/clippy_lints/src/suspicious_xor_used_as_pow.rs index 1cc27670f..d150a5f85 100644 --- a/clippy_lints/src/suspicious_xor_used_as_pow.rs +++ b/clippy_lints/src/suspicious_xor_used_as_pow.rs @@ -11,8 +11,10 @@ use rustc_session::declare_lint_pass; declare_clippy_lint! { /// ### What it does /// Warns for a Bitwise XOR (`^`) operator being probably confused as a powering. It will not trigger if any of the numbers are not in decimal. - /// ### Why is this bad? + /// + /// ### Why restrict this? /// It's most probably a typo and may lead to unexpected behaviours. + /// /// ### Example /// ```no_run /// let x = 3_i32 ^ 4_i32; diff --git a/clippy_lints/src/tests_outside_test_module.rs b/clippy_lints/src/tests_outside_test_module.rs index da5575826..58e42892c 100644 --- a/clippy_lints/src/tests_outside_test_module.rs +++ b/clippy_lints/src/tests_outside_test_module.rs @@ -11,9 +11,11 @@ declare_clippy_lint! { /// ### What it does /// Triggers when a testing function (marked with the `#[test]` attribute) isn't inside a testing module /// (marked with `#[cfg(test)]`). - /// ### Why is this bad? + /// + /// ### Why restrict this? /// The idiomatic (and more performant) way of writing tests is inside a testing module (flagged with `#[cfg(test)]`), /// having test functions outside of this module is confusing and may lead to them being "hidden". + /// /// ### Example /// ```no_run /// #[test] diff --git a/clippy_lints/src/types/mod.rs b/clippy_lints/src/types/mod.rs index 5e45ab211..62ef65ca1 100644 --- a/clippy_lints/src/types/mod.rs +++ b/clippy_lints/src/types/mod.rs @@ -217,7 +217,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for `Rc` and `Arc` when `T` is a mutable buffer type such as `String` or `Vec`. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Expressions such as `Rc` usually have no advantage over `Rc`, since /// it is larger and involves an extra level of indirection, and doesn't implement `Borrow`. /// @@ -274,7 +274,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for `Rc>`. /// - /// ### Why is this bad? + /// ### Why restrict this? /// `Rc` is used in single thread and `Mutex` is used in multi thread. /// Consider using `Rc>` in single thread or `Arc>` in multi thread. /// diff --git a/clippy_lints/src/undocumented_unsafe_blocks.rs b/clippy_lints/src/undocumented_unsafe_blocks.rs index 4120bb133..6cf9229fd 100644 --- a/clippy_lints/src/undocumented_unsafe_blocks.rs +++ b/clippy_lints/src/undocumented_unsafe_blocks.rs @@ -38,10 +38,9 @@ declare_clippy_lint! { /// ); /// ``` /// - /// ### Why is this bad? - /// Undocumented unsafe blocks and impls can make it difficult to - /// read and maintain code, as well as uncover unsoundness - /// and bugs. + /// ### Why restrict this? + /// Undocumented unsafe blocks and impls can make it difficult to read and maintain code. + /// Writing out the safety justification may help in discovering unsoundness or bugs. /// /// ### Example /// ```no_run @@ -67,7 +66,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for `// SAFETY: ` comments on safe code. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Safe code has no safety requirements, so there is no need to /// describe safety invariants. /// diff --git a/clippy_lints/src/unicode.rs b/clippy_lints/src/unicode.rs index 3d319b9fe..d42697b31 100644 --- a/clippy_lints/src/unicode.rs +++ b/clippy_lints/src/unicode.rs @@ -31,7 +31,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for non-ASCII characters in string and char literals. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Yeah, we know, the 90's called and wanted their charset /// back. Even so, there still are editors and other programs out there that /// don't work well with Unicode. So if the code is meant to be used diff --git a/clippy_lints/src/unnecessary_self_imports.rs b/clippy_lints/src/unnecessary_self_imports.rs index 528a1dfcf..93dff1b85 100644 --- a/clippy_lints/src/unnecessary_self_imports.rs +++ b/clippy_lints/src/unnecessary_self_imports.rs @@ -9,7 +9,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for imports ending in `::{self}`. /// - /// ### Why is this bad? + /// ### Why restrict this? /// In most cases, this can be written much more cleanly by omitting `::{self}`. /// /// ### Known problems diff --git a/clippy_lints/src/unwrap_in_result.rs b/clippy_lints/src/unwrap_in_result.rs index aca500590..197ab0f17 100644 --- a/clippy_lints/src/unwrap_in_result.rs +++ b/clippy_lints/src/unwrap_in_result.rs @@ -13,8 +13,10 @@ declare_clippy_lint! { /// ### What it does /// Checks for functions of type `Result` that contain `expect()` or `unwrap()` /// - /// ### Why is this bad? - /// These functions promote recoverable errors to non-recoverable errors which may be undesirable in code bases which wish to avoid panics. + /// ### Why restrict this? + /// These functions promote recoverable errors to non-recoverable errors, + /// which may be undesirable in code bases which wish to avoid panics, + /// or be a bug in the specific function. /// /// ### Known problems /// This can cause false positives in functions that handle both recoverable and non recoverable errors. diff --git a/clippy_lints/src/visibility.rs b/clippy_lints/src/visibility.rs index 9818b98dd..11dcceca7 100644 --- a/clippy_lints/src/visibility.rs +++ b/clippy_lints/src/visibility.rs @@ -32,7 +32,7 @@ declare_clippy_lint! { /// ### What it does /// Checks for usage of `pub()` with `in`. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Consistency. Use it or don't, just be consistent about it. /// /// Also see the `pub_without_shorthand` lint for an alternative. @@ -57,7 +57,7 @@ declare_clippy_lint! { /// Note: As you cannot write a module's path in `pub()`, this will only trigger on /// `pub(super)` and the like. /// - /// ### Why is this bad? + /// ### Why restrict this? /// Consistency. Use it or don't, just be consistent about it. /// /// Also see the `pub_with_shorthand` lint for an alternative. diff --git a/clippy_lints/src/write.rs b/clippy_lints/src/write.rs index ff6ee0d10..652ce88bd 100644 --- a/clippy_lints/src/write.rs +++ b/clippy_lints/src/write.rs @@ -66,7 +66,7 @@ declare_clippy_lint! { /// Checks for printing on *stdout*. The purpose of this lint /// is to catch debugging remnants. /// - /// ### Why is this bad? + /// ### Why restrict this? /// People often print on *stdout* while debugging an /// application and might forget to remove those prints afterward. /// @@ -88,7 +88,7 @@ declare_clippy_lint! { /// Checks for printing on *stderr*. The purpose of this lint /// is to catch debugging remnants. /// - /// ### Why is this bad? + /// ### Why restrict this? /// People often print on *stderr* while debugging an /// application and might forget to remove those prints afterward. /// @@ -110,15 +110,18 @@ declare_clippy_lint! { /// Checks for usage of `Debug` formatting. The purpose of this /// lint is to catch debugging remnants. /// - /// ### Why is this bad? - /// The purpose of the `Debug` trait is to facilitate - /// debugging Rust code. It should not be used in user-facing output. + /// ### Why restrict this? + /// The purpose of the `Debug` trait is to facilitate debugging Rust code, + /// and [no guarantees are made about its output][stability]. + /// It should not be used in user-facing output. /// /// ### Example /// ```no_run /// # let foo = "bar"; /// println!("{:?}", foo); /// ``` + /// + /// [stability]: https://doc.rust-lang.org/stable/std/fmt/trait.Debug.html#stability #[clippy::version = "pre 1.29.0"] pub USE_DEBUG, restriction, diff --git a/clippy_utils/src/consts.rs b/clippy_utils/src/consts.rs index f5d3967d1..cd88ccd87 100644 --- a/clippy_utils/src/consts.rs +++ b/clippy_utils/src/consts.rs @@ -412,8 +412,7 @@ impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> { /// Simple constant folding: Insert an expression, get a constant or none. pub fn expr(&mut self, e: &Expr<'_>) -> Option> { match e.kind { - ExprKind::ConstBlock(e) | - ExprKind::DropTemps(e) => self.expr(e), + ExprKind::ConstBlock(e) | ExprKind::DropTemps(e) => self.expr(e), ExprKind::Path(ref qpath) => { self.fetch_path_and_apply(qpath, e.hir_id, self.typeck_results.expr_ty(e), |this, result| { let result = mir_to_const(this.lcx, result)?; @@ -491,8 +490,7 @@ impl<'a, 'tcx> ConstEvalLateContext<'a, 'tcx> { /// leaves the local crate. pub fn expr_is_empty(&mut self, e: &Expr<'_>) -> Option { match e.kind { - ExprKind::ConstBlock(e) | - ExprKind::DropTemps(e) => self.expr_is_empty(e), + ExprKind::ConstBlock(e) | ExprKind::DropTemps(e) => self.expr_is_empty(e), ExprKind::Path(ref qpath) => { if !self .typeck_results diff --git a/clippy_utils/src/hir_utils.rs b/clippy_utils/src/hir_utils.rs index cc5ccd405..817d4095e 100644 --- a/clippy_utils/src/hir_utils.rs +++ b/clippy_utils/src/hir_utils.rs @@ -769,7 +769,7 @@ impl<'a, 'tcx> SpanlessHash<'a, 'tcx> { // closures inherit TypeckResults self.hash_expr(self.cx.tcx.hir().body(body).value); }, - ExprKind::ConstBlock(ref l_id) => { + ExprKind::ConstBlock(l_id) => { self.hash_expr(l_id); }, ExprKind::DropTemps(e) | ExprKind::Yield(e, _) => { diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 94d465637..b10830b24 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -3370,3 +3370,25 @@ pub fn is_parent_stmt(cx: &LateContext<'_>, id: HirId) -> bool { Node::Stmt(..) | Node::Block(Block { stmts: &[], .. }) ) } + +/// Returns true if the given `expr` is a block or resembled as a block, +/// such as `if`, `loop`, `match` expressions etc. +pub fn is_block_like(expr: &Expr<'_>) -> bool { + matches!( + expr.kind, + ExprKind::Block(..) | ExprKind::ConstBlock(..) | ExprKind::If(..) | ExprKind::Loop(..) | ExprKind::Match(..) + ) +} + +/// Returns true if the given `expr` is binary expression that needs to be wrapped in parentheses. +pub fn binary_expr_needs_parentheses(expr: &Expr<'_>) -> bool { + fn contains_block(expr: &Expr<'_>, is_operand: bool) -> bool { + match expr.kind { + ExprKind::Binary(_, lhs, _) => contains_block(lhs, true), + _ if is_block_like(expr) => is_operand, + _ => false, + } + } + + contains_block(expr, false) +} diff --git a/clippy_utils/src/ty.rs b/clippy_utils/src/ty.rs index 2dacc3486..3414b5ef6 100644 --- a/clippy_utils/src/ty.rs +++ b/clippy_utils/src/ty.rs @@ -9,7 +9,7 @@ use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_hir as hir; use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res}; use rustc_hir::def_id::DefId; -use rustc_hir::{Expr, FnDecl, Safety, LangItem, TyKind}; +use rustc_hir::{Expr, FnDecl, LangItem, Safety, TyKind}; use rustc_infer::infer::TyCtxtInferExt; use rustc_lint::LateContext; use rustc_middle::mir::interpret::Scalar; @@ -18,8 +18,8 @@ use rustc_middle::traits::EvaluationResult; use rustc_middle::ty::layout::ValidityRequirement; use rustc_middle::ty::{ self, AdtDef, AliasTy, AssocKind, Binder, BoundRegion, FnSig, GenericArg, GenericArgKind, GenericArgsRef, - GenericParamDefKind, IntTy, ParamEnv, Region, RegionKind, Upcast, TraitRef, Ty, TyCtxt, - TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor, UintTy, VariantDef, VariantDiscr, + GenericParamDefKind, IntTy, ParamEnv, Region, RegionKind, TraitRef, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, + TypeVisitableExt, TypeVisitor, UintTy, Upcast, VariantDef, VariantDiscr, }; use rustc_span::symbol::Ident; use rustc_span::{sym, Span, Symbol, DUMMY_SP}; diff --git a/rust-toolchain b/rust-toolchain index a0585ffdb..dd8b9ece7 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1,3 +1,3 @@ [toolchain] -channel = "nightly-2024-05-16" +channel = "nightly-2024-05-30" components = ["cargo", "llvm-tools", "rust-src", "rust-std", "rustc", "rustc-dev", "rustfmt"] diff --git a/tests/ui-internal/disallow_span_lint.rs b/tests/ui-internal/disallow_span_lint.rs index b91a83308..ca71dddcc 100644 --- a/tests/ui-internal/disallow_span_lint.rs +++ b/tests/ui-internal/disallow_span_lint.rs @@ -11,11 +11,15 @@ use rustc_lint::{Lint, LintContext}; use rustc_middle::ty::TyCtxt; pub fn a(cx: impl LintContext, lint: &'static Lint, span: impl Into, msg: impl Into) { - cx.span_lint(lint, span, |lint| { lint.primary_message(msg); }); + cx.span_lint(lint, span, |lint| { + lint.primary_message(msg); + }); } pub fn b(tcx: TyCtxt<'_>, lint: &'static Lint, hir_id: HirId, span: impl Into, msg: impl Into) { - tcx.node_span_lint(lint, hir_id, span, |lint| { lint.primary_message(msg); }); + tcx.node_span_lint(lint, hir_id, span, |lint| { + lint.primary_message(msg); + }); } fn main() {} diff --git a/tests/ui-internal/disallow_span_lint.stderr b/tests/ui-internal/disallow_span_lint.stderr index 1cfbc8efc..1be4b665b 100644 --- a/tests/ui-internal/disallow_span_lint.stderr +++ b/tests/ui-internal/disallow_span_lint.stderr @@ -1,18 +1,22 @@ error: use of a disallowed method `rustc_lint::context::LintContext::span_lint` --> tests/ui-internal/disallow_span_lint.rs:14:5 | -LL | cx.span_lint(lint, span, |lint| { lint.primary_message(msg); }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | / cx.span_lint(lint, span, |lint| { +LL | | lint.primary_message(msg); +LL | | }); + | |______^ | = note: this function does not add a link to our documentation, please use the `clippy_utils::diagnostics::span_lint*` functions instead (from clippy.toml) = note: `-D clippy::disallowed-methods` implied by `-D warnings` = help: to override `-D warnings` add `#[allow(clippy::disallowed_methods)]` error: use of a disallowed method `rustc_middle::ty::context::TyCtxt::node_span_lint` - --> tests/ui-internal/disallow_span_lint.rs:18:5 + --> tests/ui-internal/disallow_span_lint.rs:20:5 | -LL | tcx.node_span_lint(lint, hir_id, span, |lint| { lint.primary_message(msg); }); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +LL | / tcx.node_span_lint(lint, hir_id, span, |lint| { +LL | | lint.primary_message(msg); +LL | | }); + | |______^ | = note: this function does not add a link to our documentation, please use the `clippy_utils::diagnostics::span_lint_hir*` functions instead (from clippy.toml) diff --git a/tests/ui/derive_partial_eq_without_eq.fixed b/tests/ui/derive_partial_eq_without_eq.fixed index 6f42487bb..eb93eb8e8 100644 --- a/tests/ui/derive_partial_eq_without_eq.fixed +++ b/tests/ui/derive_partial_eq_without_eq.fixed @@ -1,3 +1,4 @@ +#![feature(lint_reasons)] #![allow(unused)] #![warn(clippy::derive_partial_eq_without_eq)] @@ -14,6 +15,22 @@ pub struct MissingEq { bar: String, } +// Check that we honor the `allow` attribute +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Debug, PartialEq)] +pub struct AllowedMissingEq { + foo: u32, + bar: String, +} + +// Check that we honor the `expect` attribute +#[expect(clippy::derive_partial_eq_without_eq)] +#[derive(Debug, PartialEq)] +pub struct ExpectedMissingEq { + foo: u32, + bar: String, +} + // Eq is derived #[derive(PartialEq, Eq)] pub struct NotMissingEq { diff --git a/tests/ui/derive_partial_eq_without_eq.rs b/tests/ui/derive_partial_eq_without_eq.rs index 24f687c6c..42dc435bd 100644 --- a/tests/ui/derive_partial_eq_without_eq.rs +++ b/tests/ui/derive_partial_eq_without_eq.rs @@ -1,3 +1,4 @@ +#![feature(lint_reasons)] #![allow(unused)] #![warn(clippy::derive_partial_eq_without_eq)] @@ -14,6 +15,22 @@ pub struct MissingEq { bar: String, } +// Check that we honor the `allow` attribute +#[allow(clippy::derive_partial_eq_without_eq)] +#[derive(Debug, PartialEq)] +pub struct AllowedMissingEq { + foo: u32, + bar: String, +} + +// Check that we honor the `expect` attribute +#[expect(clippy::derive_partial_eq_without_eq)] +#[derive(Debug, PartialEq)] +pub struct ExpectedMissingEq { + foo: u32, + bar: String, +} + // Eq is derived #[derive(PartialEq, Eq)] pub struct NotMissingEq { diff --git a/tests/ui/derive_partial_eq_without_eq.stderr b/tests/ui/derive_partial_eq_without_eq.stderr index 3d92112dc..29cd7da6b 100644 --- a/tests/ui/derive_partial_eq_without_eq.stderr +++ b/tests/ui/derive_partial_eq_without_eq.stderr @@ -1,5 +1,5 @@ error: you are deriving `PartialEq` and can implement `Eq` - --> tests/ui/derive_partial_eq_without_eq.rs:11:17 + --> tests/ui/derive_partial_eq_without_eq.rs:12:17 | LL | #[derive(Debug, PartialEq)] | ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq` @@ -8,73 +8,73 @@ LL | #[derive(Debug, PartialEq)] = help: to override `-D warnings` add `#[allow(clippy::derive_partial_eq_without_eq)]` error: you are deriving `PartialEq` and can implement `Eq` - --> tests/ui/derive_partial_eq_without_eq.rs:53:10 + --> tests/ui/derive_partial_eq_without_eq.rs:70:10 | LL | #[derive(PartialEq)] | ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq` error: you are deriving `PartialEq` and can implement `Eq` - --> tests/ui/derive_partial_eq_without_eq.rs:59:10 + --> tests/ui/derive_partial_eq_without_eq.rs:76:10 | LL | #[derive(PartialEq)] | ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq` error: you are deriving `PartialEq` and can implement `Eq` - --> tests/ui/derive_partial_eq_without_eq.rs:65:10 + --> tests/ui/derive_partial_eq_without_eq.rs:82:10 | LL | #[derive(PartialEq)] | ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq` error: you are deriving `PartialEq` and can implement `Eq` - --> tests/ui/derive_partial_eq_without_eq.rs:68:10 + --> tests/ui/derive_partial_eq_without_eq.rs:85:10 | LL | #[derive(PartialEq)] | ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq` error: you are deriving `PartialEq` and can implement `Eq` - --> tests/ui/derive_partial_eq_without_eq.rs:74:10 + --> tests/ui/derive_partial_eq_without_eq.rs:91:10 | LL | #[derive(PartialEq)] | ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq` error: you are deriving `PartialEq` and can implement `Eq` - --> tests/ui/derive_partial_eq_without_eq.rs:80:10 + --> tests/ui/derive_partial_eq_without_eq.rs:97:10 | LL | #[derive(PartialEq)] | ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq` error: you are deriving `PartialEq` and can implement `Eq` - --> tests/ui/derive_partial_eq_without_eq.rs:93:17 + --> tests/ui/derive_partial_eq_without_eq.rs:110:17 | LL | #[derive(Debug, PartialEq, Clone)] | ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq` error: you are deriving `PartialEq` and can implement `Eq` - --> tests/ui/derive_partial_eq_without_eq.rs:96:10 + --> tests/ui/derive_partial_eq_without_eq.rs:113:10 | LL | #[derive(PartialEq)] | ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq` error: you are deriving `PartialEq` and can implement `Eq` - --> tests/ui/derive_partial_eq_without_eq.rs:103:14 + --> tests/ui/derive_partial_eq_without_eq.rs:120:14 | LL | #[derive(PartialEq)] | ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq` error: you are deriving `PartialEq` and can implement `Eq` - --> tests/ui/derive_partial_eq_without_eq.rs:106:14 + --> tests/ui/derive_partial_eq_without_eq.rs:123:14 | LL | #[derive(PartialEq)] | ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq` error: you are deriving `PartialEq` and can implement `Eq` - --> tests/ui/derive_partial_eq_without_eq.rs:166:14 + --> tests/ui/derive_partial_eq_without_eq.rs:183:14 | LL | #[derive(PartialEq)] | ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq` error: you are deriving `PartialEq` and can implement `Eq` - --> tests/ui/derive_partial_eq_without_eq.rs:174:14 + --> tests/ui/derive_partial_eq_without_eq.rs:191:14 | LL | #[derive(PartialEq)] | ^^^^^^^^^ help: consider deriving `Eq` as well: `PartialEq, Eq` diff --git a/tests/ui/doc/doc-fixable.fixed b/tests/ui/doc/doc-fixable.fixed index 7e22c847b..84673f1f4 100644 --- a/tests/ui/doc/doc-fixable.fixed +++ b/tests/ui/doc/doc-fixable.fixed @@ -240,3 +240,6 @@ extern { /// `foo()` fn in_extern(); } + +/// +fn check_autofix_for_base_urls() {} diff --git a/tests/ui/doc/doc-fixable.rs b/tests/ui/doc/doc-fixable.rs index 3e2cb0df5..4d017a99e 100644 --- a/tests/ui/doc/doc-fixable.rs +++ b/tests/ui/doc/doc-fixable.rs @@ -240,3 +240,6 @@ extern { /// foo() fn in_extern(); } + +/// https://github.com/rust-lang/rust-clippy/pull/12836 +fn check_autofix_for_base_urls() {} diff --git a/tests/ui/doc/doc-fixable.stderr b/tests/ui/doc/doc-fixable.stderr index cd2228c47..a9263f62d 100644 --- a/tests/ui/doc/doc-fixable.stderr +++ b/tests/ui/doc/doc-fixable.stderr @@ -363,5 +363,11 @@ help: try LL | /// `foo()` | ~~~~~~~ -error: aborting due to 33 previous errors +error: you should put bare URLs between `<`/`>` or make a proper Markdown link + --> tests/ui/doc/doc-fixable.rs:244:5 + | +LL | /// https://github.com/rust-lang/rust-clippy/pull/12836 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `` + +error: aborting due to 34 previous errors diff --git a/tests/ui/doc/issue_12795.fixed b/tests/ui/doc/issue_12795.fixed new file mode 100644 index 000000000..ade23bf97 --- /dev/null +++ b/tests/ui/doc/issue_12795.fixed @@ -0,0 +1,9 @@ +#![warn(clippy::doc_markdown)] + +//! A comment with `a_b(x)` and `a_c` in it and (`a_b((c))` ) too and (maybe `a_b((c))`) +//~^ ERROR: item in documentation is missing backticks +//~| ERROR: item in documentation is missing backticks +//~| ERROR: item in documentation is missing backticks +//~| ERROR: item in documentation is missing backticks + +pub fn main() {} diff --git a/tests/ui/doc/issue_12795.rs b/tests/ui/doc/issue_12795.rs new file mode 100644 index 000000000..6d94a07e3 --- /dev/null +++ b/tests/ui/doc/issue_12795.rs @@ -0,0 +1,9 @@ +#![warn(clippy::doc_markdown)] + +//! A comment with a_b(x) and a_c in it and (a_b((c)) ) too and (maybe a_b((c))) +//~^ ERROR: item in documentation is missing backticks +//~| ERROR: item in documentation is missing backticks +//~| ERROR: item in documentation is missing backticks +//~| ERROR: item in documentation is missing backticks + +pub fn main() {} diff --git a/tests/ui/doc/issue_12795.stderr b/tests/ui/doc/issue_12795.stderr new file mode 100644 index 000000000..5700145ec --- /dev/null +++ b/tests/ui/doc/issue_12795.stderr @@ -0,0 +1,48 @@ +error: item in documentation is missing backticks + --> tests/ui/doc/issue_12795.rs:3:20 + | +LL | //! A comment with a_b(x) and a_c in it and (a_b((c)) ) too and (maybe a_b((c))) + | ^^^^^^ + | + = note: `-D clippy::doc-markdown` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(clippy::doc_markdown)]` +help: try + | +LL | //! A comment with `a_b(x)` and a_c in it and (a_b((c)) ) too and (maybe a_b((c))) + | ~~~~~~~~ + +error: item in documentation is missing backticks + --> tests/ui/doc/issue_12795.rs:3:31 + | +LL | //! A comment with a_b(x) and a_c in it and (a_b((c)) ) too and (maybe a_b((c))) + | ^^^ + | +help: try + | +LL | //! A comment with a_b(x) and `a_c` in it and (a_b((c)) ) too and (maybe a_b((c))) + | ~~~~~ + +error: item in documentation is missing backticks + --> tests/ui/doc/issue_12795.rs:3:46 + | +LL | //! A comment with a_b(x) and a_c in it and (a_b((c)) ) too and (maybe a_b((c))) + | ^^^^^^^^ + | +help: try + | +LL | //! A comment with a_b(x) and a_c in it and (`a_b((c))` ) too and (maybe a_b((c))) + | ~~~~~~~~~~ + +error: item in documentation is missing backticks + --> tests/ui/doc/issue_12795.rs:3:72 + | +LL | //! A comment with a_b(x) and a_c in it and (a_b((c)) ) too and (maybe a_b((c))) + | ^^^^^^^^ + | +help: try + | +LL | //! A comment with a_b(x) and a_c in it and (a_b((c)) ) too and (maybe `a_b((c))`) + | ~~~~~~~~~~ + +error: aborting due to 4 previous errors + diff --git a/tests/ui/iter_on_empty_collections.fixed b/tests/ui/iter_on_empty_collections.fixed index 794629f24..0f28b48d9 100644 --- a/tests/ui/iter_on_empty_collections.fixed +++ b/tests/ui/iter_on_empty_collections.fixed @@ -20,6 +20,28 @@ fn array() { }; let _ = if false { ["test"].iter() } else { [].iter() }; + + let smth = Some(vec![1, 2, 3]); + + // Don't trigger when the empty collection iter is relied upon for its concrete type + // But do trigger if it is just an iterator, despite being an argument to a method + for i in smth.as_ref().map_or([].iter(), |s| s.iter()).chain(std::iter::empty()) { + println!("{i}"); + } + + // Same as above, but for empty collection iters with extra layers + for i in smth.as_ref().map_or({ [].iter() }, |s| s.iter()) { + println!("{y}", y = i + 1); + } + + // Same as above, but for regular function calls + for i in Option::map_or(smth.as_ref(), [].iter(), |s| s.iter()) { + println!("{i}"); + } + + // Same as above, but when there are no predicates that mention the collection iter type. + let mut iter = [34, 228, 35].iter(); + let _ = std::mem::replace(&mut iter, [].iter()); } macro_rules! in_macros { diff --git a/tests/ui/iter_on_empty_collections.rs b/tests/ui/iter_on_empty_collections.rs index a6461a702..702da514d 100644 --- a/tests/ui/iter_on_empty_collections.rs +++ b/tests/ui/iter_on_empty_collections.rs @@ -20,6 +20,28 @@ fn array() { }; let _ = if false { ["test"].iter() } else { [].iter() }; + + let smth = Some(vec![1, 2, 3]); + + // Don't trigger when the empty collection iter is relied upon for its concrete type + // But do trigger if it is just an iterator, despite being an argument to a method + for i in smth.as_ref().map_or([].iter(), |s| s.iter()).chain([].iter()) { + println!("{i}"); + } + + // Same as above, but for empty collection iters with extra layers + for i in smth.as_ref().map_or({ [].iter() }, |s| s.iter()) { + println!("{y}", y = i + 1); + } + + // Same as above, but for regular function calls + for i in Option::map_or(smth.as_ref(), [].iter(), |s| s.iter()) { + println!("{i}"); + } + + // Same as above, but when there are no predicates that mention the collection iter type. + let mut iter = [34, 228, 35].iter(); + let _ = std::mem::replace(&mut iter, [].iter()); } macro_rules! in_macros { diff --git a/tests/ui/iter_on_empty_collections.stderr b/tests/ui/iter_on_empty_collections.stderr index ade20ff26..da9caa692 100644 --- a/tests/ui/iter_on_empty_collections.stderr +++ b/tests/ui/iter_on_empty_collections.stderr @@ -37,5 +37,11 @@ error: `iter` call on an empty collection LL | assert_eq!(None.iter().next(), Option::<&i32>::None); | ^^^^^^^^^^^ help: try: `std::iter::empty()` -error: aborting due to 6 previous errors +error: `iter` call on an empty collection + --> tests/ui/iter_on_empty_collections.rs:28:66 + | +LL | for i in smth.as_ref().map_or([].iter(), |s| s.iter()).chain([].iter()) { + | ^^^^^^^^^ help: try: `std::iter::empty()` + +error: aborting due to 7 previous errors diff --git a/tests/ui/let_and_return.fixed b/tests/ui/let_and_return.fixed index 4187019e5..b68b41cdc 100644 --- a/tests/ui/let_and_return.fixed +++ b/tests/ui/let_and_return.fixed @@ -210,4 +210,38 @@ fn issue9150() -> usize { x } +fn issue12801() { + fn left_is_if() -> String { + + (if true { "a".to_string() } else { "b".to_string() } + "c") + //~^ ERROR: returning the result of a `let` binding from a block + } + + fn no_par_needed() -> String { + + "c".to_string() + if true { "a" } else { "b" } + //~^ ERROR: returning the result of a `let` binding from a block + } + + fn conjunctive_blocks() -> String { + + ({ "a".to_string() } + "b" + { "c" } + "d") + //~^ ERROR: returning the result of a `let` binding from a block + } + + #[allow(clippy::overly_complex_bool_expr)] + fn other_ops() { + let _ = || { + + (if true { 2 } else { 3 } << 4) + //~^ ERROR: returning the result of a `let` binding from a block + }; + let _ = || { + + ({ true } || { false } && { 2 <= 3 }) + //~^ ERROR: returning the result of a `let` binding from a block + }; + } +} + fn main() {} diff --git a/tests/ui/let_and_return.rs b/tests/ui/let_and_return.rs index 54444957b..6b9035f94 100644 --- a/tests/ui/let_and_return.rs +++ b/tests/ui/let_and_return.rs @@ -210,4 +210,38 @@ fn issue9150() -> usize { x } +fn issue12801() { + fn left_is_if() -> String { + let s = if true { "a".to_string() } else { "b".to_string() } + "c"; + s + //~^ ERROR: returning the result of a `let` binding from a block + } + + fn no_par_needed() -> String { + let s = "c".to_string() + if true { "a" } else { "b" }; + s + //~^ ERROR: returning the result of a `let` binding from a block + } + + fn conjunctive_blocks() -> String { + let s = { "a".to_string() } + "b" + { "c" } + "d"; + s + //~^ ERROR: returning the result of a `let` binding from a block + } + + #[allow(clippy::overly_complex_bool_expr)] + fn other_ops() { + let _ = || { + let s = if true { 2 } else { 3 } << 4; + s + //~^ ERROR: returning the result of a `let` binding from a block + }; + let _ = || { + let s = { true } || { false } && { 2 <= 3 }; + s + //~^ ERROR: returning the result of a `let` binding from a block + }; + } +} + fn main() {} diff --git a/tests/ui/let_and_return.stderr b/tests/ui/let_and_return.stderr index ff5962ec1..75efa05d7 100644 --- a/tests/ui/let_and_return.stderr +++ b/tests/ui/let_and_return.stderr @@ -78,5 +78,75 @@ LL + E::B(x) => x, LL + }) as _ | -error: aborting due to 5 previous errors +error: returning the result of a `let` binding from a block + --> tests/ui/let_and_return.rs:216:9 + | +LL | let s = if true { "a".to_string() } else { "b".to_string() } + "c"; + | ------------------------------------------------------------------- unnecessary `let` binding +LL | s + | ^ + | +help: return the expression directly + | +LL ~ +LL ~ (if true { "a".to_string() } else { "b".to_string() } + "c") + | + +error: returning the result of a `let` binding from a block + --> tests/ui/let_and_return.rs:222:9 + | +LL | let s = "c".to_string() + if true { "a" } else { "b" }; + | ------------------------------------------------------- unnecessary `let` binding +LL | s + | ^ + | +help: return the expression directly + | +LL ~ +LL ~ "c".to_string() + if true { "a" } else { "b" } + | + +error: returning the result of a `let` binding from a block + --> tests/ui/let_and_return.rs:228:9 + | +LL | let s = { "a".to_string() } + "b" + { "c" } + "d"; + | -------------------------------------------------- unnecessary `let` binding +LL | s + | ^ + | +help: return the expression directly + | +LL ~ +LL ~ ({ "a".to_string() } + "b" + { "c" } + "d") + | + +error: returning the result of a `let` binding from a block + --> tests/ui/let_and_return.rs:236:13 + | +LL | let s = if true { 2 } else { 3 } << 4; + | -------------------------------------- unnecessary `let` binding +LL | s + | ^ + | +help: return the expression directly + | +LL ~ +LL ~ (if true { 2 } else { 3 } << 4) + | + +error: returning the result of a `let` binding from a block + --> tests/ui/let_and_return.rs:241:13 + | +LL | let s = { true } || { false } && { 2 <= 3 }; + | -------------------------------------------- unnecessary `let` binding +LL | s + | ^ + | +help: return the expression directly + | +LL ~ +LL ~ ({ true } || { false } && { 2 <= 3 }) + | + +error: aborting due to 10 previous errors diff --git a/tests/ui/many_single_char_names.rs b/tests/ui/many_single_char_names.rs index 2af45eaab..68578340d 100644 --- a/tests/ui/many_single_char_names.rs +++ b/tests/ui/many_single_char_names.rs @@ -1,5 +1,3 @@ -//@compile-flags: -Zdeduplicate-diagnostics=yes - #![allow(clippy::too_many_arguments, clippy::diverging_sub_expression)] #![warn(clippy::many_single_char_names)] diff --git a/tests/ui/many_single_char_names.stderr b/tests/ui/many_single_char_names.stderr index 3b2460b5c..131836ef7 100644 --- a/tests/ui/many_single_char_names.stderr +++ b/tests/ui/many_single_char_names.stderr @@ -1,5 +1,5 @@ error: 5 bindings with single-character names in scope - --> tests/ui/many_single_char_names.rs:7:9 + --> tests/ui/many_single_char_names.rs:5:9 | LL | let a: i32; | ^ @@ -14,7 +14,7 @@ LL | let e: i32; = help: to override `-D warnings` add `#[allow(clippy::many_single_char_names)]` error: 6 bindings with single-character names in scope - --> tests/ui/many_single_char_names.rs:7:9 + --> tests/ui/many_single_char_names.rs:5:9 | LL | let a: i32; | ^ @@ -28,7 +28,7 @@ LL | let f: i32; | ^ error: 5 bindings with single-character names in scope - --> tests/ui/many_single_char_names.rs:7:9 + --> tests/ui/many_single_char_names.rs:5:9 | LL | let a: i32; | ^ @@ -40,13 +40,13 @@ LL | e => panic!(), | ^ error: 8 bindings with single-character names in scope - --> tests/ui/many_single_char_names.rs:36:13 + --> tests/ui/many_single_char_names.rs:34:13 | LL | fn bindings(a: i32, b: i32, c: i32, d: i32, e: i32, f: i32, g: i32, h: i32) {} | ^ ^ ^ ^ ^ ^ ^ ^ error: 8 bindings with single-character names in scope - --> tests/ui/many_single_char_names.rs:40:10 + --> tests/ui/many_single_char_names.rs:38:10 | LL | let (a, b, c, d, e, f, g, h): (bool, bool, bool, bool, bool, bool, bool, bool) = unimplemented!(); | ^ ^ ^ ^ ^ ^ ^ ^ diff --git a/tests/ui/needless_return.fixed b/tests/ui/needless_return.fixed index 2575f2449..a9271cb39 100644 --- a/tests/ui/needless_return.fixed +++ b/tests/ui/needless_return.fixed @@ -323,4 +323,8 @@ fn allow_works() -> i32 { } } +fn conjunctive_blocks() -> String { + ({ "a".to_string() } + "b" + { "c" }) +} + fn main() {} diff --git a/tests/ui/needless_return.rs b/tests/ui/needless_return.rs index 04f21834d..dc888bf66 100644 --- a/tests/ui/needless_return.rs +++ b/tests/ui/needless_return.rs @@ -333,4 +333,8 @@ fn allow_works() -> i32 { } } +fn conjunctive_blocks() -> String { + return { "a".to_string() } + "b" + { "c" }; +} + fn main() {} diff --git a/tests/ui/needless_return.stderr b/tests/ui/needless_return.stderr index 758ff6d98..bf5a89d8b 100644 --- a/tests/ui/needless_return.stderr +++ b/tests/ui/needless_return.stderr @@ -653,5 +653,17 @@ LL - return if b1 { 0 } else { 1 } | if b2 { 2 } else { 3 } | if b3 { 4 LL + (if b1 { 0 } else { 1 } | if b2 { 2 } else { 3 } | if b3 { 4 } else { 5 }) | -error: aborting due to 52 previous errors +error: unneeded `return` statement + --> tests/ui/needless_return.rs:337:5 + | +LL | return { "a".to_string() } + "b" + { "c" }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: remove `return` and wrap the sequence with parentheses + | +LL - return { "a".to_string() } + "b" + { "c" }; +LL + ({ "a".to_string() } + "b" + { "c" }) + | + +error: aborting due to 53 previous errors diff --git a/tests/ui/numbered_fields.fixed b/tests/ui/numbered_fields.fixed index dc88081ba..108520eed 100644 --- a/tests/ui/numbered_fields.fixed +++ b/tests/ui/numbered_fields.fixed @@ -34,4 +34,9 @@ fn main() { // Aliases can't be tuple constructed #8638 let _ = Alias { 0: 0, 1: 1, 2: 2 }; + + // Issue #12367 + struct TupleStructVec(Vec); + + let _ = TupleStructVec(vec![0, 1, 2, 3]); } diff --git a/tests/ui/numbered_fields.rs b/tests/ui/numbered_fields.rs index e8fa652e3..c718661a6 100644 --- a/tests/ui/numbered_fields.rs +++ b/tests/ui/numbered_fields.rs @@ -42,4 +42,9 @@ fn main() { // Aliases can't be tuple constructed #8638 let _ = Alias { 0: 0, 1: 1, 2: 2 }; + + // Issue #12367 + struct TupleStructVec(Vec); + + let _ = TupleStructVec { 0: vec![0, 1, 2, 3] }; } diff --git a/tests/ui/numbered_fields.stderr b/tests/ui/numbered_fields.stderr index 96426cab1..9d3f59cd3 100644 --- a/tests/ui/numbered_fields.stderr +++ b/tests/ui/numbered_fields.stderr @@ -23,5 +23,11 @@ LL | | 1: 3u32, LL | | }; | |_____^ help: try: `TupleStruct(1u32, 3u32, 2u8)` -error: aborting due to 2 previous errors +error: used a field initializer for a tuple struct + --> tests/ui/numbered_fields.rs:49:13 + | +LL | let _ = TupleStructVec { 0: vec![0, 1, 2, 3] }; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `TupleStructVec(vec![0, 1, 2, 3])` + +error: aborting due to 3 previous errors diff --git a/tests/ui/significant_drop_in_scrutinee.rs b/tests/ui/significant_drop_in_scrutinee.rs index 7fc89bb95..8ee15440c 100644 --- a/tests/ui/significant_drop_in_scrutinee.rs +++ b/tests/ui/significant_drop_in_scrutinee.rs @@ -274,11 +274,20 @@ fn should_trigger_lint_for_tuple_in_scrutinee() { }, (_, _, _) => {}, }; + } +} +// Should not trigger lint since `String::as_str` returns a reference (i.e., `&str`) +// to the locked data (i.e., the `String`) and it is not surprising that matching such +// a reference needs to keep the data locked until the end of the match block. +fn should_not_trigger_lint_for_string_as_str() { + let mutex1 = Mutex::new(StateWithField { s: "one".to_owned() }); + + { + let mutex2 = Mutex::new(StateWithField { s: "two".to_owned() }); let mutex3 = Mutex::new(StateWithField { s: "three".to_owned() }); + match mutex3.lock().unwrap().s.as_str() { - //~^ ERROR: temporary with significant `Drop` in `match` scrutinee will live until - //~| NOTE: this might lead to deadlocks or other unexpected behavior "three" => { println!("started"); mutex1.lock().unwrap().s.len(); @@ -289,8 +298,6 @@ fn should_trigger_lint_for_tuple_in_scrutinee() { }; match (true, mutex3.lock().unwrap().s.as_str()) { - //~^ ERROR: temporary with significant `Drop` in `match` scrutinee will live until - //~| NOTE: this might lead to deadlocks or other unexpected behavior (_, "three") => { println!("started"); mutex1.lock().unwrap().s.len(); @@ -514,16 +521,15 @@ impl StateStringWithBoxedMutexGuard { } } -fn should_trigger_lint_for_boxed_mutex_guard_holding_string() { +fn should_not_trigger_lint_for_string_ref() { let s = StateStringWithBoxedMutexGuard::new(); let matcher = String::from("A String"); - // Should trigger lint because a temporary Box holding a type with a significant drop in a match - // scrutinee may have a potentially surprising lifetime. + // Should not trigger lint because the second `deref` returns a string reference (`&String`). + // So it is not surprising that matching the reference implies that the lock needs to be held + // until the end of the block. match s.lock().deref().deref() { - //~^ ERROR: temporary with significant `Drop` in `match` scrutinee will live until the - //~| NOTE: this might lead to deadlocks or other unexpected behavior matcher => println!("Value is {}", s.lock().deref()), _ => println!("Value was not a match"), }; @@ -639,13 +645,12 @@ fn should_trigger_lint_for_non_ref_move_and_clone_suggestion() { }; } -fn should_trigger_lint_for_read_write_lock_for_loop() { - // For-in loops desugar to match expressions and are prone to the type of deadlock this lint is - // designed to look for. +fn should_not_trigger_lint_for_read_write_lock_for_loop() { let rwlock = RwLock::>::new(vec!["1".to_string()]); + // Should not trigger lint. Since we're iterating over the data, it's obvious that the lock + // has to be held until the iteration finishes. + // https://github.com/rust-lang/rust-clippy/issues/8987 for s in rwlock.read().unwrap().iter() { - //~^ ERROR: temporary with significant `Drop` in `for` loop condition will live until - //~| NOTE: this might lead to deadlocks or other unexpected behavior println!("{}", s); } } @@ -731,4 +736,69 @@ fn should_not_trigger_for_significant_drop_ref() { }; } +struct Foo<'a>(&'a Vec); + +impl<'a> Foo<'a> { + fn copy_old_lifetime(&self) -> &'a Vec { + self.0 + } + + fn reborrow_new_lifetime(&self) -> &Vec { + self.0 + } +} + +fn should_trigger_lint_if_and_only_if_lifetime_is_irrelevant() { + let vec = Vec::new(); + let mutex = Mutex::new(Foo(&vec)); + + // Should trigger lint even if `copy_old_lifetime()` has a lifetime, as the lifetime of + // `&vec` is unrelated to the temporary with significant drop (i.e., the `MutexGuard`). + for val in mutex.lock().unwrap().copy_old_lifetime() { + //~^ ERROR: temporary with significant `Drop` in `for` loop condition will live until the + //~| NOTE: this might lead to deadlocks or other unexpected behavior + println!("{}", val); + } + + // Should not trigger lint because `reborrow_new_lifetime()` has a lifetime and the + // lifetime is related to the temporary with significant drop (i.e., the `MutexGuard`). + for val in mutex.lock().unwrap().reborrow_new_lifetime() { + println!("{}", val); + } +} + +fn should_not_trigger_lint_for_complex_lifetime() { + let mutex = Mutex::new(vec!["hello".to_owned()]); + let string = "world".to_owned(); + + // Should not trigger lint due to the relevant lifetime. + for c in mutex.lock().unwrap().first().unwrap().chars() { + println!("{}", c); + } + + // Should trigger lint due to the irrelevant lifetime. + // + // FIXME: The lifetime is too complex to analyze. In order to avoid false positives, we do not + // trigger lint. + for c in mutex.lock().unwrap().first().map(|_| &string).unwrap().chars() { + println!("{}", c); + } +} + +fn should_not_trigger_lint_with_explicit_drop() { + let mutex = Mutex::new(vec![1]); + + // Should not trigger lint since the drop explicitly happens. + for val in [drop(mutex.lock().unwrap()), ()] { + println!("{:?}", val); + } + + // Should trigger lint if there is no explicit drop. + for val in [mutex.lock().unwrap()[0], 2] { + //~^ ERROR: temporary with significant `Drop` in `for` loop condition will live until the + //~| NOTE: this might lead to deadlocks or other unexpected behavior + println!("{:?}", val); + } +} + fn main() {} diff --git a/tests/ui/significant_drop_in_scrutinee.stderr b/tests/ui/significant_drop_in_scrutinee.stderr index 811bb0655..4a483e79d 100644 --- a/tests/ui/significant_drop_in_scrutinee.stderr +++ b/tests/ui/significant_drop_in_scrutinee.stderr @@ -160,42 +160,10 @@ LL ~ match (mutex1.lock().unwrap().s.len(), true, value) { | error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression - --> tests/ui/significant_drop_in_scrutinee.rs:279:15 - | -LL | match mutex3.lock().unwrap().s.as_str() { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -... -LL | mutex1.lock().unwrap().s.len(); - | ---------------------- another value with significant `Drop` created here -LL | mutex2.lock().unwrap().s.len(); - | ---------------------- another value with significant `Drop` created here -... -LL | }; - | - temporary lives until here - | - = note: this might lead to deadlocks or other unexpected behavior - -error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression - --> tests/ui/significant_drop_in_scrutinee.rs:291:22 - | -LL | match (true, mutex3.lock().unwrap().s.as_str()) { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -... -LL | mutex1.lock().unwrap().s.len(); - | ---------------------- another value with significant `Drop` created here -LL | mutex2.lock().unwrap().s.len(); - | ---------------------- another value with significant `Drop` created here -... -LL | }; - | - temporary lives until here - | - = note: this might lead to deadlocks or other unexpected behavior - -error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression - --> tests/ui/significant_drop_in_scrutinee.rs:312:11 + --> tests/ui/significant_drop_in_scrutinee.rs:319:11 | LL | match mutex.lock().unwrap().s.len() > 1 { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ... LL | mutex.lock().unwrap().s.len(); | --------------------- another value with significant `Drop` created here @@ -206,15 +174,15 @@ LL | }; = note: this might lead to deadlocks or other unexpected behavior help: try moving the temporary above the match | -LL ~ let value = mutex.lock().unwrap().s.len() > 1; -LL ~ match value { +LL ~ let value = mutex.lock().unwrap().s.len(); +LL ~ match value > 1 { | error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression - --> tests/ui/significant_drop_in_scrutinee.rs:321:11 + --> tests/ui/significant_drop_in_scrutinee.rs:328:15 | LL | match 1 < mutex.lock().unwrap().s.len() { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ... LL | mutex.lock().unwrap().s.len(); | --------------------- another value with significant `Drop` created here @@ -225,15 +193,15 @@ LL | }; = note: this might lead to deadlocks or other unexpected behavior help: try moving the temporary above the match | -LL ~ let value = 1 < mutex.lock().unwrap().s.len(); -LL ~ match value { +LL ~ let value = mutex.lock().unwrap().s.len(); +LL ~ match 1 < value { | error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression - --> tests/ui/significant_drop_in_scrutinee.rs:341:11 + --> tests/ui/significant_drop_in_scrutinee.rs:348:11 | LL | match mutex1.lock().unwrap().s.len() < mutex2.lock().unwrap().s.len() { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ... LL | mutex1.lock().unwrap().s.len(), | ---------------------- another value with significant `Drop` created here @@ -246,15 +214,36 @@ LL | }; = note: this might lead to deadlocks or other unexpected behavior help: try moving the temporary above the match | -LL ~ let value = mutex1.lock().unwrap().s.len() < mutex2.lock().unwrap().s.len(); -LL ~ match value { +LL ~ let value = mutex1.lock().unwrap().s.len(); +LL ~ match value < mutex2.lock().unwrap().s.len() { | error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression - --> tests/ui/significant_drop_in_scrutinee.rs:354:11 + --> tests/ui/significant_drop_in_scrutinee.rs:348:44 + | +LL | match mutex1.lock().unwrap().s.len() < mutex2.lock().unwrap().s.len() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +... +LL | mutex1.lock().unwrap().s.len(), + | ---------------------- another value with significant `Drop` created here +LL | mutex2.lock().unwrap().s.len() + | ---------------------- another value with significant `Drop` created here +... +LL | }; + | - temporary lives until here + | + = note: this might lead to deadlocks or other unexpected behavior +help: try moving the temporary above the match + | +LL ~ let value = mutex2.lock().unwrap().s.len(); +LL ~ match mutex1.lock().unwrap().s.len() < value { + | + +error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression + --> tests/ui/significant_drop_in_scrutinee.rs:361:11 | LL | match mutex1.lock().unwrap().s.len() >= mutex2.lock().unwrap().s.len() { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ... LL | mutex1.lock().unwrap().s.len(), | ---------------------- another value with significant `Drop` created here @@ -267,15 +256,36 @@ LL | }; = note: this might lead to deadlocks or other unexpected behavior help: try moving the temporary above the match | -LL ~ let value = mutex1.lock().unwrap().s.len() >= mutex2.lock().unwrap().s.len(); -LL ~ match value { +LL ~ let value = mutex1.lock().unwrap().s.len(); +LL ~ match value >= mutex2.lock().unwrap().s.len() { | error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression - --> tests/ui/significant_drop_in_scrutinee.rs:391:11 + --> tests/ui/significant_drop_in_scrutinee.rs:361:45 + | +LL | match mutex1.lock().unwrap().s.len() >= mutex2.lock().unwrap().s.len() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +... +LL | mutex1.lock().unwrap().s.len(), + | ---------------------- another value with significant `Drop` created here +LL | mutex2.lock().unwrap().s.len() + | ---------------------- another value with significant `Drop` created here +... +LL | }; + | - temporary lives until here + | + = note: this might lead to deadlocks or other unexpected behavior +help: try moving the temporary above the match + | +LL ~ let value = mutex2.lock().unwrap().s.len(); +LL ~ match mutex1.lock().unwrap().s.len() >= value { + | + +error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression + --> tests/ui/significant_drop_in_scrutinee.rs:398:11 | LL | match get_mutex_guard().s.len() > 1 { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^ ... LL | mutex1.lock().unwrap().s.len(); | ---------------------- another value with significant `Drop` created here @@ -286,12 +296,12 @@ LL | }; = note: this might lead to deadlocks or other unexpected behavior help: try moving the temporary above the match | -LL ~ let value = get_mutex_guard().s.len() > 1; -LL ~ match value { +LL ~ let value = get_mutex_guard().s.len(); +LL ~ match value > 1 { | error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression - --> tests/ui/significant_drop_in_scrutinee.rs:410:11 + --> tests/ui/significant_drop_in_scrutinee.rs:417:11 | LL | match match i { | ___________^ @@ -299,9 +309,9 @@ LL | | LL | | LL | | 100 => mutex1.lock().unwrap(), ... | +LL | | .s LL | | .len() -LL | | > 1 - | |___________^ + | |__________^ ... LL | mutex1.lock().unwrap().s.len(); | ---------------------- another value with significant `Drop` created here @@ -319,13 +329,12 @@ LL + 100 => mutex1.lock().unwrap(), LL + _ => mutex2.lock().unwrap(), LL + } LL + .s -LL + .len() -LL + > 1; +LL + .len(); LL ~ match value | error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression - --> tests/ui/significant_drop_in_scrutinee.rs:438:11 + --> tests/ui/significant_drop_in_scrutinee.rs:445:11 | LL | match if i > 1 { | ___________^ @@ -333,9 +342,9 @@ LL | | LL | | LL | | mutex1.lock().unwrap() ... | +LL | | .s LL | | .len() -LL | | > 1 - | |___________^ + | |__________^ ... LL | mutex1.lock().unwrap().s.len(); | ---------------------- another value with significant `Drop` created here @@ -354,13 +363,12 @@ LL + } else { LL + mutex2.lock().unwrap() LL + } LL + .s -LL + .len() -LL + > 1; +LL + .len(); LL ~ match value | error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression - --> tests/ui/significant_drop_in_scrutinee.rs:494:11 + --> tests/ui/significant_drop_in_scrutinee.rs:501:11 | LL | match s.lock().deref().deref() { | ^^^^^^^^^^^^^^^^^^^^^^^^ @@ -374,25 +382,11 @@ LL | }; help: try moving the temporary above the match and create a copy | LL ~ let value = *s.lock().deref().deref(); -LL ~ match value { +LL ~ match (&value) { | error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression - --> tests/ui/significant_drop_in_scrutinee.rs:524:11 - | -LL | match s.lock().deref().deref() { - | ^^^^^^^^^^^^^^^^^^^^^^^^ -... -LL | matcher => println!("Value is {}", s.lock().deref()), - | -------- another value with significant `Drop` created here -LL | _ => println!("Value was not a match"), -LL | }; - | - temporary lives until here - | - = note: this might lead to deadlocks or other unexpected behavior - -error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression - --> tests/ui/significant_drop_in_scrutinee.rs:545:11 + --> tests/ui/significant_drop_in_scrutinee.rs:551:11 | LL | match mutex.lock().unwrap().i = i { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -411,10 +405,10 @@ LL ~ match () { | error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression - --> tests/ui/significant_drop_in_scrutinee.rs:553:11 + --> tests/ui/significant_drop_in_scrutinee.rs:559:15 | LL | match i = mutex.lock().unwrap().i { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^ ... LL | println!("{}", mutex.lock().unwrap().i); | --------------------- another value with significant `Drop` created here @@ -425,12 +419,12 @@ LL | }; = note: this might lead to deadlocks or other unexpected behavior help: try moving the temporary above the match | -LL ~ i = mutex.lock().unwrap().i; -LL ~ match () { +LL ~ let value = mutex.lock().unwrap().i; +LL ~ match i = value { | error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression - --> tests/ui/significant_drop_in_scrutinee.rs:561:11 + --> tests/ui/significant_drop_in_scrutinee.rs:567:11 | LL | match mutex.lock().unwrap().i += 1 { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -449,10 +443,10 @@ LL ~ match () { | error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression - --> tests/ui/significant_drop_in_scrutinee.rs:569:11 + --> tests/ui/significant_drop_in_scrutinee.rs:575:16 | LL | match i += mutex.lock().unwrap().i { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^ ... LL | println!("{}", mutex.lock().unwrap().i); | --------------------- another value with significant `Drop` created here @@ -463,12 +457,12 @@ LL | }; = note: this might lead to deadlocks or other unexpected behavior help: try moving the temporary above the match | -LL ~ i += mutex.lock().unwrap().i; -LL ~ match () { +LL ~ let value = mutex.lock().unwrap().i; +LL ~ match i += value { | error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression - --> tests/ui/significant_drop_in_scrutinee.rs:634:11 + --> tests/ui/significant_drop_in_scrutinee.rs:640:11 | LL | match rwlock.read().unwrap().to_number() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -477,20 +471,14 @@ LL | }; | - temporary lives until here | = note: this might lead to deadlocks or other unexpected behavior - -error: temporary with significant `Drop` in `for` loop condition will live until the end of the `for` expression - --> tests/ui/significant_drop_in_scrutinee.rs:646:14 +help: try moving the temporary above the match | -LL | for s in rwlock.read().unwrap().iter() { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -... -LL | } - | - temporary lives until here +LL ~ let value = rwlock.read().unwrap().to_number(); +LL ~ match value { | - = note: this might lead to deadlocks or other unexpected behavior error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression - --> tests/ui/significant_drop_in_scrutinee.rs:663:11 + --> tests/ui/significant_drop_in_scrutinee.rs:668:11 | LL | match mutex.lock().unwrap().foo() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -506,7 +494,7 @@ LL ~ match value { | error: temporary with significant `Drop` in `match` scrutinee will live until the end of the `match` expression - --> tests/ui/significant_drop_in_scrutinee.rs:726:11 + --> tests/ui/significant_drop_in_scrutinee.rs:731:11 | LL | match guard.take().len() { | ^^^^^^^^^^^^^^^^^^ @@ -521,5 +509,37 @@ LL ~ let value = guard.take().len(); LL ~ match value { | +error: temporary with significant `Drop` in `for` loop condition will live until the end of the `for` expression + --> tests/ui/significant_drop_in_scrutinee.rs:757:16 + | +LL | for val in mutex.lock().unwrap().copy_old_lifetime() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +... +LL | } + | - temporary lives until here + | + = note: this might lead to deadlocks or other unexpected behavior +help: try moving the temporary above the match + | +LL ~ let value = mutex.lock().unwrap().copy_old_lifetime(); +LL ~ for val in value { + | + +error: temporary with significant `Drop` in `for` loop condition will live until the end of the `for` expression + --> tests/ui/significant_drop_in_scrutinee.rs:797:17 + | +LL | for val in [mutex.lock().unwrap()[0], 2] { + | ^^^^^^^^^^^^^^^^^^^^^^^^ +... +LL | } + | - temporary lives until here + | + = note: this might lead to deadlocks or other unexpected behavior +help: try moving the temporary above the match + | +LL ~ let value = mutex.lock().unwrap()[0]; +LL ~ for val in [value, 2] { + | + error: aborting due to 27 previous errors diff --git a/tests/ui/unnecessary_iter_cloned.fixed b/tests/ui/unnecessary_iter_cloned.fixed index 2c582c90b..dc5e163ff 100644 --- a/tests/ui/unnecessary_iter_cloned.fixed +++ b/tests/ui/unnecessary_iter_cloned.fixed @@ -170,3 +170,32 @@ fn check_mut_iteratee_and_modify_inner_variable() { } } } + +mod issue_12821 { + fn foo() { + let v: Vec<_> = "hello".chars().collect(); + for c in v.iter() { + //~^ ERROR: unnecessary use of `cloned` + println!("{c}"); // should not suggest to remove `&` + } + } + + fn bar() { + let v: Vec<_> = "hello".chars().collect(); + for c in v.iter() { + //~^ ERROR: unnecessary use of `cloned` + let ref_c = c; //~ HELP: remove any references to the binding + println!("{ref_c}"); + } + } + + fn baz() { + let v: Vec<_> = "hello".chars().enumerate().collect(); + for (i, c) in v.iter() { + //~^ ERROR: unnecessary use of `cloned` + let ref_c = c; //~ HELP: remove any references to the binding + let ref_i = i; + println!("{i} {ref_c}"); // should not suggest to remove `&` from `i` + } + } +} diff --git a/tests/ui/unnecessary_iter_cloned.rs b/tests/ui/unnecessary_iter_cloned.rs index a28ccd1ef..8f797ac71 100644 --- a/tests/ui/unnecessary_iter_cloned.rs +++ b/tests/ui/unnecessary_iter_cloned.rs @@ -170,3 +170,32 @@ fn check_mut_iteratee_and_modify_inner_variable() { } } } + +mod issue_12821 { + fn foo() { + let v: Vec<_> = "hello".chars().collect(); + for c in v.iter().cloned() { + //~^ ERROR: unnecessary use of `cloned` + println!("{c}"); // should not suggest to remove `&` + } + } + + fn bar() { + let v: Vec<_> = "hello".chars().collect(); + for c in v.iter().cloned() { + //~^ ERROR: unnecessary use of `cloned` + let ref_c = &c; //~ HELP: remove any references to the binding + println!("{ref_c}"); + } + } + + fn baz() { + let v: Vec<_> = "hello".chars().enumerate().collect(); + for (i, c) in v.iter().cloned() { + //~^ ERROR: unnecessary use of `cloned` + let ref_c = &c; //~ HELP: remove any references to the binding + let ref_i = &i; + println!("{i} {ref_c}"); // should not suggest to remove `&` from `i` + } + } +} diff --git a/tests/ui/unnecessary_iter_cloned.stderr b/tests/ui/unnecessary_iter_cloned.stderr index fb98cfddc..0bdb37a52 100644 --- a/tests/ui/unnecessary_iter_cloned.stderr +++ b/tests/ui/unnecessary_iter_cloned.stderr @@ -10,7 +10,7 @@ help: use | LL | for (t, path) in files { | ~~~~~ -help: remove this `&` +help: remove any references to the binding | LL - let other = match get_file_path(&t) { LL + let other = match get_file_path(t) { @@ -26,11 +26,49 @@ help: use | LL | for (t, path) in files.iter() { | ~~~~~~~~~~~~ -help: remove this `&` +help: remove any references to the binding | LL - let other = match get_file_path(&t) { LL + let other = match get_file_path(t) { | -error: aborting due to 2 previous errors +error: unnecessary use of `cloned` + --> tests/ui/unnecessary_iter_cloned.rs:177:18 + | +LL | for c in v.iter().cloned() { + | ^^^^^^^^^^^^^^^^^ help: use: `v.iter()` + +error: unnecessary use of `cloned` + --> tests/ui/unnecessary_iter_cloned.rs:185:18 + | +LL | for c in v.iter().cloned() { + | ^^^^^^^^^^^^^^^^^ + | +help: use + | +LL | for c in v.iter() { + | ~~~~~~~~ +help: remove any references to the binding + | +LL - let ref_c = &c; +LL + let ref_c = c; + | + +error: unnecessary use of `cloned` + --> tests/ui/unnecessary_iter_cloned.rs:194:23 + | +LL | for (i, c) in v.iter().cloned() { + | ^^^^^^^^^^^^^^^^^ + | +help: use + | +LL | for (i, c) in v.iter() { + | ~~~~~~~~ +help: remove any references to the binding + | +LL ~ let ref_c = c; +LL ~ let ref_i = i; + | + +error: aborting due to 5 previous errors diff --git a/tests/ui/unnecessary_to_owned.stderr b/tests/ui/unnecessary_to_owned.stderr index 5475df9c7..2829f3cd6 100644 --- a/tests/ui/unnecessary_to_owned.stderr +++ b/tests/ui/unnecessary_to_owned.stderr @@ -487,7 +487,7 @@ help: use | LL | for t in file_types { | ~~~~~~~~~~ -help: remove this `&` +help: remove any references to the binding | LL - let path = match get_file_path(&t) { LL + let path = match get_file_path(t) { diff --git a/tests/ui/unsafe_derive_deserialize.rs b/tests/ui/unsafe_derive_deserialize.rs index 70dcaa3af..5187e0790 100644 --- a/tests/ui/unsafe_derive_deserialize.rs +++ b/tests/ui/unsafe_derive_deserialize.rs @@ -1,3 +1,4 @@ +#![feature(lint_reasons)] #![warn(clippy::unsafe_derive_deserialize)] #![allow(unused, clippy::missing_safety_doc)] @@ -71,4 +72,14 @@ impl G { } } +// Check that we honor the `expect` attribute on the ADT +#[expect(clippy::unsafe_derive_deserialize)] +#[derive(Deserialize)] +pub struct H; +impl H { + pub fn unsafe_block(&self) { + unsafe {} + } +} + fn main() {} diff --git a/tests/ui/unsafe_derive_deserialize.stderr b/tests/ui/unsafe_derive_deserialize.stderr index f2d4429f7..06719f23d 100644 --- a/tests/ui/unsafe_derive_deserialize.stderr +++ b/tests/ui/unsafe_derive_deserialize.stderr @@ -1,5 +1,5 @@ error: you are deriving `serde::Deserialize` on a type that has methods using `unsafe` - --> tests/ui/unsafe_derive_deserialize.rs:8:10 + --> tests/ui/unsafe_derive_deserialize.rs:9:10 | LL | #[derive(Deserialize)] | ^^^^^^^^^^^ @@ -10,7 +10,7 @@ LL | #[derive(Deserialize)] = note: this error originates in the derive macro `Deserialize` (in Nightly builds, run with -Z macro-backtrace for more info) error: you are deriving `serde::Deserialize` on a type that has methods using `unsafe` - --> tests/ui/unsafe_derive_deserialize.rs:17:10 + --> tests/ui/unsafe_derive_deserialize.rs:18:10 | LL | #[derive(Deserialize)] | ^^^^^^^^^^^ @@ -19,7 +19,7 @@ LL | #[derive(Deserialize)] = note: this error originates in the derive macro `Deserialize` (in Nightly builds, run with -Z macro-backtrace for more info) error: you are deriving `serde::Deserialize` on a type that has methods using `unsafe` - --> tests/ui/unsafe_derive_deserialize.rs:24:10 + --> tests/ui/unsafe_derive_deserialize.rs:25:10 | LL | #[derive(Deserialize)] | ^^^^^^^^^^^ @@ -28,7 +28,7 @@ LL | #[derive(Deserialize)] = note: this error originates in the derive macro `Deserialize` (in Nightly builds, run with -Z macro-backtrace for more info) error: you are deriving `serde::Deserialize` on a type that has methods using `unsafe` - --> tests/ui/unsafe_derive_deserialize.rs:33:10 + --> tests/ui/unsafe_derive_deserialize.rs:34:10 | LL | #[derive(Deserialize)] | ^^^^^^^^^^^