Skip to content

Commit 71f3e4b

Browse files
committed
Detect "bound more than once" error and suppress need-mut for it.
1 parent 08f8919 commit 71f3e4b

File tree

4 files changed

+84
-8
lines changed

4 files changed

+84
-8
lines changed

crates/hir-def/src/body/lower.rs

Lines changed: 55 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ use crate::{
3030
db::DefDatabase,
3131
expander::Expander,
3232
hir::{
33-
dummy_expr_id, Array, Binding, BindingAnnotation, BindingId, CaptureBy, ClosureKind, Expr,
34-
ExprId, Label, LabelId, Literal, LiteralOrConst, MatchArm, Movability, Pat, PatId,
35-
RecordFieldPat, RecordLitField, Statement,
33+
dummy_expr_id, Array, Binding, BindingAnnotation, BindingId, BindingProblems, CaptureBy,
34+
ClosureKind, Expr, ExprId, Label, LabelId, Literal, LiteralOrConst, MatchArm, Movability,
35+
Pat, PatId, RecordFieldPat, RecordLitField, Statement,
3636
},
3737
item_scope::BuiltinShadowMode,
3838
lang_item::LangItem,
@@ -141,6 +141,8 @@ impl RibKind {
141141
#[derive(Debug, Default)]
142142
struct BindingList {
143143
map: FxHashMap<Name, BindingId>,
144+
is_used: FxHashMap<BindingId, bool>,
145+
reject_new: bool,
144146
}
145147

146148
impl BindingList {
@@ -150,7 +152,27 @@ impl BindingList {
150152
name: Name,
151153
mode: BindingAnnotation,
152154
) -> BindingId {
153-
*self.map.entry(name).or_insert_with_key(|n| ec.alloc_binding(n.clone(), mode))
155+
let id = *self.map.entry(name).or_insert_with_key(|n| ec.alloc_binding(n.clone(), mode));
156+
if ec.body.bindings[id].mode != mode {
157+
ec.body.bindings[id].problems = Some(BindingProblems::BoundInconsistently);
158+
}
159+
self.check_is_used(ec, id);
160+
id
161+
}
162+
163+
fn check_is_used(&mut self, ec: &mut ExprCollector<'_>, id: BindingId) {
164+
match self.is_used.get(&id) {
165+
None => {
166+
if self.reject_new {
167+
ec.body.bindings[id].problems = Some(BindingProblems::NotBoundAcrossAll);
168+
}
169+
}
170+
Some(true) => {
171+
ec.body.bindings[id].problems = Some(BindingProblems::BoundMoreThanOnce);
172+
}
173+
Some(false) => {}
174+
}
175+
self.is_used.insert(id, true);
154176
}
155177
}
156178

@@ -1208,9 +1230,34 @@ impl ExprCollector<'_> {
12081230
p.path().and_then(|path| self.expander.parse_path(self.db, path)).map(Box::new);
12091231
path.map(Pat::Path).unwrap_or(Pat::Missing)
12101232
}
1211-
ast::Pat::OrPat(p) => {
1212-
let pats = p.pats().map(|p| self.collect_pat(p, binding_list)).collect();
1213-
Pat::Or(pats)
1233+
ast::Pat::OrPat(p) => 'b: {
1234+
let prev_is_used = mem::take(&mut binding_list.is_used);
1235+
let prev_reject_new = mem::take(&mut binding_list.reject_new);
1236+
let mut pats = Vec::with_capacity(p.pats().count());
1237+
let mut it = p.pats();
1238+
let Some(first) = it.next() else {
1239+
break 'b Pat::Or(Box::new([]));
1240+
};
1241+
pats.push(self.collect_pat(first, binding_list));
1242+
binding_list.reject_new = true;
1243+
for rest in it {
1244+
for (_, x) in binding_list.is_used.iter_mut() {
1245+
*x = false;
1246+
}
1247+
pats.push(self.collect_pat(rest, binding_list));
1248+
for (&id, &x) in binding_list.is_used.iter() {
1249+
if !x {
1250+
self.body.bindings[id].problems =
1251+
Some(BindingProblems::NotBoundAcrossAll);
1252+
}
1253+
}
1254+
}
1255+
binding_list.reject_new = prev_reject_new;
1256+
let current_is_used = mem::replace(&mut binding_list.is_used, prev_is_used);
1257+
for (id, _) in current_is_used.into_iter() {
1258+
binding_list.check_is_used(self, id);
1259+
}
1260+
Pat::Or(pats.into())
12141261
}
12151262
ast::Pat::ParenPat(p) => return self.collect_pat_opt(p.pat(), binding_list),
12161263
ast::Pat::TuplePat(p) => {
@@ -1499,6 +1546,7 @@ impl ExprCollector<'_> {
14991546
mode,
15001547
definitions: SmallVec::new(),
15011548
owner: self.current_binding_owner,
1549+
problems: None,
15021550
})
15031551
}
15041552

crates/hir-def/src/hir.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,16 @@ impl BindingAnnotation {
486486
}
487487
}
488488

489+
#[derive(Debug, Clone, Eq, PartialEq)]
490+
pub enum BindingProblems {
491+
/// https://doc.rust-lang.org/stable/error_codes/E0416.html
492+
BoundMoreThanOnce,
493+
/// https://doc.rust-lang.org/stable/error_codes/E0409.html
494+
BoundInconsistently,
495+
/// https://doc.rust-lang.org/stable/error_codes/E0408.html
496+
NotBoundAcrossAll,
497+
}
498+
489499
#[derive(Debug, Clone, Eq, PartialEq)]
490500
pub struct Binding {
491501
pub name: Name,
@@ -494,6 +504,7 @@ pub struct Binding {
494504
/// Id of the closure/generator that owns this binding. If it is owned by the
495505
/// top level expression, this field would be `None`.
496506
pub owner: Option<ExprId>,
507+
pub problems: Option<BindingProblems>,
497508
}
498509

499510
impl Binding {

crates/hir/src/lib.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1643,7 +1643,11 @@ impl DefWithBody {
16431643
)
16441644
}
16451645
let mol = &borrowck_result.mutability_of_locals;
1646-
for (binding_id, _) in hir_body.bindings.iter() {
1646+
for (binding_id, binding_data) in hir_body.bindings.iter() {
1647+
if binding_data.problems.is_some() {
1648+
// We should report specific diagnostics for these problems, not `need-mut` and `unused-mut`.
1649+
continue;
1650+
}
16471651
let Some(&local) = mir_body.binding_locals.get(binding_id) else {
16481652
continue;
16491653
};

crates/ide-diagnostics/src/handlers/mutability_errors.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -620,6 +620,19 @@ fn f((x, y): (i32, i32)) {
620620
);
621621
}
622622

623+
#[test]
624+
fn no_diagnostics_in_case_of_multiple_bounds() {
625+
check_diagnostics(
626+
r#"
627+
fn f() {
628+
let (b, a, b) = (2, 3, 5);
629+
a = 8;
630+
//^^^^^ 💡 error: cannot mutate immutable variable `a`
631+
}
632+
"#,
633+
);
634+
}
635+
623636
#[test]
624637
fn for_loop() {
625638
check_diagnostics(

0 commit comments

Comments
 (0)