mirror of
https://github.com/rust-lang/rust.git
synced 2024-11-25 08:13:41 +00:00
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:
commit
44ff3c407a
@ -24,7 +24,7 @@ use syntax::{ast, AstPtr, SyntaxNode, SyntaxNodePtr};
|
||||
use crate::{
|
||||
attr::Attrs,
|
||||
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,
|
||||
macro_id_to_def_id,
|
||||
nameres::DefMap,
|
||||
@ -270,6 +270,7 @@ pub struct Mark {
|
||||
pub struct Body {
|
||||
pub exprs: Arena<Expr>,
|
||||
pub pats: Arena<Pat>,
|
||||
pub bindings: Arena<Binding>,
|
||||
pub or_pats: FxHashMap<PatId, Arc<[PatId]>>,
|
||||
pub labels: Arena<Label>,
|
||||
/// The patterns for the function's parameters. While the parameter types are
|
||||
@ -435,13 +436,24 @@ impl Body {
|
||||
}
|
||||
|
||||
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();
|
||||
or_pats.shrink_to_fit();
|
||||
exprs.shrink_to_fit();
|
||||
labels.shrink_to_fit();
|
||||
params.shrink_to_fit();
|
||||
pats.shrink_to_fit();
|
||||
bindings.shrink_to_fit();
|
||||
}
|
||||
}
|
||||
|
||||
@ -451,6 +463,7 @@ impl Default for Body {
|
||||
body_expr: dummy_expr_id(),
|
||||
exprs: Default::default(),
|
||||
pats: Default::default(),
|
||||
bindings: Default::default(),
|
||||
or_pats: Default::default(),
|
||||
labels: 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.
|
||||
// Perhaps `expr_syntax` and `expr_id`?
|
||||
impl BodySourceMap {
|
||||
|
@ -15,6 +15,7 @@ use la_arena::Arena;
|
||||
use once_cell::unsync::OnceCell;
|
||||
use profile::Count;
|
||||
use rustc_hash::FxHashMap;
|
||||
use smallvec::SmallVec;
|
||||
use syntax::{
|
||||
ast::{
|
||||
self, ArrayExprKind, AstChildren, HasArgList, HasLoopBody, HasName, LiteralKind,
|
||||
@ -30,9 +31,9 @@ use crate::{
|
||||
builtin_type::{BuiltinFloat, BuiltinInt, BuiltinUint},
|
||||
db::DefDatabase,
|
||||
expr::{
|
||||
dummy_expr_id, Array, BindingAnnotation, ClosureKind, Expr, ExprId, FloatTypeWrapper,
|
||||
Label, LabelId, Literal, MatchArm, Movability, Pat, PatId, RecordFieldPat, RecordLitField,
|
||||
Statement,
|
||||
dummy_expr_id, Array, Binding, BindingAnnotation, BindingId, ClosureKind, Expr, ExprId,
|
||||
FloatTypeWrapper, Label, LabelId, Literal, MatchArm, Movability, Pat, PatId,
|
||||
RecordFieldPat, RecordLitField, Statement,
|
||||
},
|
||||
item_scope::BuiltinShadowMode,
|
||||
path::{GenericArgs, Path},
|
||||
@ -87,6 +88,7 @@ pub(super) fn lower(
|
||||
body: Body {
|
||||
exprs: Arena::default(),
|
||||
pats: Arena::default(),
|
||||
bindings: Arena::default(),
|
||||
labels: Arena::default(),
|
||||
params: Vec::new(),
|
||||
body_expr: dummy_expr_id(),
|
||||
@ -116,6 +118,22 @@ struct ExprCollector<'a> {
|
||||
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<'_> {
|
||||
fn collect(
|
||||
mut self,
|
||||
@ -127,17 +145,16 @@ impl ExprCollector<'_> {
|
||||
param_list.self_param().filter(|_| attr_enabled.next().unwrap_or(false))
|
||||
{
|
||||
let ptr = AstPtr::new(&self_param);
|
||||
let param_pat = self.alloc_pat(
|
||||
Pat::Bind {
|
||||
name: name![self],
|
||||
mode: BindingAnnotation::new(
|
||||
self_param.mut_token().is_some() && self_param.amp_token().is_none(),
|
||||
false,
|
||||
),
|
||||
subpat: None,
|
||||
},
|
||||
Either::Right(ptr),
|
||||
let binding_id = self.alloc_binding(
|
||||
name![self],
|
||||
BindingAnnotation::new(
|
||||
self_param.mut_token().is_some() && self_param.amp_token().is_none(),
|
||||
false,
|
||||
),
|
||||
);
|
||||
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);
|
||||
}
|
||||
|
||||
@ -179,6 +196,9 @@ impl ExprCollector<'_> {
|
||||
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 {
|
||||
let src = self.expander.to_source(ptr);
|
||||
let id = self.make_pat(pat, src.clone());
|
||||
@ -804,7 +824,7 @@ impl ExprCollector<'_> {
|
||||
}
|
||||
|
||||
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() {
|
||||
let pats = Arc::<[_]>::from(pats);
|
||||
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 {
|
||||
ast::Pat::IdentPat(bp) => {
|
||||
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 annotation =
|
||||
BindingAnnotation::new(bp.mut_token().is_some(), bp.ref_token().is_some());
|
||||
let subpat = bp.pat().map(|subpat| self.collect_pat_(subpat));
|
||||
let pattern = if annotation == BindingAnnotation::Unannotated && subpat.is_none() {
|
||||
let subpat = bp.pat().map(|subpat| self.collect_pat_(subpat, binding_list));
|
||||
let (binding, pattern) = if annotation == BindingAnnotation::Unannotated
|
||||
&& subpat.is_none()
|
||||
{
|
||||
// This could also be a single-segment path pattern. To
|
||||
// decide that, we need to try resolving the name.
|
||||
let (resolved, _) = self.expander.def_map.resolve_path(
|
||||
@ -839,12 +861,12 @@ impl ExprCollector<'_> {
|
||||
BuiltinShadowMode::Other,
|
||||
);
|
||||
match resolved.take_values() {
|
||||
Some(ModuleDefId::ConstId(_)) => Pat::Path(name.into()),
|
||||
Some(ModuleDefId::ConstId(_)) => (None, Pat::Path(name.into())),
|
||||
Some(ModuleDefId::EnumVariantId(_)) => {
|
||||
// this is only really valid for unit variants, but
|
||||
// shadowing other enum variants with a pattern is
|
||||
// an error anyway
|
||||
Pat::Path(name.into())
|
||||
(None, Pat::Path(name.into()))
|
||||
}
|
||||
Some(ModuleDefId::AdtId(AdtId::StructId(s)))
|
||||
if self.db.struct_data(s).variant_data.kind() != StructKind::Record =>
|
||||
@ -852,17 +874,24 @@ impl ExprCollector<'_> {
|
||||
// Funnily enough, record structs *can* be shadowed
|
||||
// by pattern bindings (but unit or tuple structs
|
||||
// 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
|
||||
_ => Pat::Bind { name, mode: annotation, subpat },
|
||||
_ => {
|
||||
let id = binding_list.find(self, name, annotation);
|
||||
(Some(id), Pat::Bind { id, subpat })
|
||||
}
|
||||
}
|
||||
} 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 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 {
|
||||
self.name_to_pat_grouping.entry(key).or_default().push(pat);
|
||||
}
|
||||
@ -871,11 +900,11 @@ impl ExprCollector<'_> {
|
||||
ast::Pat::TupleStructPat(p) => {
|
||||
let path =
|
||||
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 }
|
||||
}
|
||||
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());
|
||||
Pat::Ref { pat, mutability }
|
||||
}
|
||||
@ -886,12 +915,12 @@ impl ExprCollector<'_> {
|
||||
}
|
||||
ast::Pat::OrPat(p) => {
|
||||
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)
|
||||
}
|
||||
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) => {
|
||||
let (args, ellipsis) = self.collect_tuple_pat(p.fields());
|
||||
let (args, ellipsis) = self.collect_tuple_pat(p.fields(), binding_list);
|
||||
Pat::Tuple { args, ellipsis }
|
||||
}
|
||||
ast::Pat::WildcardPat(_) => Pat::Wild,
|
||||
@ -904,7 +933,7 @@ impl ExprCollector<'_> {
|
||||
.fields()
|
||||
.filter_map(|f| {
|
||||
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();
|
||||
Some(RecordFieldPat { name, pat })
|
||||
})
|
||||
@ -923,9 +952,15 @@ impl ExprCollector<'_> {
|
||||
|
||||
// FIXME properly handle `RestPat`
|
||||
Pat::Slice {
|
||||
prefix: prefix.into_iter().map(|p| self.collect_pat_(p)).collect(),
|
||||
slice: slice.map(|p| self.collect_pat_(p)),
|
||||
suffix: suffix.into_iter().map(|p| self.collect_pat_(p)).collect(),
|
||||
prefix: prefix
|
||||
.into_iter()
|
||||
.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) => {
|
||||
@ -948,7 +983,7 @@ impl ExprCollector<'_> {
|
||||
Pat::Missing
|
||||
}
|
||||
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 }
|
||||
}
|
||||
ast::Pat::ConstBlockPat(const_block_pat) => {
|
||||
@ -965,7 +1000,7 @@ impl ExprCollector<'_> {
|
||||
let src = self.expander.to_source(Either::Left(AstPtr::new(&pat)));
|
||||
let 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);
|
||||
return pat;
|
||||
@ -979,21 +1014,25 @@ impl ExprCollector<'_> {
|
||||
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 {
|
||||
Some(pat) => self.collect_pat_(pat),
|
||||
Some(pat) => self.collect_pat_(pat, binding_list),
|
||||
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
|
||||
// consider the possibility of there being multiple `..` here.
|
||||
let ellipsis = args.clone().position(|p| matches!(p, ast::Pat::RestPat(_)));
|
||||
// We want to skip the `..` pattern here, since we account for it above.
|
||||
let args = args
|
||||
.filter(|p| !matches!(p, ast::Pat::RestPat(_)))
|
||||
.map(|p| self.collect_pat_(p))
|
||||
.map(|p| self.collect_pat_(p, binding_list))
|
||||
.collect();
|
||||
|
||||
(args, ellipsis)
|
||||
@ -1022,6 +1061,10 @@ impl ExprCollector<'_> {
|
||||
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 {
|
||||
|
@ -5,7 +5,7 @@ use std::fmt::{self, Write};
|
||||
use syntax::ast::HasName;
|
||||
|
||||
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},
|
||||
type_ref::TypeRef,
|
||||
};
|
||||
@ -524,14 +524,8 @@ impl<'a> Printer<'a> {
|
||||
}
|
||||
Pat::Path(path) => self.print_path(path),
|
||||
Pat::Lit(expr) => self.print_expr(*expr),
|
||||
Pat::Bind { mode, name, subpat } => {
|
||||
let mode = match mode {
|
||||
BindingAnnotation::Unannotated => "",
|
||||
BindingAnnotation::Mutable => "mut ",
|
||||
BindingAnnotation::Ref => "ref ",
|
||||
BindingAnnotation::RefMut => "ref mut ",
|
||||
};
|
||||
w!(self, "{}{}", mode, name);
|
||||
Pat::Bind { id, subpat } => {
|
||||
self.print_binding(*id);
|
||||
if let Some(pat) = subpat {
|
||||
self.whitespace();
|
||||
self.print_pat(*pat);
|
||||
@ -635,4 +629,15 @@ impl<'a> Printer<'a> {
|
||||
fn print_path(&mut self, path: &Path) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ use rustc_hash::FxHashMap;
|
||||
use crate::{
|
||||
body::Body,
|
||||
db::DefDatabase,
|
||||
expr::{Expr, ExprId, LabelId, Pat, PatId, Statement},
|
||||
expr::{Binding, BindingId, Expr, ExprId, LabelId, Pat, PatId, Statement},
|
||||
BlockId, DefWithBodyId,
|
||||
};
|
||||
|
||||
@ -23,7 +23,7 @@ pub struct ExprScopes {
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct ScopeEntry {
|
||||
name: Name,
|
||||
pat: PatId,
|
||||
binding: BindingId,
|
||||
}
|
||||
|
||||
impl ScopeEntry {
|
||||
@ -31,8 +31,8 @@ impl ScopeEntry {
|
||||
&self.name
|
||||
}
|
||||
|
||||
pub fn pat(&self) -> PatId {
|
||||
self.pat
|
||||
pub fn binding(&self) -> BindingId {
|
||||
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];
|
||||
if let Pat::Bind { name, .. } = pattern {
|
||||
let entry = ScopeEntry { name: name.clone(), pat };
|
||||
self.scopes[scope].entries.push(entry);
|
||||
if let Pat::Bind { id, .. } = pattern {
|
||||
self.add_bindings(body, scope, *id);
|
||||
}
|
||||
|
||||
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]) {
|
||||
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) {
|
||||
@ -170,7 +175,7 @@ fn compute_block_scopes(
|
||||
}
|
||||
|
||||
*scope = scopes.new_scope(*scope);
|
||||
scopes.add_bindings(body, *scope, *pat);
|
||||
scopes.add_pat_bindings(body, *scope, *pat);
|
||||
}
|
||||
Statement::Expr { expr, .. } => {
|
||||
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 } => {
|
||||
compute_expr_scopes(*iterable, body, scopes, scope);
|
||||
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);
|
||||
}
|
||||
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);
|
||||
for arm in arms.iter() {
|
||||
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 {
|
||||
scope = scopes.new_scope(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 } => {
|
||||
compute_expr_scopes(expr, body, scopes, 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)),
|
||||
};
|
||||
@ -450,7 +455,7 @@ fn foo() {
|
||||
let function = find_function(&db, file_id);
|
||||
|
||||
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_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 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(
|
||||
|it| it.syntax_node_ptr().to_node(file.syntax()),
|
||||
|
@ -17,6 +17,7 @@ use std::fmt;
|
||||
use hir_expand::name::Name;
|
||||
use intern::Interned;
|
||||
use la_arena::{Idx, RawIdx};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::{
|
||||
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 BindingId = Idx<Binding>;
|
||||
|
||||
/// FIXME: this is a hacky function which should be removed
|
||||
pub(crate) fn dummy_expr_id() -> ExprId {
|
||||
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)]
|
||||
pub struct RecordFieldPat {
|
||||
pub name: Name,
|
||||
@ -451,7 +461,7 @@ pub enum Pat {
|
||||
Slice { prefix: Box<[PatId]>, slice: Option<PatId>, suffix: Box<[PatId]> },
|
||||
Path(Box<Path>),
|
||||
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> },
|
||||
Ref { pat: PatId, mutability: Mutability },
|
||||
Box { inner: PatId },
|
||||
|
@ -12,7 +12,7 @@ use crate::{
|
||||
body::scope::{ExprScopes, ScopeId},
|
||||
builtin_type::BuiltinType,
|
||||
db::DefDatabase,
|
||||
expr::{ExprId, LabelId, PatId},
|
||||
expr::{BindingId, ExprId, LabelId},
|
||||
generics::{GenericParams, TypeOrConstParamData},
|
||||
item_scope::{BuiltinShadowMode, BUILTIN_SCOPE},
|
||||
nameres::DefMap,
|
||||
@ -105,7 +105,7 @@ pub enum ResolveValueResult {
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum ValueNs {
|
||||
ImplSelf(ImplId),
|
||||
LocalBinding(PatId),
|
||||
LocalBinding(BindingId),
|
||||
FunctionId(FunctionId),
|
||||
ConstId(ConstId),
|
||||
StaticId(StaticId),
|
||||
@ -267,7 +267,7 @@ impl Resolver {
|
||||
|
||||
if let Some(e) = entry {
|
||||
return Some(ResolveValueResult::ValueNs(ValueNs::LocalBinding(
|
||||
e.pat(),
|
||||
e.binding(),
|
||||
)));
|
||||
}
|
||||
}
|
||||
@ -617,7 +617,7 @@ pub enum ScopeDef {
|
||||
ImplSelfType(ImplId),
|
||||
AdtSelfType(AdtId),
|
||||
GenericParam(GenericParamId),
|
||||
Local(PatId),
|
||||
Local(BindingId),
|
||||
Label(LabelId),
|
||||
}
|
||||
|
||||
@ -669,7 +669,7 @@ impl Scope {
|
||||
acc.add(&name, ScopeDef::Label(label))
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
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();
|
||||
// XXX: hack, account for local (and only local) shadowing.
|
||||
//
|
||||
@ -870,7 +870,7 @@ impl ScopeNames {
|
||||
cov_mark::hit!(shadowing_shows_single_completion);
|
||||
return;
|
||||
}
|
||||
set.push(ScopeDef::Local(pat))
|
||||
set.push(ScopeDef::Local(binding))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -103,6 +103,22 @@ fn references() {
|
||||
"#,
|
||||
5,
|
||||
);
|
||||
check_number(
|
||||
r#"
|
||||
struct Foo(i32);
|
||||
impl Foo {
|
||||
fn method(&mut self, x: i32) {
|
||||
self.0 = 2 * self.0 + x;
|
||||
}
|
||||
}
|
||||
const GOAL: i32 = {
|
||||
let mut x = Foo(3);
|
||||
x.method(5);
|
||||
x.0
|
||||
};
|
||||
"#,
|
||||
11,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -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]
|
||||
fn function_call() {
|
||||
check_number(
|
||||
@ -358,7 +428,7 @@ fn ifs() {
|
||||
if a < b { b } else { a }
|
||||
}
|
||||
|
||||
const GOAL: u8 = max(max(1, max(10, 3)), 0-122);
|
||||
const GOAL: i32 = max(max(1, max(10, 3)), 0-122);
|
||||
"#,
|
||||
10,
|
||||
);
|
||||
@ -366,7 +436,7 @@ fn ifs() {
|
||||
check_number(
|
||||
r#"
|
||||
const fn max(a: &i32, b: &i32) -> &i32 {
|
||||
if a < b { b } else { a }
|
||||
if *a < *b { b } else { a }
|
||||
}
|
||||
|
||||
const GOAL: i32 = *max(max(&1, max(&10, &3)), &5);
|
||||
@ -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]
|
||||
fn recursion() {
|
||||
check_number(
|
||||
@ -464,6 +571,16 @@ fn tuples() {
|
||||
"#,
|
||||
20,
|
||||
);
|
||||
check_number(
|
||||
r#"
|
||||
const GOAL: u8 = {
|
||||
let mut a = (10, 20, 3, 15);
|
||||
a.1 = 2;
|
||||
a.0 + a.1 + a.2 + a.3
|
||||
};
|
||||
"#,
|
||||
30,
|
||||
);
|
||||
check_number(
|
||||
r#"
|
||||
struct TupleLike(i32, u8, i64, u16);
|
||||
@ -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]
|
||||
fn pattern_matching_ergonomics() {
|
||||
check_number(
|
||||
@ -539,12 +683,55 @@ fn let_else() {
|
||||
let Some(x) = x else { return 10 };
|
||||
2 * x
|
||||
}
|
||||
const GOAL: u8 = f(Some(1000)) + f(None);
|
||||
const GOAL: i32 = f(Some(1000)) + f(None);
|
||||
"#,
|
||||
2010,
|
||||
);
|
||||
}
|
||||
|
||||
#[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]
|
||||
fn options() {
|
||||
check_number(
|
||||
@ -572,7 +759,7 @@ fn options() {
|
||||
0
|
||||
}
|
||||
}
|
||||
const GOAL: u8 = f(Some(Some(10))) + f(Some(None)) + f(None);
|
||||
const GOAL: i32 = f(Some(Some(10))) + f(Some(None)) + f(None);
|
||||
"#,
|
||||
11,
|
||||
);
|
||||
@ -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]
|
||||
fn array_and_index() {
|
||||
check_number(
|
||||
@ -665,24 +890,24 @@ fn enums() {
|
||||
r#"
|
||||
enum E {
|
||||
F1 = 1,
|
||||
F2 = 2 * E::F1 as u8,
|
||||
F3 = 3 * E::F2 as u8,
|
||||
F2 = 2 * E::F1 as isize, // Rustc expects an isize here
|
||||
F3 = 3 * E::F2 as isize,
|
||||
}
|
||||
const GOAL: i32 = E::F3 as u8;
|
||||
const GOAL: u8 = E::F3 as u8;
|
||||
"#,
|
||||
6,
|
||||
);
|
||||
check_number(
|
||||
r#"
|
||||
enum E { F1 = 1, F2, }
|
||||
const GOAL: i32 = E::F2 as u8;
|
||||
const GOAL: u8 = E::F2 as u8;
|
||||
"#,
|
||||
2,
|
||||
);
|
||||
check_number(
|
||||
r#"
|
||||
enum E { F1, }
|
||||
const GOAL: i32 = E::F1 as u8;
|
||||
const GOAL: u8 = E::F1 as u8;
|
||||
"#,
|
||||
0,
|
||||
);
|
||||
@ -813,8 +1038,22 @@ fn exec_limits() {
|
||||
}
|
||||
sum
|
||||
}
|
||||
const GOAL: usize = f(10000);
|
||||
const GOAL: i32 = f(10000);
|
||||
"#,
|
||||
10000 * 10000,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn type_error() {
|
||||
let e = eval_goal(
|
||||
r#"
|
||||
const GOAL: u8 = {
|
||||
let x: u16 = 2;
|
||||
let y: (u8, u8) = x;
|
||||
y.0
|
||||
};
|
||||
"#,
|
||||
);
|
||||
assert!(matches!(e, Err(ConstEvalError::MirLowerError(MirLowerError::TypeMismatch(_)))));
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ use crate::{
|
||||
chalk_db,
|
||||
consteval::ConstEvalError,
|
||||
method_resolution::{InherentImpls, TraitImpls, TyFingerprint},
|
||||
mir::{MirBody, MirLowerError},
|
||||
mir::{BorrowckResult, MirBody, MirLowerError},
|
||||
Binders, CallableDefId, Const, FnDefId, GenericArg, ImplTraitId, InferenceResult, Interner,
|
||||
PolyFnSig, QuantifiedWhereClause, ReturnTypeImplTraits, Substitution, TraitRef, Ty, TyDefId,
|
||||
ValueTyDefId,
|
||||
@ -38,6 +38,9 @@ pub trait HirDatabase: DefDatabase + Upcast<dyn DefDatabase> {
|
||||
#[salsa::cycle(crate::mir::mir_body_recover)]
|
||||
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::cycle(crate::lower::ty_recover)]
|
||||
fn ty(&self, def: TyDefId) -> Binders<Ty>;
|
||||
|
@ -235,8 +235,8 @@ impl<'a> DeclValidator<'a> {
|
||||
let pats_replacements = body
|
||||
.pats
|
||||
.iter()
|
||||
.filter_map(|(id, pat)| match pat {
|
||||
Pat::Bind { name, .. } => Some((id, name)),
|
||||
.filter_map(|(pat_id, pat)| match pat {
|
||||
Pat::Bind { id, .. } => Some((pat_id, &body.bindings[*id].name)),
|
||||
_ => None,
|
||||
})
|
||||
.filter_map(|(id, bind_name)| {
|
||||
|
@ -146,8 +146,9 @@ impl<'a> PatCtxt<'a> {
|
||||
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 name = &self.body.bindings[id].name;
|
||||
match (bm, ty.kind(Interner)) {
|
||||
(BindingMode::Ref(_), TyKind::Ref(.., rty)) => ty = rty,
|
||||
(BindingMode::Ref(_), _) => {
|
||||
|
@ -531,6 +531,7 @@ fn render_const_scalar(
|
||||
hir_def::AdtId::UnionId(u) => write!(f, "{}", f.db.union_data(u).name),
|
||||
hir_def::AdtId::EnumId(_) => f.write_str("<enum-not-supported>"),
|
||||
},
|
||||
chalk_ir::TyKind::FnDef(..) => ty.hir_fmt(f),
|
||||
_ => f.write_str("<not-supported>"),
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ use hir_def::{
|
||||
body::Body,
|
||||
builtin_type::{BuiltinInt, BuiltinType, BuiltinUint},
|
||||
data::{ConstData, StaticData},
|
||||
expr::{BindingAnnotation, ExprId, ExprOrPatId, PatId},
|
||||
expr::{BindingAnnotation, BindingId, ExprId, ExprOrPatId, PatId},
|
||||
lang_item::{LangItem, LangItemTarget},
|
||||
layout::Integer,
|
||||
path::Path,
|
||||
@ -291,8 +291,10 @@ pub enum Adjust {
|
||||
/// 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
|
||||
/// 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)]
|
||||
pub struct OverloadedDeref(pub Mutability);
|
||||
pub struct OverloadedDeref(pub Option<Mutability>);
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum AutoBorrow {
|
||||
@ -352,7 +354,10 @@ pub struct InferenceResult {
|
||||
/// **Note**: When a pattern type is resolved it may still contain
|
||||
/// unresolved or missing subpatterns or subpatterns of mismatched types.
|
||||
pub type_of_pat: ArenaMap<PatId, Ty>,
|
||||
pub type_of_binding: ArenaMap<BindingId, 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>,
|
||||
/// Interned common types to return references to.
|
||||
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.
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct InferenceContext<'a> {
|
||||
@ -534,7 +547,13 @@ impl<'a> InferenceContext<'a> {
|
||||
for ty in result.type_of_pat.values_mut() {
|
||||
*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());
|
||||
}
|
||||
for mismatch in result.type_mismatches.values_mut() {
|
||||
@ -704,6 +723,10 @@ impl<'a> InferenceContext<'a> {
|
||||
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) {
|
||||
self.result.diagnostics.push(diagnostic);
|
||||
}
|
||||
|
@ -693,7 +693,7 @@ pub(super) fn auto_deref_adjust_steps(autoderef: &Autoderef<'_, '_>) -> Vec<Adju
|
||||
.iter()
|
||||
.map(|(kind, _source)| match kind {
|
||||
// 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,
|
||||
})
|
||||
.zip(targets)
|
||||
|
@ -242,8 +242,10 @@ impl<'a> InferenceContext<'a> {
|
||||
let iterable_ty = self.infer_expr(iterable, &Expectation::none());
|
||||
let into_iter_ty =
|
||||
self.resolve_associated_type(iterable_ty, self.resolve_into_iter_item());
|
||||
let pat_ty =
|
||||
self.resolve_associated_type(into_iter_ty, self.resolve_iterator_item());
|
||||
let pat_ty = self
|
||||
.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.with_breakable_ctx(BreakableKind::Loop, None, label, |this| {
|
||||
|
@ -5,7 +5,10 @@ use std::iter::repeat_with;
|
||||
use chalk_ir::Mutability;
|
||||
use hir_def::{
|
||||
body::Body,
|
||||
expr::{BindingAnnotation, Expr, ExprId, ExprOrPatId, Literal, Pat, PatId, RecordFieldPat},
|
||||
expr::{
|
||||
Binding, BindingAnnotation, BindingId, Expr, ExprId, ExprOrPatId, Literal, Pat, PatId,
|
||||
RecordFieldPat,
|
||||
},
|
||||
path::Path,
|
||||
};
|
||||
use hir_expand::name::Name;
|
||||
@ -248,8 +251,8 @@ impl<'a> InferenceContext<'a> {
|
||||
// FIXME update resolver for the surrounding expression
|
||||
self.infer_path(path, pat.into()).unwrap_or_else(|| self.err_ty())
|
||||
}
|
||||
Pat::Bind { mode, name: _, subpat } => {
|
||||
return self.infer_bind_pat(pat, *mode, default_bm, *subpat, &expected);
|
||||
Pat::Bind { id, subpat } => {
|
||||
return self.infer_bind_pat(pat, *id, default_bm, *subpat, &expected);
|
||||
}
|
||||
Pat::Slice { prefix, slice, suffix } => {
|
||||
self.infer_slice_pat(&expected, prefix, slice, suffix, default_bm)
|
||||
@ -320,11 +323,12 @@ impl<'a> InferenceContext<'a> {
|
||||
fn infer_bind_pat(
|
||||
&mut self,
|
||||
pat: PatId,
|
||||
mode: BindingAnnotation,
|
||||
binding: BindingId,
|
||||
default_bm: BindingMode,
|
||||
subpat: Option<PatId>,
|
||||
expected: &Ty,
|
||||
) -> Ty {
|
||||
let Binding { mode, .. } = self.body.bindings[binding];
|
||||
let mode = if mode == BindingAnnotation::Unannotated {
|
||||
default_bm
|
||||
} else {
|
||||
@ -344,7 +348,8 @@ impl<'a> InferenceContext<'a> {
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
@ -420,11 +425,14 @@ fn is_non_ref_pat(body: &hir_def::body::Body, pat: PatId) -> bool {
|
||||
Pat::Lit(expr) => {
|
||||
!matches!(body[*expr], Expr::Literal(Literal::String(..) | Literal::ByteString(..)))
|
||||
}
|
||||
Pat::Bind {
|
||||
mode: BindingAnnotation::Mutable | BindingAnnotation::Unannotated,
|
||||
subpat: Some(subpat),
|
||||
..
|
||||
} => is_non_ref_pat(body, *subpat),
|
||||
Pat::Bind { id, subpat: Some(subpat), .. }
|
||||
if matches!(
|
||||
body.bindings[*id].mode,
|
||||
BindingAnnotation::Mutable | BindingAnnotation::Unannotated
|
||||
) =>
|
||||
{
|
||||
is_non_ref_pat(body, *subpat)
|
||||
}
|
||||
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 {
|
||||
let mut res = false;
|
||||
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
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ impl<'a> InferenceContext<'a> {
|
||||
};
|
||||
|
||||
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()),
|
||||
None => {
|
||||
never!("uninferred pattern?");
|
||||
|
@ -65,17 +65,9 @@ fn eval_expr(ra_fixture: &str, minicore: &str) -> Result<Layout, LayoutError> {
|
||||
})
|
||||
.unwrap();
|
||||
let hir_body = db.body(adt_id.into());
|
||||
let pat = hir_body
|
||||
.pats
|
||||
.iter()
|
||||
.find(|x| match x.1 {
|
||||
hir_def::expr::Pat::Bind { name, .. } => name.to_smol_str() == "goal",
|
||||
_ => false,
|
||||
})
|
||||
.unwrap()
|
||||
.0;
|
||||
let b = hir_body.bindings.iter().find(|x| x.1.name.to_smol_str() == "goal").unwrap().0;
|
||||
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())
|
||||
}
|
||||
|
||||
|
@ -579,8 +579,8 @@ impl ReceiverAdjustments {
|
||||
ty = new_ty.clone();
|
||||
adjust.push(Adjustment {
|
||||
kind: Adjust::Deref(match kind {
|
||||
// FIXME should we know the mutability here?
|
||||
AutoderefKind::Overloaded => Some(OverloadedDeref(Mutability::Not)),
|
||||
// FIXME should we know the mutability here, when autoref is `None`?
|
||||
AutoderefKind::Overloaded => Some(OverloadedDeref(self.autoref)),
|
||||
AutoderefKind::Builtin => None,
|
||||
}),
|
||||
target: new_ty,
|
||||
|
@ -1,23 +1,27 @@
|
||||
//! MIR definitions and implementation
|
||||
|
||||
use std::iter;
|
||||
use std::{fmt::Display, iter};
|
||||
|
||||
use crate::{
|
||||
infer::PointerCast, Const, ConstScalar, InferenceResult, Interner, MemoryMap, Substitution, Ty,
|
||||
};
|
||||
use chalk_ir::Mutability;
|
||||
use hir_def::{
|
||||
expr::{Expr, Ordering},
|
||||
expr::{BindingId, Expr, ExprId, Ordering, PatId},
|
||||
DefWithBodyId, FieldId, UnionId, VariantId,
|
||||
};
|
||||
use la_arena::{Arena, Idx, RawIdx};
|
||||
use la_arena::{Arena, ArenaMap, Idx, RawIdx};
|
||||
|
||||
mod eval;
|
||||
mod lower;
|
||||
mod borrowck;
|
||||
mod pretty;
|
||||
|
||||
pub use borrowck::{borrowck_query, BorrowckResult, MutabilityReason};
|
||||
pub use eval::{interpret_mir, pad16, Evaluator, MirEvalError};
|
||||
pub use lower::{lower_to_mir, mir_body_query, mir_body_recover, MirLowerError};
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
use stdx::impl_from;
|
||||
|
||||
use super::consteval::{intern_const_scalar, try_const_usize};
|
||||
|
||||
@ -30,13 +34,7 @@ fn return_slot() -> LocalId {
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
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 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
|
||||
@ -85,6 +83,10 @@ impl Operand {
|
||||
fn from_bytes(data: Vec<u8>, ty: Ty) -> Self {
|
||||
Operand::from_concrete_const(data, MemoryMap::default(), ty)
|
||||
}
|
||||
|
||||
fn const_zst(ty: Ty) -> Operand {
|
||||
Self::from_bytes(vec![], ty)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
@ -181,6 +183,11 @@ impl SwitchTargets {
|
||||
iter::zip(&self.values, &self.targets).map(|(x, y)| (*x, *y))
|
||||
}
|
||||
|
||||
/// Returns a slice with all possible jump targets (including the fallback target).
|
||||
pub fn all_targets(&self) -> &[BasicBlockId] {
|
||||
&self.targets
|
||||
}
|
||||
|
||||
/// Finds the `BasicBlock` to which this `SwitchInt` will branch given the
|
||||
/// specific value. This cannot fail, as it'll return the `otherwise`
|
||||
/// branch if there's not a specific match for the value.
|
||||
@ -557,6 +564,30 @@ pub enum BinOp {
|
||||
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 {
|
||||
fn from(value: hir_def::expr::ArithOp) -> Self {
|
||||
match value {
|
||||
@ -758,7 +789,7 @@ pub enum Rvalue {
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum Statement {
|
||||
pub enum StatementKind {
|
||||
Assign(Place, Rvalue),
|
||||
//FakeRead(Box<(FakeReadCause, Place)>),
|
||||
//SetDiscriminant {
|
||||
@ -773,6 +804,17 @@ pub enum Statement {
|
||||
//Intrinsic(Box<NonDivergingIntrinsic>),
|
||||
Nop,
|
||||
}
|
||||
impl StatementKind {
|
||||
fn with_span(self, span: MirSpan) -> Statement {
|
||||
Statement { kind: self, span }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub struct Statement {
|
||||
pub kind: StatementKind,
|
||||
pub span: MirSpan,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, PartialEq, Eq)]
|
||||
pub struct BasicBlock {
|
||||
@ -803,10 +845,19 @@ pub struct MirBody {
|
||||
pub start_block: BasicBlockId,
|
||||
pub owner: DefWithBodyId,
|
||||
pub arg_count: usize,
|
||||
pub binding_locals: ArenaMap<BindingId, LocalId>,
|
||||
pub param_locals: Vec<LocalId>,
|
||||
}
|
||||
|
||||
impl MirBody {}
|
||||
|
||||
fn const_as_usize(c: &Const) -> usize {
|
||||
try_const_usize(c).unwrap() as usize
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub enum MirSpan {
|
||||
ExprId(ExprId),
|
||||
PatId(PatId),
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl_from!(ExprId, PatId for MirSpan);
|
||||
|
223
crates/hir-ty/src/mir/borrowck.rs
Normal file
223
crates/hir-ty/src/mir/borrowck.rs
Normal 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
|
||||
}
|
@ -29,7 +29,7 @@ use crate::{
|
||||
|
||||
use super::{
|
||||
const_as_usize, return_slot, AggregateKind, BinOp, CastKind, LocalId, MirBody, MirLowerError,
|
||||
Operand, Place, ProjectionElem, Rvalue, Statement, Terminator, UnOp,
|
||||
Operand, Place, ProjectionElem, Rvalue, StatementKind, Terminator, UnOp,
|
||||
};
|
||||
|
||||
pub struct Evaluator<'a> {
|
||||
@ -263,12 +263,14 @@ impl Evaluator<'_> {
|
||||
for proj in &p.projection {
|
||||
match proj {
|
||||
ProjectionElem::Deref => {
|
||||
match &ty.data(Interner).kind {
|
||||
TyKind::Ref(_, _, inner) => {
|
||||
ty = inner.clone();
|
||||
ty = match &ty.data(Interner).kind {
|
||||
TyKind::Raw(_, inner) | TyKind::Ref(_, _, inner) => 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())?);
|
||||
addr = Address::from_usize(x);
|
||||
}
|
||||
@ -395,7 +397,8 @@ impl Evaluator<'_> {
|
||||
.locals
|
||||
.iter()
|
||||
.map(|(id, x)| {
|
||||
let size = self.size_of_sized(&x.ty, &locals, "no unsized local")?;
|
||||
let size =
|
||||
self.size_of_sized(&x.ty, &locals, "no unsized local in extending stack")?;
|
||||
let my_ptr = stack_ptr;
|
||||
stack_ptr += size;
|
||||
Ok((id, Stack(my_ptr)))
|
||||
@ -425,16 +428,16 @@ impl Evaluator<'_> {
|
||||
return Err(MirEvalError::ExecutionLimitExceeded);
|
||||
}
|
||||
for statement in ¤t_block.statements {
|
||||
match statement {
|
||||
Statement::Assign(l, r) => {
|
||||
match &statement.kind {
|
||||
StatementKind::Assign(l, r) => {
|
||||
let addr = self.place_addr(l, &locals)?;
|
||||
let result = self.eval_rvalue(r, &locals)?.to_vec(&self)?;
|
||||
self.write_memory(addr, &result)?;
|
||||
}
|
||||
Statement::Deinit(_) => not_supported!("de-init statement"),
|
||||
Statement::StorageLive(_) => not_supported!("storage-live statement"),
|
||||
Statement::StorageDead(_) => not_supported!("storage-dead statement"),
|
||||
Statement::Nop => (),
|
||||
StatementKind::Deinit(_) => not_supported!("de-init statement"),
|
||||
StatementKind::StorageLive(_)
|
||||
| StatementKind::StorageDead(_)
|
||||
| StatementKind::Nop => (),
|
||||
}
|
||||
}
|
||||
let Some(terminator) = current_block.terminator.as_ref() else {
|
||||
@ -1121,7 +1124,12 @@ impl Evaluator<'_> {
|
||||
}
|
||||
|
||||
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> {
|
||||
|
File diff suppressed because it is too large
Load Diff
237
crates/hir-ty/src/mir/lower/as_place.rs
Normal file
237
crates/hir-ty/src/mir/lower/as_place.rs
Normal 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)))
|
||||
}
|
||||
}
|
348
crates/hir-ty/src/mir/pretty.rs
Normal file
348
crates/hir-ty/src/mir/pretty.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -258,6 +258,7 @@ fn test() {
|
||||
|
||||
#[test]
|
||||
fn coerce_autoderef_block() {
|
||||
// FIXME: We should know mutability in overloaded deref
|
||||
check_no_mismatches(
|
||||
r#"
|
||||
//- minicore: deref
|
||||
@ -267,7 +268,7 @@ fn takes_ref_str(x: &str) {}
|
||||
fn returns_string() -> String { loop {} }
|
||||
fn test() {
|
||||
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))
|
||||
}
|
||||
"#,
|
||||
);
|
||||
|
@ -1252,6 +1252,7 @@ fn foo<T: Trait>(a: &T) {
|
||||
|
||||
#[test]
|
||||
fn autoderef_visibility_field() {
|
||||
// FIXME: We should know mutability in overloaded deref
|
||||
check(
|
||||
r#"
|
||||
//- minicore: deref
|
||||
@ -1273,7 +1274,7 @@ mod a {
|
||||
mod b {
|
||||
fn foo() {
|
||||
let x = super::a::Bar::new().0;
|
||||
// ^^^^^^^^^^^^^^^^^^^^ adjustments: Deref(Some(OverloadedDeref(Not)))
|
||||
// ^^^^^^^^^^^^^^^^^^^^ adjustments: Deref(Some(OverloadedDeref(None)))
|
||||
// ^^^^^^^^^^^^^^^^^^^^^^ type: char
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ use hir_def::path::ModPath;
|
||||
use hir_expand::{name::Name, HirFileId, InFile};
|
||||
use syntax::{ast, AstPtr, SyntaxNodePtr, TextRange};
|
||||
|
||||
use crate::{AssocItem, Field, MacroKind, Type};
|
||||
use crate::{AssocItem, Field, Local, MacroKind, Type};
|
||||
|
||||
macro_rules! diagnostics {
|
||||
($($diag:ident,)*) => {
|
||||
@ -41,6 +41,7 @@ diagnostics![
|
||||
MissingFields,
|
||||
MissingMatchArms,
|
||||
MissingUnsafe,
|
||||
NeedMut,
|
||||
NoSuchField,
|
||||
PrivateAssocItem,
|
||||
PrivateField,
|
||||
@ -54,6 +55,7 @@ diagnostics![
|
||||
UnresolvedMethodCall,
|
||||
UnresolvedModule,
|
||||
UnresolvedProcMacro,
|
||||
UnusedMut,
|
||||
];
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -209,4 +211,15 @@ pub struct TypeMismatch {
|
||||
pub actual: Type,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct NeedMut {
|
||||
pub local: Local,
|
||||
pub span: InFile<SyntaxNodePtr>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UnusedMut {
|
||||
pub local: Local,
|
||||
}
|
||||
|
||||
pub use hir_ty::diagnostics::IncorrectCase;
|
||||
|
@ -4,7 +4,7 @@
|
||||
//! are splitting the hir.
|
||||
|
||||
use hir_def::{
|
||||
expr::{LabelId, PatId},
|
||||
expr::{BindingId, LabelId},
|
||||
AdtId, AssocItemId, DefWithBodyId, EnumVariantId, FieldId, GenericDefId, GenericParamId,
|
||||
ModuleDefId, VariantId,
|
||||
};
|
||||
@ -251,9 +251,9 @@ impl From<AssocItem> for GenericDefId {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<(DefWithBodyId, PatId)> for Local {
|
||||
fn from((parent, pat_id): (DefWithBodyId, PatId)) -> Self {
|
||||
Local { parent, pat_id }
|
||||
impl From<(DefWithBodyId, BindingId)> for Local {
|
||||
fn from((parent, binding_id): (DefWithBodyId, BindingId)) -> Self {
|
||||
Local { parent, binding_id }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,8 +10,9 @@ use hir_expand::InFile;
|
||||
use syntax::ast;
|
||||
|
||||
use crate::{
|
||||
db::HirDatabase, Adt, Const, Enum, Field, FieldSource, Function, Impl, LifetimeParam, Macro,
|
||||
Module, Static, Struct, Trait, TraitAlias, TypeAlias, TypeOrConstParam, Union, Variant,
|
||||
db::HirDatabase, Adt, Const, Enum, Field, FieldSource, Function, Impl, LifetimeParam,
|
||||
LocalSource, Macro, Module, Static, Struct, Trait, TraitAlias, TypeAlias, TypeOrConstParam,
|
||||
Union, Variant,
|
||||
};
|
||||
|
||||
pub trait HasSource {
|
||||
@ -178,3 +179,11 @@ impl HasSource for LifetimeParam {
|
||||
Some(child_source.map(|it| it[self.id.local_id].clone()))
|
||||
}
|
||||
}
|
||||
|
||||
impl HasSource for LocalSource {
|
||||
type Ast = Either<ast::IdentPat, ast::SelfParam>;
|
||||
|
||||
fn source(self, _: &dyn HirDatabase) -> Option<InFile<Self::Ast>> {
|
||||
Some(self.source)
|
||||
}
|
||||
}
|
||||
|
@ -41,7 +41,7 @@ use either::Either;
|
||||
use hir_def::{
|
||||
adt::VariantData,
|
||||
body::{BodyDiagnostic, SyntheticSyntax},
|
||||
expr::{BindingAnnotation, ExprOrPatId, LabelId, Pat, PatId},
|
||||
expr::{BindingAnnotation, BindingId, ExprOrPatId, LabelId, Pat},
|
||||
generics::{LifetimeParamData, TypeOrConstParamData, TypeParamProvenance},
|
||||
item_tree::ItemTreeNode,
|
||||
lang_item::{LangItem, LangItemTarget},
|
||||
@ -63,7 +63,7 @@ use hir_ty::{
|
||||
display::HexifiedConst,
|
||||
layout::layout_of_ty,
|
||||
method_resolution::{self, TyFingerprint},
|
||||
mir::interpret_mir,
|
||||
mir::{self, interpret_mir},
|
||||
primitive::UintTy,
|
||||
traits::FnTrait,
|
||||
AliasTy, CallableDefId, CallableSig, Canonical, CanonicalVarKinds, Cast, ClosureId,
|
||||
@ -77,7 +77,7 @@ use rustc_hash::FxHashSet;
|
||||
use stdx::{impl_from, never};
|
||||
use syntax::{
|
||||
ast::{self, HasAttrs as _, HasDocComments, HasName},
|
||||
AstNode, AstPtr, SmolStr, SyntaxNodePtr, TextRange, T,
|
||||
AstNode, AstPtr, SmolStr, SyntaxNode, SyntaxNodePtr, TextRange, T,
|
||||
};
|
||||
|
||||
use crate::db::{DefDatabase, HirDatabase};
|
||||
@ -87,10 +87,10 @@ pub use crate::{
|
||||
diagnostics::{
|
||||
AnyDiagnostic, BreakOutsideOfLoop, ExpectedFunction, InactiveCode, IncorrectCase,
|
||||
InvalidDeriveTarget, MacroError, MalformedDerive, MismatchedArgCount, MissingFields,
|
||||
MissingMatchArms, MissingUnsafe, NoSuchField, PrivateAssocItem, PrivateField,
|
||||
MissingMatchArms, MissingUnsafe, NeedMut, NoSuchField, PrivateAssocItem, PrivateField,
|
||||
ReplaceFilterMapNextWithFindMap, TypeMismatch, UnimplementedBuiltinMacro,
|
||||
UnresolvedExternCrate, UnresolvedField, UnresolvedImport, UnresolvedMacroCall,
|
||||
UnresolvedMethodCall, UnresolvedModule, UnresolvedProcMacro,
|
||||
UnresolvedMethodCall, UnresolvedModule, UnresolvedProcMacro, UnusedMut,
|
||||
},
|
||||
has_source::HasSource,
|
||||
semantics::{PathResolution, Semantics, SemanticsScope, TypeInfo, VisibleTraits},
|
||||
@ -1327,6 +1327,15 @@ impl DefWithBody {
|
||||
body.pretty_print(db.upcast(), self.id())
|
||||
}
|
||||
|
||||
/// A textual representation of the MIR of this def's body for debugging purposes.
|
||||
pub fn debug_mir(self, db: &dyn HirDatabase) -> String {
|
||||
let body = db.mir_body(self.id());
|
||||
match body {
|
||||
Ok(body) => body.pretty_print(db),
|
||||
Err(e) => format!("error:\n{e:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn diagnostics(self, db: &dyn HirDatabase, acc: &mut Vec<AnyDiagnostic>) {
|
||||
let krate = self.module(db).id.krate();
|
||||
|
||||
@ -1500,6 +1509,41 @@ impl DefWithBody {
|
||||
}
|
||||
}
|
||||
|
||||
let hir_body = db.body(self.into());
|
||||
|
||||
if let Ok(borrowck_result) = db.borrowck(self.into()) {
|
||||
let mir_body = &borrowck_result.mir_body;
|
||||
let mol = &borrowck_result.mutability_of_locals;
|
||||
for (binding_id, _) in hir_body.bindings.iter() {
|
||||
let need_mut = &mol[mir_body.binding_locals[binding_id]];
|
||||
let local = Local { parent: self.into(), binding_id };
|
||||
match (need_mut, local.is_mut(db)) {
|
||||
(mir::MutabilityReason::Mut { .. }, true)
|
||||
| (mir::MutabilityReason::Not, false) => (),
|
||||
(mir::MutabilityReason::Mut { spans }, false) => {
|
||||
for span in spans {
|
||||
let span: InFile<SyntaxNodePtr> = match span {
|
||||
mir::MirSpan::ExprId(e) => match source_map.expr_syntax(*e) {
|
||||
Ok(s) => s.map(|x| x.into()),
|
||||
Err(_) => continue,
|
||||
},
|
||||
mir::MirSpan::PatId(p) => match source_map.pat_syntax(*p) {
|
||||
Ok(s) => s.map(|x| match x {
|
||||
Either::Left(e) => e.into(),
|
||||
Either::Right(e) => e.into(),
|
||||
}),
|
||||
Err(_) => continue,
|
||||
},
|
||||
mir::MirSpan::Unknown => continue,
|
||||
};
|
||||
acc.push(NeedMut { local, span }.into());
|
||||
}
|
||||
}
|
||||
(mir::MutabilityReason::Not, true) => acc.push(UnusedMut { local }.into()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for diagnostic in BodyValidationDiagnostic::collect(db, self.into()) {
|
||||
match diagnostic {
|
||||
BodyValidationDiagnostic::RecordMissingFields {
|
||||
@ -1786,8 +1830,8 @@ impl Param {
|
||||
let parent = DefWithBodyId::FunctionId(self.func.into());
|
||||
let body = db.body(parent);
|
||||
let pat_id = body.params[self.idx];
|
||||
if let Pat::Bind { .. } = &body[pat_id] {
|
||||
Some(Local { parent, pat_id: body.params[self.idx] })
|
||||
if let Pat::Bind { id, .. } = &body[pat_id] {
|
||||
Some(Local { parent, binding_id: *id })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@ -2464,13 +2508,50 @@ impl GenericDef {
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct Local {
|
||||
pub(crate) parent: DefWithBodyId,
|
||||
pub(crate) pat_id: PatId,
|
||||
pub(crate) binding_id: BindingId,
|
||||
}
|
||||
|
||||
pub struct LocalSource {
|
||||
pub local: Local,
|
||||
pub source: InFile<Either<ast::IdentPat, ast::SelfParam>>,
|
||||
}
|
||||
|
||||
impl LocalSource {
|
||||
pub fn as_ident_pat(&self) -> Option<&ast::IdentPat> {
|
||||
match &self.source.value {
|
||||
Either::Left(x) => Some(x),
|
||||
Either::Right(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_ident_pat(self) -> Option<ast::IdentPat> {
|
||||
match self.source.value {
|
||||
Either::Left(x) => Some(x),
|
||||
Either::Right(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn original_file(&self, db: &dyn HirDatabase) -> FileId {
|
||||
self.source.file_id.original_file(db.upcast())
|
||||
}
|
||||
|
||||
pub fn name(&self) -> Option<ast::Name> {
|
||||
self.source.value.name()
|
||||
}
|
||||
|
||||
pub fn syntax(&self) -> &SyntaxNode {
|
||||
self.source.value.syntax()
|
||||
}
|
||||
|
||||
pub fn syntax_ptr(self) -> InFile<SyntaxNodePtr> {
|
||||
self.source.map(|x| SyntaxNodePtr::new(x.syntax()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Local {
|
||||
pub fn is_param(self, db: &dyn HirDatabase) -> bool {
|
||||
let src = self.source(db);
|
||||
match src.value {
|
||||
let src = self.primary_source(db);
|
||||
match src.source.value {
|
||||
Either::Left(pat) => pat
|
||||
.syntax()
|
||||
.ancestors()
|
||||
@ -2490,13 +2571,7 @@ impl Local {
|
||||
|
||||
pub fn name(self, db: &dyn HirDatabase) -> Name {
|
||||
let body = db.body(self.parent);
|
||||
match &body[self.pat_id] {
|
||||
Pat::Bind { name, .. } => name.clone(),
|
||||
_ => {
|
||||
stdx::never!("hir::Local is missing a name!");
|
||||
Name::missing()
|
||||
}
|
||||
}
|
||||
body[self.binding_id].name.clone()
|
||||
}
|
||||
|
||||
pub fn is_self(self, db: &dyn HirDatabase) -> bool {
|
||||
@ -2505,15 +2580,12 @@ impl Local {
|
||||
|
||||
pub fn is_mut(self, db: &dyn HirDatabase) -> bool {
|
||||
let body = db.body(self.parent);
|
||||
matches!(&body[self.pat_id], Pat::Bind { mode: BindingAnnotation::Mutable, .. })
|
||||
body[self.binding_id].mode == BindingAnnotation::Mutable
|
||||
}
|
||||
|
||||
pub fn is_ref(self, db: &dyn HirDatabase) -> bool {
|
||||
let body = db.body(self.parent);
|
||||
matches!(
|
||||
&body[self.pat_id],
|
||||
Pat::Bind { mode: BindingAnnotation::Ref | BindingAnnotation::RefMut, .. }
|
||||
)
|
||||
matches!(body[self.binding_id].mode, BindingAnnotation::Ref | BindingAnnotation::RefMut)
|
||||
}
|
||||
|
||||
pub fn parent(self, _db: &dyn HirDatabase) -> DefWithBody {
|
||||
@ -2527,34 +2599,33 @@ impl Local {
|
||||
pub fn ty(self, db: &dyn HirDatabase) -> Type {
|
||||
let def = self.parent;
|
||||
let infer = db.infer(def);
|
||||
let ty = infer[self.pat_id].clone();
|
||||
let ty = infer[self.binding_id].clone();
|
||||
Type::new(db, def, ty)
|
||||
}
|
||||
|
||||
pub fn associated_locals(self, db: &dyn HirDatabase) -> Box<[Local]> {
|
||||
let body = db.body(self.parent);
|
||||
body.ident_patterns_for(&self.pat_id)
|
||||
/// All definitions for this local. Example: `let (a$0, _) | (_, a$0) = x;`
|
||||
pub fn sources(self, db: &dyn HirDatabase) -> Vec<LocalSource> {
|
||||
let (body, source_map) = db.body_with_source_map(self.parent);
|
||||
body[self.binding_id]
|
||||
.definitions
|
||||
.iter()
|
||||
.map(|&pat_id| Local { parent: self.parent, pat_id })
|
||||
.map(|&definition| {
|
||||
let src = source_map.pat_syntax(definition).unwrap(); // Hmm...
|
||||
let root = src.file_syntax(db.upcast());
|
||||
src.map(|ast| match ast {
|
||||
// Suspicious unwrap
|
||||
Either::Left(it) => Either::Left(it.cast().unwrap().to_node(&root)),
|
||||
Either::Right(it) => Either::Right(it.to_node(&root)),
|
||||
})
|
||||
})
|
||||
.map(|source| LocalSource { local: self, source })
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// If this local is part of a multi-local, retrieve the representative local.
|
||||
/// That is the local that references are being resolved to.
|
||||
pub fn representative(self, db: &dyn HirDatabase) -> Local {
|
||||
let body = db.body(self.parent);
|
||||
Local { pat_id: body.pattern_representative(self.pat_id), ..self }
|
||||
}
|
||||
|
||||
pub fn source(self, db: &dyn HirDatabase) -> InFile<Either<ast::IdentPat, ast::SelfParam>> {
|
||||
let (_body, source_map) = db.body_with_source_map(self.parent);
|
||||
let src = source_map.pat_syntax(self.pat_id).unwrap(); // Hmm...
|
||||
let root = src.file_syntax(db.upcast());
|
||||
src.map(|ast| match ast {
|
||||
// Suspicious unwrap
|
||||
Either::Left(it) => Either::Left(it.cast().unwrap().to_node(&root)),
|
||||
Either::Right(it) => Either::Right(it.to_node(&root)),
|
||||
})
|
||||
/// The leftmost definition for this local. Example: `let (a$0, _) | (_, a) = x;`
|
||||
pub fn primary_source(self, db: &dyn HirDatabase) -> LocalSource {
|
||||
let all_sources = self.sources(db);
|
||||
all_sources.into_iter().next().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1103,7 +1103,10 @@ impl<'db> SemanticsImpl<'db> {
|
||||
let kind = match adjust.kind {
|
||||
hir_ty::Adjust::NeverToAny => Adjust::NeverToAny,
|
||||
hir_ty::Adjust::Deref(Some(hir_ty::OverloadedDeref(m))) => {
|
||||
Adjust::Deref(Some(OverloadedDeref(mutability(m))))
|
||||
// FIXME: Should we handle unknown mutability better?
|
||||
Adjust::Deref(Some(OverloadedDeref(
|
||||
m.map(mutability).unwrap_or(Mutability::Shared),
|
||||
)))
|
||||
}
|
||||
hir_ty::Adjust::Deref(None) => Adjust::Deref(None),
|
||||
hir_ty::Adjust::Borrow(hir_ty::AutoBorrow::RawPtr(m)) => {
|
||||
@ -1654,8 +1657,8 @@ impl<'a> SemanticsScope<'a> {
|
||||
resolver::ScopeDef::ImplSelfType(it) => ScopeDef::ImplSelfType(it.into()),
|
||||
resolver::ScopeDef::AdtSelfType(it) => ScopeDef::AdtSelfType(it.into()),
|
||||
resolver::ScopeDef::GenericParam(id) => ScopeDef::GenericParam(id.into()),
|
||||
resolver::ScopeDef::Local(pat_id) => match self.resolver.body_owner() {
|
||||
Some(parent) => ScopeDef::Local(Local { parent, pat_id }),
|
||||
resolver::ScopeDef::Local(binding_id) => match self.resolver.body_owner() {
|
||||
Some(parent) => ScopeDef::Local(Local { parent, binding_id }),
|
||||
None => continue,
|
||||
},
|
||||
resolver::ScopeDef::Label(label_id) => match self.resolver.body_owner() {
|
||||
|
@ -89,7 +89,7 @@ use base_db::FileId;
|
||||
use hir_def::{
|
||||
child_by_source::ChildBySource,
|
||||
dyn_map::DynMap,
|
||||
expr::{LabelId, PatId},
|
||||
expr::{BindingId, LabelId},
|
||||
keys::{self, Key},
|
||||
AdtId, ConstId, ConstParamId, DefWithBodyId, EnumId, EnumVariantId, FieldId, FunctionId,
|
||||
GenericDefId, GenericParamId, ImplId, LifetimeParamId, MacroId, ModuleId, StaticId, StructId,
|
||||
@ -98,7 +98,7 @@ use hir_def::{
|
||||
use hir_expand::{attrs::AttrId, name::AsName, HirFileId, MacroCallId};
|
||||
use rustc_hash::FxHashMap;
|
||||
use smallvec::SmallVec;
|
||||
use stdx::impl_from;
|
||||
use stdx::{impl_from, never};
|
||||
use syntax::{
|
||||
ast::{self, HasName},
|
||||
AstNode, SyntaxNode,
|
||||
@ -216,14 +216,14 @@ impl SourceToDefCtx<'_, '_> {
|
||||
pub(super) fn bind_pat_to_def(
|
||||
&mut self,
|
||||
src: InFile<ast::IdentPat>,
|
||||
) -> Option<(DefWithBodyId, PatId)> {
|
||||
) -> Option<(DefWithBodyId, BindingId)> {
|
||||
let container = self.find_pat_or_label_container(src.syntax())?;
|
||||
let (body, source_map) = self.db.body_with_source_map(container);
|
||||
let src = src.map(ast::Pat::from);
|
||||
let pat_id = source_map.node_pat(src.as_ref())?;
|
||||
// the pattern could resolve to a constant, verify that that is not the case
|
||||
if let crate::Pat::Bind { .. } = body[pat_id] {
|
||||
Some((container, pat_id))
|
||||
if let crate::Pat::Bind { id, .. } = body[pat_id] {
|
||||
Some((container, id))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@ -231,11 +231,16 @@ impl SourceToDefCtx<'_, '_> {
|
||||
pub(super) fn self_param_to_def(
|
||||
&mut self,
|
||||
src: InFile<ast::SelfParam>,
|
||||
) -> Option<(DefWithBodyId, PatId)> {
|
||||
) -> Option<(DefWithBodyId, BindingId)> {
|
||||
let container = self.find_pat_or_label_container(src.syntax())?;
|
||||
let (_body, source_map) = self.db.body_with_source_map(container);
|
||||
let (body, source_map) = self.db.body_with_source_map(container);
|
||||
let pat_id = source_map.node_self_param(src.as_ref())?;
|
||||
Some((container, pat_id))
|
||||
if let crate::Pat::Bind { id, .. } = body[pat_id] {
|
||||
Some((container, id))
|
||||
} else {
|
||||
never!();
|
||||
None
|
||||
}
|
||||
}
|
||||
pub(super) fn label_to_def(
|
||||
&mut self,
|
||||
|
@ -422,8 +422,8 @@ impl SourceAnalyzer {
|
||||
// Shorthand syntax, resolve to the local
|
||||
let path = ModPath::from_segments(PathKind::Plain, once(local_name.clone()));
|
||||
match self.resolver.resolve_path_in_value_ns_fully(db.upcast(), &path) {
|
||||
Some(ValueNs::LocalBinding(pat_id)) => {
|
||||
Some(Local { pat_id, parent: self.resolver.body_owner()? })
|
||||
Some(ValueNs::LocalBinding(binding_id)) => {
|
||||
Some(Local { binding_id, parent: self.resolver.body_owner()? })
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
@ -1018,8 +1018,8 @@ fn resolve_hir_path_(
|
||||
let values = || {
|
||||
resolver.resolve_path_in_value_ns_fully(db.upcast(), path.mod_path()).and_then(|val| {
|
||||
let res = match val {
|
||||
ValueNs::LocalBinding(pat_id) => {
|
||||
let var = Local { parent: body_owner?, pat_id };
|
||||
ValueNs::LocalBinding(binding_id) => {
|
||||
let var = Local { parent: body_owner?, binding_id };
|
||||
PathResolution::Local(var)
|
||||
}
|
||||
ValueNs::FunctionId(it) => PathResolution::Def(Function::from(it).into()),
|
||||
|
@ -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)?;
|
||||
match NameRefClass::classify(&ctx.sema, &name_ref)? {
|
||||
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()?)
|
||||
}
|
||||
_ => None,
|
||||
|
@ -3,7 +3,8 @@ use std::iter;
|
||||
use ast::make;
|
||||
use either::Either;
|
||||
use hir::{
|
||||
HasSource, HirDisplay, InFile, Local, ModuleDef, PathResolution, Semantics, TypeInfo, TypeParam,
|
||||
HasSource, HirDisplay, InFile, Local, LocalSource, ModuleDef, PathResolution, Semantics,
|
||||
TypeInfo, TypeParam,
|
||||
};
|
||||
use ide_db::{
|
||||
defs::{Definition, NameRefClass},
|
||||
@ -710,7 +711,7 @@ impl FunctionBody {
|
||||
) => local_ref,
|
||||
_ => 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
|
||||
if !file_id.is_macro() {
|
||||
match value {
|
||||
@ -972,11 +973,11 @@ impl FunctionBody {
|
||||
locals: impl Iterator<Item = Local>,
|
||||
) -> Vec<Param> {
|
||||
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_map(|(local, src)| match src.value {
|
||||
Either::Left(src) => Some((local, src)),
|
||||
Either::Right(_) => {
|
||||
.filter_map(|(local, src)| match src.into_ident_pat() {
|
||||
Some(src) => Some((local, src)),
|
||||
None => {
|
||||
stdx::never!(false, "Local::is_self returned false, but source is SelfParam");
|
||||
None
|
||||
}
|
||||
@ -1238,17 +1239,9 @@ fn local_outlives_body(
|
||||
fn is_defined_outside_of_body(
|
||||
ctx: &AssistContext<'_>,
|
||||
body: &FunctionBody,
|
||||
src: &hir::InFile<Either<ast::IdentPat, ast::SelfParam>>,
|
||||
src: &LocalSource,
|
||||
) -> bool {
|
||||
src.file_id.original_file(ctx.db()) == ctx.file_id()
|
||||
&& !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(),
|
||||
}
|
||||
src.original_file(ctx.db()) == ctx.file_id() && !body.contains_node(src.syntax())
|
||||
}
|
||||
|
||||
/// find where to put extracted function definition
|
||||
|
@ -1,4 +1,3 @@
|
||||
use either::Either;
|
||||
use hir::{PathResolution, Semantics};
|
||||
use ide_db::{
|
||||
base_db::FileId,
|
||||
@ -205,12 +204,14 @@ fn inline_usage(
|
||||
return None;
|
||||
}
|
||||
|
||||
// FIXME: Handle multiple local definitions
|
||||
let bind_pat = match local.source(sema.db).value {
|
||||
Either::Left(ident) => ident,
|
||||
_ => return None,
|
||||
let sources = local.sources(sema.db);
|
||||
let [source] = sources.as_slice() else {
|
||||
// Not applicable with locals with multiple definitions (i.e. or patterns)
|
||||
return None;
|
||||
};
|
||||
|
||||
let bind_pat = source.as_ident_pat()?;
|
||||
|
||||
let let_stmt = ast::LetStmt::cast(bind_pat.syntax().parent()?)?;
|
||||
|
||||
let UsageSearchResult { mut references } = Definition::Local(local).usages(sema).all();
|
||||
|
@ -121,14 +121,7 @@ impl Definition {
|
||||
Definition::Trait(it) => name_range(it, sema),
|
||||
Definition::TraitAlias(it) => name_range(it, sema),
|
||||
Definition::TypeAlias(it) => name_range(it, sema),
|
||||
Definition::Local(local) => {
|
||||
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::Local(it) => name_range(it.primary_source(sema.db), sema),
|
||||
Definition::GenericParam(generic_param) => match generic_param {
|
||||
hir::GenericParam::LifetimeParam(lifetime_param) => {
|
||||
let src = lifetime_param.source(sema.db)?;
|
||||
@ -302,13 +295,7 @@ fn rename_reference(
|
||||
source_change.insert_source_edit(file_id, edit);
|
||||
Ok(())
|
||||
};
|
||||
match 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),
|
||||
}?;
|
||||
insert_def_edit(def)?;
|
||||
Ok(source_change)
|
||||
}
|
||||
|
||||
@ -471,59 +458,64 @@ fn source_edit_from_def(
|
||||
def: Definition,
|
||||
new_name: &str,
|
||||
) -> 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();
|
||||
if let Definition::Local(local) = def {
|
||||
if let Either::Left(pat) = local.source(sema.db).value {
|
||||
// special cases required for renaming fields/locals in Record patterns
|
||||
if let Some(pat_field) = pat.syntax().parent().and_then(ast::RecordPatField::cast) {
|
||||
let mut file_id = None;
|
||||
for source in local.sources(sema.db) {
|
||||
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();
|
||||
if let Some(name_ref) = pat_field.name_ref() {
|
||||
if new_name == name_ref.text() && pat.at_token().is_none() {
|
||||
// Foo { field: ref mut local } -> Foo { ref mut field }
|
||||
// ^^^^^^ delete this
|
||||
// ^^^^^ replace this with `field`
|
||||
cov_mark::hit!(test_rename_local_put_init_shorthand_pat);
|
||||
edit.delete(
|
||||
name_ref
|
||||
.syntax()
|
||||
.text_range()
|
||||
.cover_offset(pat.syntax().text_range().start()),
|
||||
);
|
||||
edit.replace(name_range, name_ref.text().to_string());
|
||||
// special cases required for renaming fields/locals in Record patterns
|
||||
if let Some(pat_field) = pat.syntax().parent().and_then(ast::RecordPatField::cast) {
|
||||
if let Some(name_ref) = pat_field.name_ref() {
|
||||
if new_name == name_ref.text() && pat.at_token().is_none() {
|
||||
// Foo { field: ref mut local } -> Foo { ref mut field }
|
||||
// ^^^^^^ delete this
|
||||
// ^^^^^ replace this with `field`
|
||||
cov_mark::hit!(test_rename_local_put_init_shorthand_pat);
|
||||
edit.delete(
|
||||
name_ref
|
||||
.syntax()
|
||||
.text_range()
|
||||
.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 {
|
||||
// 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`
|
||||
// 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());
|
||||
}
|
||||
} 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
let Some(file_id) = file_id else { bail!("No file available to rename") };
|
||||
return Ok((file_id, edit.finish()));
|
||||
}
|
||||
if edit.is_empty() {
|
||||
let (range, new_name) = match def {
|
||||
Definition::GenericParam(hir::GenericParam::LifetimeParam(_))
|
||||
| Definition::Label(_) => (
|
||||
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()),
|
||||
};
|
||||
edit.replace(range, new_name);
|
||||
}
|
||||
let FileRange { file_id, range } = def
|
||||
.range_for_rename(sema)
|
||||
.ok_or_else(|| format_err!("No identifier available to rename"))?;
|
||||
let (range, new_name) = match def {
|
||||
Definition::GenericParam(hir::GenericParam::LifetimeParam(_)) | Definition::Label(_) => (
|
||||
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()),
|
||||
};
|
||||
edit.replace(range, new_name);
|
||||
Ok((file_id, edit.finish()))
|
||||
}
|
||||
|
||||
|
@ -320,7 +320,7 @@ impl Definition {
|
||||
scope: None,
|
||||
include_self_kw_refs: None,
|
||||
local_repr: match self {
|
||||
Definition::Local(local) => Some(local.representative(sema.db)),
|
||||
Definition::Local(local) => Some(local),
|
||||
_ => None,
|
||||
},
|
||||
search_self_mod: false,
|
||||
@ -646,7 +646,7 @@ impl<'a> FindUsages<'a> {
|
||||
match NameRefClass::classify(self.sema, name_ref) {
|
||||
Some(NameRefClass::Definition(def @ Definition::Local(local)))
|
||||
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());
|
||||
@ -707,7 +707,7 @@ impl<'a> FindUsages<'a> {
|
||||
Definition::Field(_) if field == self.def => {
|
||||
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)
|
||||
}
|
||||
_ => return false,
|
||||
@ -755,7 +755,7 @@ impl<'a> FindUsages<'a> {
|
||||
Some(NameClass::Definition(def @ Definition::Local(local))) if def != self.def => {
|
||||
if matches!(
|
||||
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 reference = FileReference {
|
||||
|
592
crates/ide-diagnostics/src/handlers/mutability_errors.rs
Normal file
592
crates/ide-diagnostics/src/handlers/mutability_errors.rs
Normal 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);
|
||||
}
|
||||
"#,
|
||||
);
|
||||
}
|
||||
}
|
@ -37,6 +37,7 @@ mod handlers {
|
||||
pub(crate) mod missing_fields;
|
||||
pub(crate) mod missing_match_arms;
|
||||
pub(crate) mod missing_unsafe;
|
||||
pub(crate) mod mutability_errors;
|
||||
pub(crate) mod no_such_field;
|
||||
pub(crate) mod private_assoc_item;
|
||||
pub(crate) mod private_field;
|
||||
@ -273,7 +274,8 @@ pub fn diagnostics(
|
||||
AnyDiagnostic::InvalidDeriveTarget(d) => handlers::invalid_derive_target::invalid_derive_target(&ctx, &d),
|
||||
AnyDiagnostic::UnresolvedField(d) => handlers::unresolved_field::unresolved_field(&ctx, &d),
|
||||
AnyDiagnostic::UnresolvedMethodCall(d) => handlers::unresolved_method::unresolved_method(&ctx, &d),
|
||||
|
||||
AnyDiagnostic::NeedMut(d) => handlers::mutability_errors::need_mut(&ctx, &d),
|
||||
AnyDiagnostic::UnusedMut(d) => handlers::mutability_errors::unused_mut(&ctx, &d),
|
||||
AnyDiagnostic::InactiveCode(d) => match handlers::inactive_code::inactive_code(&ctx, &d) {
|
||||
Some(it) => it,
|
||||
None => continue,
|
||||
|
@ -14,7 +14,7 @@ use syntax::{
|
||||
SyntaxNode, SyntaxToken, TextRange, T,
|
||||
};
|
||||
|
||||
use crate::{references, NavigationTarget, TryToNav};
|
||||
use crate::{navigation_target::ToNav, references, NavigationTarget, TryToNav};
|
||||
|
||||
#[derive(PartialEq, Eq, Hash)]
|
||||
pub struct HighlightedRange {
|
||||
@ -98,32 +98,39 @@ fn highlight_references(
|
||||
category: access,
|
||||
});
|
||||
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 {
|
||||
match def {
|
||||
Definition::Local(local) => local
|
||||
.associated_locals(sema.db)
|
||||
.iter()
|
||||
.for_each(|&local| def_to_hl_range(Definition::Local(local))),
|
||||
def => def_to_hl_range(def),
|
||||
Definition::Local(local) => {
|
||||
let category = local.is_mut(sema.db).then_some(ReferenceCategory::Write);
|
||||
local
|
||||
.sources(sema.db)
|
||||
.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -635,8 +635,8 @@ fn local(db: &RootDatabase, it: hir::Local) -> Option<Markup> {
|
||||
let ty = it.ty(db);
|
||||
let ty = ty.display_truncated(db, None);
|
||||
let is_mut = if it.is_mut(db) { "mut " } else { "" };
|
||||
let desc = match it.source(db).value {
|
||||
Either::Left(ident) => {
|
||||
let desc = match it.primary_source(db).into_ident_pat() {
|
||||
Some(ident) => {
|
||||
let name = it.name(db);
|
||||
let let_kw = if ident
|
||||
.syntax()
|
||||
@ -649,7 +649,7 @@ fn local(db: &RootDatabase, it: hir::Local) -> Option<Markup> {
|
||||
};
|
||||
format!("{let_kw}{is_mut}{name}: {ty}")
|
||||
}
|
||||
Either::Right(_) => format!("{is_mut}self: {ty}"),
|
||||
None => format!("{is_mut}self: {ty}"),
|
||||
};
|
||||
markup(None, desc, None)
|
||||
}
|
||||
|
@ -55,6 +55,7 @@ mod syntax_tree;
|
||||
mod typing;
|
||||
mod view_crate_graph;
|
||||
mod view_hir;
|
||||
mod view_mir;
|
||||
mod view_item_tree;
|
||||
mod shuffle_crate_graph;
|
||||
|
||||
@ -308,6 +309,10 @@ impl Analysis {
|
||||
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> {
|
||||
self.with_db(|db| view_item_tree::view_item_tree(db, file_id))
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ use std::fmt;
|
||||
use either::Either;
|
||||
use hir::{
|
||||
symbols::FileSymbol, AssocItem, Documentation, FieldSource, HasAttrs, HasSource, HirDisplay,
|
||||
InFile, ModuleSource, Semantics,
|
||||
InFile, LocalSource, ModuleSource, Semantics,
|
||||
};
|
||||
use ide_db::{
|
||||
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 {
|
||||
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 {
|
||||
Either::Left(bind_pat) => (bind_pat.syntax(), bind_pat.name()),
|
||||
Either::Right(it) => (it.syntax(), it.name()),
|
||||
@ -398,10 +400,10 @@ impl ToNav for hir::Local {
|
||||
let FileRange { file_id, range: full_range } =
|
||||
InFile::new(file_id, node).original_file_range(db);
|
||||
|
||||
let name = self.name(db).to_smol_str();
|
||||
let kind = if self.is_self(db) {
|
||||
let name = local.name(db).to_smol_str();
|
||||
let kind = if local.is_self(db) {
|
||||
SymbolKind::SelfParam
|
||||
} else if self.is_param(db) {
|
||||
} else if local.is_param(db) {
|
||||
SymbolKind::ValueParam
|
||||
} else {
|
||||
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 {
|
||||
fn to_nav(&self, db: &RootDatabase) -> NavigationTarget {
|
||||
let InFile { file_id, value } = self.source(db);
|
||||
|
@ -353,6 +353,11 @@ mod tests {
|
||||
fn check(new_name: &str, ra_fixture_before: &str, ra_fixture_after: &str) {
|
||||
let ra_fixture_after = &trim_indent(ra_fixture_after);
|
||||
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
|
||||
.rename(position, new_name)
|
||||
.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]
|
||||
fn test_rename_local_put_init_shorthand() {
|
||||
cov_mark::check!(test_rename_local_put_init_shorthand);
|
||||
|
29
crates/ide/src/view_mir.rs
Normal file
29
crates/ide/src/view_mir.rs
Normal 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))
|
||||
}
|
@ -134,6 +134,16 @@ pub(crate) fn handle_view_hir(
|
||||
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(
|
||||
snap: GlobalStateSnapshot,
|
||||
params: lsp_types::TextDocumentIdentifier,
|
||||
|
@ -74,6 +74,14 @@ impl Request for 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 {}
|
||||
|
||||
impl Request for ViewFileText {
|
||||
|
@ -634,6 +634,7 @@ impl GlobalState {
|
||||
.on::<lsp_ext::AnalyzerStatus>(handlers::handle_analyzer_status)
|
||||
.on::<lsp_ext::SyntaxTree>(handlers::handle_syntax_tree)
|
||||
.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::ViewCrateGraph>(handlers::handle_view_crate_graph)
|
||||
.on::<lsp_ext::ViewItemTree>(handlers::handle_view_item_tree)
|
||||
|
@ -134,3 +134,5 @@ impl Iterator for AttrDocCommentIter {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<A: HasName, B: HasName> HasName for Either<A, B> {}
|
||||
|
@ -762,6 +762,20 @@ pub mod iter {
|
||||
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;
|
||||
}
|
||||
|
@ -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
|
||||
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.
|
||||
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
|
||||
|
||||
**Method:** `rust-analyzer/viewFileText`
|
||||
|
@ -114,6 +114,11 @@
|
||||
"title": "View Hir",
|
||||
"category": "rust-analyzer (debug command)"
|
||||
},
|
||||
{
|
||||
"command": "rust-analyzer.viewMir",
|
||||
"title": "View Mir",
|
||||
"category": "rust-analyzer (debug command)"
|
||||
},
|
||||
{
|
||||
"command": "rust-analyzer.viewFileText",
|
||||
"title": "View File Text (as seen by the server)",
|
||||
|
@ -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
|
||||
//
|
||||
// The contents of the file come from the `TextDocumentContentProvider`
|
||||
export function viewHir(ctx: CtxInit): Cmd {
|
||||
function viewHirOrMir(ctx: CtxInit, xir: "hir" | "mir"): Cmd {
|
||||
const viewXir = xir === "hir" ? "viewHir" : "viewMir";
|
||||
const requestType = xir === "hir" ? ra.viewHir : ra.viewMir;
|
||||
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>();
|
||||
constructor() {
|
||||
vscode.workspace.onDidChangeTextDocument(
|
||||
@ -452,7 +451,7 @@ export function viewHir(ctx: CtxInit): Cmd {
|
||||
),
|
||||
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> {
|
||||
@ -461,7 +460,7 @@ export function viewHir(ctx: CtxInit): Cmd {
|
||||
})();
|
||||
|
||||
ctx.pushExtCleanup(
|
||||
vscode.workspace.registerTextDocumentContentProvider("rust-analyzer-hir", tdcp)
|
||||
vscode.workspace.registerTextDocumentContentProvider(`rust-analyzer-${xir}`, tdcp)
|
||||
);
|
||||
|
||||
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 {
|
||||
const tdcp = new (class implements vscode.TextDocumentContentProvider {
|
||||
readonly uri = vscode.Uri.parse("rust-analyzer-file-text://viewFileText/file.rs");
|
||||
|
@ -59,6 +59,9 @@ export const viewFileText = new lc.RequestType<lc.TextDocumentIdentifier, string
|
||||
export const viewHir = new lc.RequestType<lc.TextDocumentPositionParams, string, void>(
|
||||
"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>(
|
||||
"rust-analyzer/viewItemTree"
|
||||
);
|
||||
|
@ -158,6 +158,7 @@ function createCommands(): Record<string, CommandFactory> {
|
||||
parentModule: { enabled: commands.parentModule },
|
||||
syntaxTree: { enabled: commands.syntaxTree },
|
||||
viewHir: { enabled: commands.viewHir },
|
||||
viewMir: { enabled: commands.viewMir },
|
||||
viewFileText: { enabled: commands.viewFileText },
|
||||
viewItemTree: { enabled: commands.viewItemTree },
|
||||
viewCrateGraph: { enabled: commands.viewCrateGraph },
|
||||
|
@ -94,6 +94,12 @@ impl<T, V> ArenaMap<Idx<T>, V> {
|
||||
.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.
|
||||
pub fn entry(&mut self, idx: Idx<T>) -> Entry<'_, Idx<T>, V> {
|
||||
let idx = Self::to_idx(idx);
|
||||
|
Loading…
Reference in New Issue
Block a user