Add View Mir command and fix some bugs

This commit is contained in:
hkalbasi 2023-02-26 16:04:41 +03:30
parent a25710b0c0
commit ac04bfd7a7
23 changed files with 876 additions and 122 deletions

View file

@ -18,7 +18,7 @@ use crate::{
chalk_db, chalk_db,
consteval::ConstEvalError, consteval::ConstEvalError,
method_resolution::{InherentImpls, TraitImpls, TyFingerprint}, method_resolution::{InherentImpls, TraitImpls, TyFingerprint},
mir::{MirBody, MirLowerError}, mir::{BorrowckResult, MirBody, MirLowerError},
Binders, CallableDefId, Const, FnDefId, GenericArg, ImplTraitId, InferenceResult, Interner, Binders, CallableDefId, Const, FnDefId, GenericArg, ImplTraitId, InferenceResult, Interner,
PolyFnSig, QuantifiedWhereClause, ReturnTypeImplTraits, Substitution, TraitRef, Ty, TyDefId, PolyFnSig, QuantifiedWhereClause, ReturnTypeImplTraits, Substitution, TraitRef, Ty, TyDefId,
ValueTyDefId, ValueTyDefId,
@ -38,6 +38,9 @@ pub trait HirDatabase: DefDatabase + Upcast<dyn DefDatabase> {
#[salsa::cycle(crate::mir::mir_body_recover)] #[salsa::cycle(crate::mir::mir_body_recover)]
fn mir_body(&self, def: DefWithBodyId) -> Result<Arc<MirBody>, MirLowerError>; fn mir_body(&self, def: DefWithBodyId) -> Result<Arc<MirBody>, MirLowerError>;
#[salsa::invoke(crate::mir::borrowck_query)]
fn borrowck(&self, def: DefWithBodyId) -> Result<Arc<BorrowckResult>, MirLowerError>;
#[salsa::invoke(crate::lower::ty_query)] #[salsa::invoke(crate::lower::ty_query)]
#[salsa::cycle(crate::lower::ty_recover)] #[salsa::cycle(crate::lower::ty_recover)]
fn ty(&self, def: TyDefId) -> Binders<Ty>; fn ty(&self, def: TyDefId) -> Binders<Ty>;

View file

@ -531,6 +531,7 @@ fn render_const_scalar(
hir_def::AdtId::UnionId(u) => write!(f, "{}", f.db.union_data(u).name), hir_def::AdtId::UnionId(u) => write!(f, "{}", f.db.union_data(u).name),
hir_def::AdtId::EnumId(_) => f.write_str("<enum-not-supported>"), hir_def::AdtId::EnumId(_) => f.write_str("<enum-not-supported>"),
}, },
chalk_ir::TyKind::FnDef(..) => ty.hir_fmt(f),
_ => f.write_str("<not-supported>"), _ => f.write_str("<not-supported>"),
} }
} }

View file

