Unnamed repository; edit this file 'description' to name the repository.
Fix `unused-mut` false positive for `Box`
hkalbasi 2023-06-04
parent 71f3e4b · commit 0408af6
-rw-r--r--crates/hir-ty/src/mir/borrowck.rs43
-rw-r--r--crates/ide-diagnostics/src/handlers/mutability_errors.rs52
2 files changed, 83 insertions, 12 deletions
diff --git a/crates/hir-ty/src/mir/borrowck.rs b/crates/hir-ty/src/mir/borrowck.rs
index 66af6658e8..a0ea1cc5ef 100644
--- a/crates/hir-ty/src/mir/borrowck.rs
+++ b/crates/hir-ty/src/mir/borrowck.rs
@@ -78,7 +78,7 @@ pub fn borrowck_query(
.map(|body| {
let body = body?;
Ok(BorrowckResult {
- mutability_of_locals: mutability_of_locals(&body),
+ mutability_of_locals: mutability_of_locals(db, &body),
moved_out_of_ref: moved_out_of_ref(db, &body),
mir_body: body,
})
@@ -186,10 +186,7 @@ fn moved_out_of_ref(db: &dyn HirDatabase, body: &MirBody) -> Vec<MovedOutOfRef>
result
}
-fn is_place_direct(lvalue: &Place) -> bool {
- !lvalue.projection.iter().any(|x| *x == ProjectionElem::Deref)
-}
-
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum ProjectionCase {
/// Projection is a local
Direct,
@@ -199,12 +196,14 @@ enum ProjectionCase {
Indirect,
}
-fn place_case(lvalue: &Place) -> ProjectionCase {
+fn place_case(db: &dyn HirDatabase, body: &MirBody, lvalue: &Place) -> ProjectionCase {
let mut is_part_of = false;
- for proj in lvalue.projection.iter().rev() {
+ let mut ty = body.locals[lvalue.local].ty.clone();
+ for proj in lvalue.projection.iter() {
match proj {
- ProjectionElem::Deref => return ProjectionCase::Indirect, // It's indirect
- ProjectionElem::ConstantIndex { .. }
+ ProjectionElem::Deref if ty.as_adt().is_none() => return ProjectionCase::Indirect, // It's indirect in case of reference and raw
+ ProjectionElem::Deref // It's direct in case of `Box<T>`
+ | ProjectionElem::ConstantIndex { .. }
| ProjectionElem::Subslice { .. }
| ProjectionElem::Field(_)
| ProjectionElem::TupleOrClosureField(_)
@@ -213,6 +212,23 @@ fn place_case(lvalue: &Place) -> ProjectionCase {
}
ProjectionElem::OpaqueCast(_) => (),
}
+ ty = proj.projected_ty(
+ ty,
+ db,
+ |c, subst, f| {
+ let (def, _) = db.lookup_intern_closure(c.into());
+ let infer = db.infer(def);
+ let (captures, _) = infer.closure_info(&c);
+ let parent_subst = ClosureSubst(subst).parent_subst();
+ captures
+ .get(f)
+ .expect("broken closure field")
+ .ty
+ .clone()
+ .substitute(Interner, parent_subst)
+ },
+ body.owner.module(db.upcast()).krate(),
+ );
}
if is_part_of {
ProjectionCase::DirectPart
@@ -300,7 +316,10 @@ fn ever_initialized_map(body: &MirBody) -> ArenaMap<BasicBlockId, ArenaMap<Local
result
}
-fn mutability_of_locals(body: &MirBody) -> ArenaMap<LocalId, MutabilityReason> {
+fn mutability_of_locals(
+ db: &dyn HirDatabase,
+ body: &MirBody,
+) -> ArenaMap<LocalId, MutabilityReason> {
let mut result: ArenaMap<LocalId, MutabilityReason> =
body.locals.iter().map(|x| (x.0, MutabilityReason::Not)).collect();
let mut push_mut_span = |local, span| match &mut result[local] {
@@ -313,7 +332,7 @@ fn mutability_of_locals(body: &MirBody) -> ArenaMap<LocalId, MutabilityReason> {
for statement in &block.statements {
match &statement.kind {
StatementKind::Assign(place, value) => {
- match place_case(place) {
+ match place_case(db, body, place) {
ProjectionCase::Direct => {
if ever_init_map.get(place.local).copied().unwrap_or_default() {
push_mut_span(place.local, statement.span);
@@ -328,7 +347,7 @@ fn mutability_of_locals(body: &MirBody) -> ArenaMap<LocalId, MutabilityReason> {
ProjectionCase::Indirect => (),
}
if let Rvalue::Ref(BorrowKind::Mut { .. }, p) = value {
- if is_place_direct(p) {
+ if place_case(db, body, p) != ProjectionCase::Indirect {
push_mut_span(p.local, statement.span);
}
}
diff --git a/crates/ide-diagnostics/src/handlers/mutability_errors.rs b/crates/ide-diagnostics/src/handlers/mutability_errors.rs
index b95c8c573b..8795afc2d9 100644
--- a/crates/ide-diagnostics/src/handlers/mutability_errors.rs
+++ b/crates/ide-diagnostics/src/handlers/mutability_errors.rs
@@ -994,6 +994,58 @@ fn f() {
}
#[test]
+ fn boxes() {
+ check_diagnostics(
+ r#"
+//- minicore: coerce_unsized, deref_mut, slice
+use core::ops::{Deref, DerefMut};
+use core::{marker::Unsize, ops::CoerceUnsized};
+
+#[lang = "owned_box"]
+pub struct Box<T: ?Sized> {
+ inner: *mut T,
+}
+impl<T> Box<T> {
+ fn new(t: T) -> Self {
+ #[rustc_box]
+ Box::new(t)
+ }
+}
+
+impl<T: ?Sized> Deref for Box<T> {
+ type Target = T;
+
+ fn deref(&self) -> &T {
+ &**self
+ }
+}
+
+impl<T: ?Sized> DerefMut for Box<T> {
+ fn deref_mut(&mut self) -> &mut T {
+ &mut **self
+ }
+}
+
+fn f() {
+ let x = Box::new(5);
+ x = Box::new(7);
+ //^^^^^^^^^^^^^^^ 💡 error: cannot mutate immutable variable `x`
+ let x = Box::new(5);
+ *x = 7;
+ //^^^^^^ 💡 error: cannot mutate immutable variable `x`
+ let mut y = Box::new(5);
+ //^^^^^ 💡 weak: variable does not need to be mutable
+ *x = *y;
+ //^^^^^^^ 💡 error: cannot mutate immutable variable `x`
+ let x = Box::new(5);
+ let closure = || *x = 2;
+ //^ 💡 error: cannot mutate immutable variable `x`
+}
+"#,
+ );
+ }
+
+ #[test]
fn allow_unused_mut_for_identifiers_starting_with_underline() {
check_diagnostics(
r#"