Auto merge of #14232 - HKalbasi:mir, r=Veykril

MIR episode 2

This PR adds:
1. `need-mut` and `unused-mut` diagnostics
2. `View mir` command which shows MIR for the body under cursor, useful for debugging
3. MIR lowering for or-patterns and for-loops
This commit is contained in:
bors 2023-03-07 09:49:49 +00:00
commit 44ff3c407a
57 changed files with 3164 additions and 760 deletions

View file

@ -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;

View file

@ -4,7 +4,7 @@
//! are splitting the hir.
use hir_def::{
expr::{LabelId, PatId},
expr::{BindingId, LabelId},
AdtId, AssocItemId, DefWithBodyId, EnumVariantId, FieldId, GenericDefId, GenericParamId,
ModuleDefId, VariantId,
};
@ -251,9 +251,9 @@ impl From<AssocItem> for GenericDefId {
}
}
impl From<(DefWithBodyId, PatId)> for Local {
fn from((parent, pat_id): (DefWithBodyId, PatId)) -> Self {
Local { parent, pat_id }
impl From<(DefWithBodyId, BindingId)> for Local {
fn from((parent, binding_id): (DefWithBodyId, BindingId)) -> Self {
Local { parent, binding_id }
}
}

View file

@ -10,8 +10,9 @@ use hir_expand::InFile;
use syntax::ast;
use crate::{
db::HirDatabase, Adt, Const, Enum, Field, FieldSource, Function, Impl, LifetimeParam, Macro,
Module, Static, Struct, Trait, TraitAlias, TypeAlias, TypeOrConstParam, Union, Variant,
db::HirDatabase, Adt, Const, Enum, Field, FieldSource, Function, Impl, LifetimeParam,
LocalSource, Macro, Module, Static, Struct, Trait, TraitAlias, TypeAlias, TypeOrConstParam,
Union, Variant,
};
pub trait HasSource {
@ -178,3 +179,11 @@ impl HasSource for LifetimeParam {
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

@ -41,7 +41,7 @@ use either::Either;
use hir_def::{
adt::VariantData,
body::{BodyDiagnostic, SyntheticSyntax},
expr::{BindingAnnotation, ExprOrPatId, LabelId, Pat, PatId},
expr::{BindingAnnotation, BindingId, ExprOrPatId, LabelId, Pat},
generics::{LifetimeParamData, TypeOrConstParamData, TypeParamProvenance},
item_tree::ItemTreeNode,
lang_item::{LangItem, LangItemTarget},
@ -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,
@ -77,7 +77,7 @@ use rustc_hash::FxHashSet;
use stdx::{impl_from, never};
use syntax::{
ast::{self, HasAttrs as _, HasDocComments, HasName},
AstNode, AstPtr, SmolStr, SyntaxNodePtr, TextRange, T,
AstNode, AstPtr, SmolStr, SyntaxNode, SyntaxNodePtr, TextRange, T,
};
use crate::db::{DefDatabase, HirDatabase};
@ -87,10 +87,10 @@ pub use crate::{
diagnostics::{
AnyDiagnostic, BreakOutsideOfLoop, ExpectedFunction, InactiveCode, IncorrectCase,
InvalidDeriveTarget, MacroError, MalformedDerive, MismatchedArgCount, MissingFields,
MissingMatchArms, MissingUnsafe, NoSuchField, PrivateAssocItem, PrivateField,
MissingMatchArms, MissingUnsafe, NeedMut, NoSuchField, PrivateAssocItem, PrivateField,
ReplaceFilterMapNextWithFindMap, TypeMismatch, UnimplementedBuiltinMacro,
UnresolvedExternCrate, UnresolvedField, UnresolvedImport, UnresolvedMacroCall,
UnresolvedMethodCall, UnresolvedModule, UnresolvedProcMacro,
UnresolvedMethodCall, UnresolvedModule, UnresolvedProcMacro, UnusedMut,
},
has_source::HasSource,
semantics::{PathResolution, Semantics, SemanticsScope, TypeInfo, VisibleTraits},
@ -1327,6 +1327,15 @@ impl DefWithBody {
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>) {
let krate = self.module(db).id.krate();
@ -1500,6 +1509,41 @@ impl DefWithBody {
}
}
let hir_body = db.body(self.into());
if let Ok(borrowck_result) = db.borrowck(self.into()) {
let mir_body = &borrowck_result.mir_body;
let mol = &borrowck_result.mutability_of_locals;
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::MutabilityReason::Mut { .. }, true)
| (mir::MutabilityReason::Not, false) => (),
(mir::MutabilityReason::Mut { spans }, false) => {
for span in spans {
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::MutabilityReason::Not, true) => acc.push(UnusedMut { local }.into()),
}
}
}
for diagnostic in BodyValidationDiagnostic::collect(db, self.into()) {
match diagnostic {
BodyValidationDiagnostic::RecordMissingFields {
@ -1786,8 +1830,8 @@ impl Param {
let parent = DefWithBodyId::FunctionId(self.func.into());
let body = db.body(parent);
let pat_id = body.params[self.idx];
if let Pat::Bind { .. } = &body[pat_id] {
Some(Local { parent, pat_id: body.params[self.idx] })
if let Pat::Bind { id, .. } = &body[pat_id] {
Some(Local { parent, binding_id: *id })
} else {
None
}
@ -2464,13 +2508,50 @@ impl GenericDef {
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct Local {
pub(crate) parent: DefWithBodyId,
pub(crate) pat_id: PatId,
pub(crate) binding_id: BindingId,
}
pub struct LocalSource {
pub local: Local,
pub source: InFile<Either<ast::IdentPat, ast::SelfParam>>,
}
impl LocalSource {
pub fn as_ident_pat(&self) -> Option<&ast::IdentPat> {
match &self.source.value {
Either::Left(x) => Some(x),
Either::Right(_) => None,
}
}
pub fn into_ident_pat(self) -> Option<ast::IdentPat> {
match self.source.value {
Either::Left(x) => Some(x),
Either::Right(_) => None,
}
}
pub fn original_file(&self, db: &dyn HirDatabase) -> FileId {
self.source.file_id.original_file(db.upcast())
}
pub fn name(&self) -> Option<ast::Name> {
self.source.value.name()
}
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 {
pub fn is_param(self, db: &dyn HirDatabase) -> bool {
let src = self.source(db);
match src.value {
let src = self.primary_source(db);
match src.source.value {
Either::Left(pat) => pat
.syntax()
.ancestors()
@ -2490,13 +2571,7 @@ impl Local {
pub fn name(self, db: &dyn HirDatabase) -> Name {
let body = db.body(self.parent);
match &body[self.pat_id] {
Pat::Bind { name, .. } => name.clone(),
_ => {
stdx::never!("hir::Local is missing a name!");
Name::missing()
}
}
body[self.binding_id].name.clone()
}
pub fn is_self(self, db: &dyn HirDatabase) -> bool {
@ -2505,15 +2580,12 @@ impl Local {
pub fn is_mut(self, db: &dyn HirDatabase) -> bool {
let body = db.body(self.parent);
matches!(&body[self.pat_id], Pat::Bind { mode: BindingAnnotation::Mutable, .. })
body[self.binding_id].mode == BindingAnnotation::Mutable
}
pub fn is_ref(self, db: &dyn HirDatabase) -> bool {
let body = db.body(self.parent);
matches!(
&body[self.pat_id],
Pat::Bind { mode: BindingAnnotation::Ref | BindingAnnotation::RefMut, .. }
)
matches!(body[self.binding_id].mode, BindingAnnotation::Ref | BindingAnnotation::RefMut)
}
pub fn parent(self, _db: &dyn HirDatabase) -> DefWithBody {
@ -2527,34 +2599,33 @@ impl Local {
pub fn ty(self, db: &dyn HirDatabase) -> Type {
let def = self.parent;
let infer = db.infer(def);
let ty = infer[self.pat_id].clone();
let ty = infer[self.binding_id].clone();
Type::new(db, def, ty)
}
pub fn associated_locals(self, db: &dyn HirDatabase) -> Box<[Local]> {
let body = db.body(self.parent);
body.ident_patterns_for(&self.pat_id)
/// All definitions for this local. Example: `let (a$0, _) | (_, a$0) = x;`
pub fn sources(self, db: &dyn HirDatabase) -> Vec<LocalSource> {
let (body, source_map) = db.body_with_source_map(self.parent);
body[self.binding_id]
.definitions
.iter()
.map(|&pat_id| Local { parent: self.parent, pat_id })
.map(|&definition| {
let src = source_map.pat_syntax(definition).unwrap(); // Hmm...
let root = src.file_syntax(db.upcast());
src.map(|ast| match ast {
// Suspicious unwrap
Either::Left(it) => Either::Left(it.cast().unwrap().to_node(&root)),
Either::Right(it) => Either::Right(it.to_node(&root)),
})
})
.map(|source| LocalSource { local: self, source })
.collect()
}
/// If this local is part of a multi-local, retrieve the representative local.
/// That is the local that references are being resolved to.
pub fn representative(self, db: &dyn HirDatabase) -> Local {
let body = db.body(self.parent);
Local { pat_id: body.pattern_representative(self.pat_id), ..self }
}
pub fn source(self, db: &dyn HirDatabase) -> InFile<Either<ast::IdentPat, ast::SelfParam>> {
let (_body, source_map) = db.body_with_source_map(self.parent);
let src = source_map.pat_syntax(self.pat_id).unwrap(); // Hmm...
let root = src.file_syntax(db.upcast());
src.map(|ast| match ast {
// Suspicious unwrap
Either::Left(it) => Either::Left(it.cast().unwrap().to_node(&root)),
Either::Right(it) => Either::Right(it.to_node(&root)),
})
/// The leftmost definition for this local. Example: `let (a$0, _) | (_, a) = x;`
pub fn primary_source(self, db: &dyn HirDatabase) -> LocalSource {
let all_sources = self.sources(db);
all_sources.into_iter().next().unwrap()
}
}

View file

@ -1103,7 +1103,10 @@ impl<'db> SemanticsImpl<'db> {
let kind = match adjust.kind {
hir_ty::Adjust::NeverToAny => Adjust::NeverToAny,
hir_ty::Adjust::Deref(Some(hir_ty::OverloadedDeref(m))) => {
Adjust::Deref(Some(OverloadedDeref(mutability(m))))
// FIXME: Should we handle unknown mutability better?
Adjust::Deref(Some(OverloadedDeref(
m.map(mutability).unwrap_or(Mutability::Shared),
)))
}
hir_ty::Adjust::Deref(None) => Adjust::Deref(None),
hir_ty::Adjust::Borrow(hir_ty::AutoBorrow::RawPtr(m)) => {
@ -1654,8 +1657,8 @@ impl<'a> SemanticsScope<'a> {
resolver::ScopeDef::ImplSelfType(it) => ScopeDef::ImplSelfType(it.into()),
resolver::ScopeDef::AdtSelfType(it) => ScopeDef::AdtSelfType(it.into()),
resolver::ScopeDef::GenericParam(id) => ScopeDef::GenericParam(id.into()),
resolver::ScopeDef::Local(pat_id) => match self.resolver.body_owner() {
Some(parent) => ScopeDef::Local(Local { parent, pat_id }),
resolver::ScopeDef::Local(binding_id) => match self.resolver.body_owner() {
Some(parent) => ScopeDef::Local(Local { parent, binding_id }),
None => continue,
},
resolver::ScopeDef::Label(label_id) => match self.resolver.body_owner() {

View file

@ -89,7 +89,7 @@ use base_db::FileId;
use hir_def::{
child_by_source::ChildBySource,
dyn_map::DynMap,
expr::{LabelId, PatId},
expr::{BindingId, LabelId},
keys::{self, Key},
AdtId, ConstId, ConstParamId, DefWithBodyId, EnumId, EnumVariantId, FieldId, FunctionId,
GenericDefId, GenericParamId, ImplId, LifetimeParamId, MacroId, ModuleId, StaticId, StructId,
@ -98,7 +98,7 @@ use hir_def::{
use hir_expand::{attrs::AttrId, name::AsName, HirFileId, MacroCallId};
use rustc_hash::FxHashMap;
use smallvec::SmallVec;
use stdx::impl_from;
use stdx::{impl_from, never};
use syntax::{
ast::{self, HasName},
AstNode, SyntaxNode,
@ -216,14 +216,14 @@ impl SourceToDefCtx<'_, '_> {
pub(super) fn bind_pat_to_def(
&mut self,
src: InFile<ast::IdentPat>,
) -> Option<(DefWithBodyId, PatId)> {
) -> Option<(DefWithBodyId, BindingId)> {
let container = self.find_pat_or_label_container(src.syntax())?;
let (body, source_map) = self.db.body_with_source_map(container);
let src = src.map(ast::Pat::from);
let pat_id = source_map.node_pat(src.as_ref())?;
// the pattern could resolve to a constant, verify that that is not the case
if let crate::Pat::Bind { .. } = body[pat_id] {
Some((container, pat_id))
if let crate::Pat::Bind { id, .. } = body[pat_id] {
Some((container, id))
} else {
None
}
@ -231,11 +231,16 @@ impl SourceToDefCtx<'_, '_> {
pub(super) fn self_param_to_def(
&mut self,
src: InFile<ast::SelfParam>,
) -> Option<(DefWithBodyId, PatId)> {
) -> Option<(DefWithBodyId, BindingId)> {
let container = self.find_pat_or_label_container(src.syntax())?;
let (_body, source_map) = self.db.body_with_source_map(container);
let (body, source_map) = self.db.body_with_source_map(container);
let pat_id = source_map.node_self_param(src.as_ref())?;
Some((container, pat_id))
if let crate::Pat::Bind { id, .. } = body[pat_id] {
Some((container, id))
} else {
never!();
None
}
}
pub(super) fn label_to_def(
&mut self,

View file

@ -422,8 +422,8 @@ impl SourceAnalyzer {
// Shorthand syntax, resolve to the local
let path = ModPath::from_segments(PathKind::Plain, once(local_name.clone()));
match self.resolver.resolve_path_in_value_ns_fully(db.upcast(), &path) {
Some(ValueNs::LocalBinding(pat_id)) => {
Some(Local { pat_id, parent: self.resolver.body_owner()? })
Some(ValueNs::LocalBinding(binding_id)) => {
Some(Local { binding_id, parent: self.resolver.body_owner()? })
}
_ => None,
}
@ -1018,8 +1018,8 @@ fn resolve_hir_path_(
let values = || {
resolver.resolve_path_in_value_ns_fully(db.upcast(), path.mod_path()).and_then(|val| {
let res = match val {
ValueNs::LocalBinding(pat_id) => {
let var = Local { parent: body_owner?, pat_id };
ValueNs::LocalBinding(binding_id) => {
let var = Local { parent: body_owner?, binding_id };
PathResolution::Local(var)
}
ValueNs::FunctionId(it) => PathResolution::Def(Function::from(it).into()),