summaryrefslogtreecommitdiffstats
path: root/src/tools/clippy/clippy_lints
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 12:20:39 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 12:20:39 +0000
commit1376c5a617be5c25655d0d7cb63e3beaa5a6e026 (patch)
tree3bb8d61aee02bc7a15eab3f36e3b921afc2075d0 /src/tools/clippy/clippy_lints
parentReleasing progress-linux version 1.69.0+dfsg1-1~progress7.99u1. (diff)
downloadrustc-1376c5a617be5c25655d0d7cb63e3beaa5a6e026.tar.xz
rustc-1376c5a617be5c25655d0d7cb63e3beaa5a6e026.zip
Merging upstream version 1.70.0+dfsg1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/tools/clippy/clippy_lints')
-rw-r--r--src/tools/clippy/clippy_lints/Cargo.toml3
-rw-r--r--src/tools/clippy/clippy_lints/src/allow_attributes.rs71
-rw-r--r--src/tools/clippy/clippy_lints/src/almost_complete_range.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/booleans.rs64
-rw-r--r--src/tools/clippy/clippy_lints/src/casts/cast_possible_truncation.rs42
-rw-r--r--src/tools/clippy/clippy_lints/src/casts/cast_slice_from_raw_parts.rs8
-rw-r--r--src/tools/clippy/clippy_lints/src/cognitive_complexity.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/collection_is_never_read.rs141
-rw-r--r--src/tools/clippy/clippy_lints/src/copies.rs70
-rw-r--r--src/tools/clippy/clippy_lints/src/declared_lints.rs14
-rw-r--r--src/tools/clippy/clippy_lints/src/default.rs9
-rw-r--r--src/tools/clippy/clippy_lints/src/default_instead_of_iter_empty.rs16
-rw-r--r--src/tools/clippy/clippy_lints/src/derivable_impls.rs23
-rw-r--r--src/tools/clippy/clippy_lints/src/derive.rs10
-rw-r--r--src/tools/clippy/clippy_lints/src/disallowed_script_idents.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/exit.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/explicit_write.rs29
-rw-r--r--src/tools/clippy/clippy_lints/src/extra_unused_type_parameters.rs177
-rw-r--r--src/tools/clippy/clippy_lints/src/fn_null_check.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/format.rs91
-rw-r--r--src/tools/clippy/clippy_lints/src/format_args.rs224
-rw-r--r--src/tools/clippy/clippy_lints/src/format_impl.rs60
-rw-r--r--src/tools/clippy/clippy_lints/src/functions/impl_trait_in_params.rs4
-rw-r--r--src/tools/clippy/clippy_lints/src/functions/misnamed_getters.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/functions/mod.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/functions/must_use.rs33
-rw-r--r--src/tools/clippy/clippy_lints/src/future_not_send.rs6
-rw-r--r--src/tools/clippy/clippy_lints/src/if_then_some_else_none.rs17
-rw-r--r--src/tools/clippy/clippy_lints/src/implicit_saturating_add.rs16
-rw-r--r--src/tools/clippy/clippy_lints/src/infinite_iter.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/instant_subtraction.rs13
-rw-r--r--src/tools/clippy/clippy_lints/src/items_after_statements.rs31
-rw-r--r--src/tools/clippy/clippy_lints/src/large_futures.rs87
-rw-r--r--src/tools/clippy/clippy_lints/src/len_zero.rs125
-rw-r--r--src/tools/clippy/clippy_lints/src/let_underscore.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/let_with_type_underscore.rs45
-rw-r--r--src/tools/clippy/clippy_lints/src/lib.rs53
-rw-r--r--src/tools/clippy/clippy_lints/src/lines_filter_map_ok.rs100
-rw-r--r--src/tools/clippy/clippy_lints/src/loops/manual_flatten.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/loops/never_loop.rs3
-rw-r--r--src/tools/clippy/clippy_lints/src/loops/same_item_push.rs32
-rw-r--r--src/tools/clippy/clippy_lints/src/manual_async_fn.rs24
-rw-r--r--src/tools/clippy/clippy_lints/src/manual_bits.rs11
-rw-r--r--src/tools/clippy/clippy_lints/src/manual_clamp.rs7
-rw-r--r--src/tools/clippy/clippy_lints/src/manual_is_ascii_check.rs15
-rw-r--r--src/tools/clippy/clippy_lints/src/manual_main_separator_str.rs74
-rw-r--r--src/tools/clippy/clippy_lints/src/manual_non_exhaustive.rs1
-rw-r--r--src/tools/clippy/clippy_lints/src/manual_rem_euclid.rs8
-rw-r--r--src/tools/clippy/clippy_lints/src/manual_slice_size_calculation.rs93
-rw-r--r--src/tools/clippy/clippy_lints/src/match_result_ok.rs25
-rw-r--r--src/tools/clippy/clippy_lints/src/matches/manual_unwrap_or.rs13
-rw-r--r--src/tools/clippy/clippy_lints/src/matches/match_bool.rs20
-rw-r--r--src/tools/clippy/clippy_lints/src/matches/match_ref_pats.rs28
-rw-r--r--src/tools/clippy/clippy_lints/src/matches/match_single_binding.rs52
-rw-r--r--src/tools/clippy/clippy_lints/src/matches/mod.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/matches/redundant_pattern_match.rs19
-rw-r--r--src/tools/clippy/clippy_lints/src/matches/significant_drop_in_scrutinee.rs1
-rw-r--r--src/tools/clippy/clippy_lints/src/matches/single_match.rs18
-rw-r--r--src/tools/clippy/clippy_lints/src/mem_replace.rs71
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/bind_instead_of_map.rs12
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/chars_cmp.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/clear_with_drain.rs53
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/clone_on_ref_ptr.rs8
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/expect_fun_call.rs27
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/iter_with_drain.rs24
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/mod.rs45
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/option_map_or_none.rs1
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/or_fun_call.rs28
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/unnecessary_sort_by.rs4
-rw-r--r--src/tools/clippy/clippy_lints/src/methods/unnecessary_to_owned.rs8
-rw-r--r--src/tools/clippy/clippy_lints/src/misc.rs17
-rw-r--r--src/tools/clippy/clippy_lints/src/missing_assert_message.rs82
-rw-r--r--src/tools/clippy/clippy_lints/src/missing_const_for_fn.rs1
-rw-r--r--src/tools/clippy/clippy_lints/src/missing_doc.rs12
-rw-r--r--src/tools/clippy/clippy_lints/src/multiple_unsafe_ops_per_block.rs37
-rw-r--r--src/tools/clippy/clippy_lints/src/mut_key.rs60
-rw-r--r--src/tools/clippy/clippy_lints/src/needless_bool.rs13
-rw-r--r--src/tools/clippy/clippy_lints/src/needless_pass_by_value.rs6
-rw-r--r--src/tools/clippy/clippy_lints/src/needless_question_mark.rs1
-rw-r--r--src/tools/clippy/clippy_lints/src/neg_multiply.rs6
-rw-r--r--src/tools/clippy/clippy_lints/src/no_effect.rs6
-rw-r--r--src/tools/clippy/clippy_lints/src/no_mangle_with_rust_abi.rs28
-rw-r--r--src/tools/clippy/clippy_lints/src/non_octal_unix_permissions.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/operators/arithmetic_side_effects.rs23
-rw-r--r--src/tools/clippy/clippy_lints/src/option_if_let_else.rs28
-rw-r--r--src/tools/clippy/clippy_lints/src/partialeq_ne_impl.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/permissions_set_readonly_false.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/redundant_async_block.rs108
-rw-r--r--src/tools/clippy/clippy_lints/src/redundant_static_lifetimes.rs6
-rw-r--r--src/tools/clippy/clippy_lints/src/ref_option_ref.rs4
-rw-r--r--src/tools/clippy/clippy_lints/src/returns.rs32
-rw-r--r--src/tools/clippy/clippy_lints/src/semicolon_if_nothing_returned.rs13
-rw-r--r--src/tools/clippy/clippy_lints/src/shadow.rs3
-rw-r--r--src/tools/clippy/clippy_lints/src/significant_drop_tightening.rs1
-rw-r--r--src/tools/clippy/clippy_lints/src/single_component_path_imports.rs59
-rw-r--r--src/tools/clippy/clippy_lints/src/size_of_ref.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/std_instead_of_core.rs1
-rw-r--r--src/tools/clippy/clippy_lints/src/suspicious_doc_comments.rs94
-rw-r--r--src/tools/clippy/clippy_lints/src/suspicious_operation_groupings.rs5
-rw-r--r--src/tools/clippy/clippy_lints/src/swap.rs116
-rw-r--r--src/tools/clippy/clippy_lints/src/tests_outside_test_module.rs71
-rw-r--r--src/tools/clippy/clippy_lints/src/transmute/mod.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/transmute/transmutes_expressible_as_ptr_casts.rs11
-rw-r--r--src/tools/clippy/clippy_lints/src/transmute/utils.rs57
-rw-r--r--src/tools/clippy/clippy_lints/src/types/borrowed_box.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/types/utils.rs4
-rw-r--r--src/tools/clippy/clippy_lints/src/unit_types/let_unit_value.rs13
-rw-r--r--src/tools/clippy/clippy_lints/src/unnecessary_box_returns.rs120
-rw-r--r--src/tools/clippy/clippy_lints/src/unnecessary_struct_initialization.rs88
-rw-r--r--src/tools/clippy/clippy_lints/src/use_self.rs18
-rw-r--r--src/tools/clippy/clippy_lints/src/useless_conversion.rs14
-rw-r--r--src/tools/clippy/clippy_lints/src/utils/author.rs7
-rw-r--r--src/tools/clippy/clippy_lints/src/utils/conf.rs38
-rw-r--r--src/tools/clippy/clippy_lints/src/utils/format_args_collector.rs98
-rw-r--r--src/tools/clippy/clippy_lints/src/utils/internal_lints/interning_defined_symbol.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/utils/internal_lints/metadata_collector.rs7
-rw-r--r--src/tools/clippy/clippy_lints/src/utils/internal_lints/unnecessary_def_path.rs2
-rw-r--r--src/tools/clippy/clippy_lints/src/utils/mod.rs1
-rw-r--r--src/tools/clippy/clippy_lints/src/wildcard_imports.rs18
-rw-r--r--src/tools/clippy/clippy_lints/src/write.rs160
120 files changed, 2794 insertions, 1040 deletions
diff --git a/src/tools/clippy/clippy_lints/Cargo.toml b/src/tools/clippy/clippy_lints/Cargo.toml
index 796f1ff16..18e8bf772 100644
--- a/src/tools/clippy/clippy_lints/Cargo.toml
+++ b/src/tools/clippy/clippy_lints/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "clippy_lints"
-version = "0.1.69"
+version = "0.1.70"
description = "A bunch of helpful lints to avoid common pitfalls in Rust"
repository = "https://github.com/rust-lang/rust-clippy"
readme = "README.md"
@@ -9,6 +9,7 @@ keywords = ["clippy", "lint", "plugin"]
edition = "2021"
[dependencies]
+arrayvec = { version = "0.7", default-features = false }
cargo_metadata = "0.15.3"
clippy_utils = { path = "../clippy_utils" }
declare_clippy_lint = { path = "../declare_clippy_lint" }
diff --git a/src/tools/clippy/clippy_lints/src/allow_attributes.rs b/src/tools/clippy/clippy_lints/src/allow_attributes.rs
new file mode 100644
index 000000000..15d46e954
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/allow_attributes.rs
@@ -0,0 +1,71 @@
+use ast::AttrStyle;
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use rustc_ast as ast;
+use rustc_errors::Applicability;
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// Detects uses of the `#[allow]` attribute and suggests replacing it with
+ /// the `#[expect]` (See [RFC 2383](https://rust-lang.github.io/rfcs/2383-lint-reasons.html))
+ ///
+ /// The expect attribute is still unstable and requires the `lint_reasons`
+ /// on nightly. It can be enabled by adding `#![feature(lint_reasons)]` to
+ /// the crate root.
+ ///
+ /// 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
+ /// the expectation is unfulfilled. This can be useful to be notified when the
+ /// lint is no longer triggered.
+ ///
+ /// ### Example
+ /// ```rust,ignore
+ /// #[allow(unused_mut)]
+ /// fn foo() -> usize {
+ /// let mut a = Vec::new();
+ /// a.len()
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust,ignore
+ /// #![feature(lint_reasons)]
+ /// #[expect(unused_mut)]
+ /// fn foo() -> usize {
+ /// let mut a = Vec::new();
+ /// a.len()
+ /// }
+ /// ```
+ #[clippy::version = "1.69.0"]
+ pub ALLOW_ATTRIBUTES,
+ restriction,
+ "`#[allow]` will not trigger if a warning isn't found. `#[expect]` triggers if there are no warnings."
+}
+
+declare_lint_pass!(AllowAttribute => [ALLOW_ATTRIBUTES]);
+
+impl LateLintPass<'_> for AllowAttribute {
+ // Separate each crate's features.
+ fn check_attribute(&mut self, cx: &LateContext<'_>, attr: &ast::Attribute) {
+ if_chain! {
+ if cx.tcx.features().lint_reasons;
+ if let AttrStyle::Outer = attr.style;
+ if let Some(ident) = attr.ident();
+ if ident.name == rustc_span::symbol::sym::allow;
+ then {
+ span_lint_and_sugg(
+ cx,
+ ALLOW_ATTRIBUTES,
+ ident.span,
+ "#[allow] attribute found",
+ "replace it with",
+ "expect".into(),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/almost_complete_range.rs b/src/tools/clippy/clippy_lints/src/almost_complete_range.rs
index 42e14b5cd..32d80f42e 100644
--- a/src/tools/clippy/clippy_lints/src/almost_complete_range.rs
+++ b/src/tools/clippy/clippy_lints/src/almost_complete_range.rs
@@ -24,7 +24,7 @@ declare_clippy_lint! {
/// ```rust
/// let _ = 'a'..='z';
/// ```
- #[clippy::version = "1.63.0"]
+ #[clippy::version = "1.68.0"]
pub ALMOST_COMPLETE_RANGE,
suspicious,
"almost complete range"
diff --git a/src/tools/clippy/clippy_lints/src/booleans.rs b/src/tools/clippy/clippy_lints/src/booleans.rs
index e8106beec..455f0df7c 100644
--- a/src/tools/clippy/clippy_lints/src/booleans.rs
+++ b/src/tools/clippy/clippy_lints/src/booleans.rs
@@ -7,7 +7,7 @@ use rustc_ast::ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::intravisit::{walk_expr, FnKind, Visitor};
use rustc_hir::{BinOpKind, Body, Expr, ExprKind, FnDecl, UnOp};
-use rustc_lint::{LateContext, LateLintPass};
+use rustc_lint::{LateContext, LateLintPass, Level};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::def_id::LocalDefId;
use rustc_span::source_map::Span;
@@ -430,23 +430,25 @@ impl<'a, 'tcx> NonminimalBoolVisitor<'a, 'tcx> {
}
}
let nonminimal_bool_lint = |suggestions: Vec<_>| {
- span_lint_hir_and_then(
- self.cx,
- NONMINIMAL_BOOL,
- e.hir_id,
- e.span,
- "this boolean expression can be simplified",
- |diag| {
- diag.span_suggestions(
- e.span,
- "try",
- suggestions.into_iter(),
- // nonminimal_bool can produce minimal but
- // not human readable expressions (#3141)
- Applicability::Unspecified,
- );
- },
- );
+ if self.cx.tcx.lint_level_at_node(NONMINIMAL_BOOL, e.hir_id).0 != Level::Allow {
+ span_lint_hir_and_then(
+ self.cx,
+ NONMINIMAL_BOOL,
+ e.hir_id,
+ e.span,
+ "this boolean expression can be simplified",
+ |diag| {
+ diag.span_suggestions(
+ e.span,
+ "try",
+ suggestions.into_iter(),
+ // nonminimal_bool can produce minimal but
+ // not human readable expressions (#3141)
+ Applicability::Unspecified,
+ );
+ },
+ );
+ }
};
if improvements.is_empty() {
let mut visitor = NotSimplificationVisitor { cx: self.cx };
@@ -495,18 +497,20 @@ struct NotSimplificationVisitor<'a, 'tcx> {
impl<'a, 'tcx> Visitor<'tcx> for NotSimplificationVisitor<'a, 'tcx> {
fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
- if let ExprKind::Unary(UnOp::Not, inner) = &expr.kind {
- if let Some(suggestion) = simplify_not(self.cx, inner) {
- span_lint_and_sugg(
- self.cx,
- NONMINIMAL_BOOL,
- expr.span,
- "this boolean expression can be simplified",
- "try",
- suggestion,
- Applicability::MachineApplicable,
- );
- }
+ if let ExprKind::Unary(UnOp::Not, inner) = &expr.kind &&
+ !inner.span.from_expansion() &&
+ let Some(suggestion) = simplify_not(self.cx, inner)
+ && self.cx.tcx.lint_level_at_node(NONMINIMAL_BOOL, expr.hir_id).0 != Level::Allow
+ {
+ span_lint_and_sugg(
+ self.cx,
+ NONMINIMAL_BOOL,
+ expr.span,
+ "this boolean expression can be simplified",
+ "try",
+ suggestion,
+ Applicability::MachineApplicable,
+ );
}
walk_expr(self, expr);
diff --git a/src/tools/clippy/clippy_lints/src/casts/cast_possible_truncation.rs b/src/tools/clippy/clippy_lints/src/casts/cast_possible_truncation.rs
index 823970e35..95c2ecbf7 100644
--- a/src/tools/clippy/clippy_lints/src/casts/cast_possible_truncation.rs
+++ b/src/tools/clippy/clippy_lints/src/casts/cast_possible_truncation.rs
@@ -2,8 +2,9 @@ use clippy_utils::consts::{constant, Constant};
use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
use clippy_utils::expr_or_init;
use clippy_utils::source::snippet;
+use clippy_utils::sugg::Sugg;
use clippy_utils::ty::{get_discriminant_value, is_isize_or_usize};
-use rustc_errors::{Applicability, SuggestionStyle};
+use rustc_errors::{Applicability, Diagnostic, SuggestionStyle};
use rustc_hir::def::{DefKind, Res};
use rustc_hir::{BinOpKind, Expr, ExprKind};
use rustc_lint::LateContext;
@@ -163,19 +164,34 @@ pub(super) fn check(
_ => return,
};
- let name_of_cast_from = snippet(cx, cast_expr.span, "..");
- let cast_to_snip = snippet(cx, cast_to_span, "..");
- let suggestion = format!("{cast_to_snip}::try_from({name_of_cast_from})");
-
span_lint_and_then(cx, CAST_POSSIBLE_TRUNCATION, expr.span, &msg, |diag| {
diag.help("if this is intentional allow the lint with `#[allow(clippy::cast_possible_truncation)]` ...");
- diag.span_suggestion_with_style(
- expr.span,
- "... or use `try_from` and handle the error accordingly",
- suggestion,
- Applicability::Unspecified,
- // always show the suggestion in a separate line
- SuggestionStyle::ShowAlways,
- );
+ if !cast_from.is_floating_point() {
+ offer_suggestion(cx, expr, cast_expr, cast_to_span, diag);
+ }
});
}
+
+fn offer_suggestion(
+ cx: &LateContext<'_>,
+ expr: &Expr<'_>,
+ cast_expr: &Expr<'_>,
+ cast_to_span: Span,
+ diag: &mut Diagnostic,
+) {
+ let cast_to_snip = snippet(cx, cast_to_span, "..");
+ let suggestion = if cast_to_snip == "_" {
+ format!("{}.try_into()", Sugg::hir(cx, cast_expr, "..").maybe_par())
+ } else {
+ format!("{cast_to_snip}::try_from({})", Sugg::hir(cx, cast_expr, ".."))
+ };
+
+ diag.span_suggestion_with_style(
+ expr.span,
+ "... or use `try_from` and handle the error accordingly",
+ suggestion,
+ Applicability::Unspecified,
+ // always show the suggestion in a separate line
+ SuggestionStyle::ShowAlways,
+ );
+}
diff --git a/src/tools/clippy/clippy_lints/src/casts/cast_slice_from_raw_parts.rs b/src/tools/clippy/clippy_lints/src/casts/cast_slice_from_raw_parts.rs
index 627b795d6..1233c632a 100644
--- a/src/tools/clippy/clippy_lints/src/casts/cast_slice_from_raw_parts.rs
+++ b/src/tools/clippy/clippy_lints/src/casts/cast_slice_from_raw_parts.rs
@@ -1,6 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::msrvs::{self, Msrv};
-use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::source::snippet_with_context;
use clippy_utils::{match_def_path, paths};
use if_chain::if_chain;
use rustc_errors::Applicability;
@@ -34,6 +34,8 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>,
if let ExprKind::Path(ref qpath) = fun.kind;
if let Some(fun_def_id) = cx.qpath_res(qpath, fun.hir_id).opt_def_id();
if let Some(rpk) = raw_parts_kind(cx, fun_def_id);
+ let ctxt = expr.span.ctxt();
+ if cast_expr.span.ctxt() == ctxt;
then {
let func = match rpk {
RawPartsKind::Immutable => "from_raw_parts",
@@ -41,8 +43,8 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>,
};
let span = expr.span;
let mut applicability = Applicability::MachineApplicable;
- let ptr = snippet_with_applicability(cx, ptr_arg.span, "ptr", &mut applicability);
- let len = snippet_with_applicability(cx, len_arg.span, "len", &mut applicability);
+ let ptr = snippet_with_context(cx, ptr_arg.span, ctxt, "ptr", &mut applicability).0;
+ let len = snippet_with_context(cx, len_arg.span, ctxt, "len", &mut applicability).0;
span_lint_and_sugg(
cx,
CAST_SLICE_FROM_RAW_PARTS,
diff --git a/src/tools/clippy/clippy_lints/src/cognitive_complexity.rs b/src/tools/clippy/clippy_lints/src/cognitive_complexity.rs
index e8531157e..a8926b29a 100644
--- a/src/tools/clippy/clippy_lints/src/cognitive_complexity.rs
+++ b/src/tools/clippy/clippy_lints/src/cognitive_complexity.rs
@@ -143,7 +143,7 @@ impl<'tcx> LateLintPass<'tcx> for CognitiveComplexity {
span: Span,
def_id: LocalDefId,
) {
- if !cx.tcx.has_attr(def_id.to_def_id(), sym::test) {
+ if !cx.tcx.has_attr(def_id, sym::test) {
let expr = if is_async_fn(kind) {
match get_async_fn_body(cx.tcx, body) {
Some(b) => b,
diff --git a/src/tools/clippy/clippy_lints/src/collection_is_never_read.rs b/src/tools/clippy/clippy_lints/src/collection_is_never_read.rs
new file mode 100644
index 000000000..5e2eb5789
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/collection_is_never_read.rs
@@ -0,0 +1,141 @@
+use clippy_utils::diagnostics::span_lint;
+use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item};
+use clippy_utils::visitors::for_each_expr_with_closures;
+use clippy_utils::{get_enclosing_block, get_parent_node, path_to_local_id};
+use core::ops::ControlFlow;
+use rustc_hir::{Block, ExprKind, HirId, LangItem, Local, Node, PatKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::symbol::sym;
+use rustc_span::Symbol;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for collections that are never queried.
+ ///
+ /// ### Why is this bad?
+ /// Putting effort into constructing a collection but then never querying it might indicate that
+ /// the author forgot to do whatever they intended to do with the collection. Example: Clone
+ /// a vector, sort it for iteration, but then mistakenly iterate the original vector
+ /// instead.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let samples = vec![3, 1, 2];
+ /// let mut sorted_samples = samples.clone();
+ /// sorted_samples.sort();
+ /// for sample in &samples { // Oops, meant to use `sorted_samples`.
+ /// println!("{sample}");
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// # let samples = vec![3, 1, 2];
+ /// let mut sorted_samples = samples.clone();
+ /// sorted_samples.sort();
+ /// for sample in &sorted_samples {
+ /// println!("{sample}");
+ /// }
+ /// ```
+ #[clippy::version = "1.69.0"]
+ pub COLLECTION_IS_NEVER_READ,
+ nursery,
+ "a collection is never queried"
+}
+declare_lint_pass!(CollectionIsNeverRead => [COLLECTION_IS_NEVER_READ]);
+
+// Add `String` here when it is added to diagnostic items
+static COLLECTIONS: [Symbol; 9] = [
+ sym::BTreeMap,
+ sym::BTreeSet,
+ sym::BinaryHeap,
+ sym::HashMap,
+ sym::HashSet,
+ sym::LinkedList,
+ sym::Option,
+ sym::Vec,
+ sym::VecDeque,
+];
+
+impl<'tcx> LateLintPass<'tcx> for CollectionIsNeverRead {
+ fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'tcx>) {
+ // Look for local variables whose type is a container. Search surrounding bock for read access.
+ if match_acceptable_type(cx, local, &COLLECTIONS)
+ && let PatKind::Binding(_, local_id, _, _) = local.pat.kind
+ && let Some(enclosing_block) = get_enclosing_block(cx, local.hir_id)
+ && has_no_read_access(cx, local_id, enclosing_block)
+ {
+ span_lint(cx, COLLECTION_IS_NEVER_READ, local.span, "collection is never read");
+ }
+ }
+}
+
+fn match_acceptable_type(cx: &LateContext<'_>, local: &Local<'_>, collections: &[rustc_span::Symbol]) -> bool {
+ let ty = cx.typeck_results().pat_ty(local.pat);
+ collections.iter().any(|&sym| is_type_diagnostic_item(cx, ty, sym))
+ // String type is a lang item but not a diagnostic item for now so we need a separate check
+ || is_type_lang_item(cx, ty, LangItem::String)
+}
+
+fn has_no_read_access<'tcx>(cx: &LateContext<'tcx>, id: HirId, block: &'tcx Block<'tcx>) -> bool {
+ let mut has_access = false;
+ let mut has_read_access = false;
+
+ // Inspect all expressions and sub-expressions in the block.
+ for_each_expr_with_closures(cx, block, |expr| {
+ // Ignore expressions that are not simply `id`.
+ if !path_to_local_id(expr, id) {
+ return ControlFlow::Continue(());
+ }
+
+ // `id` is being accessed. Investigate if it's a read access.
+ has_access = true;
+
+ // `id` appearing in the left-hand side of an assignment is not a read access:
+ //
+ // id = ...; // Not reading `id`.
+ if let Some(Node::Expr(parent)) = get_parent_node(cx.tcx, expr.hir_id)
+ && let ExprKind::Assign(lhs, ..) = parent.kind
+ && path_to_local_id(lhs, id)
+ {
+ return ControlFlow::Continue(());
+ }
+
+ // Look for method call with receiver `id`. It might be a non-read access:
+ //
+ // id.foo(args)
+ //
+ // Only assuming this for "official" methods defined on the type. For methods defined in extension
+ // traits (identified as local, based on the orphan rule), pessimistically assume that they might
+ // have side effects, so consider them a read.
+ if let Some(Node::Expr(parent)) = get_parent_node(cx.tcx, expr.hir_id)
+ && let ExprKind::MethodCall(_, receiver, _, _) = parent.kind
+ && path_to_local_id(receiver, id)
+ && let Some(method_def_id) = cx.typeck_results().type_dependent_def_id(parent.hir_id)
+ && !method_def_id.is_local()
+ {
+ // The method call is a statement, so the return value is not used. That's not a read access:
+ //
+ // id.foo(args);
+ if let Some(Node::Stmt(..)) = get_parent_node(cx.tcx, parent.hir_id) {
+ return ControlFlow::Continue(());
+ }
+
+ // The method call is not a statement, so its return value is used somehow but its type is the
+ // unit type, so this is not a real read access. Examples:
+ //
+ // let y = x.clear();
+ // println!("{:?}", x.clear());
+ if cx.typeck_results().expr_ty(parent).is_unit() {
+ return ControlFlow::Continue(());
+ }
+ }
+
+ // Any other access to `id` is a read access. Stop searching.
+ has_read_access = true;
+ ControlFlow::Break(())
+ });
+
+ // Ignore collections that have no access at all. Other lints should catch them.
+ has_access && !has_read_access
+}
diff --git a/src/tools/clippy/clippy_lints/src/copies.rs b/src/tools/clippy/clippy_lints/src/copies.rs
index f10c35cde..970f50049 100644
--- a/src/tools/clippy/clippy_lints/src/copies.rs
+++ b/src/tools/clippy/clippy_lints/src/copies.rs
@@ -1,18 +1,20 @@
use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_then};
use clippy_utils::source::{first_line_of_span, indent_of, reindent_multiline, snippet, snippet_opt};
-use clippy_utils::ty::needs_ordered_drop;
+use clippy_utils::ty::{is_interior_mut_ty, needs_ordered_drop};
use clippy_utils::visitors::for_each_expr;
use clippy_utils::{
- capture_local_usage, eq_expr_value, get_enclosing_block, hash_expr, hash_stmt, if_sequence, is_else_clause,
- is_lint_allowed, path_to_local, search_same, ContainsName, HirEqInterExpr, SpanlessEq,
+ capture_local_usage, def_path_def_ids, eq_expr_value, find_binding_init, get_enclosing_block, hash_expr, hash_stmt,
+ if_sequence, is_else_clause, is_lint_allowed, path_to_local, search_same, ContainsName, HirEqInterExpr, SpanlessEq,
};
use core::iter;
use core::ops::ControlFlow;
use rustc_errors::Applicability;
+use rustc_hir::def_id::DefIdSet;
use rustc_hir::intravisit;
use rustc_hir::{BinOpKind, Block, Expr, ExprKind, HirId, HirIdSet, Stmt, StmtKind};
use rustc_lint::{LateContext, LateLintPass};
-use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_middle::query::Key;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::hygiene::walk_chain;
use rustc_span::source_map::SourceMap;
use rustc_span::{BytePos, Span, Symbol};
@@ -159,7 +161,21 @@ declare_clippy_lint! {
"`if` statement with shared code in all blocks"
}
-declare_lint_pass!(CopyAndPaste => [
+pub struct CopyAndPaste {
+ ignore_interior_mutability: Vec<String>,
+ ignored_ty_ids: DefIdSet,
+}
+
+impl CopyAndPaste {
+ pub fn new(ignore_interior_mutability: Vec<String>) -> Self {
+ Self {
+ ignore_interior_mutability,
+ ignored_ty_ids: DefIdSet::new(),
+ }
+ }
+}
+
+impl_lint_pass!(CopyAndPaste => [
IFS_SAME_COND,
SAME_FUNCTIONS_IN_IF_CONDITION,
IF_SAME_THEN_ELSE,
@@ -167,10 +183,18 @@ declare_lint_pass!(CopyAndPaste => [
]);
impl<'tcx> LateLintPass<'tcx> for CopyAndPaste {
+ fn check_crate(&mut self, cx: &LateContext<'tcx>) {
+ for ignored_ty in &self.ignore_interior_mutability {
+ let path: Vec<&str> = ignored_ty.split("::").collect();
+ for id in def_path_def_ids(cx, path.as_slice()) {
+ self.ignored_ty_ids.insert(id);
+ }
+ }
+ }
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
if !expr.span.from_expansion() && matches!(expr.kind, ExprKind::If(..)) && !is_else_clause(cx.tcx, expr) {
let (conds, blocks) = if_sequence(expr);
- lint_same_cond(cx, &conds);
+ lint_same_cond(cx, &conds, &self.ignored_ty_ids);
lint_same_fns_in_if_cond(cx, &conds);
let all_same =
!is_lint_allowed(cx, IF_SAME_THEN_ELSE, expr.hir_id) && lint_if_same_then_else(cx, &conds, &blocks);
@@ -547,9 +571,39 @@ fn check_for_warn_of_moved_symbol(cx: &LateContext<'_>, symbols: &[(HirId, Symbo
})
}
+fn method_caller_is_mutable(cx: &LateContext<'_>, caller_expr: &Expr<'_>, ignored_ty_ids: &DefIdSet) -> bool {
+ let caller_ty = cx.typeck_results().expr_ty(caller_expr);
+ // Check if given type has inner mutability and was not set to ignored by the configuration
+ let is_inner_mut_ty = is_interior_mut_ty(cx, caller_ty)
+ && !matches!(caller_ty.ty_adt_id(), Some(adt_id) if ignored_ty_ids.contains(&adt_id));
+
+ is_inner_mut_ty
+ || caller_ty.is_mutable_ptr()
+ // `find_binding_init` will return the binding iff its not mutable
+ || path_to_local(caller_expr)
+ .and_then(|hid| find_binding_init(cx, hid))
+ .is_none()
+}
+
/// Implementation of `IFS_SAME_COND`.
-fn lint_same_cond(cx: &LateContext<'_>, conds: &[&Expr<'_>]) {
- for (i, j) in search_same(conds, |e| hash_expr(cx, e), |lhs, rhs| eq_expr_value(cx, lhs, rhs)) {
+fn lint_same_cond(cx: &LateContext<'_>, conds: &[&Expr<'_>], ignored_ty_ids: &DefIdSet) {
+ for (i, j) in search_same(
+ conds,
+ |e| hash_expr(cx, e),
+ |lhs, rhs| {
+ // Ignore eq_expr side effects iff one of the expressin kind is a method call
+ // and the caller is not a mutable, including inner mutable type.
+ if let ExprKind::MethodCall(_, caller, _, _) = lhs.kind {
+ if method_caller_is_mutable(cx, caller, ignored_ty_ids) {
+ false
+ } else {
+ SpanlessEq::new(cx).eq_expr(lhs, rhs)
+ }
+ } else {
+ eq_expr_value(cx, lhs, rhs)
+ }
+ },
+ ) {
span_lint_and_note(
cx,
IFS_SAME_COND,
diff --git a/src/tools/clippy/clippy_lints/src/declared_lints.rs b/src/tools/clippy/clippy_lints/src/declared_lints.rs
index cd5dd7a57..f24dab627 100644
--- a/src/tools/clippy/clippy_lints/src/declared_lints.rs
+++ b/src/tools/clippy/clippy_lints/src/declared_lints.rs
@@ -35,6 +35,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::utils::internal_lints::produce_ice::PRODUCE_ICE_INFO,
#[cfg(feature = "internal")]
crate::utils::internal_lints::unnecessary_def_path::UNNECESSARY_DEF_PATH_INFO,
+ crate::allow_attributes::ALLOW_ATTRIBUTES_INFO,
crate::almost_complete_range::ALMOST_COMPLETE_RANGE_INFO,
crate::approx_const::APPROX_CONSTANT_INFO,
crate::as_conversions::AS_CONVERSIONS_INFO,
@@ -92,6 +93,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::cognitive_complexity::COGNITIVE_COMPLEXITY_INFO,
crate::collapsible_if::COLLAPSIBLE_ELSE_IF_INFO,
crate::collapsible_if::COLLAPSIBLE_IF_INFO,
+ crate::collection_is_never_read::COLLECTION_IS_NEVER_READ_INFO,
crate::comparison_chain::COMPARISON_CHAIN_INFO,
crate::copies::BRANCHES_SHARING_CODE_INFO,
crate::copies::IFS_SAME_COND_INFO,
@@ -216,6 +218,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::iter_not_returning_iterator::ITER_NOT_RETURNING_ITERATOR_INFO,
crate::large_const_arrays::LARGE_CONST_ARRAYS_INFO,
crate::large_enum_variant::LARGE_ENUM_VARIANT_INFO,
+ crate::large_futures::LARGE_FUTURES_INFO,
crate::large_include_file::LARGE_INCLUDE_FILE_INFO,
crate::large_stack_arrays::LARGE_STACK_ARRAYS_INFO,
crate::len_zero::COMPARISON_TO_EMPTY_INFO,
@@ -226,8 +229,10 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::let_underscore::LET_UNDERSCORE_LOCK_INFO,
crate::let_underscore::LET_UNDERSCORE_MUST_USE_INFO,
crate::let_underscore::LET_UNDERSCORE_UNTYPED_INFO,
+ crate::let_with_type_underscore::LET_WITH_TYPE_UNDERSCORE_INFO,
crate::lifetimes::EXTRA_UNUSED_LIFETIMES_INFO,
crate::lifetimes::NEEDLESS_LIFETIMES_INFO,
+ crate::lines_filter_map_ok::LINES_FILTER_MAP_OK_INFO,
crate::literal_representation::DECIMAL_LITERAL_REPRESENTATION_INFO,
crate::literal_representation::INCONSISTENT_DIGIT_GROUPING_INFO,
crate::literal_representation::LARGE_DIGIT_GROUPS_INFO,
@@ -260,9 +265,11 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::manual_clamp::MANUAL_CLAMP_INFO,
crate::manual_is_ascii_check::MANUAL_IS_ASCII_CHECK_INFO,
crate::manual_let_else::MANUAL_LET_ELSE_INFO,
+ crate::manual_main_separator_str::MANUAL_MAIN_SEPARATOR_STR_INFO,
crate::manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE_INFO,
crate::manual_rem_euclid::MANUAL_REM_EUCLID_INFO,
crate::manual_retain::MANUAL_RETAIN_INFO,
+ crate::manual_slice_size_calculation::MANUAL_SLICE_SIZE_CALCULATION_INFO,
crate::manual_string_new::MANUAL_STRING_NEW_INFO,
crate::manual_strip::MANUAL_STRIP_INFO,
crate::map_unit_fn::OPTION_MAP_UNIT_FN_INFO,
@@ -303,6 +310,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::methods::CASE_SENSITIVE_FILE_EXTENSION_COMPARISONS_INFO,
crate::methods::CHARS_LAST_CMP_INFO,
crate::methods::CHARS_NEXT_CMP_INFO,
+ crate::methods::CLEAR_WITH_DRAIN_INFO,
crate::methods::CLONED_INSTEAD_OF_COPIED_INFO,
crate::methods::CLONE_DOUBLE_REF_INFO,
crate::methods::CLONE_ON_COPY_INFO,
@@ -416,6 +424,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::misc_early::UNSEPARATED_LITERAL_SUFFIX_INFO,
crate::misc_early::ZERO_PREFIXED_LITERAL_INFO,
crate::mismatching_type_param_order::MISMATCHING_TYPE_PARAM_ORDER_INFO,
+ crate::missing_assert_message::MISSING_ASSERT_MESSAGE_INFO,
crate::missing_const_for_fn::MISSING_CONST_FOR_FN_INFO,
crate::missing_doc::MISSING_DOCS_IN_PRIVATE_ITEMS_INFO,
crate::missing_enforced_import_rename::MISSING_ENFORCED_IMPORT_RENAMES_INFO,
@@ -517,6 +526,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::ranges::REVERSED_EMPTY_RANGES_INFO,
crate::rc_clone_in_vec_init::RC_CLONE_IN_VEC_INIT_INFO,
crate::read_zero_byte_vec::READ_ZERO_BYTE_VEC_INFO,
+ crate::redundant_async_block::REDUNDANT_ASYNC_BLOCK_INFO,
crate::redundant_clone::REDUNDANT_CLONE_INFO,
crate::redundant_closure_call::REDUNDANT_CLOSURE_CALL_INFO,
crate::redundant_else::REDUNDANT_ELSE_INFO,
@@ -559,6 +569,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::strings::STR_TO_STRING_INFO,
crate::strings::TRIM_SPLIT_WHITESPACE_INFO,
crate::strlen_on_c_strings::STRLEN_ON_C_STRINGS_INFO,
+ crate::suspicious_doc_comments::SUSPICIOUS_DOC_COMMENTS_INFO,
crate::suspicious_operation_groupings::SUSPICIOUS_OPERATION_GROUPINGS_INFO,
crate::suspicious_trait_impl::SUSPICIOUS_ARITHMETIC_IMPL_INFO,
crate::suspicious_trait_impl::SUSPICIOUS_OP_ASSIGN_IMPL_INFO,
@@ -568,6 +579,7 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::swap_ptr_to_ref::SWAP_PTR_TO_REF_INFO,
crate::tabs_in_doc_comments::TABS_IN_DOC_COMMENTS_INFO,
crate::temporary_assignment::TEMPORARY_ASSIGNMENT_INFO,
+ crate::tests_outside_test_module::TESTS_OUTSIDE_TEST_MODULE_INFO,
crate::to_digit_is_some::TO_DIGIT_IS_SOME_INFO,
crate::trailing_empty_array::TRAILING_EMPTY_ARRAY_INFO,
crate::trait_bounds::TRAIT_DUPLICATION_IN_BOUNDS_INFO,
@@ -610,8 +622,10 @@ pub(crate) static LINTS: &[&crate::LintInfo] = &[
crate::unit_types::UNIT_CMP_INFO,
crate::unnamed_address::FN_ADDRESS_COMPARISONS_INFO,
crate::unnamed_address::VTABLE_ADDRESS_COMPARISONS_INFO,
+ crate::unnecessary_box_returns::UNNECESSARY_BOX_RETURNS_INFO,
crate::unnecessary_owned_empty_strings::UNNECESSARY_OWNED_EMPTY_STRINGS_INFO,
crate::unnecessary_self_imports::UNNECESSARY_SELF_IMPORTS_INFO,
+ crate::unnecessary_struct_initialization::UNNECESSARY_STRUCT_INITIALIZATION_INFO,
crate::unnecessary_wraps::UNNECESSARY_WRAPS_INFO,
crate::unnested_or_patterns::UNNESTED_OR_PATTERNS_INFO,
crate::unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME_INFO,
diff --git a/src/tools/clippy/clippy_lints/src/default.rs b/src/tools/clippy/clippy_lints/src/default.rs
index 080d44e63..80c22742b 100644
--- a/src/tools/clippy/clippy_lints/src/default.rs
+++ b/src/tools/clippy/clippy_lints/src/default.rs
@@ -1,5 +1,5 @@
use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_sugg};
-use clippy_utils::source::snippet_with_macro_callsite;
+use clippy_utils::source::snippet_with_context;
use clippy_utils::ty::{has_drop, is_copy};
use clippy_utils::{
any_parent_is_automatically_derived, contains_name, get_parent_expr, is_from_proc_macro, match_def_path, paths,
@@ -160,6 +160,8 @@ impl<'tcx> LateLintPass<'tcx> for Default {
}
};
+ let init_ctxt = local.span.ctxt();
+
// find all "later statement"'s where the fields of the binding set as
// Default::default() get reassigned, unless the reassignment refers to the original binding
let mut first_assign = None;
@@ -169,7 +171,7 @@ impl<'tcx> LateLintPass<'tcx> for Default {
// find out if and which field was set by this `consecutive_statement`
if let Some((field_ident, assign_rhs)) = field_reassigned_by_stmt(consecutive_statement, binding_name) {
// interrupt and cancel lint if assign_rhs references the original binding
- if contains_name(binding_name, assign_rhs, cx) {
+ if contains_name(binding_name, assign_rhs, cx) || init_ctxt != consecutive_statement.span.ctxt() {
cancel_lint = true;
break;
}
@@ -204,11 +206,12 @@ impl<'tcx> LateLintPass<'tcx> for Default {
.iter()
.all(|field| assigned_fields.iter().any(|(a, _)| a == &field.name));
+ let mut app = Applicability::Unspecified;
let field_list = assigned_fields
.into_iter()
.map(|(field, rhs)| {
// extract and store the assigned value for help message
- let value_snippet = snippet_with_macro_callsite(cx, rhs.span, "..");
+ let value_snippet = snippet_with_context(cx, rhs.span, init_ctxt, "..", &mut app).0;
format!("{field}: {value_snippet}")
})
.collect::<Vec<String>>()
diff --git a/src/tools/clippy/clippy_lints/src/default_instead_of_iter_empty.rs b/src/tools/clippy/clippy_lints/src/default_instead_of_iter_empty.rs
index 1ad929864..f296b80d2 100644
--- a/src/tools/clippy/clippy_lints/src/default_instead_of_iter_empty.rs
+++ b/src/tools/clippy/clippy_lints/src/default_instead_of_iter_empty.rs
@@ -1,11 +1,12 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::last_path_segment;
-use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::source::snippet_with_context;
use clippy_utils::{match_def_path, paths};
use rustc_errors::Applicability;
use rustc_hir::{def, Expr, ExprKind, GenericArg, QPath, TyKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::SyntaxContext;
declare_clippy_lint! {
/// ### What it does
@@ -38,9 +39,11 @@ impl<'tcx> LateLintPass<'tcx> for DefaultIterEmpty {
&& let QPath::Resolved(None, path) = ty_path
&& let def::Res::Def(_, def_id) = &path.res
&& match_def_path(cx, *def_id, &paths::ITER_EMPTY)
+ && let ctxt = expr.span.ctxt()
+ && ty.span.ctxt() == ctxt
{
let mut applicability = Applicability::MachineApplicable;
- let sugg = make_sugg(cx, ty_path, &mut applicability);
+ let sugg = make_sugg(cx, ty_path, ctxt, &mut applicability);
span_lint_and_sugg(
cx,
DEFAULT_INSTEAD_OF_ITER_EMPTY,
@@ -54,14 +57,19 @@ impl<'tcx> LateLintPass<'tcx> for DefaultIterEmpty {
}
}
-fn make_sugg(cx: &LateContext<'_>, ty_path: &rustc_hir::QPath<'_>, applicability: &mut Applicability) -> String {
+fn make_sugg(
+ cx: &LateContext<'_>,
+ ty_path: &rustc_hir::QPath<'_>,
+ ctxt: SyntaxContext,
+ applicability: &mut Applicability,
+) -> String {
if let Some(last) = last_path_segment(ty_path).args
&& let Some(iter_ty) = last.args.iter().find_map(|arg| match arg {
GenericArg::Type(ty) => Some(ty),
_ => None,
})
{
- format!("std::iter::empty::<{}>()", snippet_with_applicability(cx, iter_ty.span, "..", applicability))
+ format!("std::iter::empty::<{}>()", snippet_with_context(cx, iter_ty.span, ctxt, "..", applicability).0)
} else {
"std::iter::empty()".to_owned()
}
diff --git a/src/tools/clippy/clippy_lints/src/derivable_impls.rs b/src/tools/clippy/clippy_lints/src/derivable_impls.rs
index f95b8ccf0..8f68f90a2 100644
--- a/src/tools/clippy/clippy_lints/src/derivable_impls.rs
+++ b/src/tools/clippy/clippy_lints/src/derivable_impls.rs
@@ -8,7 +8,7 @@ use rustc_hir::{
Body, Expr, ExprKind, GenericArg, Impl, ImplItemKind, Item, ItemKind, Node, PathSegment, QPath, Ty, TyKind,
};
use rustc_lint::{LateContext, LateLintPass};
-use rustc_middle::ty::{AdtDef, DefIdTree};
+use rustc_middle::ty::{Adt, AdtDef, SubstsRef};
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::sym;
@@ -81,13 +81,18 @@ fn check_struct<'tcx>(
self_ty: &Ty<'_>,
func_expr: &Expr<'_>,
adt_def: AdtDef<'_>,
+ substs: SubstsRef<'_>,
) {
if let TyKind::Path(QPath::Resolved(_, p)) = self_ty.kind {
- if let Some(PathSegment { args: Some(a), .. }) = p.segments.last() {
- for arg in a.args {
- if !matches!(arg, GenericArg::Lifetime(_)) {
- return;
- }
+ if let Some(PathSegment { args, .. }) = p.segments.last() {
+ let args = args.map(|a| a.args).unwrap_or(&[]);
+
+ // substs contains the generic parameters of the type declaration, while args contains the arguments
+ // used at instantiation time. If both len are not equal, it means that some parameters were not
+ // provided (which means that the default values were used); in this case we will not risk
+ // suggesting too broad a rewrite. We won't either if any argument is a type or a const.
+ if substs.len() != args.len() || args.iter().any(|arg| !matches!(arg, GenericArg::Lifetime(_))) {
+ return;
}
}
}
@@ -176,7 +181,7 @@ impl<'tcx> LateLintPass<'tcx> for DerivableImpls {
self_ty,
..
}) = item.kind;
- if !cx.tcx.has_attr(item.owner_id.to_def_id(), sym::automatically_derived);
+ if !cx.tcx.has_attr(item.owner_id, sym::automatically_derived);
if !item.span.from_expansion();
if let Some(def_id) = trait_ref.trait_def_id();
if cx.tcx.is_diagnostic_item(sym::Default, def_id);
@@ -184,7 +189,7 @@ impl<'tcx> LateLintPass<'tcx> for DerivableImpls {
if let Some(Node::ImplItem(impl_item)) = cx.tcx.hir().find(impl_item_hir);
if let ImplItemKind::Fn(_, b) = &impl_item.kind;
if let Body { value: func_expr, .. } = cx.tcx.hir().body(*b);
- if let Some(adt_def) = cx.tcx.type_of(item.owner_id).subst_identity().ty_adt_def();
+ if let &Adt(adt_def, substs) = cx.tcx.type_of(item.owner_id).subst_identity().kind();
if let attrs = cx.tcx.hir().attrs(item.hir_id());
if !attrs.iter().any(|attr| attr.doc_str().is_some());
if let child_attrs = cx.tcx.hir().attrs(impl_item_hir);
@@ -192,7 +197,7 @@ impl<'tcx> LateLintPass<'tcx> for DerivableImpls {
then {
if adt_def.is_struct() {
- check_struct(cx, item, self_ty, func_expr, adt_def);
+ check_struct(cx, item, self_ty, func_expr, adt_def, substs);
} else if adt_def.is_enum() && self.msrv.meets(msrvs::DEFAULT_ENUM_ATTRIBUTE) {
check_enum(cx, item, func_expr, adt_def);
}
diff --git a/src/tools/clippy/clippy_lints/src/derive.rs b/src/tools/clippy/clippy_lints/src/derive.rs
index b8428d66a..f425dd5fb 100644
--- a/src/tools/clippy/clippy_lints/src/derive.rs
+++ b/src/tools/clippy/clippy_lints/src/derive.rs
@@ -24,8 +24,8 @@ use rustc_span::sym;
declare_clippy_lint! {
/// ### What it does
- /// Checks for deriving `Hash` but implementing `PartialEq`
- /// explicitly or vice versa.
+ /// Lints against manual `PartialEq` implementations for types with a derived `Hash`
+ /// implementation.
///
/// ### Why is this bad?
/// The implementation of these traits must agree (for
@@ -54,8 +54,8 @@ declare_clippy_lint! {
declare_clippy_lint! {
/// ### What it does
- /// Checks for deriving `Ord` but implementing `PartialOrd`
- /// explicitly or vice versa.
+ /// Lints against manual `PartialOrd` and `Ord` implementations for types with a derived `Ord`
+ /// or `PartialOrd` implementation.
///
/// ### Why is this bad?
/// The implementation of these traits must agree (for
@@ -212,7 +212,7 @@ impl<'tcx> LateLintPass<'tcx> for Derive {
}) = item.kind
{
let ty = cx.tcx.type_of(item.owner_id).subst_identity();
- let is_automatically_derived = cx.tcx.has_attr(item.owner_id.to_def_id(), sym::automatically_derived);
+ let is_automatically_derived = cx.tcx.has_attr(item.owner_id, sym::automatically_derived);
check_hash_peq(cx, item.span, trait_ref, ty, is_automatically_derived);
check_ord_partial_ord(cx, item.span, trait_ref, ty, is_automatically_derived);
diff --git a/src/tools/clippy/clippy_lints/src/disallowed_script_idents.rs b/src/tools/clippy/clippy_lints/src/disallowed_script_idents.rs
index 084190f00..c9fad98e4 100644
--- a/src/tools/clippy/clippy_lints/src/disallowed_script_idents.rs
+++ b/src/tools/clippy/clippy_lints/src/disallowed_script_idents.rs
@@ -32,7 +32,7 @@ declare_clippy_lint! {
/// ### Example
/// ```rust
/// // Assuming that `clippy.toml` contains the following line:
- /// // allowed-locales = ["Latin", "Cyrillic"]
+ /// // allowed-scripts = ["Latin", "Cyrillic"]
/// let counter = 10; // OK, latin is allowed.
/// let счётчик = 10; // OK, cyrillic is allowed.
/// let zähler = 10; // OK, it's still latin.
diff --git a/src/tools/clippy/clippy_lints/src/exit.rs b/src/tools/clippy/clippy_lints/src/exit.rs
index 9c8b0d076..8ba6a9e48 100644
--- a/src/tools/clippy/clippy_lints/src/exit.rs
+++ b/src/tools/clippy/clippy_lints/src/exit.rs
@@ -11,7 +11,7 @@ declare_clippy_lint! {
///
/// ### 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 potentualy other
+ /// 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.
///
diff --git a/src/tools/clippy/clippy_lints/src/explicit_write.rs b/src/tools/clippy/clippy_lints/src/explicit_write.rs
index c0ea6f338..315df6c71 100644
--- a/src/tools/clippy/clippy_lints/src/explicit_write.rs
+++ b/src/tools/clippy/clippy_lints/src/explicit_write.rs
@@ -1,5 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
-use clippy_utils::macros::FormatArgsExpn;
+use clippy_utils::macros::{find_format_args, format_args_inputs_span};
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::{is_expn_of, match_function_call, paths};
use if_chain::if_chain;
@@ -8,7 +8,7 @@ use rustc_hir::def::Res;
use rustc_hir::{BindingAnnotation, Block, BlockCheckMode, Expr, ExprKind, Node, PatKind, QPath, Stmt, StmtKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
-use rustc_span::sym;
+use rustc_span::{sym, ExpnId};
declare_clippy_lint! {
/// ### What it does
@@ -43,23 +43,22 @@ declare_lint_pass!(ExplicitWrite => [EXPLICIT_WRITE]);
impl<'tcx> LateLintPass<'tcx> for ExplicitWrite {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
- if_chain! {
- // match call to unwrap
- if let ExprKind::MethodCall(unwrap_fun, write_call, [], _) = expr.kind;
- if unwrap_fun.ident.name == sym::unwrap;
+ // match call to unwrap
+ if let ExprKind::MethodCall(unwrap_fun, write_call, [], _) = expr.kind
+ && unwrap_fun.ident.name == sym::unwrap
// match call to write_fmt
- if let ExprKind::MethodCall(write_fun, write_recv, [write_arg], _) = look_in_block(cx, &write_call.kind);
- if write_fun.ident.name == sym!(write_fmt);
+ && let ExprKind::MethodCall(write_fun, write_recv, [write_arg], _) = look_in_block(cx, &write_call.kind)
+ && write_fun.ident.name == sym!(write_fmt)
// match calls to std::io::stdout() / std::io::stderr ()
- if let Some(dest_name) = if match_function_call(cx, write_recv, &paths::STDOUT).is_some() {
+ && let Some(dest_name) = if match_function_call(cx, write_recv, &paths::STDOUT).is_some() {
Some("stdout")
} else if match_function_call(cx, write_recv, &paths::STDERR).is_some() {
Some("stderr")
} else {
None
- };
- if let Some(format_args) = FormatArgsExpn::parse(cx, write_arg);
- then {
+ }
+ {
+ find_format_args(cx, write_arg, ExpnId::root(), |format_args| {
let calling_macro =
// ordering is important here, since `writeln!` uses `write!` internally
if is_expn_of(write_call.span, "writeln").is_some() {
@@ -92,7 +91,7 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitWrite {
let mut applicability = Applicability::MachineApplicable;
let inputs_snippet = snippet_with_applicability(
cx,
- format_args.inputs_span(),
+ format_args_inputs_span(format_args),
"..",
&mut applicability,
);
@@ -104,8 +103,8 @@ impl<'tcx> LateLintPass<'tcx> for ExplicitWrite {
"try this",
format!("{prefix}{sugg_mac}!({inputs_snippet})"),
applicability,
- )
- }
+ );
+ });
}
}
}
diff --git a/src/tools/clippy/clippy_lints/src/extra_unused_type_parameters.rs b/src/tools/clippy/clippy_lints/src/extra_unused_type_parameters.rs
index 20565e1d2..eeb4de8b5 100644
--- a/src/tools/clippy/clippy_lints/src/extra_unused_type_parameters.rs
+++ b/src/tools/clippy/clippy_lints/src/extra_unused_type_parameters.rs
@@ -1,10 +1,10 @@
-use clippy_utils::diagnostics::span_lint_and_help;
+use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_then};
use clippy_utils::trait_ref_of_method;
-use rustc_data_structures::fx::FxHashMap;
-use rustc_errors::MultiSpan;
+use rustc_data_structures::fx::{FxHashMap, FxHashSet};
+use rustc_errors::Applicability;
use rustc_hir::intravisit::{walk_impl_item, walk_item, walk_param_bound, walk_ty, Visitor};
use rustc_hir::{
- BodyId, ExprKind, GenericBound, GenericParamKind, Generics, ImplItem, ImplItemKind, Item, ItemKind,
+ BodyId, ExprKind, GenericBound, GenericParam, GenericParamKind, Generics, ImplItem, ImplItemKind, Item, ItemKind,
PredicateOrigin, Ty, TyKind, WherePredicate,
};
use rustc_lint::{LateContext, LateLintPass, LintContext};
@@ -53,13 +53,19 @@ impl ExtraUnusedTypeParameters {
}
}
- /// Don't lint external macros or functions with empty bodies. Also, don't lint public items if
- /// the `avoid_breaking_exported_api` config option is set.
- fn check_false_positive(&self, cx: &LateContext<'_>, span: Span, def_id: LocalDefId, body_id: BodyId) -> bool {
+ /// Don't lint external macros or functions with empty bodies. Also, don't lint exported items
+ /// if the `avoid_breaking_exported_api` config option is set.
+ fn is_empty_exported_or_macro(
+ &self,
+ cx: &LateContext<'_>,
+ span: Span,
+ def_id: LocalDefId,
+ body_id: BodyId,
+ ) -> bool {
let body = cx.tcx.hir().body(body_id).value;
let fn_empty = matches!(&body.kind, ExprKind::Block(blk, None) if blk.stmts.is_empty() && blk.expr.is_none());
let is_exported = cx.effective_visibilities.is_exported(def_id);
- in_external_macro(cx.sess(), span) || (self.avoid_breaking_exported_api && is_exported) || fn_empty
+ in_external_macro(cx.sess(), span) || fn_empty || (is_exported && self.avoid_breaking_exported_api)
}
}
@@ -69,85 +75,129 @@ impl_lint_pass!(ExtraUnusedTypeParameters => [EXTRA_UNUSED_TYPE_PARAMETERS]);
/// trait bounds those parameters have.
struct TypeWalker<'cx, 'tcx> {
cx: &'cx LateContext<'tcx>,
- /// Collection of all the function's type parameters.
+ /// Collection of the function's type parameters. Once the function has been walked, this will
+ /// contain only unused type parameters.
ty_params: FxHashMap<DefId, Span>,
- /// Collection of any (inline) trait bounds corresponding to each type parameter.
- bounds: FxHashMap<DefId, Span>,
+ /// Collection of any inline trait bounds corresponding to each type parameter.
+ inline_bounds: FxHashMap<DefId, Span>,
+ /// Collection of any type parameters with trait bounds that appear in a where clause.
+ where_bounds: FxHashSet<DefId>,
/// The entire `Generics` object of the function, useful for querying purposes.
generics: &'tcx Generics<'tcx>,
- /// The value of this will remain `true` if *every* parameter:
- /// 1. Is a type parameter, and
- /// 2. Goes unused in the function.
- /// Otherwise, if any type parameters end up being used, or if any lifetime or const-generic
- /// parameters are present, this will be set to `false`.
- all_params_unused: bool,
}
impl<'cx, 'tcx> TypeWalker<'cx, 'tcx> {
fn new(cx: &'cx LateContext<'tcx>, generics: &'tcx Generics<'tcx>) -> Self {
- let mut all_params_unused = true;
let ty_params = generics
.params
.iter()
- .filter_map(|param| {
- if let GenericParamKind::Type { synthetic, .. } = param.kind {
- (!synthetic).then_some((param.def_id.into(), param.span))
- } else {
- if !param.is_elided_lifetime() {
- all_params_unused = false;
- }
- None
- }
+ .filter_map(|param| match param.kind {
+ GenericParamKind::Type { synthetic, .. } if !synthetic => Some((param.def_id.into(), param.span)),
+ _ => None,
})
.collect();
Self {
cx,
ty_params,
- bounds: FxHashMap::default(),
+ inline_bounds: FxHashMap::default(),
+ where_bounds: FxHashSet::default(),
generics,
- all_params_unused,
}
}
- fn mark_param_used(&mut self, def_id: DefId) {
- if self.ty_params.remove(&def_id).is_some() {
- self.all_params_unused = false;
- }
+ fn get_bound_span(&self, param: &'tcx GenericParam<'tcx>) -> Span {
+ self.inline_bounds
+ .get(&param.def_id.to_def_id())
+ .map_or(param.span, |bound_span| param.span.with_hi(bound_span.hi()))
+ }
+
+ fn emit_help(&self, spans: Vec<Span>, msg: &str, help: &'static str) {
+ span_lint_and_help(self.cx, EXTRA_UNUSED_TYPE_PARAMETERS, spans, msg, None, help);
+ }
+
+ fn emit_sugg(&self, spans: Vec<Span>, msg: &str, help: &'static str) {
+ let suggestions: Vec<(Span, String)> = spans.iter().copied().zip(std::iter::repeat(String::new())).collect();
+ span_lint_and_then(self.cx, EXTRA_UNUSED_TYPE_PARAMETERS, spans, msg, |diag| {
+ diag.multipart_suggestion(help, suggestions, Applicability::MachineApplicable);
+ });
}
fn emit_lint(&self) {
- let (msg, help) = match self.ty_params.len() {
+ let explicit_params = self
+ .generics
+ .params
+ .iter()
+ .filter(|param| !param.is_elided_lifetime() && !param.is_impl_trait())
+ .collect::<Vec<_>>();
+
+ let extra_params = explicit_params
+ .iter()
+ .enumerate()
+ .filter(|(_, param)| self.ty_params.contains_key(&param.def_id.to_def_id()))
+ .collect::<Vec<_>>();
+
+ let (msg, help) = match extra_params.len() {
0 => return,
1 => (
- "type parameter goes unused in function definition",
+ format!(
+ "type parameter `{}` goes unused in function definition",
+ extra_params[0].1.name.ident()
+ ),
"consider removing the parameter",
),
_ => (
- "type parameters go unused in function definition",
+ format!(
+ "type parameters go unused in function definition: {}",
+ extra_params
+ .iter()
+ .map(|(_, param)| param.name.ident().to_string())
+ .collect::<Vec<_>>()
+ .join(", ")
+ ),
"consider removing the parameters",
),
};
- let source_map = self.cx.sess().source_map();
- let span = if self.all_params_unused {
- self.generics.span.into() // Remove the entire list of generics
+ // If any parameters are bounded in where clauses, don't try to form a suggestion.
+ // Otherwise, the leftover where bound would produce code that wouldn't compile.
+ if extra_params
+ .iter()
+ .any(|(_, param)| self.where_bounds.contains(&param.def_id.to_def_id()))
+ {
+ let spans = extra_params
+ .iter()
+ .map(|(_, param)| self.get_bound_span(param))
+ .collect::<Vec<_>>();
+ self.emit_help(spans, &msg, help);
} else {
- MultiSpan::from_spans(
- self.ty_params
+ let spans = if explicit_params.len() == extra_params.len() {
+ vec![self.generics.span] // Remove the entire list of generics
+ } else {
+ let mut end: Option<LocalDefId> = None;
+ extra_params
.iter()
- .map(|(def_id, &span)| {
- // Extend the span past any trait bounds, and include the comma at the end.
- let span_to_extend = self.bounds.get(def_id).copied().map_or(span, Span::shrink_to_hi);
- let comma_range = source_map.span_extend_to_next_char(span_to_extend, '>', false);
- let comma_span = source_map.span_through_char(comma_range, ',');
- span.with_hi(comma_span.hi())
+ .rev()
+ .map(|(idx, param)| {
+ if let Some(next) = explicit_params.get(idx + 1) && end != Some(next.def_id) {
+ // Extend the current span forward, up until the next param in the list.
+ param.span.until(next.span)
+ } else {
+ // Extend the current span back to include the comma following the previous
+ // param. If the span of the next param in the list has already been
+ // extended, we continue the chain. This is why we're iterating in reverse.
+ end = Some(param.def_id);
+
+ // idx will never be 0, else we'd be removing the entire list of generics
+ let prev = explicit_params[idx - 1];
+ let prev_span = self.get_bound_span(prev);
+ self.get_bound_span(param).with_lo(prev_span.hi())
+ }
})
- .collect(),
- )
+ .collect()
+ };
+ self.emit_sugg(spans, &msg, help);
};
-
- span_lint_and_help(self.cx, EXTRA_UNUSED_TYPE_PARAMETERS, span, msg, None, help);
}
}
@@ -162,7 +212,7 @@ impl<'cx, 'tcx> Visitor<'tcx> for TypeWalker<'cx, 'tcx> {
fn visit_ty(&mut self, t: &'tcx Ty<'tcx>) {
if let Some((def_id, _)) = t.peel_refs().as_generic_param() {
- self.mark_param_used(def_id);
+ self.ty_params.remove(&def_id);
} else if let TyKind::OpaqueDef(id, _, _) = t.kind {
// Explicitly walk OpaqueDef. Normally `walk_ty` would do the job, but it calls
// `visit_nested_item`, which checks that `Self::NestedFilter::INTER` is set. We're
@@ -176,9 +226,18 @@ impl<'cx, 'tcx> Visitor<'tcx> for TypeWalker<'cx, 'tcx> {
fn visit_where_predicate(&mut self, predicate: &'tcx WherePredicate<'tcx>) {
if let WherePredicate::BoundPredicate(predicate) = predicate {
- // Collect spans for any bounds on type parameters. We only keep bounds that appear in
- // the list of generics (not in a where-clause).
+ // Collect spans for any bounds on type parameters.
if let Some((def_id, _)) = predicate.bounded_ty.peel_refs().as_generic_param() {
+ match predicate.origin {
+ PredicateOrigin::GenericParam => {
+ self.inline_bounds.insert(def_id, predicate.span);
+ },
+ PredicateOrigin::WhereClause => {
+ self.where_bounds.insert(def_id);
+ },
+ PredicateOrigin::ImplTrait => (),
+ }
+
// If the bound contains non-public traits, err on the safe side and don't lint the
// corresponding parameter.
if !predicate
@@ -187,12 +246,10 @@ impl<'cx, 'tcx> Visitor<'tcx> for TypeWalker<'cx, 'tcx> {
.filter_map(bound_to_trait_def_id)
.all(|id| self.cx.effective_visibilities.is_exported(id))
{
- self.mark_param_used(def_id);
- } else if let PredicateOrigin::GenericParam = predicate.origin {
- self.bounds.insert(def_id, predicate.span);
+ self.ty_params.remove(&def_id);
}
}
- // Only walk the right-hand side of where-bounds
+ // Only walk the right-hand side of where bounds
for bound in predicate.bounds {
walk_param_bound(self, bound);
}
@@ -207,7 +264,7 @@ impl<'cx, 'tcx> Visitor<'tcx> for TypeWalker<'cx, 'tcx> {
impl<'tcx> LateLintPass<'tcx> for ExtraUnusedTypeParameters {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
if let ItemKind::Fn(_, generics, body_id) = item.kind
- && !self.check_false_positive(cx, item.span, item.owner_id.def_id, body_id)
+ && !self.is_empty_exported_or_macro(cx, item.span, item.owner_id.def_id, body_id)
{
let mut walker = TypeWalker::new(cx, generics);
walk_item(&mut walker, item);
@@ -219,7 +276,7 @@ impl<'tcx> LateLintPass<'tcx> for ExtraUnusedTypeParameters {
// Only lint on inherent methods, not trait methods.
if let ImplItemKind::Fn(.., body_id) = item.kind
&& trait_ref_of_method(cx, item.owner_id.def_id).is_none()
- && !self.check_false_positive(cx, item.span, item.owner_id.def_id, body_id)
+ && !self.is_empty_exported_or_macro(cx, item.span, item.owner_id.def_id, body_id)
{
let mut walker = TypeWalker::new(cx, item.generics);
walk_impl_item(&mut walker, item);
diff --git a/src/tools/clippy/clippy_lints/src/fn_null_check.rs b/src/tools/clippy/clippy_lints/src/fn_null_check.rs
index 91c8c340c..d8f4a5fe2 100644
--- a/src/tools/clippy/clippy_lints/src/fn_null_check.rs
+++ b/src/tools/clippy/clippy_lints/src/fn_null_check.rs
@@ -25,7 +25,7 @@ declare_clippy_lint! {
///
/// if fn_ptr.is_none() { ... }
/// ```
- #[clippy::version = "1.67.0"]
+ #[clippy::version = "1.68.0"]
pub FN_NULL_CHECK,
correctness,
"`fn()` type assumed to be nullable"
diff --git a/src/tools/clippy/clippy_lints/src/format.rs b/src/tools/clippy/clippy_lints/src/format.rs
index d0fab6949..d34d6e927 100644
--- a/src/tools/clippy/clippy_lints/src/format.rs
+++ b/src/tools/clippy/clippy_lints/src/format.rs
@@ -1,14 +1,13 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
-use clippy_utils::macros::{root_macro_call_first_node, FormatArgsExpn};
-use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::macros::{find_format_arg_expr, find_format_args, root_macro_call_first_node};
+use clippy_utils::source::{snippet_opt, snippet_with_context};
use clippy_utils::sugg::Sugg;
-use if_chain::if_chain;
+use rustc_ast::{FormatArgsPiece, FormatOptions, FormatTrait};
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty;
use rustc_session::{declare_lint_pass, declare_tool_lint};
-use rustc_span::symbol::kw;
use rustc_span::{sym, Span};
declare_clippy_lint! {
@@ -44,55 +43,53 @@ declare_lint_pass!(UselessFormat => [USELESS_FORMAT]);
impl<'tcx> LateLintPass<'tcx> for UselessFormat {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
- let (format_args, call_site) = if_chain! {
- if let Some(macro_call) = root_macro_call_first_node(cx, expr);
- if cx.tcx.is_diagnostic_item(sym::format_macro, macro_call.def_id);
- if let Some(format_args) = FormatArgsExpn::find_nested(cx, expr, macro_call.expn);
- then {
- (format_args, macro_call.span)
- } else {
- return
- }
- };
+ let Some(macro_call) = root_macro_call_first_node(cx, expr) else { return };
+ if !cx.tcx.is_diagnostic_item(sym::format_macro, macro_call.def_id) {
+ return;
+ }
+
+ find_format_args(cx, expr, macro_call.expn, |format_args| {
+ let mut applicability = Applicability::MachineApplicable;
+ let call_site = macro_call.span;
- let mut applicability = Applicability::MachineApplicable;
- if format_args.args.is_empty() {
- match *format_args.format_string.parts {
- [] => span_useless_format_empty(cx, call_site, "String::new()".to_owned(), applicability),
- [_] => {
+ match (format_args.arguments.all_args(), &format_args.template[..]) {
+ ([], []) => span_useless_format_empty(cx, call_site, "String::new()".to_owned(), applicability),
+ ([], [_]) => {
// Simulate macro expansion, converting {{ and }} to { and }.
- let s_expand = format_args.format_string.snippet.replace("{{", "{").replace("}}", "}");
+ let Some(snippet) = snippet_opt(cx, format_args.span) else { return };
+ let s_expand = snippet.replace("{{", "{").replace("}}", "}");
let sugg = format!("{s_expand}.to_string()");
span_useless_format(cx, call_site, sugg, applicability);
},
- [..] => {},
- }
- } else if let [arg] = &*format_args.args {
- let value = arg.param.value;
- if_chain! {
- if format_args.format_string.parts == [kw::Empty];
- if arg.format.is_default();
- if match cx.typeck_results().expr_ty(value).peel_refs().kind() {
- ty::Adt(adt, _) => Some(adt.did()) == cx.tcx.lang_items().string(),
- ty::Str => true,
- _ => false,
- };
- then {
- let is_new_string = match value.kind {
- ExprKind::Binary(..) => true,
- ExprKind::MethodCall(path, ..) => path.ident.name == sym::to_string,
- _ => false,
- };
- let sugg = if is_new_string {
- snippet_with_applicability(cx, value.span, "..", &mut applicability).into_owned()
- } else {
- let sugg = Sugg::hir_with_applicability(cx, value, "<arg>", &mut applicability);
- format!("{}.to_string()", sugg.maybe_par())
- };
- span_useless_format(cx, call_site, sugg, applicability);
- }
+ ([arg], [piece]) => {
+ if let Ok(value) = find_format_arg_expr(expr, arg)
+ && let FormatArgsPiece::Placeholder(placeholder) = piece
+ && placeholder.format_trait == FormatTrait::Display
+ && placeholder.format_options == FormatOptions::default()
+ && match cx.typeck_results().expr_ty(value).peel_refs().kind() {
+ ty::Adt(adt, _) => Some(adt.did()) == cx.tcx.lang_items().string(),
+ ty::Str => true,
+ _ => false,
+ }
+ {
+ let is_new_string = match value.kind {
+ ExprKind::Binary(..) => true,
+ ExprKind::MethodCall(path, ..) => path.ident.name == sym::to_string,
+ _ => false,
+ };
+ let sugg = if is_new_string {
+ snippet_with_context(cx, value.span, call_site.ctxt(), "..", &mut applicability).0.into_owned()
+ } else {
+ let sugg = Sugg::hir_with_context(cx, value, call_site.ctxt(), "<arg>", &mut applicability);
+ format!("{}.to_string()", sugg.maybe_par())
+ };
+ span_useless_format(cx, call_site, sugg, applicability);
+
+ }
+ },
+ _ => {},
}
- };
+ });
}
}
diff --git a/src/tools/clippy/clippy_lints/src/format_args.rs b/src/tools/clippy/clippy_lints/src/format_args.rs
index c511d85e9..08e45ed7d 100644
--- a/src/tools/clippy/clippy_lints/src/format_args.rs
+++ b/src/tools/clippy/clippy_lints/src/format_args.rs
@@ -1,27 +1,31 @@
+use arrayvec::ArrayVec;
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
use clippy_utils::is_diag_trait_item;
-use clippy_utils::macros::FormatParamKind::{Implicit, Named, NamedInline, Numbered, Starred};
use clippy_utils::macros::{
- is_assert_macro, is_format_macro, is_panic, root_macro_call, Count, FormatArg, FormatArgsExpn, FormatParam,
- FormatParamUsage,
+ find_format_arg_expr, find_format_args, format_arg_removal_span, format_placeholder_format_span, is_assert_macro,
+ is_format_macro, is_panic, root_macro_call, root_macro_call_first_node, FormatParamUsage,
};
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::snippet_opt;
use clippy_utils::ty::{implements_trait, is_type_lang_item};
use if_chain::if_chain;
use itertools::Itertools;
+use rustc_ast::{
+ FormatArgPosition, FormatArgPositionKind, FormatArgsPiece, FormatArgumentKind, FormatCount, FormatOptions,
+ FormatPlaceholder, FormatTrait,
+};
use rustc_errors::{
Applicability,
SuggestionStyle::{CompletelyHidden, ShowCode},
};
-use rustc_hir::{Expr, ExprKind, HirId, LangItem, QPath};
+use rustc_hir::{Expr, ExprKind, LangItem};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::ty::adjustment::{Adjust, Adjustment};
use rustc_middle::ty::Ty;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::def_id::DefId;
use rustc_span::edition::Edition::Edition2021;
-use rustc_span::{sym, ExpnData, ExpnKind, Span, Symbol};
+use rustc_span::{sym, Span, Symbol};
declare_clippy_lint! {
/// ### What it does
@@ -184,72 +188,79 @@ impl FormatArgs {
impl<'tcx> LateLintPass<'tcx> for FormatArgs {
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
- if let Some(format_args) = FormatArgsExpn::parse(cx, expr)
- && let expr_expn_data = expr.span.ctxt().outer_expn_data()
- && let outermost_expn_data = outermost_expn_data(expr_expn_data)
- && let Some(macro_def_id) = outermost_expn_data.macro_def_id
- && is_format_macro(cx, macro_def_id)
- && let ExpnKind::Macro(_, name) = outermost_expn_data.kind
- {
- for arg in &format_args.args {
- check_unused_format_specifier(cx, arg);
- if !arg.format.is_default() {
- continue;
- }
- if is_aliased(&format_args, arg.param.value.hir_id) {
- continue;
+ let Some(macro_call) = root_macro_call_first_node(cx, expr) else { return };
+ if !is_format_macro(cx, macro_call.def_id) {
+ return;
+ }
+ let name = cx.tcx.item_name(macro_call.def_id);
+
+ find_format_args(cx, expr, macro_call.expn, |format_args| {
+ for piece in &format_args.template {
+ if let FormatArgsPiece::Placeholder(placeholder) = piece
+ && let Ok(index) = placeholder.argument.index
+ && let Some(arg) = format_args.arguments.all_args().get(index)
+ {
+ let arg_expr = find_format_arg_expr(expr, arg);
+
+ check_unused_format_specifier(cx, placeholder, arg_expr);
+
+ if placeholder.format_trait != FormatTrait::Display
+ || placeholder.format_options != FormatOptions::default()
+ || is_aliased(format_args, index)
+ {
+ continue;
+ }
+
+ if let Ok(arg_hir_expr) = arg_expr {
+ check_format_in_format_args(cx, macro_call.span, name, arg_hir_expr);
+ check_to_string_in_format_args(cx, name, arg_hir_expr);
+ }
}
- check_format_in_format_args(cx, outermost_expn_data.call_site, name, arg.param.value);
- check_to_string_in_format_args(cx, name, arg.param.value);
}
+
if self.msrv.meets(msrvs::FORMAT_ARGS_CAPTURE) {
- check_uninlined_args(cx, &format_args, outermost_expn_data.call_site, macro_def_id, self.ignore_mixed);
+ check_uninlined_args(cx, format_args, macro_call.span, macro_call.def_id, self.ignore_mixed);
}
- }
+ });
}
extract_msrv_attr!(LateContext);
}
-fn check_unused_format_specifier(cx: &LateContext<'_>, arg: &FormatArg<'_>) {
- let param_ty = cx.typeck_results().expr_ty(arg.param.value).peel_refs();
+fn check_unused_format_specifier(
+ cx: &LateContext<'_>,
+ placeholder: &FormatPlaceholder,
+ arg_expr: Result<&Expr<'_>, &rustc_ast::Expr>,
+) {
+ let ty_or_ast_expr = arg_expr.map(|expr| cx.typeck_results().expr_ty(expr).peel_refs());
- if let Count::Implied(Some(mut span)) = arg.format.precision
- && !span.is_empty()
- {
- span_lint_and_then(
- cx,
- UNUSED_FORMAT_SPECS,
- span,
- "empty precision specifier has no effect",
- |diag| {
- if param_ty.is_floating_point() {
- diag.note("a precision specifier is not required to format floats");
- }
+ let is_format_args = match ty_or_ast_expr {
+ Ok(ty) => is_type_lang_item(cx, ty, LangItem::FormatArguments),
+ Err(expr) => matches!(expr.peel_parens_and_refs().kind, rustc_ast::ExprKind::FormatArgs(_)),
+ };
- if arg.format.is_default() {
- // If there's no other specifiers remove the `:` too
- span = arg.format_span();
- }
+ let options = &placeholder.format_options;
- diag.span_suggestion_verbose(span, "remove the `.`", "", Applicability::MachineApplicable);
- },
- );
- }
+ let arg_span = match arg_expr {
+ Ok(expr) => expr.span,
+ Err(expr) => expr.span,
+ };
- if is_type_lang_item(cx, param_ty, LangItem::FormatArguments) && !arg.format.is_default_for_trait() {
+ if let Some(placeholder_span) = placeholder.span
+ && is_format_args
+ && *options != FormatOptions::default()
+ {
span_lint_and_then(
cx,
UNUSED_FORMAT_SPECS,
- arg.span,
+ placeholder_span,
"format specifiers have no effect on `format_args!()`",
|diag| {
- let mut suggest_format = |spec, span| {
+ let mut suggest_format = |spec| {
let message = format!("for the {spec} to apply consider using `format!()`");
- if let Some(mac_call) = root_macro_call(arg.param.value.span)
+ if let Some(mac_call) = root_macro_call(arg_span)
&& cx.tcx.is_diagnostic_item(sym::format_args_macro, mac_call.def_id)
- && arg.span.eq_ctxt(mac_call.span)
{
diag.span_suggestion(
cx.sess().source_map().span_until_char(mac_call.span, '!'),
@@ -257,25 +268,27 @@ fn check_unused_format_specifier(cx: &LateContext<'_>, arg: &FormatArg<'_>) {
"format",
Applicability::MaybeIncorrect,
);
- } else if let Some(span) = span {
- diag.span_help(span, message);
+ } else {
+ diag.help(message);
}
};
- if !arg.format.width.is_implied() {
- suggest_format("width", arg.format.width.span());
+ if options.width.is_some() {
+ suggest_format("width");
}
- if !arg.format.precision.is_implied() {
- suggest_format("precision", arg.format.precision.span());
+ if options.precision.is_some() {
+ suggest_format("precision");
}
- diag.span_suggestion_verbose(
- arg.format_span(),
- "if the current behavior is intentional, remove the format specifiers",
- "",
- Applicability::MaybeIncorrect,
- );
+ if let Some(format_span) = format_placeholder_format_span(placeholder) {
+ diag.span_suggestion_verbose(
+ format_span,
+ "if the current behavior is intentional, remove the format specifiers",
+ "",
+ Applicability::MaybeIncorrect,
+ );
+ }
},
);
}
@@ -283,12 +296,12 @@ fn check_unused_format_specifier(cx: &LateContext<'_>, arg: &FormatArg<'_>) {
fn check_uninlined_args(
cx: &LateContext<'_>,
- args: &FormatArgsExpn<'_>,
+ args: &rustc_ast::FormatArgs,
call_site: Span,
def_id: DefId,
ignore_mixed: bool,
) {
- if args.format_string.span.from_expansion() {
+ if args.span.from_expansion() {
return;
}
if call_site.edition() < Edition2021 && (is_panic(cx, def_id) || is_assert_macro(cx, def_id)) {
@@ -303,7 +316,13 @@ fn check_uninlined_args(
// we cannot remove any other arguments in the format string,
// because the index numbers might be wrong after inlining.
// Example of an un-inlinable format: print!("{}{1}", foo, 2)
- if !args.params().all(|p| check_one_arg(args, &p, &mut fixes, ignore_mixed)) || fixes.is_empty() {
+ for (pos, usage) in format_arg_positions(args) {
+ if !check_one_arg(args, pos, usage, &mut fixes, ignore_mixed) {
+ return;
+ }
+ }
+
+ if fixes.is_empty() {
return;
}
@@ -332,47 +351,40 @@ fn check_uninlined_args(
}
fn check_one_arg(
- args: &FormatArgsExpn<'_>,
- param: &FormatParam<'_>,
+ args: &rustc_ast::FormatArgs,
+ pos: &FormatArgPosition,
+ usage: FormatParamUsage,
fixes: &mut Vec<(Span, String)>,
ignore_mixed: bool,
) -> bool {
- if matches!(param.kind, Implicit | Starred | Named(_) | Numbered)
- && let ExprKind::Path(QPath::Resolved(None, path)) = param.value.kind
- && let [segment] = path.segments
+ let index = pos.index.unwrap();
+ let arg = &args.arguments.all_args()[index];
+
+ if !matches!(arg.kind, FormatArgumentKind::Captured(_))
+ && let rustc_ast::ExprKind::Path(None, path) = &arg.expr.kind
+ && let [segment] = path.segments.as_slice()
&& segment.args.is_none()
- && let Some(arg_span) = args.value_with_prev_comma_span(param.value.hir_id)
+ && let Some(arg_span) = format_arg_removal_span(args, index)
+ && let Some(pos_span) = pos.span
{
- let replacement = match param.usage {
+ let replacement = match usage {
FormatParamUsage::Argument => segment.ident.name.to_string(),
FormatParamUsage::Width => format!("{}$", segment.ident.name),
FormatParamUsage::Precision => format!(".{}$", segment.ident.name),
};
- fixes.push((param.span, replacement));
+ fixes.push((pos_span, replacement));
fixes.push((arg_span, String::new()));
true // successful inlining, continue checking
} else {
// Do not continue inlining (return false) in case
// * if we can't inline a numbered argument, e.g. `print!("{0} ...", foo.bar, ...)`
// * if allow_mixed_uninlined_format_args is false and this arg hasn't been inlined already
- param.kind != Numbered && (!ignore_mixed || matches!(param.kind, NamedInline(_)))
- }
-}
-
-fn outermost_expn_data(expn_data: ExpnData) -> ExpnData {
- if expn_data.call_site.from_expansion() {
- outermost_expn_data(expn_data.call_site.ctxt().outer_expn_data())
- } else {
- expn_data
+ pos.kind != FormatArgPositionKind::Number
+ && (!ignore_mixed || matches!(arg.kind, FormatArgumentKind::Captured(_)))
}
}
-fn check_format_in_format_args(
- cx: &LateContext<'_>,
- call_site: Span,
- name: Symbol,
- arg: &Expr<'_>,
-) {
+fn check_format_in_format_args(cx: &LateContext<'_>, call_site: Span, name: Symbol, arg: &Expr<'_>) {
let expn_data = arg.span.ctxt().outer_expn_data();
if expn_data.call_site.from_expansion() {
return;
@@ -443,9 +455,33 @@ fn check_to_string_in_format_args(cx: &LateContext<'_>, name: Symbol, value: &Ex
}
}
-/// Returns true if `hir_id` is referred to by multiple format params
-fn is_aliased(args: &FormatArgsExpn<'_>, hir_id: HirId) -> bool {
- args.params().filter(|param| param.value.hir_id == hir_id).at_most_one().is_err()
+fn format_arg_positions(
+ format_args: &rustc_ast::FormatArgs,
+) -> impl Iterator<Item = (&FormatArgPosition, FormatParamUsage)> {
+ format_args.template.iter().flat_map(|piece| match piece {
+ FormatArgsPiece::Placeholder(placeholder) => {
+ let mut positions = ArrayVec::<_, 3>::new();
+
+ positions.push((&placeholder.argument, FormatParamUsage::Argument));
+ if let Some(FormatCount::Argument(position)) = &placeholder.format_options.width {
+ positions.push((position, FormatParamUsage::Width));
+ }
+ if let Some(FormatCount::Argument(position)) = &placeholder.format_options.precision {
+ positions.push((position, FormatParamUsage::Precision));
+ }
+
+ positions
+ },
+ FormatArgsPiece::Literal(_) => ArrayVec::new(),
+ })
+}
+
+/// Returns true if the format argument at `index` is referred to by multiple format params
+fn is_aliased(format_args: &rustc_ast::FormatArgs, index: usize) -> bool {
+ format_arg_positions(format_args)
+ .filter(|(position, _)| position.index == Ok(index))
+ .at_most_one()
+ .is_err()
}
fn count_needed_derefs<'tcx, I>(mut ty: Ty<'tcx>, mut iter: I) -> (usize, Ty<'tcx>)
@@ -455,7 +491,11 @@ where
let mut n_total = 0;
let mut n_needed = 0;
loop {
- if let Some(Adjustment { kind: Adjust::Deref(overloaded_deref), target }) = iter.next() {
+ if let Some(Adjustment {
+ kind: Adjust::Deref(overloaded_deref),
+ target,
+ }) = iter.next()
+ {
n_total += 1;
if overloaded_deref.is_some() {
n_needed = n_total;
diff --git a/src/tools/clippy/clippy_lints/src/format_impl.rs b/src/tools/clippy/clippy_lints/src/format_impl.rs
index ed1342a54..e3ddbfb59 100644
--- a/src/tools/clippy/clippy_lints/src/format_impl.rs
+++ b/src/tools/clippy/clippy_lints/src/format_impl.rs
@@ -1,11 +1,13 @@
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
-use clippy_utils::macros::{is_format_macro, root_macro_call_first_node, FormatArg, FormatArgsExpn};
+use clippy_utils::macros::{find_format_arg_expr, find_format_args, is_format_macro, root_macro_call_first_node};
use clippy_utils::{get_parent_as_impl, is_diag_trait_item, path_to_local, peel_ref_operators};
use if_chain::if_chain;
+use rustc_ast::{FormatArgsPiece, FormatTrait};
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, Impl, ImplItem, ImplItemKind, QPath};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::Span;
use rustc_span::{sym, symbol::kw, Symbol};
declare_clippy_lint! {
@@ -89,7 +91,7 @@ declare_clippy_lint! {
}
#[derive(Clone, Copy)]
-struct FormatTrait {
+struct FormatTraitNames {
/// e.g. `sym::Display`
name: Symbol,
/// `f` in `fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {}`
@@ -99,7 +101,7 @@ struct FormatTrait {
#[derive(Default)]
pub struct FormatImpl {
// Whether we are inside Display or Debug trait impl - None for neither
- format_trait_impl: Option<FormatTrait>,
+ format_trait_impl: Option<FormatTraitNames>,
}
impl FormatImpl {
@@ -161,43 +163,57 @@ fn check_to_string_in_display(cx: &LateContext<'_>, expr: &Expr<'_>) {
}
}
-fn check_self_in_format_args<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, impl_trait: FormatTrait) {
+fn check_self_in_format_args<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>, impl_trait: FormatTraitNames) {
// Check each arg in format calls - do we ever use Display on self (directly or via deref)?
- if_chain! {
- if let Some(outer_macro) = root_macro_call_first_node(cx, expr);
- if let macro_def_id = outer_macro.def_id;
- if let Some(format_args) = FormatArgsExpn::find_nested(cx, expr, outer_macro.expn);
- if is_format_macro(cx, macro_def_id);
- then {
- for arg in format_args.args {
- if arg.format.r#trait != impl_trait.name {
- continue;
+ if let Some(outer_macro) = root_macro_call_first_node(cx, expr)
+ && let macro_def_id = outer_macro.def_id
+ && is_format_macro(cx, macro_def_id)
+ {
+ find_format_args(cx, expr, outer_macro.expn, |format_args| {
+ for piece in &format_args.template {
+ if let FormatArgsPiece::Placeholder(placeholder) = piece
+ && let trait_name = match placeholder.format_trait {
+ FormatTrait::Display => sym::Display,
+ FormatTrait::Debug => sym::Debug,
+ FormatTrait::LowerExp => sym!(LowerExp),
+ FormatTrait::UpperExp => sym!(UpperExp),
+ FormatTrait::Octal => sym!(Octal),
+ FormatTrait::Pointer => sym::Pointer,
+ FormatTrait::Binary => sym!(Binary),
+ FormatTrait::LowerHex => sym!(LowerHex),
+ FormatTrait::UpperHex => sym!(UpperHex),
+ }
+ && trait_name == impl_trait.name
+ && let Ok(index) = placeholder.argument.index
+ && let Some(arg) = format_args.arguments.all_args().get(index)
+ && let Ok(arg_expr) = find_format_arg_expr(expr, arg)
+ {
+ check_format_arg_self(cx, expr.span, arg_expr, impl_trait);
}
- check_format_arg_self(cx, expr, &arg, impl_trait);
}
- }
+ });
}
}
-fn check_format_arg_self(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &FormatArg<'_>, impl_trait: FormatTrait) {
+fn check_format_arg_self(cx: &LateContext<'_>, span: Span, arg: &Expr<'_>, impl_trait: FormatTraitNames) {
// Handle multiple dereferencing of references e.g. &&self
// Handle dereference of &self -> self that is equivalent (i.e. via *self in fmt() impl)
// Since the argument to fmt is itself a reference: &self
- let reference = peel_ref_operators(cx, arg.param.value);
+ let reference = peel_ref_operators(cx, arg);
let map = cx.tcx.hir();
// Is the reference self?
if path_to_local(reference).map(|x| map.name(x)) == Some(kw::SelfLower) {
- let FormatTrait { name, .. } = impl_trait;
+ let FormatTraitNames { name, .. } = impl_trait;
span_lint(
cx,
RECURSIVE_FORMAT_IMPL,
- expr.span,
+ span,
&format!("using `self` as `{name}` in `impl {name}` will cause infinite recursion"),
);
}
}
-fn check_print_in_format_impl(cx: &LateContext<'_>, expr: &Expr<'_>, impl_trait: FormatTrait) {
+fn check_print_in_format_impl(cx: &LateContext<'_>, expr: &Expr<'_>, impl_trait: FormatTraitNames) {
if_chain! {
if let Some(macro_call) = root_macro_call_first_node(cx, expr);
if let Some(name) = cx.tcx.get_diagnostic_name(macro_call.def_id);
@@ -227,7 +243,7 @@ fn check_print_in_format_impl(cx: &LateContext<'_>, expr: &Expr<'_>, impl_trait:
}
}
-fn is_format_trait_impl(cx: &LateContext<'_>, impl_item: &ImplItem<'_>) -> Option<FormatTrait> {
+fn is_format_trait_impl(cx: &LateContext<'_>, impl_item: &ImplItem<'_>) -> Option<FormatTraitNames> {
if_chain! {
if impl_item.ident.name == sym::fmt;
if let ImplItemKind::Fn(_, body_id) = impl_item.kind;
@@ -241,7 +257,7 @@ fn is_format_trait_impl(cx: &LateContext<'_>, impl_item: &ImplItem<'_>) -> Optio
.and_then(|param| param.pat.simple_ident())
.map(|ident| ident.name);
- Some(FormatTrait {
+ Some(FormatTraitNames {
name,
formatter_name,
})
diff --git a/src/tools/clippy/clippy_lints/src/functions/impl_trait_in_params.rs b/src/tools/clippy/clippy_lints/src/functions/impl_trait_in_params.rs
index 2811a73f6..d3d0d91c1 100644
--- a/src/tools/clippy/clippy_lints/src/functions/impl_trait_in_params.rs
+++ b/src/tools/clippy/clippy_lints/src/functions/impl_trait_in_params.rs
@@ -22,7 +22,7 @@ pub(super) fn check_fn<'tcx>(cx: &LateContext<'_>, kind: &'tcx FnKind<'_>, body:
if let Some(gen_span) = generics.span_for_param_suggestion() {
diag.span_suggestion_with_style(
gen_span,
- "add a type paremeter",
+ "add a type parameter",
format!(", {{ /* Generic name */ }}: {}", &param.name.ident().as_str()[5..]),
rustc_errors::Applicability::HasPlaceholders,
rustc_errors::SuggestionStyle::ShowAlways,
@@ -35,7 +35,7 @@ pub(super) fn check_fn<'tcx>(cx: &LateContext<'_>, kind: &'tcx FnKind<'_>, body:
ident.span.ctxt(),
ident.span.parent(),
),
- "add a type paremeter",
+ "add a type parameter",
format!("<{{ /* Generic name */ }}: {}>", &param.name.ident().as_str()[5..]),
rustc_errors::Applicability::HasPlaceholders,
rustc_errors::SuggestionStyle::ShowAlways,
diff --git a/src/tools/clippy/clippy_lints/src/functions/misnamed_getters.rs b/src/tools/clippy/clippy_lints/src/functions/misnamed_getters.rs
index 8b53ee68e..e5945939e 100644
--- a/src/tools/clippy/clippy_lints/src/functions/misnamed_getters.rs
+++ b/src/tools/clippy/clippy_lints/src/functions/misnamed_getters.rs
@@ -97,7 +97,7 @@ pub fn check_fn(cx: &LateContext<'_>, kind: FnKind<'_>, decl: &FnDecl<'_>, body:
let Some(correct_field) = correct_field else {
// There is no field corresponding to the getter name.
- // FIXME: This can be a false positive if the correct field is reachable trought deeper autodereferences than used_field is
+ // FIXME: This can be a false positive if the correct field is reachable through deeper autodereferences than used_field is
return;
};
diff --git a/src/tools/clippy/clippy_lints/src/functions/mod.rs b/src/tools/clippy/clippy_lints/src/functions/mod.rs
index d2852b4ac..7c5e44bb7 100644
--- a/src/tools/clippy/clippy_lints/src/functions/mod.rs
+++ b/src/tools/clippy/clippy_lints/src/functions/mod.rs
@@ -185,7 +185,7 @@ declare_clippy_lint! {
/// ### Examples
/// ```rust
/// // this could be annotated with `#[must_use]`.
- /// fn id<T>(t: T) -> T { t }
+ /// pub fn id<T>(t: T) -> T { t }
/// ```
#[clippy::version = "1.40.0"]
pub MUST_USE_CANDIDATE,
diff --git a/src/tools/clippy/clippy_lints/src/functions/must_use.rs b/src/tools/clippy/clippy_lints/src/functions/must_use.rs
index 29bdc46b6..d0ad26282 100644
--- a/src/tools/clippy/clippy_lints/src/functions/must_use.rs
+++ b/src/tools/clippy/clippy_lints/src/functions/must_use.rs
@@ -1,7 +1,9 @@
+use hir::FnSig;
use rustc_ast::ast::Attribute;
use rustc_errors::Applicability;
use rustc_hir::def_id::DefIdSet;
use rustc_hir::{self as hir, def::Res, QPath};
+use rustc_infer::infer::TyCtxtInferExt;
use rustc_lint::{LateContext, LintContext};
use rustc_middle::{
lint::in_external_macro,
@@ -22,13 +24,13 @@ use super::{DOUBLE_MUST_USE, MUST_USE_CANDIDATE, MUST_USE_UNIT};
pub(super) fn check_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
let attrs = cx.tcx.hir().attrs(item.hir_id());
- let attr = cx.tcx.get_attr(item.owner_id.to_def_id(), sym::must_use);
+ let attr = cx.tcx.get_attr(item.owner_id, sym::must_use);
if let hir::ItemKind::Fn(ref sig, _generics, ref body_id) = item.kind {
let is_public = cx.effective_visibilities.is_exported(item.owner_id.def_id);
let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
if let Some(attr) = attr {
- check_needless_must_use(cx, sig.decl, item.owner_id, item.span, fn_header_span, attr);
- } else if is_public && !is_proc_macro(cx.sess(), attrs) && !attrs.iter().any(|a| a.has_name(sym::no_mangle)) {
+ check_needless_must_use(cx, sig.decl, item.owner_id, item.span, fn_header_span, attr, sig);
+ } else if is_public && !is_proc_macro(attrs) && !attrs.iter().any(|a| a.has_name(sym::no_mangle)) {
check_must_use_candidate(
cx,
sig.decl,
@@ -47,13 +49,10 @@ pub(super) fn check_impl_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::Imp
let is_public = cx.effective_visibilities.is_exported(item.owner_id.def_id);
let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
let attrs = cx.tcx.hir().attrs(item.hir_id());
- let attr = cx.tcx.get_attr(item.owner_id.to_def_id(), sym::must_use);
+ let attr = cx.tcx.get_attr(item.owner_id, sym::must_use);
if let Some(attr) = attr {
- check_needless_must_use(cx, sig.decl, item.owner_id, item.span, fn_header_span, attr);
- } else if is_public
- && !is_proc_macro(cx.sess(), attrs)
- && trait_ref_of_method(cx, item.owner_id.def_id).is_none()
- {
+ check_needless_must_use(cx, sig.decl, item.owner_id, item.span, fn_header_span, attr, sig);
+ } else if is_public && !is_proc_macro(attrs) && trait_ref_of_method(cx, item.owner_id.def_id).is_none() {
check_must_use_candidate(
cx,
sig.decl,
@@ -73,12 +72,12 @@ pub(super) fn check_trait_item<'tcx>(cx: &LateContext<'tcx>, item: &'tcx hir::Tr
let fn_header_span = item.span.with_hi(sig.decl.output.span().hi());
let attrs = cx.tcx.hir().attrs(item.hir_id());
- let attr = cx.tcx.get_attr(item.owner_id.to_def_id(), sym::must_use);
+ let attr = cx.tcx.get_attr(item.owner_id, sym::must_use);
if let Some(attr) = attr {
- check_needless_must_use(cx, sig.decl, item.owner_id, item.span, fn_header_span, attr);
+ check_needless_must_use(cx, sig.decl, item.owner_id, item.span, fn_header_span, attr, sig);
} else if let hir::TraitFn::Provided(eid) = *eid {
let body = cx.tcx.hir().body(eid);
- if attr.is_none() && is_public && !is_proc_macro(cx.sess(), attrs) {
+ if attr.is_none() && is_public && !is_proc_macro(attrs) {
check_must_use_candidate(
cx,
sig.decl,
@@ -100,6 +99,7 @@ fn check_needless_must_use(
item_span: Span,
fn_header_span: Span,
attr: &Attribute,
+ sig: &FnSig<'_>,
) {
if in_external_macro(cx.sess(), item_span) {
return;
@@ -115,6 +115,15 @@ fn check_needless_must_use(
},
);
} else if attr.value_str().is_none() && is_must_use_ty(cx, return_ty(cx, item_id)) {
+ // Ignore async functions unless Future::Output type is a must_use type
+ if sig.header.is_async() {
+ let infcx = cx.tcx.infer_ctxt().build();
+ if let Some(future_ty) = infcx.get_impl_future_output_ty(return_ty(cx, item_id))
+ && !is_must_use_ty(cx, future_ty) {
+ return;
+ }
+ }
+
span_lint_and_help(
cx,
DOUBLE_MUST_USE,
diff --git a/src/tools/clippy/clippy_lints/src/future_not_send.rs b/src/tools/clippy/clippy_lints/src/future_not_send.rs
index 9fb73a371..ed0bd58c7 100644
--- a/src/tools/clippy/clippy_lints/src/future_not_send.rs
+++ b/src/tools/clippy/clippy_lints/src/future_not_send.rs
@@ -9,7 +9,7 @@ use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::def_id::LocalDefId;
use rustc_span::{sym, Span};
use rustc_trait_selection::traits::error_reporting::suggestions::TypeErrCtxtExt;
-use rustc_trait_selection::traits::{self, FulfillmentError};
+use rustc_trait_selection::traits::{self, FulfillmentError, ObligationCtxt};
declare_clippy_lint! {
/// ### What it does
@@ -79,8 +79,10 @@ impl<'tcx> LateLintPass<'tcx> for FutureNotSend {
let send_trait = cx.tcx.get_diagnostic_item(sym::Send).unwrap();
let span = decl.output.span();
let infcx = cx.tcx.infer_ctxt().build();
+ let ocx = ObligationCtxt::new(&infcx);
let cause = traits::ObligationCause::misc(span, fn_def_id);
- let send_errors = traits::fully_solve_bound(&infcx, cause, cx.param_env, ret_ty, send_trait);
+ ocx.register_bound(cause, cx.param_env, ret_ty, send_trait);
+ let send_errors = ocx.select_all_or_error();
if !send_errors.is_empty() {
span_lint_and_then(
cx,
diff --git a/src/tools/clippy/clippy_lints/src/if_then_some_else_none.rs b/src/tools/clippy/clippy_lints/src/if_then_some_else_none.rs
index 9cadaaa49..725bd3d54 100644
--- a/src/tools/clippy/clippy_lints/src/if_then_some_else_none.rs
+++ b/src/tools/clippy/clippy_lints/src/if_then_some_else_none.rs
@@ -1,8 +1,10 @@
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::eager_or_lazy::switch_to_eager_eval;
use clippy_utils::msrvs::{self, Msrv};
-use clippy_utils::source::snippet_with_macro_callsite;
+use clippy_utils::source::snippet_with_context;
+use clippy_utils::sugg::Sugg;
use clippy_utils::{contains_return, higher, is_else_clause, is_res_lang_ctor, path_res, peel_blocks};
+use rustc_errors::Applicability;
use rustc_hir::LangItem::{OptionNone, OptionSome};
use rustc_hir::{Expr, ExprKind, Stmt, StmtKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
@@ -72,21 +74,20 @@ impl<'tcx> LateLintPass<'tcx> for IfThenSomeElseNone {
return;
}
+ let ctxt = expr.span.ctxt();
+
if let Some(higher::If { cond, then, r#else: Some(els) }) = higher::If::hir(expr)
&& let ExprKind::Block(then_block, _) = then.kind
&& let Some(then_expr) = then_block.expr
&& let ExprKind::Call(then_call, [then_arg]) = then_expr.kind
+ && then_expr.span.ctxt() == ctxt
&& is_res_lang_ctor(cx, path_res(cx, then_call), OptionSome)
&& is_res_lang_ctor(cx, path_res(cx, peel_blocks(els)), OptionNone)
&& !stmts_contains_early_return(then_block.stmts)
{
- let cond_snip = snippet_with_macro_callsite(cx, cond.span, "[condition]");
- let cond_snip = if matches!(cond.kind, ExprKind::Unary(_, _) | ExprKind::Binary(_, _, _)) {
- format!("({cond_snip})")
- } else {
- cond_snip.into_owned()
- };
- let arg_snip = snippet_with_macro_callsite(cx, then_arg.span, "");
+ let mut app = Applicability::Unspecified;
+ let cond_snip = Sugg::hir_with_context(cx, cond, expr.span.ctxt(), "[condition]", &mut app).maybe_par().to_string();
+ let arg_snip = snippet_with_context(cx, then_arg.span, ctxt, "[body]", &mut app).0;
let mut method_body = if then_block.stmts.is_empty() {
arg_snip.into_owned()
} else {
diff --git a/src/tools/clippy/clippy_lints/src/implicit_saturating_add.rs b/src/tools/clippy/clippy_lints/src/implicit_saturating_add.rs
index 6e1934393..57e6caa87 100644
--- a/src/tools/clippy/clippy_lints/src/implicit_saturating_add.rs
+++ b/src/tools/clippy/clippy_lints/src/implicit_saturating_add.rs
@@ -1,7 +1,7 @@
use clippy_utils::consts::{constant, Constant};
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::get_parent_expr;
-use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::source::snippet_with_context;
use if_chain::if_chain;
use rustc_ast::ast::{LitIntType, LitKind};
use rustc_errors::Applicability;
@@ -55,6 +55,9 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitSaturatingAdd {
if let ExprKind::AssignOp(op1, target, value) = ex.kind;
let ty = cx.typeck_results().expr_ty(target);
if Some(c) == get_int_max(ty);
+ let ctxt = expr.span.ctxt();
+ if ex.span.ctxt() == ctxt;
+ if expr1.span.ctxt() == ctxt;
if clippy_utils::SpanlessEq::new(cx).eq_expr(l, target);
if BinOpKind::Add == op1.node;
if let ExprKind::Lit(ref lit) = value.kind;
@@ -62,8 +65,15 @@ impl<'tcx> LateLintPass<'tcx> for ImplicitSaturatingAdd {
if block.expr.is_none();
then {
let mut app = Applicability::MachineApplicable;
- let code = snippet_with_applicability(cx, target.span, "_", &mut app);
- let sugg = if let Some(parent) = get_parent_expr(cx, expr) && let ExprKind::If(_cond, _then, Some(else_)) = parent.kind && else_.hir_id == expr.hir_id {format!("{{{code} = {code}.saturating_add(1); }}")} else {format!("{code} = {code}.saturating_add(1);")};
+ let code = snippet_with_context(cx, target.span, ctxt, "_", &mut app).0;
+ let sugg = if let Some(parent) = get_parent_expr(cx, expr)
+ && let ExprKind::If(_cond, _then, Some(else_)) = parent.kind
+ && else_.hir_id == expr.hir_id
+ {
+ format!("{{{code} = {code}.saturating_add(1); }}")
+ } else {
+ format!("{code} = {code}.saturating_add(1);")
+ };
span_lint_and_sugg(cx, IMPLICIT_SATURATING_ADD, expr.span, "manual saturating add detected", "use instead", sugg, app);
}
}
diff --git a/src/tools/clippy/clippy_lints/src/infinite_iter.rs b/src/tools/clippy/clippy_lints/src/infinite_iter.rs
index d1d2db27c..fe28c526b 100644
--- a/src/tools/clippy/clippy_lints/src/infinite_iter.rs
+++ b/src/tools/clippy/clippy_lints/src/infinite_iter.rs
@@ -167,7 +167,7 @@ fn is_infinite(cx: &LateContext<'_>, expr: &Expr<'_>) -> Finiteness {
Finite
},
ExprKind::Block(block, _) => block.expr.as_ref().map_or(Finite, |e| is_infinite(cx, e)),
- ExprKind::Box(e) | ExprKind::AddrOf(BorrowKind::Ref, _, e) => is_infinite(cx, e),
+ ExprKind::AddrOf(BorrowKind::Ref, _, e) => is_infinite(cx, e),
ExprKind::Call(path, _) => {
if let ExprKind::Path(ref qpath) = path.kind {
cx.qpath_res(qpath, path.hir_id)
diff --git a/src/tools/clippy/clippy_lints/src/instant_subtraction.rs b/src/tools/clippy/clippy_lints/src/instant_subtraction.rs
index 668110c7c..34e999158 100644
--- a/src/tools/clippy/clippy_lints/src/instant_subtraction.rs
+++ b/src/tools/clippy/clippy_lints/src/instant_subtraction.rs
@@ -1,6 +1,6 @@
use clippy_utils::diagnostics::{self, span_lint_and_sugg};
use clippy_utils::msrvs::{self, Msrv};
-use clippy_utils::source;
+use clippy_utils::source::snippet_with_context;
use clippy_utils::sugg::Sugg;
use clippy_utils::ty;
use rustc_errors::Applicability;
@@ -161,14 +161,9 @@ fn print_unchecked_duration_subtraction_sugg(
) {
let mut applicability = Applicability::MachineApplicable;
- let left_expr =
- source::snippet_with_applicability(cx, left_expr.span, "std::time::Instant::now()", &mut applicability);
- let right_expr = source::snippet_with_applicability(
- cx,
- right_expr.span,
- "std::time::Duration::from_secs(1)",
- &mut applicability,
- );
+ let ctxt = expr.span.ctxt();
+ let left_expr = snippet_with_context(cx, left_expr.span, ctxt, "<instant>", &mut applicability).0;
+ let right_expr = snippet_with_context(cx, right_expr.span, ctxt, "<duration>", &mut applicability).0;
diagnostics::span_lint_and_sugg(
cx,
diff --git a/src/tools/clippy/clippy_lints/src/items_after_statements.rs b/src/tools/clippy/clippy_lints/src/items_after_statements.rs
index 46d439b44..a7ec57e28 100644
--- a/src/tools/clippy/clippy_lints/src/items_after_statements.rs
+++ b/src/tools/clippy/clippy_lints/src/items_after_statements.rs
@@ -1,8 +1,8 @@
//! lint when items are used after statements
-use clippy_utils::diagnostics::span_lint;
-use rustc_ast::ast::{Block, ItemKind, StmtKind};
-use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
+use clippy_utils::diagnostics::span_lint_hir;
+use rustc_hir::{Block, ItemKind, StmtKind};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_session::{declare_lint_pass, declare_tool_lint};
@@ -52,33 +52,34 @@ declare_clippy_lint! {
declare_lint_pass!(ItemsAfterStatements => [ITEMS_AFTER_STATEMENTS]);
-impl EarlyLintPass for ItemsAfterStatements {
- fn check_block(&mut self, cx: &EarlyContext<'_>, item: &Block) {
- if in_external_macro(cx.sess(), item.span) {
+impl LateLintPass<'_> for ItemsAfterStatements {
+ fn check_block(&mut self, cx: &LateContext<'_>, block: &Block<'_>) {
+ if in_external_macro(cx.sess(), block.span) {
return;
}
- // skip initial items and trailing semicolons
- let stmts = item
+ // skip initial items
+ let stmts = block
.stmts
.iter()
- .map(|stmt| &stmt.kind)
- .skip_while(|s| matches!(**s, StmtKind::Item(..) | StmtKind::Empty));
+ .skip_while(|stmt| matches!(stmt.kind, StmtKind::Item(..)));
// lint on all further items
for stmt in stmts {
- if let StmtKind::Item(ref it) = *stmt {
- if in_external_macro(cx.sess(), it.span) {
+ if let StmtKind::Item(item_id) = stmt.kind {
+ let item = cx.tcx.hir().item(item_id);
+ if in_external_macro(cx.sess(), item.span) || !item.span.eq_ctxt(block.span) {
return;
}
- if let ItemKind::MacroDef(..) = it.kind {
+ if let ItemKind::Macro(..) = item.kind {
// do not lint `macro_rules`, but continue processing further statements
continue;
}
- span_lint(
+ span_lint_hir(
cx,
ITEMS_AFTER_STATEMENTS,
- it.span,
+ item.hir_id(),
+ item.span,
"adding items after statements is confusing, since items exist from the \
start of the scope",
);
diff --git a/src/tools/clippy/clippy_lints/src/large_futures.rs b/src/tools/clippy/clippy_lints/src/large_futures.rs
new file mode 100644
index 000000000..1b0544813
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/large_futures.rs
@@ -0,0 +1,87 @@
+use clippy_utils::source::snippet;
+use clippy_utils::{diagnostics::span_lint_and_sugg, ty::implements_trait};
+use rustc_errors::Applicability;
+use rustc_hir::{Expr, ExprKind, LangItem, MatchSource, QPath};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_target::abi::Size;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// It checks for the size of a `Future` created by `async fn` or `async {}`.
+ ///
+ /// ### Why is this bad?
+ /// Due to the current [unideal implemention](https://github.com/rust-lang/rust/issues/69826) of `Generator`,
+ /// large size of a `Future` may cause stack overflows.
+ ///
+ /// ### Example
+ /// ```rust
+ /// async fn wait(f: impl std::future::Future<Output = ()>) {}
+ ///
+ /// async fn big_fut(arg: [u8; 1024]) {}
+ ///
+ /// pub async fn test() {
+ /// let fut = big_fut([0u8; 1024]);
+ /// wait(fut).await;
+ /// }
+ /// ```
+ ///
+ /// `Box::pin` the big future instead.
+ ///
+ /// ```rust
+ /// async fn wait(f: impl std::future::Future<Output = ()>) {}
+ ///
+ /// async fn big_fut(arg: [u8; 1024]) {}
+ ///
+ /// pub async fn test() {
+ /// let fut = Box::pin(big_fut([0u8; 1024]));
+ /// wait(fut).await;
+ /// }
+ /// ```
+ #[clippy::version = "1.68.0"]
+ pub LARGE_FUTURES,
+ pedantic,
+ "large future may lead to unexpected stack overflows"
+}
+
+#[derive(Copy, Clone)]
+pub struct LargeFuture {
+ future_size_threshold: u64,
+}
+
+impl LargeFuture {
+ pub fn new(future_size_threshold: u64) -> Self {
+ Self { future_size_threshold }
+ }
+}
+
+impl_lint_pass!(LargeFuture => [LARGE_FUTURES]);
+
+impl<'tcx> LateLintPass<'tcx> for LargeFuture {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
+ if matches!(expr.span.ctxt().outer_expn_data().kind, rustc_span::ExpnKind::Macro(..)) {
+ return;
+ }
+ if let ExprKind::Match(expr, _, MatchSource::AwaitDesugar) = expr.kind {
+ if let ExprKind::Call(func, [expr, ..]) = expr.kind
+ && let ExprKind::Path(QPath::LangItem(LangItem::IntoFutureIntoFuture, ..)) = func.kind
+ && let ty = cx.typeck_results().expr_ty(expr)
+ && let Some(future_trait_def_id) = cx.tcx.lang_items().future_trait()
+ && implements_trait(cx, ty, future_trait_def_id, &[])
+ && let Ok(layout) = cx.tcx.layout_of(cx.param_env.and(ty))
+ && let size = layout.layout.size()
+ && size >= Size::from_bytes(self.future_size_threshold)
+ {
+ span_lint_and_sugg(
+ cx,
+ LARGE_FUTURES,
+ expr.span,
+ &format!("large future with a size of {} bytes", size.bytes()),
+ "consider `Box::pin` on it",
+ format!("Box::pin({})", snippet(cx, expr.span, "..")),
+ Applicability::Unspecified,
+ );
+ }
+ }
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/len_zero.rs b/src/tools/clippy/clippy_lints/src/len_zero.rs
index e13bc4797..0805b4b19 100644
--- a/src/tools/clippy/clippy_lints/src/len_zero.rs
+++ b/src/tools/clippy/clippy_lints/src/len_zero.rs
@@ -1,13 +1,14 @@
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_and_then};
-use clippy_utils::source::snippet_with_applicability;
-use clippy_utils::{get_item_name, get_parent_as_impl, is_lint_allowed, peel_ref_operators};
+use clippy_utils::source::snippet_with_context;
+use clippy_utils::{get_item_name, get_parent_as_impl, is_lint_allowed, peel_ref_operators, sugg::Sugg};
use if_chain::if_chain;
use rustc_ast::ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::def_id::DefIdSet;
use rustc_hir::{
- def_id::DefId, AssocItemKind, BinOpKind, Expr, ExprKind, FnRetTy, ImplItem, ImplItemKind, ImplicitSelfKind, Item,
- ItemKind, Mutability, Node, TraitItemRef, TyKind, UnOp,
+ def::Res, def_id::DefId, lang_items::LangItem, AssocItemKind, BinOpKind, Expr, ExprKind, FnRetTy, GenericArg,
+ GenericBound, ImplItem, ImplItemKind, ImplicitSelfKind, Item, ItemKind, Mutability, Node, PathSegment, PrimTy,
+ QPath, TraitItemRef, TyKind, TypeBindingKind,
};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{self, AssocKind, FnSig, Ty};
@@ -16,7 +17,6 @@ use rustc_span::{
source_map::{Span, Spanned, Symbol},
symbol::sym,
};
-use std::borrow::Cow;
declare_clippy_lint! {
/// ### What it does
@@ -251,33 +251,98 @@ fn check_trait_items(cx: &LateContext<'_>, visited_trait: &Item<'_>, trait_items
}
#[derive(Debug, Clone, Copy)]
-enum LenOutput<'tcx> {
+enum LenOutput {
Integral,
Option(DefId),
- Result(DefId, Ty<'tcx>),
+ Result(DefId),
}
-fn parse_len_output<'tcx>(cx: &LateContext<'_>, sig: FnSig<'tcx>) -> Option<LenOutput<'tcx>> {
+
+fn extract_future_output<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<&'tcx PathSegment<'tcx>> {
+ if let ty::Alias(_, alias_ty) = ty.kind() &&
+ let Some(Node::Item(item)) = cx.tcx.hir().get_if_local(alias_ty.def_id) &&
+ let Item { kind: ItemKind::OpaqueTy(opaque), .. } = item &&
+ opaque.bounds.len() == 1 &&
+ let GenericBound::LangItemTrait(LangItem::Future, _, _, generic_args) = &opaque.bounds[0] &&
+ generic_args.bindings.len() == 1 &&
+ let TypeBindingKind::Equality {
+ term: rustc_hir::Term::Ty(rustc_hir::Ty {kind: TyKind::Path(QPath::Resolved(_, path)), .. }),
+ } = &generic_args.bindings[0].kind &&
+ path.segments.len() == 1 {
+ return Some(&path.segments[0]);
+ }
+
+ None
+}
+
+fn is_first_generic_integral<'tcx>(segment: &'tcx PathSegment<'tcx>) -> bool {
+ if let Some(generic_args) = segment.args {
+ if generic_args.args.is_empty() {
+ return false;
+ }
+ let arg = &generic_args.args[0];
+ if let GenericArg::Type(rustc_hir::Ty {
+ kind: TyKind::Path(QPath::Resolved(_, path)),
+ ..
+ }) = arg
+ {
+ let segments = &path.segments;
+ let segment = &segments[0];
+ let res = &segment.res;
+ if matches!(res, Res::PrimTy(PrimTy::Uint(_))) || matches!(res, Res::PrimTy(PrimTy::Int(_))) {
+ return true;
+ }
+ }
+ }
+
+ false
+}
+
+fn parse_len_output<'tcx>(cx: &LateContext<'tcx>, sig: FnSig<'tcx>) -> Option<LenOutput> {
+ if let Some(segment) = extract_future_output(cx, sig.output()) {
+ let res = segment.res;
+
+ if matches!(res, Res::PrimTy(PrimTy::Uint(_))) || matches!(res, Res::PrimTy(PrimTy::Int(_))) {
+ return Some(LenOutput::Integral);
+ }
+
+ if let Res::Def(_, def_id) = res {
+ if cx.tcx.is_diagnostic_item(sym::Option, def_id) && is_first_generic_integral(segment) {
+ return Some(LenOutput::Option(def_id));
+ } else if cx.tcx.is_diagnostic_item(sym::Result, def_id) && is_first_generic_integral(segment) {
+ return Some(LenOutput::Result(def_id));
+ }
+ }
+
+ return None;
+ }
+
match *sig.output().kind() {
ty::Int(_) | ty::Uint(_) => Some(LenOutput::Integral),
ty::Adt(adt, subs) if cx.tcx.is_diagnostic_item(sym::Option, adt.did()) => {
subs.type_at(0).is_integral().then(|| LenOutput::Option(adt.did()))
},
- ty::Adt(adt, subs) if cx.tcx.is_diagnostic_item(sym::Result, adt.did()) => subs
- .type_at(0)
- .is_integral()
- .then(|| LenOutput::Result(adt.did(), subs.type_at(1))),
+ ty::Adt(adt, subs) if cx.tcx.is_diagnostic_item(sym::Result, adt.did()) => {
+ subs.type_at(0).is_integral().then(|| LenOutput::Result(adt.did()))
+ },
_ => None,
}
}
-impl<'tcx> LenOutput<'tcx> {
- fn matches_is_empty_output(self, ty: Ty<'tcx>) -> bool {
+impl LenOutput {
+ fn matches_is_empty_output<'tcx>(self, cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
+ if let Some(segment) = extract_future_output(cx, ty) {
+ return match (self, segment.res) {
+ (_, Res::PrimTy(PrimTy::Bool)) => true,
+ (Self::Option(_), Res::Def(_, def_id)) if cx.tcx.is_diagnostic_item(sym::Option, def_id) => true,
+ (Self::Result(_), Res::Def(_, def_id)) if cx.tcx.is_diagnostic_item(sym::Result, def_id) => true,
+ _ => false,
+ };
+ }
+
match (self, ty.kind()) {
(_, &ty::Bool) => true,
(Self::Option(id), &ty::Adt(adt, subs)) if id == adt.did() => subs.type_at(0).is_bool(),
- (Self::Result(id, err_ty), &ty::Adt(adt, subs)) if id == adt.did() => {
- subs.type_at(0).is_bool() && subs.type_at(1) == err_ty
- },
+ (Self::Result(id), &ty::Adt(adt, subs)) if id == adt.did() => subs.type_at(0).is_bool(),
_ => false,
}
}
@@ -301,9 +366,14 @@ impl<'tcx> LenOutput<'tcx> {
}
/// Checks if the given signature matches the expectations for `is_empty`
-fn check_is_empty_sig<'tcx>(sig: FnSig<'tcx>, self_kind: ImplicitSelfKind, len_output: LenOutput<'tcx>) -> bool {
+fn check_is_empty_sig<'tcx>(
+ cx: &LateContext<'tcx>,
+ sig: FnSig<'tcx>,
+ self_kind: ImplicitSelfKind,
+ len_output: LenOutput,
+) -> bool {
match &**sig.inputs_and_output {
- [arg, res] if len_output.matches_is_empty_output(*res) => {
+ [arg, res] if len_output.matches_is_empty_output(cx, *res) => {
matches!(
(arg.kind(), self_kind),
(ty::Ref(_, _, Mutability::Not), ImplicitSelfKind::ImmRef)
@@ -315,11 +385,11 @@ fn check_is_empty_sig<'tcx>(sig: FnSig<'tcx>, self_kind: ImplicitSelfKind, len_o
}
/// Checks if the given type has an `is_empty` method with the appropriate signature.
-fn check_for_is_empty<'tcx>(
- cx: &LateContext<'tcx>,
+fn check_for_is_empty(
+ cx: &LateContext<'_>,
span: Span,
self_kind: ImplicitSelfKind,
- output: LenOutput<'tcx>,
+ output: LenOutput,
impl_ty: DefId,
item_name: Symbol,
item_kind: &str,
@@ -352,6 +422,7 @@ fn check_for_is_empty<'tcx>(
Some(is_empty)
if !(is_empty.fn_has_self_parameter
&& check_is_empty_sig(
+ cx,
cx.tcx.fn_sig(is_empty.def_id).subst_identity().skip_binder(),
self_kind,
output,
@@ -431,7 +502,7 @@ fn check_len(
&format!("using `{op}is_empty` is clearer and more explicit"),
format!(
"{op}{}.is_empty()",
- snippet_with_applicability(cx, receiver.span, "_", &mut applicability)
+ snippet_with_context(cx, receiver.span, span.ctxt(), "_", &mut applicability).0,
),
applicability,
);
@@ -444,13 +515,7 @@ fn check_empty_expr(cx: &LateContext<'_>, span: Span, lit1: &Expr<'_>, lit2: &Ex
let mut applicability = Applicability::MachineApplicable;
let lit1 = peel_ref_operators(cx, lit1);
- let mut lit_str = snippet_with_applicability(cx, lit1.span, "_", &mut applicability);
-
- // Wrap the expression in parentheses if it's a deref expression. Otherwise operator precedence will
- // cause the code to dereference boolean(won't compile).
- if let ExprKind::Unary(UnOp::Deref, _) = lit1.kind {
- lit_str = Cow::from(format!("({lit_str})"));
- }
+ let lit_str = Sugg::hir_with_context(cx, lit1, span.ctxt(), "_", &mut applicability).maybe_par();
span_lint_and_sugg(
cx,
diff --git a/src/tools/clippy/clippy_lints/src/let_underscore.rs b/src/tools/clippy/clippy_lints/src/let_underscore.rs
index 7600777fa..51b5de27d 100644
--- a/src/tools/clippy/clippy_lints/src/let_underscore.rs
+++ b/src/tools/clippy/clippy_lints/src/let_underscore.rs
@@ -124,7 +124,7 @@ declare_clippy_lint! {
/// ```
#[clippy::version = "1.69.0"]
pub LET_UNDERSCORE_UNTYPED,
- pedantic,
+ restriction,
"non-binding `let` without a type annotation"
}
diff --git a/src/tools/clippy/clippy_lints/src/let_with_type_underscore.rs b/src/tools/clippy/clippy_lints/src/let_with_type_underscore.rs
new file mode 100644
index 000000000..c01e3882d
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/let_with_type_underscore.rs
@@ -0,0 +1,45 @@
+use clippy_utils::diagnostics::span_lint_and_help;
+use rustc_hir::{Local, TyKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::lint::in_external_macro;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Detects when a variable is declared with an explicit type of `_`.
+ /// ### Why is this bad?
+ /// It adds noise, `: _` provides zero clarity or utility.
+ /// ### Example
+ /// ```rust,ignore
+ /// let my_number: _ = 1;
+ /// ```
+ /// Use instead:
+ /// ```rust,ignore
+ /// let my_number = 1;
+ /// ```
+ #[clippy::version = "1.69.0"]
+ pub LET_WITH_TYPE_UNDERSCORE,
+ complexity,
+ "unneeded underscore type (`_`) in a variable declaration"
+}
+declare_lint_pass!(UnderscoreTyped => [LET_WITH_TYPE_UNDERSCORE]);
+
+impl LateLintPass<'_> for UnderscoreTyped {
+ fn check_local<'tcx>(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'tcx>) {
+ if_chain! {
+ if !in_external_macro(cx.tcx.sess, local.span);
+ if let Some(ty) = local.ty; // Ensure that it has a type defined
+ if let TyKind::Infer = &ty.kind; // that type is '_'
+ if local.span.ctxt() == ty.span.ctxt();
+ then {
+ span_lint_and_help(cx,
+ LET_WITH_TYPE_UNDERSCORE,
+ local.span,
+ "variable declared with type underscore",
+ Some(ty.span.with_lo(local.pat.span.hi())),
+ "remove the explicit type `_` declaration"
+ )
+ }
+ };
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/lib.rs b/src/tools/clippy/clippy_lints/src/lib.rs
index c626e0bd9..b0ec14855 100644
--- a/src/tools/clippy/clippy_lints/src/lib.rs
+++ b/src/tools/clippy/clippy_lints/src/lib.rs
@@ -1,13 +1,11 @@
#![feature(array_windows)]
#![feature(binary_heap_into_iter_sorted)]
#![feature(box_patterns)]
-#![feature(drain_filter)]
#![feature(if_let_guard)]
#![feature(iter_intersperse)]
#![feature(let_chains)]
#![feature(lint_reasons)]
#![feature(never_type)]
-#![feature(once_cell)]
#![feature(rustc_private)]
#![feature(stmt_expr_attributes)]
#![recursion_limit = "512"]
@@ -67,6 +65,7 @@ mod declared_lints;
mod renamed_lints;
// begin lints modules, do not remove this comment, it’s used in `update_lints`
+mod allow_attributes;
mod almost_complete_range;
mod approx_const;
mod as_conversions;
@@ -87,6 +86,7 @@ mod casts;
mod checked_conversions;
mod cognitive_complexity;
mod collapsible_if;
+mod collection_is_never_read;
mod comparison_chain;
mod copies;
mod copy_iterator;
@@ -161,12 +161,15 @@ mod items_after_statements;
mod iter_not_returning_iterator;
mod large_const_arrays;
mod large_enum_variant;
+mod large_futures;
mod large_include_file;
mod large_stack_arrays;
mod len_zero;
mod let_if_seq;
mod let_underscore;
+mod let_with_type_underscore;
mod lifetimes;
+mod lines_filter_map_ok;
mod literal_representation;
mod loops;
mod macro_use;
@@ -177,9 +180,11 @@ mod manual_bits;
mod manual_clamp;
mod manual_is_ascii_check;
mod manual_let_else;
+mod manual_main_separator_str;
mod manual_non_exhaustive;
mod manual_rem_euclid;
mod manual_retain;
+mod manual_slice_size_calculation;
mod manual_string_new;
mod manual_strip;
mod map_unit_fn;
@@ -192,6 +197,7 @@ mod minmax;
mod misc;
mod misc_early;
mod mismatching_type_param_order;
+mod missing_assert_message;
mod missing_const_for_fn;
mod missing_doc;
mod missing_enforced_import_rename;
@@ -249,6 +255,7 @@ mod question_mark_used;
mod ranges;
mod rc_clone_in_vec_init;
mod read_zero_byte_vec;
+mod redundant_async_block;
mod redundant_clone;
mod redundant_closure_call;
mod redundant_else;
@@ -276,6 +283,7 @@ mod slow_vector_initialization;
mod std_instead_of_core;
mod strings;
mod strlen_on_c_strings;
+mod suspicious_doc_comments;
mod suspicious_operation_groupings;
mod suspicious_trait_impl;
mod suspicious_xor_used_as_pow;
@@ -283,6 +291,7 @@ mod swap;
mod swap_ptr_to_ref;
mod tabs_in_doc_comments;
mod temporary_assignment;
+mod tests_outside_test_module;
mod to_digit_is_some;
mod trailing_empty_array;
mod trait_bounds;
@@ -294,8 +303,10 @@ mod uninit_vec;
mod unit_return_expecting_ord;
mod unit_types;
mod unnamed_address;
+mod unnecessary_box_returns;
mod unnecessary_owned_empty_strings;
mod unnecessary_self_imports;
+mod unnecessary_struct_initialization;
mod unnecessary_wraps;
mod unnested_or_patterns;
mod unsafe_removed_from_name;
@@ -338,13 +349,17 @@ pub fn register_pre_expansion_lints(store: &mut rustc_lint::LintStore, sess: &Se
}
#[doc(hidden)]
-pub fn read_conf(sess: &Session, path: &io::Result<Option<PathBuf>>) -> Conf {
+pub fn read_conf(sess: &Session, path: &io::Result<(Option<PathBuf>, Vec<String>)>) -> Conf {
+ if let Ok((_, warnings)) = path {
+ for warning in warnings {
+ sess.warn(warning);
+ }
+ }
let file_name = match path {
- Ok(Some(path)) => path,
- Ok(None) => return Conf::default(),
+ Ok((Some(path), _)) => path,
+ Ok((None, _)) => return Conf::default(),
Err(error) => {
- sess.struct_err(format!("error finding Clippy's configuration file: {error}"))
- .emit();
+ sess.err(format!("error finding Clippy's configuration file: {error}"));
return Conf::default();
},
};
@@ -533,6 +548,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
.collect(),
))
});
+ store.register_early_pass(|| Box::new(utils::format_args_collector::FormatArgsCollector));
store.register_late_pass(|_| Box::new(utils::dump_hir::DumpHir));
store.register_late_pass(|_| Box::new(utils::author::Author));
let await_holding_invalid_types = conf.await_holding_invalid_types.clone();
@@ -651,7 +667,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|_| Box::new(empty_enum::EmptyEnum));
store.register_late_pass(|_| Box::new(invalid_upcast_comparisons::InvalidUpcastComparisons));
store.register_late_pass(|_| Box::new(regex::Regex));
- store.register_late_pass(|_| Box::new(copies::CopyAndPaste));
+ let ignore_interior_mutability = conf.ignore_interior_mutability.clone();
+ store.register_late_pass(move |_| Box::new(copies::CopyAndPaste::new(ignore_interior_mutability.clone())));
store.register_late_pass(|_| Box::new(copy_iterator::CopyIterator));
store.register_late_pass(|_| Box::new(format::UselessFormat));
store.register_late_pass(|_| Box::new(swap::Swap));
@@ -738,7 +755,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_early_pass(|| Box::new(unused_unit::UnusedUnit));
store.register_late_pass(|_| Box::new(returns::Return));
store.register_early_pass(|| Box::new(collapsible_if::CollapsibleIf));
- store.register_early_pass(|| Box::new(items_after_statements::ItemsAfterStatements));
+ store.register_late_pass(|_| Box::new(items_after_statements::ItemsAfterStatements));
store.register_early_pass(|| Box::new(precedence::Precedence));
store.register_late_pass(|_| Box::new(needless_parens_on_range_literals::NeedlessParensOnRangeLiterals));
store.register_early_pass(|| Box::new(needless_continue::NeedlessContinue));
@@ -800,6 +817,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(move |_| Box::new(dereference::Dereferencing::new(msrv())));
store.register_late_pass(|_| Box::new(option_if_let_else::OptionIfLetElse));
store.register_late_pass(|_| Box::new(future_not_send::FutureNotSend));
+ let future_size_threshold = conf.future_size_threshold;
+ store.register_late_pass(move |_| Box::new(large_futures::LargeFuture::new(future_size_threshold)));
store.register_late_pass(|_| Box::new(if_let_mutex::IfLetMutex));
store.register_late_pass(|_| Box::new(if_not_else::IfNotElse));
store.register_late_pass(|_| Box::new(equatable_if_let::PatternEquality));
@@ -924,6 +943,22 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
))
});
store.register_late_pass(|_| Box::new(no_mangle_with_rust_abi::NoMangleWithRustAbi));
+ store.register_late_pass(|_| Box::new(collection_is_never_read::CollectionIsNeverRead));
+ store.register_late_pass(|_| Box::new(missing_assert_message::MissingAssertMessage));
+ store.register_late_pass(|_| Box::new(redundant_async_block::RedundantAsyncBlock));
+ store.register_late_pass(|_| Box::new(let_with_type_underscore::UnderscoreTyped));
+ store.register_late_pass(|_| Box::new(allow_attributes::AllowAttribute));
+ store.register_late_pass(move |_| Box::new(manual_main_separator_str::ManualMainSeparatorStr::new(msrv())));
+ store.register_late_pass(|_| Box::new(unnecessary_struct_initialization::UnnecessaryStruct));
+ store.register_late_pass(move |_| {
+ Box::new(unnecessary_box_returns::UnnecessaryBoxReturns::new(
+ avoid_breaking_exported_api,
+ ))
+ });
+ store.register_late_pass(|_| Box::new(lines_filter_map_ok::LinesFilterMapOk));
+ store.register_late_pass(|_| Box::new(tests_outside_test_module::TestsOutsideTestModule));
+ store.register_late_pass(|_| Box::new(manual_slice_size_calculation::ManualSliceSizeCalculation));
+ store.register_early_pass(|| Box::new(suspicious_doc_comments::SuspiciousDocComments));
// add lints here, do not remove this comment, it's used in `new_lint`
}
diff --git a/src/tools/clippy/clippy_lints/src/lines_filter_map_ok.rs b/src/tools/clippy/clippy_lints/src/lines_filter_map_ok.rs
new file mode 100644
index 000000000..b0f927647
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/lines_filter_map_ok.rs
@@ -0,0 +1,100 @@
+use clippy_utils::{
+ diagnostics::span_lint_and_then, is_diag_item_method, is_trait_method, match_def_path, path_to_local_id, paths,
+ ty::match_type,
+};
+use rustc_errors::Applicability;
+use rustc_hir::{Body, Closure, Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Detect uses of `lines.filter_map(Result::ok)` or `lines.flat_map(Result::ok)`
+ /// when `lines` has type `std::io::Lines`.
+ ///
+ /// ### Why is this bad?
+ /// `Lines` instances might produce a never-ending stream of `Err`, in which case
+ /// `filter_map(Result::ok)` will enter an infinite loop while waiting for an
+ /// `Ok` variant. Calling `next()` once is sufficient to enter the infinite loop,
+ /// even in the absence of explicit loops in the user code.
+ ///
+ /// This situation can arise when working with user-provided paths. On some platforms,
+ /// `std::fs::File::open(path)` might return `Ok(fs)` even when `path` is a directory,
+ /// but any later attempt to read from `fs` will return an error.
+ ///
+ /// ### Known problems
+ /// This lint suggests replacing `filter_map()` or `flat_map()` applied to a `Lines`
+ /// instance in all cases. There two cases where the suggestion might not be
+ /// appropriate or necessary:
+ ///
+ /// - If the `Lines` instance can never produce any error, or if an error is produced
+ /// only once just before terminating the iterator, using `map_while()` is not
+ /// necessary but will not do any harm.
+ /// - If the `Lines` instance can produce intermittent errors then recover and produce
+ /// successful results, using `map_while()` would stop at the first error.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # use std::{fs::File, io::{self, BufRead, BufReader}};
+ /// # let _ = || -> io::Result<()> {
+ /// let mut lines = BufReader::new(File::open("some-path")?).lines().filter_map(Result::ok);
+ /// // If "some-path" points to a directory, the next statement never terminates:
+ /// let first_line: Option<String> = lines.next();
+ /// # Ok(()) };
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// # use std::{fs::File, io::{self, BufRead, BufReader}};
+ /// # let _ = || -> io::Result<()> {
+ /// let mut lines = BufReader::new(File::open("some-path")?).lines().map_while(Result::ok);
+ /// let first_line: Option<String> = lines.next();
+ /// # Ok(()) };
+ /// ```
+ #[clippy::version = "1.70.0"]
+ pub LINES_FILTER_MAP_OK,
+ suspicious,
+ "filtering `std::io::Lines` with `filter_map()` or `flat_map()` might cause an infinite loop"
+}
+declare_lint_pass!(LinesFilterMapOk => [LINES_FILTER_MAP_OK]);
+
+impl LateLintPass<'_> for LinesFilterMapOk {
+ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
+ if let ExprKind::MethodCall(fm_method, fm_receiver, [fm_arg], fm_span) = expr.kind &&
+ is_trait_method(cx, expr, sym::Iterator) &&
+ (fm_method.ident.as_str() == "filter_map" || fm_method.ident.as_str() == "flat_map") &&
+ match_type(cx, cx.typeck_results().expr_ty_adjusted(fm_receiver), &paths::STD_IO_LINES)
+ {
+ let lint = match &fm_arg.kind {
+ // Detect `Result::ok`
+ ExprKind::Path(qpath) =>
+ cx.qpath_res(qpath, fm_arg.hir_id).opt_def_id().map(|did|
+ match_def_path(cx, did, &paths::CORE_RESULT_OK_METHOD)).unwrap_or_default(),
+ // Detect `|x| x.ok()`
+ ExprKind::Closure(Closure { body, .. }) =>
+ if let Body { params: [param], value, .. } = cx.tcx.hir().body(*body) &&
+ let ExprKind::MethodCall(method, receiver, [], _) = value.kind &&
+ path_to_local_id(receiver, param.pat.hir_id) &&
+ let Some(method_did) = cx.typeck_results().type_dependent_def_id(value.hir_id)
+ {
+ is_diag_item_method(cx, method_did, sym::Result) && method.ident.as_str() == "ok"
+ } else {
+ false
+ }
+ _ => false,
+ };
+ if lint {
+ span_lint_and_then(cx,
+ LINES_FILTER_MAP_OK,
+ fm_span,
+ &format!("`{}()` will run forever if the iterator repeatedly produces an `Err`", fm_method.ident),
+ |diag| {
+ diag.span_note(
+ fm_receiver.span,
+ "this expression returning a `std::io::Lines` may produce an infinite number of `Err` in case of a read error");
+ diag.span_suggestion(fm_span, "replace with", "map_while(Result::ok)", Applicability::MaybeIncorrect);
+ });
+ }
+ }
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/loops/manual_flatten.rs b/src/tools/clippy/clippy_lints/src/loops/manual_flatten.rs
index 8c27c0940..1e02a30e3 100644
--- a/src/tools/clippy/clippy_lints/src/loops/manual_flatten.rs
+++ b/src/tools/clippy/clippy_lints/src/loops/manual_flatten.rs
@@ -9,7 +9,7 @@ use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::{Expr, Pat, PatKind};
use rustc_lint::LateContext;
-use rustc_middle::ty::{self, DefIdTree};
+use rustc_middle::ty;
use rustc_span::source_map::Span;
/// Check for unnecessary `if let` usage in a for loop where only the `Some` or `Ok` variant of the
diff --git a/src/tools/clippy/clippy_lints/src/loops/never_loop.rs b/src/tools/clippy/clippy_lints/src/loops/never_loop.rs
index b1bc10802..f0a1b1dfe 100644
--- a/src/tools/clippy/clippy_lints/src/loops/never_loop.rs
+++ b/src/tools/clippy/clippy_lints/src/loops/never_loop.rs
@@ -124,8 +124,7 @@ fn stmt_to_expr<'tcx>(stmt: &Stmt<'tcx>) -> Option<(&'tcx Expr<'tcx>, Option<&'t
#[allow(clippy::too_many_lines)]
fn never_loop_expr(expr: &Expr<'_>, ignore_ids: &mut Vec<HirId>, main_loop_id: HirId) -> NeverLoopResult {
match expr.kind {
- ExprKind::Box(e)
- | ExprKind::Unary(_, e)
+ ExprKind::Unary(_, e)
| ExprKind::Cast(e, _)
| ExprKind::Type(e, _)
| ExprKind::Field(e, _)
diff --git a/src/tools/clippy/clippy_lints/src/loops/same_item_push.rs b/src/tools/clippy/clippy_lints/src/loops/same_item_push.rs
index 540656a2c..9d9341559 100644
--- a/src/tools/clippy/clippy_lints/src/loops/same_item_push.rs
+++ b/src/tools/clippy/clippy_lints/src/loops/same_item_push.rs
@@ -1,15 +1,17 @@
use super::SAME_ITEM_PUSH;
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::path_to_local;
-use clippy_utils::source::snippet_with_macro_callsite;
+use clippy_utils::source::snippet_with_context;
use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
use if_chain::if_chain;
use rustc_data_structures::fx::FxHashSet;
+use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::intravisit::{walk_expr, Visitor};
use rustc_hir::{BindingAnnotation, Block, Expr, ExprKind, HirId, Mutability, Node, Pat, PatKind, Stmt, StmtKind};
use rustc_lint::LateContext;
use rustc_span::symbol::sym;
+use rustc_span::SyntaxContext;
use std::iter::Iterator;
/// Detects for loop pushing the same item into a Vec
@@ -20,9 +22,10 @@ pub(super) fn check<'tcx>(
body: &'tcx Expr<'_>,
_: &'tcx Expr<'_>,
) {
- fn emit_lint(cx: &LateContext<'_>, vec: &Expr<'_>, pushed_item: &Expr<'_>) {
- let vec_str = snippet_with_macro_callsite(cx, vec.span, "");
- let item_str = snippet_with_macro_callsite(cx, pushed_item.span, "");
+ fn emit_lint(cx: &LateContext<'_>, vec: &Expr<'_>, pushed_item: &Expr<'_>, ctxt: SyntaxContext) {
+ let mut app = Applicability::Unspecified;
+ let vec_str = snippet_with_context(cx, vec.span, ctxt, "", &mut app).0;
+ let item_str = snippet_with_context(cx, pushed_item.span, ctxt, "", &mut app).0;
span_lint_and_help(
cx,
@@ -43,7 +46,7 @@ pub(super) fn check<'tcx>(
walk_expr(&mut same_item_push_visitor, body);
if_chain! {
if same_item_push_visitor.should_lint();
- if let Some((vec, pushed_item)) = same_item_push_visitor.vec_push;
+ if let Some((vec, pushed_item, ctxt)) = same_item_push_visitor.vec_push;
let vec_ty = cx.typeck_results().expr_ty(vec);
let ty = vec_ty.walk().nth(1).unwrap().expect_ty();
if cx
@@ -69,11 +72,11 @@ pub(super) fn check<'tcx>(
then {
match init.kind {
// immutable bindings that are initialized with literal
- ExprKind::Lit(..) => emit_lint(cx, vec, pushed_item),
+ ExprKind::Lit(..) => emit_lint(cx, vec, pushed_item, ctxt),
// immutable bindings that are initialized with constant
ExprKind::Path(ref path) => {
if let Res::Def(DefKind::Const, ..) = cx.qpath_res(path, init.hir_id) {
- emit_lint(cx, vec, pushed_item);
+ emit_lint(cx, vec, pushed_item, ctxt);
}
}
_ => {},
@@ -82,11 +85,11 @@ pub(super) fn check<'tcx>(
}
},
// constant
- Res::Def(DefKind::Const, ..) => emit_lint(cx, vec, pushed_item),
+ Res::Def(DefKind::Const, ..) => emit_lint(cx, vec, pushed_item, ctxt),
_ => {},
}
},
- ExprKind::Lit(..) => emit_lint(cx, vec, pushed_item),
+ ExprKind::Lit(..) => emit_lint(cx, vec, pushed_item, ctxt),
_ => {},
}
}
@@ -98,7 +101,7 @@ struct SameItemPushVisitor<'a, 'tcx> {
non_deterministic_expr: bool,
multiple_pushes: bool,
// this field holds the last vec push operation visited, which should be the only push seen
- vec_push: Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>)>,
+ vec_push: Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>, SyntaxContext)>,
cx: &'a LateContext<'tcx>,
used_locals: FxHashSet<HirId>,
}
@@ -118,7 +121,7 @@ impl<'a, 'tcx> SameItemPushVisitor<'a, 'tcx> {
if_chain! {
if !self.non_deterministic_expr;
if !self.multiple_pushes;
- if let Some((vec, _)) = self.vec_push;
+ if let Some((vec, _, _)) = self.vec_push;
if let Some(hir_id) = path_to_local(vec);
then {
!self.used_locals.contains(&hir_id)
@@ -173,7 +176,10 @@ impl<'a, 'tcx> Visitor<'tcx> for SameItemPushVisitor<'a, 'tcx> {
// Given some statement, determine if that statement is a push on a Vec. If it is, return
// the Vec being pushed into and the item being pushed
-fn get_vec_push<'tcx>(cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) -> Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>)> {
+fn get_vec_push<'tcx>(
+ cx: &LateContext<'tcx>,
+ stmt: &'tcx Stmt<'_>,
+) -> Option<(&'tcx Expr<'tcx>, &'tcx Expr<'tcx>, SyntaxContext)> {
if_chain! {
// Extract method being called
if let StmtKind::Semi(semi_stmt) = &stmt.kind;
@@ -184,7 +190,7 @@ fn get_vec_push<'tcx>(cx: &LateContext<'tcx>, stmt: &'tcx Stmt<'_>) -> Option<(&
if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(self_expr), sym::Vec);
if path.ident.name.as_str() == "push";
then {
- return Some((self_expr, pushed_item))
+ return Some((self_expr, pushed_item, semi_stmt.span.ctxt()))
}
}
None
diff --git a/src/tools/clippy/clippy_lints/src/manual_async_fn.rs b/src/tools/clippy/clippy_lints/src/manual_async_fn.rs
index 3778eb4c7..577bc1d66 100644
--- a/src/tools/clippy/clippy_lints/src/manual_async_fn.rs
+++ b/src/tools/clippy/clippy_lints/src/manual_async_fn.rs
@@ -1,12 +1,11 @@
use clippy_utils::diagnostics::span_lint_and_then;
-use clippy_utils::match_function_call_with_def_id;
use clippy_utils::source::{position_before_rarrow, snippet_block, snippet_opt};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::intravisit::FnKind;
use rustc_hir::{
AsyncGeneratorKind, Block, Body, Closure, Expr, ExprKind, FnDecl, FnRetTy, GeneratorKind, GenericArg, GenericBound,
- ItemKind, LifetimeName, Term, TraitRef, Ty, TyKind, TypeBindingKind,
+ ImplItem, Item, ItemKind, LifetimeName, Node, Term, TraitRef, Ty, TyKind, TypeBindingKind,
};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
@@ -46,7 +45,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualAsyncFn {
decl: &'tcx FnDecl<'_>,
body: &'tcx Body<'_>,
span: Span,
- _: LocalDefId,
+ def_id: LocalDefId,
) {
if_chain! {
if let Some(header) = kind.header();
@@ -60,6 +59,8 @@ impl<'tcx> LateLintPass<'tcx> for ManualAsyncFn {
if let ExprKind::Block(block, _) = body.value.kind;
if block.stmts.is_empty();
if let Some(closure_body) = desugared_async_block(cx, block);
+ if let Node::Item(Item {vis_span, ..}) | Node::ImplItem(ImplItem {vis_span, ..}) =
+ cx.tcx.hir().get_by_def_id(def_id);
then {
let header_span = span.with_hi(ret_ty.span.hi());
@@ -70,15 +71,22 @@ impl<'tcx> LateLintPass<'tcx> for ManualAsyncFn {
"this function can be simplified using the `async fn` syntax",
|diag| {
if_chain! {
+ if let Some(vis_snip) = snippet_opt(cx, *vis_span);
if let Some(header_snip) = snippet_opt(cx, header_span);
if let Some(ret_pos) = position_before_rarrow(&header_snip);
if let Some((ret_sugg, ret_snip)) = suggested_ret(cx, output);
then {
+ let header_snip = if vis_snip.is_empty() {
+ format!("async {}", &header_snip[..ret_pos])
+ } else {
+ format!("{} async {}", vis_snip, &header_snip[vis_snip.len() + 1..ret_pos])
+ };
+
let help = format!("make the function `async` and {ret_sugg}");
diag.span_suggestion(
header_span,
help,
- format!("async {}{ret_snip}", &header_snip[..ret_pos]),
+ format!("{header_snip}{ret_snip}"),
Applicability::MachineApplicable
);
@@ -175,16 +183,10 @@ fn captures_all_lifetimes(inputs: &[Ty<'_>], output_lifetimes: &[LifetimeName])
fn desugared_async_block<'tcx>(cx: &LateContext<'tcx>, block: &'tcx Block<'tcx>) -> Option<&'tcx Body<'tcx>> {
if_chain! {
if let Some(block_expr) = block.expr;
- if let Some(args) = cx
- .tcx
- .lang_items()
- .identity_future_fn()
- .and_then(|def_id| match_function_call_with_def_id(cx, block_expr, def_id));
- if args.len() == 1;
if let Expr {
kind: ExprKind::Closure(&Closure { body, .. }),
..
- } = args[0];
+ } = block_expr;
let closure_body = cx.tcx.hir().body(body);
if closure_body.generator_kind == Some(GeneratorKind::Async(AsyncGeneratorKind::Block));
then {
diff --git a/src/tools/clippy/clippy_lints/src/manual_bits.rs b/src/tools/clippy/clippy_lints/src/manual_bits.rs
index 462d73cf0..bc815dc4a 100644
--- a/src/tools/clippy/clippy_lints/src/manual_bits.rs
+++ b/src/tools/clippy/clippy_lints/src/manual_bits.rs
@@ -1,11 +1,12 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::get_parent_expr;
use clippy_utils::msrvs::{self, Msrv};
-use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::source::snippet_with_context;
use rustc_ast::ast::LitKind;
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind, GenericArg, QPath};
-use rustc_lint::{LateContext, LateLintPass};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_middle::lint::in_external_macro;
use rustc_middle::ty::{self, Ty};
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::sym;
@@ -55,13 +56,17 @@ impl<'tcx> LateLintPass<'tcx> for ManualBits {
if_chain! {
if let ExprKind::Binary(bin_op, left_expr, right_expr) = expr.kind;
if let BinOpKind::Mul = &bin_op.node;
+ if !in_external_macro(cx.sess(), expr.span);
+ let ctxt = expr.span.ctxt();
+ if left_expr.span.ctxt() == ctxt;
+ if right_expr.span.ctxt() == ctxt;
if let Some((real_ty, resolved_ty, other_expr)) = get_one_size_of_ty(cx, left_expr, right_expr);
if matches!(resolved_ty.kind(), ty::Int(_) | ty::Uint(_));
if let ExprKind::Lit(lit) = &other_expr.kind;
if let LitKind::Int(8, _) = lit.node;
then {
let mut app = Applicability::MachineApplicable;
- let ty_snip = snippet_with_applicability(cx, real_ty.span, "..", &mut app);
+ let ty_snip = snippet_with_context(cx, real_ty.span, ctxt, "..", &mut app).0;
let sugg = create_sugg(cx, expr, format!("{ty_snip}::BITS"));
span_lint_and_sugg(
diff --git a/src/tools/clippy/clippy_lints/src/manual_clamp.rs b/src/tools/clippy/clippy_lints/src/manual_clamp.rs
index f239736d3..440362b96 100644
--- a/src/tools/clippy/clippy_lints/src/manual_clamp.rs
+++ b/src/tools/clippy/clippy_lints/src/manual_clamp.rs
@@ -6,7 +6,8 @@ use clippy_utils::ty::implements_trait;
use clippy_utils::visitors::is_const_evaluatable;
use clippy_utils::MaybePath;
use clippy_utils::{
- eq_expr_value, is_diag_trait_item, is_trait_method, path_res, path_to_local_id, peel_blocks, peel_blocks_with_stmt,
+ eq_expr_value, in_constant, is_diag_trait_item, is_trait_method, path_res, path_to_local_id, peel_blocks,
+ peel_blocks_with_stmt,
};
use itertools::Itertools;
use rustc_errors::Applicability;
@@ -117,7 +118,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualClamp {
if !self.msrv.meets(msrvs::CLAMP) {
return;
}
- if !expr.span.from_expansion() {
+ if !expr.span.from_expansion() && !in_constant(cx, expr.hir_id) {
let suggestion = is_if_elseif_else_pattern(cx, expr)
.or_else(|| is_max_min_pattern(cx, expr))
.or_else(|| is_call_max_min_pattern(cx, expr))
@@ -130,7 +131,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualClamp {
}
fn check_block(&mut self, cx: &LateContext<'tcx>, block: &'tcx Block<'tcx>) {
- if !self.msrv.meets(msrvs::CLAMP) {
+ if !self.msrv.meets(msrvs::CLAMP) || in_constant(cx, block.hir_id) {
return;
}
for suggestion in is_two_if_pattern(cx, block) {
diff --git a/src/tools/clippy/clippy_lints/src/manual_is_ascii_check.rs b/src/tools/clippy/clippy_lints/src/manual_is_ascii_check.rs
index 2fd32c009..31264261f 100644
--- a/src/tools/clippy/clippy_lints/src/manual_is_ascii_check.rs
+++ b/src/tools/clippy/clippy_lints/src/manual_is_ascii_check.rs
@@ -1,5 +1,5 @@
use clippy_utils::msrvs::{self, Msrv};
-use clippy_utils::{diagnostics::span_lint_and_sugg, higher, in_constant, macros::root_macro_call, source::snippet};
+use clippy_utils::{diagnostics::span_lint_and_sugg, higher, in_constant, macros::root_macro_call, sugg::Sugg};
use rustc_ast::ast::RangeLimits;
use rustc_ast::LitKind::{Byte, Char};
use rustc_errors::Applicability;
@@ -115,15 +115,8 @@ fn check_is_ascii(cx: &LateContext<'_>, span: Span, recv: &Expr<'_>, range: &Cha
CharRange::Otherwise => None,
} {
let default_snip = "..";
- // `snippet_with_applicability` may set applicability to `MaybeIncorrect` for
- // macro span, so we check applicability manually by comparing `recv` is not default.
- let recv = snippet(cx, recv.span, default_snip);
-
- let applicability = if recv == default_snip {
- Applicability::HasPlaceholders
- } else {
- Applicability::MachineApplicable
- };
+ let mut app = Applicability::MachineApplicable;
+ let recv = Sugg::hir_with_context(cx, recv, span.ctxt(), default_snip, &mut app).maybe_par();
span_lint_and_sugg(
cx,
@@ -132,7 +125,7 @@ fn check_is_ascii(cx: &LateContext<'_>, span: Span, recv: &Expr<'_>, range: &Cha
"manual check for common ascii range",
"try",
format!("{recv}.{sugg}()"),
- applicability,
+ app,
);
}
}
diff --git a/src/tools/clippy/clippy_lints/src/manual_main_separator_str.rs b/src/tools/clippy/clippy_lints/src/manual_main_separator_str.rs
new file mode 100644
index 000000000..c292bbe4e
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/manual_main_separator_str.rs
@@ -0,0 +1,74 @@
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::msrvs::{self, Msrv};
+use clippy_utils::{is_trait_method, match_def_path, paths, peel_hir_expr_refs};
+use rustc_errors::Applicability;
+use rustc_hir::def::{DefKind, Res};
+use rustc_hir::{Expr, ExprKind, Mutability, QPath};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty;
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for references on `std::path::MAIN_SEPARATOR.to_string()` used
+ /// to build a `&str`.
+ ///
+ /// ### Why is this bad?
+ /// There exists a `std::path::MAIN_SEPARATOR_STR` which does not require
+ /// an extra memory allocation.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let s: &str = &std::path::MAIN_SEPARATOR.to_string();
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let s: &str = std::path::MAIN_SEPARATOR_STR;
+ /// ```
+ #[clippy::version = "1.70.0"]
+ pub MANUAL_MAIN_SEPARATOR_STR,
+ complexity,
+ "`&std::path::MAIN_SEPARATOR.to_string()` can be replaced by `std::path::MAIN_SEPARATOR_STR`"
+}
+
+pub struct ManualMainSeparatorStr {
+ msrv: Msrv,
+}
+
+impl ManualMainSeparatorStr {
+ #[must_use]
+ pub fn new(msrv: Msrv) -> Self {
+ Self { msrv }
+ }
+}
+
+impl_lint_pass!(ManualMainSeparatorStr => [MANUAL_MAIN_SEPARATOR_STR]);
+
+impl LateLintPass<'_> for ManualMainSeparatorStr {
+ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
+ if self.msrv.meets(msrvs::PATH_MAIN_SEPARATOR_STR) &&
+ let (target, _) = peel_hir_expr_refs(expr) &&
+ is_trait_method(cx, target, sym::ToString) &&
+ let ExprKind::MethodCall(path, receiver, &[], _) = target.kind &&
+ path.ident.name == sym::to_string &&
+ let ExprKind::Path(QPath::Resolved(None, path)) = receiver.kind &&
+ let Res::Def(DefKind::Const, receiver_def_id) = path.res &&
+ match_def_path(cx, receiver_def_id, &paths::PATH_MAIN_SEPARATOR) &&
+ let ty::Ref(_, ty, Mutability::Not) = cx.typeck_results().expr_ty_adjusted(expr).kind() &&
+ ty.is_str()
+ {
+ span_lint_and_sugg(
+ cx,
+ MANUAL_MAIN_SEPARATOR_STR,
+ expr.span,
+ "taking a reference on `std::path::MAIN_SEPARATOR` conversion to `String`",
+ "replace with",
+ "std::path::MAIN_SEPARATOR_STR".to_owned(),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+
+ extract_msrv_attr!(LateContext);
+}
diff --git a/src/tools/clippy/clippy_lints/src/manual_non_exhaustive.rs b/src/tools/clippy/clippy_lints/src/manual_non_exhaustive.rs
index 9a84068d4..0e22485db 100644
--- a/src/tools/clippy/clippy_lints/src/manual_non_exhaustive.rs
+++ b/src/tools/clippy/clippy_lints/src/manual_non_exhaustive.rs
@@ -8,7 +8,6 @@ use rustc_errors::Applicability;
use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res};
use rustc_hir::{self as hir, Expr, ExprKind, QPath};
use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext};
-use rustc_middle::ty::DefIdTree;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::def_id::{DefId, LocalDefId};
use rustc_span::{sym, Span};
diff --git a/src/tools/clippy/clippy_lints/src/manual_rem_euclid.rs b/src/tools/clippy/clippy_lints/src/manual_rem_euclid.rs
index 38f41d077..aafee9271 100644
--- a/src/tools/clippy/clippy_lints/src/manual_rem_euclid.rs
+++ b/src/tools/clippy/clippy_lints/src/manual_rem_euclid.rs
@@ -1,7 +1,7 @@
use clippy_utils::consts::{constant_full_int, FullInt};
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::msrvs::{self, Msrv};
-use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::source::snippet_with_context;
use clippy_utils::{in_constant, path_to_local};
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Expr, ExprKind, Node, TyKind};
@@ -60,12 +60,16 @@ impl<'tcx> LateLintPass<'tcx> for ManualRemEuclid {
return;
}
+ // (x % c + c) % c
if let ExprKind::Binary(op1, expr1, right) = expr.kind
&& op1.node == BinOpKind::Rem
+ && let ctxt = expr.span.ctxt()
+ && expr1.span.ctxt() == ctxt
&& let Some(const1) = check_for_unsigned_int_constant(cx, right)
&& let ExprKind::Binary(op2, left, right) = expr1.kind
&& op2.node == BinOpKind::Add
&& let Some((const2, expr2)) = check_for_either_unsigned_int_constant(cx, left, right)
+ && expr2.span.ctxt() == ctxt
&& let ExprKind::Binary(op3, expr3, right) = expr2.kind
&& op3.node == BinOpKind::Rem
&& let Some(const3) = check_for_unsigned_int_constant(cx, right)
@@ -86,7 +90,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualRemEuclid {
};
let mut app = Applicability::MachineApplicable;
- let rem_of = snippet_with_applicability(cx, expr3.span, "_", &mut app);
+ let rem_of = snippet_with_context(cx, expr3.span, ctxt, "_", &mut app).0;
span_lint_and_sugg(
cx,
MANUAL_REM_EUCLID,
diff --git a/src/tools/clippy/clippy_lints/src/manual_slice_size_calculation.rs b/src/tools/clippy/clippy_lints/src/manual_slice_size_calculation.rs
new file mode 100644
index 000000000..92ee79453
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/manual_slice_size_calculation.rs
@@ -0,0 +1,93 @@
+use clippy_utils::diagnostics::span_lint_and_help;
+use clippy_utils::{expr_or_init, in_constant};
+use rustc_hir::{BinOpKind, Expr, ExprKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::ty;
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::symbol::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// When `a` is `&[T]`, detect `a.len() * size_of::<T>()` and suggest `size_of_val(a)`
+ /// instead.
+ ///
+ /// ### Why is this better?
+ /// * Shorter to write
+ /// * Removes the need for the human and the compiler to worry about overflow in the
+ /// multiplication
+ /// * Potentially faster at runtime as rust emits special no-wrapping flags when it
+ /// calculates the byte length
+ /// * Less turbofishing
+ ///
+ /// ### Example
+ /// ```rust
+ /// # let data : &[i32] = &[1, 2, 3];
+ /// let newlen = data.len() * std::mem::size_of::<i32>();
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// # let data : &[i32] = &[1, 2, 3];
+ /// let newlen = std::mem::size_of_val(data);
+ /// ```
+ #[clippy::version = "1.70.0"]
+ pub MANUAL_SLICE_SIZE_CALCULATION,
+ complexity,
+ "manual slice size calculation"
+}
+declare_lint_pass!(ManualSliceSizeCalculation => [MANUAL_SLICE_SIZE_CALCULATION]);
+
+impl<'tcx> LateLintPass<'tcx> for ManualSliceSizeCalculation {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
+ // Does not apply inside const because size_of_value is not cost in stable.
+ if !in_constant(cx, expr.hir_id)
+ && let ExprKind::Binary(ref op, left, right) = expr.kind
+ && BinOpKind::Mul == op.node
+ && let Some(_receiver) = simplify(cx, left, right)
+ {
+ span_lint_and_help(
+ cx,
+ MANUAL_SLICE_SIZE_CALCULATION,
+ expr.span,
+ "manual slice size calculation",
+ None,
+ "consider using std::mem::size_of_value instead");
+ }
+ }
+}
+
+fn simplify<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr1: &'tcx Expr<'tcx>,
+ expr2: &'tcx Expr<'tcx>,
+) -> Option<&'tcx Expr<'tcx>> {
+ let expr1 = expr_or_init(cx, expr1);
+ let expr2 = expr_or_init(cx, expr2);
+
+ simplify_half(cx, expr1, expr2).or_else(|| simplify_half(cx, expr2, expr1))
+}
+
+fn simplify_half<'tcx>(
+ cx: &LateContext<'tcx>,
+ expr1: &'tcx Expr<'tcx>,
+ expr2: &'tcx Expr<'tcx>,
+) -> Option<&'tcx Expr<'tcx>> {
+ if
+ // expr1 is `[T1].len()`?
+ let ExprKind::MethodCall(method_path, receiver, _, _) = expr1.kind
+ && method_path.ident.name == sym::len
+ && let receiver_ty = cx.typeck_results().expr_ty(receiver)
+ && let ty::Slice(ty1) = receiver_ty.peel_refs().kind()
+ // expr2 is `size_of::<T2>()`?
+ && let ExprKind::Call(func, _) = expr2.kind
+ && let ExprKind::Path(ref func_qpath) = func.kind
+ && let Some(def_id) = cx.qpath_res(func_qpath, func.hir_id).opt_def_id()
+ && cx.tcx.is_diagnostic_item(sym::mem_size_of, def_id)
+ && let Some(ty2) = cx.typeck_results().node_substs(func.hir_id).types().next()
+ // T1 == T2?
+ && *ty1 == ty2
+ {
+ Some(receiver)
+ } else {
+ None
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/match_result_ok.rs b/src/tools/clippy/clippy_lints/src/match_result_ok.rs
index a020282d2..6ec978403 100644
--- a/src/tools/clippy/clippy_lints/src/match_result_ok.rs
+++ b/src/tools/clippy/clippy_lints/src/match_result_ok.rs
@@ -1,11 +1,11 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::higher;
-use clippy_utils::method_chain_args;
-use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::is_res_lang_ctor;
+use clippy_utils::source::snippet_with_context;
use clippy_utils::ty::is_type_diagnostic_item;
use if_chain::if_chain;
use rustc_errors::Applicability;
-use rustc_hir::{Expr, ExprKind, PatKind, QPath};
+use rustc_hir::{Expr, ExprKind, LangItem, PatKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::sym;
@@ -58,17 +58,18 @@ impl<'tcx> LateLintPass<'tcx> for MatchResultOk {
};
if_chain! {
- if let ExprKind::MethodCall(ok_path, result_types_0, ..) = let_expr.kind; //check is expr.ok() has type Result<T,E>.ok(, _)
- if let PatKind::TupleStruct(QPath::Resolved(_, x), y, _) = let_pat.kind; //get operation
- if method_chain_args(let_expr, &["ok"]).is_some(); //test to see if using ok() method use std::marker::Sized;
- if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(result_types_0), sym::Result);
- if rustc_hir_pretty::to_string(rustc_hir_pretty::NO_ANN, |s| s.print_path(x, false)) == "Some";
-
+ if let ExprKind::MethodCall(ok_path, recv, [], ..) = let_expr.kind; //check is expr.ok() has type Result<T,E>.ok(, _)
+ if let PatKind::TupleStruct(ref pat_path, [ok_pat], _) = let_pat.kind; //get operation
+ if ok_path.ident.as_str() == "ok";
+ if is_type_diagnostic_item(cx, cx.typeck_results().expr_ty(recv), sym::Result);
+ if is_res_lang_ctor(cx, cx.qpath_res(pat_path, let_pat.hir_id), LangItem::OptionSome);
+ let ctxt = expr.span.ctxt();
+ if let_expr.span.ctxt() == ctxt;
+ if let_pat.span.ctxt() == ctxt;
then {
-
let mut applicability = Applicability::MachineApplicable;
- let some_expr_string = snippet_with_applicability(cx, y[0].span, "", &mut applicability);
- let trimmed_ok = snippet_with_applicability(cx, let_expr.span.until(ok_path.ident.span), "", &mut applicability);
+ let some_expr_string = snippet_with_context(cx, ok_pat.span, ctxt, "", &mut applicability).0;
+ let trimmed_ok = snippet_with_context(cx, recv.span, ctxt, "", &mut applicability).0;
let sugg = format!(
"{ifwhile} let Ok({some_expr_string}) = {}",
trimmed_ok.trim().trim_end_matches('.'),
diff --git a/src/tools/clippy/clippy_lints/src/matches/manual_unwrap_or.rs b/src/tools/clippy/clippy_lints/src/matches/manual_unwrap_or.rs
index 587c926dc..b94501bf0 100644
--- a/src/tools/clippy/clippy_lints/src/matches/manual_unwrap_or.rs
+++ b/src/tools/clippy/clippy_lints/src/matches/manual_unwrap_or.rs
@@ -10,7 +10,6 @@ use rustc_hir::def::{DefKind, Res};
use rustc_hir::LangItem::{OptionNone, ResultErr};
use rustc_hir::{Arm, Expr, PatKind};
use rustc_lint::LateContext;
-use rustc_middle::ty::DefIdTree;
use rustc_span::sym;
use super::MANUAL_UNWRAP_OR;
@@ -33,14 +32,8 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, scrutinee:
let reindented_or_body =
reindent_multiline(or_body_snippet.into(), true, Some(indent));
- let suggestion = if scrutinee.span.from_expansion() {
- // we don't want parentheses around macro, e.g. `(some_macro!()).unwrap_or(0)`
- sugg::Sugg::hir_with_macro_callsite(cx, scrutinee, "..")
- }
- else {
- sugg::Sugg::hir(cx, scrutinee, "..").maybe_par()
- };
-
+ let mut app = Applicability::MachineApplicable;
+ let suggestion = sugg::Sugg::hir_with_context(cx, scrutinee, expr.span.ctxt(), "..", &mut app).maybe_par();
span_lint_and_sugg(
cx,
MANUAL_UNWRAP_OR, expr.span,
@@ -49,7 +42,7 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>, scrutinee:
format!(
"{suggestion}.unwrap_or({reindented_or_body})",
),
- Applicability::MachineApplicable,
+ app,
);
}
}
diff --git a/src/tools/clippy/clippy_lints/src/matches/match_bool.rs b/src/tools/clippy/clippy_lints/src/matches/match_bool.rs
index 1c216e135..df1e585f1 100644
--- a/src/tools/clippy/clippy_lints/src/matches/match_bool.rs
+++ b/src/tools/clippy/clippy_lints/src/matches/match_bool.rs
@@ -10,9 +10,9 @@ use rustc_middle::ty;
use super::MATCH_BOOL;
-pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) {
+pub(crate) fn check(cx: &LateContext<'_>, scrutinee: &Expr<'_>, arms: &[Arm<'_>], expr: &Expr<'_>) {
// Type of expression is `bool`.
- if *cx.typeck_results().expr_ty(ex).kind() == ty::Bool {
+ if *cx.typeck_results().expr_ty(scrutinee).kind() == ty::Bool {
span_lint_and_then(
cx,
MATCH_BOOL,
@@ -36,24 +36,26 @@ pub(crate) fn check(cx: &LateContext<'_>, ex: &Expr<'_>, arms: &[Arm<'_>], expr:
};
if let Some((true_expr, false_expr)) = exprs {
+ let mut app = Applicability::HasPlaceholders;
+ let ctxt = expr.span.ctxt();
let sugg = match (is_unit_expr(true_expr), is_unit_expr(false_expr)) {
(false, false) => Some(format!(
"if {} {} else {}",
- snippet(cx, ex.span, "b"),
- expr_block(cx, true_expr, None, "..", Some(expr.span)),
- expr_block(cx, false_expr, None, "..", Some(expr.span))
+ snippet(cx, scrutinee.span, "b"),
+ expr_block(cx, true_expr, ctxt, "..", Some(expr.span), &mut app),
+ expr_block(cx, false_expr, ctxt, "..", Some(expr.span), &mut app)
)),
(false, true) => Some(format!(
"if {} {}",
- snippet(cx, ex.span, "b"),
- expr_block(cx, true_expr, None, "..", Some(expr.span))
+ snippet(cx, scrutinee.span, "b"),
+ expr_block(cx, true_expr, ctxt, "..", Some(expr.span), &mut app)
)),
(true, false) => {
- let test = Sugg::hir(cx, ex, "..");
+ let test = Sugg::hir(cx, scrutinee, "..");
Some(format!(
"if {} {}",
!test,
- expr_block(cx, false_expr, None, "..", Some(expr.span))
+ expr_block(cx, false_expr, ctxt, "..", Some(expr.span), &mut app)
))
},
(true, true) => None,
diff --git a/src/tools/clippy/clippy_lints/src/matches/match_ref_pats.rs b/src/tools/clippy/clippy_lints/src/matches/match_ref_pats.rs
index 80f964ba1..aba4c85c5 100644
--- a/src/tools/clippy/clippy_lints/src/matches/match_ref_pats.rs
+++ b/src/tools/clippy/clippy_lints/src/matches/match_ref_pats.rs
@@ -1,13 +1,14 @@
use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then};
-use clippy_utils::source::snippet;
+use clippy_utils::source::{snippet, walk_span_to_context};
use clippy_utils::sugg::Sugg;
use core::iter::once;
+use rustc_errors::Applicability;
use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability, Pat, PatKind};
use rustc_lint::LateContext;
use super::MATCH_REF_PATS;
-pub(crate) fn check<'a, 'b, I>(cx: &LateContext<'_>, ex: &Expr<'_>, pats: I, expr: &Expr<'_>)
+pub(crate) fn check<'a, 'b, I>(cx: &LateContext<'_>, scrutinee: &Expr<'_>, pats: I, expr: &Expr<'_>)
where
'b: 'a,
I: Clone + Iterator<Item = &'a Pat<'b>>,
@@ -17,13 +18,28 @@ where
}
let (first_sugg, msg, title);
- let span = ex.span.source_callsite();
- if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, inner) = ex.kind {
- first_sugg = once((span, Sugg::hir_with_macro_callsite(cx, inner, "..").to_string()));
+ let ctxt = expr.span.ctxt();
+ let mut app = Applicability::Unspecified;
+ if let ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, inner) = scrutinee.kind {
+ if scrutinee.span.ctxt() != ctxt {
+ return;
+ }
+ first_sugg = once((
+ scrutinee.span,
+ Sugg::hir_with_context(cx, inner, ctxt, "..", &mut app).to_string(),
+ ));
msg = "try";
title = "you don't need to add `&` to both the expression and the patterns";
} else {
- first_sugg = once((span, Sugg::hir_with_macro_callsite(cx, ex, "..").deref().to_string()));
+ let Some(span) = walk_span_to_context(scrutinee.span, ctxt) else {
+ return;
+ };
+ first_sugg = once((
+ span,
+ Sugg::hir_with_context(cx, scrutinee, ctxt, "..", &mut app)
+ .deref()
+ .to_string(),
+ ));
msg = "instead of prefixing all patterns with `&`, you can dereference the expression";
title = "you don't need to add `&` to all patterns";
}
diff --git a/src/tools/clippy/clippy_lints/src/matches/match_single_binding.rs b/src/tools/clippy/clippy_lints/src/matches/match_single_binding.rs
index 065a5c726..89da7a55c 100644
--- a/src/tools/clippy/clippy_lints/src/matches/match_single_binding.rs
+++ b/src/tools/clippy/clippy_lints/src/matches/match_single_binding.rs
@@ -1,10 +1,9 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::macros::HirNode;
-use clippy_utils::source::{indent_of, snippet, snippet_block, snippet_with_applicability};
-use clippy_utils::sugg::Sugg;
+use clippy_utils::source::{indent_of, snippet, snippet_block_with_context, snippet_with_applicability};
use clippy_utils::{get_parent_expr, is_refutable, peel_blocks};
use rustc_errors::Applicability;
-use rustc_hir::{Arm, Expr, ExprKind, Node, PatKind};
+use rustc_hir::{Arm, Expr, ExprKind, Node, PatKind, StmtKind};
use rustc_lint::LateContext;
use rustc_span::Span;
@@ -24,21 +23,30 @@ pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], e
let matched_vars = ex.span;
let bind_names = arms[0].pat.span;
let match_body = peel_blocks(arms[0].body);
- let mut snippet_body = if match_body.span.from_expansion() {
- Sugg::hir_with_macro_callsite(cx, match_body, "..").to_string()
- } else {
- snippet_block(cx, match_body.span, "..", Some(expr.span)).to_string()
- };
+ let mut app = Applicability::MaybeIncorrect;
+ let mut snippet_body = snippet_block_with_context(
+ cx,
+ match_body.span,
+ arms[0].span.ctxt(),
+ "..",
+ Some(expr.span),
+ &mut app,
+ )
+ .0
+ .to_string();
// Do we need to add ';' to suggestion ?
- if let ExprKind::Block(block, _) = match_body.kind {
- // macro + expr_ty(body) == ()
- if block.span.from_expansion() && cx.typeck_results().expr_ty(match_body).is_unit() {
- snippet_body.push(';');
+ if let Node::Stmt(stmt) = cx.tcx.hir().get_parent(expr.hir_id)
+ && let StmtKind::Expr(_) = stmt.kind
+ && match match_body.kind {
+ // We don't need to add a ; to blocks, unless that block is from a macro expansion
+ ExprKind::Block(block, _) => block.span.from_expansion(),
+ _ => true,
}
+ {
+ snippet_body.push(';');
}
- let mut applicability = Applicability::MaybeIncorrect;
match arms[0].pat.kind {
PatKind::Binding(..) | PatKind::Tuple(_, _) | PatKind::Struct(..) => {
let (target_span, sugg) = match opt_parent_assign_span(cx, ex) {
@@ -48,7 +56,7 @@ pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], e
(ex, expr),
(bind_names, matched_vars),
&snippet_body,
- &mut applicability,
+ &mut app,
Some(span),
true,
);
@@ -60,7 +68,7 @@ pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], e
"this assignment could be simplified",
"consider removing the `match` expression",
sugg,
- applicability,
+ app,
);
return;
@@ -69,10 +77,10 @@ pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], e
span,
format!(
"let {} = {};\n{}let {} = {snippet_body};",
- snippet_with_applicability(cx, bind_names, "..", &mut applicability),
- snippet_with_applicability(cx, matched_vars, "..", &mut applicability),
+ snippet_with_applicability(cx, bind_names, "..", &mut app),
+ snippet_with_applicability(cx, matched_vars, "..", &mut app),
" ".repeat(indent_of(cx, expr.span).unwrap_or(0)),
- snippet_with_applicability(cx, pat_span, "..", &mut applicability)
+ snippet_with_applicability(cx, pat_span, "..", &mut app)
),
),
None => {
@@ -81,7 +89,7 @@ pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], e
(ex, expr),
(bind_names, matched_vars),
&snippet_body,
- &mut applicability,
+ &mut app,
None,
true,
);
@@ -96,7 +104,7 @@ pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], e
"this match could be written as a `let` statement",
"consider using a `let` statement",
sugg,
- applicability,
+ app,
);
},
PatKind::Wild => {
@@ -106,7 +114,7 @@ pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], e
(ex, expr),
(bind_names, matched_vars),
&snippet_body,
- &mut applicability,
+ &mut app,
None,
false,
);
@@ -118,7 +126,7 @@ pub(crate) fn check<'a>(cx: &LateContext<'a>, ex: &Expr<'a>, arms: &[Arm<'_>], e
"this match could be replaced by its scrutinee and body",
"consider using the scrutinee and body instead",
sugg,
- applicability,
+ app,
);
} else {
span_lint_and_sugg(
diff --git a/src/tools/clippy/clippy_lints/src/matches/mod.rs b/src/tools/clippy/clippy_lints/src/matches/mod.rs
index 7b15a307f..97ecca450 100644
--- a/src/tools/clippy/clippy_lints/src/matches/mod.rs
+++ b/src/tools/clippy/clippy_lints/src/matches/mod.rs
@@ -925,7 +925,7 @@ declare_clippy_lint! {
#[clippy::version = "1.66.0"]
pub MANUAL_FILTER,
complexity,
- "reimplentation of `filter`"
+ "reimplementation of `filter`"
}
#[derive(Default)]
diff --git a/src/tools/clippy/clippy_lints/src/matches/redundant_pattern_match.rs b/src/tools/clippy/clippy_lints/src/matches/redundant_pattern_match.rs
index 81bebff34..7b609ff3d 100644
--- a/src/tools/clippy/clippy_lints/src/matches/redundant_pattern_match.rs
+++ b/src/tools/clippy/clippy_lints/src/matches/redundant_pattern_match.rs
@@ -1,6 +1,6 @@
use super::REDUNDANT_PATTERN_MATCHING;
use clippy_utils::diagnostics::span_lint_and_then;
-use clippy_utils::source::snippet;
+use clippy_utils::source::{snippet, walk_span_to_context};
use clippy_utils::sugg::Sugg;
use clippy_utils::ty::{is_type_diagnostic_item, needs_ordered_drop};
use clippy_utils::visitors::any_temporaries_need_ordered_drop;
@@ -12,7 +12,7 @@ use rustc_hir::def::{DefKind, Res};
use rustc_hir::LangItem::{self, OptionNone, OptionSome, PollPending, PollReady, ResultErr, ResultOk};
use rustc_hir::{Arm, Expr, ExprKind, Node, Pat, PatKind, QPath, UnOp};
use rustc_lint::LateContext;
-use rustc_middle::ty::{self, subst::GenericArgKind, DefIdTree, Ty};
+use rustc_middle::ty::{self, subst::GenericArgKind, Ty};
use rustc_span::{sym, Symbol};
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
@@ -150,22 +150,25 @@ fn find_sugg_for_if_let<'tcx>(
// if/while let ... = ... { ... }
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^
let expr_span = expr.span;
+ let ctxt = expr.span.ctxt();
// if/while let ... = ... { ... }
- // ^^^
- let op_span = result_expr.span.source_callsite();
+ // ^^^
+ let Some(res_span) = walk_span_to_context(result_expr.span.source_callsite(), ctxt) else {
+ return;
+ };
// if/while let ... = ... { ... }
- // ^^^^^^^^^^^^^^^^^^^
- let span = expr_span.until(op_span.shrink_to_hi());
+ // ^^^^^^^^^^^^^^^^^^^^^^
+ let span = expr_span.until(res_span.shrink_to_hi());
- let app = if needs_drop {
+ let mut app = if needs_drop {
Applicability::MaybeIncorrect
} else {
Applicability::MachineApplicable
};
- let sugg = Sugg::hir_with_macro_callsite(cx, result_expr, "_")
+ let sugg = Sugg::hir_with_context(cx, result_expr, ctxt, "_", &mut app)
.maybe_par()
.to_string();
diff --git a/src/tools/clippy/clippy_lints/src/matches/significant_drop_in_scrutinee.rs b/src/tools/clippy/clippy_lints/src/matches/significant_drop_in_scrutinee.rs
index b33a24781..04225beeb 100644
--- a/src/tools/clippy/clippy_lints/src/matches/significant_drop_in_scrutinee.rs
+++ b/src/tools/clippy/clippy_lints/src/matches/significant_drop_in_scrutinee.rs
@@ -321,7 +321,6 @@ impl<'a, 'tcx> Visitor<'tcx> for SigDropHelper<'a, 'tcx> {
self.has_significant_drop = true;
}
}
- ExprKind::Box(..) |
ExprKind::Array(..) |
ExprKind::Call(..) |
ExprKind::Unary(..) |
diff --git a/src/tools/clippy/clippy_lints/src/matches/single_match.rs b/src/tools/clippy/clippy_lints/src/matches/single_match.rs
index 19b49c44d..ad47c1389 100644
--- a/src/tools/clippy/clippy_lints/src/matches/single_match.rs
+++ b/src/tools/clippy/clippy_lints/src/matches/single_match.rs
@@ -67,8 +67,10 @@ fn report_single_pattern(
els: Option<&Expr<'_>>,
) {
let lint = if els.is_some() { SINGLE_MATCH_ELSE } else { SINGLE_MATCH };
+ let ctxt = expr.span.ctxt();
+ let mut app = Applicability::HasPlaceholders;
let els_str = els.map_or(String::new(), |els| {
- format!(" else {}", expr_block(cx, els, None, "..", Some(expr.span)))
+ format!(" else {}", expr_block(cx, els, ctxt, "..", Some(expr.span), &mut app))
});
let (pat, pat_ref_count) = peel_hir_pat_refs(arms[0].pat);
@@ -103,7 +105,7 @@ fn report_single_pattern(
// PartialEq for different reference counts may not exist.
"&".repeat(ref_count_diff),
snippet(cx, arms[0].pat.span, ".."),
- expr_block(cx, arms[0].body, None, "..", Some(expr.span)),
+ expr_block(cx, arms[0].body, ctxt, "..", Some(expr.span), &mut app),
);
(msg, sugg)
} else {
@@ -112,21 +114,13 @@ fn report_single_pattern(
"if let {} = {} {}{els_str}",
snippet(cx, arms[0].pat.span, ".."),
snippet(cx, ex.span, ".."),
- expr_block(cx, arms[0].body, None, "..", Some(expr.span)),
+ expr_block(cx, arms[0].body, ctxt, "..", Some(expr.span), &mut app),
);
(msg, sugg)
}
};
- span_lint_and_sugg(
- cx,
- lint,
- expr.span,
- msg,
- "try this",
- sugg,
- Applicability::HasPlaceholders,
- );
+ span_lint_and_sugg(cx, lint, expr.span, msg, "try this", sugg, app);
}
fn check_opt_like<'a>(
diff --git a/src/tools/clippy/clippy_lints/src/mem_replace.rs b/src/tools/clippy/clippy_lints/src/mem_replace.rs
index 35024ec12..8a921d4af 100644
--- a/src/tools/clippy/clippy_lints/src/mem_replace.rs
+++ b/src/tools/clippy/clippy_lints/src/mem_replace.rs
@@ -1,12 +1,13 @@
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg, span_lint_and_then};
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::{snippet, snippet_with_applicability};
+use clippy_utils::sugg::Sugg;
use clippy_utils::ty::is_non_aggregate_primitive_type;
-use clippy_utils::{is_default_equivalent, is_res_lang_ctor, path_res};
+use clippy_utils::{is_default_equivalent, is_res_lang_ctor, path_res, peel_ref_operators};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::LangItem::OptionNone;
-use rustc_hir::{BorrowKind, Expr, ExprKind, Mutability, QPath};
+use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::lint::in_external_macro;
use rustc_session::{declare_tool_lint, impl_lint_pass};
@@ -101,40 +102,26 @@ declare_clippy_lint! {
impl_lint_pass!(MemReplace =>
[MEM_REPLACE_OPTION_WITH_NONE, MEM_REPLACE_WITH_UNINIT, MEM_REPLACE_WITH_DEFAULT]);
-fn check_replace_option_with_none(cx: &LateContext<'_>, src: &Expr<'_>, dest: &Expr<'_>, expr_span: Span) {
- // Check that second argument is `Option::None`
- if is_res_lang_ctor(cx, path_res(cx, src), OptionNone) {
- // Since this is a late pass (already type-checked),
- // and we already know that the second argument is an
- // `Option`, we do not need to check the first
- // argument's type. All that's left is to get
- // replacee's path.
- let replaced_path = match dest.kind {
- ExprKind::AddrOf(BorrowKind::Ref, Mutability::Mut, replaced) => {
- if let ExprKind::Path(QPath::Resolved(None, replaced_path)) = replaced.kind {
- replaced_path
- } else {
- return;
- }
- },
- ExprKind::Path(QPath::Resolved(None, replaced_path)) => replaced_path,
- _ => return,
- };
-
- let mut applicability = Applicability::MachineApplicable;
- span_lint_and_sugg(
- cx,
- MEM_REPLACE_OPTION_WITH_NONE,
- expr_span,
- "replacing an `Option` with `None`",
- "consider `Option::take()` instead",
- format!(
- "{}.take()",
- snippet_with_applicability(cx, replaced_path.span, "", &mut applicability)
- ),
- applicability,
- );
- }
+fn check_replace_option_with_none(cx: &LateContext<'_>, dest: &Expr<'_>, expr_span: Span) {
+ // Since this is a late pass (already type-checked),
+ // and we already know that the second argument is an
+ // `Option`, we do not need to check the first
+ // argument's type. All that's left is to get
+ // the replacee's expr after peeling off the `&mut`
+ let sugg_expr = peel_ref_operators(cx, dest);
+ let mut applicability = Applicability::MachineApplicable;
+ span_lint_and_sugg(
+ cx,
+ MEM_REPLACE_OPTION_WITH_NONE,
+ expr_span,
+ "replacing an `Option` with `None`",
+ "consider `Option::take()` instead",
+ format!(
+ "{}.take()",
+ Sugg::hir_with_context(cx, sugg_expr, expr_span.ctxt(), "", &mut applicability).maybe_par()
+ ),
+ applicability,
+ );
}
fn check_replace_with_uninit(cx: &LateContext<'_>, src: &Expr<'_>, dest: &Expr<'_>, expr_span: Span) {
@@ -200,10 +187,6 @@ fn check_replace_with_default(cx: &LateContext<'_>, src: &Expr<'_>, dest: &Expr<
if is_non_aggregate_primitive_type(expr_type) {
return;
}
- // disable lint for Option since it is covered in another lint
- if is_res_lang_ctor(cx, path_res(cx, src), OptionNone) {
- return;
- }
if is_default_equivalent(cx, src) && !in_external_macro(cx.tcx.sess, expr_span) {
span_lint_and_then(
cx,
@@ -246,11 +229,13 @@ impl<'tcx> LateLintPass<'tcx> for MemReplace {
if let Some(def_id) = cx.qpath_res(func_qpath, func.hir_id).opt_def_id();
if cx.tcx.is_diagnostic_item(sym::mem_replace, def_id);
then {
- check_replace_option_with_none(cx, src, dest, expr.span);
- check_replace_with_uninit(cx, src, dest, expr.span);
- if self.msrv.meets(msrvs::MEM_TAKE) {
+ // Check that second argument is `Option::None`
+ if is_res_lang_ctor(cx, path_res(cx, src), OptionNone) {
+ check_replace_option_with_none(cx, dest, expr.span);
+ } else if self.msrv.meets(msrvs::MEM_TAKE) {
check_replace_with_default(cx, src, dest, expr.span);
}
+ check_replace_with_uninit(cx, src, dest, expr.span);
}
}
}
diff --git a/src/tools/clippy/clippy_lints/src/methods/bind_instead_of_map.rs b/src/tools/clippy/clippy_lints/src/methods/bind_instead_of_map.rs
index 4720a6e68..008533488 100644
--- a/src/tools/clippy/clippy_lints/src/methods/bind_instead_of_map.rs
+++ b/src/tools/clippy/clippy_lints/src/methods/bind_instead_of_map.rs
@@ -1,6 +1,6 @@
use super::{contains_return, BIND_INSTEAD_OF_MAP};
use clippy_utils::diagnostics::{multispan_sugg_with_applicability, span_lint_and_sugg, span_lint_and_then};
-use clippy_utils::source::{snippet, snippet_with_macro_callsite};
+use clippy_utils::source::{snippet, snippet_with_context};
use clippy_utils::{peel_blocks, visitors::find_all_ret_expressions};
use if_chain::if_chain;
use rustc_errors::Applicability;
@@ -8,7 +8,6 @@ use rustc_hir as hir;
use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res};
use rustc_hir::{LangItem, QPath};
use rustc_lint::LateContext;
-use rustc_middle::ty::DefIdTree;
use rustc_span::Span;
pub(crate) struct OptionAndThenSome;
@@ -77,11 +76,8 @@ pub(crate) trait BindInsteadOfMap {
if !contains_return(inner_expr);
if let Some(msg) = Self::lint_msg(cx);
then {
- let some_inner_snip = if inner_expr.span.from_expansion() {
- snippet_with_macro_callsite(cx, inner_expr.span, "_")
- } else {
- snippet(cx, inner_expr.span, "_")
- };
+ let mut app = Applicability::MachineApplicable;
+ let some_inner_snip = snippet_with_context(cx, inner_expr.span, closure_expr.span.ctxt(), "_", &mut app).0;
let closure_args_snip = snippet(cx, closure_args_span, "..");
let option_snip = snippet(cx, recv.span, "..");
@@ -93,7 +89,7 @@ pub(crate) trait BindInsteadOfMap {
&msg,
"try this",
note,
- Applicability::MachineApplicable,
+ app,
);
true
} else {
diff --git a/src/tools/clippy/clippy_lints/src/methods/chars_cmp.rs b/src/tools/clippy/clippy_lints/src/methods/chars_cmp.rs
index 56b7fbb9d..079df2226 100644
--- a/src/tools/clippy/clippy_lints/src/methods/chars_cmp.rs
+++ b/src/tools/clippy/clippy_lints/src/methods/chars_cmp.rs
@@ -6,7 +6,7 @@ use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_lint::LateContext;
use rustc_lint::Lint;
-use rustc_middle::ty::{self, DefIdTree};
+use rustc_middle::ty;
/// Wrapper fn for `CHARS_NEXT_CMP` and `CHARS_LAST_CMP` lints.
pub(super) fn check(
diff --git a/src/tools/clippy/clippy_lints/src/methods/clear_with_drain.rs b/src/tools/clippy/clippy_lints/src/methods/clear_with_drain.rs
new file mode 100644
index 000000000..67ad58d5a
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/methods/clear_with_drain.rs
@@ -0,0 +1,53 @@
+use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::is_range_full;
+use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item};
+use rustc_errors::Applicability;
+use rustc_hir as hir;
+use rustc_hir::{Expr, ExprKind, LangItem, QPath};
+use rustc_lint::LateContext;
+use rustc_span::symbol::sym;
+use rustc_span::Span;
+
+use super::CLEAR_WITH_DRAIN;
+
+// Add `String` here when it is added to diagnostic items
+const ACCEPTABLE_TYPES_WITH_ARG: [rustc_span::Symbol; 2] = [sym::Vec, sym::VecDeque];
+
+const ACCEPTABLE_TYPES_WITHOUT_ARG: [rustc_span::Symbol; 3] = [sym::BinaryHeap, sym::HashMap, sym::HashSet];
+
+pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, span: Span, arg: Option<&Expr<'_>>) {
+ if let Some(arg) = arg {
+ if match_acceptable_type(cx, recv, &ACCEPTABLE_TYPES_WITH_ARG)
+ && let ExprKind::Path(QPath::Resolved(None, container_path)) = recv.kind
+ && is_range_full(cx, arg, Some(container_path))
+ {
+ suggest(cx, expr, recv, span);
+ }
+ } else if match_acceptable_type(cx, recv, &ACCEPTABLE_TYPES_WITHOUT_ARG) {
+ suggest(cx, expr, recv, span);
+ }
+}
+
+fn match_acceptable_type(cx: &LateContext<'_>, expr: &hir::Expr<'_>, types: &[rustc_span::Symbol]) -> bool {
+ let expr_ty = cx.typeck_results().expr_ty(expr).peel_refs();
+ types.iter().any(|&ty| is_type_diagnostic_item(cx, expr_ty, ty))
+ // String type is a lang item but not a diagnostic item for now so we need a separate check
+ || is_type_lang_item(cx, expr_ty, LangItem::String)
+}
+
+fn suggest(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, span: Span) {
+ if let Some(adt) = cx.typeck_results().expr_ty(recv).ty_adt_def()
+ // Use `opt_item_name` while `String` is not a diagnostic item
+ && let Some(ty_name) = cx.tcx.opt_item_name(adt.did())
+ {
+ span_lint_and_sugg(
+ cx,
+ CLEAR_WITH_DRAIN,
+ span.with_hi(expr.span.hi()),
+ &format!("`drain` used to clear a `{ty_name}`"),
+ "try",
+ "clear()".to_string(),
+ Applicability::MachineApplicable,
+ );
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/methods/clone_on_ref_ptr.rs b/src/tools/clippy/clippy_lints/src/methods/clone_on_ref_ptr.rs
index 355f53532..5e8ad0861 100644
--- a/src/tools/clippy/clippy_lints/src/methods/clone_on_ref_ptr.rs
+++ b/src/tools/clippy/clippy_lints/src/methods/clone_on_ref_ptr.rs
@@ -1,6 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::paths;
-use clippy_utils::source::snippet_with_macro_callsite;
+use clippy_utils::source::snippet_with_context;
use clippy_utils::ty::{is_type_diagnostic_item, match_type};
use rustc_errors::Applicability;
use rustc_hir as hir;
@@ -33,7 +33,9 @@ pub(super) fn check(
return;
};
- let snippet = snippet_with_macro_callsite(cx, receiver.span, "..");
+ // Sometimes unnecessary ::<_> after Rc/Arc/Weak
+ let mut app = Applicability::Unspecified;
+ let snippet = snippet_with_context(cx, receiver.span, expr.span.ctxt(), "..", &mut app).0;
span_lint_and_sugg(
cx,
@@ -42,7 +44,7 @@ pub(super) fn check(
"using `.clone()` on a ref-counted pointer",
"try this",
format!("{caller_type}::<{}>::clone(&{snippet})", subst.type_at(0)),
- Applicability::Unspecified, // Sometimes unnecessary ::<_> after Rc/Arc/Weak
+ app,
);
}
}
diff --git a/src/tools/clippy/clippy_lints/src/methods/expect_fun_call.rs b/src/tools/clippy/clippy_lints/src/methods/expect_fun_call.rs
index a22285058..92d21bb89 100644
--- a/src/tools/clippy/clippy_lints/src/methods/expect_fun_call.rs
+++ b/src/tools/clippy/clippy_lints/src/methods/expect_fun_call.rs
@@ -1,5 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
-use clippy_utils::macros::{root_macro_call_first_node, FormatArgsExpn};
+use clippy_utils::macros::{find_format_args, format_args_inputs_span, root_macro_call_first_node};
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::ty::{is_type_diagnostic_item, is_type_lang_item};
use rustc_errors::Applicability;
@@ -136,18 +136,19 @@ pub(super) fn check<'tcx>(
if !cx.tcx.is_diagnostic_item(sym::format_macro, macro_call.def_id) {
return;
}
- let Some(format_args) = FormatArgsExpn::find_nested(cx, arg_root, macro_call.expn) else { return };
- let span = format_args.inputs_span();
- let sugg = snippet_with_applicability(cx, span, "..", &mut applicability);
- span_lint_and_sugg(
- cx,
- EXPECT_FUN_CALL,
- span_replace_word,
- &format!("use of `{name}` followed by a function call"),
- "try this",
- format!("unwrap_or_else({closure_args} panic!({sugg}))"),
- applicability,
- );
+ find_format_args(cx, arg_root, macro_call.expn, |format_args| {
+ let span = format_args_inputs_span(format_args);
+ let sugg = snippet_with_applicability(cx, span, "..", &mut applicability);
+ span_lint_and_sugg(
+ cx,
+ EXPECT_FUN_CALL,
+ span_replace_word,
+ &format!("use of `{name}` followed by a function call"),
+ "try this",
+ format!("unwrap_or_else({closure_args} panic!({sugg}))"),
+ applicability,
+ );
+ });
return;
}
diff --git a/src/tools/clippy/clippy_lints/src/methods/iter_with_drain.rs b/src/tools/clippy/clippy_lints/src/methods/iter_with_drain.rs
index 3da230e12..f6772c5c6 100644
--- a/src/tools/clippy/clippy_lints/src/methods/iter_with_drain.rs
+++ b/src/tools/clippy/clippy_lints/src/methods/iter_with_drain.rs
@@ -1,7 +1,5 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
-use clippy_utils::higher::Range;
-use clippy_utils::is_integer_const;
-use rustc_ast::ast::RangeLimits;
+use clippy_utils::is_range_full;
use rustc_errors::Applicability;
use rustc_hir::{Expr, ExprKind, QPath};
use rustc_lint::LateContext;
@@ -15,8 +13,8 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, span
&& let Some(adt) = cx.typeck_results().expr_ty(recv).ty_adt_def()
&& let Some(ty_name) = cx.tcx.get_diagnostic_name(adt.did())
&& matches!(ty_name, sym::Vec | sym::VecDeque)
- && let Some(range) = Range::hir(arg)
- && is_full_range(cx, recv, range)
+ && let ExprKind::Path(QPath::Resolved(None, container_path)) = recv.kind
+ && is_range_full(cx, arg, Some(container_path))
{
span_lint_and_sugg(
cx,
@@ -29,19 +27,3 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, recv: &Expr<'_>, span
);
};
}
-
-fn is_full_range(cx: &LateContext<'_>, container: &Expr<'_>, range: Range<'_>) -> bool {
- range.start.map_or(true, |e| is_integer_const(cx, e, 0))
- && range.end.map_or(true, |e| {
- if range.limits == RangeLimits::HalfOpen
- && let ExprKind::Path(QPath::Resolved(None, container_path)) = container.kind
- && let ExprKind::MethodCall(name, self_arg, [], _) = e.kind
- && name.ident.name == sym::len
- && let ExprKind::Path(QPath::Resolved(None, path)) = self_arg.kind
- {
- container_path.res == path.res
- } else {
- false
- }
- })
-}
diff --git a/src/tools/clippy/clippy_lints/src/methods/mod.rs b/src/tools/clippy/clippy_lints/src/methods/mod.rs
index 702df4b28..64bf55ba2 100644
--- a/src/tools/clippy/clippy_lints/src/methods/mod.rs
+++ b/src/tools/clippy/clippy_lints/src/methods/mod.rs
@@ -9,6 +9,7 @@ mod chars_last_cmp;
mod chars_last_cmp_with_unwrap;
mod chars_next_cmp;
mod chars_next_cmp_with_unwrap;
+mod clear_with_drain;
mod clone_on_copy;
mod clone_on_ref_ptr;
mod cloned_instead_of_copied;
@@ -110,7 +111,7 @@ use clippy_utils::ty::{contains_ty_adt_constructor_opaque, implements_trait, is_
use clippy_utils::{contains_return, is_bool, is_trait_method, iter_input_pats, return_ty};
use if_chain::if_chain;
use rustc_hir as hir;
-use rustc_hir::{Expr, ExprKind, TraitItem, TraitItemKind};
+use rustc_hir::{Expr, ExprKind, Node, Stmt, StmtKind, TraitItem, TraitItemKind};
use rustc_hir_analysis::hir_ty_to_ty;
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
@@ -340,8 +341,9 @@ declare_clippy_lint! {
declare_clippy_lint! {
/// ### What it does
- /// Checks for methods with certain name prefixes and which
- /// doesn't match how self is taken. The actual rules are:
+ /// Checks for methods with certain name prefixes or suffixes, and which
+ /// do not adhere to standard conventions regarding how `self` is taken.
+ /// The actual rules are:
///
/// |Prefix |Postfix |`self` taken | `self` type |
/// |-------|------------|-------------------------------|--------------|
@@ -3189,6 +3191,31 @@ declare_clippy_lint! {
"single command line argument that looks like it should be multiple arguments"
}
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for usage of `.drain(..)` for the sole purpose of clearing a container.
+ ///
+ /// ### Why is this bad?
+ /// This creates an unnecessary iterator that is dropped immediately.
+ ///
+ /// Calling `.clear()` also makes the intent clearer.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let mut v = vec![1, 2, 3];
+ /// v.drain(..);
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let mut v = vec![1, 2, 3];
+ /// v.clear();
+ /// ```
+ #[clippy::version = "1.69.0"]
+ pub CLEAR_WITH_DRAIN,
+ nursery,
+ "calling `drain` in order to `clear` a container"
+}
+
pub struct Methods {
avoid_breaking_exported_api: bool,
msrv: Msrv,
@@ -3317,6 +3344,7 @@ impl_lint_pass!(Methods => [
SEEK_TO_START_INSTEAD_OF_REWIND,
NEEDLESS_COLLECT,
SUSPICIOUS_COMMAND_ARG_SPACE,
+ CLEAR_WITH_DRAIN,
]);
/// Extracts a method call name, args, and `Span` of the method name.
@@ -3561,8 +3589,15 @@ impl Methods {
Some(("bytes", recv2, [], _, _)) => bytes_count_to_len::check(cx, expr, recv, recv2),
_ => {},
},
- ("drain", [arg]) => {
- iter_with_drain::check(cx, expr, recv, span, arg);
+ ("drain", ..) => {
+ if let Node::Stmt(Stmt { hir_id: _, kind, .. }) = cx.tcx.hir().get_parent(expr.hir_id)
+ && matches!(kind, StmtKind::Semi(_))
+ && args.len() <= 1
+ {
+ clear_with_drain::check(cx, expr, recv, span, args.first());
+ } else if let [arg] = args {
+ iter_with_drain::check(cx, expr, recv, span, arg);
+ }
},
("ends_with", [arg]) => {
if let ExprKind::MethodCall(.., span) = expr.kind {
diff --git a/src/tools/clippy/clippy_lints/src/methods/option_map_or_none.rs b/src/tools/clippy/clippy_lints/src/methods/option_map_or_none.rs
index 3a23ecc50..41ceef19e 100644
--- a/src/tools/clippy/clippy_lints/src/methods/option_map_or_none.rs
+++ b/src/tools/clippy/clippy_lints/src/methods/option_map_or_none.rs
@@ -6,7 +6,6 @@ use rustc_errors::Applicability;
use rustc_hir as hir;
use rustc_hir::LangItem::{OptionNone, OptionSome};
use rustc_lint::LateContext;
-use rustc_middle::ty::DefIdTree;
use rustc_span::symbol::sym;
use super::OPTION_MAP_OR_NONE;
diff --git a/src/tools/clippy/clippy_lints/src/methods/or_fun_call.rs b/src/tools/clippy/clippy_lints/src/methods/or_fun_call.rs
index 4460f38fc..7ce28ea93 100644
--- a/src/tools/clippy/clippy_lints/src/methods/or_fun_call.rs
+++ b/src/tools/clippy/clippy_lints/src/methods/or_fun_call.rs
@@ -1,6 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::eager_or_lazy::switch_to_lazy_eval;
-use clippy_utils::source::{snippet, snippet_with_macro_callsite};
+use clippy_utils::source::snippet_with_context;
use clippy_utils::ty::{implements_trait, is_type_diagnostic_item};
use clippy_utils::{contains_return, is_trait_item, last_path_segment};
use if_chain::if_chain;
@@ -9,7 +9,6 @@ use rustc_hir as hir;
use rustc_lint::LateContext;
use rustc_span::source_map::Span;
use rustc_span::symbol::{kw, sym, Symbol};
-use std::borrow::Cow;
use super::OR_FUN_CALL;
@@ -111,37 +110,24 @@ pub(super) fn check<'tcx>(
if poss.contains(&name);
then {
+ let ctxt = span.ctxt();
+ let mut app = Applicability::HasPlaceholders;
let sugg = {
let (snippet_span, use_lambda) = match (fn_has_arguments, fun_span) {
(false, Some(fun_span)) => (fun_span, false),
_ => (arg.span, true),
};
- let format_span = |span: Span| {
- let not_macro_argument_snippet = snippet_with_macro_callsite(cx, span, "..");
- let snip = if not_macro_argument_snippet == "vec![]" {
- let macro_expanded_snipped = snippet(cx, snippet_span, "..");
- match macro_expanded_snipped.strip_prefix("$crate::vec::") {
- Some(stripped) => Cow::Owned(stripped.to_owned()),
- None => macro_expanded_snipped,
- }
- } else {
- not_macro_argument_snippet
- };
-
- snip.to_string()
- };
-
- let snip = format_span(snippet_span);
+ let snip = snippet_with_context(cx, snippet_span, ctxt, "..", &mut app).0;
let snip = if use_lambda {
let l_arg = if fn_has_arguments { "_" } else { "" };
format!("|{l_arg}| {snip}")
} else {
- snip
+ snip.into_owned()
};
if let Some(f) = second_arg {
- let f = format_span(f.span);
+ let f = snippet_with_context(cx, f.span, ctxt, "..", &mut app).0;
format!("{snip}, {f}")
} else {
snip
@@ -155,7 +141,7 @@ pub(super) fn check<'tcx>(
&format!("use of `{name}` followed by a function call"),
"try this",
format!("{name}_{suffix}({sugg})"),
- Applicability::HasPlaceholders,
+ app,
);
}
}
diff --git a/src/tools/clippy/clippy_lints/src/methods/unnecessary_sort_by.rs b/src/tools/clippy/clippy_lints/src/methods/unnecessary_sort_by.rs
index 5201da52b..67618f703 100644
--- a/src/tools/clippy/clippy_lints/src/methods/unnecessary_sort_by.rs
+++ b/src/tools/clippy/clippy_lints/src/methods/unnecessary_sort_by.rs
@@ -33,10 +33,6 @@ struct SortByKeyDetection {
/// contains a and the other replaces it with b)
fn mirrored_exprs(a_expr: &Expr<'_>, a_ident: &Ident, b_expr: &Expr<'_>, b_ident: &Ident) -> bool {
match (&a_expr.kind, &b_expr.kind) {
- // Two boxes with mirrored contents
- (ExprKind::Box(left_expr), ExprKind::Box(right_expr)) => {
- mirrored_exprs(left_expr, a_ident, right_expr, b_ident)
- },
// Two arrays with mirrored contents
(ExprKind::Array(left_exprs), ExprKind::Array(right_exprs)) => {
iter::zip(*left_exprs, *right_exprs).all(|(left, right)| mirrored_exprs(left, a_ident, right, b_ident))
diff --git a/src/tools/clippy/clippy_lints/src/methods/unnecessary_to_owned.rs b/src/tools/clippy/clippy_lints/src/methods/unnecessary_to_owned.rs
index df26b36b7..4c4c003ca 100644
--- a/src/tools/clippy/clippy_lints/src/methods/unnecessary_to_owned.rs
+++ b/src/tools/clippy/clippy_lints/src/methods/unnecessary_to_owned.rs
@@ -369,10 +369,10 @@ fn can_change_type<'a>(cx: &LateContext<'a>, mut expr: &'a Expr<'a>, mut ty: Ty<
Node::Item(item) => {
if let ItemKind::Fn(_, _, body_id) = &item.kind
&& let output_ty = return_ty(cx, item.owner_id)
- && Inherited::build(cx.tcx, item.owner_id.def_id).enter(|inherited| {
- let fn_ctxt = FnCtxt::new(inherited, cx.param_env, item.owner_id.def_id);
- fn_ctxt.can_coerce(ty, output_ty)
- }) {
+ && let inherited = Inherited::new(cx.tcx, item.owner_id.def_id)
+ && let fn_ctxt = FnCtxt::new(&inherited, cx.param_env, item.owner_id.def_id)
+ && fn_ctxt.can_coerce(ty, output_ty)
+ {
if has_lifetime(output_ty) && has_lifetime(ty) {
return false;
}
diff --git a/src/tools/clippy/clippy_lints/src/misc.rs b/src/tools/clippy/clippy_lints/src/misc.rs
index 0705029a6..3752b9a94 100644
--- a/src/tools/clippy/clippy_lints/src/misc.rs
+++ b/src/tools/clippy/clippy_lints/src/misc.rs
@@ -1,5 +1,5 @@
use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg, span_lint_hir_and_then};
-use clippy_utils::source::{snippet, snippet_opt};
+use clippy_utils::source::{snippet, snippet_opt, snippet_with_context};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::intravisit::FnKind;
@@ -181,20 +181,17 @@ impl<'tcx> LateLintPass<'tcx> for LintPass {
if let PatKind::Binding(BindingAnnotation(ByRef::Yes, mutabl), .., name, None) = local.pat.kind;
if let Some(init) = local.init;
then {
- // use the macro callsite when the init span (but not the whole local span)
- // comes from an expansion like `vec![1, 2, 3]` in `let ref _ = vec![1, 2, 3];`
- let sugg_init = if init.span.from_expansion() && !local.span.from_expansion() {
- Sugg::hir_with_macro_callsite(cx, init, "..")
- } else {
- Sugg::hir(cx, init, "..")
- };
+ let ctxt = local.span.ctxt();
+ let mut app = Applicability::MachineApplicable;
+ let sugg_init = Sugg::hir_with_context(cx, init, ctxt, "..", &mut app);
let (mutopt, initref) = if mutabl == Mutability::Mut {
("mut ", sugg_init.mut_addr())
} else {
("", sugg_init.addr())
};
let tyopt = if let Some(ty) = local.ty {
- format!(": &{mutopt}{ty}", ty=snippet(cx, ty.span, ".."))
+ let ty_snip = snippet_with_context(cx, ty.span, ctxt, "_", &mut app).0;
+ format!(": &{mutopt}{ty_snip}")
} else {
String::new()
};
@@ -212,7 +209,7 @@ impl<'tcx> LateLintPass<'tcx> for LintPass {
"let {name}{tyopt} = {initref};",
name=snippet(cx, name.span, ".."),
),
- Applicability::MachineApplicable,
+ app,
);
}
);
diff --git a/src/tools/clippy/clippy_lints/src/missing_assert_message.rs b/src/tools/clippy/clippy_lints/src/missing_assert_message.rs
new file mode 100644
index 000000000..2214a568d
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/missing_assert_message.rs
@@ -0,0 +1,82 @@
+use clippy_utils::diagnostics::span_lint_and_help;
+use clippy_utils::macros::{find_assert_args, find_assert_eq_args, root_macro_call_first_node, PanicExpn};
+use clippy_utils::{is_in_cfg_test, is_in_test_function};
+use rustc_hir::Expr;
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::sym;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks assertions without a custom panic message.
+ ///
+ /// ### Why is this bad?
+ /// 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.
+ ///
+ /// ### Known problems
+ /// This lint cannot check the quality of the custom panic messages.
+ /// Hence, you can suppress this lint simply by adding placeholder messages
+ /// like "assertion failed". However, we recommend coming up with good messages
+ /// that provide useful information instead of placeholder messages that
+ /// don't provide any extra information.
+ ///
+ /// ### Example
+ /// ```rust
+ /// # struct Service { ready: bool }
+ /// fn call(service: Service) {
+ /// assert!(service.ready);
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// # struct Service { ready: bool }
+ /// fn call(service: Service) {
+ /// assert!(service.ready, "`service.poll_ready()` must be called first to ensure that service is ready to receive requests");
+ /// }
+ /// ```
+ #[clippy::version = "1.69.0"]
+ pub MISSING_ASSERT_MESSAGE,
+ restriction,
+ "checks assertions without a custom panic message"
+}
+
+declare_lint_pass!(MissingAssertMessage => [MISSING_ASSERT_MESSAGE]);
+
+impl<'tcx> LateLintPass<'tcx> for MissingAssertMessage {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ let Some(macro_call) = root_macro_call_first_node(cx, expr) else { return };
+ let single_argument = match cx.tcx.get_diagnostic_name(macro_call.def_id) {
+ Some(sym::assert_macro | sym::debug_assert_macro) => true,
+ Some(
+ sym::assert_eq_macro | sym::assert_ne_macro | sym::debug_assert_eq_macro | sym::debug_assert_ne_macro,
+ ) => false,
+ _ => return,
+ };
+
+ // This lint would be very noisy in tests, so just ignore if we're in test context
+ if is_in_test_function(cx.tcx, expr.hir_id) || is_in_cfg_test(cx.tcx, expr.hir_id) {
+ return;
+ }
+
+ let panic_expn = if single_argument {
+ let Some((_, panic_expn)) = find_assert_args(cx, expr, macro_call.expn) else { return };
+ panic_expn
+ } else {
+ let Some((_, _, panic_expn)) = find_assert_eq_args(cx, expr, macro_call.expn) else { return };
+ panic_expn
+ };
+
+ if let PanicExpn::Empty = panic_expn {
+ span_lint_and_help(
+ cx,
+ MISSING_ASSERT_MESSAGE,
+ macro_call.span,
+ "assert without any message",
+ None,
+ "consider describing why the failing assert is problematic",
+ );
+ }
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/missing_const_for_fn.rs b/src/tools/clippy/clippy_lints/src/missing_const_for_fn.rs
index 87bd007a2..f1831a304 100644
--- a/src/tools/clippy/clippy_lints/src/missing_const_for_fn.rs
+++ b/src/tools/clippy/clippy_lints/src/missing_const_for_fn.rs
@@ -41,6 +41,7 @@ declare_clippy_lint! {
/// can't be const as it calls a non-const function. Making `a` const and running Clippy again,
/// will suggest to make `b` const, too.
///
+ /// If you are marking a public function with `const`, removing it again will break API compatibility.
/// ### Example
/// ```rust
/// # struct Foo {
diff --git a/src/tools/clippy/clippy_lints/src/missing_doc.rs b/src/tools/clippy/clippy_lints/src/missing_doc.rs
index 9659ca8ce..f2773cad4 100644
--- a/src/tools/clippy/clippy_lints/src/missing_doc.rs
+++ b/src/tools/clippy/clippy_lints/src/missing_doc.rs
@@ -8,12 +8,12 @@
use clippy_utils::attrs::is_doc_hidden;
use clippy_utils::diagnostics::span_lint;
use clippy_utils::is_from_proc_macro;
-use hir::def_id::LocalDefId;
use if_chain::if_chain;
use rustc_ast::ast::{self, MetaItem, MetaItemKind};
use rustc_hir as hir;
+use rustc_hir::def_id::LocalDefId;
use rustc_lint::{LateContext, LateLintPass, LintContext};
-use rustc_middle::ty::{DefIdTree, Visibility};
+use rustc_middle::ty::Visibility;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::def_id::CRATE_DEF_ID;
use rustc_span::source_map::Span;
@@ -21,8 +21,7 @@ use rustc_span::sym;
declare_clippy_lint! {
/// ### What it does
- /// Warns if there is missing doc for any documentable item
- /// (public or private).
+ /// Warns if there is missing doc for any private documentable item
///
/// ### Why is this bad?
/// Doc is good. *rustc* has a `MISSING_DOCS`
@@ -32,7 +31,7 @@ declare_clippy_lint! {
#[clippy::version = "pre 1.29.0"]
pub MISSING_DOCS_IN_PRIVATE_ITEMS,
restriction,
- "detects missing documentation for public and private members"
+ "detects missing documentation for private members"
}
pub struct MissingDoc {
@@ -107,11 +106,14 @@ impl MissingDoc {
if vis == Visibility::Public || vis != Visibility::Restricted(CRATE_DEF_ID.into()) {
return;
}
+ } else if def_id != CRATE_DEF_ID && cx.effective_visibilities.is_exported(def_id) {
+ return;
}
let has_doc = attrs
.iter()
.any(|a| a.doc_str().is_some() || Self::has_include(a.meta()));
+
if !has_doc {
span_lint(
cx,
diff --git a/src/tools/clippy/clippy_lints/src/multiple_unsafe_ops_per_block.rs b/src/tools/clippy/clippy_lints/src/multiple_unsafe_ops_per_block.rs
index 63c575fca..5418616de 100644
--- a/src/tools/clippy/clippy_lints/src/multiple_unsafe_ops_per_block.rs
+++ b/src/tools/clippy/clippy_lints/src/multiple_unsafe_ops_per_block.rs
@@ -11,6 +11,7 @@ use rustc_ast::Mutability;
use rustc_hir as hir;
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::lint::in_external_macro;
+use rustc_middle::ty;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::Span;
@@ -120,33 +121,15 @@ fn collect_unsafe_exprs<'tcx>(
unsafe_ops.push(("raw pointer dereference occurs here", expr.span));
},
- ExprKind::Call(path_expr, _) => match path_expr.kind {
- ExprKind::Path(QPath::Resolved(
- _,
- hir::Path {
- res: Res::Def(kind, def_id),
- ..
- },
- )) if kind.is_fn_like() => {
- let sig = cx.tcx.fn_sig(*def_id);
- if sig.0.unsafety() == Unsafety::Unsafe {
- unsafe_ops.push(("unsafe function call occurs here", expr.span));
- }
- },
-
- ExprKind::Path(QPath::TypeRelative(..)) => {
- if let Some(sig) = cx
- .typeck_results()
- .type_dependent_def_id(path_expr.hir_id)
- .map(|def_id| cx.tcx.fn_sig(def_id))
- {
- if sig.0.unsafety() == Unsafety::Unsafe {
- unsafe_ops.push(("unsafe function call occurs here", expr.span));
- }
- }
- },
-
- _ => {},
+ ExprKind::Call(path_expr, _) => {
+ let sig = match *cx.typeck_results().expr_ty(path_expr).kind() {
+ ty::FnDef(id, _) => cx.tcx.fn_sig(id).skip_binder(),
+ ty::FnPtr(sig) => sig,
+ _ => return Continue(Descend::Yes),
+ };
+ if sig.unsafety() == Unsafety::Unsafe {
+ unsafe_ops.push(("unsafe function call occurs here", expr.span));
+ }
},
ExprKind::MethodCall(..) => {
diff --git a/src/tools/clippy/clippy_lints/src/mut_key.rs b/src/tools/clippy/clippy_lints/src/mut_key.rs
index 8aa814b74..309f67521 100644
--- a/src/tools/clippy/clippy_lints/src/mut_key.rs
+++ b/src/tools/clippy/clippy_lints/src/mut_key.rs
@@ -1,10 +1,11 @@
use clippy_utils::diagnostics::span_lint;
+use clippy_utils::ty::is_interior_mut_ty;
use clippy_utils::{def_path_def_ids, trait_ref_of_method};
use rustc_data_structures::fx::FxHashSet;
use rustc_hir as hir;
use rustc_lint::{LateContext, LateLintPass};
-use rustc_middle::ty::TypeVisitableExt;
-use rustc_middle::ty::{Adt, Array, Ref, Slice, Tuple, Ty};
+use rustc_middle::query::Key;
+use rustc_middle::ty::{Adt, Ty};
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::def_id::LocalDefId;
use rustc_span::source_map::Span;
@@ -153,53 +154,18 @@ impl MutableKeyType {
let is_keyed_type = [sym::HashMap, sym::BTreeMap, sym::HashSet, sym::BTreeSet]
.iter()
.any(|diag_item| cx.tcx.is_diagnostic_item(*diag_item, def.did()));
- if is_keyed_type && self.is_interior_mutable_type(cx, substs.type_at(0)) {
- span_lint(cx, MUTABLE_KEY_TYPE, span, "mutable key type");
+ if !is_keyed_type {
+ return;
}
- }
- }
- /// Determines if a type contains interior mutability which would affect its implementation of
- /// [`Hash`] or [`Ord`].
- fn is_interior_mutable_type<'tcx>(&self, cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
- match *ty.kind() {
- Ref(_, inner_ty, mutbl) => mutbl == hir::Mutability::Mut || self.is_interior_mutable_type(cx, inner_ty),
- Slice(inner_ty) => self.is_interior_mutable_type(cx, inner_ty),
- Array(inner_ty, size) => {
- size.try_eval_target_usize(cx.tcx, cx.param_env)
- .map_or(true, |u| u != 0)
- && self.is_interior_mutable_type(cx, inner_ty)
- },
- Tuple(fields) => fields.iter().any(|ty| self.is_interior_mutable_type(cx, ty)),
- Adt(def, substs) => {
- // Special case for collections in `std` who's impl of `Hash` or `Ord` delegates to
- // that of their type parameters. Note: we don't include `HashSet` and `HashMap`
- // because they have no impl for `Hash` or `Ord`.
- let def_id = def.did();
- let is_std_collection = [
- sym::Option,
- sym::Result,
- sym::LinkedList,
- sym::Vec,
- sym::VecDeque,
- sym::BTreeMap,
- sym::BTreeSet,
- sym::Rc,
- sym::Arc,
- ]
- .iter()
- .any(|diag_item| cx.tcx.is_diagnostic_item(*diag_item, def_id));
- let is_box = Some(def_id) == cx.tcx.lang_items().owned_box();
- if is_std_collection || is_box || self.ignore_mut_def_ids.contains(&def_id) {
- // The type is mutable if any of its type parameters are
- substs.types().any(|ty| self.is_interior_mutable_type(cx, ty))
- } else {
- !ty.has_escaping_bound_vars()
- && cx.tcx.layout_of(cx.param_env.and(ty)).is_ok()
- && !ty.is_freeze(cx.tcx, cx.param_env)
- }
- },
- _ => false,
+ let subst_ty = substs.type_at(0);
+ // Determines if a type contains interior mutability which would affect its implementation of
+ // [`Hash`] or [`Ord`].
+ if is_interior_mut_ty(cx, subst_ty)
+ && !matches!(subst_ty.ty_adt_id(), Some(adt_id) if self.ignore_mut_def_ids.contains(&adt_id))
+ {
+ span_lint(cx, MUTABLE_KEY_TYPE, span, "mutable key type");
+ }
}
}
}
diff --git a/src/tools/clippy/clippy_lints/src/needless_bool.rs b/src/tools/clippy/clippy_lints/src/needless_bool.rs
index a4eec95b3..c87059bf6 100644
--- a/src/tools/clippy/clippy_lints/src/needless_bool.rs
+++ b/src/tools/clippy/clippy_lints/src/needless_bool.rs
@@ -340,18 +340,11 @@ fn suggest_bool_comparison<'a, 'tcx>(
cx: &LateContext<'tcx>,
e: &'tcx Expr<'_>,
expr: &Expr<'_>,
- mut applicability: Applicability,
+ mut app: Applicability,
message: &str,
conv_hint: impl FnOnce(Sugg<'a>) -> Sugg<'a>,
) {
- let hint = if expr.span.from_expansion() {
- if applicability != Applicability::Unspecified {
- applicability = Applicability::MaybeIncorrect;
- }
- Sugg::hir_with_macro_callsite(cx, expr, "..")
- } else {
- Sugg::hir_with_applicability(cx, expr, "..", &mut applicability)
- };
+ let hint = Sugg::hir_with_context(cx, expr, e.span.ctxt(), "..", &mut app);
span_lint_and_sugg(
cx,
BOOL_COMPARISON,
@@ -359,7 +352,7 @@ fn suggest_bool_comparison<'a, 'tcx>(
message,
"try simplifying it as shown",
conv_hint(hint).to_string(),
- applicability,
+ app,
);
}
diff --git a/src/tools/clippy/clippy_lints/src/needless_pass_by_value.rs b/src/tools/clippy/clippy_lints/src/needless_pass_by_value.rs
index 1ab81aee7..0bb1775aa 100644
--- a/src/tools/clippy/clippy_lints/src/needless_pass_by_value.rs
+++ b/src/tools/clippy/clippy_lints/src/needless_pass_by_value.rs
@@ -122,11 +122,11 @@ impl<'tcx> LateLintPass<'tcx> for NeedlessPassByValue {
let sized_trait = need!(cx.tcx.lang_items().sized_trait());
- let preds = traits::elaborate_predicates(cx.tcx, cx.param_env.caller_bounds().iter())
+ let preds = traits::elaborate(cx.tcx, cx.param_env.caller_bounds().iter())
.filter(|p| !p.is_global())
- .filter_map(|obligation| {
+ .filter_map(|pred| {
// Note that we do not want to deal with qualified predicates here.
- match obligation.predicate.kind().no_bound_vars() {
+ match pred.kind().no_bound_vars() {
Some(ty::PredicateKind::Clause(ty::Clause::Trait(pred))) if pred.def_id() != sized_trait => {
Some(pred)
},
diff --git a/src/tools/clippy/clippy_lints/src/needless_question_mark.rs b/src/tools/clippy/clippy_lints/src/needless_question_mark.rs
index 97c8cfbd3..e2a7ba02a 100644
--- a/src/tools/clippy/clippy_lints/src/needless_question_mark.rs
+++ b/src/tools/clippy/clippy_lints/src/needless_question_mark.rs
@@ -6,7 +6,6 @@ use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::{AsyncGeneratorKind, Block, Body, Expr, ExprKind, GeneratorKind, LangItem, MatchSource, QPath};
use rustc_lint::{LateContext, LateLintPass};
-use rustc_middle::ty::DefIdTree;
use rustc_session::{declare_lint_pass, declare_tool_lint};
declare_clippy_lint! {
diff --git a/src/tools/clippy/clippy_lints/src/neg_multiply.rs b/src/tools/clippy/clippy_lints/src/neg_multiply.rs
index fb9a4abd0..ed3e2c6e7 100644
--- a/src/tools/clippy/clippy_lints/src/neg_multiply.rs
+++ b/src/tools/clippy/clippy_lints/src/neg_multiply.rs
@@ -1,6 +1,6 @@
use clippy_utils::consts::{self, Constant};
use clippy_utils::diagnostics::span_lint_and_sugg;
-use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::source::snippet_with_context;
use clippy_utils::sugg::has_enclosing_paren;
use if_chain::if_chain;
use rustc_ast::util::parser::PREC_PREFIX;
@@ -60,8 +60,8 @@ fn check_mul(cx: &LateContext<'_>, span: Span, lit: &Expr<'_>, exp: &Expr<'_>) {
then {
let mut applicability = Applicability::MachineApplicable;
- let snip = snippet_with_applicability(cx, exp.span, "..", &mut applicability);
- let suggestion = if exp.precedence().order() < PREC_PREFIX && !has_enclosing_paren(&snip) {
+ let (snip, from_macro) = snippet_with_context(cx, exp.span, span.ctxt(), "..", &mut applicability);
+ let suggestion = if !from_macro && exp.precedence().order() < PREC_PREFIX && !has_enclosing_paren(&snip) {
format!("-({snip})")
} else {
format!("-{snip}")
diff --git a/src/tools/clippy/clippy_lints/src/no_effect.rs b/src/tools/clippy/clippy_lints/src/no_effect.rs
index 79c1ae486..e3712190e 100644
--- a/src/tools/clippy/clippy_lints/src/no_effect.rs
+++ b/src/tools/clippy/clippy_lints/src/no_effect.rs
@@ -127,8 +127,7 @@ fn has_no_effect(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
| ExprKind::Type(inner, _)
| ExprKind::Unary(_, inner)
| ExprKind::Field(inner, _)
- | ExprKind::AddrOf(_, _, inner)
- | ExprKind::Box(inner) => has_no_effect(cx, inner),
+ | ExprKind::AddrOf(_, _, inner) => has_no_effect(cx, inner),
ExprKind::Struct(_, fields, ref base) => {
!has_drop(cx, cx.typeck_results().expr_ty(expr))
&& fields.iter().all(|field| has_no_effect(cx, field.expr))
@@ -234,8 +233,7 @@ fn reduce_expression<'a>(cx: &LateContext<'_>, expr: &'a Expr<'a>) -> Option<Vec
| ExprKind::Type(inner, _)
| ExprKind::Unary(_, inner)
| ExprKind::Field(inner, _)
- | ExprKind::AddrOf(_, _, inner)
- | ExprKind::Box(inner) => reduce_expression(cx, inner).or_else(|| Some(vec![inner])),
+ | ExprKind::AddrOf(_, _, inner) => reduce_expression(cx, inner).or_else(|| Some(vec![inner])),
ExprKind::Struct(_, fields, ref base) => {
if has_drop(cx, cx.typeck_results().expr_ty(expr)) {
None
diff --git a/src/tools/clippy/clippy_lints/src/no_mangle_with_rust_abi.rs b/src/tools/clippy/clippy_lints/src/no_mangle_with_rust_abi.rs
index bc64ccb29..8fd9ae351 100644
--- a/src/tools/clippy/clippy_lints/src/no_mangle_with_rust_abi.rs
+++ b/src/tools/clippy/clippy_lints/src/no_mangle_with_rust_abi.rs
@@ -1,9 +1,10 @@
-use clippy_utils::diagnostics::span_lint_and_sugg;
+use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet_with_applicability;
use rustc_errors::Applicability;
use rustc_hir::{Item, ItemKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::{BytePos, Pos};
use rustc_target::spec::abi::Abi;
declare_clippy_lint! {
@@ -38,25 +39,28 @@ impl<'tcx> LateLintPass<'tcx> for NoMangleWithRustAbi {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'tcx>) {
if let ItemKind::Fn(fn_sig, _, _) = &item.kind {
let attrs = cx.tcx.hir().attrs(item.hir_id());
- let mut applicability = Applicability::MachineApplicable;
- let snippet = snippet_with_applicability(cx, fn_sig.span, "..", &mut applicability);
+ let mut app = Applicability::MaybeIncorrect;
+ let snippet = snippet_with_applicability(cx, fn_sig.span, "..", &mut app);
for attr in attrs {
if let Some(ident) = attr.ident()
&& ident.name == rustc_span::sym::no_mangle
&& fn_sig.header.abi == Abi::Rust
- && !snippet.contains("extern") {
+ && let Some((fn_attrs, _)) = snippet.split_once("fn")
+ && !fn_attrs.contains("extern")
+ {
+ let sugg_span = fn_sig.span
+ .with_lo(fn_sig.span.lo() + BytePos::from_usize(fn_attrs.len()))
+ .shrink_to_lo();
- let suggestion = snippet.split_once("fn")
- .map_or(String::new(), |(first, second)| format!(r#"{first}extern "C" fn{second}"#));
-
- span_lint_and_sugg(
+ span_lint_and_then(
cx,
NO_MANGLE_WITH_RUST_ABI,
fn_sig.span,
- "attribute #[no_mangle] set on a Rust ABI function",
- "try",
- suggestion,
- applicability
+ "`#[no_mangle]` set on a function with the default (`Rust`) ABI",
+ |diag| {
+ diag.span_suggestion(sugg_span, "set an ABI", "extern \"C\" ", app)
+ .span_suggestion(sugg_span, "or explicitly set the default", "extern \"Rust\" ", app);
+ },
);
}
}
diff --git a/src/tools/clippy/clippy_lints/src/non_octal_unix_permissions.rs b/src/tools/clippy/clippy_lints/src/non_octal_unix_permissions.rs
index 2ecb04874..e1de494eb 100644
--- a/src/tools/clippy/clippy_lints/src/non_octal_unix_permissions.rs
+++ b/src/tools/clippy/clippy_lints/src/non_octal_unix_permissions.rs
@@ -53,6 +53,7 @@ impl<'tcx> LateLintPass<'tcx> for NonOctalUnixPermissions {
|| is_type_diagnostic_item(cx, obj_ty, sym::DirBuilder)))
|| (path.ident.name == sym!(set_mode) && match_type(cx, obj_ty, &paths::PERMISSIONS));
if let ExprKind::Lit(_) = param.kind;
+ if param.span.ctxt() == expr.span.ctxt();
then {
let Some(snip) = snippet_opt(cx, param.span) else {
@@ -71,6 +72,7 @@ impl<'tcx> LateLintPass<'tcx> for NonOctalUnixPermissions {
if let Some(def_id) = cx.qpath_res(path, func.hir_id).opt_def_id();
if match_def_path(cx, def_id, &paths::PERMISSIONS_FROM_MODE);
if let ExprKind::Lit(_) = param.kind;
+ if param.span.ctxt() == expr.span.ctxt();
if let Some(snip) = snippet_opt(cx, param.span);
if !snip.starts_with("0o");
then {
diff --git a/src/tools/clippy/clippy_lints/src/operators/arithmetic_side_effects.rs b/src/tools/clippy/clippy_lints/src/operators/arithmetic_side_effects.rs
index 87a8a2ed1..e57137356 100644
--- a/src/tools/clippy/clippy_lints/src/operators/arithmetic_side_effects.rs
+++ b/src/tools/clippy/clippy_lints/src/operators/arithmetic_side_effects.rs
@@ -143,6 +143,10 @@ impl ArithmeticSideEffects {
return;
}
let has_valid_op = if Self::is_integral(lhs_ty) && Self::is_integral(rhs_ty) {
+ if let hir::BinOpKind::Shl | hir::BinOpKind::Shr = op.node {
+ // At least for integers, shifts are already handled by the CTFE
+ return;
+ }
let (actual_lhs, lhs_ref_counter) = peel_hir_expr_refs(lhs);
let (actual_rhs, rhs_ref_counter) = peel_hir_expr_refs(rhs);
match (
@@ -150,11 +154,22 @@ impl ArithmeticSideEffects {
Self::literal_integer(cx, actual_rhs),
) {
(None, None) => false,
- (None, Some(n)) | (Some(n), None) => match (&op.node, n) {
- (hir::BinOpKind::Div | hir::BinOpKind::Rem, 0) => false,
+ (None, Some(n)) => match (&op.node, n) {
+ // Division and module are always valid if applied to non-zero integers
+ (hir::BinOpKind::Div | hir::BinOpKind::Rem, local_n) if local_n != 0 => true,
+ // Adding or subtracting zeros is always a no-op
+ (hir::BinOpKind::Add | hir::BinOpKind::Sub, 0)
+ // Multiplication by 1 or 0 will never overflow
+ | (hir::BinOpKind::Mul, 0 | 1)
+ => true,
+ _ => false,
+ },
+ (Some(n), None) => match (&op.node, n) {
+ // Adding or subtracting zeros is always a no-op
(hir::BinOpKind::Add | hir::BinOpKind::Sub, 0)
- | (hir::BinOpKind::Div | hir::BinOpKind::Rem, _)
- | (hir::BinOpKind::Mul, 0 | 1) => true,
+ // Multiplication by 1 or 0 will never overflow
+ | (hir::BinOpKind::Mul, 0 | 1)
+ => true,
_ => false,
},
(Some(_), Some(_)) => {
diff --git a/src/tools/clippy/clippy_lints/src/option_if_let_else.rs b/src/tools/clippy/clippy_lints/src/option_if_let_else.rs
index c5ea09590..bbbcda069 100644
--- a/src/tools/clippy/clippy_lints/src/option_if_let_else.rs
+++ b/src/tools/clippy/clippy_lints/src/option_if_let_else.rs
@@ -12,6 +12,7 @@ use rustc_hir::{
};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::SyntaxContext;
declare_clippy_lint! {
/// ### What it does
@@ -95,10 +96,10 @@ struct OptionOccurrence {
none_expr: String,
}
-fn format_option_in_sugg(cx: &LateContext<'_>, cond_expr: &Expr<'_>, as_ref: bool, as_mut: bool) -> String {
+fn format_option_in_sugg(cond_sugg: Sugg<'_>, as_ref: bool, as_mut: bool) -> String {
format!(
"{}{}",
- Sugg::hir_with_macro_callsite(cx, cond_expr, "..").maybe_par(),
+ cond_sugg.maybe_par(),
if as_mut {
".as_mut()"
} else if as_ref {
@@ -111,6 +112,7 @@ fn format_option_in_sugg(cx: &LateContext<'_>, cond_expr: &Expr<'_>, as_ref: boo
fn try_get_option_occurrence<'tcx>(
cx: &LateContext<'tcx>,
+ ctxt: SyntaxContext,
pat: &Pat<'tcx>,
expr: &Expr<'_>,
if_then: &'tcx Expr<'_>,
@@ -160,11 +162,23 @@ fn try_get_option_occurrence<'tcx>(
}
}
+ let mut app = Applicability::Unspecified;
return Some(OptionOccurrence {
- option: format_option_in_sugg(cx, cond_expr, as_ref, as_mut),
+ option: format_option_in_sugg(
+ Sugg::hir_with_context(cx, cond_expr, ctxt, "..", &mut app),
+ as_ref,
+ as_mut,
+ ),
method_sugg: method_sugg.to_string(),
- some_expr: format!("|{capture_mut}{capture_name}| {}", Sugg::hir_with_macro_callsite(cx, some_body, "..")),
- none_expr: format!("{}{}", if method_sugg == "map_or" { "" } else { "|| " }, Sugg::hir_with_macro_callsite(cx, none_body, "..")),
+ some_expr: format!(
+ "|{capture_mut}{capture_name}| {}",
+ Sugg::hir_with_context(cx, some_body, ctxt, "..", &mut app),
+ ),
+ none_expr: format!(
+ "{}{}",
+ if method_sugg == "map_or" { "" } else { "|| " },
+ Sugg::hir_with_context(cx, none_body, ctxt, "..", &mut app),
+ ),
});
}
}
@@ -194,7 +208,7 @@ fn detect_option_if_let_else<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) ->
}) = higher::IfLet::hir(cx, expr)
{
if !is_else_clause(cx.tcx, expr) {
- return try_get_option_occurrence(cx, let_pat, let_expr, if_then, if_else);
+ return try_get_option_occurrence(cx, expr.span.ctxt(), let_pat, let_expr, if_then, if_else);
}
}
None
@@ -203,7 +217,7 @@ fn detect_option_if_let_else<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) ->
fn detect_option_match<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> Option<OptionOccurrence> {
if let ExprKind::Match(ex, arms, MatchSource::Normal) = expr.kind {
if let Some((let_pat, if_then, if_else)) = try_convert_match(cx, arms) {
- return try_get_option_occurrence(cx, let_pat, ex, if_then, if_else);
+ return try_get_option_occurrence(cx, expr.span.ctxt(), let_pat, ex, if_then, if_else);
}
}
None
diff --git a/src/tools/clippy/clippy_lints/src/partialeq_ne_impl.rs b/src/tools/clippy/clippy_lints/src/partialeq_ne_impl.rs
index 5aa3c6f2f..a8c4823fe 100644
--- a/src/tools/clippy/clippy_lints/src/partialeq_ne_impl.rs
+++ b/src/tools/clippy/clippy_lints/src/partialeq_ne_impl.rs
@@ -36,7 +36,7 @@ impl<'tcx> LateLintPass<'tcx> for PartialEqNeImpl {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
if_chain! {
if let ItemKind::Impl(Impl { of_trait: Some(ref trait_ref), items: impl_items, .. }) = item.kind;
- if !cx.tcx.has_attr(item.owner_id.to_def_id(), sym::automatically_derived);
+ if !cx.tcx.has_attr(item.owner_id, sym::automatically_derived);
if let Some(eq_trait) = cx.tcx.lang_items().eq_trait();
if trait_ref.path.res.def_id() == eq_trait;
then {
diff --git a/src/tools/clippy/clippy_lints/src/permissions_set_readonly_false.rs b/src/tools/clippy/clippy_lints/src/permissions_set_readonly_false.rs
index e7095ec19..664d44d65 100644
--- a/src/tools/clippy/clippy_lints/src/permissions_set_readonly_false.rs
+++ b/src/tools/clippy/clippy_lints/src/permissions_set_readonly_false.rs
@@ -21,7 +21,7 @@ declare_clippy_lint! {
/// let mut permissions = metadata.permissions();
/// permissions.set_readonly(false);
/// ```
- #[clippy::version = "1.66.0"]
+ #[clippy::version = "1.68.0"]
pub PERMISSIONS_SET_READONLY_FALSE,
suspicious,
"Checks for calls to `std::fs::Permissions.set_readonly` with argument `false`"
diff --git a/src/tools/clippy/clippy_lints/src/redundant_async_block.rs b/src/tools/clippy/clippy_lints/src/redundant_async_block.rs
new file mode 100644
index 000000000..a0f831764
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/redundant_async_block.rs
@@ -0,0 +1,108 @@
+use std::ops::ControlFlow;
+
+use clippy_utils::{
+ diagnostics::span_lint_and_sugg,
+ peel_blocks,
+ source::{snippet, walk_span_to_context},
+ visitors::for_each_expr,
+};
+use rustc_errors::Applicability;
+use rustc_hir::{AsyncGeneratorKind, Closure, Expr, ExprKind, GeneratorKind, MatchSource};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_middle::{lint::in_external_macro, ty::UpvarCapture};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for `async` block that only returns `await` on a future.
+ ///
+ /// ### Why is this bad?
+ /// It is simpler and more efficient to use the future directly.
+ ///
+ /// ### Example
+ /// ```rust
+ /// let f = async {
+ /// 1 + 2
+ /// };
+ /// let fut = async {
+ /// f.await
+ /// };
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// let f = async {
+ /// 1 + 2
+ /// };
+ /// let fut = f;
+ /// ```
+ #[clippy::version = "1.69.0"]
+ pub REDUNDANT_ASYNC_BLOCK,
+ complexity,
+ "`async { future.await }` can be replaced by `future`"
+}
+declare_lint_pass!(RedundantAsyncBlock => [REDUNDANT_ASYNC_BLOCK]);
+
+impl<'tcx> LateLintPass<'tcx> for RedundantAsyncBlock {
+ fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
+ let span = expr.span;
+ if !in_external_macro(cx.tcx.sess, span) &&
+ let Some(body_expr) = desugar_async_block(cx, expr) &&
+ let Some(expr) = desugar_await(peel_blocks(body_expr)) &&
+ // The await prefix must not come from a macro as its content could change in the future.
+ expr.span.ctxt() == body_expr.span.ctxt() &&
+ // An async block does not have immediate side-effects from a `.await` point-of-view.
+ (!expr.can_have_side_effects() || desugar_async_block(cx, expr).is_some()) &&
+ let Some(shortened_span) = walk_span_to_context(expr.span, span.ctxt())
+ {
+ span_lint_and_sugg(
+ cx,
+ REDUNDANT_ASYNC_BLOCK,
+ span,
+ "this async expression only awaits a single future",
+ "you can reduce it to",
+ snippet(cx, shortened_span, "..").into_owned(),
+ Applicability::MachineApplicable,
+ );
+ }
+ }
+}
+
+/// If `expr` is a desugared `async` block, return the original expression if it does not capture
+/// any variable by ref.
+fn desugar_async_block<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
+ if let ExprKind::Closure(Closure { body, def_id, .. }) = expr.kind &&
+ let body = cx.tcx.hir().body(*body) &&
+ matches!(body.generator_kind, Some(GeneratorKind::Async(AsyncGeneratorKind::Block)))
+ {
+ cx
+ .typeck_results()
+ .closure_min_captures
+ .get(def_id)
+ .map_or(true, |m| {
+ m.values().all(|places| {
+ places
+ .iter()
+ .all(|place| matches!(place.info.capture_kind, UpvarCapture::ByValue))
+ })
+ })
+ .then_some(body.value)
+ } else {
+ None
+ }
+}
+
+/// If `expr` is a desugared `.await`, return the original expression if it does not come from a
+/// macro expansion.
+fn desugar_await<'tcx>(expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> {
+ if let ExprKind::Match(match_value, _, MatchSource::AwaitDesugar) = expr.kind &&
+ let ExprKind::Call(_, [into_future_arg]) = match_value.kind &&
+ let ctxt = expr.span.ctxt() &&
+ for_each_expr(into_future_arg, |e|
+ walk_span_to_context(e.span, ctxt)
+ .map_or(ControlFlow::Break(()), |_| ControlFlow::Continue(()))).is_none()
+ {
+ Some(into_future_arg)
+ } else {
+ None
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/redundant_static_lifetimes.rs b/src/tools/clippy/clippy_lints/src/redundant_static_lifetimes.rs
index 44bf824aa..038dfe8e4 100644
--- a/src/tools/clippy/clippy_lints/src/redundant_static_lifetimes.rs
+++ b/src/tools/clippy/clippy_lints/src/redundant_static_lifetimes.rs
@@ -1,7 +1,7 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::snippet;
-use rustc_ast::ast::{Item, ItemKind, Ty, TyKind};
+use rustc_ast::ast::{ConstItem, Item, ItemKind, StaticItem, Ty, TyKind};
use rustc_errors::Applicability;
use rustc_lint::{EarlyContext, EarlyLintPass};
use rustc_session::{declare_tool_lint, impl_lint_pass};
@@ -100,13 +100,13 @@ impl EarlyLintPass for RedundantStaticLifetimes {
}
if !item.span.from_expansion() {
- if let ItemKind::Const(_, ref var_type, _) = item.kind {
+ if let ItemKind::Const(box ConstItem { ty: ref var_type, .. }) = item.kind {
Self::visit_type(var_type, cx, "constants have by default a `'static` lifetime");
// Don't check associated consts because `'static` cannot be elided on those (issue
// #2438)
}
- if let ItemKind::Static(ref var_type, _, _) = item.kind {
+ if let ItemKind::Static(box StaticItem { ty: ref var_type, .. }) = item.kind {
Self::visit_type(var_type, cx, "statics have by default a `'static` lifetime");
}
}
diff --git a/src/tools/clippy/clippy_lints/src/ref_option_ref.rs b/src/tools/clippy/clippy_lints/src/ref_option_ref.rs
index 448a32b77..c984a8286 100644
--- a/src/tools/clippy/clippy_lints/src/ref_option_ref.rs
+++ b/src/tools/clippy/clippy_lints/src/ref_option_ref.rs
@@ -3,7 +3,7 @@ use clippy_utils::last_path_segment;
use clippy_utils::source::snippet;
use if_chain::if_chain;
use rustc_errors::Applicability;
-use rustc_hir::{GenericArg, Mutability, Ty, TyKind};
+use rustc_hir::{GenericArg, GenericArgsParentheses, Mutability, Ty, TyKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::symbol::sym;
@@ -47,7 +47,7 @@ impl<'tcx> LateLintPass<'tcx> for RefOptionRef {
if cx.tcx.is_diagnostic_item(sym::Option, def_id);
if let Some(params) = last_path_segment(qpath).args ;
- if !params.parenthesized;
+ if params.parenthesized == GenericArgsParentheses::No;
if let Some(inner_ty) = params.args.iter().find_map(|arg| match arg {
GenericArg::Type(inner_ty) => Some(inner_ty),
_ => None,
diff --git a/src/tools/clippy/clippy_lints/src/returns.rs b/src/tools/clippy/clippy_lints/src/returns.rs
index f0d7dd23a..df126d761 100644
--- a/src/tools/clippy/clippy_lints/src/returns.rs
+++ b/src/tools/clippy/clippy_lints/src/returns.rs
@@ -9,7 +9,7 @@ use rustc_hir::intravisit::FnKind;
use rustc_hir::{Block, Body, Expr, ExprKind, FnDecl, LangItem, MatchSource, PatKind, QPath, StmtKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
-use rustc_middle::ty::subst::GenericArgKind;
+use rustc_middle::ty::{self, subst::GenericArgKind, Ty};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::def_id::LocalDefId;
use rustc_span::source_map::Span;
@@ -175,7 +175,7 @@ impl<'tcx> LateLintPass<'tcx> for Return {
} else {
RetReplacement::Empty
};
- check_final_expr(cx, body.value, vec![], replacement);
+ check_final_expr(cx, body.value, vec![], replacement, None);
},
FnKind::ItemFn(..) | FnKind::Method(..) => {
check_block_return(cx, &body.value.kind, sp, vec![]);
@@ -188,11 +188,11 @@ impl<'tcx> LateLintPass<'tcx> for Return {
fn check_block_return<'tcx>(cx: &LateContext<'tcx>, expr_kind: &ExprKind<'tcx>, sp: Span, mut semi_spans: Vec<Span>) {
if let ExprKind::Block(block, _) = expr_kind {
if let Some(block_expr) = block.expr {
- check_final_expr(cx, block_expr, semi_spans, RetReplacement::Empty);
+ check_final_expr(cx, block_expr, semi_spans, RetReplacement::Empty, None);
} else if let Some(stmt) = block.stmts.iter().last() {
match stmt.kind {
StmtKind::Expr(expr) => {
- check_final_expr(cx, expr, semi_spans, RetReplacement::Empty);
+ check_final_expr(cx, expr, semi_spans, RetReplacement::Empty, None);
},
StmtKind::Semi(semi_expr) => {
// Remove ending semicolons and any whitespace ' ' in between.
@@ -202,7 +202,7 @@ fn check_block_return<'tcx>(cx: &LateContext<'tcx>, expr_kind: &ExprKind<'tcx>,
span_find_starting_semi(cx.sess().source_map(), semi_span.with_hi(sp.hi()));
semi_spans.push(semi_span_to_remove);
}
- check_final_expr(cx, semi_expr, semi_spans, RetReplacement::Empty);
+ check_final_expr(cx, semi_expr, semi_spans, RetReplacement::Empty, None);
},
_ => (),
}
@@ -216,6 +216,7 @@ fn check_final_expr<'tcx>(
semi_spans: Vec<Span>, /* containing all the places where we would need to remove semicolons if finding an
* needless return */
replacement: RetReplacement<'tcx>,
+ match_ty_opt: Option<Ty<'_>>,
) {
let peeled_drop_expr = expr.peel_drop_temps();
match &peeled_drop_expr.kind {
@@ -244,7 +245,22 @@ fn check_final_expr<'tcx>(
RetReplacement::Expr(snippet, applicability)
}
} else {
- replacement
+ match match_ty_opt {
+ Some(match_ty) => {
+ match match_ty.kind() {
+ // If the code got till here with
+ // tuple not getting detected before it,
+ // then we are sure it's going to be Unit
+ // type
+ ty::Tuple(_) => RetReplacement::Unit,
+ // We don't want to anything in this case
+ // cause we can't predict what the user would
+ // want here
+ _ => return,
+ }
+ },
+ None => replacement,
+ }
};
if !cx.tcx.hir().attrs(expr.hir_id).is_empty() {
@@ -268,8 +284,9 @@ fn check_final_expr<'tcx>(
// note, if without else is going to be a type checking error anyways
// (except for unit type functions) so we don't match it
ExprKind::Match(_, arms, MatchSource::Normal) => {
+ let match_ty = cx.typeck_results().expr_ty(peeled_drop_expr);
for arm in arms.iter() {
- check_final_expr(cx, arm.body, semi_spans.clone(), RetReplacement::Unit);
+ check_final_expr(cx, arm.body, semi_spans.clone(), RetReplacement::Unit, Some(match_ty));
}
},
// if it's a whole block, check it
@@ -293,6 +310,7 @@ fn emit_return_lint(cx: &LateContext<'_>, ret_span: Span, semi_spans: Vec<Span>,
if ret_span.from_expansion() {
return;
}
+
let applicability = replacement.applicability().unwrap_or(Applicability::MachineApplicable);
let return_replacement = replacement.to_string();
let sugg_help = replacement.sugg_help();
diff --git a/src/tools/clippy/clippy_lints/src/semicolon_if_nothing_returned.rs b/src/tools/clippy/clippy_lints/src/semicolon_if_nothing_returned.rs
index 66638eed9..355f907e2 100644
--- a/src/tools/clippy/clippy_lints/src/semicolon_if_nothing_returned.rs
+++ b/src/tools/clippy/clippy_lints/src/semicolon_if_nothing_returned.rs
@@ -1,7 +1,6 @@
use crate::rustc_lint::LintContext;
use clippy_utils::diagnostics::span_lint_and_sugg;
-use clippy_utils::source::snippet_with_macro_callsite;
-use clippy_utils::sugg;
+use clippy_utils::source::snippet_with_context;
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::{Block, ExprKind};
@@ -44,7 +43,8 @@ impl<'tcx> LateLintPass<'tcx> for SemicolonIfNothingReturned {
if let Some(expr) = block.expr;
let t_expr = cx.typeck_results().expr_ty(expr);
if t_expr.is_unit();
- if let snippet = snippet_with_macro_callsite(cx, expr.span, "}");
+ let mut app = Applicability::MaybeIncorrect;
+ if let snippet = snippet_with_context(cx, expr.span, block.span.ctxt(), "}", &mut app).0;
if !snippet.ends_with('}') && !snippet.ends_with(';');
if cx.sess().source_map().is_multiline(block.span);
then {
@@ -52,17 +52,14 @@ impl<'tcx> LateLintPass<'tcx> for SemicolonIfNothingReturned {
if let ExprKind::DropTemps(..) = &expr.kind {
return;
}
-
- let sugg = sugg::Sugg::hir_with_macro_callsite(cx, expr, "..");
- let suggestion = format!("{sugg};");
span_lint_and_sugg(
cx,
SEMICOLON_IF_NOTHING_RETURNED,
expr.span.source_callsite(),
"consider adding a `;` to the last statement for consistent formatting",
"add a `;` here",
- suggestion,
- Applicability::MaybeIncorrect,
+ format!("{snippet};"),
+ app,
);
}
}
diff --git a/src/tools/clippy/clippy_lints/src/shadow.rs b/src/tools/clippy/clippy_lints/src/shadow.rs
index 87f966ced..ae7d19624 100644
--- a/src/tools/clippy/clippy_lints/src/shadow.rs
+++ b/src/tools/clippy/clippy_lints/src/shadow.rs
@@ -213,8 +213,7 @@ fn is_self_shadow(cx: &LateContext<'_>, pat: &Pat<'_>, mut expr: &Expr<'_>, hir_
}
loop {
expr = match expr.kind {
- ExprKind::Box(e)
- | ExprKind::AddrOf(_, _, e)
+ ExprKind::AddrOf(_, _, e)
| ExprKind::Block(
&Block {
stmts: [],
diff --git a/src/tools/clippy/clippy_lints/src/significant_drop_tightening.rs b/src/tools/clippy/clippy_lints/src/significant_drop_tightening.rs
index c3e99aa00..869358fb1 100644
--- a/src/tools/clippy/clippy_lints/src/significant_drop_tightening.rs
+++ b/src/tools/clippy/clippy_lints/src/significant_drop_tightening.rs
@@ -404,7 +404,6 @@ impl<'cx, 'sdt, 'tcx> Visitor<'tcx> for SigDropFinder<'cx, 'sdt, 'tcx> {
| hir::ExprKind::Assign(..)
| hir::ExprKind::AssignOp(..)
| hir::ExprKind::Binary(..)
- | hir::ExprKind::Box(..)
| hir::ExprKind::Call(..)
| hir::ExprKind::Field(..)
| hir::ExprKind::If(..)
diff --git a/src/tools/clippy/clippy_lints/src/single_component_path_imports.rs b/src/tools/clippy/clippy_lints/src/single_component_path_imports.rs
index d46f6a635..5743dd21c 100644
--- a/src/tools/clippy/clippy_lints/src/single_component_path_imports.rs
+++ b/src/tools/clippy/clippy_lints/src/single_component_path_imports.rs
@@ -1,6 +1,7 @@
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg};
use rustc_ast::node_id::{NodeId, NodeMap};
-use rustc_ast::{ptr::P, Crate, Item, ItemKind, MacroDef, ModKind, UseTreeKind};
+use rustc_ast::visit::{walk_expr, Visitor};
+use rustc_ast::{ptr::P, Crate, Expr, ExprKind, Item, ItemKind, MacroDef, ModKind, Ty, TyKind, UseTreeKind};
use rustc_errors::Applicability;
use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
use rustc_session::{declare_tool_lint, impl_lint_pass};
@@ -55,7 +56,7 @@ impl EarlyLintPass for SingleComponentPathImports {
return;
}
- self.check_mod(cx, &krate.items);
+ self.check_mod(&krate.items);
}
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
@@ -84,8 +85,43 @@ impl EarlyLintPass for SingleComponentPathImports {
}
}
+#[derive(Default)]
+struct ImportUsageVisitor {
+ // keep track of imports reused with `self` keyword, such as `self::std` in the example below.
+ // Removing the `use std;` would make this a compile error (#10549)
+ // ```
+ // use std;
+ //
+ // fn main() {
+ // let _ = self::std::io::stdout();
+ // }
+ // ```
+ imports_referenced_with_self: Vec<Symbol>,
+}
+
+impl<'tcx> Visitor<'tcx> for ImportUsageVisitor {
+ fn visit_expr(&mut self, expr: &Expr) {
+ if let ExprKind::Path(_, path) = &expr.kind
+ && path.segments.len() > 1
+ && path.segments[0].ident.name == kw::SelfLower
+ {
+ self.imports_referenced_with_self.push(path.segments[1].ident.name);
+ }
+ walk_expr(self, expr);
+ }
+
+ fn visit_ty(&mut self, ty: &Ty) {
+ if let TyKind::Path(_, path) = &ty.kind
+ && path.segments.len() > 1
+ && path.segments[0].ident.name == kw::SelfLower
+ {
+ self.imports_referenced_with_self.push(path.segments[1].ident.name);
+ }
+ }
+}
+
impl SingleComponentPathImports {
- fn check_mod(&mut self, cx: &EarlyContext<'_>, items: &[P<Item>]) {
+ fn check_mod(&mut self, items: &[P<Item>]) {
// keep track of imports reused with `self` keyword, such as `self::crypto_hash` in the example
// below. Removing the `use crypto_hash;` would make this a compile error
// ```
@@ -108,18 +144,16 @@ impl SingleComponentPathImports {
// ```
let mut macros = Vec::new();
+ let mut import_usage_visitor = ImportUsageVisitor::default();
for item in items {
- self.track_uses(
- cx,
- item,
- &mut imports_reused_with_self,
- &mut single_use_usages,
- &mut macros,
- );
+ self.track_uses(item, &mut imports_reused_with_self, &mut single_use_usages, &mut macros);
+ import_usage_visitor.visit_item(item);
}
for usage in single_use_usages {
- if !imports_reused_with_self.contains(&usage.name) {
+ if !imports_reused_with_self.contains(&usage.name)
+ && !import_usage_visitor.imports_referenced_with_self.contains(&usage.name)
+ {
self.found.entry(usage.item_id).or_default().push(usage);
}
}
@@ -127,7 +161,6 @@ impl SingleComponentPathImports {
fn track_uses(
&mut self,
- cx: &EarlyContext<'_>,
item: &Item,
imports_reused_with_self: &mut Vec<Symbol>,
single_use_usages: &mut Vec<SingleUse>,
@@ -139,7 +172,7 @@ impl SingleComponentPathImports {
match &item.kind {
ItemKind::Mod(_, ModKind::Loaded(ref items, ..)) => {
- self.check_mod(cx, items);
+ self.check_mod(items);
},
ItemKind::MacroDef(MacroDef { macro_rules: true, .. }) => {
macros.push(item.ident.name);
diff --git a/src/tools/clippy/clippy_lints/src/size_of_ref.rs b/src/tools/clippy/clippy_lints/src/size_of_ref.rs
index 3fcdb4288..8abec06c6 100644
--- a/src/tools/clippy/clippy_lints/src/size_of_ref.rs
+++ b/src/tools/clippy/clippy_lints/src/size_of_ref.rs
@@ -45,7 +45,7 @@ declare_clippy_lint! {
/// }
/// }
/// ```
- #[clippy::version = "1.67.0"]
+ #[clippy::version = "1.68.0"]
pub SIZE_OF_REF,
suspicious,
"Argument to `std::mem::size_of_val()` is a double-reference, which is almost certainly unintended"
diff --git a/src/tools/clippy/clippy_lints/src/std_instead_of_core.rs b/src/tools/clippy/clippy_lints/src/std_instead_of_core.rs
index d6b336bef..a13bc7a51 100644
--- a/src/tools/clippy/clippy_lints/src/std_instead_of_core.rs
+++ b/src/tools/clippy/clippy_lints/src/std_instead_of_core.rs
@@ -2,7 +2,6 @@ use clippy_utils::diagnostics::span_lint_and_help;
use rustc_hir::def_id::DefId;
use rustc_hir::{def::Res, HirId, Path, PathSegment};
use rustc_lint::{LateContext, LateLintPass};
-use rustc_middle::ty::DefIdTree;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::{sym, symbol::kw, Span};
diff --git a/src/tools/clippy/clippy_lints/src/suspicious_doc_comments.rs b/src/tools/clippy/clippy_lints/src/suspicious_doc_comments.rs
new file mode 100644
index 000000000..e5746ca99
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/suspicious_doc_comments.rs
@@ -0,0 +1,94 @@
+use clippy_utils::diagnostics::{multispan_sugg_with_applicability, span_lint_and_then};
+use if_chain::if_chain;
+use rustc_ast::{token::CommentKind, AttrKind, AttrStyle, Attribute, Item};
+use rustc_errors::Applicability;
+use rustc_lint::{EarlyContext, EarlyLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::Span;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Detects the use of outer doc comments (`///`, `/**`) followed by a bang (`!`): `///!`
+ ///
+ /// ### Why is this bad?
+ /// Triple-slash comments (known as "outer doc comments") apply to items that follow it.
+ /// An outer doc comment followed by a bang (i.e. `///!`) has no specific meaning.
+ ///
+ /// The user most likely meant to write an inner doc comment (`//!`, `/*!`), which
+ /// applies to the parent item (i.e. the item that the comment is contained in,
+ /// usually a module or crate).
+ ///
+ /// ### Known problems
+ /// Inner doc comments can only appear before items, so there are certain cases where the suggestion
+ /// made by this lint is not valid code. For example:
+ /// ```rs
+ /// fn foo() {}
+ /// ///!
+ /// fn bar() {}
+ /// ```
+ /// This lint detects the doc comment and suggests changing it to `//!`, but an inner doc comment
+ /// is not valid at that position.
+ ///
+ /// ### Example
+ /// In this example, the doc comment is attached to the *function*, rather than the *module*.
+ /// ```rust
+ /// pub mod util {
+ /// ///! This module contains utility functions.
+ ///
+ /// pub fn dummy() {}
+ /// }
+ /// ```
+ ///
+ /// Use instead:
+ /// ```rust
+ /// pub mod util {
+ /// //! This module contains utility functions.
+ ///
+ /// pub fn dummy() {}
+ /// }
+ /// ```
+ #[clippy::version = "1.70.0"]
+ pub SUSPICIOUS_DOC_COMMENTS,
+ suspicious,
+ "suspicious usage of (outer) doc comments"
+}
+declare_lint_pass!(SuspiciousDocComments => [SUSPICIOUS_DOC_COMMENTS]);
+
+const WARNING: &str = "this is an outer doc comment and does not apply to the parent module or crate";
+const HELP: &str = "use an inner doc comment to document the parent module or crate";
+
+impl EarlyLintPass for SuspiciousDocComments {
+ fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
+ let replacements = collect_doc_comment_replacements(&item.attrs);
+
+ if let Some(((lo_span, _), (hi_span, _))) = replacements.first().zip(replacements.last()) {
+ let span = lo_span.to(*hi_span);
+
+ span_lint_and_then(cx, SUSPICIOUS_DOC_COMMENTS, span, WARNING, |diag| {
+ multispan_sugg_with_applicability(diag, HELP, Applicability::MaybeIncorrect, replacements);
+ });
+ }
+ }
+}
+
+fn collect_doc_comment_replacements(attrs: &[Attribute]) -> Vec<(Span, String)> {
+ attrs
+ .iter()
+ .filter_map(|attr| {
+ if_chain! {
+ if let AttrKind::DocComment(com_kind, sym) = attr.kind;
+ if let AttrStyle::Outer = attr.style;
+ if let Some(com) = sym.as_str().strip_prefix('!');
+ then {
+ let sugg = match com_kind {
+ CommentKind::Line => format!("//!{com}"),
+ CommentKind::Block => format!("/*!{com}*/")
+ };
+ Some((attr.span, sugg))
+ } else {
+ None
+ }
+ }
+ })
+ .collect()
+}
diff --git a/src/tools/clippy/clippy_lints/src/suspicious_operation_groupings.rs b/src/tools/clippy/clippy_lints/src/suspicious_operation_groupings.rs
index e111c7d22..fab8e9c2e 100644
--- a/src/tools/clippy/clippy_lints/src/suspicious_operation_groupings.rs
+++ b/src/tools/clippy/clippy_lints/src/suspicious_operation_groupings.rs
@@ -578,7 +578,7 @@ fn ident_difference_expr_with_base_location(
| (Assign(_, _, _), Assign(_, _, _))
| (TryBlock(_), TryBlock(_))
| (Await(_), Await(_))
- | (Async(_, _, _), Async(_, _, _))
+ | (Async(_, _), Async(_, _))
| (Block(_, _), Block(_, _))
| (Closure(_), Closure(_))
| (Match(_, _), Match(_, _))
@@ -596,8 +596,7 @@ fn ident_difference_expr_with_base_location(
| (MethodCall(_), MethodCall(_))
| (Call(_, _), Call(_, _))
| (ConstBlock(_), ConstBlock(_))
- | (Array(_), Array(_))
- | (Box(_), Box(_)) => {
+ | (Array(_), Array(_)) => {
// keep going
},
_ => {
diff --git a/src/tools/clippy/clippy_lints/src/swap.rs b/src/tools/clippy/clippy_lints/src/swap.rs
index 0f062cecf..f7eef03d1 100644
--- a/src/tools/clippy/clippy_lints/src/swap.rs
+++ b/src/tools/clippy/clippy_lints/src/swap.rs
@@ -1,15 +1,17 @@
use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_and_then};
-use clippy_utils::source::snippet_with_applicability;
+use clippy_utils::source::snippet_with_context;
use clippy_utils::sugg::Sugg;
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::{can_mut_borrow_both, eq_expr_value, in_constant, std_or_core};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::{BinOpKind, Block, Expr, ExprKind, PatKind, QPath, Stmt, StmtKind};
-use rustc_lint::{LateContext, LateLintPass};
+use rustc_lint::{LateContext, LateLintPass, LintContext};
+use rustc_middle::lint::in_external_macro;
use rustc_middle::ty;
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::source_map::Spanned;
+use rustc_span::SyntaxContext;
use rustc_span::{sym, symbol::Ident, Span};
declare_clippy_lint! {
@@ -80,43 +82,45 @@ impl<'tcx> LateLintPass<'tcx> for Swap {
}
fn generate_swap_warning(cx: &LateContext<'_>, e1: &Expr<'_>, e2: &Expr<'_>, span: Span, is_xor_based: bool) {
+ let ctxt = span.ctxt();
let mut applicability = Applicability::MachineApplicable;
if !can_mut_borrow_both(cx, e1, e2) {
- if let ExprKind::Index(lhs1, idx1) = e1.kind {
- if let ExprKind::Index(lhs2, idx2) = e2.kind {
- if eq_expr_value(cx, lhs1, lhs2) {
- let ty = cx.typeck_results().expr_ty(lhs1).peel_refs();
+ if let ExprKind::Index(lhs1, idx1) = e1.kind
+ && let ExprKind::Index(lhs2, idx2) = e2.kind
+ && eq_expr_value(cx, lhs1, lhs2)
+ && e1.span.ctxt() == ctxt
+ && e2.span.ctxt() == ctxt
+ {
+ let ty = cx.typeck_results().expr_ty(lhs1).peel_refs();
- if matches!(ty.kind(), ty::Slice(_))
- || matches!(ty.kind(), ty::Array(_, _))
- || is_type_diagnostic_item(cx, ty, sym::Vec)
- || is_type_diagnostic_item(cx, ty, sym::VecDeque)
- {
- let slice = Sugg::hir_with_applicability(cx, lhs1, "<slice>", &mut applicability);
- span_lint_and_sugg(
- cx,
- MANUAL_SWAP,
- span,
- &format!("this looks like you are swapping elements of `{slice}` manually"),
- "try",
- format!(
- "{}.swap({}, {})",
- slice.maybe_par(),
- snippet_with_applicability(cx, idx1.span, "..", &mut applicability),
- snippet_with_applicability(cx, idx2.span, "..", &mut applicability),
- ),
- applicability,
- );
- }
- }
+ if matches!(ty.kind(), ty::Slice(_))
+ || matches!(ty.kind(), ty::Array(_, _))
+ || is_type_diagnostic_item(cx, ty, sym::Vec)
+ || is_type_diagnostic_item(cx, ty, sym::VecDeque)
+ {
+ let slice = Sugg::hir_with_applicability(cx, lhs1, "<slice>", &mut applicability);
+ span_lint_and_sugg(
+ cx,
+ MANUAL_SWAP,
+ span,
+ &format!("this looks like you are swapping elements of `{slice}` manually"),
+ "try",
+ format!(
+ "{}.swap({}, {});",
+ slice.maybe_par(),
+ snippet_with_context(cx, idx1.span, ctxt, "..", &mut applicability).0,
+ snippet_with_context(cx, idx2.span, ctxt, "..", &mut applicability).0,
+ ),
+ applicability,
+ );
}
}
return;
}
- let first = Sugg::hir_with_applicability(cx, e1, "..", &mut applicability);
- let second = Sugg::hir_with_applicability(cx, e2, "..", &mut applicability);
+ let first = Sugg::hir_with_context(cx, e1, ctxt, "..", &mut applicability);
+ let second = Sugg::hir_with_context(cx, e2, ctxt, "..", &mut applicability);
let Some(sugg) = std_or_core(cx) else { return };
span_lint_and_then(
@@ -128,7 +132,7 @@ fn generate_swap_warning(cx: &LateContext<'_>, e1: &Expr<'_>, e2: &Expr<'_>, spa
diag.span_suggestion(
span,
"try",
- format!("{sugg}::mem::swap({}, {})", first.mut_addr(), second.mut_addr()),
+ format!("{sugg}::mem::swap({}, {});", first.mut_addr(), second.mut_addr()),
applicability,
);
if !is_xor_based {
@@ -144,19 +148,19 @@ fn check_manual_swap(cx: &LateContext<'_>, block: &Block<'_>) {
return;
}
- for w in block.stmts.windows(3) {
+ for [s1, s2, s3] in block.stmts.array_windows::<3>() {
if_chain! {
// let t = foo();
- if let StmtKind::Local(tmp) = w[0].kind;
+ if let StmtKind::Local(tmp) = s1.kind;
if let Some(tmp_init) = tmp.init;
if let PatKind::Binding(.., ident, None) = tmp.pat.kind;
// foo() = bar();
- if let StmtKind::Semi(first) = w[1].kind;
+ if let StmtKind::Semi(first) = s2.kind;
if let ExprKind::Assign(lhs1, rhs1, _) = first.kind;
// bar() = t;
- if let StmtKind::Semi(second) = w[2].kind;
+ if let StmtKind::Semi(second) = s3.kind;
if let ExprKind::Assign(lhs2, rhs2, _) = second.kind;
if let ExprKind::Path(QPath::Resolved(None, rhs2)) = rhs2.kind;
if rhs2.segments.len() == 1;
@@ -164,8 +168,15 @@ fn check_manual_swap(cx: &LateContext<'_>, block: &Block<'_>) {
if ident.name == rhs2.segments[0].ident.name;
if eq_expr_value(cx, tmp_init, lhs1);
if eq_expr_value(cx, rhs1, lhs2);
+
+ let ctxt = s1.span.ctxt();
+ if s2.span.ctxt() == ctxt;
+ if s3.span.ctxt() == ctxt;
+ if first.span.ctxt() == ctxt;
+ if second.span.ctxt() == ctxt;
+
then {
- let span = w[0].span.to(second.span);
+ let span = s1.span.to(s3.span);
generate_swap_warning(cx, lhs1, lhs2, span, false);
}
}
@@ -178,8 +189,10 @@ fn check_suspicious_swap(cx: &LateContext<'_>, block: &Block<'_>) {
if let Some((lhs0, rhs0)) = parse(first)
&& let Some((lhs1, rhs1)) = parse(second)
&& first.span.eq_ctxt(second.span)
+ && !in_external_macro(cx.sess(), first.span)
&& is_same(cx, lhs0, rhs1)
&& is_same(cx, lhs1, rhs0)
+ && !is_same(cx, lhs1, rhs1) // Ignore a = b; a = a (#10421)
&& let Some(lhs_sugg) = match &lhs0 {
ExprOrIdent::Expr(expr) => Sugg::hir_opt(cx, expr),
ExprOrIdent::Ident(ident) => Some(Sugg::NonParen(ident.as_str().into())),
@@ -246,17 +259,20 @@ fn parse<'a, 'hir>(stmt: &'a Stmt<'hir>) -> Option<(ExprOrIdent<'hir>, &'a Expr<
/// Implementation of the xor case for `MANUAL_SWAP` lint.
fn check_xor_swap(cx: &LateContext<'_>, block: &Block<'_>) {
- for window in block.stmts.windows(3) {
+ for [s1, s2, s3] in block.stmts.array_windows::<3>() {
+ let ctxt = s1.span.ctxt();
if_chain! {
- if let Some((lhs0, rhs0)) = extract_sides_of_xor_assign(&window[0]);
- if let Some((lhs1, rhs1)) = extract_sides_of_xor_assign(&window[1]);
- if let Some((lhs2, rhs2)) = extract_sides_of_xor_assign(&window[2]);
+ if let Some((lhs0, rhs0)) = extract_sides_of_xor_assign(s1, ctxt);
+ if let Some((lhs1, rhs1)) = extract_sides_of_xor_assign(s2, ctxt);
+ if let Some((lhs2, rhs2)) = extract_sides_of_xor_assign(s3, ctxt);
if eq_expr_value(cx, lhs0, rhs1);
if eq_expr_value(cx, lhs2, rhs1);
if eq_expr_value(cx, lhs1, rhs0);
if eq_expr_value(cx, lhs1, rhs2);
+ if s2.span.ctxt() == ctxt;
+ if s3.span.ctxt() == ctxt;
then {
- let span = window[0].span.to(window[2].span);
+ let span = s1.span.to(s3.span);
generate_swap_warning(cx, lhs0, rhs0, span, true);
}
};
@@ -264,9 +280,12 @@ fn check_xor_swap(cx: &LateContext<'_>, block: &Block<'_>) {
}
/// Returns the lhs and rhs of an xor assignment statement.
-fn extract_sides_of_xor_assign<'a, 'hir>(stmt: &'a Stmt<'hir>) -> Option<(&'a Expr<'hir>, &'a Expr<'hir>)> {
- if let StmtKind::Semi(expr) = stmt.kind {
- if let ExprKind::AssignOp(
+fn extract_sides_of_xor_assign<'a, 'hir>(
+ stmt: &'a Stmt<'hir>,
+ ctxt: SyntaxContext,
+) -> Option<(&'a Expr<'hir>, &'a Expr<'hir>)> {
+ if let StmtKind::Semi(expr) = stmt.kind
+ && let ExprKind::AssignOp(
Spanned {
node: BinOpKind::BitXor,
..
@@ -274,9 +293,10 @@ fn extract_sides_of_xor_assign<'a, 'hir>(stmt: &'a Stmt<'hir>) -> Option<(&'a Ex
lhs,
rhs,
) = expr.kind
- {
- return Some((lhs, rhs));
- }
+ && expr.span.ctxt() == ctxt
+ {
+ Some((lhs, rhs))
+ } else {
+ None
}
- None
}
diff --git a/src/tools/clippy/clippy_lints/src/tests_outside_test_module.rs b/src/tools/clippy/clippy_lints/src/tests_outside_test_module.rs
new file mode 100644
index 000000000..0a0a77082
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/tests_outside_test_module.rs
@@ -0,0 +1,71 @@
+use clippy_utils::{diagnostics::span_lint_and_note, is_in_cfg_test, is_in_test_function};
+use rustc_hir::{intravisit::FnKind, Body, FnDecl};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::{def_id::LocalDefId, Span};
+
+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?
+ /// 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
+ /// ```rust
+ /// #[test]
+ /// fn my_cool_test() {
+ /// // [...]
+ /// }
+ ///
+ /// #[cfg(test)]
+ /// mod tests {
+ /// // [...]
+ /// }
+ ///
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// #[cfg(test)]
+ /// mod tests {
+ /// #[test]
+ /// fn my_cool_test() {
+ /// // [...]
+ /// }
+ /// }
+ /// ```
+ #[clippy::version = "1.70.0"]
+ pub TESTS_OUTSIDE_TEST_MODULE,
+ restriction,
+ "A test function is outside the testing module."
+}
+
+declare_lint_pass!(TestsOutsideTestModule => [TESTS_OUTSIDE_TEST_MODULE]);
+
+impl LateLintPass<'_> for TestsOutsideTestModule {
+ fn check_fn(
+ &mut self,
+ cx: &LateContext<'_>,
+ kind: FnKind<'_>,
+ _: &FnDecl<'_>,
+ body: &Body<'_>,
+ sp: Span,
+ _: LocalDefId,
+ ) {
+ if_chain! {
+ if !matches!(kind, FnKind::Closure);
+ if is_in_test_function(cx.tcx, body.id().hir_id);
+ if !is_in_cfg_test(cx.tcx, body.id().hir_id);
+ then {
+ span_lint_and_note(
+ cx,
+ TESTS_OUTSIDE_TEST_MODULE,
+ sp,
+ "this function marked with #[test] is outside a #[cfg(test)] module",
+ None,
+ "move it to a testing module marked with #[cfg(test)]",
+ );
+ }
+ }
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/transmute/mod.rs b/src/tools/clippy/clippy_lints/src/transmute/mod.rs
index c01cbe509..0dc30f7a9 100644
--- a/src/tools/clippy/clippy_lints/src/transmute/mod.rs
+++ b/src/tools/clippy/clippy_lints/src/transmute/mod.rs
@@ -458,7 +458,7 @@ declare_clippy_lint! {
/// ```rust
/// let null_fn: Option<fn()> = None;
/// ```
- #[clippy::version = "1.67.0"]
+ #[clippy::version = "1.68.0"]
pub TRANSMUTE_NULL_TO_FN,
correctness,
"transmute results in a null function pointer, which is undefined behavior"
diff --git a/src/tools/clippy/clippy_lints/src/transmute/transmutes_expressible_as_ptr_casts.rs b/src/tools/clippy/clippy_lints/src/transmute/transmutes_expressible_as_ptr_casts.rs
index 8530b4324..85cd74f23 100644
--- a/src/tools/clippy/clippy_lints/src/transmute/transmutes_expressible_as_ptr_casts.rs
+++ b/src/tools/clippy/clippy_lints/src/transmute/transmutes_expressible_as_ptr_casts.rs
@@ -2,8 +2,9 @@ use super::utils::check_cast;
use super::TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::sugg::Sugg;
+use rustc_ast::ExprPrecedence;
use rustc_errors::Applicability;
-use rustc_hir::Expr;
+use rustc_hir::{Expr, Node};
use rustc_lint::LateContext;
use rustc_middle::ty::{cast::CastKind, Ty};
@@ -19,7 +20,7 @@ pub(super) fn check<'tcx>(
) -> bool {
use CastKind::{AddrPtrCast, ArrayPtrCast, FnPtrAddrCast, FnPtrPtrCast, PtrAddrCast, PtrPtrCast};
let mut app = Applicability::MachineApplicable;
- let sugg = match check_cast(cx, e, from_ty, to_ty) {
+ let mut sugg = match check_cast(cx, e, from_ty, to_ty) {
Some(PtrPtrCast | AddrPtrCast | ArrayPtrCast | FnPtrPtrCast | FnPtrAddrCast) => {
Sugg::hir_with_context(cx, arg, e.span.ctxt(), "..", &mut app)
.as_ty(to_ty.to_string())
@@ -39,6 +40,12 @@ pub(super) fn check<'tcx>(
_ => return false,
};
+ if let Node::Expr(parent) = cx.tcx.hir().get_parent(e.hir_id)
+ && parent.precedence().order() > ExprPrecedence::Cast.order()
+ {
+ sugg = format!("({sugg})");
+ }
+
span_lint_and_sugg(
cx,
TRANSMUTES_EXPRESSIBLE_AS_PTR_CASTS,
diff --git a/src/tools/clippy/clippy_lints/src/transmute/utils.rs b/src/tools/clippy/clippy_lints/src/transmute/utils.rs
index cddaf9450..62efd13b8 100644
--- a/src/tools/clippy/clippy_lints/src/transmute/utils.rs
+++ b/src/tools/clippy/clippy_lints/src/transmute/utils.rs
@@ -33,38 +33,37 @@ pub(super) fn check_cast<'tcx>(
let hir_id = e.hir_id;
let local_def_id = hir_id.owner.def_id;
- Inherited::build(cx.tcx, local_def_id).enter(|inherited| {
- let fn_ctxt = FnCtxt::new(inherited, cx.param_env, local_def_id);
+ let inherited = Inherited::new(cx.tcx, local_def_id);
+ let fn_ctxt = FnCtxt::new(&inherited, cx.param_env, local_def_id);
- // If we already have errors, we can't be sure we can pointer cast.
+ // If we already have errors, we can't be sure we can pointer cast.
+ assert!(
+ !fn_ctxt.errors_reported_since_creation(),
+ "Newly created FnCtxt contained errors"
+ );
+
+ if let Ok(check) = cast::CastCheck::new(
+ &fn_ctxt,
+ e,
+ from_ty,
+ to_ty,
+ // We won't show any error to the user, so we don't care what the span is here.
+ DUMMY_SP,
+ DUMMY_SP,
+ hir::Constness::NotConst,
+ ) {
+ let res = check.do_check(&fn_ctxt);
+
+ // do_check's documentation says that it might return Ok and create
+ // errors in the fcx instead of returning Err in some cases. Those cases
+ // should be filtered out before getting here.
assert!(
!fn_ctxt.errors_reported_since_creation(),
- "Newly created FnCtxt contained errors"
+ "`fn_ctxt` contained errors after cast check!"
);
- if let Ok(check) = cast::CastCheck::new(
- &fn_ctxt,
- e,
- from_ty,
- to_ty,
- // We won't show any error to the user, so we don't care what the span is here.
- DUMMY_SP,
- DUMMY_SP,
- hir::Constness::NotConst,
- ) {
- let res = check.do_check(&fn_ctxt);
-
- // do_check's documentation says that it might return Ok and create
- // errors in the fcx instead of returning Err in some cases. Those cases
- // should be filtered out before getting here.
- assert!(
- !fn_ctxt.errors_reported_since_creation(),
- "`fn_ctxt` contained errors after cast check!"
- );
-
- res.ok()
- } else {
- None
- }
- })
+ res.ok()
+ } else {
+ None
+ }
}
diff --git a/src/tools/clippy/clippy_lints/src/types/borrowed_box.rs b/src/tools/clippy/clippy_lints/src/types/borrowed_box.rs
index 65dfe7637..acdf54710 100644
--- a/src/tools/clippy/clippy_lints/src/types/borrowed_box.rs
+++ b/src/tools/clippy/clippy_lints/src/types/borrowed_box.rs
@@ -20,7 +20,7 @@ pub(super) fn check(cx: &LateContext<'_>, hir_ty: &hir::Ty<'_>, lt: &Lifetime, m
if let QPath::Resolved(None, path) = *qpath;
if let [ref bx] = *path.segments;
if let Some(params) = bx.args;
- if !params.parenthesized;
+ if params.parenthesized == hir::GenericArgsParentheses::No;
if let Some(inner) = params.args.iter().find_map(|arg| match arg {
GenericArg::Type(ty) => Some(ty),
_ => None,
diff --git a/src/tools/clippy/clippy_lints/src/types/utils.rs b/src/tools/clippy/clippy_lints/src/types/utils.rs
index 7f43b7841..a30748db8 100644
--- a/src/tools/clippy/clippy_lints/src/types/utils.rs
+++ b/src/tools/clippy/clippy_lints/src/types/utils.rs
@@ -1,6 +1,6 @@
use clippy_utils::last_path_segment;
use if_chain::if_chain;
-use rustc_hir::{GenericArg, QPath, TyKind};
+use rustc_hir::{GenericArg, GenericArgsParentheses, QPath, TyKind};
use rustc_lint::LateContext;
use rustc_span::source_map::Span;
@@ -8,7 +8,7 @@ pub(super) fn match_borrows_parameter(_cx: &LateContext<'_>, qpath: &QPath<'_>)
let last = last_path_segment(qpath);
if_chain! {
if let Some(params) = last.args;
- if !params.parenthesized;
+ if params.parenthesized == GenericArgsParentheses::No;
if let Some(ty) = params.args.iter().find_map(|arg| match arg {
GenericArg::Type(ty) => Some(ty),
_ => None,
diff --git a/src/tools/clippy/clippy_lints/src/unit_types/let_unit_value.rs b/src/tools/clippy/clippy_lints/src/unit_types/let_unit_value.rs
index d6167a621..cc7c2b039 100644
--- a/src/tools/clippy/clippy_lints/src/unit_types/let_unit_value.rs
+++ b/src/tools/clippy/clippy_lints/src/unit_types/let_unit_value.rs
@@ -1,11 +1,11 @@
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::get_parent_node;
-use clippy_utils::source::snippet_with_macro_callsite;
+use clippy_utils::source::snippet_with_context;
use clippy_utils::visitors::{for_each_local_assignment, for_each_value_source};
use core::ops::ControlFlow;
use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res};
-use rustc_hir::{Expr, ExprKind, HirId, HirIdSet, Local, Node, PatKind, QPath, TyKind};
+use rustc_hir::{Expr, ExprKind, HirId, HirIdSet, Local, MatchSource, Node, PatKind, QPath, TyKind};
use rustc_lint::{LateContext, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_middle::ty;
@@ -41,6 +41,10 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, local: &'tcx Local<'_>) {
);
}
} else {
+ if let ExprKind::Match(_, _, MatchSource::AwaitDesugar) = init.kind {
+ return
+ }
+
span_lint_and_then(
cx,
LET_UNIT_VALUE,
@@ -48,12 +52,13 @@ pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, local: &'tcx Local<'_>) {
"this let-binding has unit value",
|diag| {
if let Some(expr) = &local.init {
- let snip = snippet_with_macro_callsite(cx, expr.span, "()");
+ let mut app = Applicability::MachineApplicable;
+ let snip = snippet_with_context(cx, expr.span, local.span.ctxt(), "()", &mut app).0;
diag.span_suggestion(
local.span,
"omit the `let` binding",
format!("{snip};"),
- Applicability::MachineApplicable, // snippet
+ app,
);
}
},
diff --git a/src/tools/clippy/clippy_lints/src/unnecessary_box_returns.rs b/src/tools/clippy/clippy_lints/src/unnecessary_box_returns.rs
new file mode 100644
index 000000000..912bcda63
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/unnecessary_box_returns.rs
@@ -0,0 +1,120 @@
+use clippy_utils::diagnostics::span_lint_and_then;
+use rustc_errors::Applicability;
+use rustc_hir::{def_id::LocalDefId, FnDecl, FnRetTy, ImplItemKind, Item, ItemKind, Node, TraitItem, TraitItemKind};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_tool_lint, impl_lint_pass};
+use rustc_span::Symbol;
+
+declare_clippy_lint! {
+ /// ### What it does
+ ///
+ /// Checks for a return type containing a `Box<T>` where `T` implements `Sized`
+ ///
+ /// ### Why is this bad?
+ ///
+ /// It's better to just return `T` in these cases. The caller may not need
+ /// the value to be boxed, and it's expensive to free the memory once the
+ /// `Box<T>` been dropped.
+ ///
+ /// ### Example
+ /// ```rust
+ /// fn foo() -> Box<String> {
+ /// Box::new(String::from("Hello, world!"))
+ /// }
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// fn foo() -> String {
+ /// String::from("Hello, world!")
+ /// }
+ /// ```
+ #[clippy::version = "1.70.0"]
+ pub UNNECESSARY_BOX_RETURNS,
+ pedantic,
+ "Needlessly returning a Box"
+}
+
+pub struct UnnecessaryBoxReturns {
+ avoid_breaking_exported_api: bool,
+}
+
+impl_lint_pass!(UnnecessaryBoxReturns => [UNNECESSARY_BOX_RETURNS]);
+
+impl UnnecessaryBoxReturns {
+ pub fn new(avoid_breaking_exported_api: bool) -> Self {
+ Self {
+ avoid_breaking_exported_api,
+ }
+ }
+
+ fn check_fn_item(&mut self, cx: &LateContext<'_>, decl: &FnDecl<'_>, def_id: LocalDefId, name: Symbol) {
+ // we don't want to tell someone to break an exported function if they ask us not to
+ if self.avoid_breaking_exported_api && cx.effective_visibilities.is_exported(def_id) {
+ return;
+ }
+
+ // functions which contain the word "box" are exempt from this lint
+ if name.as_str().contains("box") {
+ return;
+ }
+
+ let FnRetTy::Return(return_ty_hir) = &decl.output else { return };
+
+ let return_ty = cx
+ .tcx
+ .erase_late_bound_regions(cx.tcx.fn_sig(def_id).skip_binder())
+ .output();
+
+ if !return_ty.is_box() {
+ return;
+ }
+
+ let boxed_ty = return_ty.boxed_ty();
+
+ // it's sometimes useful to return Box<T> if T is unsized, so don't lint those
+ if boxed_ty.is_sized(cx.tcx, cx.param_env) {
+ span_lint_and_then(
+ cx,
+ UNNECESSARY_BOX_RETURNS,
+ return_ty_hir.span,
+ format!("boxed return of the sized type `{boxed_ty}`").as_str(),
+ |diagnostic| {
+ diagnostic.span_suggestion(
+ return_ty_hir.span,
+ "try",
+ boxed_ty.to_string(),
+ // the return value and function callers also needs to
+ // be changed, so this can't be MachineApplicable
+ Applicability::Unspecified,
+ );
+ diagnostic.help("changing this also requires a change to the return expressions in this function");
+ },
+ );
+ }
+ }
+}
+
+impl LateLintPass<'_> for UnnecessaryBoxReturns {
+ fn check_trait_item(&mut self, cx: &LateContext<'_>, item: &TraitItem<'_>) {
+ let TraitItemKind::Fn(signature, _) = &item.kind else { return };
+ self.check_fn_item(cx, signature.decl, item.owner_id.def_id, item.ident.name);
+ }
+
+ fn check_impl_item(&mut self, cx: &LateContext<'_>, item: &rustc_hir::ImplItem<'_>) {
+ // Ignore implementations of traits, because the lint should be on the
+ // trait, not on the implmentation of it.
+ let Node::Item(parent) = cx.tcx.hir().get_parent(item.hir_id()) else { return };
+ let ItemKind::Impl(parent) = parent.kind else { return };
+ if parent.of_trait.is_some() {
+ return;
+ }
+
+ let ImplItemKind::Fn(signature, ..) = &item.kind else { return };
+ self.check_fn_item(cx, signature.decl, item.owner_id.def_id, item.ident.name);
+ }
+
+ fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
+ let ItemKind::Fn(signature, ..) = &item.kind else { return };
+ self.check_fn_item(cx, signature.decl, item.owner_id.def_id, item.ident.name);
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/unnecessary_struct_initialization.rs b/src/tools/clippy/clippy_lints/src/unnecessary_struct_initialization.rs
new file mode 100644
index 000000000..084b03198
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/unnecessary_struct_initialization.rs
@@ -0,0 +1,88 @@
+use clippy_utils::{diagnostics::span_lint_and_sugg, get_parent_expr, path_to_local, source::snippet, ty::is_copy};
+use rustc_hir::{BindingAnnotation, Expr, ExprKind, Node, PatKind, UnOp};
+use rustc_lint::{LateContext, LateLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Checks for initialization of a `struct` by copying a base without setting
+ /// any field.
+ ///
+ /// ### Why is this bad?
+ /// Readability suffers from unnecessary struct building.
+ ///
+ /// ### Example
+ /// ```rust
+ /// struct S { s: String }
+ ///
+ /// let a = S { s: String::from("Hello, world!") };
+ /// let b = S { ..a };
+ /// ```
+ /// Use instead:
+ /// ```rust
+ /// struct S { s: String }
+ ///
+ /// let a = S { s: String::from("Hello, world!") };
+ /// let b = a;
+ /// ```
+ ///
+ /// ### Known Problems
+ /// Has false positives when the base is a place expression that cannot be
+ /// moved out of, see [#10547](https://github.com/rust-lang/rust-clippy/issues/10547).
+ #[clippy::version = "1.70.0"]
+ pub UNNECESSARY_STRUCT_INITIALIZATION,
+ nursery,
+ "struct built from a base that can be written mode concisely"
+}
+declare_lint_pass!(UnnecessaryStruct => [UNNECESSARY_STRUCT_INITIALIZATION]);
+
+impl LateLintPass<'_> for UnnecessaryStruct {
+ fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
+ if let ExprKind::Struct(_, &[], Some(base)) = expr.kind {
+ if let Some(parent) = get_parent_expr(cx, expr) &&
+ let parent_ty = cx.typeck_results().expr_ty_adjusted(parent) &&
+ parent_ty.is_any_ptr()
+ {
+ if is_copy(cx, cx.typeck_results().expr_ty(expr)) && path_to_local(base).is_some() {
+ // When the type implements `Copy`, a reference to the new struct works on the
+ // copy. Using the original would borrow it.
+ return;
+ }
+
+ if parent_ty.is_mutable_ptr() && !is_mutable(cx, base) {
+ // The original can be used in a mutable reference context only if it is mutable.
+ return;
+ }
+ }
+
+ // TODO: do not propose to replace *XX if XX is not Copy
+ if let ExprKind::Unary(UnOp::Deref, target) = base.kind &&
+ matches!(target.kind, ExprKind::Path(..)) &&
+ !is_copy(cx, cx.typeck_results().expr_ty(expr))
+ {
+ // `*base` cannot be used instead of the struct in the general case if it is not Copy.
+ return;
+ }
+
+ span_lint_and_sugg(
+ cx,
+ UNNECESSARY_STRUCT_INITIALIZATION,
+ expr.span,
+ "unnecessary struct building",
+ "replace with",
+ snippet(cx, base.span, "..").into_owned(),
+ rustc_errors::Applicability::MachineApplicable,
+ );
+ }
+ }
+}
+
+fn is_mutable(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
+ if let Some(hir_id) = path_to_local(expr) &&
+ let Node::Pat(pat) = cx.tcx.hir().get(hir_id)
+ {
+ matches!(pat.kind, PatKind::Binding(BindingAnnotation::MUT, ..))
+ } else {
+ true
+ }
+}
diff --git a/src/tools/clippy/clippy_lints/src/use_self.rs b/src/tools/clippy/clippy_lints/src/use_self.rs
index e7c540006..5a0298745 100644
--- a/src/tools/clippy/clippy_lints/src/use_self.rs
+++ b/src/tools/clippy/clippy_lints/src/use_self.rs
@@ -10,8 +10,8 @@ use rustc_hir::{
def::{CtorOf, DefKind, Res},
def_id::LocalDefId,
intravisit::{walk_inf, walk_ty, Visitor},
- Expr, ExprKind, FnRetTy, FnSig, GenericArg, HirId, Impl, ImplItemKind, Item, ItemKind, Pat, PatKind, Path, QPath,
- TyKind,
+ Expr, ExprKind, FnRetTy, FnSig, GenericArg, GenericArgsParentheses, GenericParam, GenericParamKind, HirId, Impl,
+ ImplItemKind, Item, ItemKind, Pat, PatKind, Path, QPath, Ty, TyKind,
};
use rustc_hir_analysis::hir_ty_to_ty;
use rustc_lint::{LateContext, LateLintPass};
@@ -96,19 +96,27 @@ impl<'tcx> LateLintPass<'tcx> for UseSelf {
// avoid linting on nested items, we push `StackItem::NoCheck` on the stack to signal, that
// we're in an `impl` or nested item, that we don't want to lint
let stack_item = if_chain! {
- if let ItemKind::Impl(Impl { self_ty, .. }) = item.kind;
+ if let ItemKind::Impl(Impl { self_ty, generics,.. }) = item.kind;
if let TyKind::Path(QPath::Resolved(_, item_path)) = self_ty.kind;
let parameters = &item_path.segments.last().expect(SEGMENTS_MSG).args;
if parameters.as_ref().map_or(true, |params| {
- !params.parenthesized && !params.args.iter().any(|arg| matches!(arg, GenericArg::Lifetime(_)))
+ params.parenthesized == GenericArgsParentheses::No
+ && !params.args.iter().any(|arg| matches!(arg, GenericArg::Lifetime(_)))
});
if !item.span.from_expansion();
if !is_from_proc_macro(cx, item); // expensive, should be last check
then {
+ // Self cannot be used inside const generic parameters
+ let types_to_skip = generics.params.iter().filter_map(|param| {
+ match param {
+ GenericParam { kind: GenericParamKind::Const { ty: Ty { hir_id, ..}, ..}, ..} => Some(*hir_id),
+ _ => None,
+ }
+ }).chain(std::iter::once(self_ty.hir_id)).collect();
StackItem::Check {
impl_id: item.owner_id.def_id,
in_body: 0,
- types_to_skip: std::iter::once(self_ty.hir_id).collect(),
+ types_to_skip,
}
} else {
StackItem::NoCheck
diff --git a/src/tools/clippy/clippy_lints/src/useless_conversion.rs b/src/tools/clippy/clippy_lints/src/useless_conversion.rs
index fede625f7..ddbe6b2c7 100644
--- a/src/tools/clippy/clippy_lints/src/useless_conversion.rs
+++ b/src/tools/clippy/clippy_lints/src/useless_conversion.rs
@@ -1,5 +1,5 @@
use clippy_utils::diagnostics::{span_lint_and_help, span_lint_and_sugg};
-use clippy_utils::source::{snippet, snippet_with_macro_callsite};
+use clippy_utils::source::{snippet, snippet_with_context};
use clippy_utils::sugg::Sugg;
use clippy_utils::ty::{is_copy, is_type_diagnostic_item, same_type_and_consts};
use clippy_utils::{get_parent_expr, is_trait_method, match_def_path, path_to_local, paths};
@@ -68,15 +68,16 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion {
let a = cx.typeck_results().expr_ty(e);
let b = cx.typeck_results().expr_ty(recv);
if same_type_and_consts(a, b) {
- let sugg = snippet_with_macro_callsite(cx, recv.span, "<expr>").to_string();
+ let mut app = Applicability::MachineApplicable;
+ let sugg = snippet_with_context(cx, recv.span, e.span.ctxt(), "<expr>", &mut app).0;
span_lint_and_sugg(
cx,
USELESS_CONVERSION,
e.span,
&format!("useless conversion to the same type: `{b}`"),
"consider removing `.into()`",
- sugg,
- Applicability::MachineApplicable, // snippet
+ sugg.into_owned(),
+ app,
);
}
}
@@ -165,7 +166,8 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion {
if same_type_and_consts(a, b);
then {
- let sugg = Sugg::hir_with_macro_callsite(cx, arg, "<expr>").maybe_par();
+ let mut app = Applicability::MachineApplicable;
+ let sugg = Sugg::hir_with_context(cx, arg, e.span.ctxt(), "<expr>", &mut app).maybe_par();
let sugg_msg =
format!("consider removing `{}()`", snippet(cx, path.span, "From::from"));
span_lint_and_sugg(
@@ -175,7 +177,7 @@ impl<'tcx> LateLintPass<'tcx> for UselessConversion {
&format!("useless conversion to the same type: `{b}`"),
&sugg_msg,
sugg.to_string(),
- Applicability::MachineApplicable, // snippet
+ app,
);
}
}
diff --git a/src/tools/clippy/clippy_lints/src/utils/author.rs b/src/tools/clippy/clippy_lints/src/utils/author.rs
index c37e5bb67..bc4adf159 100644
--- a/src/tools/clippy/clippy_lints/src/utils/author.rs
+++ b/src/tools/clippy/clippy_lints/src/utils/author.rs
@@ -395,11 +395,6 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
}
self.expr(field!(let_expr.init));
},
- ExprKind::Box(inner) => {
- bind!(self, inner);
- kind!("Box({inner})");
- self.expr(inner);
- },
ExprKind::Array(elements) => {
bind!(self, elements);
kind!("Array({elements})");
@@ -588,7 +583,7 @@ impl<'a, 'tcx> PrintVisitor<'a, 'tcx> {
},
}
},
- ExprKind::Err(_) => kind!("Err"),
+ ExprKind::Err(_) => kind!("Err(_)"),
ExprKind::DropTemps(expr) => {
bind!(self, expr);
kind!("DropTemps({expr})");
diff --git a/src/tools/clippy/clippy_lints/src/utils/conf.rs b/src/tools/clippy/clippy_lints/src/utils/conf.rs
index 1c7f3e96d..896a01af3 100644
--- a/src/tools/clippy/clippy_lints/src/utils/conf.rs
+++ b/src/tools/clippy/clippy_lints/src/utils/conf.rs
@@ -249,7 +249,7 @@ define_Conf! {
/// arithmetic-side-effects-allowed-unary = ["SomeType", "AnotherType"]
/// ```
(arithmetic_side_effects_allowed_unary: rustc_data_structures::fx::FxHashSet<String> = <_>::default()),
- /// Lint: ENUM_VARIANT_NAMES, LARGE_TYPES_PASSED_BY_VALUE, TRIVIALLY_COPY_PASS_BY_REF, UNNECESSARY_WRAPS, UNUSED_SELF, UPPER_CASE_ACRONYMS, WRONG_SELF_CONVENTION, BOX_COLLECTION, REDUNDANT_ALLOCATION, RC_BUFFER, VEC_BOX, OPTION_OPTION, LINKEDLIST, RC_MUTEX.
+ /// Lint: ENUM_VARIANT_NAMES, LARGE_TYPES_PASSED_BY_VALUE, TRIVIALLY_COPY_PASS_BY_REF, UNNECESSARY_WRAPS, UNUSED_SELF, UPPER_CASE_ACRONYMS, WRONG_SELF_CONVENTION, BOX_COLLECTION, REDUNDANT_ALLOCATION, RC_BUFFER, VEC_BOX, OPTION_OPTION, LINKEDLIST, RC_MUTEX, UNNECESSARY_BOX_RETURNS.
///
/// Suppress lints whenever the suggested change would cause breakage for other crates.
(avoid_breaking_exported_api: bool = true),
@@ -275,13 +275,13 @@ define_Conf! {
///
/// The list of disallowed names to lint about. NB: `bar` is not here since it has legitimate uses. The value
/// `".."` can be used as part of the list to indicate, that the configured values should be appended to the
- /// default configuration of Clippy. By default any configuration will replace the default value.
+ /// default configuration of Clippy. By default, any configuration will replace the default value.
(disallowed_names: Vec<String> = super::DEFAULT_DISALLOWED_NAMES.iter().map(ToString::to_string).collect()),
/// Lint: DOC_MARKDOWN.
///
/// The list of words this lint should not consider as identifiers needing ticks. The value
/// `".."` can be used as part of the list to indicate, that the configured values should be appended to the
- /// default configuration of Clippy. By default any configuraction will replace the default value. For example:
+ /// default configuration of Clippy. By default, any configuration will replace the default value. For example:
/// * `doc-valid-idents = ["ClipPy"]` would replace the default list with `["ClipPy"]`.
/// * `doc-valid-idents = ["ClipPy", ".."]` would append `ClipPy` to the default list.
///
@@ -390,7 +390,7 @@ define_Conf! {
/// Enforce the named macros always use the braces specified.
///
/// A `MacroMatcher` can be added like so `{ name = "macro_name", brace = "(" }`. If the macro
- /// is could be used with a full path two `MacroMatcher`s have to be added one with the full path
+ /// could be used with a full path two `MacroMatcher`s have to be added one with the full path
/// `crate_name::macro_name` and one with just the macro name.
(standard_macro_braces: Vec<crate::nonstandard_macro_braces::MacroMatcher> = Vec::new()),
/// Lint: MISSING_ENFORCED_IMPORT_RENAMES.
@@ -408,7 +408,7 @@ define_Conf! {
/// Lint: INDEX_REFUTABLE_SLICE.
///
/// When Clippy suggests using a slice pattern, this is the maximum number of elements allowed in
- /// the slice pattern that is suggested. If more elements would be necessary, the lint is suppressed.
+ /// the slice pattern that is suggested. If more elements are necessary, the lint is suppressed.
/// For example, `[_, _, _, e, ..]` is a slice pattern with 4 elements.
(max_suggested_slice_pattern_length: u64 = 3),
/// Lint: AWAIT_HOLDING_INVALID_TYPE.
@@ -437,7 +437,7 @@ define_Conf! {
///
/// The maximum size of the `Err`-variant in a `Result` returned from a function
(large_error_threshold: u64 = 128),
- /// Lint: MUTABLE_KEY_TYPE.
+ /// Lint: MUTABLE_KEY_TYPE, IFS_SAME_COND.
///
/// A list of paths to types that should be treated like `Arc`, i.e. ignored but
/// for the generic parameters for determining interior mutability
@@ -459,6 +459,10 @@ define_Conf! {
/// Whether to **only** check for missing documentation in items visible within the current
/// crate. For example, `pub(crate)` items.
(missing_docs_in_crate_items: bool = false),
+ /// Lint: LARGE_FUTURES.
+ ///
+ /// The maximum byte size a `Future` can have, before it triggers the `clippy::large_futures` lint
+ (future_size_threshold: u64 = 16 * 1024),
}
/// Search for the configuration file.
@@ -466,7 +470,7 @@ define_Conf! {
/// # Errors
///
/// Returns any unexpected filesystem error encountered when searching for the config file
-pub fn lookup_conf_file() -> io::Result<Option<PathBuf>> {
+pub fn lookup_conf_file() -> io::Result<(Option<PathBuf>, Vec<String>)> {
/// Possible filename to search for.
const CONFIG_FILE_NAMES: [&str; 2] = [".clippy.toml", "clippy.toml"];
@@ -474,9 +478,11 @@ pub fn lookup_conf_file() -> io::Result<Option<PathBuf>> {
// If neither of those exist, use ".".
let mut current = env::var_os("CLIPPY_CONF_DIR")
.or_else(|| env::var_os("CARGO_MANIFEST_DIR"))
- .map_or_else(|| PathBuf::from("."), PathBuf::from);
+ .map_or_else(|| PathBuf::from("."), PathBuf::from)
+ .canonicalize()?;
let mut found_config: Option<PathBuf> = None;
+ let mut warnings = vec![];
loop {
for config_file_name in &CONFIG_FILE_NAMES {
@@ -487,12 +493,12 @@ pub fn lookup_conf_file() -> io::Result<Option<PathBuf>> {
Ok(md) if md.is_dir() => {},
Ok(_) => {
// warn if we happen to find two config files #8323
- if let Some(ref found_config_) = found_config {
- eprintln!(
- "Using config file `{}`\nWarning: `{}` will be ignored.",
- found_config_.display(),
- config_file.display(),
- );
+ if let Some(ref found_config) = found_config {
+ warnings.push(format!(
+ "using config file `{}`, `{}` will be ignored",
+ found_config.display(),
+ config_file.display()
+ ));
} else {
found_config = Some(config_file);
}
@@ -502,12 +508,12 @@ pub fn lookup_conf_file() -> io::Result<Option<PathBuf>> {
}
if found_config.is_some() {
- return Ok(found_config);
+ return Ok((found_config, warnings));
}
// If the current directory has no parent, we're done searching.
if !current.pop() {
- return Ok(None);
+ return Ok((None, warnings));
}
}
}
diff --git a/src/tools/clippy/clippy_lints/src/utils/format_args_collector.rs b/src/tools/clippy/clippy_lints/src/utils/format_args_collector.rs
new file mode 100644
index 000000000..09fcb82c3
--- /dev/null
+++ b/src/tools/clippy/clippy_lints/src/utils/format_args_collector.rs
@@ -0,0 +1,98 @@
+use clippy_utils::macros::collect_ast_format_args;
+use clippy_utils::source::snippet_opt;
+use itertools::Itertools;
+use rustc_ast::{Expr, ExprKind, FormatArgs};
+use rustc_lexer::{tokenize, TokenKind};
+use rustc_lint::{EarlyContext, EarlyLintPass};
+use rustc_session::{declare_lint_pass, declare_tool_lint};
+use rustc_span::hygiene;
+use std::iter::once;
+
+declare_clippy_lint! {
+ /// ### What it does
+ /// Collects [`rustc_ast::FormatArgs`] so that future late passes can call
+ /// [`clippy_utils::macros::find_format_args`]
+ pub FORMAT_ARGS_COLLECTOR,
+ internal_warn,
+ "collects `format_args` AST nodes for use in later lints"
+}
+
+declare_lint_pass!(FormatArgsCollector => [FORMAT_ARGS_COLLECTOR]);
+
+impl EarlyLintPass for FormatArgsCollector {
+ fn check_expr(&mut self, cx: &EarlyContext<'_>, expr: &Expr) {
+ if let ExprKind::FormatArgs(args) = &expr.kind {
+ if has_span_from_proc_macro(cx, args) {
+ return;
+ }
+
+ collect_ast_format_args(expr.span, args);
+ }
+ }
+}
+
+/// Detects if the format string or an argument has its span set by a proc macro to something inside
+/// a macro callsite, e.g.
+///
+/// ```ignore
+/// println!(some_proc_macro!("input {}"), a);
+/// ```
+///
+/// Where `some_proc_macro` expands to
+///
+/// ```ignore
+/// println!("output {}", a);
+/// ```
+///
+/// But with the span of `"output {}"` set to the macro input
+///
+/// ```ignore
+/// println!(some_proc_macro!("input {}"), a);
+/// // ^^^^^^^^^^
+/// ```
+fn has_span_from_proc_macro(cx: &EarlyContext<'_>, args: &FormatArgs) -> bool {
+ let ctxt = args.span.ctxt();
+
+ // `format!("{} {} {c}", "one", "two", c = "three")`
+ // ^^^^^ ^^^^^ ^^^^^^^
+ let argument_span = args
+ .arguments
+ .explicit_args()
+ .iter()
+ .map(|argument| hygiene::walk_chain(argument.expr.span, ctxt));
+
+ // `format!("{} {} {c}", "one", "two", c = "three")`
+ // ^^ ^^ ^^^^^^
+ let between_spans = once(args.span)
+ .chain(argument_span)
+ .tuple_windows()
+ .map(|(start, end)| start.between(end));
+
+ for between_span in between_spans {
+ let mut seen_comma = false;
+
+ let Some(snippet) = snippet_opt(cx, between_span) else { return true };
+ for token in tokenize(&snippet) {
+ match token.kind {
+ TokenKind::LineComment { .. } | TokenKind::BlockComment { .. } | TokenKind::Whitespace => {},
+ TokenKind::Comma if !seen_comma => seen_comma = true,
+ // named arguments, `start_val, name = end_val`
+ // ^^^^^^^^^ between_span
+ TokenKind::Ident | TokenKind::Eq if seen_comma => {},
+ // An unexpected token usually indicates that we crossed a macro boundary
+ //
+ // `println!(some_proc_macro!("input {}"), a)`
+ // ^^^ between_span
+ // `println!("{}", val!(x))`
+ // ^^^^^^^ between_span
+ _ => return true,
+ }
+ }
+
+ if !seen_comma {
+ return true;
+ }
+ }
+
+ false
+}
diff --git a/src/tools/clippy/clippy_lints/src/utils/internal_lints/interning_defined_symbol.rs b/src/tools/clippy/clippy_lints/src/utils/internal_lints/interning_defined_symbol.rs
index 688a8b865..f8978e30a 100644
--- a/src/tools/clippy/clippy_lints/src/utils/internal_lints/interning_defined_symbol.rs
+++ b/src/tools/clippy/clippy_lints/src/utils/internal_lints/interning_defined_symbol.rs
@@ -11,7 +11,7 @@ use rustc_hir::def_id::DefId;
use rustc_hir::{BinOpKind, Expr, ExprKind, UnOp};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::mir::interpret::ConstValue;
-use rustc_middle::ty::{self};
+use rustc_middle::ty;
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::symbol::Symbol;
diff --git a/src/tools/clippy/clippy_lints/src/utils/internal_lints/metadata_collector.rs b/src/tools/clippy/clippy_lints/src/utils/internal_lints/metadata_collector.rs
index b1b5164ff..3d0d4a525 100644
--- a/src/tools/clippy/clippy_lints/src/utils/internal_lints/metadata_collector.rs
+++ b/src/tools/clippy/clippy_lints/src/utils/internal_lints/metadata_collector.rs
@@ -26,7 +26,7 @@ use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::symbol::Ident;
use rustc_span::{sym, Loc, Span, Symbol};
use serde::{ser::SerializeStruct, Serialize, Serializer};
-use std::collections::BinaryHeap;
+use std::collections::{BTreeSet, BinaryHeap};
use std::fmt;
use std::fmt::Write as _;
use std::fs::{self, OpenOptions};
@@ -264,6 +264,9 @@ struct LintMetadata {
/// This field is only used in the output and will only be
/// mapped shortly before the actual output.
applicability: Option<ApplicabilityInfo>,
+ /// All the past names of lints which have been renamed.
+ #[serde(skip_serializing_if = "BTreeSet::is_empty")]
+ former_ids: BTreeSet<String>,
}
impl LintMetadata {
@@ -283,6 +286,7 @@ impl LintMetadata {
version,
docs,
applicability: None,
+ former_ids: BTreeSet::new(),
}
}
}
@@ -901,6 +905,7 @@ fn collect_renames(lints: &mut Vec<LintMetadata>) {
if name == lint_name;
if let Some(past_name) = k.strip_prefix(CLIPPY_LINT_GROUP_PREFIX);
then {
+ lint.former_ids.insert(past_name.to_owned());
writeln!(collected, "* `{past_name}`").unwrap();
names.push(past_name.to_string());
}
diff --git a/src/tools/clippy/clippy_lints/src/utils/internal_lints/unnecessary_def_path.rs b/src/tools/clippy/clippy_lints/src/utils/internal_lints/unnecessary_def_path.rs
index b59ef4086..14ed1368e 100644
--- a/src/tools/clippy/clippy_lints/src/utils/internal_lints/unnecessary_def_path.rs
+++ b/src/tools/clippy/clippy_lints/src/utils/internal_lints/unnecessary_def_path.rs
@@ -11,7 +11,7 @@ use rustc_hir::def_id::DefId;
use rustc_hir::{Expr, ExprKind, Local, Mutability, Node};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::mir::interpret::{Allocation, ConstValue, GlobalAlloc};
-use rustc_middle::ty::{self, DefIdTree, Ty};
+use rustc_middle::ty::{self, Ty};
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::symbol::Symbol;
use rustc_span::Span;
diff --git a/src/tools/clippy/clippy_lints/src/utils/mod.rs b/src/tools/clippy/clippy_lints/src/utils/mod.rs
index 787e9fd98..dc647af26 100644
--- a/src/tools/clippy/clippy_lints/src/utils/mod.rs
+++ b/src/tools/clippy/clippy_lints/src/utils/mod.rs
@@ -1,5 +1,6 @@
pub mod author;
pub mod conf;
pub mod dump_hir;
+pub mod format_args_collector;
#[cfg(feature = "internal")]
pub mod internal_lints;
diff --git a/src/tools/clippy/clippy_lints/src/wildcard_imports.rs b/src/tools/clippy/clippy_lints/src/wildcard_imports.rs
index e4d1ee195..36f910c98 100644
--- a/src/tools/clippy/clippy_lints/src/wildcard_imports.rs
+++ b/src/tools/clippy/clippy_lints/src/wildcard_imports.rs
@@ -155,19 +155,13 @@ impl LateLintPass<'_> for WildcardImports {
)
};
- let imports_string = if used_imports.len() == 1 {
- used_imports.iter().next().unwrap().to_string()
+ let mut imports = used_imports.items().map(ToString::to_string).into_sorted_stable_ord(false);
+ let imports_string = if imports.len() == 1 {
+ imports.pop().unwrap()
+ } else if braced_glob {
+ imports.join(", ")
} else {
- let mut imports = used_imports
- .iter()
- .map(ToString::to_string)
- .collect::<Vec<_>>();
- imports.sort();
- if braced_glob {
- imports.join(", ")
- } else {
- format!("{{{}}}", imports.join(", "))
- }
+ format!("{{{}}}", imports.join(", "))
};
let sugg = if braced_glob {
diff --git a/src/tools/clippy/clippy_lints/src/write.rs b/src/tools/clippy/clippy_lints/src/write.rs
index df3350388..d7c94b909 100644
--- a/src/tools/clippy/clippy_lints/src/write.rs
+++ b/src/tools/clippy/clippy_lints/src/write.rs
@@ -1,10 +1,11 @@
use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
-use clippy_utils::macros::{root_macro_call_first_node, FormatArgsExpn, MacroCall};
+use clippy_utils::macros::{find_format_args, format_arg_removal_span, root_macro_call_first_node, MacroCall};
use clippy_utils::source::{expand_past_previous_comma, snippet_opt};
use clippy_utils::{is_in_cfg_test, is_in_test_function};
-use rustc_ast::LitKind;
+use rustc_ast::token::LitKind;
+use rustc_ast::{FormatArgPosition, FormatArgs, FormatArgsPiece, FormatOptions, FormatPlaceholder, FormatTrait};
use rustc_errors::Applicability;
-use rustc_hir::{Expr, ExprKind, HirIdMap, Impl, Item, ItemKind};
+use rustc_hir::{Expr, Impl, Item, ItemKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_session::{declare_tool_lint, impl_lint_pass};
use rustc_span::{sym, BytePos};
@@ -297,34 +298,40 @@ impl<'tcx> LateLintPass<'tcx> for Write {
_ => return,
}
- let Some(format_args) = FormatArgsExpn::find_nested(cx, expr, macro_call.expn) else { return };
-
- // ignore `writeln!(w)` and `write!(v, some_macro!())`
- if format_args.format_string.span.from_expansion() {
- return;
- }
+ find_format_args(cx, expr, macro_call.expn, |format_args| {
+ // ignore `writeln!(w)` and `write!(v, some_macro!())`
+ if format_args.span.from_expansion() {
+ return;
+ }
- match diag_name {
- sym::print_macro | sym::eprint_macro | sym::write_macro => {
- check_newline(cx, &format_args, &macro_call, name);
- },
- sym::println_macro | sym::eprintln_macro | sym::writeln_macro => {
- check_empty_string(cx, &format_args, &macro_call, name);
- },
- _ => {},
- }
+ match diag_name {
+ sym::print_macro | sym::eprint_macro | sym::write_macro => {
+ check_newline(cx, format_args, &macro_call, name);
+ },
+ sym::println_macro | sym::eprintln_macro | sym::writeln_macro => {
+ check_empty_string(cx, format_args, &macro_call, name);
+ },
+ _ => {},
+ }
- check_literal(cx, &format_args, name);
+ check_literal(cx, format_args, name);
- if !self.in_debug_impl {
- for arg in &format_args.args {
- if arg.format.r#trait == sym::Debug {
- span_lint(cx, USE_DEBUG, arg.span, "use of `Debug`-based formatting");
+ if !self.in_debug_impl {
+ for piece in &format_args.template {
+ if let &FormatArgsPiece::Placeholder(FormatPlaceholder {
+ span: Some(span),
+ format_trait: FormatTrait::Debug,
+ ..
+ }) = piece
+ {
+ span_lint(cx, USE_DEBUG, span, "use of `Debug`-based formatting");
+ }
}
}
- }
+ });
}
}
+
fn is_debug_impl(cx: &LateContext<'_>, item: &Item<'_>) -> bool {
if let ItemKind::Impl(Impl { of_trait: Some(trait_ref), .. }) = &item.kind
&& let Some(trait_id) = trait_ref.trait_def_id()
@@ -335,16 +342,18 @@ fn is_debug_impl(cx: &LateContext<'_>, item: &Item<'_>) -> bool {
}
}
-fn check_newline(cx: &LateContext<'_>, format_args: &FormatArgsExpn<'_>, macro_call: &MacroCall, name: &str) {
- let format_string_parts = &format_args.format_string.parts;
- let mut format_string_span = format_args.format_string.span;
-
- let Some(last) = format_string_parts.last() else { return };
+fn check_newline(cx: &LateContext<'_>, format_args: &FormatArgs, macro_call: &MacroCall, name: &str) {
+ let Some(FormatArgsPiece::Literal(last)) = format_args.template.last() else { return };
let count_vertical_whitespace = || {
- format_string_parts
+ format_args
+ .template
.iter()
- .flat_map(|part| part.as_str().chars())
+ .filter_map(|piece| match piece {
+ FormatArgsPiece::Literal(literal) => Some(literal),
+ FormatArgsPiece::Placeholder(_) => None,
+ })
+ .flat_map(|literal| literal.as_str().chars())
.filter(|ch| matches!(ch, '\r' | '\n'))
.count()
};
@@ -352,10 +361,9 @@ fn check_newline(cx: &LateContext<'_>, format_args: &FormatArgsExpn<'_>, macro_c
if last.as_str().ends_with('\n')
// ignore format strings with other internal vertical whitespace
&& count_vertical_whitespace() == 1
-
- // ignore trailing arguments: `print!("Issue\n{}", 1265);`
- && format_string_parts.len() > format_args.args.len()
{
+ let mut format_string_span = format_args.span;
+
let lint = if name == "write" {
format_string_span = expand_past_previous_comma(cx, format_string_span);
@@ -373,7 +381,7 @@ fn check_newline(cx: &LateContext<'_>, format_args: &FormatArgsExpn<'_>, macro_c
let name_span = cx.sess().source_map().span_until_char(macro_call.span, '!');
let Some(format_snippet) = snippet_opt(cx, format_string_span) else { return };
- if format_string_parts.len() == 1 && last.as_str() == "\n" {
+ if format_args.template.len() == 1 && last.as_str() == "\n" {
// print!("\n"), write!(f, "\n")
diag.multipart_suggestion(
@@ -398,11 +406,12 @@ fn check_newline(cx: &LateContext<'_>, format_args: &FormatArgsExpn<'_>, macro_c
}
}
-fn check_empty_string(cx: &LateContext<'_>, format_args: &FormatArgsExpn<'_>, macro_call: &MacroCall, name: &str) {
- if let [part] = &format_args.format_string.parts[..]
- && let mut span = format_args.format_string.span
- && part.as_str() == "\n"
+fn check_empty_string(cx: &LateContext<'_>, format_args: &FormatArgs, macro_call: &MacroCall, name: &str) {
+ if let [FormatArgsPiece::Literal(literal)] = &format_args.template[..]
+ && literal.as_str() == "\n"
{
+ let mut span = format_args.span;
+
let lint = if name == "writeln" {
span = expand_past_previous_comma(cx, span);
@@ -428,33 +437,49 @@ fn check_empty_string(cx: &LateContext<'_>, format_args: &FormatArgsExpn<'_>, ma
}
}
-fn check_literal(cx: &LateContext<'_>, format_args: &FormatArgsExpn<'_>, name: &str) {
- let mut counts = HirIdMap::<usize>::default();
- for param in format_args.params() {
- *counts.entry(param.value.hir_id).or_default() += 1;
+fn check_literal(cx: &LateContext<'_>, format_args: &FormatArgs, name: &str) {
+ let arg_index = |argument: &FormatArgPosition| argument.index.unwrap_or_else(|pos| pos);
+
+ let mut counts = vec![0u32; format_args.arguments.all_args().len()];
+ for piece in &format_args.template {
+ if let FormatArgsPiece::Placeholder(placeholder) = piece {
+ counts[arg_index(&placeholder.argument)] += 1;
+ }
}
- for arg in &format_args.args {
- let value = arg.param.value;
-
- if counts[&value.hir_id] == 1
- && arg.format.is_default()
- && let ExprKind::Lit(lit) = &value.kind
- && !value.span.from_expansion()
- && let Some(value_string) = snippet_opt(cx, value.span)
- {
- let (replacement, replace_raw) = match lit.node {
- LitKind::Str(..) => extract_str_literal(&value_string),
- LitKind::Char(ch) => (
- match ch {
- '"' => "\\\"",
- '\'' => "'",
- _ => &value_string[1..value_string.len() - 1],
+ for piece in &format_args.template {
+ if let FormatArgsPiece::Placeholder(FormatPlaceholder {
+ argument,
+ span: Some(placeholder_span),
+ format_trait: FormatTrait::Display,
+ format_options,
+ }) = piece
+ && *format_options == FormatOptions::default()
+ && let index = arg_index(argument)
+ && counts[index] == 1
+ && let Some(arg) = format_args.arguments.by_index(index)
+ && let rustc_ast::ExprKind::Lit(lit) = &arg.expr.kind
+ && !arg.expr.span.from_expansion()
+ && let Some(value_string) = snippet_opt(cx, arg.expr.span)
+ {
+ let (replacement, replace_raw) = match lit.kind {
+ LitKind::Str | LitKind::StrRaw(_) => match extract_str_literal(&value_string) {
+ Some(extracted) => extracted,
+ None => return,
+ },
+ LitKind::Char => (
+ match lit.symbol.as_str() {
+ "\"" => "\\\"",
+ "\\'" => "'",
+ _ => match value_string.strip_prefix('\'').and_then(|s| s.strip_suffix('\'')) {
+ Some(stripped) => stripped,
+ None => return,
+ },
}
.to_string(),
false,
),
- LitKind::Bool(b) => (b.to_string(), false),
+ LitKind::Bool => (lit.symbol.to_string(), false),
_ => continue,
};
@@ -464,7 +489,9 @@ fn check_literal(cx: &LateContext<'_>, format_args: &FormatArgsExpn<'_>, name: &
PRINT_LITERAL
};
- let format_string_is_raw = format_args.format_string.style.is_some();
+ let Some(format_string_snippet) = snippet_opt(cx, format_args.span) else { continue };
+ let format_string_is_raw = format_string_snippet.starts_with('r');
+
let replacement = match (format_string_is_raw, replace_raw) {
(false, false) => Some(replacement),
(false, true) => Some(replacement.replace('"', "\\\"").replace('\\', "\\\\")),
@@ -485,23 +512,24 @@ fn check_literal(cx: &LateContext<'_>, format_args: &FormatArgsExpn<'_>, name: &
span_lint_and_then(
cx,
lint,
- value.span,
+ arg.expr.span,
"literal with an empty format string",
|diag| {
if let Some(replacement) = replacement
// `format!("{}", "a")`, `format!("{named}", named = "b")
// ~~~~~ ~~~~~~~~~~~~~
- && let Some(value_span) = format_args.value_with_prev_comma_span(value.hir_id)
+ && let Some(removal_span) = format_arg_removal_span(format_args, index)
{
let replacement = replacement.replace('{', "{{").replace('}', "}}");
diag.multipart_suggestion(
"try this",
- vec![(arg.span, replacement), (value_span, String::new())],
+ vec![(*placeholder_span, replacement), (removal_span, String::new())],
Applicability::MachineApplicable,
);
}
},
);
+
}
}
}
@@ -511,13 +539,13 @@ fn check_literal(cx: &LateContext<'_>, format_args: &FormatArgsExpn<'_>, name: &
/// `r#"a"#` -> (`a`, true)
///
/// `"b"` -> (`b`, false)
-fn extract_str_literal(literal: &str) -> (String, bool) {
+fn extract_str_literal(literal: &str) -> Option<(String, bool)> {
let (literal, raw) = match literal.strip_prefix('r') {
Some(stripped) => (stripped.trim_matches('#'), true),
None => (literal, false),
};
- (literal[1..literal.len() - 1].to_string(), raw)
+ Some((literal.strip_prefix('"')?.strip_suffix('"')?.to_string(), raw))
}
enum UnescapeErr {