Skip to content

Commit eea4c36

Browse files
authored
Merge branch 'rust-lang:master' into master
2 parents 264e20a + 76eee82 commit eea4c36

21 files changed

+418
-64
lines changed

book/src/configuration.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ Very rarely, you may wish to prevent Clippy from evaluating certain sections of
133133
`clippy` cfg is not set. You may need to provide a stub so that the code compiles:
134134

135135
```rust
136-
#[cfg(not(clippy)]
136+
#[cfg(not(clippy))]
137137
include!(concat!(env!("OUT_DIR"), "/my_big_function-generated.rs"));
138138

139139
#[cfg(clippy)]

clippy_lints/src/dereference.rs

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ use clippy_utils::diagnostics::{span_lint_and_sugg, span_lint_hir_and_then};
22
use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
33
use clippy_utils::sugg::has_enclosing_paren;
44
use clippy_utils::ty::{implements_trait, is_manually_drop, peel_mid_ty_refs};
5-
use clippy_utils::{expr_use_ctxt, get_parent_expr, is_lint_allowed, path_to_local, DefinedTy, ExprUseNode};
5+
use clippy_utils::{
6+
expr_use_ctxt, get_parent_expr, is_block_like, is_lint_allowed, path_to_local, DefinedTy, ExprUseNode,
7+
};
68
use core::mem;
79
use rustc_ast::util::parser::{PREC_POSTFIX, PREC_PREFIX};
810
use rustc_data_structures::fx::FxIndexMap;
@@ -1038,14 +1040,8 @@ fn report<'tcx>(
10381040
);
10391041
},
10401042
State::ExplicitDeref { mutability } => {
1041-
if matches!(
1042-
expr.kind,
1043-
ExprKind::Block(..)
1044-
| ExprKind::ConstBlock(_)
1045-
| ExprKind::If(..)
1046-
| ExprKind::Loop(..)
1047-
| ExprKind::Match(..)
1048-
) && let ty::Ref(_, ty, _) = data.adjusted_ty.kind()
1043+
if is_block_like(expr)
1044+
&& let ty::Ref(_, ty, _) = data.adjusted_ty.kind()
10491045
&& ty.is_sized(cx.tcx, cx.param_env)
10501046
{
10511047
// Rustc bug: auto deref doesn't work on block expression when targeting sized types.

clippy_lints/src/methods/iter_on_single_or_empty_collections.rs

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1+
use std::iter::once;
2+
13
use clippy_utils::diagnostics::span_lint_and_sugg;
24
use clippy_utils::source::snippet;
35
use clippy_utils::{get_expr_use_or_unification_node, is_res_lang_ctor, path_res, std_or_core};
46

57
use rustc_errors::Applicability;
8+
use rustc_hir::def_id::DefId;
9+
use rustc_hir::hir_id::HirId;
610
use rustc_hir::LangItem::{OptionNone, OptionSome};
711
use rustc_hir::{Expr, ExprKind, Node};
812
use rustc_lint::LateContext;
@@ -25,7 +29,29 @@ impl IterType {
2529
}
2630
}
2731

28-
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, method_name: &str, recv: &Expr<'_>) {
32+
fn is_arg_ty_unified_in_fn<'tcx>(
33+
cx: &LateContext<'tcx>,
34+
fn_id: DefId,
35+
arg_id: HirId,
36+
args: impl IntoIterator<Item = &'tcx Expr<'tcx>>,
37+
) -> bool {
38+
let fn_sig = cx.tcx.fn_sig(fn_id).instantiate_identity();
39+
let arg_id_in_args = args.into_iter().position(|e| e.hir_id == arg_id).unwrap();
40+
let arg_ty_in_args = fn_sig.input(arg_id_in_args).skip_binder();
41+
42+
cx.tcx.predicates_of(fn_id).predicates.iter().any(|(clause, _)| {
43+
clause
44+
.as_projection_clause()
45+
.and_then(|p| p.map_bound(|p| p.term.ty()).transpose())
46+
.is_some_and(|ty| ty.skip_binder() == arg_ty_in_args)
47+
}) || fn_sig
48+
.inputs()
49+
.iter()
50+
.enumerate()
51+
.any(|(i, ty)| i != arg_id_in_args && ty.skip_binder().walk().any(|arg| arg.as_type() == Some(arg_ty_in_args)))
52+
}
53+
54+
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, method_name: &str, recv: &'tcx Expr<'tcx>) {
2955
let item = match recv.kind {
3056
ExprKind::Array([]) => None,
3157
ExprKind::Array([e]) => Some(e),
@@ -43,6 +69,25 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, method_name: &str, re
4369
let is_unified = match get_expr_use_or_unification_node(cx.tcx, expr) {
4470
Some((Node::Expr(parent), child_id)) => match parent.kind {
4571
ExprKind::If(e, _, _) | ExprKind::Match(e, _, _) if e.hir_id == child_id => false,
72+
ExprKind::Call(
73+
Expr {
74+
kind: ExprKind::Path(path),
75+
hir_id,
76+
..
77+
},
78+
args,
79+
) => cx
80+
.typeck_results()
81+
.qpath_res(path, *hir_id)
82+
.opt_def_id()
83+
.filter(|fn_id| cx.tcx.def_kind(fn_id).is_fn_like())
84+
.is_some_and(|fn_id| is_arg_ty_unified_in_fn(cx, fn_id, child_id, args)),
85+
ExprKind::MethodCall(_name, recv, args, _span) => is_arg_ty_unified_in_fn(
86+
cx,
87+
cx.typeck_results().type_dependent_def_id(parent.hir_id).unwrap(),
88+
child_id,
89+
once(recv).chain(args.iter()),
90+
),
4691
ExprKind::If(_, _, _)
4792
| ExprKind::Match(_, _, _)
4893
| ExprKind::Closure(_)

clippy_lints/src/methods/unnecessary_iter_cloned.rs

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ pub fn check_for_loop_iter(
3838
) -> bool {
3939
if let Some(grandparent) = get_parent_expr(cx, expr).and_then(|parent| get_parent_expr(cx, parent))
4040
&& let Some(ForLoop { pat, body, .. }) = ForLoop::hir(grandparent)
41-
&& let (clone_or_copy_needed, addr_of_exprs) = clone_or_copy_needed(cx, pat, body)
41+
&& let (clone_or_copy_needed, references_to_binding) = clone_or_copy_needed(cx, pat, body)
4242
&& !clone_or_copy_needed
4343
&& let Some(receiver_snippet) = snippet_opt(cx, receiver.span)
4444
{
@@ -123,14 +123,12 @@ pub fn check_for_loop_iter(
123123
Applicability::MachineApplicable
124124
};
125125
diag.span_suggestion(expr.span, "use", snippet, applicability);
126-
for addr_of_expr in addr_of_exprs {
127-
match addr_of_expr.kind {
128-
ExprKind::AddrOf(_, _, referent) => {
129-
let span = addr_of_expr.span.with_hi(referent.span.lo());
130-
diag.span_suggestion(span, "remove this `&`", "", applicability);
131-
},
132-
_ => unreachable!(),
133-
}
126+
if !references_to_binding.is_empty() {
127+
diag.multipart_suggestion(
128+
"remove any references to the binding",
129+
references_to_binding,
130+
applicability,
131+
);
134132
}
135133
},
136134
);

clippy_lints/src/methods/utils.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use rustc_lint::LateContext;
99
use rustc_middle::hir::nested_filter;
1010
use rustc_middle::ty::{self, Ty};
1111
use rustc_span::symbol::sym;
12+
use rustc_span::Span;
1213

1314
pub(super) fn derefs_to_slice<'tcx>(
1415
cx: &LateContext<'tcx>,
@@ -96,15 +97,15 @@ pub(super) fn clone_or_copy_needed<'tcx>(
9697
cx: &LateContext<'tcx>,
9798
pat: &Pat<'tcx>,
9899
body: &'tcx Expr<'tcx>,
99-
) -> (bool, Vec<&'tcx Expr<'tcx>>) {
100+
) -> (bool, Vec<(Span, String)>) {
100101
let mut visitor = CloneOrCopyVisitor {
101102
cx,
102103
binding_hir_ids: pat_bindings(pat),
103104
clone_or_copy_needed: false,
104-
addr_of_exprs: Vec::new(),
105+
references_to_binding: Vec::new(),
105106
};
106107
visitor.visit_expr(body);
107-
(visitor.clone_or_copy_needed, visitor.addr_of_exprs)
108+
(visitor.clone_or_copy_needed, visitor.references_to_binding)
108109
}
109110

110111
/// Returns a vector of all `HirId`s bound by the pattern.
@@ -127,7 +128,7 @@ struct CloneOrCopyVisitor<'cx, 'tcx> {
127128
cx: &'cx LateContext<'tcx>,
128129
binding_hir_ids: Vec<HirId>,
129130
clone_or_copy_needed: bool,
130-
addr_of_exprs: Vec<&'tcx Expr<'tcx>>,
131+
references_to_binding: Vec<(Span, String)>,
131132
}
132133

133134
impl<'cx, 'tcx> Visitor<'tcx> for CloneOrCopyVisitor<'cx, 'tcx> {
@@ -142,8 +143,11 @@ impl<'cx, 'tcx> Visitor<'tcx> for CloneOrCopyVisitor<'cx, 'tcx> {
142143
if self.is_binding(expr) {
143144
if let Some(parent) = get_parent_expr(self.cx, expr) {
144145
match parent.kind {
145-
ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, _) => {
146-
self.addr_of_exprs.push(parent);
146+
ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, referent) => {
147+
if !parent.span.from_expansion() {
148+
self.references_to_binding
149+
.push((parent.span.until(referent.span), String::new()));
150+
}
147151
return;
148152
},
149153
ExprKind::MethodCall(.., args, _) => {

clippy_lints/src/needless_bool.rs

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ use clippy_utils::diagnostics::{span_lint, span_lint_and_sugg};
66
use clippy_utils::source::snippet_with_applicability;
77
use clippy_utils::sugg::Sugg;
88
use clippy_utils::{
9-
higher, is_else_clause, is_expn_of, is_parent_stmt, peel_blocks, peel_blocks_with_stmt, span_extract_comment,
10-
SpanlessEq,
9+
higher, is_block_like, is_else_clause, is_expn_of, is_parent_stmt, peel_blocks, peel_blocks_with_stmt,
10+
span_extract_comment, SpanlessEq,
1111
};
1212
use rustc_ast::ast::LitKind;
1313
use rustc_errors::Applicability;
@@ -121,14 +121,7 @@ fn condition_needs_parentheses(e: &Expr<'_>) -> bool {
121121
| ExprKind::Type(i, _)
122122
| ExprKind::Index(i, _, _) = inner.kind
123123
{
124-
if matches!(
125-
i.kind,
126-
ExprKind::Block(..)
127-
| ExprKind::ConstBlock(..)
128-
| ExprKind::If(..)
129-
| ExprKind::Loop(..)
130-
| ExprKind::Match(..)
131-
) {
124+
if is_block_like(i) {
132125
return true;
133126
}
134127
inner = i;

clippy_lints/src/returns.rs

Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ use clippy_utils::source::{snippet_opt, snippet_with_context};
33
use clippy_utils::sugg::has_enclosing_paren;
44
use clippy_utils::visitors::{for_each_expr_with_closures, Descend};
55
use clippy_utils::{
6-
fn_def_id, is_from_proc_macro, is_inside_let_else, is_res_lang_ctor, path_res, path_to_local_id, span_contains_cfg,
7-
span_find_starting_semi,
6+
binary_expr_needs_parentheses, fn_def_id, is_from_proc_macro, is_inside_let_else, is_res_lang_ctor, path_res,
7+
path_to_local_id, span_contains_cfg, span_find_starting_semi,
88
};
99
use core::ops::ControlFlow;
1010
use rustc_errors::Applicability;
@@ -129,7 +129,7 @@ enum RetReplacement<'tcx> {
129129
Empty,
130130
Block,
131131
Unit,
132-
IfSequence(Cow<'tcx, str>, Applicability),
132+
NeedsPar(Cow<'tcx, str>, Applicability),
133133
Expr(Cow<'tcx, str>, Applicability),
134134
}
135135

@@ -139,13 +139,13 @@ impl<'tcx> RetReplacement<'tcx> {
139139
Self::Empty | Self::Expr(..) => "remove `return`",
140140
Self::Block => "replace `return` with an empty block",
141141
Self::Unit => "replace `return` with a unit value",
142-
Self::IfSequence(..) => "remove `return` and wrap the sequence with parentheses",
142+
Self::NeedsPar(..) => "remove `return` and wrap the sequence with parentheses",
143143
}
144144
}
145145

146146
fn applicability(&self) -> Applicability {
147147
match self {
148-
Self::Expr(_, ap) | Self::IfSequence(_, ap) => *ap,
148+
Self::Expr(_, ap) | Self::NeedsPar(_, ap) => *ap,
149149
_ => Applicability::MachineApplicable,
150150
}
151151
}
@@ -157,7 +157,7 @@ impl<'tcx> Display for RetReplacement<'tcx> {
157157
Self::Empty => write!(f, ""),
158158
Self::Block => write!(f, "{{}}"),
159159
Self::Unit => write!(f, "()"),
160-
Self::IfSequence(inner, _) => write!(f, "({inner})"),
160+
Self::NeedsPar(inner, _) => write!(f, "({inner})"),
161161
Self::Expr(inner, _) => write!(f, "{inner}"),
162162
}
163163
}
@@ -244,7 +244,11 @@ impl<'tcx> LateLintPass<'tcx> for Return {
244244
err.span_label(local.span, "unnecessary `let` binding");
245245

246246
if let Some(mut snippet) = snippet_opt(cx, initexpr.span) {
247-
if !cx.typeck_results().expr_adjustments(retexpr).is_empty() {
247+
if binary_expr_needs_parentheses(initexpr) {
248+
if !has_enclosing_paren(&snippet) {
249+
snippet = format!("({snippet})");
250+
}
251+
} else if !cx.typeck_results().expr_adjustments(retexpr).is_empty() {
248252
if !has_enclosing_paren(&snippet) {
249253
snippet = format!("({snippet})");
250254
}
@@ -349,8 +353,8 @@ fn check_final_expr<'tcx>(
349353

350354
let mut applicability = Applicability::MachineApplicable;
351355
let (snippet, _) = snippet_with_context(cx, inner_expr.span, ret_span.ctxt(), "..", &mut applicability);
352-
if expr_contains_conjunctive_ifs(inner_expr) {
353-
RetReplacement::IfSequence(snippet, applicability)
356+
if binary_expr_needs_parentheses(inner_expr) {
357+
RetReplacement::NeedsPar(snippet, applicability)
354358
} else {
355359
RetReplacement::Expr(snippet, applicability)
356360
}
@@ -404,18 +408,6 @@ fn check_final_expr<'tcx>(
404408
}
405409
}
406410

407-
fn expr_contains_conjunctive_ifs<'tcx>(expr: &'tcx Expr<'tcx>) -> bool {
408-
fn contains_if(expr: &Expr<'_>, on_if: bool) -> bool {
409-
match expr.kind {
410-
ExprKind::If(..) => on_if,
411-
ExprKind::Binary(_, left, right) => contains_if(left, true) || contains_if(right, true),
412-
_ => false,
413-
}
414-
}
415-
416-
contains_if(expr, false)
417-
}
418-
419411
fn emit_return_lint(
420412
cx: &LateContext<'_>,
421413
ret_span: Span,

clippy_utils/src/lib.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3370,3 +3370,25 @@ pub fn is_parent_stmt(cx: &LateContext<'_>, id: HirId) -> bool {
33703370
Node::Stmt(..) | Node::Block(Block { stmts: &[], .. })
33713371
)
33723372
}
3373+
3374+
/// Returns true if the given `expr` is a block or resembled as a block,
3375+
/// such as `if`, `loop`, `match` expressions etc.
3376+
pub fn is_block_like(expr: &Expr<'_>) -> bool {
3377+
matches!(
3378+
expr.kind,
3379+
ExprKind::Block(..) | ExprKind::ConstBlock(..) | ExprKind::If(..) | ExprKind::Loop(..) | ExprKind::Match(..)
3380+
)
3381+
}
3382+
3383+
/// Returns true if the given `expr` is binary expression that needs to be wrapped in parentheses.
3384+
pub fn binary_expr_needs_parentheses(expr: &Expr<'_>) -> bool {
3385+
fn contains_block(expr: &Expr<'_>, is_operand: bool) -> bool {
3386+
match expr.kind {
3387+
ExprKind::Binary(_, lhs, _) => contains_block(lhs, true),
3388+
_ if is_block_like(expr) => is_operand,
3389+
_ => false,
3390+
}
3391+
}
3392+
3393+
contains_block(expr, false)
3394+
}

tests/ui/iter_on_empty_collections.fixed

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,28 @@ fn array() {
2020
};
2121

2222
let _ = if false { ["test"].iter() } else { [].iter() };
23+
24+
let smth = Some(vec![1, 2, 3]);
25+
26+
// Don't trigger when the empty collection iter is relied upon for its concrete type
27+
// But do trigger if it is just an iterator, despite being an argument to a method
28+
for i in smth.as_ref().map_or([].iter(), |s| s.iter()).chain(std::iter::empty()) {
29+
println!("{i}");
30+
}
31+
32+
// Same as above, but for empty collection iters with extra layers
33+
for i in smth.as_ref().map_or({ [].iter() }, |s| s.iter()) {
34+
println!("{y}", y = i + 1);
35+
}
36+
37+
// Same as above, but for regular function calls
38+
for i in Option::map_or(smth.as_ref(), [].iter(), |s| s.iter()) {
39+
println!("{i}");
40+
}
41+
42+
// Same as above, but when there are no predicates that mention the collection iter type.
43+
let mut iter = [34, 228, 35].iter();
44+
let _ = std::mem::replace(&mut iter, [].iter());
2345
}
2446

2547
macro_rules! in_macros {

tests/ui/iter_on_empty_collections.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,28 @@ fn array() {
2020
};
2121

2222
let _ = if false { ["test"].iter() } else { [].iter() };
23+
24+
let smth = Some(vec![1, 2, 3]);
25+
26+
// Don't trigger when the empty collection iter is relied upon for its concrete type
27+
// But do trigger if it is just an iterator, despite being an argument to a method
28+
for i in smth.as_ref().map_or([].iter(), |s| s.iter()).chain([].iter()) {
29+
println!("{i}");
30+
}
31+
32+
// Same as above, but for empty collection iters with extra layers
33+
for i in smth.as_ref().map_or({ [].iter() }, |s| s.iter()) {
34+
println!("{y}", y = i + 1);
35+
}
36+
37+
// Same as above, but for regular function calls
38+
for i in Option::map_or(smth.as_ref(), [].iter(), |s| s.iter()) {
39+
println!("{i}");
40+
}
41+
42+
// Same as above, but when there are no predicates that mention the collection iter type.
43+
let mut iter = [34, 228, 35].iter();
44+
let _ = std::mem::replace(&mut iter, [].iter());
2345
}
2446

2547
macro_rules! in_macros {

tests/ui/iter_on_empty_collections.stderr

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,5 +37,11 @@ error: `iter` call on an empty collection
3737
LL | assert_eq!(None.iter().next(), Option::<&i32>::None);
3838
| ^^^^^^^^^^^ help: try: `std::iter::empty()`
3939

40-
error: aborting due to 6 previous errors
40+
error: `iter` call on an empty collection
41+
--> tests/ui/iter_on_empty_collections.rs:28:66
42+
|
43+
LL | for i in smth.as_ref().map_or([].iter(), |s| s.iter()).chain([].iter()) {
44+
| ^^^^^^^^^ help: try: `std::iter::empty()`
45+
46+
error: aborting due to 7 previous errors
4147

0 commit comments

Comments
 (0)