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

@ -24,7 +24,7 @@ use syntax::{ast, AstPtr, SyntaxNode, SyntaxNodePtr};
use crate::{ use crate::{
attr::Attrs, attr::Attrs,
db::DefDatabase, db::DefDatabase,
expr::{dummy_expr_id, Expr, ExprId, Label, LabelId, Pat, PatId}, expr::{dummy_expr_id, Binding, BindingId, Expr, ExprId, Label, LabelId, Pat, PatId},
item_scope::BuiltinShadowMode, item_scope::BuiltinShadowMode,
macro_id_to_def_id, macro_id_to_def_id,
nameres::DefMap, nameres::DefMap,
@ -270,6 +270,7 @@ pub struct Mark {
pub struct Body { pub struct Body {
pub exprs: Arena<Expr>, pub exprs: Arena<Expr>,
pub pats: Arena<Pat>, pub pats: Arena<Pat>,
pub bindings: Arena<Binding>,
pub or_pats: FxHashMap<PatId, Arc<[PatId]>>, pub or_pats: FxHashMap<PatId, Arc<[PatId]>>,
pub labels: Arena<Label>, pub labels: Arena<Label>,
/// The patterns for the function's parameters. While the parameter types are /// The patterns for the function's parameters. While the parameter types are
@ -435,13 +436,24 @@ impl Body {
} }
fn shrink_to_fit(&mut self) { fn shrink_to_fit(&mut self) {
let Self { _c: _, body_expr: _, block_scopes, or_pats, exprs, labels, params, pats } = self; let Self {
_c: _,
body_expr: _,
block_scopes,
or_pats,
exprs,
labels,
params,
pats,
bindings,
} = self;
block_scopes.shrink_to_fit(); block_scopes.shrink_to_fit();
or_pats.shrink_to_fit(); or_pats.shrink_to_fit();
exprs.shrink_to_fit(); exprs.shrink_to_fit();
labels.shrink_to_fit(); labels.shrink_to_fit();
params.shrink_to_fit(); params.shrink_to_fit();
pats.shrink_to_fit(); pats.shrink_to_fit();
bindings.shrink_to_fit();
} }
} }
@ -451,6 +463,7 @@ impl Default for Body {
body_expr: dummy_expr_id(), body_expr: dummy_expr_id(),
exprs: Default::default(), exprs: Default::default(),
pats: Default::default(), pats: Default::default(),
bindings: Default::default(),
or_pats: Default::default(), or_pats: Default::default(),
labels: Default::default(), labels: Default::default(),
params: Default::default(), params: Default::default(),
@ -484,6 +497,14 @@ impl Index<LabelId> for Body {
} }
} }
impl Index<BindingId> for Body {
type Output = Binding;
fn index(&self, b: BindingId) -> &Binding {
&self.bindings[b]
}
}
// FIXME: Change `node_` prefix to something more reasonable. // FIXME: Change `node_` prefix to something more reasonable.
// Perhaps `expr_syntax` and `expr_id`? // Perhaps `expr_syntax` and `expr_id`?
impl BodySourceMap { impl BodySourceMap {

View file

@ -15,6 +15,7 @@ use la_arena::Arena;
use once_cell::unsync::OnceCell; use once_cell::unsync::OnceCell;
use profile::Count; use profile::Count;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use smallvec::SmallVec;
use syntax::{ use syntax::{
ast::{ ast::{
self, ArrayExprKind, AstChildren, HasArgList, HasLoopBody, HasName, LiteralKind, self, ArrayExprKind, AstChildren, HasArgList, HasLoopBody, HasName, LiteralKind,
@ -30,9 +31,9 @@ use crate::{
builtin_type::{BuiltinFloat, BuiltinInt, BuiltinUint}, builtin_type::{BuiltinFloat, BuiltinInt, BuiltinUint},
db::DefDatabase, db::DefDatabase,
expr::{ expr::{
dummy_expr_id, Array, BindingAnnotation, ClosureKind, Expr, ExprId, FloatTypeWrapper, dummy_expr_id, Array, Binding, BindingAnnotation, BindingId, ClosureKind, Expr, ExprId,
Label, LabelId, Literal, MatchArm, Movability, Pat, PatId, RecordFieldPat, RecordLitField, FloatTypeWrapper, Label, LabelId, Literal, MatchArm, Movability, Pat, PatId,
Statement, RecordFieldPat, RecordLitField, Statement,
}, },
item_scope::BuiltinShadowMode, item_scope::BuiltinShadowMode,
path::{GenericArgs, Path}, path::{GenericArgs, Path},
@ -87,6 +88,7 @@ pub(super) fn lower(
body: Body { body: Body {
exprs: Arena::default(), exprs: Arena::default(),
pats: Arena::default(), pats: Arena::default(),
bindings: Arena::default(),
labels: Arena::default(), labels: Arena::default(),
params: Vec::new(), params: Vec::new(),
body_expr: dummy_expr_id(), body_expr: dummy_expr_id(),
@ -116,6 +118,22 @@ struct ExprCollector<'a> {
is_lowering_generator: bool, is_lowering_generator: bool,
} }
#[derive(Debug, Default)]
struct BindingList {
map: FxHashMap<Name, BindingId>,
}
impl BindingList {
fn find(
&mut self,
ec: &mut ExprCollector<'_>,
name: Name,
mode: BindingAnnotation,
) -> BindingId {
*self.map.entry(name).or_insert_with_key(|n| ec.alloc_binding(n.clone(), mode))
}
}
impl ExprCollector<'_> { impl ExprCollector<'_> {
fn collect( fn collect(
mut self, mut self,
@ -127,17 +145,16 @@ impl ExprCollector<'_> {
param_list.self_param().filter(|_| attr_enabled.next().unwrap_or(false)) param_list.self_param().filter(|_| attr_enabled.next().unwrap_or(false))
{ {
let ptr = AstPtr::new(&self_param); let ptr = AstPtr::new(&self_param);
let param_pat = self.alloc_pat( let binding_id = self.alloc_binding(
Pat::Bind { name![self],
name: name![self], BindingAnnotation::new(
mode: BindingAnnotation::new( self_param.mut_token().is_some() && self_param.amp_token().is_none(),
self_param.mut_token().is_some() && self_param.amp_token().is_none(), false,
false, ),
),
subpat: None,
},
Either::Right(ptr),
); );
let param_pat =
self.alloc_pat(Pat::Bind { id: binding_id, subpat: None }, Either::Right(ptr));
self.add_definition_to_binding(binding_id, param_pat);
self.body.params.push(param_pat); self.body.params.push(param_pat);
} }
@ -179,6 +196,9 @@ impl ExprCollector<'_> {
id id
} }
fn alloc_binding(&mut self, name: Name, mode: BindingAnnotation) -> BindingId {
self.body.bindings.alloc(Binding { name, mode, definitions: SmallVec::new() })
}
fn alloc_pat(&mut self, pat: Pat, ptr: PatPtr) -> PatId { fn alloc_pat(&mut self, pat: Pat, ptr: PatPtr) -> PatId {
let src = self.expander.to_source(ptr); let src = self.expander.to_source(ptr);
let id = self.make_pat(pat, src.clone()); let id = self.make_pat(pat, src.clone());
@ -804,7 +824,7 @@ impl ExprCollector<'_> {
} }
fn collect_pat(&mut self, pat: ast::Pat) -> PatId { fn collect_pat(&mut self, pat: ast::Pat) -> PatId {
let pat_id = self.collect_pat_(pat); let pat_id = self.collect_pat_(pat, &mut BindingList::default());
for (_, pats) in self.name_to_pat_grouping.drain() { for (_, pats) in self.name_to_pat_grouping.drain() {
let pats = Arc::<[_]>::from(pats); let pats = Arc::<[_]>::from(pats);
self.body.or_pats.extend(pats.iter().map(|&pat| (pat, pats.clone()))); self.body.or_pats.extend(pats.iter().map(|&pat| (pat, pats.clone())));
@ -820,7 +840,7 @@ impl ExprCollector<'_> {
} }
} }
fn collect_pat_(&mut self, pat: ast::Pat) -> PatId { fn collect_pat_(&mut self, pat: ast::Pat, binding_list: &mut BindingList) -> PatId {
let pattern = match &pat { let pattern = match &pat {
ast::Pat::IdentPat(bp) => { ast::Pat::IdentPat(bp) => {
let name = bp.name().map(|nr| nr.as_name()).unwrap_or_else(Name::missing); let name = bp.name().map(|nr| nr.as_name()).unwrap_or_else(Name::missing);
@ -828,8 +848,10 @@ impl ExprCollector<'_> {
let key = self.is_lowering_inside_or_pat.then(|| name.clone()); let key = self.is_lowering_inside_or_pat.then(|| name.clone());
let annotation = let annotation =
BindingAnnotation::new(bp.mut_token().is_some(), bp.ref_token().is_some()); BindingAnnotation::new(bp.mut_token().is_some(), bp.ref_token().is_some());
let subpat = bp.pat().map(|subpat| self.collect_pat_(subpat)); let subpat = bp.pat().map(|subpat| self.collect_pat_(subpat, binding_list));
let pattern = if annotation == BindingAnnotation::Unannotated && subpat.is_none() { let (binding, pattern) = if annotation == BindingAnnotation::Unannotated
&& subpat.is_none()
{
// This could also be a single-segment path pattern. To // This could also be a single-segment path pattern. To
// decide that, we need to try resolving the name. // decide that, we need to try resolving the name.
let (resolved, _) = self.expander.def_map.resolve_path( let (resolved, _) = self.expander.def_map.resolve_path(
@ -839,12 +861,12 @@ impl ExprCollector<'_> {
BuiltinShadowMode::Other, BuiltinShadowMode::Other,
); );
match resolved.take_values() { match resolved.take_values() {
Some(ModuleDefId::ConstId(_)) => Pat::Path(name.into()), Some(ModuleDefId::ConstId(_)) => (None, Pat::Path(name.into())),
Some(ModuleDefId::EnumVariantId(_)) => { Some(ModuleDefId::EnumVariantId(_)) => {
// this is only really valid for unit variants, but // this is only really valid for unit variants, but
// shadowing other enum variants with a pattern is // shadowing other enum variants with a pattern is
// an error anyway // an error anyway
Pat::Path(name.into()) (None, Pat::Path(name.into()))
} }
Some(ModuleDefId::AdtId(AdtId::StructId(s))) Some(ModuleDefId::AdtId(AdtId::StructId(s)))
if self.db.struct_data(s).variant_data.kind() != StructKind::Record => if self.db.struct_data(s).variant_data.kind() != StructKind::Record =>
@ -852,17 +874,24 @@ impl ExprCollector<'_> {
// Funnily enough, record structs *can* be shadowed // Funnily enough, record structs *can* be shadowed
// by pattern bindings (but unit or tuple structs // by pattern bindings (but unit or tuple structs
// can't). // can't).
Pat::Path(name.into()) (None, Pat::Path(name.into()))
} }
// shadowing statics is an error as well, so we just ignore that case here // shadowing statics is an error as well, so we just ignore that case here
_ => Pat::Bind { name, mode: annotation, subpat }, _ => {
let id = binding_list.find(self, name, annotation);
(Some(id), Pat::Bind { id, subpat })
}
} }
} else { } else {
Pat::Bind { name, mode: annotation, subpat } let id = binding_list.find(self, name, annotation);
(Some(id), Pat::Bind { id, subpat })
}; };
let ptr = AstPtr::new(&pat); let ptr = AstPtr::new(&pat);
let pat = self.alloc_pat(pattern, Either::Left(ptr)); let pat = self.alloc_pat(pattern, Either::Left(ptr));
if let Some(binding_id) = binding {
self.add_definition_to_binding(binding_id, pat);
}
if let Some(key) = key { if let Some(key) = key {
self.name_to_pat_grouping.entry(key).or_default().push(pat); self.name_to_pat_grouping.entry(key).or_default().push(pat);
} }
@ -871,11 +900,11 @@ impl ExprCollector<'_> {
ast::Pat::TupleStructPat(p) => { ast::Pat::TupleStructPat(p) => {
let path = let path =
p.path().and_then(|path| self.expander.parse_path(self.db, path)).map(Box::new); p.path().and_then(|path| self.expander.parse_path(self.db, path)).map(Box::new);
let (args, ellipsis) = self.collect_tuple_pat(p.fields()); let (args, ellipsis) = self.collect_tuple_pat(p.fields(), binding_list);
Pat::TupleStruct { path, args, ellipsis } Pat::TupleStruct { path, args, ellipsis }
} }
ast::Pat::RefPat(p) => { ast::Pat::RefPat(p) => {
let pat = self.collect_pat_opt(p.pat()); let pat = self.collect_pat_opt_(p.pat(), binding_list);
let mutability = Mutability::from_mutable(p.mut_token().is_some()); let mutability = Mutability::from_mutable(p.mut_token().is_some());
Pat::Ref { pat, mutability } Pat::Ref { pat, mutability }
} }
@ -886,12 +915,12 @@ impl ExprCollector<'_> {
} }
ast::Pat::OrPat(p) => { ast::Pat::OrPat(p) => {
self.is_lowering_inside_or_pat = true; self.is_lowering_inside_or_pat = true;
let pats = p.pats().map(|p| self.collect_pat_(p)).collect(); let pats = p.pats().map(|p| self.collect_pat_(p, binding_list)).collect();
Pat::Or(pats) Pat::Or(pats)
} }
ast::Pat::ParenPat(p) => return self.collect_pat_opt_(p.pat()), ast::Pat::ParenPat(p) => return self.collect_pat_opt_(p.pat(), binding_list),
ast::Pat::TuplePat(p) => { ast::Pat::TuplePat(p) => {
let (args, ellipsis) = self.collect_tuple_pat(p.fields()); let (args, ellipsis) = self.collect_tuple_pat(p.fields(), binding_list);
Pat::Tuple { args, ellipsis } Pat::Tuple { args, ellipsis }
} }
ast::Pat::WildcardPat(_) => Pat::Wild, ast::Pat::WildcardPat(_) => Pat::Wild,
@ -904,7 +933,7 @@ impl ExprCollector<'_> {
.fields() .fields()
.filter_map(|f| { .filter_map(|f| {
let ast_pat = f.pat()?; let ast_pat = f.pat()?;
let pat = self.collect_pat_(ast_pat); let pat = self.collect_pat_(ast_pat, binding_list);
let name = f.field_name()?.as_name(); let name = f.field_name()?.as_name();
Some(RecordFieldPat { name, pat }) Some(RecordFieldPat { name, pat })
}) })
@ -923,9 +952,15 @@ impl ExprCollector<'_> {
// FIXME properly handle `RestPat` // FIXME properly handle `RestPat`
Pat::Slice { Pat::Slice {
prefix: prefix.into_iter().map(|p| self.collect_pat_(p)).collect(), prefix: prefix
slice: slice.map(|p| self.collect_pat_(p)), .into_iter()
suffix: suffix.into_iter().map(|p| self.collect_pat_(p)).collect(), .map(|p| self.collect_pat_(p, binding_list))
.collect(),
slice: slice.map(|p| self.collect_pat_(p, binding_list)),
suffix: suffix
.into_iter()
.map(|p| self.collect_pat_(p, binding_list))
.collect(),
} }
} }
ast::Pat::LiteralPat(lit) => { ast::Pat::LiteralPat(lit) => {
@ -948,7 +983,7 @@ impl ExprCollector<'_> {
Pat::Missing Pat::Missing
} }
ast::Pat::BoxPat(boxpat) => { ast::Pat::BoxPat(boxpat) => {
let inner = self.collect_pat_opt_(boxpat.pat()); let inner = self.collect_pat_opt_(boxpat.pat(), binding_list);
Pat::Box { inner } Pat::Box { inner }
} }
ast::Pat::ConstBlockPat(const_block_pat) => { ast::Pat::ConstBlockPat(const_block_pat) => {
@ -965,7 +1000,7 @@ impl ExprCollector<'_> {
let src = self.expander.to_source(Either::Left(AstPtr::new(&pat))); let src = self.expander.to_source(Either::Left(AstPtr::new(&pat)));
let pat = let pat =
self.collect_macro_call(call, macro_ptr, true, |this, expanded_pat| { self.collect_macro_call(call, macro_ptr, true, |this, expanded_pat| {
this.collect_pat_opt_(expanded_pat) this.collect_pat_opt_(expanded_pat, binding_list)
}); });
self.source_map.pat_map.insert(src, pat); self.source_map.pat_map.insert(src, pat);
return pat; return pat;
@ -979,21 +1014,25 @@ impl ExprCollector<'_> {
self.alloc_pat(pattern, Either::Left(ptr)) self.alloc_pat(pattern, Either::Left(ptr))
} }
fn collect_pat_opt_(&mut self, pat: Option<ast::Pat>) -> PatId { fn collect_pat_opt_(&mut self, pat: Option<ast::Pat>, binding_list: &mut BindingList) -> PatId {
match pat { match pat {
Some(pat) => self.collect_pat_(pat), Some(pat) => self.collect_pat_(pat, binding_list),
None => self.missing_pat(), None => self.missing_pat(),
} }
} }
fn collect_tuple_pat(&mut self, args: AstChildren<ast::Pat>) -> (Box<[PatId]>, Option<usize>) { fn collect_tuple_pat(
&mut self,
args: AstChildren<ast::Pat>,
binding_list: &mut BindingList,
) -> (Box<[PatId]>, Option<usize>) {
// Find the location of the `..`, if there is one. Note that we do not // Find the location of the `..`, if there is one. Note that we do not
// consider the possibility of there being multiple `..` here. // consider the possibility of there being multiple `..` here.
let ellipsis = args.clone().position(|p| matches!(p, ast::Pat::RestPat(_))); let ellipsis = args.clone().position(|p| matches!(p, ast::Pat::RestPat(_)));
// We want to skip the `..` pattern here, since we account for it above. // We want to skip the `..` pattern here, since we account for it above.
let args = args let args = args
.filter(|p| !matches!(p, ast::Pat::RestPat(_))) .filter(|p| !matches!(p, ast::Pat::RestPat(_)))
.map(|p| self.collect_pat_(p)) .map(|p| self.collect_pat_(p, binding_list))
.collect(); .collect();
(args, ellipsis) (args, ellipsis)
@ -1022,6 +1061,10 @@ impl ExprCollector<'_> {
None => Some(()), None => Some(()),
} }
} }
fn add_definition_to_binding(&mut self, binding_id: BindingId, pat_id: PatId) {
self.body.bindings[binding_id].definitions.push(pat_id);
}
} }
impl From<ast::LiteralKind> for Literal { impl From<ast::LiteralKind> for Literal {

View file

@ -5,7 +5,7 @@ use std::fmt::{self, Write};
use syntax::ast::HasName; use syntax::ast::HasName;
use crate::{ use crate::{
expr::{Array, BindingAnnotation, ClosureKind, Literal, Movability, Statement}, expr::{Array, BindingAnnotation, BindingId, ClosureKind, Literal, Movability, Statement},
pretty::{print_generic_args, print_path, print_type_ref}, pretty::{print_generic_args, print_path, print_type_ref},
type_ref::TypeRef, type_ref::TypeRef,
}; };
@ -524,14 +524,8 @@ impl<'a> Printer<'a> {
} }
Pat::Path(path) => self.print_path(path), Pat::Path(path) => self.print_path(path),
Pat::Lit(expr) => self.print_expr(*expr), Pat::Lit(expr) => self.print_expr(*expr),
Pat::Bind { mode, name, subpat } => { Pat::Bind { id, subpat } => {
let mode = match mode { self.print_binding(*id);
BindingAnnotation::Unannotated => "",
BindingAnnotation::Mutable => "mut ",
BindingAnnotation::Ref => "ref ",
BindingAnnotation::RefMut => "ref mut ",
};
w!(self, "{}{}", mode, name);
if let Some(pat) = subpat { if let Some(pat) = subpat {
self.whitespace(); self.whitespace();
self.print_pat(*pat); self.print_pat(*pat);
@ -635,4 +629,15 @@ impl<'a> Printer<'a> {
fn print_path(&mut self, path: &Path) { fn print_path(&mut self, path: &Path) {
print_path(path, self).unwrap(); print_path(path, self).unwrap();
} }
fn print_binding(&mut self, id: BindingId) {
let Binding { name, mode, .. } = &self.body.bindings[id];
let mode = match mode {
BindingAnnotation::Unannotated => "",
BindingAnnotation::Mutable => "mut ",
BindingAnnotation::Ref => "ref ",
BindingAnnotation::RefMut => "ref mut ",
};
w!(self, "{}{}", mode, name);
}
} }

View file

@ -8,7 +8,7 @@ use rustc_hash::FxHashMap;
use crate::{ use crate::{
body::Body, body::Body,
db::DefDatabase, db::DefDatabase,
expr::{Expr, ExprId, LabelId, Pat, PatId, Statement}, expr::{Binding, BindingId, Expr, ExprId, LabelId, Pat, PatId, Statement},
BlockId, DefWithBodyId, BlockId, DefWithBodyId,
}; };
@ -23,7 +23,7 @@ pub struct ExprScopes {
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
pub struct ScopeEntry { pub struct ScopeEntry {
name: Name, name: Name,
pat: PatId, binding: BindingId,
} }
impl ScopeEntry { impl ScopeEntry {
@ -31,8 +31,8 @@ impl ScopeEntry {
&self.name &self.name
} }
pub fn pat(&self) -> PatId { pub fn binding(&self) -> BindingId {
self.pat self.binding
} }
} }
@ -126,18 +126,23 @@ impl ExprScopes {
}) })
} }
fn add_bindings(&mut self, body: &Body, scope: ScopeId, pat: PatId) { fn add_bindings(&mut self, body: &Body, scope: ScopeId, binding: BindingId) {
let Binding { name, .. } = &body.bindings[binding];
let entry = ScopeEntry { name: name.clone(), binding };
self.scopes[scope].entries.push(entry);
}
fn add_pat_bindings(&mut self, body: &Body, scope: ScopeId, pat: PatId) {
let pattern = &body[pat]; let pattern = &body[pat];
if let Pat::Bind { name, .. } = pattern { if let Pat::Bind { id, .. } = pattern {
let entry = ScopeEntry { name: name.clone(), pat }; self.add_bindings(body, scope, *id);
self.scopes[scope].entries.push(entry);
} }
pattern.walk_child_pats(|pat| self.add_bindings(body, scope, pat)); pattern.walk_child_pats(|pat| self.add_pat_bindings(body, scope, pat));
} }
fn add_params_bindings(&mut self, body: &Body, scope: ScopeId, params: &[PatId]) { fn add_params_bindings(&mut self, body: &Body, scope: ScopeId, params: &[PatId]) {
params.iter().for_each(|pat| self.add_bindings(body, scope, *pat)); params.iter().for_each(|pat| self.add_pat_bindings(body, scope, *pat));
} }
fn set_scope(&mut self, node: ExprId, scope: ScopeId) { fn set_scope(&mut self, node: ExprId, scope: ScopeId) {
@ -170,7 +175,7 @@ fn compute_block_scopes(
} }
*scope = scopes.new_scope(*scope); *scope = scopes.new_scope(*scope);
scopes.add_bindings(body, *scope, *pat); scopes.add_pat_bindings(body, *scope, *pat);
} }
Statement::Expr { expr, .. } => { Statement::Expr { expr, .. } => {
compute_expr_scopes(*expr, body, scopes, scope); compute_expr_scopes(*expr, body, scopes, scope);
@ -208,7 +213,7 @@ fn compute_expr_scopes(expr: ExprId, body: &Body, scopes: &mut ExprScopes, scope
Expr::For { iterable, pat, body: body_expr, label } => { Expr::For { iterable, pat, body: body_expr, label } => {
compute_expr_scopes(*iterable, body, scopes, scope); compute_expr_scopes(*iterable, body, scopes, scope);
let mut scope = scopes.new_labeled_scope(*scope, make_label(label)); let mut scope = scopes.new_labeled_scope(*scope, make_label(label));
scopes.add_bindings(body, scope, *pat); scopes.add_pat_bindings(body, scope, *pat);
compute_expr_scopes(*body_expr, body, scopes, &mut scope); compute_expr_scopes(*body_expr, body, scopes, &mut scope);
} }
Expr::While { condition, body: body_expr, label } => { Expr::While { condition, body: body_expr, label } => {
@ -229,7 +234,7 @@ fn compute_expr_scopes(expr: ExprId, body: &Body, scopes: &mut ExprScopes, scope
compute_expr_scopes(*expr, body, scopes, scope); compute_expr_scopes(*expr, body, scopes, scope);
for arm in arms.iter() { for arm in arms.iter() {
let mut scope = scopes.new_scope(*scope); let mut scope = scopes.new_scope(*scope);
scopes.add_bindings(body, scope, arm.pat); scopes.add_pat_bindings(body, scope, arm.pat);
if let Some(guard) = arm.guard { if let Some(guard) = arm.guard {
scope = scopes.new_scope(scope); scope = scopes.new_scope(scope);
compute_expr_scopes(guard, body, scopes, &mut scope); compute_expr_scopes(guard, body, scopes, &mut scope);
@ -248,7 +253,7 @@ fn compute_expr_scopes(expr: ExprId, body: &Body, scopes: &mut ExprScopes, scope
&Expr::Let { pat, expr } => { &Expr::Let { pat, expr } => {
compute_expr_scopes(expr, body, scopes, scope); compute_expr_scopes(expr, body, scopes, scope);
*scope = scopes.new_scope(*scope); *scope = scopes.new_scope(*scope);
scopes.add_bindings(body, *scope, pat); scopes.add_pat_bindings(body, *scope, pat);
} }
e => e.walk_child_exprs(|e| compute_expr_scopes(e, body, scopes, scope)), e => e.walk_child_exprs(|e| compute_expr_scopes(e, body, scopes, scope)),
}; };
@ -450,7 +455,7 @@ fn foo() {
let function = find_function(&db, file_id); let function = find_function(&db, file_id);
let scopes = db.expr_scopes(function.into()); let scopes = db.expr_scopes(function.into());
let (_body, source_map) = db.body_with_source_map(function.into()); let (body, source_map) = db.body_with_source_map(function.into());
let expr_scope = { let expr_scope = {
let expr_ast = name_ref.syntax().ancestors().find_map(ast::Expr::cast).unwrap(); let expr_ast = name_ref.syntax().ancestors().find_map(ast::Expr::cast).unwrap();
@ -460,7 +465,9 @@ fn foo() {
}; };
let resolved = scopes.resolve_name_in_scope(expr_scope, &name_ref.as_name()).unwrap(); let resolved = scopes.resolve_name_in_scope(expr_scope, &name_ref.as_name()).unwrap();
let pat_src = source_map.pat_syntax(resolved.pat()).unwrap(); let pat_src = source_map
.pat_syntax(*body.bindings[resolved.binding()].definitions.first().unwrap())
.unwrap();
let local_name = pat_src.value.either( let local_name = pat_src.value.either(
|it| it.syntax_node_ptr().to_node(file.syntax()), |it| it.syntax_node_ptr().to_node(file.syntax()),

View file

@ -17,6 +17,7 @@ use std::fmt;
use hir_expand::name::Name; use hir_expand::name::Name;
use intern::Interned; use intern::Interned;
use la_arena::{Idx, RawIdx}; use la_arena::{Idx, RawIdx};
use smallvec::SmallVec;
use crate::{ use crate::{
builtin_type::{BuiltinFloat, BuiltinInt, BuiltinUint}, builtin_type::{BuiltinFloat, BuiltinInt, BuiltinUint},
@ -29,6 +30,8 @@ pub use syntax::ast::{ArithOp, BinaryOp, CmpOp, LogicOp, Ordering, RangeOp, Unar
pub type ExprId = Idx<Expr>; pub type ExprId = Idx<Expr>;
pub type BindingId = Idx<Binding>;
/// FIXME: this is a hacky function which should be removed /// FIXME: this is a hacky function which should be removed
pub(crate) fn dummy_expr_id() -> ExprId { pub(crate) fn dummy_expr_id() -> ExprId {
ExprId::from_raw(RawIdx::from(u32::MAX)) ExprId::from_raw(RawIdx::from(u32::MAX))
@ -433,6 +436,13 @@ impl BindingAnnotation {
} }
} }
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Binding {
pub name: Name,
pub mode: BindingAnnotation,
pub definitions: SmallVec<[PatId; 1]>,
}
#[derive(Debug, Clone, Eq, PartialEq)] #[derive(Debug, Clone, Eq, PartialEq)]
pub struct RecordFieldPat { pub struct RecordFieldPat {
pub name: Name, pub name: Name,
@ -451,7 +461,7 @@ pub enum Pat {
Slice { prefix: Box<[PatId]>, slice: Option<PatId>, suffix: Box<[PatId]> }, Slice { prefix: Box<[PatId]>, slice: Option<PatId>, suffix: Box<[PatId]> },
Path(Box<Path>), Path(Box<Path>),
Lit(ExprId), Lit(ExprId),
Bind { mode: BindingAnnotation, name: Name, subpat: Option<PatId> }, Bind { id: BindingId, subpat: Option<PatId> },
TupleStruct { path: Option<Box<Path>>, args: Box<[PatId]>, ellipsis: Option<usize> }, TupleStruct { path: Option<Box<Path>>, args: Box<[PatId]>, ellipsis: Option<usize> },
Ref { pat: PatId, mutability: Mutability }, Ref { pat: PatId, mutability: Mutability },
Box { inner: PatId }, Box { inner: PatId },

View file

@ -12,7 +12,7 @@ use crate::{
body::scope::{ExprScopes, ScopeId}, body::scope::{ExprScopes, ScopeId},
builtin_type::BuiltinType, builtin_type::BuiltinType,
db::DefDatabase, db::DefDatabase,
expr::{ExprId, LabelId, PatId}, expr::{BindingId, ExprId, LabelId},
generics::{GenericParams, TypeOrConstParamData}, generics::{GenericParams, TypeOrConstParamData},
item_scope::{BuiltinShadowMode, BUILTIN_SCOPE}, item_scope::{BuiltinShadowMode, BUILTIN_SCOPE},
nameres::DefMap, nameres::DefMap,
@ -105,7 +105,7 @@ pub enum ResolveValueResult {
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum ValueNs { pub enum ValueNs {
ImplSelf(ImplId), ImplSelf(ImplId),
LocalBinding(PatId), LocalBinding(BindingId),
FunctionId(FunctionId), FunctionId(FunctionId),
ConstId(ConstId), ConstId(ConstId),
StaticId(StaticId), StaticId(StaticId),
@ -267,7 +267,7 @@ impl Resolver {
if let Some(e) = entry { if let Some(e) = entry {
return Some(ResolveValueResult::ValueNs(ValueNs::LocalBinding( return Some(ResolveValueResult::ValueNs(ValueNs::LocalBinding(
e.pat(), e.binding(),
))); )));
} }
} }
@ -617,7 +617,7 @@ pub enum ScopeDef {
ImplSelfType(ImplId), ImplSelfType(ImplId),
AdtSelfType(AdtId), AdtSelfType(AdtId),
GenericParam(GenericParamId), GenericParam(GenericParamId),
Local(PatId), Local(BindingId),
Label(LabelId), Label(LabelId),
} }
@ -669,7 +669,7 @@ impl Scope {
acc.add(&name, ScopeDef::Label(label)) acc.add(&name, ScopeDef::Label(label))
} }
scope.expr_scopes.entries(scope.scope_id).iter().for_each(|e| { scope.expr_scopes.entries(scope.scope_id).iter().for_each(|e| {
acc.add_local(e.name(), e.pat()); acc.add_local(e.name(), e.binding());
}); });
} }
} }
@ -859,7 +859,7 @@ impl ScopeNames {
self.add(name, ScopeDef::Unknown) self.add(name, ScopeDef::Unknown)
} }
} }
fn add_local(&mut self, name: &Name, pat: PatId) { fn add_local(&mut self, name: &Name, binding: BindingId) {
let set = self.map.entry(name.clone()).or_default(); let set = self.map.entry(name.clone()).or_default();
// XXX: hack, account for local (and only local) shadowing. // XXX: hack, account for local (and only local) shadowing.
// //
@ -870,7 +870,7 @@ impl ScopeNames {
cov_mark::hit!(shadowing_shows_single_completion); cov_mark::hit!(shadowing_shows_single_completion);
return; return;
} }
set.push(ScopeDef::Local(pat)) set.push(ScopeDef::Local(binding))
} }
} }

View file

@ -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]
@ -132,6 +148,60 @@ fn reference_autoderef() {
); );
} }
#[test]
fn overloaded_deref() {
// FIXME: We should support this.
check_fail(
r#"
//- minicore: deref_mut
struct Foo;
impl core::ops::Deref for Foo {
type Target = i32;
fn deref(&self) -> &i32 {
&5
}
}
const GOAL: i32 = {
let x = Foo;
let y = &*x;
*y + *x
};
"#,
ConstEvalError::MirLowerError(MirLowerError::NotSupported(
"explicit overloaded deref".into(),
)),
);
}
#[test]
fn overloaded_deref_autoref() {
check_number(
r#"
//- minicore: deref_mut
struct Foo;
struct Bar;
impl core::ops::Deref for Foo {
type Target = Bar;
fn deref(&self) -> &Bar {
&Bar
}
}
impl Bar {
fn method(&self) -> i32 {
5
}
}
const GOAL: i32 = Foo.method();
"#,
5,
);
}
#[test] #[test]
fn function_call() { fn function_call() {
check_number( check_number(
@ -358,7 +428,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 +436,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);
@ -399,6 +469,43 @@ fn loops() {
); );
} }
#[test]
fn for_loops() {
check_number(
r#"
//- minicore: iterator
struct Range {
start: u8,
end: u8,
}
impl Iterator for Range {
type Item = u8;
fn next(&mut self) -> Option<u8> {
if self.start >= self.end {
None
} else {
let r = self.start;
self.start = self.start + 1;
Some(r)
}
}
}
const GOAL: u8 = {
let mut sum = 0;
let ar = Range { start: 1, end: 11 };
for i in ar {
sum = sum + i;
}
sum
};
"#,
55,
);
}
#[test] #[test]
fn recursion() { fn recursion() {
check_number( check_number(
@ -464,6 +571,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);
@ -492,6 +609,33 @@ fn tuples() {
); );
} }
#[test]
fn path_pattern_matching() {
check_number(
r#"
enum Season {
Spring,
Summer,
Fall,
Winter,
}
use Season::*;
const fn f(x: Season) -> i32 {
match x {
Spring => 1,
Summer => 2,
Fall => 3,
Winter => 4,
}
}
const GOAL: i32 = f(Spring) + 10 * f(Summer) + 100 * f(Fall) + 1000 * f(Winter);
"#,
4321,
);
}
#[test] #[test]
fn pattern_matching_ergonomics() { fn pattern_matching_ergonomics() {
check_number( check_number(
@ -539,12 +683,55 @@ 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,
); );
} }
#[test]
fn function_param_patterns() {
check_number(
r#"
const fn f((a, b): &(u8, u8)) -> u8 {
*a + *b
}
const GOAL: u8 = f(&(2, 3));
"#,
5,
);
check_number(
r#"
const fn f(c @ (a, b): &(u8, u8)) -> u8 {
*a + *b + c.0 + (*c).1
}
const GOAL: u8 = f(&(2, 3));
"#,
10,
);
check_number(
r#"
const fn f(ref a: u8) -> u8 {
*a
}
const GOAL: u8 = f(2);
"#,
2,
);
check_number(
r#"
struct Foo(u8);
impl Foo {
const fn f(&self, (a, b): &(u8, u8)) -> u8 {
self.0 + *a + *b
}
}
const GOAL: u8 = Foo(4).f(&(2, 3));
"#,
9,
);
}
#[test] #[test]
fn options() { fn options() {
check_number( check_number(
@ -572,7 +759,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,
); );
@ -598,6 +785,44 @@ fn options() {
); );
} }
#[test]
fn or_pattern() {
check_number(
r#"
const GOAL: u8 = {
let (a | a) = 2;
a
};
"#,
2,
);
check_number(
r#"
//- minicore: option
const fn f(x: Option<i32>) -> i32 {
let (Some(a) | Some(a)) = x else { return 2; };
a
}
const GOAL: i32 = f(Some(10)) + f(None);
"#,
12,
);
check_number(
r#"
//- minicore: option
const fn f(x: Option<i32>, y: Option<i32>) -> i32 {
match (x, y) {
(Some(x), Some(y)) => x * y,
(Some(a), _) | (_, Some(a)) => a,
_ => 10,
}
}
const GOAL: i32 = f(Some(10), Some(20)) + f(Some(30), None) + f(None, Some(40)) + f(None, None);
"#,
280,
);
}
#[test] #[test]
fn array_and_index() { fn array_and_index() {
check_number( check_number(
@ -665,24 +890,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,
); );
@ -813,8 +1038,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(_)))));
}

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

@ -235,8 +235,8 @@ impl<'a> DeclValidator<'a> {
let pats_replacements = body let pats_replacements = body
.pats .pats
.iter() .iter()
.filter_map(|(id, pat)| match pat { .filter_map(|(pat_id, pat)| match pat {
Pat::Bind { name, .. } => Some((id, name)), Pat::Bind { id, .. } => Some((pat_id, &body.bindings[*id].name)),
_ => None, _ => None,
}) })
.filter_map(|(id, bind_name)| { .filter_map(|(id, bind_name)| {

View file

@ -146,8 +146,9 @@ impl<'a> PatCtxt<'a> {
PatKind::Leaf { subpatterns } PatKind::Leaf { subpatterns }
} }
hir_def::expr::Pat::Bind { ref name, subpat, .. } => { hir_def::expr::Pat::Bind { id, subpat, .. } => {
let bm = self.infer.pat_binding_modes[&pat]; let bm = self.infer.pat_binding_modes[&pat];
let name = &self.body.bindings[id].name;
match (bm, ty.kind(Interner)) { match (bm, ty.kind(Interner)) {
(BindingMode::Ref(_), TyKind::Ref(.., rty)) => ty = rty, (BindingMode::Ref(_), TyKind::Ref(.., rty)) => ty = rty,
(BindingMode::Ref(_), _) => { (BindingMode::Ref(_), _) => {

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

@ -22,7 +22,7 @@ use hir_def::{
body::Body, body::Body,
builtin_type::{BuiltinInt, BuiltinType, BuiltinUint}, builtin_type::{BuiltinInt, BuiltinType, BuiltinUint},
data::{ConstData, StaticData}, data::{ConstData, StaticData},
expr::{BindingAnnotation, ExprId, ExprOrPatId, PatId}, expr::{BindingAnnotation, BindingId, ExprId, ExprOrPatId, PatId},
lang_item::{LangItem, LangItemTarget}, lang_item::{LangItem, LangItemTarget},
layout::Integer, layout::Integer,
path::Path, path::Path,
@ -291,8 +291,10 @@ pub enum Adjust {
/// call, with the signature `&'a T -> &'a U` or `&'a mut T -> &'a mut U`. /// call, with the signature `&'a T -> &'a U` or `&'a mut T -> &'a mut U`.
/// The target type is `U` in both cases, with the region and mutability /// The target type is `U` in both cases, with the region and mutability
/// being those shared by both the receiver and the returned reference. /// being those shared by both the receiver and the returned reference.
///
/// Mutability is `None` when we are not sure.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct OverloadedDeref(pub Mutability); pub struct OverloadedDeref(pub Option<Mutability>);
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum AutoBorrow { pub enum AutoBorrow {
@ -352,7 +354,10 @@ pub struct InferenceResult {
/// **Note**: When a pattern type is resolved it may still contain /// **Note**: When a pattern type is resolved it may still contain
/// unresolved or missing subpatterns or subpatterns of mismatched types. /// unresolved or missing subpatterns or subpatterns of mismatched types.
pub type_of_pat: ArenaMap<PatId, Ty>, pub type_of_pat: ArenaMap<PatId, Ty>,
pub type_of_binding: ArenaMap<BindingId, Ty>,
pub type_of_rpit: ArenaMap<RpitId, Ty>, pub type_of_rpit: ArenaMap<RpitId, Ty>,
/// Type of the result of `.into_iter()` on the for. `ExprId` is the one of the whole for loop.
pub type_of_for_iterator: FxHashMap<ExprId, Ty>,
type_mismatches: FxHashMap<ExprOrPatId, TypeMismatch>, type_mismatches: FxHashMap<ExprOrPatId, TypeMismatch>,
/// Interned common types to return references to. /// Interned common types to return references to.
standard_types: InternedStandardTypes, standard_types: InternedStandardTypes,
@ -414,6 +419,14 @@ impl Index<PatId> for InferenceResult {
} }
} }
impl Index<BindingId> for InferenceResult {
type Output = Ty;
fn index(&self, b: BindingId) -> &Ty {
self.type_of_binding.get(b).unwrap_or(&self.standard_types.unknown)
}
}
/// The inference context contains all information needed during type inference. /// The inference context contains all information needed during type inference.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub(crate) struct InferenceContext<'a> { pub(crate) struct InferenceContext<'a> {
@ -534,7 +547,13 @@ impl<'a> InferenceContext<'a> {
for ty in result.type_of_pat.values_mut() { for ty in result.type_of_pat.values_mut() {
*ty = table.resolve_completely(ty.clone()); *ty = table.resolve_completely(ty.clone());
} }
for ty in result.type_of_rpit.iter_mut().map(|x| x.1) { for ty in result.type_of_binding.values_mut() {
*ty = table.resolve_completely(ty.clone());
}
for ty in result.type_of_rpit.values_mut() {
*ty = table.resolve_completely(ty.clone());
}
for ty in result.type_of_for_iterator.values_mut() {
*ty = table.resolve_completely(ty.clone()); *ty = table.resolve_completely(ty.clone());
} }
for mismatch in result.type_mismatches.values_mut() { for mismatch in result.type_mismatches.values_mut() {
@ -704,6 +723,10 @@ impl<'a> InferenceContext<'a> {
self.result.type_of_pat.insert(pat, ty); self.result.type_of_pat.insert(pat, ty);
} }
fn write_binding_ty(&mut self, id: BindingId, ty: Ty) {
self.result.type_of_binding.insert(id, ty);
}
fn push_diagnostic(&mut self, diagnostic: InferenceDiagnostic) { fn push_diagnostic(&mut self, diagnostic: InferenceDiagnostic) {
self.result.diagnostics.push(diagnostic); self.result.diagnostics.push(diagnostic);
} }

View file

@ -693,7 +693,7 @@ pub(super) fn auto_deref_adjust_steps(autoderef: &Autoderef<'_, '_>) -> Vec<Adju
.iter() .iter()
.map(|(kind, _source)| match kind { .map(|(kind, _source)| match kind {
// We do not know what kind of deref we require at this point yet // We do not know what kind of deref we require at this point yet
AutoderefKind::Overloaded => Some(OverloadedDeref(Mutability::Not)), AutoderefKind::Overloaded => Some(OverloadedDeref(None)),
AutoderefKind::Builtin => None, AutoderefKind::Builtin => None,
}) })
.zip(targets) .zip(targets)

View file

@ -242,8 +242,10 @@ impl<'a> InferenceContext<'a> {
let iterable_ty = self.infer_expr(iterable, &Expectation::none()); let iterable_ty = self.infer_expr(iterable, &Expectation::none());
let into_iter_ty = let into_iter_ty =
self.resolve_associated_type(iterable_ty, self.resolve_into_iter_item()); self.resolve_associated_type(iterable_ty, self.resolve_into_iter_item());
let pat_ty = let pat_ty = self
self.resolve_associated_type(into_iter_ty, self.resolve_iterator_item()); .resolve_associated_type(into_iter_ty.clone(), self.resolve_iterator_item());
self.result.type_of_for_iterator.insert(tgt_expr, into_iter_ty);
self.infer_top_pat(pat, &pat_ty); self.infer_top_pat(pat, &pat_ty);
self.with_breakable_ctx(BreakableKind::Loop, None, label, |this| { self.with_breakable_ctx(BreakableKind::Loop, None, label, |this| {

View file

@ -5,7 +5,10 @@ use std::iter::repeat_with;
use chalk_ir::Mutability; use chalk_ir::Mutability;
use hir_def::{ use hir_def::{
body::Body, body::Body,
expr::{BindingAnnotation, Expr, ExprId, ExprOrPatId, Literal, Pat, PatId, RecordFieldPat}, expr::{
Binding, BindingAnnotation, BindingId, Expr, ExprId, ExprOrPatId, Literal, Pat, PatId,
RecordFieldPat,
},
path::Path, path::Path,
}; };
use hir_expand::name::Name; use hir_expand::name::Name;
@ -248,8 +251,8 @@ impl<'a> InferenceContext<'a> {
// FIXME update resolver for the surrounding expression // FIXME update resolver for the surrounding expression
self.infer_path(path, pat.into()).unwrap_or_else(|| self.err_ty()) self.infer_path(path, pat.into()).unwrap_or_else(|| self.err_ty())
} }
Pat::Bind { mode, name: _, subpat } => { Pat::Bind { id, subpat } => {
return self.infer_bind_pat(pat, *mode, default_bm, *subpat, &expected); return self.infer_bind_pat(pat, *id, default_bm, *subpat, &expected);
} }
Pat::Slice { prefix, slice, suffix } => { Pat::Slice { prefix, slice, suffix } => {
self.infer_slice_pat(&expected, prefix, slice, suffix, default_bm) self.infer_slice_pat(&expected, prefix, slice, suffix, default_bm)
@ -320,11 +323,12 @@ impl<'a> InferenceContext<'a> {
fn infer_bind_pat( fn infer_bind_pat(
&mut self, &mut self,
pat: PatId, pat: PatId,
mode: BindingAnnotation, binding: BindingId,
default_bm: BindingMode, default_bm: BindingMode,
subpat: Option<PatId>, subpat: Option<PatId>,
expected: &Ty, expected: &Ty,
) -> Ty { ) -> Ty {
let Binding { mode, .. } = self.body.bindings[binding];
let mode = if mode == BindingAnnotation::Unannotated { let mode = if mode == BindingAnnotation::Unannotated {
default_bm default_bm
} else { } else {
@ -344,7 +348,8 @@ impl<'a> InferenceContext<'a> {
} }
BindingMode::Move => inner_ty.clone(), BindingMode::Move => inner_ty.clone(),
}; };
self.write_pat_ty(pat, bound_ty); self.write_pat_ty(pat, bound_ty.clone());
self.write_binding_ty(binding, bound_ty);
return inner_ty; return inner_ty;
} }
@ -420,11 +425,14 @@ fn is_non_ref_pat(body: &hir_def::body::Body, pat: PatId) -> bool {
Pat::Lit(expr) => { Pat::Lit(expr) => {
!matches!(body[*expr], Expr::Literal(Literal::String(..) | Literal::ByteString(..))) !matches!(body[*expr], Expr::Literal(Literal::String(..) | Literal::ByteString(..)))
} }
Pat::Bind { Pat::Bind { id, subpat: Some(subpat), .. }
mode: BindingAnnotation::Mutable | BindingAnnotation::Unannotated, if matches!(
subpat: Some(subpat), body.bindings[*id].mode,
.. BindingAnnotation::Mutable | BindingAnnotation::Unannotated
} => is_non_ref_pat(body, *subpat), ) =>
{
is_non_ref_pat(body, *subpat)
}
Pat::Wild | Pat::Bind { .. } | Pat::Ref { .. } | Pat::Box { .. } | Pat::Missing => false, Pat::Wild | Pat::Bind { .. } | Pat::Ref { .. } | Pat::Box { .. } | Pat::Missing => false,
} }
} }
@ -432,7 +440,7 @@ fn is_non_ref_pat(body: &hir_def::body::Body, pat: PatId) -> bool {
pub(super) fn contains_explicit_ref_binding(body: &Body, pat_id: PatId) -> bool { pub(super) fn contains_explicit_ref_binding(body: &Body, pat_id: PatId) -> bool {
let mut res = false; let mut res = false;
walk_pats(body, pat_id, &mut |pat| { walk_pats(body, pat_id, &mut |pat| {
res |= matches!(pat, Pat::Bind { mode: BindingAnnotation::Ref, .. }) res |= matches!(pat, Pat::Bind { id, .. } if body.bindings[*id].mode == BindingAnnotation::Ref);
}); });
res res
} }

View file

@ -50,7 +50,7 @@ impl<'a> InferenceContext<'a> {
}; };
let typable: ValueTyDefId = match value { let typable: ValueTyDefId = match value {
ValueNs::LocalBinding(pat) => match self.result.type_of_pat.get(pat) { ValueNs::LocalBinding(pat) => match self.result.type_of_binding.get(pat) {
Some(ty) => return Some(ty.clone()), Some(ty) => return Some(ty.clone()),
None => { None => {
never!("uninferred pattern?"); never!("uninferred pattern?");

View file

@ -65,17 +65,9 @@ fn eval_expr(ra_fixture: &str, minicore: &str) -> Result<Layout, LayoutError> {
}) })
.unwrap(); .unwrap();
let hir_body = db.body(adt_id.into()); let hir_body = db.body(adt_id.into());
let pat = hir_body let b = hir_body.bindings.iter().find(|x| x.1.name.to_smol_str() == "goal").unwrap().0;
.pats
.iter()
.find(|x| match x.1 {
hir_def::expr::Pat::Bind { name, .. } => name.to_smol_str() == "goal",
_ => false,
})
.unwrap()
.0;
let infer = db.infer(adt_id.into()); let infer = db.infer(adt_id.into());
let goal_ty = infer.type_of_pat[pat].clone(); let goal_ty = infer.type_of_binding[b].clone();
layout_of_ty(&db, &goal_ty, module_id.krate()) layout_of_ty(&db, &goal_ty, module_id.krate())
} }

View file

@ -579,8 +579,8 @@ impl ReceiverAdjustments {
ty = new_ty.clone(); ty = new_ty.clone();
adjust.push(Adjustment { adjust.push(Adjustment {
kind: Adjust::Deref(match kind { kind: Adjust::Deref(match kind {
// FIXME should we know the mutability here? // FIXME should we know the mutability here, when autoref is `None`?
AutoderefKind::Overloaded => Some(OverloadedDeref(Mutability::Not)), AutoderefKind::Overloaded => Some(OverloadedDeref(self.autoref)),
AutoderefKind::Builtin => None, AutoderefKind::Builtin => None,
}), }),
target: new_ty, target: new_ty,

View file

@ -1,23 +1,27 @@
//! 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,
}; };
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;
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};
use stdx::impl_from;
use super::consteval::{intern_const_scalar, try_const_usize}; use super::consteval::{intern_const_scalar, try_const_usize};
@ -30,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
@ -85,6 +83,10 @@ impl Operand {
fn from_bytes(data: Vec<u8>, ty: Ty) -> Self { fn from_bytes(data: Vec<u8>, ty: Ty) -> Self {
Operand::from_concrete_const(data, MemoryMap::default(), ty) Operand::from_concrete_const(data, MemoryMap::default(), ty)
} }
fn const_zst(ty: Ty) -> Operand {
Self::from_bytes(vec![], ty)
}
} }
#[derive(Debug, PartialEq, Eq, Clone)] #[derive(Debug, PartialEq, Eq, Clone)]
@ -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.
@ -557,6 +564,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 {
@ -758,7 +789,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 +804,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,10 +845,19 @@ 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>,
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
} }
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum MirSpan {
ExprId(ExprId),
PatId(PatId),
Unknown,
}
impl_from!(ExprId, PatId for MirSpan);

View file

@ -0,0 +1,223 @@
//! 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
// if needed for implementing a proper borrow checker.
use std::sync::Arc;
use hir_def::DefWithBodyId;
use la_arena::ArenaMap;
use stdx::never;
use crate::db::HirDatabase;
use super::{
BasicBlockId, BorrowKind, LocalId, MirBody, MirLowerError, MirSpan, Place, ProjectionElem,
Rvalue, StatementKind, Terminator,
};
#[derive(Debug, Clone, PartialEq, Eq)]
/// Stores spans which implies that the local should be mutable.
pub enum MutabilityReason {
Mut { spans: Vec<MirSpan> },
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 _p = profile::span("borrowck_query");
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 {
!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 &l in &body.param_locals {
result[body.start_block].insert(l, true);
dfs(body, body.start_block, 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
}
fn mutability_of_locals(body: &MirBody) -> ArenaMap<LocalId, MutabilityReason> {
let mut result: ArenaMap<LocalId, MutabilityReason> =
body.locals.iter().map(|x| (x.0, MutabilityReason::Not)).collect();
let mut push_mut_span = |local, span| match &mut result[local] {
MutabilityReason::Mut { spans } => spans.push(span),
x @ MutabilityReason::Not => *x = MutabilityReason::Mut { spans: vec![span] },
};
let ever_init_maps = ever_initialized_map(body);
for (block_id, mut ever_init_map) in ever_init_maps.into_iter() {
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() {
push_mut_span(place.local, statement.span);
} else {
ever_init_map.insert(place.local, true);
}
}
ProjectionCase::DirectPart => {
// Partial initialization is not supported, so it is definitely `mut`
push_mut_span(place.local, statement.span);
}
ProjectionCase::Indirect => (),
}
if let Rvalue::Ref(BorrowKind::Mut { .. }, p) = value {
if is_place_direct(p) {
push_mut_span(p.local, 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() {
push_mut_span(destination.local, MirSpan::Unknown);
} else {
ever_init_map.insert(destination.local, true);
}
}
}
}
}
result
}

View file

@ -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> {
@ -263,12 +263,14 @@ impl Evaluator<'_> {
for proj in &p.projection { for proj in &p.projection {
match proj { match proj {
ProjectionElem::Deref => { ProjectionElem::Deref => {
match &ty.data(Interner).kind { ty = match &ty.data(Interner).kind {
TyKind::Ref(_, _, inner) => { TyKind::Raw(_, inner) | TyKind::Ref(_, _, inner) => inner.clone(),
ty = inner.clone(); _ => {
return Err(MirEvalError::TypeError(
"Overloaded deref in MIR is disallowed",
))
} }
_ => not_supported!("dereferencing smart pointers"), };
}
let x = from_bytes!(usize, self.read_memory(addr, self.ptr_size())?); let x = from_bytes!(usize, self.read_memory(addr, self.ptr_size())?);
addr = Address::from_usize(x); addr = Address::from_usize(x);
} }
@ -395,7 +397,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 +428,16 @@ impl Evaluator<'_> {
return Err(MirEvalError::ExecutionLimitExceeded); return Err(MirEvalError::ExecutionLimitExceeded);
} }
for statement in &current_block.statements { for statement in &current_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 {
@ -1121,7 +1124,12 @@ impl Evaluator<'_> {
} }
fn detect_lang_function(&self, def: FunctionId) -> Option<LangItem> { fn detect_lang_function(&self, def: FunctionId) -> Option<LangItem> {
lang_attr(self.db.upcast(), def) let candidate = lang_attr(self.db.upcast(), def)?;
// filter normal lang functions out
if [LangItem::IntoIterIntoIter, LangItem::IteratorNext].contains(&candidate) {
return None;
}
Some(candidate)
} }
fn create_memory_map(&self, bytes: &[u8], ty: &Ty, locals: &Locals<'_>) -> Result<MemoryMap> { fn create_memory_map(&self, bytes: &[u8], ty: &Ty, locals: &Locals<'_>) -> Result<MemoryMap> {

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,237 @@
//! MIR lowering for places
use super::*;
use hir_expand::name;
macro_rules! not_supported {
($x: expr) => {
return Err(MirLowerError::NotSupported(format!($x)))
};
}
impl MirLowerCtx<'_> {
fn lower_expr_to_some_place_without_adjust(
&mut self,
expr_id: ExprId,
prev_block: BasicBlockId,
) -> Result<Option<(Place, BasicBlockId)>> {
let ty = self.expr_ty(expr_id);
let place = self.temp(ty)?;
let Some(current) = self.lower_expr_to_place_without_adjust(expr_id, place.into(), prev_block)? else {
return Ok(None);
};
Ok(Some((place.into(), current)))
}
fn lower_expr_to_some_place_with_adjust(
&mut self,
expr_id: ExprId,
prev_block: BasicBlockId,
adjustments: &[Adjustment],
) -> Result<Option<(Place, BasicBlockId)>> {
let ty =
adjustments.last().map(|x| x.target.clone()).unwrap_or_else(|| self.expr_ty(expr_id));
let place = self.temp(ty)?;
let Some(current) = self.lower_expr_to_place_with_adjust(expr_id, place.into(), prev_block, adjustments)? else {
return Ok(None);
};
Ok(Some((place.into(), current)))
}
pub(super) fn lower_expr_as_place_with_adjust(
&mut self,
current: BasicBlockId,
expr_id: ExprId,
upgrade_rvalue: bool,
adjustments: &[Adjustment],
) -> Result<Option<(Place, BasicBlockId)>> {
let try_rvalue = |this: &mut MirLowerCtx<'_>| {
if !upgrade_rvalue {
return Err(MirLowerError::MutatingRvalue);
}
this.lower_expr_to_some_place_with_adjust(expr_id, current, adjustments)
};
if let Some((last, rest)) = adjustments.split_last() {
match last.kind {
Adjust::Deref(None) => {
let Some(mut x) = self.lower_expr_as_place_with_adjust(
current,
expr_id,
upgrade_rvalue,
rest,
)? else {
return Ok(None);
};
x.0.projection.push(ProjectionElem::Deref);
Ok(Some(x))
}
Adjust::Deref(Some(od)) => {
let Some((r, current)) = self.lower_expr_as_place_with_adjust(
current,
expr_id,
upgrade_rvalue,
rest,
)? else {
return Ok(None);
};
self.lower_overloaded_deref(
current,
r,
rest.last()
.map(|x| x.target.clone())
.unwrap_or_else(|| self.expr_ty(expr_id)),
last.target.clone(),
expr_id.into(),
match od.0 {
Some(Mutability::Mut) => true,
Some(Mutability::Not) => false,
None => {
not_supported!("implicit overloaded deref with unknown mutability")
}
},
)
}
Adjust::NeverToAny | Adjust::Borrow(_) | Adjust::Pointer(_) => try_rvalue(self),
}
} else {
self.lower_expr_as_place_without_adjust(current, expr_id, upgrade_rvalue)
}
}
pub(super) fn lower_expr_as_place(
&mut self,
current: BasicBlockId,
expr_id: ExprId,
upgrade_rvalue: bool,
) -> Result<Option<(Place, BasicBlockId)>> {
match self.infer.expr_adjustments.get(&expr_id) {
Some(a) => self.lower_expr_as_place_with_adjust(current, expr_id, upgrade_rvalue, a),
None => self.lower_expr_as_place_without_adjust(current, expr_id, upgrade_rvalue),
}
}
fn lower_expr_as_place_without_adjust(
&mut self,
current: BasicBlockId,
expr_id: ExprId,
upgrade_rvalue: bool,
) -> Result<Option<(Place, BasicBlockId)>> {
let try_rvalue = |this: &mut MirLowerCtx<'_>| {
if !upgrade_rvalue {
return Err(MirLowerError::MutatingRvalue);
}
this.lower_expr_to_some_place_without_adjust(expr_id, current)
};
match &self.body.exprs[expr_id] {
Expr::Path(p) => {
let resolver = resolver_for_expr(self.db.upcast(), self.owner, expr_id);
let Some(pr) = resolver.resolve_path_in_value_ns(self.db.upcast(), p.mod_path()) else {
return Err(MirLowerError::unresolved_path(self.db, p));
};
let pr = match pr {
ResolveValueResult::ValueNs(v) => v,
ResolveValueResult::Partial(..) => return try_rvalue(self),
};
match pr {
ValueNs::LocalBinding(pat_id) => {
Ok(Some((self.result.binding_locals[pat_id].into(), current)))
}
_ => try_rvalue(self),
}
}
Expr::UnaryOp { expr, op } => match op {
hir_def::expr::UnaryOp::Deref => {
if !matches!(
self.expr_ty(*expr).kind(Interner),
TyKind::Ref(..) | TyKind::Raw(..)
) {
let Some(_) = self.lower_expr_as_place(current, *expr, true)? else {
return Ok(None);
};
not_supported!("explicit overloaded deref");
}
let Some((mut r, current)) = self.lower_expr_as_place(current, *expr, true)? else {
return Ok(None);
};
r.projection.push(ProjectionElem::Deref);
Ok(Some((r, current)))
}
_ => try_rvalue(self),
},
Expr::Field { expr, .. } => {
let Some((mut r, current)) = self.lower_expr_as_place(current, *expr, true)? else {
return Ok(None);
};
self.push_field_projection(&mut r, expr_id)?;
Ok(Some((r, current)))
}
Expr::Index { base, index } => {
let base_ty = self.expr_ty_after_adjustments(*base);
let index_ty = self.expr_ty_after_adjustments(*index);
if index_ty != TyBuilder::usize()
|| !matches!(base_ty.kind(Interner), TyKind::Array(..) | TyKind::Slice(..))
{
not_supported!("overloaded index");
}
let Some((mut p_base, current)) =
self.lower_expr_as_place(current, *base, true)? else {
return Ok(None);
};
let l_index = self.temp(self.expr_ty_after_adjustments(*index))?;
let Some(current) = self.lower_expr_to_place(*index, l_index.into(), current)? else {
return Ok(None);
};
p_base.projection.push(ProjectionElem::Index(l_index));
Ok(Some((p_base, current)))
}
_ => try_rvalue(self),
}
}
fn lower_overloaded_deref(
&mut self,
current: BasicBlockId,
place: Place,
source_ty: Ty,
target_ty: Ty,
span: MirSpan,
mutability: bool,
) -> Result<Option<(Place, BasicBlockId)>> {
let (chalk_mut, trait_lang_item, trait_method_name, borrow_kind) = if !mutability {
(Mutability::Not, LangItem::Deref, name![deref], BorrowKind::Shared)
} else {
(
Mutability::Mut,
LangItem::DerefMut,
name![deref_mut],
BorrowKind::Mut { allow_two_phase_borrow: false },
)
};
let ty_ref = TyKind::Ref(chalk_mut, static_lifetime(), source_ty.clone()).intern(Interner);
let target_ty_ref = TyKind::Ref(chalk_mut, static_lifetime(), target_ty).intern(Interner);
let ref_place: Place = self.temp(ty_ref)?.into();
self.push_assignment(current, ref_place.clone(), Rvalue::Ref(borrow_kind, place), span);
let deref_trait = self
.resolve_lang_item(trait_lang_item)?
.as_trait()
.ok_or(MirLowerError::LangItemNotFound(trait_lang_item))?;
let deref_fn = self
.db
.trait_data(deref_trait)
.method_by_name(&trait_method_name)
.ok_or(MirLowerError::LangItemNotFound(trait_lang_item))?;
let deref_fn_op = Operand::const_zst(
TyKind::FnDef(
self.db.intern_callable_def(CallableDefId::FunctionId(deref_fn)).into(),
Substitution::from1(Interner, source_ty),
)
.intern(Interner),
);
let mut result: Place = self.temp(target_ty_ref)?.into();
let Some(current) = self.lower_call(deref_fn_op, vec![Operand::Copy(ref_place)], result.clone(), current, false)? else {
return Ok(None);
};
result.projection.push(ProjectionElem::Deref);
Ok(Some((result, current)))
}
}

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

@ -258,6 +258,7 @@ fn test() {
#[test] #[test]
fn coerce_autoderef_block() { fn coerce_autoderef_block() {
// FIXME: We should know mutability in overloaded deref
check_no_mismatches( check_no_mismatches(
r#" r#"
//- minicore: deref //- minicore: deref
@ -267,7 +268,7 @@ fn takes_ref_str(x: &str) {}
fn returns_string() -> String { loop {} } fn returns_string() -> String { loop {} }
fn test() { fn test() {
takes_ref_str(&{ returns_string() }); takes_ref_str(&{ returns_string() });
// ^^^^^^^^^^^^^^^^^^^^^ adjustments: Deref(None), Deref(Some(OverloadedDeref(Not))), Borrow(Ref(Not)) // ^^^^^^^^^^^^^^^^^^^^^ adjustments: Deref(None), Deref(Some(OverloadedDeref(None))), Borrow(Ref(Not))
} }
"#, "#,
); );

View file

@ -1252,6 +1252,7 @@ fn foo<T: Trait>(a: &T) {
#[test] #[test]
fn autoderef_visibility_field() { fn autoderef_visibility_field() {
// FIXME: We should know mutability in overloaded deref
check( check(
r#" r#"
//- minicore: deref //- minicore: deref
@ -1273,7 +1274,7 @@ mod a {
mod b { mod b {
fn foo() { fn foo() {
let x = super::a::Bar::new().0; let x = super::a::Bar::new().0;
// ^^^^^^^^^^^^^^^^^^^^ adjustments: Deref(Some(OverloadedDeref(Not))) // ^^^^^^^^^^^^^^^^^^^^ adjustments: Deref(Some(OverloadedDeref(None)))
// ^^^^^^^^^^^^^^^^^^^^^^ type: char // ^^^^^^^^^^^^^^^^^^^^^^ type: char
} }
} }

View file

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

View file

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

View file

@ -10,8 +10,9 @@ 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, TraitAlias, TypeAlias, TypeOrConstParam,
Union, Variant,
}; };
pub trait HasSource { pub trait HasSource {
@ -178,3 +179,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

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

View file

@ -1103,7 +1103,10 @@ impl<'db> SemanticsImpl<'db> {
let kind = match adjust.kind { let kind = match adjust.kind {
hir_ty::Adjust::NeverToAny => Adjust::NeverToAny, hir_ty::Adjust::NeverToAny => Adjust::NeverToAny,
hir_ty::Adjust::Deref(Some(hir_ty::OverloadedDeref(m))) => { 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::Deref(None) => Adjust::Deref(None),
hir_ty::Adjust::Borrow(hir_ty::AutoBorrow::RawPtr(m)) => { 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::ImplSelfType(it) => ScopeDef::ImplSelfType(it.into()),
resolver::ScopeDef::AdtSelfType(it) => ScopeDef::AdtSelfType(it.into()), resolver::ScopeDef::AdtSelfType(it) => ScopeDef::AdtSelfType(it.into()),
resolver::ScopeDef::GenericParam(id) => ScopeDef::GenericParam(id.into()), resolver::ScopeDef::GenericParam(id) => ScopeDef::GenericParam(id.into()),
resolver::ScopeDef::Local(pat_id) => match self.resolver.body_owner() { resolver::ScopeDef::Local(binding_id) => match self.resolver.body_owner() {
Some(parent) => ScopeDef::Local(Local { parent, pat_id }), Some(parent) => ScopeDef::Local(Local { parent, binding_id }),
None => continue, None => continue,
}, },
resolver::ScopeDef::Label(label_id) => match self.resolver.body_owner() { resolver::ScopeDef::Label(label_id) => match self.resolver.body_owner() {

View file

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

View file

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

View file

@ -101,7 +101,7 @@ fn find_extracted_variable(ctx: &AssistContext<'_>, arm: &ast::MatchArm) -> Opti
let name_ref = path.syntax().descendants().find_map(ast::NameRef::cast)?; let name_ref = path.syntax().descendants().find_map(ast::NameRef::cast)?;
match NameRefClass::classify(&ctx.sema, &name_ref)? { match NameRefClass::classify(&ctx.sema, &name_ref)? {
NameRefClass::Definition(Definition::Local(local)) => { NameRefClass::Definition(Definition::Local(local)) => {
let source = local.source(ctx.db()).value.left()?; let source = local.primary_source(ctx.db()).into_ident_pat()?;
Some(source.name()?) Some(source.name()?)
} }
_ => None, _ => None,

View file

@ -3,7 +3,8 @@ use std::iter;
use ast::make; use ast::make;
use either::Either; use either::Either;
use hir::{ use hir::{
HasSource, HirDisplay, InFile, Local, ModuleDef, PathResolution, Semantics, TypeInfo, TypeParam, HasSource, HirDisplay, InFile, Local, LocalSource, ModuleDef, PathResolution, Semantics,
TypeInfo, TypeParam,
}; };
use ide_db::{ use ide_db::{
defs::{Definition, NameRefClass}, defs::{Definition, NameRefClass},
@ -710,7 +711,7 @@ impl FunctionBody {
) => local_ref, ) => local_ref,
_ => return, _ => return,
}; };
let InFile { file_id, value } = local_ref.source(sema.db); let InFile { file_id, value } = local_ref.primary_source(sema.db).source;
// locals defined inside macros are not relevant to us // locals defined inside macros are not relevant to us
if !file_id.is_macro() { if !file_id.is_macro() {
match value { match value {
@ -972,11 +973,11 @@ impl FunctionBody {
locals: impl Iterator<Item = Local>, locals: impl Iterator<Item = Local>,
) -> Vec<Param> { ) -> Vec<Param> {
locals locals
.map(|local| (local, local.source(ctx.db()))) .map(|local| (local, local.primary_source(ctx.db())))
.filter(|(_, src)| is_defined_outside_of_body(ctx, self, src)) .filter(|(_, src)| is_defined_outside_of_body(ctx, self, src))
.filter_map(|(local, src)| match src.value { .filter_map(|(local, src)| match src.into_ident_pat() {
Either::Left(src) => Some((local, src)), Some(src) => Some((local, src)),
Either::Right(_) => { None => {
stdx::never!(false, "Local::is_self returned false, but source is SelfParam"); stdx::never!(false, "Local::is_self returned false, but source is SelfParam");
None None
} }
@ -1238,17 +1239,9 @@ fn local_outlives_body(
fn is_defined_outside_of_body( fn is_defined_outside_of_body(
ctx: &AssistContext<'_>, ctx: &AssistContext<'_>,
body: &FunctionBody, body: &FunctionBody,
src: &hir::InFile<Either<ast::IdentPat, ast::SelfParam>>, src: &LocalSource,
) -> bool { ) -> bool {
src.file_id.original_file(ctx.db()) == ctx.file_id() src.original_file(ctx.db()) == ctx.file_id() && !body.contains_node(src.syntax())
&& !body.contains_node(either_syntax(&src.value))
}
fn either_syntax(value: &Either<ast::IdentPat, ast::SelfParam>) -> &SyntaxNode {
match value {
Either::Left(pat) => pat.syntax(),
Either::Right(it) => it.syntax(),
}
} }
/// find where to put extracted function definition /// find where to put extracted function definition

View file

@ -1,4 +1,3 @@
use either::Either;
use hir::{PathResolution, Semantics}; use hir::{PathResolution, Semantics};
use ide_db::{ use ide_db::{
base_db::FileId, base_db::FileId,
@ -205,12 +204,14 @@ fn inline_usage(
return None; return None;
} }
// FIXME: Handle multiple local definitions let sources = local.sources(sema.db);
let bind_pat = match local.source(sema.db).value { let [source] = sources.as_slice() else {
Either::Left(ident) => ident, // Not applicable with locals with multiple definitions (i.e. or patterns)
_ => return None, return None;
}; };
let bind_pat = source.as_ident_pat()?;
let let_stmt = ast::LetStmt::cast(bind_pat.syntax().parent()?)?; let let_stmt = ast::LetStmt::cast(bind_pat.syntax().parent()?)?;
let UsageSearchResult { mut references } = Definition::Local(local).usages(sema).all(); let UsageSearchResult { mut references } = Definition::Local(local).usages(sema).all();

View file

@ -121,14 +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),
Definition::Local(local) => { Definition::Local(it) => name_range(it.primary_source(sema.db), sema),
let src = local.source(sema.db);
let name = match &src.value {
Either::Left(bind_pat) => bind_pat.name()?,
Either::Right(_) => return None,
};
src.with_value(name.syntax()).original_file_range_opt(sema.db)
}
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)?;
@ -302,13 +295,7 @@ fn rename_reference(
source_change.insert_source_edit(file_id, edit); source_change.insert_source_edit(file_id, edit);
Ok(()) Ok(())
}; };
match def { insert_def_edit(def)?;
Definition::Local(l) => l
.associated_locals(sema.db)
.iter()
.try_for_each(|&local| insert_def_edit(Definition::Local(local))),
def => insert_def_edit(def),
}?;
Ok(source_change) Ok(source_change)
} }
@ -471,59 +458,64 @@ fn source_edit_from_def(
def: Definition, def: Definition,
new_name: &str, new_name: &str,
) -> Result<(FileId, TextEdit)> { ) -> Result<(FileId, TextEdit)> {
let FileRange { file_id, range } = def
.range_for_rename(sema)
.ok_or_else(|| format_err!("No identifier available to rename"))?;
let mut edit = TextEdit::builder(); let mut edit = TextEdit::builder();
if let Definition::Local(local) = def { if let Definition::Local(local) = def {
if let Either::Left(pat) = local.source(sema.db).value { let mut file_id = None;
// special cases required for renaming fields/locals in Record patterns for source in local.sources(sema.db) {
if let Some(pat_field) = pat.syntax().parent().and_then(ast::RecordPatField::cast) { let source = source.source;
file_id = source.file_id.file_id();
if let Either::Left(pat) = source.value {
let name_range = pat.name().unwrap().syntax().text_range(); let name_range = pat.name().unwrap().syntax().text_range();
if let Some(name_ref) = pat_field.name_ref() { // special cases required for renaming fields/locals in Record patterns
if new_name == name_ref.text() && pat.at_token().is_none() { if let Some(pat_field) = pat.syntax().parent().and_then(ast::RecordPatField::cast) {
// Foo { field: ref mut local } -> Foo { ref mut field } if let Some(name_ref) = pat_field.name_ref() {
// ^^^^^^ delete this if new_name == name_ref.text() && pat.at_token().is_none() {
// ^^^^^ replace this with `field` // Foo { field: ref mut local } -> Foo { ref mut field }
cov_mark::hit!(test_rename_local_put_init_shorthand_pat); // ^^^^^^ delete this
edit.delete( // ^^^^^ replace this with `field`
name_ref cov_mark::hit!(test_rename_local_put_init_shorthand_pat);
.syntax() edit.delete(
.text_range() name_ref
.cover_offset(pat.syntax().text_range().start()), .syntax()
); .text_range()
edit.replace(name_range, name_ref.text().to_string()); .cover_offset(pat.syntax().text_range().start()),
);
edit.replace(name_range, name_ref.text().to_string());
} else {
// Foo { field: ref mut local @ local 2} -> Foo { field: ref mut new_name @ local2 }
// Foo { field: ref mut local } -> Foo { field: ref mut new_name }
// ^^^^^ replace this with `new_name`
edit.replace(name_range, new_name.to_string());
}
} else { } else {
// Foo { field: ref mut local @ local 2} -> Foo { field: ref mut new_name @ local2 } // Foo { ref mut field } -> Foo { field: ref mut new_name }
// Foo { field: ref mut local } -> Foo { field: ref mut new_name } // ^ insert `field: `
// ^^^^^ replace this with `new_name` // ^^^^^ replace this with `new_name`
edit.insert(
pat.syntax().text_range().start(),
format!("{}: ", pat_field.field_name().unwrap()),
);
edit.replace(name_range, new_name.to_string()); edit.replace(name_range, new_name.to_string());
} }
} else { } else {
// Foo { ref mut field } -> Foo { field: ref mut new_name }
// ^ insert `field: `
// ^^^^^ replace this with `new_name`
edit.insert(
pat.syntax().text_range().start(),
format!("{}: ", pat_field.field_name().unwrap()),
);
edit.replace(name_range, new_name.to_string()); edit.replace(name_range, new_name.to_string());
} }
} }
} }
let Some(file_id) = file_id else { bail!("No file available to rename") };
return Ok((file_id, edit.finish()));
} }
if edit.is_empty() { let FileRange { file_id, range } = def
let (range, new_name) = match def { .range_for_rename(sema)
Definition::GenericParam(hir::GenericParam::LifetimeParam(_)) .ok_or_else(|| format_err!("No identifier available to rename"))?;
| Definition::Label(_) => ( let (range, new_name) = match def {
TextRange::new(range.start() + syntax::TextSize::from(1), range.end()), Definition::GenericParam(hir::GenericParam::LifetimeParam(_)) | Definition::Label(_) => (
new_name.strip_prefix('\'').unwrap_or(new_name).to_owned(), TextRange::new(range.start() + syntax::TextSize::from(1), range.end()),
), new_name.strip_prefix('\'').unwrap_or(new_name).to_owned(),
_ => (range, new_name.to_owned()), ),
}; _ => (range, new_name.to_owned()),
edit.replace(range, new_name); };
} edit.replace(range, new_name);
Ok((file_id, edit.finish())) Ok((file_id, edit.finish()))
} }

View file

@ -320,7 +320,7 @@ impl Definition {
scope: None, scope: None,
include_self_kw_refs: None, include_self_kw_refs: None,
local_repr: match self { local_repr: match self {
Definition::Local(local) => Some(local.representative(sema.db)), Definition::Local(local) => Some(local),
_ => None, _ => None,
}, },
search_self_mod: false, search_self_mod: false,
@ -646,7 +646,7 @@ impl<'a> FindUsages<'a> {
match NameRefClass::classify(self.sema, name_ref) { match NameRefClass::classify(self.sema, name_ref) {
Some(NameRefClass::Definition(def @ Definition::Local(local))) Some(NameRefClass::Definition(def @ Definition::Local(local)))
if matches!( if matches!(
self.local_repr, Some(repr) if repr == local.representative(self.sema.db) self.local_repr, Some(repr) if repr == local
) => ) =>
{ {
let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax()); let FileRange { file_id, range } = self.sema.original_range(name_ref.syntax());
@ -707,7 +707,7 @@ impl<'a> FindUsages<'a> {
Definition::Field(_) if field == self.def => { Definition::Field(_) if field == self.def => {
ReferenceCategory::new(&field, name_ref) ReferenceCategory::new(&field, name_ref)
} }
Definition::Local(_) if matches!(self.local_repr, Some(repr) if repr == local.representative(self.sema.db)) => { Definition::Local(_) if matches!(self.local_repr, Some(repr) if repr == local) => {
ReferenceCategory::new(&Definition::Local(local), name_ref) ReferenceCategory::new(&Definition::Local(local), name_ref)
} }
_ => return false, _ => return false,
@ -755,7 +755,7 @@ impl<'a> FindUsages<'a> {
Some(NameClass::Definition(def @ Definition::Local(local))) if def != self.def => { Some(NameClass::Definition(def @ Definition::Local(local))) if def != self.def => {
if matches!( if matches!(
self.local_repr, self.local_repr,
Some(repr) if local.representative(self.sema.db) == repr Some(repr) if local == repr
) { ) {
let FileRange { file_id, range } = self.sema.original_range(name.syntax()); let FileRange { file_id, range } = self.sema.original_range(name.syntax());
let reference = FileReference { let reference = FileReference {

View file

@ -0,0 +1,592 @@
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
//
// This diagnostic is triggered on mutating an immutable variable.
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(
"add_mut",
"Change it to be mutable",
SourceChange::from_text_edit(file_id, edit),
use_range,
)])
})();
Diagnostic::new(
"need-mut",
format!("cannot mutate immutable variable `{}`", d.local.name(ctx.sema.db)),
ctx.sema.diagnostics_display_range(d.span.clone()).range,
)
.with_fixes(fixes)
}
// 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 {
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(
"unused-mut",
"variable does not need to be mutable",
ctx.sema.diagnostics_display_range(ast).range,
)
.severity(Severity::WeakWarning)
.experimental() // Not supporting `#[allow(unused_mut)]` leads to false positive.
.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)]
mod tests {
use crate::tests::{check_diagnostics, check_fix};
#[test]
fn unused_mut_simple() {
check_diagnostics(
r#"
fn f(_: i32) {}
fn main() {
let mut x = 2;
//^^^^^ 💡 weak: variable does not need to be mutable
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 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]
fn field_mutate() {
check_diagnostics(
r#"
fn f(_: i32) {}
fn main() {
let mut x = (2, 7);
//^^^^^ 💡 weak: variable does not need to be mutable
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: variable does not need to be mutable
*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: variable does not need to be mutable
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: variable does not need to be mutable
&mut x;
}
"#,
);
check_diagnostics(
r#"
fn main() {
loop {}
let mut x = 2;
//^^^^^ 💡 weak: variable does not need to be mutable
&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: variable does not need to be mutable
&mut x;
}
"#,
);
check_diagnostics(
r#"
fn main(b: bool) {
if b {
loop {}
} else {
return;
}
let mut x = 2;
//^^^^^ 💡 weak: variable does not need to be mutable
&mut x;
}
"#,
);
}
#[test]
fn initialization_is_not_mutation() {
check_diagnostics(
r#"
fn f(_: i32) {}
fn main() {
let mut x;
//^^^^^ 💡 weak: variable does not need to be mutable
x = 5;
f(x);
}
"#,
);
check_diagnostics(
r#"
fn f(_: i32) {}
fn main(b: bool) {
let mut x;
//^^^^^ 💡 weak: variable does not need to be mutable
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: variable does not need to be mutable
f(x);
if let mut y = 2 {
//^^^^^ 💡 weak: variable does not need to be mutable
f(y);
}
match 3 {
mut z => f(z),
//^^^^^ 💡 weak: variable does not need to be mutable
}
}
}
"#,
);
}
#[test]
fn function_arguments_are_initialized() {
check_diagnostics(
r#"
fn f(mut x: i32) {
//^^^^^ 💡 weak: variable does not need to be mutable
}
"#,
);
check_diagnostics(
r#"
fn f(x: i32) {
x = 5;
//^^^^^ 💡 error: cannot mutate immutable variable `x`
}
"#,
);
}
#[test]
fn for_loop() {
check_diagnostics(
r#"
//- minicore: iterators
fn f(x: [(i32, u8); 10]) {
for (a, mut b) in x {
//^^^^^ 💡 weak: variable does not need to be mutable
a = 2;
//^^^^^ 💡 error: cannot mutate immutable variable `a`
}
}
"#,
);
}
#[test]
fn overloaded_deref() {
// FIXME: check for false negative
check_diagnostics(
r#"
//- minicore: deref_mut
use core::ops::{Deref, DerefMut};
struct Foo;
impl Deref for Foo {
type Target = i32;
fn deref(&self) -> &i32 {
&5
}
}
impl DerefMut for Foo {
fn deref_mut(&mut self) -> &mut i32 {
&mut 5
}
}
fn f() {
let x = Foo;
let y = &*x;
let x = Foo;
let mut x = Foo;
let y: &mut i32 = &mut x;
}
"#,
);
}
#[test]
fn or_pattern() {
check_diagnostics(
r#"
//- minicore: option
fn f(_: i32) {}
fn main() {
let ((Some(mut x), None) | (_, Some(mut x))) = (None, Some(7));
//^^^^^ 💡 weak: variable does not need to be mutable
f(x);
}
"#,
);
}
#[test]
fn respect_allow_unused_mut() {
// FIXME: respect
check_diagnostics(
r#"
fn f(_: i32) {}
fn main() {
#[allow(unused_mut)]
let mut x = 2;
//^^^^^ 💡 weak: variable does not need to be mutable
f(x);
}
"#,
);
}
}

View file

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

View file

@ -14,7 +14,7 @@ use syntax::{
SyntaxNode, SyntaxToken, TextRange, T, SyntaxNode, SyntaxToken, TextRange, T,
}; };
use crate::{references, NavigationTarget, TryToNav}; use crate::{navigation_target::ToNav, references, NavigationTarget, TryToNav};
#[derive(PartialEq, Eq, Hash)] #[derive(PartialEq, Eq, Hash)]
pub struct HighlightedRange { pub struct HighlightedRange {
@ -98,32 +98,39 @@ fn highlight_references(
category: access, category: access,
}); });
let mut res = FxHashSet::default(); let mut res = FxHashSet::default();
let mut def_to_hl_range = |def| {
let hl_range = match def {
Definition::Module(module) => {
Some(NavigationTarget::from_module_to_decl(sema.db, module))
}
def => def.try_to_nav(sema.db),
}
.filter(|decl| decl.file_id == file_id)
.and_then(|decl| decl.focus_range)
.map(|range| {
let category =
references::decl_mutability(&def, node, range).then_some(ReferenceCategory::Write);
HighlightedRange { range, category }
});
if let Some(hl_range) = hl_range {
res.insert(hl_range);
}
};
for &def in &defs { for &def in &defs {
match def { match def {
Definition::Local(local) => local Definition::Local(local) => {
.associated_locals(sema.db) let category = local.is_mut(sema.db).then_some(ReferenceCategory::Write);
.iter() local
.for_each(|&local| def_to_hl_range(Definition::Local(local))), .sources(sema.db)
def => def_to_hl_range(def), .into_iter()
.map(|x| x.to_nav(sema.db))
.filter(|decl| decl.file_id == file_id)
.filter_map(|decl| decl.focus_range)
.map(|range| HighlightedRange { range, category })
.for_each(|x| {
res.insert(x);
});
}
def => {
let hl_range = match def {
Definition::Module(module) => {
Some(NavigationTarget::from_module_to_decl(sema.db, module))
}
def => def.try_to_nav(sema.db),
}
.filter(|decl| decl.file_id == file_id)
.and_then(|decl| decl.focus_range)
.map(|range| {
let category = references::decl_mutability(&def, node, range)
.then_some(ReferenceCategory::Write);
HighlightedRange { range, category }
});
if let Some(hl_range) = hl_range {
res.insert(hl_range);
}
}
} }
} }

View file

@ -635,8 +635,8 @@ fn local(db: &RootDatabase, it: hir::Local) -> Option<Markup> {
let ty = it.ty(db); let ty = it.ty(db);
let ty = ty.display_truncated(db, None); let ty = ty.display_truncated(db, None);
let is_mut = if it.is_mut(db) { "mut " } else { "" }; let is_mut = if it.is_mut(db) { "mut " } else { "" };
let desc = match it.source(db).value { let desc = match it.primary_source(db).into_ident_pat() {
Either::Left(ident) => { Some(ident) => {
let name = it.name(db); let name = it.name(db);
let let_kw = if ident let let_kw = if ident
.syntax() .syntax()
@ -649,7 +649,7 @@ fn local(db: &RootDatabase, it: hir::Local) -> Option<Markup> {
}; };
format!("{let_kw}{is_mut}{name}: {ty}") format!("{let_kw}{is_mut}{name}: {ty}")
} }
Either::Right(_) => format!("{is_mut}self: {ty}"), None => format!("{is_mut}self: {ty}"),
}; };
markup(None, desc, None) markup(None, desc, None)
} }

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

@ -5,7 +5,7 @@ use std::fmt;
use either::Either; use either::Either;
use hir::{ use hir::{
symbols::FileSymbol, AssocItem, Documentation, FieldSource, HasAttrs, HasSource, HirDisplay, symbols::FileSymbol, AssocItem, Documentation, FieldSource, HasAttrs, HasSource, HirDisplay,
InFile, ModuleSource, Semantics, InFile, LocalSource, ModuleSource, Semantics,
}; };
use ide_db::{ use ide_db::{
base_db::{FileId, FileRange}, base_db::{FileId, FileRange},
@ -387,9 +387,11 @@ impl TryToNav for hir::GenericParam {
} }
} }
impl ToNav for hir::Local { impl ToNav for LocalSource {
fn to_nav(&self, db: &RootDatabase) -> NavigationTarget { fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
let InFile { file_id, value } = self.source(db); let InFile { file_id, value } = &self.source;
let file_id = *file_id;
let local = self.local;
let (node, name) = match &value { let (node, name) = match &value {
Either::Left(bind_pat) => (bind_pat.syntax(), bind_pat.name()), Either::Left(bind_pat) => (bind_pat.syntax(), bind_pat.name()),
Either::Right(it) => (it.syntax(), it.name()), Either::Right(it) => (it.syntax(), it.name()),
@ -398,10 +400,10 @@ impl ToNav for hir::Local {
let FileRange { file_id, range: full_range } = let FileRange { file_id, range: full_range } =
InFile::new(file_id, node).original_file_range(db); InFile::new(file_id, node).original_file_range(db);
let name = self.name(db).to_smol_str(); let name = local.name(db).to_smol_str();
let kind = if self.is_self(db) { let kind = if local.is_self(db) {
SymbolKind::SelfParam SymbolKind::SelfParam
} else if self.is_param(db) { } else if local.is_param(db) {
SymbolKind::ValueParam SymbolKind::ValueParam
} else { } else {
SymbolKind::Local SymbolKind::Local
@ -419,6 +421,12 @@ impl ToNav for hir::Local {
} }
} }
impl ToNav for hir::Local {
fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
self.primary_source(db).to_nav(db)
}
}
impl ToNav for hir::Label { impl ToNav for hir::Label {
fn to_nav(&self, db: &RootDatabase) -> NavigationTarget { fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
let InFile { file_id, value } = self.source(db); let InFile { file_id, value } = self.source(db);

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

@ -762,6 +762,20 @@ pub mod iter {
self self
} }
} }
pub struct IntoIter<T, const N: usize>([T; N]);
impl<T, const N: usize> IntoIterator for [T; N] {
type Item = T;
type IntoIter = IntoIter<T, N>;
fn into_iter(self) -> I {
IntoIter(self)
}
}
impl<T, const N: usize> Iterator for IntoIter<T, N> {
type Item = T;
fn next(&mut self) -> Option<T> {
loop {}
}
}
} }
pub use self::collect::IntoIterator; pub use self::collect::IntoIterator;
} }

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