@ -1,6 +1,6 @@
//! MIR definitions and implementation //! MIR definitions and implementation
use std::iter; use std::{fmt::Display, iter};
use crate::{ use crate::{
infer::PointerCast, Const, ConstScalar, InferenceResult, Interner, MemoryMap, Substitution, Ty, infer::PointerCast, Const, ConstScalar, InferenceResult, Interner, MemoryMap, Substitution, Ty,
@ -14,8 +14,10 @@ use la_arena::{Arena, ArenaMap, Idx, RawIdx};
mod eval; mod eval;
mod lower; mod lower;
pub mod borrowck; mod borrowck;
mod pretty;
pub use borrowck::{borrowck_query, BorrowckResult, MutabilityReason};
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};
@ -32,13 +34,7 @@ fn return_slot() -> LocalId {
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub struct Local { pub struct Local {
pub mutability: Mutability,
//pub local_info: Option<Box<LocalInfo>>,
//pub internal: bool,
//pub is_block_tail: Option<BlockTailInfo>,
pub ty: Ty, pub ty: Ty,
//pub user_ty: Option<Box<UserTypeProjections>>,
//pub source_info: SourceInfo,
} }
/// An operand in MIR represents a "value" in Rust, the definition of which is undecided and part of /// An operand in MIR represents a "value" in Rust, the definition of which is undecided and part of
@ -564,6 +560,30 @@ pub enum BinOp {
Offset, Offset,
} }
impl Display for BinOp {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
BinOp::Add => "+",
BinOp::Sub => "-",
BinOp::Mul => "*",
BinOp::Div => "/",
BinOp::Rem => "%",
BinOp::BitXor => "^",
BinOp::BitAnd => "&",
BinOp::BitOr => "|",
BinOp::Shl => "<<",
BinOp::Shr => ">>",
BinOp::Eq => "==",
BinOp::Lt => "<",
BinOp::Le => "<=",
BinOp::Ne => "!=",
BinOp::Ge => ">=",
BinOp::Gt => ">",
BinOp::Offset => "`offset`",
})
}
}
impl From<hir_def::expr::ArithOp> for BinOp { impl From<hir_def::expr::ArithOp> for BinOp {
fn from(value: hir_def::expr::ArithOp) -> Self { fn from(value: hir_def::expr::ArithOp) -> Self {
match value { match value {
@ -822,10 +842,9 @@ pub struct MirBody {
pub owner: DefWithBodyId, pub owner: DefWithBodyId,
pub arg_count: usize, pub arg_count: usize,
pub binding_locals: ArenaMap<BindingId, LocalId>, pub binding_locals: ArenaMap<BindingId, LocalId>,
pub param_locals: Vec<LocalId>,
} }
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
} }

View file

@ -1,22 +1,43 @@
//! MIR borrow checker, which is used in diagnostics like `unused_mut` //! 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 // 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. // if needed for implementing a proper borrow checker.
use std::sync::Arc;
use hir_def::DefWithBodyId;
use la_arena::ArenaMap; use la_arena::ArenaMap;
use stdx::never; use stdx::never;
use crate::db::HirDatabase;
use super::{ use super::{
BasicBlockId, BorrowKind, LocalId, MirBody, MirSpan, Place, ProjectionElem, Rvalue, BasicBlockId, BorrowKind, LocalId, MirBody, MirLowerError, MirSpan, Place, ProjectionElem,
StatementKind, Terminator, Rvalue, StatementKind, Terminator,
}; };
#[derive(Debug)] #[derive(Debug, Clone, PartialEq, Eq)]
pub enum Mutability { /// Stores spans which implies that the local should be mutable.
Mut { span: MirSpan }, pub enum MutabilityReason {
Mut { spans: Vec<MirSpan> },
Not, Not,
} }
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BorrowckResult {
pub mir_body: Arc<MirBody>,
pub mutability_of_locals: ArenaMap<LocalId, MutabilityReason>,
}
pub fn borrowck_query(
db: &dyn HirDatabase,
def: DefWithBodyId,
) -> Result<Arc<BorrowckResult>, MirLowerError> {
let body = db.mir_body(def)?;
let r = BorrowckResult { mutability_of_locals: mutability_of_locals(&body), mir_body: body };
Ok(Arc::new(r))
}
fn is_place_direct(lvalue: &Place) -> bool { fn is_place_direct(lvalue: &Place) -> bool {
!lvalue.projection.iter().any(|x| *x == ProjectionElem::Deref) !lvalue.projection.iter().any(|x| *x == ProjectionElem::Deref)
} }
@ -116,28 +137,28 @@ fn ever_initialized_map(body: &MirBody) -> ArenaMap<BasicBlockId, ArenaMap<Local
} }
} }
} }
for (b, block) in body.basic_blocks.iter() { for &l in &body.param_locals {
for statement in &block.statements { result[body.start_block].insert(l, true);
if let StatementKind::Assign(p, _) = &statement.kind { dfs(body, body.start_block, l, &mut result);
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);
}
}
} }
for l in body.locals.iter().map(|x| x.0) {
if !result[body.start_block].contains_idx(l) {
result[body.start_block].insert(l, false);
dfs(body, body.start_block, l, &mut result);
} }
} }
result result
} }
pub fn mutability_of_locals(body: &MirBody) -> ArenaMap<LocalId, Mutability> { fn mutability_of_locals(body: &MirBody) -> ArenaMap<LocalId, MutabilityReason> {
let mut result: ArenaMap<LocalId, Mutability> = let mut result: ArenaMap<LocalId, MutabilityReason> =
body.locals.iter().map(|x| (x.0, Mutability::Not)).collect(); body.locals.iter().map(|x| (x.0, MutabilityReason::Not)).collect();
let mut push_mut_span = |local, span| match &mut result[local] {
MutabilityReason::Mut { spans } => spans.push(span),
x @ MutabilityReason::Not => *x = MutabilityReason::Mut { spans: vec![span] },
};
let ever_init_maps = ever_initialized_map(body); let ever_init_maps = ever_initialized_map(body);
for (block_id, ever_init_map) in ever_init_maps.iter() { for (block_id, mut ever_init_map) in ever_init_maps.into_iter() {
let mut ever_init_map = ever_init_map.clone();
let block = &body.basic_blocks[block_id]; let block = &body.basic_blocks[block_id];
for statement in &block.statements { for statement in &block.statements {
match &statement.kind { match &statement.kind {
@ -145,20 +166,20 @@ pub fn mutability_of_locals(body: &MirBody) -> ArenaMap<LocalId, Mutability> {
match place_case(place) { match place_case(place) {
ProjectionCase::Direct => { ProjectionCase::Direct => {
if ever_init_map.get(place.local).copied().unwrap_or_default() { if ever_init_map.get(place.local).copied().unwrap_or_default() {
result[place.local] = Mutability::Mut { span: statement.span }; push_mut_span(place.local, statement.span);
} else { } else {
ever_init_map.insert(place.local, true); ever_init_map.insert(place.local, true);
} }
} }
ProjectionCase::DirectPart => { ProjectionCase::DirectPart => {
// Partial initialization is not supported, so it is definitely `mut` // Partial initialization is not supported, so it is definitely `mut`
result[place.local] = Mutability::Mut { span: statement.span }; push_mut_span(place.local, statement.span);
} }
ProjectionCase::Indirect => (), ProjectionCase::Indirect => (),
} }
if let Rvalue::Ref(BorrowKind::Mut { .. }, p) = value { if let Rvalue::Ref(BorrowKind::Mut { .. }, p) = value {
if is_place_direct(p) { if is_place_direct(p) {
result[p.local] = Mutability::Mut { span: statement.span }; push_mut_span(p.local, statement.span);
} }
} }
} }
@ -189,7 +210,7 @@ pub fn mutability_of_locals(body: &MirBody) -> ArenaMap<LocalId, Mutability> {
Terminator::Call { destination, .. } => { Terminator::Call { destination, .. } => {
if destination.projection.len() == 0 { if destination.projection.len() == 0 {
if ever_init_map.get(destination.local).copied().unwrap_or_default() { if ever_init_map.get(destination.local).copied().unwrap_or_default() {
result[destination.local] = Mutability::Mut { span: MirSpan::Unknown }; push_mut_span(destination.local, MirSpan::Unknown);
} else { } else {
ever_init_map.insert(destination.local, true); ever_init_map.insert(destination.local, true);
} }

View file

@ -16,7 +16,7 @@ use hir_def::{
use la_arena::ArenaMap; use la_arena::ArenaMap;
use crate::{ use crate::{
consteval::ConstEvalError, db::HirDatabase, infer::TypeMismatch, consteval::ConstEvalError, db::HirDatabase, display::HirDisplay, infer::TypeMismatch,
inhabitedness::is_ty_uninhabited_from, layout::layout_of_ty, mapping::ToChalk, utils::generics, inhabitedness::is_ty_uninhabited_from, layout::layout_of_ty, mapping::ToChalk, utils::generics,
Adjust, AutoBorrow, CallableDefId, TyBuilder, TyExt, Adjust, AutoBorrow, CallableDefId, TyBuilder, TyExt,
}; };
@ -46,6 +46,7 @@ pub enum MirLowerError {
LayoutError(LayoutError), LayoutError(LayoutError),
IncompleteExpr, IncompleteExpr,
UnresolvedName(String), UnresolvedName(String),
RecordLiteralWithoutPath,
UnresolvedMethod, UnresolvedMethod,
UnresolvedField, UnresolvedField,
MissingFunctionDefinition, MissingFunctionDefinition,
@ -88,7 +89,7 @@ impl MirLowerCtx<'_> {
if matches!(ty.kind(Interner), TyKind::Slice(_) | TyKind::Dyn(_)) { if matches!(ty.kind(Interner), TyKind::Slice(_) | TyKind::Dyn(_)) {
not_supported!("unsized temporaries"); not_supported!("unsized temporaries");
} }
Ok(self.result.locals.alloc(Local { mutability: Mutability::Not, ty })) Ok(self.result.locals.alloc(Local { ty }))
} }
fn lower_expr_as_place(&self, expr_id: ExprId) -> Option<Place> { fn lower_expr_as_place(&self, expr_id: ExprId) -> Option<Place> {
@ -251,7 +252,7 @@ impl MirLowerCtx<'_> {
match &self.body.exprs[expr_id] { match &self.body.exprs[expr_id] {
Expr::Missing => Err(MirLowerError::IncompleteExpr), Expr::Missing => Err(MirLowerError::IncompleteExpr),
Expr::Path(p) => { Expr::Path(p) => {
let unresolved_name = || MirLowerError::UnresolvedName("".to_string()); let unresolved_name = || MirLowerError::UnresolvedName(p.display(self.db).to_string());
let resolver = resolver_for_expr(self.db.upcast(), self.owner, expr_id); let resolver = resolver_for_expr(self.db.upcast(), self.owner, expr_id);
let pr = resolver let pr = resolver
.resolve_path_in_value_ns(self.db.upcast(), p.mod_path()) .resolve_path_in_value_ns(self.db.upcast(), p.mod_path())
@ -259,19 +260,29 @@ impl MirLowerCtx<'_> {
let pr = match pr { let pr = match pr {
ResolveValueResult::ValueNs(v) => v, ResolveValueResult::ValueNs(v) => v,
ResolveValueResult::Partial(..) => { ResolveValueResult::Partial(..) => {
return match self if let Some(assoc) = self
.infer .infer
.assoc_resolutions_for_expr(expr_id) .assoc_resolutions_for_expr(expr_id)
.ok_or_else(unresolved_name)?
.0
//.ok_or(ConstEvalError::SemanticError("unresolved assoc item"))?
{ {
match assoc.0 {
hir_def::AssocItemId::ConstId(c) => { hir_def::AssocItemId::ConstId(c) => {
self.lower_const(c, current, place, expr_id.into())?; self.lower_const(c, current, place, expr_id.into())?;
Ok(Some(current)) return Ok(Some(current))
}, },
_ => return Err(unresolved_name()), _ => not_supported!("associated functions and types"),
}; }
} else if let Some(variant) = self
.infer
.variant_resolution_for_expr(expr_id)
{
match variant {
VariantId::EnumVariantId(e) => ValueNs::EnumVariantId(e),
VariantId::StructId(s) => ValueNs::StructId(s),
VariantId::UnionId(_) => return Err(MirLowerError::ImplementationError("Union variant as path")),
}
} else {
return Err(unresolved_name());
}
} }
}; };
match pr { match pr {
@ -597,11 +608,14 @@ impl MirLowerCtx<'_> {
Ok(None) Ok(None)
} }
Expr::Yield { .. } => not_supported!("yield"), Expr::Yield { .. } => not_supported!("yield"),
Expr::RecordLit { fields, .. } => { Expr::RecordLit { fields, path, .. } => {
let variant_id = self let variant_id = self
.infer .infer
.variant_resolution_for_expr(expr_id) .variant_resolution_for_expr(expr_id)
.ok_or_else(|| MirLowerError::UnresolvedName("".to_string()))?; .ok_or_else(|| match path {
Some(p) => MirLowerError::UnresolvedName(p.display(self.db).to_string()),
None => MirLowerError::RecordLiteralWithoutPath,
})?;
let subst = match self.expr_ty(expr_id).kind(Interner) { let subst = match self.expr_ty(expr_id).kind(Interner) {
TyKind::Adt(_, s) => s.clone(), TyKind::Adt(_, s) => s.clone(),
_ => not_supported!("Non ADT record literal"), _ => not_supported!("Non ADT record literal"),
@ -1437,17 +1451,17 @@ pub fn lower_to_mir(
basic_blocks.alloc(BasicBlock { statements: vec![], terminator: None, is_cleanup: false }); basic_blocks.alloc(BasicBlock { statements: vec![], terminator: None, is_cleanup: false });
let mut locals = Arena::new(); let mut locals = Arena::new();
// 0 is return local // 0 is return local
locals.alloc(Local { mutability: Mutability::Mut, ty: infer[root_expr].clone() }); locals.alloc(Local { ty: infer[root_expr].clone() });
let mut binding_locals: ArenaMap<BindingId, LocalId> = ArenaMap::new(); let mut binding_locals: ArenaMap<BindingId, LocalId> = ArenaMap::new();
let param_locals: ArenaMap<PatId, LocalId> = if let DefWithBodyId::FunctionId(fid) = owner { // 1 to param_len is for params
let param_locals: Vec<LocalId> = if let DefWithBodyId::FunctionId(fid) = owner {
let substs = TyBuilder::placeholder_subst(db, fid); let substs = TyBuilder::placeholder_subst(db, fid);
let callable_sig = db.callable_item_signature(fid.into()).substitute(Interner, &substs); let callable_sig = db.callable_item_signature(fid.into()).substitute(Interner, &substs);
// 1 to param_len is for params
body.params body.params
.iter() .iter()
.zip(callable_sig.params().iter()) .zip(callable_sig.params().iter())
.map(|(&x, ty)| { .map(|(&x, ty)| {
let local_id = locals.alloc(Local { mutability: Mutability::Not, ty: ty.clone() }); let local_id = locals.alloc(Local { ty: ty.clone() });
if let Pat::Bind { id, subpat: None } = body[x] { if let Pat::Bind { id, subpat: None } = body[x] {
if matches!( if matches!(
body.bindings[id].mode, body.bindings[id].mode,
@ -1456,22 +1470,19 @@ pub fn lower_to_mir(
binding_locals.insert(id, local_id); binding_locals.insert(id, local_id);
} }
} }
(x, local_id) local_id
}) })
.collect() .collect()
} else { } else {
if !body.params.is_empty() { if !body.params.is_empty() {
return Err(MirLowerError::TypeError("Unexpected parameter for non function body")); return Err(MirLowerError::TypeError("Unexpected parameter for non function body"));
} }
ArenaMap::new() vec![]
}; };
// and then rest of bindings // and then rest of bindings
for (id, _) in body.bindings.iter() { for (id, _) in body.bindings.iter() {
if !binding_locals.contains_idx(id) { if !binding_locals.contains_idx(id) {
binding_locals.insert( binding_locals.insert(id, locals.alloc(Local { ty: infer[id].clone() }));
id,
locals.alloc(Local { mutability: Mutability::Not, ty: infer[id].clone() }),
);
} }
} }
let mir = MirBody { let mir = MirBody {
@ -1479,6 +1490,7 @@ pub fn lower_to_mir(
locals, locals,
start_block, start_block,
binding_locals, binding_locals,
param_locals,
owner, owner,
arg_count: body.params.len(), arg_count: body.params.len(),
}; };
@ -1492,17 +1504,17 @@ pub fn lower_to_mir(
discr_temp: None, discr_temp: None,
}; };
let mut current = start_block; let mut current = start_block;
for &param in &body.params { for (&param, local) in body.params.iter().zip(ctx.result.param_locals.clone().into_iter()) {
if let Pat::Bind { id, .. } = body[param] { if let Pat::Bind { id, .. } = body[param] {
if param_locals[param] == ctx.result.binding_locals[id] { if local == ctx.result.binding_locals[id] {
continue; continue;
} }
} }
let r = ctx.pattern_match( let r = ctx.pattern_match(
current, current,
None, None,
param_locals[param].into(), local.into(),
ctx.result.locals[param_locals[param]].ty.clone(), ctx.result.locals[local].ty.clone(),
param, param,
BindingAnnotation::Unannotated, BindingAnnotation::Unannotated,
)?; )?;

View file

@ -0,0 +1,348 @@
//! A pretty-printer for MIR.
use std::fmt::{Display, Write};
use hir_def::{body::Body, expr::BindingId};
use hir_expand::name::Name;
use la_arena::ArenaMap;
use crate::{
db::HirDatabase,
display::HirDisplay,
mir::{PlaceElem, ProjectionElem, StatementKind, Terminator},
};
use super::{
AggregateKind, BasicBlockId, BorrowKind, LocalId, MirBody, Operand, Place, Rvalue, UnOp,
};
impl MirBody {
pub fn pretty_print(&self, db: &dyn HirDatabase) -> String {
let hir_body = db.body(self.owner);
let mut ctx = MirPrettyCtx::new(self, &hir_body, db);
ctx.for_body();
ctx.result
}
}
struct MirPrettyCtx<'a> {
body: &'a MirBody,
hir_body: &'a Body,
db: &'a dyn HirDatabase,
result: String,
ident: String,
local_to_binding: ArenaMap<LocalId, BindingId>,
}
macro_rules! w {
($dst:expr, $($arg:tt)*) => {
{ let _ = write!($dst, $($arg)*); }
};
}
macro_rules! wln {
($dst:expr) => {
{ let _ = writeln!($dst); }
};
($dst:expr, $($arg:tt)*) => {
{ let _ = writeln!($dst, $($arg)*); }
};
}
impl Write for MirPrettyCtx<'_> {
fn write_str(&mut self, s: &str) -> std::fmt::Result {
let mut it = s.split('\n'); // note: `.lines()` is wrong here
self.write(it.next().unwrap_or_default());
for line in it {
self.write_line();
self.write(line);
}
Ok(())
}
}
enum LocalName {
Unknown(LocalId),
Binding(Name, LocalId),
}
impl Display for LocalName {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
LocalName::Unknown(l) => write!(f, "_{}", u32::from(l.into_raw())),
LocalName::Binding(n, l) => write!(f, "{n}_{}", u32::from(l.into_raw())),
}
}
}
impl<'a> MirPrettyCtx<'a> {
fn for_body(&mut self) {
self.with_block(|this| {
this.locals();
wln!(this);
this.blocks();
});
}
fn with_block(&mut self, f: impl FnOnce(&mut MirPrettyCtx<'_>)) {
self.ident += " ";
wln!(self, "{{");
f(self);
for _ in 0..4 {
self.result.pop();
self.ident.pop();
}
wln!(self, "}}");
}
fn new(body: &'a MirBody, hir_body: &'a Body, db: &'a dyn HirDatabase) -> Self {
let local_to_binding = body.binding_locals.iter().map(|(x, y)| (*y, x)).collect();
MirPrettyCtx {
body,
db,
result: String::new(),
ident: String::new(),
local_to_binding,
hir_body,
}
}
fn write_line(&mut self) {
self.result.push('\n');
self.result += &self.ident;
}
fn write(&mut self, line: &str) {
self.result += line;
}
fn locals(&mut self) {
for (id, local) in self.body.locals.iter() {
wln!(self, "let {}: {};", self.local_name(id), local.ty.display(self.db));
}
}
fn local_name(&self, local: LocalId) -> LocalName {
match self.local_to_binding.get(local) {
Some(b) => LocalName::Binding(self.hir_body.bindings[*b].name.clone(), local),
None => LocalName::Unknown(local),
}
}
fn basic_block_id(&self, basic_block_id: BasicBlockId) -> String {
format!("'bb{}", u32::from(basic_block_id.into_raw()))
}
fn blocks(&mut self) {
for (id, block) in self.body.basic_blocks.iter() {
wln!(self);
w!(self, "{}: ", self.basic_block_id(id));
self.with_block(|this| {
for statement in &block.statements {
match &statement.kind {
StatementKind::Assign(l, r) => {
this.place(l);
w!(this, " = ");
this.rvalue(r);
wln!(this, ";");
}
StatementKind::StorageDead(p) => {
wln!(this, "StorageDead({})", this.local_name(*p));
}
StatementKind::StorageLive(p) => {
wln!(this, "StorageLive({})", this.local_name(*p));
}
StatementKind::Deinit(p) => {
w!(this, "Deinit(");
this.place(p);
wln!(this, ");");
}
StatementKind::Nop => wln!(this, "Nop;"),
}
}
match &block.terminator {
Some(terminator) => match terminator {
Terminator::Goto { target } => {
wln!(this, "goto 'bb{};", u32::from(target.into_raw()))
}
Terminator::SwitchInt { discr, targets } => {
w!(this, "switch ");
this.operand(discr);
w!(this, " ");
this.with_block(|this| {
for (c, b) in targets.iter() {
wln!(this, "{c} => {},", this.basic_block_id(b));
}
wln!(this, "_ => {},", this.basic_block_id(targets.otherwise()));
});
}
Terminator::Call { func, args, destination, target, .. } => {
w!(this, "Call ");
this.with_block(|this| {
w!(this, "func: ");
this.operand(func);
wln!(this, ",");
w!(this, "args: [");
this.operand_list(args);
wln!(this, "],");
w!(this, "destination: ");
this.place(destination);
wln!(this, ",");
w!(this, "target: ");
match target {
Some(t) => w!(this, "{}", this.basic_block_id(*t)),
None => w!(this, "<unreachable>"),
}
wln!(this, ",");
});
}
_ => wln!(this, "{:?};", terminator),
},
None => wln!(this, "<no-terminator>;"),
}
})
}
}
fn place(&mut self, p: &Place) {
fn f(this: &mut MirPrettyCtx<'_>, local: LocalId, projections: &[PlaceElem]) {
let Some((last, head)) = projections.split_last() else {
// no projection
w!(this, "{}", this.local_name(local));
return;
};
match last {
ProjectionElem::Deref => {
w!(this, "(*");
f(this, local, head);
w!(this, ")");
}
ProjectionElem::Field(field) => {
let variant_data = field.parent.variant_data(this.db.upcast());
let name = &variant_data.fields()[field.local_id].name;
match field.parent {
hir_def::VariantId::EnumVariantId(e) => {
w!(this, "(");
f(this, local, head);
let variant_name =
&this.db.enum_data(e.parent).variants[e.local_id].name;
w!(this, " as {}).{}", variant_name, name);
}
hir_def::VariantId::StructId(_) | hir_def::VariantId::UnionId(_) => {
f(this, local, head);
w!(this, ".{name}");
}
}
}
ProjectionElem::TupleField(x) => {
f(this, local, head);
w!(this, ".{}", x);
}
ProjectionElem::Index(l) => {
f(this, local, head);
w!(this, "[{}]", this.local_name(*l));
}
x => {
f(this, local, head);
w!(this, ".{:?}", x);
}
}
}
f(self, p.local, &p.projection);
}
fn operand(&mut self, r: &Operand) {
match r {
Operand::Copy(p) | Operand::Move(p) => {
// MIR at the time of writing doesn't have difference between move and copy, so we show them
// equally. Feel free to change it.
self.place(p);
}
Operand::Constant(c) => w!(self, "Const({})", c.display(self.db)),
}
}
fn rvalue(&mut self, r: &Rvalue) {
match r {
Rvalue::Use(op) => self.operand(op),
Rvalue::Ref(r, p) => {
match r {
BorrowKind::Shared => w!(self, "&"),
BorrowKind::Shallow => w!(self, "&shallow "),
BorrowKind::Unique => w!(self, "&uniq "),
BorrowKind::Mut { .. } => w!(self, "&mut "),
}
self.place(p);
}
Rvalue::Aggregate(AggregateKind::Tuple(_), x) => {
w!(self, "(");
self.operand_list(x);
w!(self, ")");
}
Rvalue::Aggregate(AggregateKind::Array(_), x) => {
w!(self, "[");
self.operand_list(x);
w!(self, "]");
}
Rvalue::Aggregate(AggregateKind::Adt(_, _), x) => {
w!(self, "Adt(");
self.operand_list(x);
w!(self, ")");
}
Rvalue::Aggregate(AggregateKind::Union(_, _), x) => {
w!(self, "Union(");
self.operand_list(x);
w!(self, ")");
}
Rvalue::Len(p) => {
w!(self, "Len(");
self.place(p);
w!(self, ")");
}
Rvalue::Cast(ck, op, ty) => {
w!(self, "Discriminant({ck:?}");
self.operand(op);
w!(self, "{})", ty.display(self.db));
}
Rvalue::CheckedBinaryOp(b, o1, o2) => {
self.operand(o1);
w!(self, " {b} ");
self.operand(o2);
}
Rvalue::UnaryOp(u, o) => {
let u = match u {
UnOp::Not => "!",
UnOp::Neg => "-",
};
w!(self, "{u} ");
self.operand(o);
}
Rvalue::Discriminant(p) => {
w!(self, "Discriminant(");
self.place(p);
w!(self, ")");
}
Rvalue::ShallowInitBox(op, _) => {
w!(self, "ShallowInitBox(");
self.operand(op);
w!(self, ")");
}
Rvalue::CopyForDeref(p) => {
w!(self, "CopyForDeref(");
self.place(p);
w!(self, ")");
}
}
}
fn operand_list(&mut self, x: &[Operand]) {
let mut it = x.iter();
if let Some(first) = it.next() {
self.operand(first);
for op in it {
w!(self, ", ");
self.operand(op);
}
}
}
}

View file

@ -10,8 +10,8 @@ use hir_expand::InFile;
use syntax::ast; use syntax::ast;
use crate::{ use crate::{
db::HirDatabase, Adt, Const, Enum, Field, FieldSource, Function, Impl, LifetimeParam, Macro, db::HirDatabase, Adt, Const, Enum, Field, FieldSource, Function, Impl, LifetimeParam,
Module, Static, Struct, Trait, TraitAlias, TypeAlias, TypeOrConstParam, Union, Variant, LocalSource, Macro, Module, Static, Struct, Trait, TypeAlias, TraitAlias, TypeOrConstParam, Union, Variant,
}; };
pub trait HasSource { pub trait HasSource {
@ -178,3 +178,11 @@ impl HasSource for LifetimeParam {
Some(child_source.map(|it| it[self.id.local_id].clone())) Some(child_source.map(|it| it[self.id.local_id].clone()))
} }
} }
impl HasSource for LocalSource {
type Ast = Either<ast::IdentPat, ast::SelfParam>;
fn source(self, _: &dyn HirDatabase) -> Option<InFile<Self::Ast>> {
Some(self.source)
}
}

View file

@ -1327,6 +1327,15 @@ impl DefWithBody {
body.pretty_print(db.upcast(), self.id()) body.pretty_print(db.upcast(), self.id())
} }
/// A textual representation of the MIR of this def's body for debugging purposes.
pub fn debug_mir(self, db: &dyn HirDatabase) -> String {
let body = db.mir_body(self.id());
match body {
Ok(body) => body.pretty_print(db),
Err(e) => format!("error:\n{e:?}"),
}
}
pub fn diagnostics(self, db: &dyn HirDatabase, acc: &mut Vec<AnyDiagnostic>) { pub fn diagnostics(self, db: &dyn HirDatabase, acc: &mut Vec<AnyDiagnostic>) {
let krate = self.module(db).id.krate(); let krate = self.module(db).id.krate();
@ -1502,15 +1511,17 @@ impl DefWithBody {
let hir_body = db.body(self.into()); let hir_body = db.body(self.into());
if let Ok(mir_body) = db.mir_body(self.into()) { if let Ok(borrowck_result) = db.borrowck(self.into()) {
let mol = mir::borrowck::mutability_of_locals(&mir_body); let mir_body = &borrowck_result.mir_body;
let mol = &borrowck_result.mutability_of_locals;
for (binding_id, _) in hir_body.bindings.iter() { for (binding_id, _) in hir_body.bindings.iter() {
let need_mut = &mol[mir_body.binding_locals[binding_id]]; let need_mut = &mol[mir_body.binding_locals[binding_id]];
let local = Local { parent: self.into(), binding_id }; let local = Local { parent: self.into(), binding_id };
match (need_mut, local.is_mut(db)) { match (need_mut, local.is_mut(db)) {
(mir::borrowck::Mutability::Mut { .. }, true) (mir::MutabilityReason::Mut { .. }, true)
| (mir::borrowck::Mutability::Not, false) => (), | (mir::MutabilityReason::Not, false) => (),
(mir::borrowck::Mutability::Mut { span }, false) => { (mir::MutabilityReason::Mut { spans }, false) => {
for span in spans {
let span: InFile<SyntaxNodePtr> = match span { let span: InFile<SyntaxNodePtr> = match span {
mir::MirSpan::ExprId(e) => match source_map.expr_syntax(*e) { mir::MirSpan::ExprId(e) => match source_map.expr_syntax(*e) {
Ok(s) => s.map(|x| x.into()), Ok(s) => s.map(|x| x.into()),
@ -1527,7 +1538,8 @@ impl DefWithBody {
}; };
acc.push(NeedMut { local, span }.into()); acc.push(NeedMut { local, span }.into());
} }
(mir::borrowck::Mutability::Not, true) => acc.push(UnusedMut { local }.into()), }
(mir::MutabilityReason::Not, true) => acc.push(UnusedMut { local }.into()),
} }
} }
} }
@ -2519,6 +2531,10 @@ impl LocalSource {
self.source.file_id.original_file(db.upcast()) self.source.file_id.original_file(db.upcast())
} }
pub fn name(&self) -> Option<ast::Name> {
self.source.value.name()
}
pub fn syntax(&self) -> &SyntaxNode { pub fn syntax(&self) -> &SyntaxNode {
self.source.value.syntax() self.source.value.syntax()
} }

View file

@ -121,8 +121,7 @@ impl Definition {
Definition::Trait(it) => name_range(it, sema), Definition::Trait(it) => name_range(it, sema),
Definition::TraitAlias(it) => name_range(it, sema), Definition::TraitAlias(it) => name_range(it, sema),
Definition::TypeAlias(it) => name_range(it, sema), Definition::TypeAlias(it) => name_range(it, sema),
// A local might be `self` or have multiple definitons like `let (a | a) = 2`, so it should be handled as a special case Definition::Local(it) => name_range(it.primary_source(sema.db), sema),
Definition::Local(_) => return None,
Definition::GenericParam(generic_param) => match generic_param { Definition::GenericParam(generic_param) => match generic_param {
hir::GenericParam::LifetimeParam(lifetime_param) => { hir::GenericParam::LifetimeParam(lifetime_param) => {
let src = lifetime_param.source(sema.db)?; let src = lifetime_param.source(sema.db)?;

View file

@ -1,31 +1,85 @@
use crate::{Diagnostic, DiagnosticsContext, Severity}; use ide_db::source_change::SourceChange;
use syntax::{AstNode, SyntaxKind, SyntaxNode, SyntaxToken, T};
use text_edit::TextEdit;
use crate::{fix, Diagnostic, DiagnosticsContext, Severity};
// Diagnostic: need-mut // Diagnostic: need-mut
// //
// This diagnostic is triggered on mutating an immutable variable. // This diagnostic is triggered on mutating an immutable variable.
pub(crate) fn need_mut(ctx: &DiagnosticsContext<'_>, d: &hir::NeedMut) -> Diagnostic { pub(crate) fn need_mut(ctx: &DiagnosticsContext<'_>, d: &hir::NeedMut) -> Diagnostic {
let fixes = (|| {
if d.local.is_ref(ctx.sema.db) {
// There is no simple way to add `mut` to `ref x` and `ref mut x`
return None;
}
let file_id = d.span.file_id.file_id()?;
let mut edit_builder = TextEdit::builder();
let use_range = d.span.value.text_range();
for source in d.local.sources(ctx.sema.db) {
let Some(ast) = source.name() else { continue };
edit_builder.insert(ast.syntax().text_range().start(), "mut ".to_string());
}
let edit = edit_builder.finish();
Some(vec![fix(
"remove_mut",
"Remove unnecessary `mut`",
SourceChange::from_text_edit(file_id, edit),
use_range,
)])
})();
Diagnostic::new( Diagnostic::new(
"need-mut", "need-mut",
format!("cannot mutate immutable variable `{}`", d.local.name(ctx.sema.db)), format!("cannot mutate immutable variable `{}`", d.local.name(ctx.sema.db)),
ctx.sema.diagnostics_display_range(d.span.clone()).range, ctx.sema.diagnostics_display_range(d.span.clone()).range,
) )
.with_fixes(fixes)
} }
// Diagnostic: unused-mut // Diagnostic: unused-mut
// //
// This diagnostic is triggered when a mutable variable isn't actually mutated. // This diagnostic is triggered when a mutable variable isn't actually mutated.
pub(crate) fn unused_mut(ctx: &DiagnosticsContext<'_>, d: &hir::UnusedMut) -> Diagnostic { pub(crate) fn unused_mut(ctx: &DiagnosticsContext<'_>, d: &hir::UnusedMut) -> Diagnostic {
let ast = d.local.primary_source(ctx.sema.db).syntax_ptr();
let fixes = (|| {
let file_id = ast.file_id.file_id()?;
let mut edit_builder = TextEdit::builder();
let use_range = ast.value.text_range();
for source in d.local.sources(ctx.sema.db) {
let ast = source.syntax();
let Some(mut_token) = token(ast, T![mut]) else { continue };
edit_builder.delete(mut_token.text_range());
if let Some(token) = mut_token.next_token() {
if token.kind() == SyntaxKind::WHITESPACE {
edit_builder.delete(token.text_range());
}
}
}
let edit = edit_builder.finish();
Some(vec![fix(
"remove_mut",
"Remove unnecessary `mut`",
SourceChange::from_text_edit(file_id, edit),
use_range,
)])
})();
let ast = d.local.primary_source(ctx.sema.db).syntax_ptr();
Diagnostic::new( Diagnostic::new(
"unused-mut", "unused-mut",
"remove this `mut`", "remove this `mut`",
ctx.sema.diagnostics_display_range(d.local.primary_source(ctx.sema.db).syntax_ptr()).range, ctx.sema.diagnostics_display_range(ast).range,
) )
.severity(Severity::WeakWarning) .severity(Severity::WeakWarning)
.with_fixes(fixes)
}
pub(super) fn token(parent: &SyntaxNode, kind: SyntaxKind) -> Option<SyntaxToken> {
parent.children_with_tokens().filter_map(|it| it.into_token()).find(|it| it.kind() == kind)
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::tests::check_diagnostics; use crate::tests::{check_diagnostics, check_fix};
#[test] #[test]
fn unused_mut_simple() { fn unused_mut_simple() {
@ -34,7 +88,7 @@ mod tests {
fn f(_: i32) {} fn f(_: i32) {}
fn main() { fn main() {
let mut x = 2; let mut x = 2;
//^^^^^ weak: remove this `mut` //^^^^^ 💡 weak: remove this `mut`
f(x); f(x);
} }
"#, "#,
@ -64,6 +118,144 @@ fn main() {
); );
} }
#[test]
fn multiple_errors_for_single_variable() {
check_diagnostics(
r#"
fn f(_: i32) {}
fn main() {
let x = 2;
x = 10;
//^^^^^^ 💡 error: cannot mutate immutable variable `x`
x = 5;
//^^^^^ 💡 error: cannot mutate immutable variable `x`
&mut x;
//^^^^^^ 💡 error: cannot mutate immutable variable `x`
f(x);
}
"#,
);
}
#[test]
fn unused_mut_fix() {
check_fix(
r#"
fn f(_: i32) {}
fn main() {
let mu$0t x = 2;
f(x);
}
"#,
r#"
fn f(_: i32) {}
fn main() {
let x = 2;
f(x);
}
"#,
);
check_fix(
r#"
fn f(_: i32) {}
fn main() {
let ((mu$0t x, _) | (_, mut x)) = (2, 3);
f(x);
}
"#,
r#"
fn f(_: i32) {}
fn main() {
let ((x, _) | (_, x)) = (2, 3);
f(x);
}
"#,
);
}
#[test]
fn need_mut_fix() {
check_fix(
r#"
fn f(_: i32) {}
fn main() {
let x = 2;
x$0 = 5;
f(x);
}
"#,
r#"
fn f(_: i32) {}
fn main() {
let mut x = 2;
x = 5;
f(x);
}
"#,
);
check_fix(
r#"
fn f(_: i32) {}
fn main() {
let ((x, _) | (_, x)) = (2, 3);
x =$0 4;
f(x);
}
"#,
r#"
fn f(_: i32) {}
fn main() {
let ((mut x, _) | (_, mut x)) = (2, 3);
x = 4;
f(x);
}
"#,
);
check_fix(
r#"
struct Foo(i32);
impl Foo {
fn foo(self) {
self = Fo$0o(5);
}
}
"#,
r#"
struct Foo(i32);
impl Foo {
fn foo(mut self) {
self = Foo(5);
}
}
"#,
);
}
#[test]
fn need_mut_fix_not_applicable_on_ref() {
check_diagnostics(
r#"
fn main() {
let ref x = 2;
x = &5;
//^^^^^^ error: cannot mutate immutable variable `x`
}
"#,
);
check_diagnostics(
r#"
fn main() {
let ref mut x = 2;
x = &mut 5;
//^^^^^^^^^^ error: cannot mutate immutable variable `x`
}
"#,
);
}
#[test] #[test]
fn field_mutate() { fn field_mutate() {
check_diagnostics( check_diagnostics(
@ -71,7 +263,7 @@ fn main() {
fn f(_: i32) {} fn f(_: i32) {}
fn main() { fn main() {
let mut x = (2, 7); let mut x = (2, 7);
//^^^^^ weak: remove this `mut` //^^^^^ 💡 weak: remove this `mut`
f(x.1); f(x.1);
} }
"#, "#,
@ -92,7 +284,7 @@ fn f(_: i32) {}
fn main() { fn main() {
let x = (2, 7); let x = (2, 7);
x.0 = 5; x.0 = 5;
//^^^^^^^ error: cannot mutate immutable variable `x` //^^^^^^^ 💡 error: cannot mutate immutable variable `x`
f(x.1); f(x.1);
} }
"#, "#,
@ -105,7 +297,7 @@ fn main() {
r#" r#"
fn main() { fn main() {
let mut x = &mut 2; let mut x = &mut 2;
//^^^^^ weak: remove this `mut` //^^^^^ 💡 weak: remove this `mut`
*x = 5; *x = 5;
} }
"#, "#,
@ -115,7 +307,7 @@ fn main() {
fn main() { fn main() {
let x = 2; let x = 2;
&mut x; &mut x;
//^^^^^^ error: cannot mutate immutable variable `x` //^^^^^^ 💡 error: cannot mutate immutable variable `x`
} }
"#, "#,
); );
@ -124,7 +316,7 @@ fn main() {
fn main() { fn main() {
let x_own = 2; let x_own = 2;
let ref mut x_ref = x_own; let ref mut x_ref = x_own;
//^^^^^^^^^^^^^ error: cannot mutate immutable variable `x_own` //^^^^^^^^^^^^^ 💡 error: cannot mutate immutable variable `x_own`
} }
"#, "#,
); );
@ -137,7 +329,7 @@ impl Foo {
fn main() { fn main() {
let x = Foo; let x = Foo;
x.method(2); x.method(2);
//^ error: cannot mutate immutable variable `x` //^ 💡 error: cannot mutate immutable variable `x`
} }
"#, "#,
); );
@ -150,9 +342,9 @@ fn main() {
fn main() { fn main() {
match (2, 3) { match (2, 3) {
(x, mut y) => { (x, mut y) => {
//^^^^^ weak: remove this `mut` //^^^^^ 💡 weak: remove this `mut`
x = 7; x = 7;
//^^^^^ error: cannot mutate immutable variable `x` //^^^^^ 💡 error: cannot mutate immutable variable `x`
} }
} }
} }
@ -171,7 +363,7 @@ fn main() {
fn main() { fn main() {
return; return;
let mut x = 2; let mut x = 2;
//^^^^^ weak: remove this `mut` //^^^^^ 💡 weak: remove this `mut`
&mut x; &mut x;
} }
"#, "#,
@ -181,7 +373,7 @@ fn main() {
fn main() { fn main() {
loop {} loop {}
let mut x = 2; let mut x = 2;
//^^^^^ weak: remove this `mut` //^^^^^ 💡 weak: remove this `mut`
&mut x; &mut x;
} }
"#, "#,
@ -202,7 +394,7 @@ fn main(b: bool) {
g(); g();
} }
let mut x = 2; let mut x = 2;
//^^^^^ weak: remove this `mut` //^^^^^ 💡 weak: remove this `mut`
&mut x; &mut x;
} }
"#, "#,
@ -216,7 +408,7 @@ fn main(b: bool) {
return; return;
} }
let mut x = 2; let mut x = 2;
//^^^^^ weak: remove this `mut` //^^^^^ 💡 weak: remove this `mut`
&mut x; &mut x;
} }
"#, "#,
@ -230,7 +422,7 @@ fn main(b: bool) {
fn f(_: i32) {} fn f(_: i32) {}
fn main() { fn main() {
let mut x; let mut x;
//^^^^^ weak: remove this `mut` //^^^^^ 💡 weak: remove this `mut`
x = 5; x = 5;
f(x); f(x);
} }
@ -241,7 +433,7 @@ fn main() {
fn f(_: i32) {} fn f(_: i32) {}
fn main(b: bool) { fn main(b: bool) {
let mut x; let mut x;
//^^^^^ weak: remove this `mut` //^^^^^ 💡 weak: remove this `mut`
if b { if b {
x = 1; x = 1;
} else { } else {
@ -260,7 +452,7 @@ fn main(b: bool) {
x = 1; x = 1;
} }
x = 3; x = 3;
//^^^^^ error: cannot mutate immutable variable `x` //^^^^^ 💡 error: cannot mutate immutable variable `x`
f(x); f(x);
} }
"#, "#,
@ -272,7 +464,7 @@ fn main() {
let x; let x;
loop { loop {
x = 1; x = 1;
//^^^^^ error: cannot mutate immutable variable `x` //^^^^^ 💡 error: cannot mutate immutable variable `x`
f(x); f(x);
} }
} }
@ -284,18 +476,37 @@ fn f(_: i32) {}
fn main() { fn main() {
loop { loop {
let mut x = 1; let mut x = 1;
//^^^^^ weak: remove this `mut` //^^^^^ 💡 weak: remove this `mut`
f(x); f(x);
if let mut y = 2 { if let mut y = 2 {
//^^^^^ weak: remove this `mut` //^^^^^ 💡 weak: remove this `mut`
f(y); f(y);
} }
match 3 { match 3 {
mut z => f(z), mut z => f(z),
//^^^^^ weak: remove this `mut` //^^^^^ 💡 weak: remove this `mut`
} }
} }
} }
"#,
);
}
#[test]
fn function_arguments_are_initialized() {
check_diagnostics(
r#"
fn f(mut x: i32) {
//^^^^^ 💡 weak: remove this `mut`
}
"#,
);
check_diagnostics(
r#"
fn f(x: i32) {
x = 5;
//^^^^^ 💡 error: cannot mutate immutable variable `x`
}
"#, "#,
); );
} }

View file

@ -55,6 +55,7 @@ mod syntax_tree;
mod typing; mod typing;
mod view_crate_graph; mod view_crate_graph;
mod view_hir; mod view_hir;
mod view_mir;
mod view_item_tree; mod view_item_tree;
mod shuffle_crate_graph; mod shuffle_crate_graph;
@ -308,6 +309,10 @@ impl Analysis {
self.with_db(|db| view_hir::view_hir(db, position)) self.with_db(|db| view_hir::view_hir(db, position))
} }
pub fn view_mir(&self, position: FilePosition) -> Cancellable<String> {
self.with_db(|db| view_mir::view_mir(db, position))
}
pub fn view_item_tree(&self, file_id: FileId) -> Cancellable<String> { pub fn view_item_tree(&self, file_id: FileId) -> Cancellable<String> {
self.with_db(|db| view_item_tree::view_item_tree(db, file_id)) self.with_db(|db| view_item_tree::view_item_tree(db, file_id))
} }

View file

@ -353,6 +353,11 @@ mod tests {
fn check(new_name: &str, ra_fixture_before: &str, ra_fixture_after: &str) { fn check(new_name: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
let ra_fixture_after = &trim_indent(ra_fixture_after); let ra_fixture_after = &trim_indent(ra_fixture_after);
let (analysis, position) = fixture::position(ra_fixture_before); let (analysis, position) = fixture::position(ra_fixture_before);
if !ra_fixture_after.starts_with("error: ") {
if let Err(err) = analysis.prepare_rename(position).unwrap() {
panic!("Prepare rename to '{new_name}' was failed: {err}")
}
}
let rename_result = analysis let rename_result = analysis
.rename(position, new_name) .rename(position, new_name)
.unwrap_or_else(|err| panic!("Rename to '{new_name}' was cancelled: {err}")); .unwrap_or_else(|err| panic!("Rename to '{new_name}' was cancelled: {err}"));
@ -1709,6 +1714,23 @@ fn foo(bar: i32) -> Foo {
); );
} }
#[test]
fn test_rename_local_simple() {
check(
"i",
r#"
fn foo(bar$0: i32) -> i32 {
bar
}
"#,
r#"
fn foo(i: i32) -> i32 {
i
}
"#,
);
}
#[test] #[test]
fn test_rename_local_put_init_shorthand() { fn test_rename_local_put_init_shorthand() {
cov_mark::check!(test_rename_local_put_init_shorthand); cov_mark::check!(test_rename_local_put_init_shorthand);

View file

@ -0,0 +1,29 @@
use hir::{DefWithBody, Semantics};
use ide_db::base_db::FilePosition;
use ide_db::RootDatabase;
use syntax::{algo::find_node_at_offset, ast, AstNode};
// Feature: View Mir
//
// |===
// | Editor | Action Name
//
// | VS Code | **rust-analyzer: View Mir**
// |===
pub(crate) fn view_mir(db: &RootDatabase, position: FilePosition) -> String {
body_mir(db, position).unwrap_or_else(|| "Not inside a function body".to_string())
}
fn body_mir(db: &RootDatabase, position: FilePosition) -> Option<String> {
let sema = Semantics::new(db);
let source_file = sema.parse(position.file_id);
let item = find_node_at_offset::<ast::Item>(source_file.syntax(), position.offset)?;
let def: DefWithBody = match item {
ast::Item::Fn(it) => sema.to_def(&it)?.into(),
ast::Item::Const(it) => sema.to_def(&it)?.into(),
ast::Item::Static(it) => sema.to_def(&it)?.into(),
_ => return None,
};
Some(def.debug_mir(db))
}

View file

@ -134,6 +134,16 @@ pub(crate) fn handle_view_hir(
Ok(res) Ok(res)
} }
pub(crate) fn handle_view_mir(
snap: GlobalStateSnapshot,
params: lsp_types::TextDocumentPositionParams,
) -> Result<String> {
let _p = profile::span("handle_view_mir");
let position = from_proto::file_position(&snap, params)?;
let res = snap.analysis.view_mir(position)?;
Ok(res)
}
pub(crate) fn handle_view_file_text( pub(crate) fn handle_view_file_text(
snap: GlobalStateSnapshot, snap: GlobalStateSnapshot,
params: lsp_types::TextDocumentIdentifier, params: lsp_types::TextDocumentIdentifier,

View file

@ -74,6 +74,14 @@ impl Request for ViewHir {
const METHOD: &'static str = "rust-analyzer/viewHir"; const METHOD: &'static str = "rust-analyzer/viewHir";
} }
pub enum ViewMir {}
impl Request for ViewMir {
type Params = lsp_types::TextDocumentPositionParams;
type Result = String;
const METHOD: &'static str = "rust-analyzer/viewMir";
}
pub enum ViewFileText {} pub enum ViewFileText {}
impl Request for ViewFileText { impl Request for ViewFileText {

View file

@ -634,6 +634,7 @@ impl GlobalState {
.on::<lsp_ext::AnalyzerStatus>(handlers::handle_analyzer_status) .on::<lsp_ext::AnalyzerStatus>(handlers::handle_analyzer_status)
.on::<lsp_ext::SyntaxTree>(handlers::handle_syntax_tree) .on::<lsp_ext::SyntaxTree>(handlers::handle_syntax_tree)
.on::<lsp_ext::ViewHir>(handlers::handle_view_hir) .on::<lsp_ext::ViewHir>(handlers::handle_view_hir)
.on::<lsp_ext::ViewMir>(handlers::handle_view_mir)
.on::<lsp_ext::ViewFileText>(handlers::handle_view_file_text) .on::<lsp_ext::ViewFileText>(handlers::handle_view_file_text)
.on::<lsp_ext::ViewCrateGraph>(handlers::handle_view_crate_graph) .on::<lsp_ext::ViewCrateGraph>(handlers::handle_view_crate_graph)
.on::<lsp_ext::ViewItemTree>(handlers::handle_view_item_tree) .on::<lsp_ext::ViewItemTree>(handlers::handle_view_item_tree)

View file

@ -134,3 +134,5 @@ impl Iterator for AttrDocCommentIter {
}) })
} }
} }
impl<A: HasName, B: HasName> HasName for Either<A, B> {}

View file

@ -1,5 +1,5 @@
<!--- <!---
lsp_ext.rs hash: d87477896dfe41d4 lsp_ext.rs hash: 37f31ae648632897
If you need to change the above hash to make the test pass, please check if you If you need to change the above hash to make the test pass, please check if you
need to adjust this doc as well and ping this issue: need to adjust this doc as well and ping this issue:
@ -527,6 +527,17 @@ Primarily for debugging, but very useful for all people working on rust-analyzer
Returns a textual representation of the HIR of the function containing the cursor. Returns a textual representation of the HIR of the function containing the cursor.
For debugging or when working on rust-analyzer itself. For debugging or when working on rust-analyzer itself.
## View Mir
**Method:** `rust-analyzer/viewMir`
**Request:** `TextDocumentPositionParams`
**Response:** `string`
Returns a textual representation of the MIR of the function containing the cursor.
For debugging or when working on rust-analyzer itself.
## View File Text ## View File Text
**Method:** `rust-analyzer/viewFileText` **Method:** `rust-analyzer/viewFileText`

View file

@ -114,6 +114,11 @@
"title": "View Hir", "title": "View Hir",
"category": "rust-analyzer (debug command)" "category": "rust-analyzer (debug command)"
}, },
{
"command": "rust-analyzer.viewMir",
"title": "View Mir",
"category": "rust-analyzer (debug command)"
},
{ {
"command": "rust-analyzer.viewFileText", "command": "rust-analyzer.viewFileText",
"title": "View File Text (as seen by the server)", "title": "View File Text (as seen by the server)",

View file

@ -405,12 +405,11 @@ export function syntaxTree(ctx: CtxInit): Cmd {
}; };
} }
// Opens the virtual file that will show the HIR of the function containing the cursor position function viewHirOrMir(ctx: CtxInit, xir: "hir" | "mir"): Cmd {
// const viewXir = xir === "hir" ? "viewHir" : "viewMir";
// The contents of the file come from the `TextDocumentContentProvider` const requestType = xir === "hir" ? ra.viewHir : ra.viewMir;
export function viewHir(ctx: CtxInit): Cmd {
const tdcp = new (class implements vscode.TextDocumentContentProvider { const tdcp = new (class implements vscode.TextDocumentContentProvider {
readonly uri = vscode.Uri.parse("rust-analyzer-hir://viewHir/hir.rs"); readonly uri = vscode.Uri.parse(`rust-analyzer-${xir}://${viewXir}/${xir}.rs`);
readonly eventEmitter = new vscode.EventEmitter<vscode.Uri>(); readonly eventEmitter = new vscode.EventEmitter<vscode.Uri>();
constructor() { constructor() {
vscode.workspace.onDidChangeTextDocument( vscode.workspace.onDidChangeTextDocument(
@ -452,7 +451,7 @@ export function viewHir(ctx: CtxInit): Cmd {
), ),
position: client.code2ProtocolConverter.asPosition(rustEditor.selection.active), position: client.code2ProtocolConverter.asPosition(rustEditor.selection.active),
}; };
return client.sendRequest(ra.viewHir, params, ct); return client.sendRequest(requestType, params, ct);
} }
get onDidChange(): vscode.Event<vscode.Uri> { get onDidChange(): vscode.Event<vscode.Uri> {
@ -461,7 +460,7 @@ export function viewHir(ctx: CtxInit): Cmd {
})(); })();
ctx.pushExtCleanup( ctx.pushExtCleanup(
vscode.workspace.registerTextDocumentContentProvider("rust-analyzer-hir", tdcp) vscode.workspace.registerTextDocumentContentProvider(`rust-analyzer-${xir}`, tdcp)
); );
return async () => { return async () => {
@ -474,6 +473,20 @@ export function viewHir(ctx: CtxInit): Cmd {
}; };
} }
// Opens the virtual file that will show the HIR of the function containing the cursor position
//
// The contents of the file come from the `TextDocumentContentProvider`
export function viewHir(ctx: CtxInit): Cmd {
return viewHirOrMir(ctx, "hir");
}
// Opens the virtual file that will show the MIR of the function containing the cursor position
//
// The contents of the file come from the `TextDocumentContentProvider`
export function viewMir(ctx: CtxInit): Cmd {
return viewHirOrMir(ctx, "mir");
}
export function viewFileText(ctx: CtxInit): Cmd { export function viewFileText(ctx: CtxInit): Cmd {
const tdcp = new (class implements vscode.TextDocumentContentProvider { const tdcp = new (class implements vscode.TextDocumentContentProvider {
readonly uri = vscode.Uri.parse("rust-analyzer-file-text://viewFileText/file.rs"); readonly uri = vscode.Uri.parse("rust-analyzer-file-text://viewFileText/file.rs");

View file

@ -59,6 +59,9 @@ export const viewFileText = new lc.RequestType<lc.TextDocumentIdentifier, string
export const viewHir = new lc.RequestType<lc.TextDocumentPositionParams, string, void>( export const viewHir = new lc.RequestType<lc.TextDocumentPositionParams, string, void>(
"rust-analyzer/viewHir" "rust-analyzer/viewHir"
); );
export const viewMir = new lc.RequestType<lc.TextDocumentPositionParams, string, void>(
"rust-analyzer/viewMir"
);
export const viewItemTree = new lc.RequestType<ViewItemTreeParams, string, void>( export const viewItemTree = new lc.RequestType<ViewItemTreeParams, string, void>(
"rust-analyzer/viewItemTree" "rust-analyzer/viewItemTree"
); );

View file

@ -158,6 +158,7 @@ function createCommands(): Record<string, CommandFactory> {
parentModule: { enabled: commands.parentModule }, parentModule: { enabled: commands.parentModule },
syntaxTree: { enabled: commands.syntaxTree }, syntaxTree: { enabled: commands.syntaxTree },
viewHir: { enabled: commands.viewHir }, viewHir: { enabled: commands.viewHir },
viewMir: { enabled: commands.viewMir },
viewFileText: { enabled: commands.viewFileText }, viewFileText: { enabled: commands.viewFileText },
viewItemTree: { enabled: commands.viewItemTree }, viewItemTree: { enabled: commands.viewItemTree },
viewCrateGraph: { enabled: commands.viewCrateGraph }, viewCrateGraph: { enabled: commands.viewCrateGraph },

View file

@ -94,6 +94,12 @@ impl<T, V> ArenaMap<Idx<T>, V> {
.filter_map(|(idx, o)| Some((Self::from_idx(idx), o.as_mut()?))) .filter_map(|(idx, o)| Some((Self::from_idx(idx), o.as_mut()?)))
} }
/// Returns an iterator over the arena indexes and values in the map.
// FIXME: Implement `IntoIterator` trait.
pub fn into_iter(self) -> impl Iterator<Item = (Idx<T>, V)> {
self.v.into_iter().enumerate().filter_map(|(idx, o)| Some((Self::from_idx(idx), o?)))
}
/// Gets the given key's corresponding entry in the map for in-place manipulation. /// Gets the given key's corresponding entry in the map for in-place manipulation.
pub fn entry(&mut self, idx: Idx<T>) -> Entry<'_, Idx<T>, V> { pub fn entry(&mut self, idx: Idx<T>) -> Entry<'_, Idx<T>, V> {
let idx = Self::to_idx(idx); let idx = Self::to_idx(idx);