From 0524773b5d9f2460b706b83c275bb5b5c28e358c Mon Sep 17 00:00:00 2001 From: yanglsh Date: Sat, 24 May 2025 14:31:52 +0800 Subject: [PATCH 1/3] fix: `unit_arg` suggests wrongly for `Default::default` --- clippy_lints/src/default.rs | 18 ++---------------- clippy_lints/src/unit_types/unit_arg.rs | 16 +++++++++------- clippy_utils/src/lib.rs | 13 +++++++++++++ ...pty_blocks.fixed => unit_arg_fixable.fixed} | 6 ++++++ ...arg_empty_blocks.rs => unit_arg_fixable.rs} | 6 ++++++ ...y_blocks.stderr => unit_arg_fixable.stderr} | 18 +++++++++++++----- 6 files changed, 49 insertions(+), 28 deletions(-) rename tests/ui/{unit_arg_empty_blocks.fixed => unit_arg_fixable.fixed} (87%) rename tests/ui/{unit_arg_empty_blocks.rs => unit_arg_fixable.rs} (84%) rename tests/ui/{unit_arg_empty_blocks.stderr => unit_arg_fixable.stderr} (69%) diff --git a/clippy_lints/src/default.rs b/clippy_lints/src/default.rs index 886c325b355f..a48e4d2fbd57 100644 --- a/clippy_lints/src/default.rs +++ b/clippy_lints/src/default.rs @@ -1,10 +1,9 @@ use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_sugg}; use clippy_utils::source::snippet_with_context; use clippy_utils::ty::{has_drop, is_copy}; -use clippy_utils::{contains_name, get_parent_expr, in_automatically_derived, is_from_proc_macro}; +use clippy_utils::{contains_name, get_parent_expr, in_automatically_derived, is_expr_default, is_from_proc_macro}; use rustc_data_structures::fx::FxHashSet; use rustc_errors::Applicability; -use rustc_hir::def::Res; use rustc_hir::{Block, Expr, ExprKind, PatKind, QPath, Stmt, StmtKind, StructTailExpr}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty; @@ -129,7 +128,7 @@ impl<'tcx> LateLintPass<'tcx> for Default { // only take bindings to identifiers && let PatKind::Binding(_, binding_id, ident, _) = local.pat.kind // only when assigning `... = Default::default()` - && is_expr_default(expr, cx) + && is_expr_default(cx, expr) && let binding_type = cx.typeck_results().node_type(binding_id) && let ty::Adt(adt, args) = *binding_type.kind() && adt.is_struct() @@ -251,19 +250,6 @@ impl<'tcx> LateLintPass<'tcx> for Default { } } -/// Checks if the given expression is the `default` method belonging to the `Default` trait. -fn is_expr_default<'tcx>(expr: &'tcx Expr<'tcx>, cx: &LateContext<'tcx>) -> bool { - if let ExprKind::Call(fn_expr, []) = &expr.kind - && let ExprKind::Path(qpath) = &fn_expr.kind - && let Res::Def(_, def_id) = cx.qpath_res(qpath, fn_expr.hir_id) - { - // right hand side of assignment is `Default::default` - cx.tcx.is_diagnostic_item(sym::default_fn, def_id) - } else { - false - } -} - /// Returns the reassigned field and the assigning expression (right-hand side of assign). fn field_reassigned_by_stmt<'tcx>(this: &Stmt<'tcx>, binding_name: Symbol) -> Option<(Ident, &'tcx Expr<'tcx>)> { if let StmtKind::Semi(later_expr) = this.kind diff --git a/clippy_lints/src/unit_types/unit_arg.rs b/clippy_lints/src/unit_types/unit_arg.rs index 019ae16ca859..e09b362800c0 100644 --- a/clippy_lints/src/unit_types/unit_arg.rs +++ b/clippy_lints/src/unit_types/unit_arg.rs @@ -1,6 +1,6 @@ use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::is_from_proc_macro; use clippy_utils::source::{SourceText, SpanRangeExt, indent_of, reindent_multiline}; +use clippy_utils::{is_expr_default, is_from_proc_macro}; use rustc_errors::Applicability; use rustc_hir::{Block, Expr, ExprKind, MatchSource, Node, StmtKind}; use rustc_lint::LateContext; @@ -59,7 +59,7 @@ fn is_questionmark_desugar_marked_call(expr: &Expr<'_>) -> bool { } } -fn lint_unit_args(cx: &LateContext<'_>, expr: &Expr<'_>, args_to_recover: &[&Expr<'_>]) { +fn lint_unit_args<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, args_to_recover: &[&'tcx Expr<'tcx>]) { let mut applicability = Applicability::MachineApplicable; let (singular, plural) = if args_to_recover.len() > 1 { ("", "s") @@ -102,9 +102,11 @@ fn lint_unit_args(cx: &LateContext<'_>, expr: &Expr<'_>, args_to_recover: &[&Exp .iter() .filter_map(|arg| arg.span.get_source_text(cx)) .collect(); - let arg_snippets_without_empty_blocks: Vec<_> = args_to_recover + + // If the argument is an empty block or `Default::default()`, we can replace it with `()`. + let arg_snippets_without_redundant_exprs: Vec<_> = args_to_recover .iter() - .filter(|arg| !is_empty_block(arg)) + .filter(|arg| !is_empty_block(arg) && !is_expr_default(cx, arg)) .filter_map(|arg| arg.span.get_source_text(cx)) .collect(); @@ -114,10 +116,10 @@ fn lint_unit_args(cx: &LateContext<'_>, expr: &Expr<'_>, args_to_recover: &[&Exp expr, &call_snippet, &arg_snippets, - &arg_snippets_without_empty_blocks, + &arg_snippets_without_redundant_exprs, ); - if arg_snippets_without_empty_blocks.is_empty() { + if arg_snippets_without_redundant_exprs.is_empty() { db.multipart_suggestion( format!("use {singular}unit literal{plural} instead"), args_to_recover @@ -127,7 +129,7 @@ fn lint_unit_args(cx: &LateContext<'_>, expr: &Expr<'_>, args_to_recover: &[&Exp applicability, ); } else { - let plural = arg_snippets_without_empty_blocks.len() > 1; + let plural = arg_snippets_without_redundant_exprs.len() > 1; let empty_or_s = if plural { "s" } else { "" }; let it_or_them = if plural { "them" } else { "it" }; db.span_suggestion( diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index 3a3191765710..f3cd22de5229 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -3471,3 +3471,16 @@ pub fn desugar_await<'tcx>(expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> { None } } + +/// Checks if the given expression is the `default` method belonging to the `Default` trait. +pub fn is_expr_default<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool { + if let ExprKind::Call(fn_expr, []) = &expr.kind + && let ExprKind::Path(qpath) = &fn_expr.kind + && let Res::Def(_, def_id) = cx.qpath_res(qpath, fn_expr.hir_id) + { + // right hand side of assignment is `Default::default` + cx.tcx.is_diagnostic_item(sym::default_fn, def_id) + } else { + false + } +} diff --git a/tests/ui/unit_arg_empty_blocks.fixed b/tests/ui/unit_arg_fixable.fixed similarity index 87% rename from tests/ui/unit_arg_empty_blocks.fixed rename to tests/ui/unit_arg_fixable.fixed index b045a33608d7..7a25b93e5aee 100644 --- a/tests/ui/unit_arg_empty_blocks.fixed +++ b/tests/ui/unit_arg_fixable.fixed @@ -32,3 +32,9 @@ fn taking_three_units(a: (), b: (), c: ()) {} fn main() { bad(); } + +fn issue14857() { + let fn_take_unit = |_: ()| {}; + fn_take_unit(()); + //~^ unit_arg +} diff --git a/tests/ui/unit_arg_empty_blocks.rs b/tests/ui/unit_arg_fixable.rs similarity index 84% rename from tests/ui/unit_arg_empty_blocks.rs rename to tests/ui/unit_arg_fixable.rs index ab305913f3f6..65152abbe7b8 100644 --- a/tests/ui/unit_arg_empty_blocks.rs +++ b/tests/ui/unit_arg_fixable.rs @@ -29,3 +29,9 @@ fn taking_three_units(a: (), b: (), c: ()) {} fn main() { bad(); } + +fn issue14857() { + let fn_take_unit = |_: ()| {}; + fn_take_unit(Default::default()); + //~^ unit_arg +} diff --git a/tests/ui/unit_arg_empty_blocks.stderr b/tests/ui/unit_arg_fixable.stderr similarity index 69% rename from tests/ui/unit_arg_empty_blocks.stderr rename to tests/ui/unit_arg_fixable.stderr index 2c686d58ecc1..79afe67e5724 100644 --- a/tests/ui/unit_arg_empty_blocks.stderr +++ b/tests/ui/unit_arg_fixable.stderr @@ -1,5 +1,5 @@ error: passing a unit value to a function - --> tests/ui/unit_arg_empty_blocks.rs:16:5 + --> tests/ui/unit_arg_fixable.rs:16:5 | LL | foo({}); | ^^^^--^ @@ -10,7 +10,7 @@ LL | foo({}); = help: to override `-D warnings` add `#[allow(clippy::unit_arg)]` error: passing a unit value to a function - --> tests/ui/unit_arg_empty_blocks.rs:18:5 + --> tests/ui/unit_arg_fixable.rs:18:5 | LL | foo3({}, 2, 2); | ^^^^^--^^^^^^^ @@ -18,7 +18,7 @@ LL | foo3({}, 2, 2); | help: use a unit literal instead: `()` error: passing unit values to a function - --> tests/ui/unit_arg_empty_blocks.rs:20:5 + --> tests/ui/unit_arg_fixable.rs:20:5 | LL | taking_two_units({}, foo(0)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -30,7 +30,7 @@ LL ~ taking_two_units((), ()); | error: passing unit values to a function - --> tests/ui/unit_arg_empty_blocks.rs:22:5 + --> tests/ui/unit_arg_fixable.rs:22:5 | LL | taking_three_units({}, foo(0), foo(1)); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -42,5 +42,13 @@ LL + foo(1); LL ~ taking_three_units((), (), ()); | -error: aborting due to 4 previous errors +error: passing a unit value to a function + --> tests/ui/unit_arg_fixable.rs:35:5 + | +LL | fn_take_unit(Default::default()); + | ^^^^^^^^^^^^^------------------^ + | | + | help: use a unit literal instead: `()` + +error: aborting due to 5 previous errors From ebf39a54786ffb10a8ff5aec5504e4fbd29db19a Mon Sep 17 00:00:00 2001 From: yanglsh Date: Sat, 24 May 2025 21:10:46 +0800 Subject: [PATCH 2/3] fix: `unit_arg` wrongly handled macros --- clippy_lints/src/unit_types/unit_arg.rs | 54 ++++++++++++++++++------- clippy_utils/src/lib.rs | 3 +- tests/ui/unit_arg.rs | 24 +++++++++++ tests/ui/unit_arg.stderr | 23 ++++++++++- tests/ui/unit_arg_fixable.fixed | 30 ++++++++++++++ tests/ui/unit_arg_fixable.rs | 27 +++++++++++++ tests/ui/unit_arg_fixable.stderr | 46 ++++++++++++++++++++- 7 files changed, 188 insertions(+), 19 deletions(-) diff --git a/clippy_lints/src/unit_types/unit_arg.rs b/clippy_lints/src/unit_types/unit_arg.rs index e09b362800c0..74b3331414c3 100644 --- a/clippy_lints/src/unit_types/unit_arg.rs +++ b/clippy_lints/src/unit_types/unit_arg.rs @@ -1,9 +1,11 @@ use clippy_utils::diagnostics::span_lint_and_then; -use clippy_utils::source::{SourceText, SpanRangeExt, indent_of, reindent_multiline}; +use clippy_utils::source::{SpanRangeExt, indent_of, reindent_multiline}; +use clippy_utils::sugg::Sugg; use clippy_utils::{is_expr_default, is_from_proc_macro}; use rustc_errors::Applicability; use rustc_hir::{Block, Expr, ExprKind, MatchSource, Node, StmtKind}; use rustc_lint::LateContext; +use rustc_span::SyntaxContext; use super::{UNIT_ARG, utils}; @@ -100,14 +102,16 @@ fn lint_unit_args<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, args_to_ let arg_snippets: Vec<_> = args_to_recover .iter() - .filter_map(|arg| arg.span.get_source_text(cx)) + // If the argument is from an expansion and is a `Default::default()`, we skip it + .filter(|arg| !arg.span.from_expansion() || !is_expr_default_nested(cx, arg)) + .filter_map(|arg| get_expr_snippet(cx, arg)) .collect(); // If the argument is an empty block or `Default::default()`, we can replace it with `()`. let arg_snippets_without_redundant_exprs: Vec<_> = args_to_recover .iter() - .filter(|arg| !is_empty_block(arg) && !is_expr_default(cx, arg)) - .filter_map(|arg| arg.span.get_source_text(cx)) + .filter(|arg| !is_expr_default_nested(cx, arg) && (arg.span.from_expansion() || !is_empty_block(arg))) + .filter_map(|arg| get_expr_snippet(cx, arg)) .collect(); if let Some(call_snippet) = expr.span.get_source_text(cx) { @@ -119,13 +123,17 @@ fn lint_unit_args<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, args_to_ &arg_snippets_without_redundant_exprs, ); - if arg_snippets_without_redundant_exprs.is_empty() { + if arg_snippets_without_redundant_exprs.is_empty() + && let suggestions = args_to_recover + .iter() + .filter(|arg| !arg.span.from_expansion() || !is_expr_default_nested(cx, arg)) + .map(|arg| (arg.span.parent_callsite().unwrap_or(arg.span), "()".to_string())) + .collect::>() + && !suggestions.is_empty() + { db.multipart_suggestion( format!("use {singular}unit literal{plural} instead"), - args_to_recover - .iter() - .map(|arg| (arg.span, "()".to_string())) - .collect::>(), + suggestions, applicability, ); } else { @@ -146,6 +154,22 @@ fn lint_unit_args<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, args_to_ ); } +fn is_expr_default_nested<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool { + is_expr_default(cx, expr) + || matches!(expr.kind, ExprKind::Block(block, _) + if block.expr.is_some() && is_expr_default_nested(cx, block.expr.unwrap())) +} + +fn get_expr_snippet<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option> { + let mut app = Applicability::MachineApplicable; + let snip = Sugg::hir_with_context(cx, expr, SyntaxContext::root(), "..", &mut app); + if app != Applicability::MachineApplicable { + return None; + } + + Some(snip) +} + fn is_empty_block(expr: &Expr<'_>) -> bool { matches!( expr.kind, @@ -164,17 +188,17 @@ fn fmt_stmts_and_call( cx: &LateContext<'_>, call_expr: &Expr<'_>, call_snippet: &str, - args_snippets: &[SourceText], - non_empty_block_args_snippets: &[SourceText], + args_snippets: &[Sugg<'_>], + non_empty_block_args_snippets: &[Sugg<'_>], ) -> String { let call_expr_indent = indent_of(cx, call_expr.span).unwrap_or(0); - let call_snippet_with_replacements = args_snippets - .iter() - .fold(call_snippet.to_owned(), |acc, arg| acc.replacen(arg.as_ref(), "()", 1)); + let call_snippet_with_replacements = args_snippets.iter().fold(call_snippet.to_owned(), |acc, arg| { + acc.replacen(&arg.to_string(), "()", 1) + }); let mut stmts_and_call = non_empty_block_args_snippets .iter() - .map(|it| it.as_ref().to_owned()) + .map(ToString::to_string) .collect::>(); stmts_and_call.push(call_snippet_with_replacements); stmts_and_call = stmts_and_call diff --git a/clippy_utils/src/lib.rs b/clippy_utils/src/lib.rs index f3cd22de5229..4e588adf43eb 100644 --- a/clippy_utils/src/lib.rs +++ b/clippy_utils/src/lib.rs @@ -3472,13 +3472,12 @@ pub fn desugar_await<'tcx>(expr: &'tcx Expr<'_>) -> Option<&'tcx Expr<'tcx>> { } } -/// Checks if the given expression is the `default` method belonging to the `Default` trait. +/// Checks if the given expression is a call to `Default::default()`. pub fn is_expr_default<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool { if let ExprKind::Call(fn_expr, []) = &expr.kind && let ExprKind::Path(qpath) = &fn_expr.kind && let Res::Def(_, def_id) = cx.qpath_res(qpath, fn_expr.hir_id) { - // right hand side of assignment is `Default::default` cx.tcx.is_diagnostic_item(sym::default_fn, def_id) } else { false diff --git a/tests/ui/unit_arg.rs b/tests/ui/unit_arg.rs index 22a6a26dab62..4208efad6774 100644 --- a/tests/ui/unit_arg.rs +++ b/tests/ui/unit_arg.rs @@ -151,3 +151,27 @@ fn main() { bad(); ok(); } + +fn issue14857() { + let fn_take_unit = |_: ()| {}; + fn some_other_fn(_: &i32) {} + + macro_rules! mac { + (def) => { + Default::default() + }; + (func $f:expr) => { + $f() + }; + (nonempty_block $e:expr) => {{ + some_other_fn(&$e); + $e + }}; + } + fn_take_unit(mac!(def)); + //~^ unit_arg + fn_take_unit(mac!(func Default::default)); + //~^ unit_arg + fn_take_unit(mac!(nonempty_block Default::default())); + //~^ unit_arg +} diff --git a/tests/ui/unit_arg.stderr b/tests/ui/unit_arg.stderr index 6c333d9792d4..0dcfb02b9fa0 100644 --- a/tests/ui/unit_arg.stderr +++ b/tests/ui/unit_arg.stderr @@ -199,5 +199,26 @@ LL ~ foo(1); LL + Some(()) | -error: aborting due to 10 previous errors +error: passing a unit value to a function + --> tests/ui/unit_arg.rs:171:5 + | +LL | fn_take_unit(mac!(def)); + | ^^^^^^^^^^^^^^^^^^^^^^^ + | + +error: passing a unit value to a function + --> tests/ui/unit_arg.rs:173:5 + | +LL | fn_take_unit(mac!(func Default::default)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + +error: passing a unit value to a function + --> tests/ui/unit_arg.rs:175:5 + | +LL | fn_take_unit(mac!(nonempty_block Default::default())); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + +error: aborting due to 13 previous errors diff --git a/tests/ui/unit_arg_fixable.fixed b/tests/ui/unit_arg_fixable.fixed index 7a25b93e5aee..3ef6a953c729 100644 --- a/tests/ui/unit_arg_fixable.fixed +++ b/tests/ui/unit_arg_fixable.fixed @@ -37,4 +37,34 @@ fn issue14857() { let fn_take_unit = |_: ()| {}; fn_take_unit(()); //~^ unit_arg + + fn some_other_fn(_: &i32) {} + + macro_rules! another_mac { + () => { + some_other_fn(&Default::default()) + }; + ($e:expr) => { + some_other_fn(&$e) + }; + } + + another_mac!(); + fn_take_unit(()); + //~^ unit_arg + another_mac!(1); + fn_take_unit(()); + //~^ unit_arg + + macro_rules! mac { + (nondef $e:expr) => { + $e + }; + (empty_block) => {{}}; + } + fn_take_unit(mac!(nondef ())); + //~^ unit_arg + mac!(empty_block); + fn_take_unit(()); + //~^ unit_arg } diff --git a/tests/ui/unit_arg_fixable.rs b/tests/ui/unit_arg_fixable.rs index 65152abbe7b8..097d51e9481c 100644 --- a/tests/ui/unit_arg_fixable.rs +++ b/tests/ui/unit_arg_fixable.rs @@ -34,4 +34,31 @@ fn issue14857() { let fn_take_unit = |_: ()| {}; fn_take_unit(Default::default()); //~^ unit_arg + + fn some_other_fn(_: &i32) {} + + macro_rules! another_mac { + () => { + some_other_fn(&Default::default()) + }; + ($e:expr) => { + some_other_fn(&$e) + }; + } + + fn_take_unit(another_mac!()); + //~^ unit_arg + fn_take_unit(another_mac!(1)); + //~^ unit_arg + + macro_rules! mac { + (nondef $e:expr) => { + $e + }; + (empty_block) => {{}}; + } + fn_take_unit(mac!(nondef Default::default())); + //~^ unit_arg + fn_take_unit(mac!(empty_block)); + //~^ unit_arg } diff --git a/tests/ui/unit_arg_fixable.stderr b/tests/ui/unit_arg_fixable.stderr index 79afe67e5724..94063f02a3f1 100644 --- a/tests/ui/unit_arg_fixable.stderr +++ b/tests/ui/unit_arg_fixable.stderr @@ -50,5 +50,49 @@ LL | fn_take_unit(Default::default()); | | | help: use a unit literal instead: `()` -error: aborting due to 5 previous errors +error: passing a unit value to a function + --> tests/ui/unit_arg_fixable.rs:49:5 + | +LL | fn_take_unit(another_mac!()); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: move the expression in front of the call and replace it with the unit literal `()` + | +LL ~ another_mac!(); +LL ~ fn_take_unit(()); + | + +error: passing a unit value to a function + --> tests/ui/unit_arg_fixable.rs:51:5 + | +LL | fn_take_unit(another_mac!(1)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: move the expression in front of the call and replace it with the unit literal `()` + | +LL ~ another_mac!(1); +LL ~ fn_take_unit(()); + | + +error: passing a unit value to a function + --> tests/ui/unit_arg_fixable.rs:60:5 + | +LL | fn_take_unit(mac!(nondef Default::default())); + | ^^^^^^^^^^^^^^^^^^^^^^^^^------------------^^ + | | + | help: use a unit literal instead: `()` + +error: passing a unit value to a function + --> tests/ui/unit_arg_fixable.rs:62:5 + | +LL | fn_take_unit(mac!(empty_block)); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +help: move the expression in front of the call and replace it with the unit literal `()` + | +LL ~ mac!(empty_block); +LL ~ fn_take_unit(()); + | + +error: aborting due to 9 previous errors From 8029208130666a6f69dab5b1e47d3298cc50922f Mon Sep 17 00:00:00 2001 From: yanglsh Date: Sun, 1 Jun 2025 01:29:44 +0800 Subject: [PATCH 3/3] Lint more cases in `unit_arg` --- clippy_lints/src/unit_types/unit_arg.rs | 70 ++++++++++++++++++------- tests/ui/unit_arg_fixable.fixed | 8 +++ tests/ui/unit_arg_fixable.rs | 7 +++ tests/ui/unit_arg_fixable.stderr | 14 ++++- 4 files changed, 79 insertions(+), 20 deletions(-) diff --git a/clippy_lints/src/unit_types/unit_arg.rs b/clippy_lints/src/unit_types/unit_arg.rs index 74b3331414c3..ae6d8a1c1aa3 100644 --- a/clippy_lints/src/unit_types/unit_arg.rs +++ b/clippy_lints/src/unit_types/unit_arg.rs @@ -1,6 +1,9 @@ +use std::iter; + use clippy_utils::diagnostics::span_lint_and_then; use clippy_utils::source::{SpanRangeExt, indent_of, reindent_multiline}; use clippy_utils::sugg::Sugg; +use clippy_utils::ty::expr_type_is_certain; use clippy_utils::{is_expr_default, is_from_proc_macro}; use rustc_errors::Applicability; use rustc_hir::{Block, Expr, ExprKind, MatchSource, Node, StmtKind}; @@ -111,18 +114,10 @@ fn lint_unit_args<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, args_to_ let arg_snippets_without_redundant_exprs: Vec<_> = args_to_recover .iter() .filter(|arg| !is_expr_default_nested(cx, arg) && (arg.span.from_expansion() || !is_empty_block(arg))) - .filter_map(|arg| get_expr_snippet(cx, arg)) + .filter_map(|arg| get_expr_snippet_with_type_certainty(cx, arg)) .collect(); if let Some(call_snippet) = expr.span.get_source_text(cx) { - let sugg = fmt_stmts_and_call( - cx, - expr, - &call_snippet, - &arg_snippets, - &arg_snippets_without_redundant_exprs, - ); - if arg_snippets_without_redundant_exprs.is_empty() && let suggestions = args_to_recover .iter() @@ -138,6 +133,13 @@ fn lint_unit_args<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, args_to_ ); } else { let plural = arg_snippets_without_redundant_exprs.len() > 1; + let sugg = fmt_stmts_and_call( + cx, + expr, + &call_snippet, + arg_snippets, + arg_snippets_without_redundant_exprs, + ); let empty_or_s = if plural { "s" } else { "" }; let it_or_them = if plural { "them" } else { "it" }; db.span_suggestion( @@ -160,6 +162,20 @@ fn is_expr_default_nested<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) if block.expr.is_some() && is_expr_default_nested(cx, block.expr.unwrap())) } +enum MaybeTypeUncertain<'tcx> { + Certain(Sugg<'tcx>), + Uncertain(Sugg<'tcx>), +} + +impl From> for String { + fn from(value: MaybeTypeUncertain<'_>) -> Self { + match value { + MaybeTypeUncertain::Certain(sugg) => sugg.to_string(), + MaybeTypeUncertain::Uncertain(sugg) => format!("let _: () = {sugg}"), + } + } +} + fn get_expr_snippet<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Option> { let mut app = Applicability::MachineApplicable; let snip = Sugg::hir_with_context(cx, expr, SyntaxContext::root(), "..", &mut app); @@ -170,6 +186,25 @@ fn get_expr_snippet<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> Opt Some(snip) } +fn get_expr_snippet_with_type_certainty<'tcx>( + cx: &LateContext<'tcx>, + expr: &'tcx Expr<'tcx>, +) -> Option> { + get_expr_snippet(cx, expr).map(|snip| { + // If the type of the expression is certain, we can use it directly. + // Otherwise, we wrap it in a `let _: () = ...` to ensure the type is correct. + if !expr_type_is_certain(cx, expr) && !is_block_with_no_expr(expr) { + MaybeTypeUncertain::Uncertain(snip) + } else { + MaybeTypeUncertain::Certain(snip) + } + }) +} + +fn is_block_with_no_expr(expr: &Expr<'_>) -> bool { + matches!(expr.kind, ExprKind::Block(Block { expr: None, .. }, _)) +} + fn is_empty_block(expr: &Expr<'_>) -> bool { matches!( expr.kind, @@ -188,23 +223,20 @@ fn fmt_stmts_and_call( cx: &LateContext<'_>, call_expr: &Expr<'_>, call_snippet: &str, - args_snippets: &[Sugg<'_>], - non_empty_block_args_snippets: &[Sugg<'_>], + args_snippets: Vec>, + non_empty_block_args_snippets: Vec>, ) -> String { let call_expr_indent = indent_of(cx, call_expr.span).unwrap_or(0); - let call_snippet_with_replacements = args_snippets.iter().fold(call_snippet.to_owned(), |acc, arg| { + let call_snippet_with_replacements = args_snippets.into_iter().fold(call_snippet.to_owned(), |acc, arg| { acc.replacen(&arg.to_string(), "()", 1) }); - let mut stmts_and_call = non_empty_block_args_snippets - .iter() - .map(ToString::to_string) - .collect::>(); - stmts_and_call.push(call_snippet_with_replacements); - stmts_and_call = stmts_and_call + let stmts_and_call = non_empty_block_args_snippets .into_iter() + .map(Into::into) + .chain(iter::once(call_snippet_with_replacements)) .map(|v| reindent_multiline(&v, true, Some(call_expr_indent))) - .collect(); + .collect::>(); let mut stmts_and_call_snippet = stmts_and_call.join(&format!("{}{}", ";\n", " ".repeat(call_expr_indent))); // expr is not in a block statement or result expression position, wrap in a block diff --git a/tests/ui/unit_arg_fixable.fixed b/tests/ui/unit_arg_fixable.fixed index 3ef6a953c729..03353a14081b 100644 --- a/tests/ui/unit_arg_fixable.fixed +++ b/tests/ui/unit_arg_fixable.fixed @@ -67,4 +67,12 @@ fn issue14857() { mac!(empty_block); fn_take_unit(()); //~^ unit_arg + + fn def() -> T { + Default::default() + } + + let _: () = def(); + fn_take_unit(()); + //~^ unit_arg } diff --git a/tests/ui/unit_arg_fixable.rs b/tests/ui/unit_arg_fixable.rs index 097d51e9481c..03fd96efdf90 100644 --- a/tests/ui/unit_arg_fixable.rs +++ b/tests/ui/unit_arg_fixable.rs @@ -61,4 +61,11 @@ fn issue14857() { //~^ unit_arg fn_take_unit(mac!(empty_block)); //~^ unit_arg + + fn def() -> T { + Default::default() + } + + fn_take_unit(def()); + //~^ unit_arg } diff --git a/tests/ui/unit_arg_fixable.stderr b/tests/ui/unit_arg_fixable.stderr index 94063f02a3f1..ccd5aa8322f9 100644 --- a/tests/ui/unit_arg_fixable.stderr +++ b/tests/ui/unit_arg_fixable.stderr @@ -94,5 +94,17 @@ LL ~ mac!(empty_block); LL ~ fn_take_unit(()); | -error: aborting due to 9 previous errors +error: passing a unit value to a function + --> tests/ui/unit_arg_fixable.rs:69:5 + | +LL | fn_take_unit(def()); + | ^^^^^^^^^^^^^^^^^^^ + | +help: move the expression in front of the call and replace it with the unit literal `()` + | +LL ~ let _: () = def(); +LL ~ fn_take_unit(()); + | + +error: aborting due to 10 previous errors