Auto merge of #13136 - xFrednet:07797-restriction-and-then, r=blyxyas

Make restriction lint's use `span_lint_and_then` (a -> e)

This migrates a few restriction lints to use `span_lint_and_then`. This change is motivated by https://github.com/rust-lang/rust-clippy/issues/7797.

I'm also interested if it will have an impact on performance. With some of these lints, like [`clippy::implicit_return`](https://rust-lang.github.io/rust-clippy/master/index.html#/implicit_return) I expect an impact, as it was previously creating a suggestion **for every implicit return** which is just wild.

I've also cleaned up some lint message. Mostly minor stuff. For example: suggestions with a longer message than `"try"` now use `SuggestionStyle::ShowAlways`

---

`@blyxyas` Could you benchmark this PR? I want to get all the numbers :3

---

This also crashed our new lintcheck CI with the following message:

> Error: $GITHUB_STEP_SUMMARY upload aborted, supports content up to a size of 1024k, got 46731k. For more information see: https://docs.github.com/actions/using-workflows/workflow-commands-for-github-actions#adding-a-markdown-summary

Which is just wild. Like, I've [tested the first 20 lints](https://github.com/xFrednet/rust-clippy/actions/runs/10027528172) and got like four changes and then this. 50 MB of changed lint messages o.O. Looks like I'll create a separate PR to fix that step ^^

---

cc: https://github.com/rust-lang/rust-clippy/issues/7797

changelog: none

r? `@blyxyas`
This commit is contained in:
bors 2024-08-05 13:33:16 +00:00
commit c082bc2cb8
34 changed files with 927 additions and 470 deletions

View file

@ -1,4 +1,4 @@
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::is_from_proc_macro;
use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass, LintContext};
@ -52,13 +52,15 @@ impl<'tcx> LateLintPass<'tcx> for AsConversions {
&& !in_external_macro(cx.sess(), expr.span)
&& !is_from_proc_macro(cx, expr)
{
span_lint_and_help(
#[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")]
span_lint_and_then(
cx,
AS_CONVERSIONS,
expr.span,
"using a potentially dangerous silent `as` conversion",
None,
"consider using a safe wrapper for this conversion",
|diag| {
diag.help("consider using a safe wrapper for this conversion");
},
);
}
}

View file

@ -1,4 +1,4 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::macros::{find_assert_args, root_macro_call_first_node, PanicExpn};
use clippy_utils::source::snippet_with_context;
use clippy_utils::ty::{has_debug_impl, is_copy, is_type_diagnostic_item};
@ -68,39 +68,28 @@ impl<'tcx> LateLintPass<'tcx> for AssertionsOnResultStates {
return;
}
}
let semicolon = if is_expr_final_block_expr(cx.tcx, e) { ";" } else { "" };
let mut app = Applicability::MachineApplicable;
match method_segment.ident.as_str() {
let (message, replacement) = match method_segment.ident.as_str() {
"is_ok" if type_suitable_to_unwrap(cx, args.type_at(1)) => {
span_lint_and_sugg(
cx,
ASSERTIONS_ON_RESULT_STATES,
macro_call.span,
"called `assert!` with `Result::is_ok`",
"replace with",
format!(
"{}.unwrap(){semicolon}",
snippet_with_context(cx, recv.span, condition.span.ctxt(), "..", &mut app).0
),
app,
);
("called `assert!` with `Result::is_ok`", "unwrap")
},
"is_err" if type_suitable_to_unwrap(cx, args.type_at(0)) => {
span_lint_and_sugg(
cx,
ASSERTIONS_ON_RESULT_STATES,
macro_call.span,
"called `assert!` with `Result::is_err`",
"replace with",
format!(
"{}.unwrap_err(){semicolon}",
snippet_with_context(cx, recv.span, condition.span.ctxt(), "..", &mut app).0
),
app,
);
("called `assert!` with `Result::is_err`", "unwrap_err")
},
_ => (),
_ => return,
};
span_lint_and_then(cx, ASSERTIONS_ON_RESULT_STATES, macro_call.span, message, |diag| {
let semicolon = if is_expr_final_block_expr(cx.tcx, e) { ";" } else { "" };
let mut app = Applicability::MachineApplicable;
diag.span_suggestion(
macro_call.span,
"replace with",
format!(
"{}.{replacement}(){semicolon}",
snippet_with_context(cx, recv.span, condition.span.ctxt(), "..", &mut app).0
),
app,
);
});
}
}
}

View file

@ -1,5 +1,5 @@
use super::ALLOW_ATTRIBUTES;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::is_from_proc_macro;
use rustc_ast::{AttrStyle, Attribute};
use rustc_errors::Applicability;
@ -13,14 +13,14 @@ pub fn check<'cx>(cx: &LateContext<'cx>, attr: &'cx Attribute) {
&& let Some(ident) = attr.ident()
&& !is_from_proc_macro(cx, attr)
{
span_lint_and_sugg(
cx,
ALLOW_ATTRIBUTES,
ident.span,
"#[allow] attribute found",
"replace it with",
"expect".into(),
Applicability::MachineApplicable,
);
#[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")]
span_lint_and_then(cx, ALLOW_ATTRIBUTES, ident.span, "#[allow] attribute found", |diag| {
diag.span_suggestion(
ident.span,
"replace it with",
"expect",
Applicability::MachineApplicable,
);
});
}
}

