Skip to content

Commit ecb2d5c

Browse files
committed
diagnostics: fix borrowck suggestions for if/while let conditionals
This code detects the case where one of the borrows is inside the let init expr while the other end is not. If that happens, we don't want to suggest adding a semicolon, because it won't work.
1 parent e08cd3c commit ecb2d5c

File tree

3 files changed

+227
-18
lines changed

3 files changed

+227
-18
lines changed

compiler/rustc_borrowck/src/diagnostics/explain_borrow.rs

+92-18
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,98 @@ impl<'tcx> BorrowExplanation<'tcx> {
248248
);
249249
err.span_label(body.source_info(drop_loc).span, message);
250250

251-
if let LocalInfo::BlockTailTemp(info) = local_decl.local_info() {
251+
struct FindLetExpr<'hir> {
252+
span: Span,
253+
result: Option<(Span, &'hir hir::Pat<'hir>, &'hir hir::Expr<'hir>)>,
254+
tcx: TyCtxt<'hir>,
255+
}
256+
257+
impl<'hir> rustc_hir::intravisit::Visitor<'hir> for FindLetExpr<'hir> {
258+
type NestedFilter = rustc_middle::hir::nested_filter::OnlyBodies;
259+
fn nested_visit_map(&mut self) -> Self::Map {
260+
self.tcx.hir()
261+
}
262+
fn visit_expr(&mut self, expr: &'hir hir::Expr<'hir>) {
263+
if let hir::ExprKind::If(cond, _conseq, _alt)
264+
| hir::ExprKind::Loop(
265+
hir::Block {
266+
expr:
267+
Some(&hir::Expr {
268+
kind: hir::ExprKind::If(cond, _conseq, _alt),
269+
..
270+
}),
271+
..
272+
},
273+
_,
274+
hir::LoopSource::While,
275+
_,
276+
) = expr.kind
277+
&& let hir::ExprKind::Let(hir::LetExpr {
278+
init: let_expr_init,
279+
span: let_expr_span,
280+
pat: let_expr_pat,
281+
..
282+
}) = cond.kind
283+
&& let_expr_init.span.contains(self.span)
284+
{
285+
self.result =
286+
Some((*let_expr_span, let_expr_pat, let_expr_init))
287+
} else {
288+
hir::intravisit::walk_expr(self, expr);
289+
}
290+
}
291+
}
292+
293+
if let &LocalInfo::IfThenRescopeTemp { if_then } = local_decl.local_info()
294+
&& let hir::Node::Expr(expr) = tcx.hir_node(if_then)
295+
&& let hir::ExprKind::If(cond, conseq, alt) = expr.kind
296+
&& let hir::ExprKind::Let(&hir::LetExpr {
297+
span: _,
298+
pat,
299+
init,
300+
// FIXME(#101728): enable rewrite when type ascription is
301+
// stabilized again.
302+
ty: None,
303+
recovered: _,
304+
}) = cond.kind
305+
&& pat.span.can_be_used_for_suggestions()
306+
&& let Ok(pat) = tcx.sess.source_map().span_to_snippet(pat.span)
307+
{
308+
suggest_rewrite_if_let(tcx, expr, &pat, init, conseq, alt, err);
309+
} else if let Some((old, new)) = multiple_borrow_span
310+
&& let def_id = body.source.def_id()
311+
&& let Some(node) = tcx.hir().get_if_local(def_id)
312+
&& let Some(body_id) = node.body_id()
313+
&& let hir_body = tcx.hir().body(body_id)
314+
&& let mut expr_finder = (FindLetExpr { span: old, result: None, tcx })
315+
&& let Some((let_expr_span, let_expr_pat, let_expr_init)) = {
316+
expr_finder.visit_expr(hir_body.value);
317+
expr_finder.result
318+
}
319+
&& !let_expr_span.contains(new)
320+
{
321+
// #133941: The `old` expression is at the conditional part of an
322+
// if/while let expression. Adding a semicolon won't work.
323+
// Instead, try suggesting the `matches!` macro or a temporary.
324+
if let_expr_pat
325+
.walk_short(|pat| !matches!(pat.kind, hir::PatKind::Binding(..)))
326+
{
327+
if let Ok(pat_snippet) =
328+
tcx.sess.source_map().span_to_snippet(let_expr_pat.span)
329+
&& let Ok(init_snippet) =
330+
tcx.sess.source_map().span_to_snippet(let_expr_init.span)
331+
{
332+
err.span_suggestion_verbose(
333+
let_expr_span,
334+
"consider using the `matches!` macro",
335+
format!("matches!({init_snippet}, {pat_snippet})"),
336+
Applicability::MaybeIncorrect,
337+
);
338+
} else {
339+
err.note("consider using the `matches!` macro");
340+
}
341+
}
342+
} else if let LocalInfo::BlockTailTemp(info) = local_decl.local_info() {
252343
if info.tail_result_is_ignored {
253344
// #85581: If the first mutable borrow's scope contains
254345
// the second borrow, this suggestion isn't helpful.
@@ -281,23 +372,6 @@ impl<'tcx> BorrowExplanation<'tcx> {
281372
Applicability::MaybeIncorrect,
282373
);
283374
};
284-
} else if let &LocalInfo::IfThenRescopeTemp { if_then } =
285-
local_decl.local_info()
286-
&& let hir::Node::Expr(expr) = tcx.hir_node(if_then)
287-
&& let hir::ExprKind::If(cond, conseq, alt) = expr.kind
288-
&& let hir::ExprKind::Let(&hir::LetExpr {
289-
span: _,
290-
pat,
291-
init,
292-
// FIXME(#101728): enable rewrite when type ascription is
293-
// stabilized again.
294-
ty: None,
295-
recovered: _,
296-
}) = cond.kind
297-
&& pat.span.can_be_used_for_suggestions()
298-
&& let Ok(pat) = tcx.sess.source_map().span_to_snippet(pat.span)
299-
{
300-
suggest_rewrite_if_let(tcx, expr, &pat, init, conseq, alt, err);
301375
}
302376
}
303377
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// https://github.com/rust-lang/rust/issues/133941
2+
use std::marker::PhantomData;
3+
4+
struct Bar<'a>(PhantomData<&'a mut i32>);
5+
6+
impl<'a> Drop for Bar<'a> {
7+
fn drop(&mut self) {}
8+
}
9+
10+
struct Foo();
11+
12+
impl Foo {
13+
fn f(&mut self) -> Option<Bar<'_>> {
14+
None
15+
}
16+
17+
fn g(&mut self) {}
18+
}
19+
20+
fn main() {
21+
let mut foo = Foo();
22+
while let Some(_) = foo.f() {
23+
//~^ HELP matches!
24+
foo.g();
25+
//~^ ERROR [E0499]
26+
}
27+
if let Some(_) = foo.f() {
28+
//~^ HELP matches!
29+
foo.g();
30+
//~^ ERROR [E0499]
31+
}
32+
while let Some(_x) = foo.f() {
33+
foo.g();
34+
//~^ ERROR [E0499]
35+
}
36+
if let Some(_x) = foo.f() {
37+
foo.g();
38+
//~^ ERROR [E0499]
39+
}
40+
while let Some(_x) = {let _x = foo.f(); foo.g(); None::<()>} {
41+
//~^ ERROR [E0499]
42+
}
43+
if let Some(_x) = {let _x = foo.f(); foo.g(); None::<()>} {
44+
//~^ ERROR [E0499]
45+
}
46+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
error[E0499]: cannot borrow `foo` as mutable more than once at a time
2+
--> $DIR/already-borrowed-as-mutable-if-let-133941.rs:24:9
3+
|
4+
LL | while let Some(_) = foo.f() {
5+
| -------
6+
| |
7+
| first mutable borrow occurs here
8+
| a temporary with access to the first borrow is created here ...
9+
LL |
10+
LL | foo.g();
11+
| ^^^ second mutable borrow occurs here
12+
LL |
13+
LL | }
14+
| - ... and the first borrow might be used here, when that temporary is dropped and runs the destructor for type `Option<Bar<'_>>`
15+
|
16+
help: consider using the `matches!` macro
17+
|
18+
LL | while matches!(foo.f(), Some(_)) {
19+
| ~~~~~~~~~~~~~~~~~~~~~~~~~~
20+
21+
error[E0499]: cannot borrow `foo` as mutable more than once at a time
22+
--> $DIR/already-borrowed-as-mutable-if-let-133941.rs:29:9
23+
|
24+
LL | if let Some(_) = foo.f() {
25+
| -------
26+
| |
27+
| first mutable borrow occurs here
28+
| a temporary with access to the first borrow is created here ...
29+
LL |
30+
LL | foo.g();
31+
| ^^^ second mutable borrow occurs here
32+
LL |
33+
LL | }
34+
| - ... and the first borrow might be used here, when that temporary is dropped and runs the destructor for type `Option<Bar<'_>>`
35+
|
36+
help: consider using the `matches!` macro
37+
|
38+
LL | if matches!(foo.f(), Some(_)) {
39+
| ~~~~~~~~~~~~~~~~~~~~~~~~~~
40+
41+
error[E0499]: cannot borrow `foo` as mutable more than once at a time
42+
--> $DIR/already-borrowed-as-mutable-if-let-133941.rs:33:9
43+
|
44+
LL | while let Some(_x) = foo.f() {
45+
| -------
46+
| |
47+
| first mutable borrow occurs here
48+
| a temporary with access to the first borrow is created here ...
49+
LL | foo.g();
50+
| ^^^ second mutable borrow occurs here
51+
LL |
52+
LL | }
53+
| - ... and the first borrow might be used here, when that temporary is dropped and runs the destructor for type `Option<Bar<'_>>`
54+
55+
error[E0499]: cannot borrow `foo` as mutable more than once at a time
56+
--> $DIR/already-borrowed-as-mutable-if-let-133941.rs:37:9
57+
|
58+
LL | if let Some(_x) = foo.f() {
59+
| -------
60+
| |
61+
| first mutable borrow occurs here
62+
| a temporary with access to the first borrow is created here ...
63+
LL | foo.g();
64+
| ^^^ second mutable borrow occurs here
65+
LL |
66+
LL | }
67+
| - ... and the first borrow might be used here, when that temporary is dropped and runs the destructor for type `Option<Bar<'_>>`
68+
69+
error[E0499]: cannot borrow `foo` as mutable more than once at a time
70+
--> $DIR/already-borrowed-as-mutable-if-let-133941.rs:40:45
71+
|
72+
LL | while let Some(_x) = {let _x = foo.f(); foo.g(); None::<()>} {
73+
| --- ^^^ - first borrow might be used here, when `_x` is dropped and runs the destructor for type `Option<Bar<'_>>`
74+
| | |
75+
| | second mutable borrow occurs here
76+
| first mutable borrow occurs here
77+
78+
error[E0499]: cannot borrow `foo` as mutable more than once at a time
79+
--> $DIR/already-borrowed-as-mutable-if-let-133941.rs:43:42
80+
|
81+
LL | if let Some(_x) = {let _x = foo.f(); foo.g(); None::<()>} {
82+
| --- ^^^ - first borrow might be used here, when `_x` is dropped and runs the destructor for type `Option<Bar<'_>>`
83+
| | |
84+
| | second mutable borrow occurs here
85+
| first mutable borrow occurs here
86+
87+
error: aborting due to 6 previous errors
88+
89+
For more information about this error, try `rustc --explain E0499`.

0 commit comments

Comments
 (0)