mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-26 21:13:37 +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 {
|
||||
pretty::print_body_hir(db, self, owner)
|
||||
}
|
||||
|
|
|
@ -103,6 +103,22 @@ fn references() {
|
|||
"#,
|
||||
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]
|
||||
|
@ -358,7 +374,7 @@ fn ifs() {
|
|||
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,
|
||||
);
|
||||
|
@ -366,7 +382,7 @@ fn ifs() {
|
|||
check_number(
|
||||
r#"
|
||||
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);
|
||||
|
@ -464,6 +480,16 @@ fn tuples() {
|
|||
"#,
|
||||
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(
|
||||
r#"
|
||||
struct TupleLike(i32, u8, i64, u16);
|
||||
|
@ -539,7 +565,7 @@ fn let_else() {
|
|||
let Some(x) = x else { return 10 };
|
||||
2 * x
|
||||
}
|
||||
const GOAL: u8 = f(Some(1000)) + f(None);
|
||||
const GOAL: i32 = f(Some(1000)) + f(None);
|
||||
"#,
|
||||
2010,
|
||||
);
|
||||
|
@ -615,7 +641,7 @@ fn options() {
|
|||
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,
|
||||
);
|
||||
|
@ -746,24 +772,24 @@ fn enums() {
|
|||
r#"
|
||||
enum E {
|
||||
F1 = 1,
|
||||
F2 = 2 * E::F1 as u8,
|
||||
F3 = 3 * E::F2 as u8,
|
||||
F2 = 2 * E::F1 as isize, // Rustc expects an isize here
|
||||
F3 = 3 * E::F2 as isize,
|
||||
}
|
||||
const GOAL: i32 = E::F3 as u8;
|
||||
const GOAL: u8 = E::F3 as u8;
|
||||
"#,
|
||||
6,
|
||||
);
|
||||
check_number(
|
||||
r#"
|
||||
enum E { F1 = 1, F2, }
|
||||
const GOAL: i32 = E::F2 as u8;
|
||||
const GOAL: u8 = E::F2 as u8;
|
||||
"#,
|
||||
2,
|
||||
);
|
||||
check_number(
|
||||
r#"
|
||||
enum E { F1, }
|
||||
const GOAL: i32 = E::F1 as u8;
|
||||
const GOAL: u8 = E::F1 as u8;
|
||||
"#,
|
||||
0,
|
||||
);
|
||||
|
@ -894,8 +920,22 @@ fn exec_limits() {
|
|||
}
|
||||
sum
|
||||
}
|
||||
const GOAL: usize = f(10000);
|
||||
const GOAL: i32 = f(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 hir_def::{
|
||||
expr::{Expr, Ordering},
|
||||
expr::{BindingId, Expr, ExprId, Ordering, PatId},
|
||||
DefWithBodyId, FieldId, UnionId, VariantId,
|
||||
};
|
||||
use la_arena::{Arena, Idx, RawIdx};
|
||||
use la_arena::{Arena, ArenaMap, Idx, RawIdx};
|
||||
|
||||
mod eval;
|
||||
mod lower;
|
||||
pub mod borrowck;
|
||||
|
||||
pub use eval::{interpret_mir, pad16, Evaluator, MirEvalError};
|
||||
pub use lower::{lower_to_mir, mir_body_query, mir_body_recover, MirLowerError};
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
use stdx::impl_from;
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
/// 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
|
||||
/// specific value. This cannot fail, as it'll return the `otherwise`
|
||||
/// branch if there's not a specific match for the value.
|
||||
|
@ -758,7 +765,7 @@ pub enum Rvalue {
|
|||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum Statement {
|
||||
pub enum StatementKind {
|
||||
Assign(Place, Rvalue),
|
||||
//FakeRead(Box<(FakeReadCause, Place)>),
|
||||
//SetDiscriminant {
|
||||
|
@ -773,6 +780,17 @@ pub enum Statement {
|
|||
//Intrinsic(Box<NonDivergingIntrinsic>),
|
||||
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)]
|
||||
pub struct BasicBlock {
|
||||
|
@ -803,6 +821,7 @@ pub struct MirBody {
|
|||
pub start_block: BasicBlockId,
|
||||
pub owner: DefWithBodyId,
|
||||
pub arg_count: usize,
|
||||
pub binding_locals: ArenaMap<BindingId, LocalId>,
|
||||
}
|
||||
|
||||
impl MirBody {}
|
||||
|
@ -810,3 +829,12 @@ impl MirBody {}
|
|||
fn const_as_usize(c: &Const) -> 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::{
|
||||
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> {
|
||||
|
@ -395,7 +395,8 @@ impl Evaluator<'_> {
|
|||
.locals
|
||||
.iter()
|
||||
.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;
|
||||
stack_ptr += size;
|
||||
Ok((id, Stack(my_ptr)))
|
||||
|
@ -425,16 +426,16 @@ impl Evaluator<'_> {
|
|||
return Err(MirEvalError::ExecutionLimitExceeded);
|
||||
}
|
||||
for statement in ¤t_block.statements {
|
||||
match statement {
|
||||
Statement::Assign(l, r) => {
|
||||
match &statement.kind {
|
||||
StatementKind::Assign(l, r) => {
|
||||
let addr = self.place_addr(l, &locals)?;
|
||||
let result = self.eval_rvalue(r, &locals)?.to_vec(&self)?;
|
||||
self.write_memory(addr, &result)?;
|
||||
}
|
||||
Statement::Deinit(_) => not_supported!("de-init statement"),
|
||||
Statement::StorageLive(_) => not_supported!("storage-live statement"),
|
||||
Statement::StorageDead(_) => not_supported!("storage-dead statement"),
|
||||
Statement::Nop => (),
|
||||
StatementKind::Deinit(_) => not_supported!("de-init statement"),
|
||||
StatementKind::StorageLive(_)
|
||||
| StatementKind::StorageDead(_)
|
||||
| StatementKind::Nop => (),
|
||||
}
|
||||
}
|
||||
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 syntax::{ast, AstPtr, SyntaxNodePtr, TextRange};
|
||||
|
||||
use crate::{AssocItem, Field, MacroKind, Type};
|
||||
use crate::{AssocItem, Field, Local, MacroKind, Type};
|
||||
|
||||
macro_rules! diagnostics {
|
||||
($($diag:ident,)*) => {
|
||||
|
@ -41,6 +41,7 @@ diagnostics![
|
|||
MissingFields,
|
||||
MissingMatchArms,
|
||||
MissingUnsafe,
|
||||
NeedMut,
|
||||
NoSuchField,
|
||||
PrivateAssocItem,
|
||||
PrivateField,
|
||||
|
@ -54,6 +55,7 @@ diagnostics![
|
|||
UnresolvedMethodCall,
|
||||
UnresolvedModule,
|
||||
UnresolvedProcMacro,
|
||||
UnusedMut,
|
||||
];
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -209,4 +211,15 @@ pub struct TypeMismatch {
|
|||
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;
|
||||
|
|
|
@ -63,7 +63,7 @@ use hir_ty::{
|
|||
display::HexifiedConst,
|
||||
layout::layout_of_ty,
|
||||
method_resolution::{self, TyFingerprint},
|
||||
mir::interpret_mir,
|
||||
mir::{self, interpret_mir},
|
||||
primitive::UintTy,
|
||||
traits::FnTrait,
|
||||
AliasTy, CallableDefId, CallableSig, Canonical, CanonicalVarKinds, Cast, ClosureId,
|
||||
|
@ -85,12 +85,12 @@ use crate::db::{DefDatabase, HirDatabase};
|
|||
pub use crate::{
|
||||
attrs::{HasAttrs, Namespace},
|
||||
diagnostics::{
|
||||
AnyDiagnostic, BreakOutsideOfLoop, ExpectedFunction, InactiveCode, IncorrectCase,
|
||||
InvalidDeriveTarget, MacroError, MalformedDerive, MismatchedArgCount, MissingFields,
|
||||
MissingMatchArms, MissingUnsafe, NoSuchField, PrivateAssocItem, PrivateField,
|
||||
AnyDiagnostic, BreakOutsideOfLoop, InactiveCode, IncorrectCase, InvalidDeriveTarget,
|
||||
MacroError, MalformedDerive, MismatchedArgCount, MissingFields, MissingMatchArms,
|
||||
MissingUnsafe, NeedMut, NoSuchField, PrivateAssocItem, PrivateField,
|
||||
ReplaceFilterMapNextWithFindMap, TypeMismatch, UnimplementedBuiltinMacro,
|
||||
UnresolvedExternCrate, UnresolvedField, UnresolvedImport, UnresolvedMacroCall,
|
||||
UnresolvedMethodCall, UnresolvedModule, UnresolvedProcMacro,
|
||||
UnresolvedExternCrate, UnresolvedImport, UnresolvedMacroCall, UnresolvedModule,
|
||||
UnresolvedProcMacro, UnusedMut,
|
||||
},
|
||||
has_source::HasSource,
|
||||
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()) {
|
||||
match diagnostic {
|
||||
BodyValidationDiagnostic::RecordMissingFields {
|
||||
|
@ -2490,6 +2522,10 @@ impl LocalSource {
|
|||
pub fn syntax(&self) -> &SyntaxNode {
|
||||
self.source.value.syntax()
|
||||
}
|
||||
|
||||
pub fn syntax_ptr(self) -> InFile<SyntaxNodePtr> {
|
||||
self.source.map(|x| SyntaxNodePtr::new(x.syntax()))
|
||||
}
|
||||
}
|
||||
|
||||
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_match_arms;
|
||||
pub(crate) mod missing_unsafe;
|
||||
pub(crate) mod mutability_errors;
|
||||
pub(crate) mod no_such_field;
|
||||
pub(crate) mod private_assoc_item;
|
||||
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::UnresolvedField(d) => handlers::unresolved_field::unresolved_field(&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) {
|
||||
Some(it) => it,
|
||||
None => continue,
|
||||
|
|
Loading…
Reference in a new issue