View file

@ -1,5 +1,5 @@
use super::{Attribute, ALLOW_ATTRIBUTES_WITHOUT_REASON};
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::is_from_proc_macro;
use rustc_ast::{MetaItemKind, NestedMetaItem};
use rustc_lint::{LateContext, LintContext};
@ -21,12 +21,14 @@ pub(super) fn check<'cx>(cx: &LateContext<'cx>, name: Symbol, items: &[NestedMet
return;
}
span_lint_and_help(
#[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")]
span_lint_and_then(
cx,
ALLOW_ATTRIBUTES_WITHOUT_REASON,
attr.span,
format!("`{}` attribute without specifying a reason", name.as_str()),
None,
"try adding a reason at the end with `, reason = \"..\"`",
|diag| {
diag.help("try adding a reason at the end with `, reason = \"..\"`");
},
);
}

View file

@ -1,6 +1,6 @@
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_errors::{Applicability, SuggestionStyle};
use rustc_hir::Expr;
use rustc_lint::LateContext;
use rustc_middle::ty::{self, Ty};
@ -14,21 +14,24 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, cast_expr: &Expr<'_>,
_ => { /* continue to checks */ },
}
match cast_from.kind() {
ty::FnDef(..) | ty::FnPtr(_) => {
let mut applicability = Applicability::MaybeIncorrect;
let from_snippet = snippet_with_applicability(cx, cast_expr.span, "..", &mut applicability);
if let ty::FnDef(..) | ty::FnPtr(_) = cast_from.kind() {
let mut applicability = Applicability::MaybeIncorrect;
let from_snippet = snippet_with_applicability(cx, cast_expr.span, "..", &mut applicability);
span_lint_and_sugg(
cx,
FN_TO_NUMERIC_CAST_ANY,
expr.span,
format!("casting function pointer `{from_snippet}` to `{cast_to}`"),
"did you mean to invoke the function?",
format!("{from_snippet}() as {cast_to}"),
applicability,
);
},
_ => {},
span_lint_and_then(
cx,
FN_TO_NUMERIC_CAST_ANY,
expr.span,
format!("casting function pointer `{from_snippet}` to `{cast_to}`"),
|diag| {
diag.span_suggestion_with_style(
expr.span,
"did you mean to invoke the function?",
format!("{from_snippet}() as {cast_to}"),
applicability,
SuggestionStyle::ShowAlways,
);
},
);
}
}

View file

