mirror of
https://github.com/rust-lang/rust-analyzer
synced 2025-01-14 06:03:58 +00:00
Add need-mut
and unused-mut
diagnostics
This commit is contained in:
parent
c0a0664d12
commit
a25710b0c0
10 changed files with 1089 additions and 277 deletions
|
@ -422,6 +422,13 @@ impl Body {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn walk_child_bindings(&self, pat: PatId, f: &mut impl FnMut(BindingId)) {
|
||||||
|
if let Pat::Bind { id, .. } = self[pat] {
|
||||||
|
f(id)
|
||||||
|
}
|
||||||
|
self[pat].walk_child_pats(|p| self.walk_child_bindings(p, f));
|
||||||
|
}
|
||||||
|
|
||||||
pub fn pretty_print(&self, db: &dyn DefDatabase, owner: DefWithBodyId) -> String {
|
pub fn pretty_print(&self, db: &dyn DefDatabase, owner: DefWithBodyId) -> String {
|
||||||
pretty::print_body_hir(db, self, owner)
|
pretty::print_body_hir(db, self, owner)
|
||||||
}
|
}
|
||||||
|
|
|
@ -103,6 +103,22 @@ fn references() {
|
||||||
"#,
|
"#,
|
||||||
5,
|
5,
|
||||||
);
|
);
|
||||||
|
check_number(
|
||||||
|
r#"
|
||||||
|
struct Foo(i32);
|
||||||
|
impl Foo {
|
||||||
|
fn method(&mut self, x: i32) {
|
||||||
|
self.0 = 2 * self.0 + x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const GOAL: i32 = {
|
||||||
|
let mut x = Foo(3);
|
||||||
|
x.method(5);
|
||||||
|
x.0
|
||||||
|
};
|
||||||
|
"#,
|
||||||
|
11,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -358,7 +374,7 @@ fn ifs() {
|
||||||
if a < b { b } else { a }
|
if a < b { b } else { a }
|
||||||
}
|
}
|
||||||
|
|
||||||
const GOAL: u8 = max(max(1, max(10, 3)), 0-122);
|
const GOAL: i32 = max(max(1, max(10, 3)), 0-122);
|
||||||
"#,
|
"#,
|
||||||
10,
|
10,
|
||||||
);
|
);
|
||||||
|
@ -366,7 +382,7 @@ fn ifs() {
|
||||||
check_number(
|
check_number(
|
||||||
r#"
|
r#"
|
||||||
const fn max(a: &i32, b: &i32) -> &i32 {
|
const fn max(a: &i32, b: &i32) -> &i32 {
|
||||||
if a < b { b } else { a }
|
if *a < *b { b } else { a }
|
||||||
}
|
}
|
||||||
|
|
||||||
const GOAL: i32 = *max(max(&1, max(&10, &3)), &5);
|
const GOAL: i32 = *max(max(&1, max(&10, &3)), &5);
|
||||||
|
@ -464,6 +480,16 @@ fn tuples() {
|
||||||
"#,
|
"#,
|
||||||
20,
|
20,
|
||||||
);
|
);
|
||||||
|
check_number(
|
||||||
|
r#"
|
||||||
|
const GOAL: u8 = {
|
||||||
|
let mut a = (10, 20, 3, 15);
|
||||||
|
a.1 = 2;
|
||||||
|
a.0 + a.1 + a.2 + a.3
|
||||||
|
};
|
||||||
|
"#,
|
||||||
|
30,
|
||||||
|
);
|
||||||
check_number(
|
check_number(
|
||||||
r#"
|
r#"
|
||||||
struct TupleLike(i32, u8, i64, u16);
|
struct TupleLike(i32, u8, i64, u16);
|
||||||
|
@ -539,7 +565,7 @@ fn let_else() {
|
||||||
let Some(x) = x else { return 10 };
|
let Some(x) = x else { return 10 };
|
||||||
2 * x
|
2 * x
|
||||||
}
|
}
|
||||||
const GOAL: u8 = f(Some(1000)) + f(None);
|
const GOAL: i32 = f(Some(1000)) + f(None);
|
||||||
"#,
|
"#,
|
||||||
2010,
|
2010,
|
||||||
);
|
);
|
||||||
|
@ -615,7 +641,7 @@ fn options() {
|
||||||
0
|
0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const GOAL: u8 = f(Some(Some(10))) + f(Some(None)) + f(None);
|
const GOAL: i32 = f(Some(Some(10))) + f(Some(None)) + f(None);
|
||||||
"#,
|
"#,
|
||||||
11,
|
11,
|
||||||
);
|
);
|
||||||
|
@ -746,24 +772,24 @@ fn enums() {
|
||||||
r#"
|
r#"
|
||||||
enum E {
|
enum E {
|
||||||
F1 = 1,
|
F1 = 1,
|
||||||
F2 = 2 * E::F1 as u8,
|
F2 = 2 * E::F1 as isize, // Rustc expects an isize here
|
||||||
F3 = 3 * E::F2 as u8,
|
F3 = 3 * E::F2 as isize,
|
||||||
}
|
}
|
||||||
const GOAL: i32 = E::F3 as u8;
|
const GOAL: u8 = E::F3 as u8;
|
||||||
"#,
|
"#,
|
||||||
6,
|
6,
|
||||||
);
|
);
|
||||||
check_number(
|
check_number(
|
||||||
r#"
|
r#"
|
||||||
enum E { F1 = 1, F2, }
|
enum E { F1 = 1, F2, }
|
||||||
const GOAL: i32 = E::F2 as u8;
|
const GOAL: u8 = E::F2 as u8;
|
||||||
"#,
|
"#,
|
||||||
2,
|
2,
|
||||||
);
|
);
|
||||||
check_number(
|
check_number(
|
||||||
r#"
|
r#"
|
||||||
enum E { F1, }
|
enum E { F1, }
|
||||||
const GOAL: i32 = E::F1 as u8;
|
const GOAL: u8 = E::F1 as u8;
|
||||||
"#,
|
"#,
|
||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
|
@ -894,8 +920,22 @@ fn exec_limits() {
|
||||||
}
|
}
|
||||||
sum
|
sum
|
||||||
}
|
}
|
||||||
const GOAL: usize = f(10000);
|
const GOAL: i32 = f(10000);
|
||||||
"#,
|
"#,
|
||||||
10000 * 10000,
|
10000 * 10000,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn type_error() {
|
||||||
|
let e = eval_goal(
|
||||||
|
r#"
|
||||||
|
const GOAL: u8 = {
|
||||||
|
let x: u16 = 2;
|
||||||
|
let y: (u8, u8) = x;
|
||||||
|
y.0
|
||||||
|
};
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
assert!(matches!(e, Err(ConstEvalError::MirLowerError(MirLowerError::TypeMismatch(_)))));
|
||||||
|
}
|
||||||
|
|
|
@ -7,17 +7,19 @@ use crate::{
|
||||||
};
|
};
|
||||||
use chalk_ir::Mutability;
|
use chalk_ir::Mutability;
|
||||||
use hir_def::{
|
use hir_def::{
|
||||||
expr::{Expr, Ordering},
|
expr::{BindingId, Expr, ExprId, Ordering, PatId},
|
||||||
DefWithBodyId, FieldId, UnionId, VariantId,
|
DefWithBodyId, FieldId, UnionId, VariantId,
|
||||||
};
|
};
|
||||||
use la_arena::{Arena, Idx, RawIdx};
|
use la_arena::{Arena, ArenaMap, Idx, RawIdx};
|
||||||
|
|
||||||
mod eval;
|
mod eval;
|
||||||
mod lower;
|
mod lower;
|
||||||
|
pub mod borrowck;
|
||||||
|
|
||||||
pub use eval::{interpret_mir, pad16, Evaluator, MirEvalError};
|
pub use eval::{interpret_mir, pad16, Evaluator, MirEvalError};
|
||||||
pub use lower::{lower_to_mir, mir_body_query, mir_body_recover, MirLowerError};
|
pub use lower::{lower_to_mir, mir_body_query, mir_body_recover, MirLowerError};
|
||||||
use smallvec::{smallvec, SmallVec};
|
use smallvec::{smallvec, SmallVec};
|
||||||
|
use stdx::impl_from;
|
||||||
|
|
||||||
use super::consteval::{intern_const_scalar, try_const_usize};
|
use super::consteval::{intern_const_scalar, try_const_usize};
|
||||||
|
|
||||||
|
@ -181,6 +183,11 @@ impl SwitchTargets {
|
||||||
iter::zip(&self.values, &self.targets).map(|(x, y)| (*x, *y))
|
iter::zip(&self.values, &self.targets).map(|(x, y)| (*x, *y))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns a slice with all possible jump targets (including the fallback target).
|
||||||
|
pub fn all_targets(&self) -> &[BasicBlockId] {
|
||||||
|
&self.targets
|
||||||
|
}
|
||||||
|
|
||||||
/// Finds the `BasicBlock` to which this `SwitchInt` will branch given the
|
/// Finds the `BasicBlock` to which this `SwitchInt` will branch given the
|
||||||
/// specific value. This cannot fail, as it'll return the `otherwise`
|
/// specific value. This cannot fail, as it'll return the `otherwise`
|
||||||
/// branch if there's not a specific match for the value.
|
/// branch if there's not a specific match for the value.
|
||||||
|
@ -758,7 +765,7 @@ pub enum Rvalue {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
pub enum Statement {
|
pub enum StatementKind {
|
||||||
Assign(Place, Rvalue),
|
Assign(Place, Rvalue),
|
||||||
//FakeRead(Box<(FakeReadCause, Place)>),
|
//FakeRead(Box<(FakeReadCause, Place)>),
|
||||||
//SetDiscriminant {
|
//SetDiscriminant {
|
||||||
|
@ -773,6 +780,17 @@ pub enum Statement {
|
||||||
//Intrinsic(Box<NonDivergingIntrinsic>),
|
//Intrinsic(Box<NonDivergingIntrinsic>),
|
||||||
Nop,
|
Nop,
|
||||||
}
|
}
|
||||||
|
impl StatementKind {
|
||||||
|
fn with_span(self, span: MirSpan) -> Statement {
|
||||||
|
Statement { kind: self, span }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||||
|
pub struct Statement {
|
||||||
|
pub kind: StatementKind,
|
||||||
|
pub span: MirSpan,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, PartialEq, Eq)]
|
#[derive(Debug, Default, PartialEq, Eq)]
|
||||||
pub struct BasicBlock {
|
pub struct BasicBlock {
|
||||||
|
@ -803,6 +821,7 @@ pub struct MirBody {
|
||||||
pub start_block: BasicBlockId,
|
pub start_block: BasicBlockId,
|
||||||
pub owner: DefWithBodyId,
|
pub owner: DefWithBodyId,
|
||||||
pub arg_count: usize,
|
pub arg_count: usize,
|
||||||
|
pub binding_locals: ArenaMap<BindingId, LocalId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MirBody {}
|
impl MirBody {}
|
||||||
|
@ -810,3 +829,12 @@ impl MirBody {}
|
||||||
fn const_as_usize(c: &Const) -> usize {
|
fn const_as_usize(c: &Const) -> usize {
|
||||||
try_const_usize(c).unwrap() as usize
|
try_const_usize(c).unwrap() as usize
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||||
|
pub enum MirSpan {
|
||||||
|
ExprId(ExprId),
|
||||||
|
PatId(PatId),
|
||||||
|
Unknown,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_from!(ExprId, PatId for MirSpan);
|
||||||
|
|
201
crates/hir-ty/src/mir/borrowck.rs
Normal file
201
crates/hir-ty/src/mir/borrowck.rs
Normal file
|
@ -0,0 +1,201 @@
|
||||||
|
//! MIR borrow checker, which is used in diagnostics like `unused_mut`
|
||||||
|
|
||||||
|
// Currently it is an ad-hoc implementation, only useful for mutability analysis. Feel free to remove all of these
|
||||||
|
// and implement a proper borrow checker.
|
||||||
|
|
||||||
|
use la_arena::ArenaMap;
|
||||||
|
use stdx::never;
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
BasicBlockId, BorrowKind, LocalId, MirBody, MirSpan, Place, ProjectionElem, Rvalue,
|
||||||
|
StatementKind, Terminator,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Mutability {
|
||||||
|
Mut { span: MirSpan },
|
||||||
|
Not,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_place_direct(lvalue: &Place) -> bool {
|
||||||
|
!lvalue.projection.iter().any(|x| *x == ProjectionElem::Deref)
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ProjectionCase {
|
||||||
|
/// Projection is a local
|
||||||
|
Direct,
|
||||||
|
/// Projection is some field or slice of a local
|
||||||
|
DirectPart,
|
||||||
|
/// Projection is deref of something
|
||||||
|
Indirect,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn place_case(lvalue: &Place) -> ProjectionCase {
|
||||||
|
let mut is_part_of = false;
|
||||||
|
for proj in lvalue.projection.iter().rev() {
|
||||||
|
match proj {
|
||||||
|
ProjectionElem::Deref => return ProjectionCase::Indirect, // It's indirect
|
||||||
|
ProjectionElem::ConstantIndex { .. }
|
||||||
|
| ProjectionElem::Subslice { .. }
|
||||||
|
| ProjectionElem::Field(_)
|
||||||
|
| ProjectionElem::TupleField(_)
|
||||||
|
| ProjectionElem::Index(_) => {
|
||||||
|
is_part_of = true;
|
||||||
|
}
|
||||||
|
ProjectionElem::OpaqueCast(_) => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if is_part_of {
|
||||||
|
ProjectionCase::DirectPart
|
||||||
|
} else {
|
||||||
|
ProjectionCase::Direct
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a map from basic blocks to the set of locals that might be ever initialized before
|
||||||
|
/// the start of the block. Only `StorageDead` can remove something from this map, and we ignore
|
||||||
|
/// `Uninit` and `drop` and similars after initialization.
|
||||||
|
fn ever_initialized_map(body: &MirBody) -> ArenaMap<BasicBlockId, ArenaMap<LocalId, bool>> {
|
||||||
|
let mut result: ArenaMap<BasicBlockId, ArenaMap<LocalId, bool>> =
|
||||||
|
body.basic_blocks.iter().map(|x| (x.0, ArenaMap::default())).collect();
|
||||||
|
fn dfs(
|
||||||
|
body: &MirBody,
|
||||||
|
b: BasicBlockId,
|
||||||
|
l: LocalId,
|
||||||
|
result: &mut ArenaMap<BasicBlockId, ArenaMap<LocalId, bool>>,
|
||||||
|
) {
|
||||||
|
let mut is_ever_initialized = result[b][l]; // It must be filled, as we use it as mark for dfs
|
||||||
|
let block = &body.basic_blocks[b];
|
||||||
|
for statement in &block.statements {
|
||||||
|
match &statement.kind {
|
||||||
|
StatementKind::Assign(p, _) => {
|
||||||
|
if p.projection.len() == 0 && p.local == l {
|
||||||
|
is_ever_initialized = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StatementKind::StorageDead(p) => {
|
||||||
|
if *p == l {
|
||||||
|
is_ever_initialized = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StatementKind::Deinit(_) | StatementKind::Nop | StatementKind::StorageLive(_) => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let Some(terminator) = &block.terminator else {
|
||||||
|
never!("Terminator should be none only in construction");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let targets = match terminator {
|
||||||
|
Terminator::Goto { target } => vec![*target],
|
||||||
|
Terminator::SwitchInt { targets, .. } => targets.all_targets().to_vec(),
|
||||||
|
Terminator::Resume
|
||||||
|
| Terminator::Abort
|
||||||
|
| Terminator::Return
|
||||||
|
| Terminator::Unreachable => vec![],
|
||||||
|
Terminator::Call { target, cleanup, destination, .. } => {
|
||||||
|
if destination.projection.len() == 0 && destination.local == l {
|
||||||
|
is_ever_initialized = true;
|
||||||
|
}
|
||||||
|
target.into_iter().chain(cleanup.into_iter()).copied().collect()
|
||||||
|
}
|
||||||
|
Terminator::Drop { .. }
|
||||||
|
| Terminator::DropAndReplace { .. }
|
||||||
|
| Terminator::Assert { .. }
|
||||||
|
| Terminator::Yield { .. }
|
||||||
|
| Terminator::GeneratorDrop
|
||||||
|
| Terminator::FalseEdge { .. }
|
||||||
|
| Terminator::FalseUnwind { .. } => {
|
||||||
|
never!("We don't emit these MIR terminators yet");
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
for target in targets {
|
||||||
|
if !result[target].contains_idx(l) || !result[target][l] && is_ever_initialized {
|
||||||
|
result[target].insert(l, is_ever_initialized);
|
||||||
|
dfs(body, target, l, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (b, block) in body.basic_blocks.iter() {
|
||||||
|
for statement in &block.statements {
|
||||||
|
if let StatementKind::Assign(p, _) = &statement.kind {
|
||||||
|
if p.projection.len() == 0 {
|
||||||
|
let l = p.local;
|
||||||
|
if !result[b].contains_idx(l) {
|
||||||
|
result[b].insert(l, false);
|
||||||
|
dfs(body, b, l, &mut result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mutability_of_locals(body: &MirBody) -> ArenaMap<LocalId, Mutability> {
|
||||||
|
let mut result: ArenaMap<LocalId, Mutability> =
|
||||||
|
body.locals.iter().map(|x| (x.0, Mutability::Not)).collect();
|
||||||
|
let ever_init_maps = ever_initialized_map(body);
|
||||||
|
for (block_id, ever_init_map) in ever_init_maps.iter() {
|
||||||
|
let mut ever_init_map = ever_init_map.clone();
|
||||||
|
let block = &body.basic_blocks[block_id];
|
||||||
|
for statement in &block.statements {
|
||||||
|
match &statement.kind {
|
||||||
|
StatementKind::Assign(place, value) => {
|
||||||
|
match place_case(place) {
|
||||||
|
ProjectionCase::Direct => {
|
||||||
|
if ever_init_map.get(place.local).copied().unwrap_or_default() {
|
||||||
|
result[place.local] = Mutability::Mut { span: statement.span };
|
||||||
|
} else {
|
||||||
|
ever_init_map.insert(place.local, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ProjectionCase::DirectPart => {
|
||||||
|
// Partial initialization is not supported, so it is definitely `mut`
|
||||||
|
result[place.local] = Mutability::Mut { span: statement.span };
|
||||||
|
}
|
||||||
|
ProjectionCase::Indirect => (),
|
||||||
|
}
|
||||||
|
if let Rvalue::Ref(BorrowKind::Mut { .. }, p) = value {
|
||||||
|
if is_place_direct(p) {
|
||||||
|
result[p.local] = Mutability::Mut { span: statement.span };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StatementKind::StorageDead(p) => {
|
||||||
|
ever_init_map.insert(*p, false);
|
||||||
|
}
|
||||||
|
StatementKind::Deinit(_) | StatementKind::StorageLive(_) | StatementKind::Nop => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let Some(terminator) = &block.terminator else {
|
||||||
|
never!("Terminator should be none only in construction");
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
match terminator {
|
||||||
|
Terminator::Goto { .. }
|
||||||
|
| Terminator::Resume
|
||||||
|
| Terminator::Abort
|
||||||
|
| Terminator::Return
|
||||||
|
| Terminator::Unreachable
|
||||||
|
| Terminator::FalseEdge { .. }
|
||||||
|
| Terminator::FalseUnwind { .. }
|
||||||
|
| Terminator::GeneratorDrop
|
||||||
|
| Terminator::SwitchInt { .. }
|
||||||
|
| Terminator::Drop { .. }
|
||||||
|
| Terminator::DropAndReplace { .. }
|
||||||
|
| Terminator::Assert { .. }
|
||||||
|
| Terminator::Yield { .. } => (),
|
||||||
|
Terminator::Call { destination, .. } => {
|
||||||
|
if destination.projection.len() == 0 {
|
||||||
|
if ever_init_map.get(destination.local).copied().unwrap_or_default() {
|
||||||
|
result[destination.local] = Mutability::Mut { span: MirSpan::Unknown };
|
||||||
|
} else {
|
||||||
|
ever_init_map.insert(destination.local, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result
|
||||||
|
}
|
|
@ -29,7 +29,7 @@ use crate::{
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
const_as_usize, return_slot, AggregateKind, BinOp, CastKind, LocalId, MirBody, MirLowerError,
|
const_as_usize, return_slot, AggregateKind, BinOp, CastKind, LocalId, MirBody, MirLowerError,
|
||||||
Operand, Place, ProjectionElem, Rvalue, Statement, Terminator, UnOp,
|
Operand, Place, ProjectionElem, Rvalue, StatementKind, Terminator, UnOp,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct Evaluator<'a> {
|
pub struct Evaluator<'a> {
|
||||||
|
@ -395,7 +395,8 @@ impl Evaluator<'_> {
|
||||||
.locals
|
.locals
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(id, x)| {
|
.map(|(id, x)| {
|
||||||
let size = self.size_of_sized(&x.ty, &locals, "no unsized local")?;
|
let size =
|
||||||
|
self.size_of_sized(&x.ty, &locals, "no unsized local in extending stack")?;
|
||||||
let my_ptr = stack_ptr;
|
let my_ptr = stack_ptr;
|
||||||
stack_ptr += size;
|
stack_ptr += size;
|
||||||
Ok((id, Stack(my_ptr)))
|
Ok((id, Stack(my_ptr)))
|
||||||
|
@ -425,16 +426,16 @@ impl Evaluator<'_> {
|
||||||
return Err(MirEvalError::ExecutionLimitExceeded);
|
return Err(MirEvalError::ExecutionLimitExceeded);
|
||||||
}
|
}
|
||||||
for statement in ¤t_block.statements {
|
for statement in ¤t_block.statements {
|
||||||
match statement {
|
match &statement.kind {
|
||||||
Statement::Assign(l, r) => {
|
StatementKind::Assign(l, r) => {
|
||||||
let addr = self.place_addr(l, &locals)?;
|
let addr = self.place_addr(l, &locals)?;
|
||||||
let result = self.eval_rvalue(r, &locals)?.to_vec(&self)?;
|
let result = self.eval_rvalue(r, &locals)?.to_vec(&self)?;
|
||||||
self.write_memory(addr, &result)?;
|
self.write_memory(addr, &result)?;
|
||||||
}
|
}
|
||||||
Statement::Deinit(_) => not_supported!("de-init statement"),
|
StatementKind::Deinit(_) => not_supported!("de-init statement"),
|
||||||
Statement::StorageLive(_) => not_supported!("storage-live statement"),
|
StatementKind::StorageLive(_)
|
||||||
Statement::StorageDead(_) => not_supported!("storage-dead statement"),
|
| StatementKind::StorageDead(_)
|
||||||
Statement::Nop => (),
|
| StatementKind::Nop => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let Some(terminator) = current_block.terminator.as_ref() else {
|
let Some(terminator) = current_block.terminator.as_ref() else {
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -10,7 +10,7 @@ use hir_def::path::ModPath;
|
||||||
use hir_expand::{name::Name, HirFileId, InFile};
|
use hir_expand::{name::Name, HirFileId, InFile};
|
||||||
use syntax::{ast, AstPtr, SyntaxNodePtr, TextRange};
|
use syntax::{ast, AstPtr, SyntaxNodePtr, TextRange};
|
||||||
|
|
||||||
use crate::{AssocItem, Field, MacroKind, Type};
|
use crate::{AssocItem, Field, Local, MacroKind, Type};
|
||||||
|
|
||||||
macro_rules! diagnostics {
|
macro_rules! diagnostics {
|
||||||
($($diag:ident,)*) => {
|
($($diag:ident,)*) => {
|
||||||
|
@ -41,6 +41,7 @@ diagnostics![
|
||||||
MissingFields,
|
MissingFields,
|
||||||
MissingMatchArms,
|
MissingMatchArms,
|
||||||
MissingUnsafe,
|
MissingUnsafe,
|
||||||
|
NeedMut,
|
||||||
NoSuchField,
|
NoSuchField,
|
||||||
PrivateAssocItem,
|
PrivateAssocItem,
|
||||||
PrivateField,
|
PrivateField,
|
||||||
|
@ -54,6 +55,7 @@ diagnostics![
|
||||||
UnresolvedMethodCall,
|
UnresolvedMethodCall,
|
||||||
UnresolvedModule,
|
UnresolvedModule,
|
||||||
UnresolvedProcMacro,
|
UnresolvedProcMacro,
|
||||||
|
UnusedMut,
|
||||||
];
|
];
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -209,4 +211,15 @@ pub struct TypeMismatch {
|
||||||
pub actual: Type,
|
pub actual: Type,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct NeedMut {
|
||||||
|
pub local: Local,
|
||||||
|
pub span: InFile<SyntaxNodePtr>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct UnusedMut {
|
||||||
|
pub local: Local,
|
||||||
|
}
|
||||||
|
|
||||||
pub use hir_ty::diagnostics::IncorrectCase;
|
pub use hir_ty::diagnostics::IncorrectCase;
|
||||||
|
|
|
@ -63,7 +63,7 @@ use hir_ty::{
|
||||||
display::HexifiedConst,
|
display::HexifiedConst,
|
||||||
layout::layout_of_ty,
|
layout::layout_of_ty,
|
||||||
method_resolution::{self, TyFingerprint},
|
method_resolution::{self, TyFingerprint},
|
||||||
mir::interpret_mir,
|
mir::{self, interpret_mir},
|
||||||
primitive::UintTy,
|
primitive::UintTy,
|
||||||
traits::FnTrait,
|
traits::FnTrait,
|
||||||
AliasTy, CallableDefId, CallableSig, Canonical, CanonicalVarKinds, Cast, ClosureId,
|
AliasTy, CallableDefId, CallableSig, Canonical, CanonicalVarKinds, Cast, ClosureId,
|
||||||
|
@ -85,12 +85,12 @@ use crate::db::{DefDatabase, HirDatabase};
|
||||||
pub use crate::{
|
pub use crate::{
|
||||||
attrs::{HasAttrs, Namespace},
|
attrs::{HasAttrs, Namespace},
|
||||||
diagnostics::{
|
diagnostics::{
|
||||||
AnyDiagnostic, BreakOutsideOfLoop, ExpectedFunction, InactiveCode, IncorrectCase,
|
AnyDiagnostic, BreakOutsideOfLoop, InactiveCode, IncorrectCase, InvalidDeriveTarget,
|
||||||
InvalidDeriveTarget, MacroError, MalformedDerive, MismatchedArgCount, MissingFields,
|
MacroError, MalformedDerive, MismatchedArgCount, MissingFields, MissingMatchArms,
|
||||||
MissingMatchArms, MissingUnsafe, NoSuchField, PrivateAssocItem, PrivateField,
|
MissingUnsafe, NeedMut, NoSuchField, PrivateAssocItem, PrivateField,
|
||||||
ReplaceFilterMapNextWithFindMap, TypeMismatch, UnimplementedBuiltinMacro,
|
ReplaceFilterMapNextWithFindMap, TypeMismatch, UnimplementedBuiltinMacro,
|
||||||
UnresolvedExternCrate, UnresolvedField, UnresolvedImport, UnresolvedMacroCall,
|
UnresolvedExternCrate, UnresolvedImport, UnresolvedMacroCall, UnresolvedModule,
|
||||||
UnresolvedMethodCall, UnresolvedModule, UnresolvedProcMacro,
|
UnresolvedProcMacro, UnusedMut,
|
||||||
},
|
},
|
||||||
has_source::HasSource,
|
has_source::HasSource,
|
||||||
semantics::{PathResolution, Semantics, SemanticsScope, TypeInfo, VisibleTraits},
|
semantics::{PathResolution, Semantics, SemanticsScope, TypeInfo, VisibleTraits},
|
||||||
|
@ -1500,6 +1500,38 @@ impl DefWithBody {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let hir_body = db.body(self.into());
|
||||||
|
|
||||||
|
if let Ok(mir_body) = db.mir_body(self.into()) {
|
||||||
|
let mol = mir::borrowck::mutability_of_locals(&mir_body);
|
||||||
|
for (binding_id, _) in hir_body.bindings.iter() {
|
||||||
|
let need_mut = &mol[mir_body.binding_locals[binding_id]];
|
||||||
|
let local = Local { parent: self.into(), binding_id };
|
||||||
|
match (need_mut, local.is_mut(db)) {
|
||||||
|
(mir::borrowck::Mutability::Mut { .. }, true)
|
||||||
|
| (mir::borrowck::Mutability::Not, false) => (),
|
||||||
|
(mir::borrowck::Mutability::Mut { span }, false) => {
|
||||||
|
let span: InFile<SyntaxNodePtr> = match span {
|
||||||
|
mir::MirSpan::ExprId(e) => match source_map.expr_syntax(*e) {
|
||||||
|
Ok(s) => s.map(|x| x.into()),
|
||||||
|
Err(_) => continue,
|
||||||
|
},
|
||||||
|
mir::MirSpan::PatId(p) => match source_map.pat_syntax(*p) {
|
||||||
|
Ok(s) => s.map(|x| match x {
|
||||||
|
Either::Left(e) => e.into(),
|
||||||
|
Either::Right(e) => e.into(),
|
||||||
|
}),
|
||||||
|
Err(_) => continue,
|
||||||
|
},
|
||||||
|
mir::MirSpan::Unknown => continue,
|
||||||
|
};
|
||||||
|
acc.push(NeedMut { local, span }.into());
|
||||||
|
}
|
||||||
|
(mir::borrowck::Mutability::Not, true) => acc.push(UnusedMut { local }.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for diagnostic in BodyValidationDiagnostic::collect(db, self.into()) {
|
for diagnostic in BodyValidationDiagnostic::collect(db, self.into()) {
|
||||||
match diagnostic {
|
match diagnostic {
|
||||||
BodyValidationDiagnostic::RecordMissingFields {
|
BodyValidationDiagnostic::RecordMissingFields {
|
||||||
|
@ -2490,6 +2522,10 @@ impl LocalSource {
|
||||||
pub fn syntax(&self) -> &SyntaxNode {
|
pub fn syntax(&self) -> &SyntaxNode {
|
||||||
self.source.value.syntax()
|
self.source.value.syntax()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn syntax_ptr(self) -> InFile<SyntaxNodePtr> {
|
||||||
|
self.source.map(|x| SyntaxNodePtr::new(x.syntax()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Local {
|
impl Local {
|
||||||
|
|
302
crates/ide-diagnostics/src/handlers/mutability_errors.rs
Normal file
302
crates/ide-diagnostics/src/handlers/mutability_errors.rs
Normal file
|
@ -0,0 +1,302 @@
|
||||||
|
use crate::{Diagnostic, DiagnosticsContext, Severity};
|
||||||
|
|
||||||
|
// Diagnostic: need-mut
|
||||||
|
//
|
||||||
|
// This diagnostic is triggered on mutating an immutable variable.
|
||||||
|
pub(crate) fn need_mut(ctx: &DiagnosticsContext<'_>, d: &hir::NeedMut) -> Diagnostic {
|
||||||
|
Diagnostic::new(
|
||||||
|
"need-mut",
|
||||||
|
format!("cannot mutate immutable variable `{}`", d.local.name(ctx.sema.db)),
|
||||||
|
ctx.sema.diagnostics_display_range(d.span.clone()).range,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Diagnostic: unused-mut
|
||||||
|
//
|
||||||
|
// This diagnostic is triggered when a mutable variable isn't actually mutated.
|
||||||
|
pub(crate) fn unused_mut(ctx: &DiagnosticsContext<'_>, d: &hir::UnusedMut) -> Diagnostic {
|
||||||
|
Diagnostic::new(
|
||||||
|
"unused-mut",
|
||||||
|
"remove this `mut`",
|
||||||
|
ctx.sema.diagnostics_display_range(d.local.primary_source(ctx.sema.db).syntax_ptr()).range,
|
||||||
|
)
|
||||||
|
.severity(Severity::WeakWarning)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::tests::check_diagnostics;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn unused_mut_simple() {
|
||||||
|
check_diagnostics(
|
||||||
|
r#"
|
||||||
|
fn f(_: i32) {}
|
||||||
|
fn main() {
|
||||||
|
let mut x = 2;
|
||||||
|
//^^^^^ weak: remove this `mut`
|
||||||
|
f(x);
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_false_positive_simple() {
|
||||||
|
check_diagnostics(
|
||||||
|
r#"
|
||||||
|
fn f(_: i32) {}
|
||||||
|
fn main() {
|
||||||
|
let x = 2;
|
||||||
|
f(x);
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
check_diagnostics(
|
||||||
|
r#"
|
||||||
|
fn f(_: i32) {}
|
||||||
|
fn main() {
|
||||||
|
let mut x = 2;
|
||||||
|
x = 5;
|
||||||
|
f(x);
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn field_mutate() {
|
||||||
|
check_diagnostics(
|
||||||
|
r#"
|
||||||
|
fn f(_: i32) {}
|
||||||
|
fn main() {
|
||||||
|
let mut x = (2, 7);
|
||||||
|
//^^^^^ weak: remove this `mut`
|
||||||
|
f(x.1);
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
check_diagnostics(
|
||||||
|
r#"
|
||||||
|
fn f(_: i32) {}
|
||||||
|
fn main() {
|
||||||
|
let mut x = (2, 7);
|
||||||
|
x.0 = 5;
|
||||||
|
f(x.1);
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
check_diagnostics(
|
||||||
|
r#"
|
||||||
|
fn f(_: i32) {}
|
||||||
|
fn main() {
|
||||||
|
let x = (2, 7);
|
||||||
|
x.0 = 5;
|
||||||
|
//^^^^^^^ error: cannot mutate immutable variable `x`
|
||||||
|
f(x.1);
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mutable_reference() {
|
||||||
|
check_diagnostics(
|
||||||
|
r#"
|
||||||
|
fn main() {
|
||||||
|
let mut x = &mut 2;
|
||||||
|
//^^^^^ weak: remove this `mut`
|
||||||
|
*x = 5;
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
check_diagnostics(
|
||||||
|
r#"
|
||||||
|
fn main() {
|
||||||
|
let x = 2;
|
||||||
|
&mut x;
|
||||||
|
//^^^^^^ error: cannot mutate immutable variable `x`
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
check_diagnostics(
|
||||||
|
r#"
|
||||||
|
fn main() {
|
||||||
|
let x_own = 2;
|
||||||
|
let ref mut x_ref = x_own;
|
||||||
|
//^^^^^^^^^^^^^ error: cannot mutate immutable variable `x_own`
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
check_diagnostics(
|
||||||
|
r#"
|
||||||
|
struct Foo;
|
||||||
|
impl Foo {
|
||||||
|
fn method(&mut self, x: i32) {}
|
||||||
|
}
|
||||||
|
fn main() {
|
||||||
|
let x = Foo;
|
||||||
|
x.method(2);
|
||||||
|
//^ error: cannot mutate immutable variable `x`
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn match_bindings() {
|
||||||
|
check_diagnostics(
|
||||||
|
r#"
|
||||||
|
fn main() {
|
||||||
|
match (2, 3) {
|
||||||
|
(x, mut y) => {
|
||||||
|
//^^^^^ weak: remove this `mut`
|
||||||
|
x = 7;
|
||||||
|
//^^^^^ error: cannot mutate immutable variable `x`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mutation_in_dead_code() {
|
||||||
|
// This one is interesting. Dead code is not represented at all in the MIR, so
|
||||||
|
// there would be no mutablility error for locals in dead code. Rustc tries to
|
||||||
|
// not emit `unused_mut` in this case, but since it works without `mut`, and
|
||||||
|
// special casing it is not trivial, we emit it.
|
||||||
|
check_diagnostics(
|
||||||
|
r#"
|
||||||
|
fn main() {
|
||||||
|
return;
|
||||||
|
let mut x = 2;
|
||||||
|
//^^^^^ weak: remove this `mut`
|
||||||
|
&mut x;
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
check_diagnostics(
|
||||||
|
r#"
|
||||||
|
fn main() {
|
||||||
|
loop {}
|
||||||
|
let mut x = 2;
|
||||||
|
//^^^^^ weak: remove this `mut`
|
||||||
|
&mut x;
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
check_diagnostics(
|
||||||
|
r#"
|
||||||
|
enum X {}
|
||||||
|
fn g() -> X {
|
||||||
|
loop {}
|
||||||
|
}
|
||||||
|
fn f() -> ! {
|
||||||
|
loop {}
|
||||||
|
}
|
||||||
|
fn main(b: bool) {
|
||||||
|
if b {
|
||||||
|
f();
|
||||||
|
} else {
|
||||||
|
g();
|
||||||
|
}
|
||||||
|
let mut x = 2;
|
||||||
|
//^^^^^ weak: remove this `mut`
|
||||||
|
&mut x;
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
check_diagnostics(
|
||||||
|
r#"
|
||||||
|
fn main(b: bool) {
|
||||||
|
if b {
|
||||||
|
loop {}
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let mut x = 2;
|
||||||
|
//^^^^^ weak: remove this `mut`
|
||||||
|
&mut x;
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn initialization_is_not_mutation() {
|
||||||
|
check_diagnostics(
|
||||||
|
r#"
|
||||||
|
fn f(_: i32) {}
|
||||||
|
fn main() {
|
||||||
|
let mut x;
|
||||||
|
//^^^^^ weak: remove this `mut`
|
||||||
|
x = 5;
|
||||||
|
f(x);
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
check_diagnostics(
|
||||||
|
r#"
|
||||||
|
fn f(_: i32) {}
|
||||||
|
fn main(b: bool) {
|
||||||
|
let mut x;
|
||||||
|
//^^^^^ weak: remove this `mut`
|
||||||
|
if b {
|
||||||
|
x = 1;
|
||||||
|
} else {
|
||||||
|
x = 3;
|
||||||
|
}
|
||||||
|
f(x);
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
check_diagnostics(
|
||||||
|
r#"
|
||||||
|
fn f(_: i32) {}
|
||||||
|
fn main(b: bool) {
|
||||||
|
let x;
|
||||||
|
if b {
|
||||||
|
x = 1;
|
||||||
|
}
|
||||||
|
x = 3;
|
||||||
|
//^^^^^ error: cannot mutate immutable variable `x`
|
||||||
|
f(x);
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
check_diagnostics(
|
||||||
|
r#"
|
||||||
|
fn f(_: i32) {}
|
||||||
|
fn main() {
|
||||||
|
let x;
|
||||||
|
loop {
|
||||||
|
x = 1;
|
||||||
|
//^^^^^ error: cannot mutate immutable variable `x`
|
||||||
|
f(x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
check_diagnostics(
|
||||||
|
r#"
|
||||||
|
fn f(_: i32) {}
|
||||||
|
fn main() {
|
||||||
|
loop {
|
||||||
|
let mut x = 1;
|
||||||
|
//^^^^^ weak: remove this `mut`
|
||||||
|
f(x);
|
||||||
|
if let mut y = 2 {
|
||||||
|
//^^^^^ weak: remove this `mut`
|
||||||
|
f(y);
|
||||||
|
}
|
||||||
|
match 3 {
|
||||||
|
mut z => f(z),
|
||||||
|
//^^^^^ weak: remove this `mut`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -37,6 +37,7 @@ mod handlers {
|
||||||
pub(crate) mod missing_fields;
|
pub(crate) mod missing_fields;
|
||||||
pub(crate) mod missing_match_arms;
|
pub(crate) mod missing_match_arms;
|
||||||
pub(crate) mod missing_unsafe;
|
pub(crate) mod missing_unsafe;
|
||||||
|
pub(crate) mod mutability_errors;
|
||||||
pub(crate) mod no_such_field;
|
pub(crate) mod no_such_field;
|
||||||
pub(crate) mod private_assoc_item;
|
pub(crate) mod private_assoc_item;
|
||||||
pub(crate) mod private_field;
|
pub(crate) mod private_field;
|
||||||
|
@ -273,7 +274,8 @@ pub fn diagnostics(
|
||||||
AnyDiagnostic::InvalidDeriveTarget(d) => handlers::invalid_derive_target::invalid_derive_target(&ctx, &d),
|
AnyDiagnostic::InvalidDeriveTarget(d) => handlers::invalid_derive_target::invalid_derive_target(&ctx, &d),
|
||||||
AnyDiagnostic::UnresolvedField(d) => handlers::unresolved_field::unresolved_field(&ctx, &d),
|
AnyDiagnostic::UnresolvedField(d) => handlers::unresolved_field::unresolved_field(&ctx, &d),
|
||||||
AnyDiagnostic::UnresolvedMethodCall(d) => handlers::unresolved_method::unresolved_method(&ctx, &d),
|
AnyDiagnostic::UnresolvedMethodCall(d) => handlers::unresolved_method::unresolved_method(&ctx, &d),
|
||||||
|
AnyDiagnostic::NeedMut(d) => handlers::mutability_errors::need_mut(&ctx, &d),
|
||||||
|
AnyDiagnostic::UnusedMut(d) => handlers::mutability_errors::unused_mut(&ctx, &d),
|
||||||
AnyDiagnostic::InactiveCode(d) => match handlers::inactive_code::inactive_code(&ctx, &d) {
|
AnyDiagnostic::InactiveCode(d) => match handlers::inactive_code::inactive_code(&ctx, &d) {
|
||||||
Some(it) => it,
|
Some(it) => it,
|
||||||
None => continue,
|
None => continue,
|
||||||
|
|
Loading…
Reference in a new issue