@ -1,6 +1,6 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::source::snippet;
use rustc_errors::Applicability;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet_with_applicability;
use rustc_errors::{Applicability, SuggestionStyle};
use rustc_hir::{Expr, ExprKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
@ -39,14 +39,24 @@ impl LateLintPass<'_> for CreateDir {
&& let Some(def_id) = cx.qpath_res(path, func.hir_id).opt_def_id()
&& cx.tcx.is_diagnostic_item(sym::fs_create_dir, def_id)
{
span_lint_and_sugg(
span_lint_and_then(
cx,
CREATE_DIR,
expr.span,
"calling `std::fs::create_dir` where there may be a better way",
"consider calling `std::fs::create_dir_all` instead",
format!("create_dir_all({})", snippet(cx, arg.span, "..")),
Applicability::MaybeIncorrect,
|diag| {
let mut app = Applicability::MaybeIncorrect;
diag.span_suggestion_with_style(
expr.span,
"consider calling `std::fs::create_dir_all` instead",
format!(
"create_dir_all({})",
snippet_with_applicability(cx, arg.span, "..", &mut app)
),
app,
SuggestionStyle::ShowAlways,
);
},
);
}
}

View file

@ -1,5 +1,5 @@
use clippy_config::Conf;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::is_in_test;
use clippy_utils::macros::{macro_backtrace, MacroCall};
use clippy_utils::source::snippet_with_applicability;
@ -65,61 +65,67 @@ impl LateLintPass<'_> for DbgMacro {
// allows `dbg!` in test code if allow-dbg-in-test is set to true in clippy.toml
!(self.allow_dbg_in_tests && is_in_test(cx.tcx, expr.hir_id))
{
let mut applicability = Applicability::MachineApplicable;
let (sugg_span, suggestion) = match expr.peel_drop_temps().kind {
// dbg!()
ExprKind::Block(..) => {
// If the `dbg!` macro is a "free" statement and not contained within other expressions,
// remove the whole statement.
if let Node::Stmt(_) = cx.tcx.parent_hir_node(expr.hir_id)
&& let Some(semi_span) = cx.sess().source_map().mac_call_stmt_semi_span(macro_call.span)
{
(macro_call.span.to(semi_span), String::new())
} else {
(macro_call.span, String::from("()"))
}
},
// dbg!(1)
ExprKind::Match(val, ..) => (
macro_call.span,
snippet_with_applicability(cx, val.span.source_callsite(), "..", &mut applicability).to_string(),
),
// dbg!(2, 3)
ExprKind::Tup(
[
Expr {
kind: ExprKind::Match(first, ..),
..
},
..,
Expr {
kind: ExprKind::Match(last, ..),
..
},
],
) => {
let snippet = snippet_with_applicability(
cx,
first.span.source_callsite().to(last.span.source_callsite()),
"..",
&mut applicability,
);
(macro_call.span, format!("({snippet})"))
},
_ => return,
};
self.prev_ctxt = cur_syntax_ctxt;
span_lint_and_sugg(
span_lint_and_then(
cx,
DBG_MACRO,
sugg_span,
macro_call.span,
"the `dbg!` macro is intended as a debugging tool",
"remove the invocation before committing it to a version control system",
suggestion,
applicability,
|diag| {
let mut applicability = Applicability::MachineApplicable;
let (sugg_span, suggestion) = match expr.peel_drop_temps().kind {
// dbg!()
ExprKind::Block(..) => {
// If the `dbg!` macro is a "free" statement and not contained within other expressions,
// remove the whole statement.
if let Node::Stmt(_) = cx.tcx.parent_hir_node(expr.hir_id)
&& let Some(semi_span) = cx.sess().source_map().mac_call_stmt_semi_span(macro_call.span)
{
(macro_call.span.to(semi_span), String::new())
} else {
(macro_call.span, String::from("()"))
}
},
// dbg!(1)
ExprKind::Match(val, ..) => (
macro_call.span,
snippet_with_applicability(cx, val.span.source_callsite(), "..", &mut applicability)
.to_string(),
),
// dbg!(2, 3)
ExprKind::Tup(
[
Expr {
kind: ExprKind::Match(first, ..),
..
},
..,
Expr {
kind: ExprKind::Match(last, ..),
..
},
],
) => {
let snippet = snippet_with_applicability(
cx,
first.span.source_callsite().to(last.span.source_callsite()),
"..",
&mut applicability,
);
(macro_call.span, format!("({snippet})"))
},
_ => unreachable!(),
};
diag.span_suggestion(
sugg_span,
"remove the invocation before committing it to a version control system",
suggestion,
applicability,
);
},
);
}
}

View file

@ -92,20 +92,8 @@ impl<'a, 'tcx> NumericFallbackVisitor<'a, 'tcx> {
let (suffix, is_float) = match lit_ty.kind() {
ty::Int(IntTy::I32) => ("i32", false),
ty::Float(FloatTy::F64) => ("f64", true),
// Default numeric fallback never results in other types.
_ => return,
};
let src = if let Some(src) = snippet_opt(self.cx, lit.span) {
src
} else {
match lit.node {
LitKind::Int(src, _) => format!("{src}"),
LitKind::Float(src, _) => format!("{src}"),
_ => return,
}
};
let sugg = numeric_literal::format(&src, Some(suffix), is_float);
span_lint_hir_and_then(
self.cx,
DEFAULT_NUMERIC_FALLBACK,
@ -113,6 +101,17 @@ impl<'a, 'tcx> NumericFallbackVisitor<'a, 'tcx> {
lit.span,
"default numeric fallback might occur",
|diag| {
let src = if let Some(src) = snippet_opt(self.cx, lit.span) {
src
} else {
match lit.node {
LitKind::Int(src, _) => format!("{src}"),
LitKind::Float(src, _) => format!("{src}"),
_ => unreachable!("Default numeric fallback never results in other types"),
}
};
let sugg = numeric_literal::format(&src, Some(suffix), is_float);
diag.span_suggestion(lit.span, "consider adding suffix", sugg, Applicability::MaybeIncorrect);
},
);

View file

@ -1,4 +1,4 @@
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::diagnostics::span_lint_and_then;
use rustc_hir::{HirId, Item, ItemKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::layout::LayoutOf;
@ -56,16 +56,18 @@ impl<'tcx> LateLintPass<'tcx> for DefaultUnionRepresentation {
&& is_union_with_two_non_zst_fields(cx, item)
&& !has_c_repr_attr(cx, item.hir_id())
{
span_lint_and_help(
#[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")]
span_lint_and_then(
cx,
DEFAULT_UNION_REPRESENTATION,
item.span,
"this union has the default representation",
None,
format!(
"consider annotating `{}` with `#[repr(C)]` to explicitly specify memory layout",
cx.tcx.def_path_str(item.owner_id)
),
|diag| {
diag.help(format!(
"consider annotating `{}` with `#[repr(C)]` to explicitly specify memory layout",
cx.tcx.def_path_str(item.owner_id)
));
},
);
}
}

View file

@ -1,6 +1,6 @@
//! Lint on if expressions with an else if, but without a final else branch.
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::diagnostics::span_lint_and_then;
use rustc_ast::ast::{Expr, ExprKind};
use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
use rustc_middle::lint::in_external_macro;
@ -54,13 +54,15 @@ impl EarlyLintPass for ElseIfWithoutElse {
&& let ExprKind::If(_, _, None) = els.kind
&& !in_external_macro(cx.sess(), item.span)
{
span_lint_and_help(
#[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")]
span_lint_and_then(
cx,
ELSE_IF_WITHOUT_ELSE,
els.span,
"`if` expression with an `else if`, but without a final `else`",
None,
"add an `else` block here",
|diag| {
diag.help("add an `else` block here");
},
);
}
}

View file

@ -1,4 +1,4 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::peel_blocks;
use rustc_errors::Applicability;
use rustc_hir::{Body, ExprKind, Impl, ImplItemKind, Item, ItemKind, Node};
@ -50,15 +50,14 @@ impl LateLintPass<'_> for EmptyDrop {
&& block.stmts.is_empty()
&& block.expr.is_none()
{
span_lint_and_sugg(
cx,
EMPTY_DROP,
item.span,
"empty drop implementation",
"try removing this impl",
String::new(),
Applicability::MaybeIncorrect,
);
span_lint_and_then(cx, EMPTY_DROP, item.span, "empty drop implementation", |diag| {
diag.span_suggestion_hidden(
item.span,
"try removing this impl",
String::new(),
Applicability::MaybeIncorrect,
);
});
}
}
}

View file

@ -7,7 +7,6 @@ use rustc_middle::lint::in_external_macro;
use rustc_middle::ty::Ty;
use rustc_session::declare_lint_pass;
use rustc_span::Symbol;
use std::borrow::Cow;
declare_clippy_lint! {
/// ### What it does
@ -141,52 +140,6 @@ fn maybe_lint_endian_bytes(cx: &LateContext<'_>, expr: &Expr<'_>, prefix: Prefix
_ => return,
};
let mut help = None;
'build_help: {
// all lints disallowed, don't give help here
if [&[lint], other_lints.as_slice()]
.concat()
.iter()
.all(|lint| !lint.allowed(cx, expr))
{
break 'build_help;
}
// ne_bytes and all other lints allowed
if lint.as_name(prefix) == ne && other_lints.iter().all(|lint| lint.allowed(cx, expr)) {
help = Some(Cow::Borrowed("specify the desired endianness explicitly"));
break 'build_help;
}
// le_bytes where ne_bytes allowed but be_bytes is not, or le_bytes where ne_bytes allowed but
// le_bytes is not
if (lint.as_name(prefix) == le || lint.as_name(prefix) == be) && LintKind::Host.allowed(cx, expr) {
help = Some(Cow::Borrowed("use the native endianness instead"));
break 'build_help;
}
let allowed_lints = other_lints.iter().filter(|lint| lint.allowed(cx, expr));
let len = allowed_lints.clone().count();
let mut help_str = "use ".to_owned();
for (i, lint) in allowed_lints.enumerate() {
let only_one = len == 1;
if !only_one {
help_str.push_str("either of ");
}
help_str.push_str(&format!("`{ty}::{}` ", lint.as_name(prefix)));
if i != len && !only_one {
help_str.push_str("or ");
}
}
help = Some(Cow::Owned(help_str + "instead"));
}
span_lint_and_then(
cx,
lint.as_lint(),
@ -198,9 +151,47 @@ fn maybe_lint_endian_bytes(cx: &LateContext<'_>, expr: &Expr<'_>, prefix: Prefix
if prefix == Prefix::To { " method" } else { "" },
),
move |diag| {
if let Some(help) = help {
diag.help(help);
// all lints disallowed, don't give help here
if [&[lint], other_lints.as_slice()]
.concat()
.iter()
.all(|lint| !lint.allowed(cx, expr))
{
return;
}
// ne_bytes and all other lints allowed
if lint.as_name(prefix) == ne && other_lints.iter().all(|lint| lint.allowed(cx, expr)) {
diag.help("specify the desired endianness explicitly");
return;
}
// le_bytes where ne_bytes allowed but be_bytes is not, or le_bytes where ne_bytes allowed but
// le_bytes is not
if (lint.as_name(prefix) == le || lint.as_name(prefix) == be) && LintKind::Host.allowed(cx, expr) {
diag.help("use the native endianness instead");
return;
}
let allowed_lints = other_lints.iter().filter(|lint| lint.allowed(cx, expr));
let len = allowed_lints.clone().count();
let mut help_str = "use ".to_owned();
for (i, lint) in allowed_lints.enumerate() {
let only_one = len == 1;
if !only_one {
help_str.push_str("either of ");
}
help_str.push_str(&format!("`{ty}::{}` ", lint.as_name(prefix)));
if i != len && !only_one {
help_str.push_str("or ");
}
}
help_str.push_str("instead");
diag.help(help_str);
},
);
}

View file

@ -88,11 +88,11 @@ impl LateLintPass<'_> for ExhaustiveItems {
&& !attrs.iter().any(|a| a.has_name(sym::non_exhaustive))
&& fields.iter().all(|f| cx.tcx.visibility(f.def_id).is_public())
{
let suggestion_span = item.span.shrink_to_lo();
let indent = " ".repeat(indent_of(cx, item.span).unwrap_or(0));
span_lint_and_then(cx, lint, item.span, msg, |diag| {
let suggestion_span = item.span.shrink_to_lo();
let indent = " ".repeat(indent_of(cx, item.span).unwrap_or(0));
let sugg = format!("#[non_exhaustive]\n{indent}");
diag.span_suggestion(
diag.span_suggestion_verbose(
suggestion_span,
"try adding #[non_exhaustive]",
sugg,

View file

@ -1,4 +1,4 @@
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::diagnostics::span_lint_and_then;
use rustc_ast::ast::{Item, ItemKind, VisibilityKind};
use rustc_lint::{EarlyContext, EarlyLintPass};
use rustc_session::declare_lint_pass;
@ -62,13 +62,15 @@ impl EarlyLintPass for FieldScopedVisibilityModifiers {
// pub(self) is equivalent to not using pub at all, so we ignore it
continue;
}
span_lint_and_help(
#[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")]
span_lint_and_then(
cx,
FIELD_SCOPED_VISIBILITY_MODIFIERS,
field.vis.span,
"scoped visibility modifier on a field",
None,
"consider making the field private and adding a scoped visibility method for it",
|diag| {
diag.help("consider making the field private and adding a scoped visibility method for it");
},
);
}
}

View file

@ -1,4 +1,4 @@
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::ty::is_type_lang_item;
use clippy_utils::{higher, match_def_path, paths};
use rustc_hir::{BinOpKind, Expr, ExprKind, LangItem, MatchSource};
@ -81,13 +81,15 @@ impl<'tcx> LateLintPass<'tcx> for FormatPushString {
_ => return,
};
if is_format(cx, arg) {
span_lint_and_help(
#[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")]
span_lint_and_then(
cx,
FORMAT_PUSH_STRING,
expr.span,
"`format!(..)` appended to existing `String`",
None,
"consider using `write!` to avoid the extra allocation",
|diag| {
diag.help("consider using `write!` to avoid the extra allocation");
},
);
}
}

View file

@ -1,6 +1,6 @@
use clippy_config::msrvs::{self, Msrv};
use clippy_config::Conf;
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::eager_or_lazy::switch_to_eager_eval;
use clippy_utils::source::snippet_with_context;
use clippy_utils::sugg::Sugg;
@ -81,32 +81,39 @@ impl<'tcx> LateLintPass<'tcx> for IfThenSomeElseNone {
&& self.msrv.meets(msrvs::BOOL_THEN)
&& !contains_return(then_block.stmts)
{
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 {
format!("{{ /* snippet */ {arg_snip} }}")
};
let method_name = if switch_to_eager_eval(cx, expr) && self.msrv.meets(msrvs::BOOL_THEN_SOME) {
"then_some"
} else {
method_body.insert_str(0, "|| ");
"then"
};
let help =
format!("consider using `bool::{method_name}` like: `{cond_snip}.{method_name}({method_body})`",);
span_lint_and_help(
span_lint_and_then(
cx,
IF_THEN_SOME_ELSE_NONE,
expr.span,
format!("this could be simplified with `bool::{method_name}`"),
None,
help,
|diag| {
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 method_body = if let Some(first_stmt) = then_block.stmts.first() {
let (block_snippet, _) =
snippet_with_context(cx, first_stmt.span.until(then_arg.span), ctxt, "..", &mut app);
let closure = if method_name == "then" { "|| " } else { "" };
format!("{closure} {{ {block_snippet}; {arg_snip} }}")
} else {
arg_snip.into_owned()
};
diag.span_suggestion(
expr.span,
"try",
format!("{cond_snip}.{method_name}({method_body})"),
app,
);
},
);
}
}

View file

@ -3,7 +3,7 @@ use clippy_utils::source::{snippet_with_applicability, snippet_with_context, wal
use clippy_utils::visitors::for_each_expr_without_closures;
use clippy_utils::{get_async_fn_body, is_async_fn, is_from_proc_macro};
use core::ops::ControlFlow;
use rustc_errors::Applicability;
use rustc_errors::{Applicability, SuggestionStyle};
use rustc_hir::intravisit::FnKind;
use rustc_hir::{Block, Body, Expr, ExprKind, FnDecl, FnRetTy, HirId};
use rustc_lint::{LateContext, LateLintPass, LintContext};
@ -45,8 +45,6 @@ declare_clippy_lint! {
declare_lint_pass!(ImplicitReturn => [IMPLICIT_RETURN]);
fn lint_return(cx: &LateContext<'_>, emission_place: HirId, span: Span) {
let mut app = Applicability::MachineApplicable;
let snip = snippet_with_applicability(cx, span, "..", &mut app);
span_lint_hir_and_then(
cx,
IMPLICIT_RETURN,
@ -54,14 +52,20 @@ fn lint_return(cx: &LateContext<'_>, emission_place: HirId, span: Span) {
span,
"missing `return` statement",
|diag| {
diag.span_suggestion(span, "add `return` as shown", format!("return {snip}"), app);
let mut app = Applicability::MachineApplicable;
let snip = snippet_with_applicability(cx, span, "..", &mut app);
diag.span_suggestion_with_style(
span,
"add `return` as shown",
format!("return {snip}"),
app,
SuggestionStyle::ShowAlways,
);
},
);
}
fn lint_break(cx: &LateContext<'_>, emission_place: HirId, break_span: Span, expr_span: Span) {
let mut app = Applicability::MachineApplicable;
let snip = snippet_with_context(cx, expr_span, break_span.ctxt(), "..", &mut app).0;
span_lint_hir_and_then(
cx,
IMPLICIT_RETURN,
@ -69,11 +73,14 @@ fn lint_break(cx: &LateContext<'_>, emission_place: HirId, break_span: Span, exp
break_span,
"missing `return` statement",
|diag| {
diag.span_suggestion(
let mut app = Applicability::MachineApplicable;
let snip = snippet_with_context(cx, expr_span, break_span.ctxt(), "..", &mut app).0;
diag.span_suggestion_with_style(
break_span,
"change `break` to `return` as shown",
format!("return {snip}"),
app,
SuggestionStyle::ShowAlways,
);
},
);

View file

@ -2,13 +2,13 @@
//! floating-point literal expressions.
use clippy_config::Conf;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::numeric_literal::{NumericLiteral, Radix};
use clippy_utils::source::snippet_opt;
use rustc_ast::ast::{Expr, ExprKind, LitKind};
use rustc_ast::token;
use rustc_errors::Applicability;
use rustc_lint::{EarlyContext, EarlyLintPass, LintContext};
use rustc_lint::{EarlyContext, EarlyLintPass, Lint, LintContext};
use rustc_middle::lint::in_external_macro;
use rustc_session::impl_lint_pass;
use rustc_span::Span;
@ -159,63 +159,39 @@ enum WarningType {
}
impl WarningType {
fn display(&self, suggested_format: String, cx: &EarlyContext<'_>, span: Span) {
fn lint_and_text(&self) -> (&'static Lint, &'static str, &'static str) {
match self {
Self::MistypedLiteralSuffix => span_lint_and_sugg(
cx,
Self::MistypedLiteralSuffix => (
MISTYPED_LITERAL_SUFFIXES,
span,
"mistyped literal suffix",
"did you mean to write",
suggested_format,
Applicability::MaybeIncorrect,
),
Self::UnreadableLiteral => span_lint_and_sugg(
cx,
UNREADABLE_LITERAL,
span,
"long literal lacking separators",
"consider",
suggested_format,
Applicability::MachineApplicable,
),
Self::LargeDigitGroups => span_lint_and_sugg(
cx,
LARGE_DIGIT_GROUPS,
span,
"digit groups should be smaller",
"consider",
suggested_format,
Applicability::MachineApplicable,
),
Self::InconsistentDigitGrouping => span_lint_and_sugg(
cx,
Self::UnreadableLiteral => (UNREADABLE_LITERAL, "long literal lacking separators", "consider"),
Self::LargeDigitGroups => (LARGE_DIGIT_GROUPS, "digit groups should be smaller", "consider"),
Self::InconsistentDigitGrouping => (
INCONSISTENT_DIGIT_GROUPING,
span,
"digits grouped inconsistently by underscores",
"consider",
suggested_format,
Applicability::MachineApplicable,
),
Self::DecimalRepresentation => span_lint_and_sugg(
cx,
Self::DecimalRepresentation => (
DECIMAL_LITERAL_REPRESENTATION,
span,
"integer literal has a better hexadecimal representation",
"consider",
suggested_format,
Applicability::MachineApplicable,
),
Self::UnusualByteGroupings => span_lint_and_sugg(
cx,
Self::UnusualByteGroupings => (
UNUSUAL_BYTE_GROUPINGS,
span,
"digits of hex, binary or octal literal not in groups of equal size",
"consider",
suggested_format,
Applicability::MachineApplicable,
),
};
}
}
fn display(&self, num_lit: &NumericLiteral<'_>, cx: &EarlyContext<'_>, span: Span) {
let (lint, message, try_msg) = self.lint_and_text();
#[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")]
span_lint_and_then(cx, lint, span, message, |diag| {
diag.span_suggestion(span, try_msg, num_lit.format(), Applicability::MaybeIncorrect);
});
}
}
@ -293,7 +269,7 @@ impl LiteralDigitGrouping {
WarningType::DecimalRepresentation | WarningType::MistypedLiteralSuffix => true,
};
if should_warn {
warning_type.display(num_lit.format(), cx, span);
warning_type.display(&num_lit, cx, span);
}
}
}
@ -346,11 +322,14 @@ impl LiteralDigitGrouping {
}
}
*part = main_part;
let mut sugg = num_lit.format();
sugg.push('_');
sugg.push(missing_char);
sugg.push_str(last_group);
WarningType::MistypedLiteralSuffix.display(sugg, cx, span);
let (lint, message, try_msg) = WarningType::MistypedLiteralSuffix.lint_and_text();
span_lint_and_then(cx, lint, span, message, |diag| {
let mut sugg = num_lit.format();
sugg.push('_');
sugg.push(missing_char);
sugg.push_str(last_group);
diag.span_suggestion(span, try_msg, sugg, Applicability::MaybeIncorrect);
});
false
} else {
true
@ -471,7 +450,7 @@ impl DecimalLiteralRepresentation {
let hex = format!("{val:#X}");
let num_lit = NumericLiteral::new(&hex, num_lit.suffix, false);
let _: Result<(), ()> = Self::do_lint(num_lit.integer).map_err(|warning_type| {
warning_type.display(num_lit.format(), cx, span);
warning_type.display(&num_lit, cx, span);
});
}
}

View file

@ -1,4 +1,4 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet_with_context;
use rustc_errors::Applicability;
use rustc_hir as hir;
@ -29,19 +29,22 @@ pub(super) fn check(
sym::RcWeak | sym::ArcWeak => "Weak",
_ => return,
};
// 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(
span_lint_and_then(
cx,
CLONE_ON_REF_PTR,
expr.span,
"using `.clone()` on a ref-counted pointer",
"try",
format!("{caller_type}::<{}>::clone(&{snippet})", subst.type_at(0)),
app,
|diag| {
// 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;
diag.span_suggestion(
expr.span,
"try",
format!("{caller_type}::<{}>::clone(&{snippet})", subst.type_at(0)),
app,
);
},
);
}
}

View file

@ -1,4 +1,4 @@
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::get_parent_expr;
use clippy_utils::ty::is_type_diagnostic_item;
use rustc_hir as hir;
@ -33,6 +33,9 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, recv: &hir::Expr
span = expr.span;
}
let lint_msg = format!("`{lint_unary}FileType::is_file()` only {verb} regular files");
let help_msg = format!("use `{help_unary}FileType::is_dir()` instead");
span_lint_and_help(cx, FILETYPE_IS_FILE, span, lint_msg, None, help_msg);
#[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")]
span_lint_and_then(cx, FILETYPE_IS_FILE, span, lint_msg, |diag| {
diag.help(format!("use `{help_unary}FileType::is_dir()` instead"));
});
}

View file

@ -1,5 +1,5 @@
use super::utils::derefs_to_slice;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::get_parent_expr;
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::ty::is_type_diagnostic_item;
@ -19,9 +19,7 @@ pub(super) fn check<'tcx>(
) {
// Note: we don't want to lint `get_mut().unwrap` for `HashMap` or `BTreeMap`,
// because they do not implement `IndexMut`
let mut applicability = Applicability::MachineApplicable;
let expr_ty = cx.typeck_results().expr_ty(recv);
let get_args_str = snippet_with_applicability(cx, get_arg.span, "..", &mut applicability);
let caller_type = if derefs_to_slice(cx, recv, expr_ty).is_some() {
"slice"
} else if is_type_diagnostic_item(cx, expr_ty, sym::Vec) {
@ -58,24 +56,34 @@ pub(super) fn check<'tcx>(
};
let mut_str = if is_mut { "_mut" } else { "" };
let borrow_str = if !needs_ref {
""
} else if is_mut {
"&mut "
} else {
"&"
};
span_lint_and_sugg(
span_lint_and_then(
cx,
GET_UNWRAP,
span,
format!("called `.get{mut_str}().unwrap()` on a {caller_type}. Using `[]` is more clear and more concise"),
"try",
format!(
"{borrow_str}{}[{get_args_str}]",
snippet_with_applicability(cx, recv.span, "..", &mut applicability)
),
applicability,
format!("called `.get{mut_str}().unwrap()` on a {caller_type}"),
|diag| {
let mut applicability = Applicability::MachineApplicable;
let get_args_str = snippet_with_applicability(cx, get_arg.span, "..", &mut applicability);
let borrow_str = if !needs_ref {
""
} else if is_mut {
"&mut "
} else {
"&"
};
diag.span_suggestion_with_style(
span,
"using `[]` is clearer and more concise",
format!(
"{borrow_str}{}[{get_args_str}]",
snippet_with_applicability(cx, recv.span, "..", &mut applicability)
),
applicability,
rustc_errors::SuggestionStyle::ShowAlways,
);
},
);
}

View file

@ -1,4 +1,4 @@
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::source::snippet_with_context;
use clippy_utils::ty::is_type_lang_item;
use clippy_utils::{get_parent_expr, peel_middle_ty_refs};
@ -86,9 +86,8 @@ impl<'tcx> LateLintPass<'tcx> for RedundantSlicing {
let (indexed_ty, indexed_ref_count) = peel_middle_ty_refs(cx.typeck_results().expr_ty(indexed));
let parent_expr = get_parent_expr(cx, expr);
let needs_parens_for_prefix = parent_expr.map_or(false, |parent| parent.precedence().order() > PREC_PREFIX);
let mut app = Applicability::MachineApplicable;
let ((lint, msg), help, sugg) = if expr_ty == indexed_ty {
if expr_ty == indexed_ty {
if expr_ref_count > indexed_ref_count {
// Indexing takes self by reference and can't return a reference to that
// reference as it's a local variable. The only way this could happen is if
@ -99,7 +98,7 @@ impl<'tcx> LateLintPass<'tcx> for RedundantSlicing {
}
let deref_count = indexed_ref_count - expr_ref_count;
let (lint, reborrow_str, help_str) = if mutability == Mutability::Mut {
let ((lint, msg), reborrow_str, help_msg) = if mutability == Mutability::Mut {
// The slice was used to reborrow the mutable reference.
(DEREF_BY_SLICING_LINT, "&mut *", "reborrow the original value instead")
} else if matches!(
@ -125,38 +124,36 @@ impl<'tcx> LateLintPass<'tcx> for RedundantSlicing {
(REDUNDANT_SLICING_LINT, "", "use the original value instead")
};
let snip = snippet_with_context(cx, indexed.span, ctxt, "..", &mut app).0;
let sugg = if (deref_count != 0 || !reborrow_str.is_empty()) && needs_parens_for_prefix {
format!("({reborrow_str}{}{snip})", "*".repeat(deref_count))
} else {
format!("{reborrow_str}{}{snip}", "*".repeat(deref_count))
};
(lint, help_str, sugg)
span_lint_and_then(cx, lint, expr.span, msg, |diag| {
let mut app = Applicability::MachineApplicable;
let snip = snippet_with_context(cx, indexed.span, ctxt, "..", &mut app).0;
let sugg = if (deref_count != 0 || !reborrow_str.is_empty()) && needs_parens_for_prefix {
format!("({reborrow_str}{}{snip})", "*".repeat(deref_count))
} else {
format!("{reborrow_str}{}{snip}", "*".repeat(deref_count))
};
diag.span_suggestion(expr.span, help_msg, sugg, app);
});
} else if let Some(target_id) = cx.tcx.lang_items().deref_target() {
if let Ok(deref_ty) = cx.tcx.try_normalize_erasing_regions(
cx.param_env,
Ty::new_projection_from_args(cx.tcx, target_id, cx.tcx.mk_args(&[GenericArg::from(indexed_ty)])),
) {
if deref_ty == expr_ty {
let snip = snippet_with_context(cx, indexed.span, ctxt, "..", &mut app).0;
let sugg = if needs_parens_for_prefix {
format!("(&{}{}*{snip})", mutability.prefix_str(), "*".repeat(indexed_ref_count))
} else {
format!("&{}{}*{snip}", mutability.prefix_str(), "*".repeat(indexed_ref_count))
};
(DEREF_BY_SLICING_LINT, "dereference the original value instead", sugg)
} else {
return;
let (lint, msg) = DEREF_BY_SLICING_LINT;
span_lint_and_then(cx, lint, expr.span, msg, |diag| {
let mut app = Applicability::MachineApplicable;
let snip = snippet_with_context(cx, indexed.span, ctxt, "..", &mut app).0;
let sugg = if needs_parens_for_prefix {
format!("(&{}{}*{snip})", mutability.prefix_str(), "*".repeat(indexed_ref_count))
} else {
format!("&{}{}*{snip}", mutability.prefix_str(), "*".repeat(indexed_ref_count))
};
diag.span_suggestion(expr.span, "dereference the original value instead", sugg, app);
});
}
} else {
return;
}
} else {
return;
};
span_lint_and_sugg(cx, lint, expr.span, msg, help, sugg, app);
}
}
}
}

View file

@ -1,6 +1,6 @@
use clippy_config::msrvs::Msrv;
use clippy_config::Conf;
use clippy_utils::diagnostics::span_lint_and_sugg;
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::is_from_proc_macro;
use rustc_attr::{StabilityLevel, StableSince};
use rustc_errors::Applicability;
@ -136,14 +136,20 @@ impl<'tcx> LateLintPass<'tcx> for StdReexports {
_ => return,
};
if first_segment.ident.span != self.prev_span {
span_lint_and_sugg(
#[expect(clippy::collapsible_span_lint_calls, reason = "rust-clippy#7797")]
span_lint_and_then(
cx,
lint,
first_segment.ident.span,
format!("used import from `{used_mod}` instead of `{replace_with}`"),
format!("consider importing the item from `{replace_with}`"),
replace_with.to_string(),
Applicability::MachineApplicable,
|diag| {
diag.span_suggestion(
first_segment.ident.span,
format!("consider importing the item from `{replace_with}`"),
replace_with.to_string(),
Applicability::MachineApplicable,
);
},
);
self.prev_span = first_segment.ident.span;
}