diff options
| author | bors <bors@rust-lang.org> | 2023-03-07 09:49:49 +0000 |
|---|---|---|
| committer | bors <bors@rust-lang.org> | 2023-03-07 09:49:49 +0000 |
| commit | 44ff3c407a9db70f0c8f02b04d73ab1f883f37fe (patch) | |
| tree | 7448ecc21d6428cd3f4bbf07859a5a44ad8703b2 | |
| parent | 31c12ec282de5e2d4e835f320f8858277b3ba133 (diff) | |
| parent | bcd7ecb242cb546c83a40c1a3341543f1ed71064 (diff) | |
| download | rust-44ff3c407a9db70f0c8f02b04d73ab1f883f37fe.tar.gz rust-44ff3c407a9db70f0c8f02b04d73ab1f883f37fe.zip | |
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
57 files changed, 3164 insertions, 760 deletions
diff --git a/crates/hir-def/src/body.rs b/crates/hir-def/src/body.rs index 8fd9255b8b1..545d2bebf5f 100644 --- a/crates/hir-def/src/body.rs +++ b/crates/hir-def/src/body.rs @@ -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 { diff --git a/crates/hir-def/src/body/lower.rs b/crates/hir-def/src/body/lower.rs index 3164a5f4c29..b7458bfb8a7 100644 --- a/crates/hir-def/src/body/lower.rs +++ b/crates/hir-def/src/body/lower.rs @@ -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 { diff --git a/crates/hir-def/src/body/pretty.rs b/crates/hir-def/src/body/pretty.rs index 622756ee8a9..f8b159797e4 100644 --- a/crates/hir-def/src/body/pretty.rs +++ b/crates/hir-def/src/body/pretty.rs @@ -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); + } } diff --git a/crates/hir-def/src/body/scope.rs b/crates/hir-def/src/body/scope.rs index cab657b807e..12fc1f116d7 100644 --- a/crates/hir-def/src/body/scope.rs +++ b/crates/hir-def/src/body/scope.rs @@ -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()), diff --git a/crates/hir-def/src/expr.rs b/crates/hir-def/src/expr.rs index 78a2f861233..bbea608c55e 100644 --- a/crates/hir-def/src/expr.rs +++ b/crates/hir-def/src/expr.rs @@ -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)) @@ -434,6 +437,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, pub pat: PatId, @@ -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 }, diff --git a/crates/hir-def/src/resolver.rs b/crates/hir-def/src/resolver.rs index eea837ddd23..61e64fc1036 100644 --- a/crates/hir-def/src/resolver.rs +++ b/crates/hir-def/src/resolver.rs @@ -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)) } } diff --git a/crates/hir-ty/src/consteval/tests.rs b/crates/hir-ty/src/consteval/tests.rs index 19145b2d98e..f2e42d6e503 100644 --- a/crates/hir-ty/src/consteval/tests.rs +++ b/crates/hir-ty/src/consteval/tests.rs @@ -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] @@ -133,6 +149,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( r#" @@ -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); @@ -400,6 +470,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( r#" @@ -466,6 +573,16 @@ fn tuples() { ); 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); const GOAL: u8 = { let a = TupleLike(10, 20, 3, 15); @@ -493,6 +610,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( r#" @@ -539,13 +683,56 @@ 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( r#" @@ -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, ); @@ -599,6 +786,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( r#" @@ -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(_))))); +} diff --git a/crates/hir-ty/src/db.rs b/crates/hir-ty/src/db.rs index 60e51b65f6b..304c78767f1 100644 --- a/crates/hir-ty/src/db.rs +++ b/crates/hir-ty/src/db.rs @@ -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>; diff --git a/crates/hir-ty/src/diagnostics/decl_check.rs b/crates/hir-ty/src/diagnostics/decl_check.rs index f4d1013ceb0..d36b93e3bdd 100644 --- a/crates/hir-ty/src/diagnostics/decl_check.rs +++ b/crates/hir-ty/src/diagnostics/decl_check.rs @@ -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)| { diff --git a/crates/hir-ty/src/diagnostics/match_check.rs b/crates/hir-ty/src/diagnostics/match_check.rs index 8b0f051b46b..859a37804ae 100644 --- a/crates/hir-ty/src/diagnostics/match_check.rs +++ b/crates/hir-ty/src/diagnostics/match_check.rs @@ -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(_), _) => { diff --git a/crates/hir-ty/src/display.rs b/crates/hir-ty/src/display.rs index 6dde4773602..bd3eccfe43d 100644 --- a/crates/hir-ty/src/display.rs +++ b/crates/hir-ty/src/display.rs @@ -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>"), } } diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs index 869b39ab37d..3a75f871211 100644 --- a/crates/hir-ty/src/infer.rs +++ b/crates/hir-ty/src/infer.rs @@ -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); } diff --git a/crates/hir-ty/src/infer/coerce.rs b/crates/hir-ty/src/infer/coerce.rs index 8bce47d71cb..48c91530266 100644 --- a/crates/hir-ty/src/infer/coerce.rs +++ b/crates/hir-ty/src/infer/coerce.rs @@ -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) diff --git a/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs index cca84488c94..535189ff028 100644 --- a/crates/hir-ty/src/infer/expr.rs +++ b/crates/hir-ty/src/infer/expr.rs @@ -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| { diff --git a/crates/hir-ty/src/infer/pat.rs b/crates/hir-ty/src/infer/pat.rs index a7bd009e34b..0f49e837881 100644 --- a/crates/hir-ty/src/infer/pat.rs +++ b/crates/hir-ty/src/infer/pat.rs @@ -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 } diff --git a/crates/hir-ty/src/infer/path.rs b/crates/hir-ty/src/infer/path.rs index 891e1fab2ed..2267fedaa8e 100644 --- a/crates/hir-ty/src/infer/path.rs +++ b/crates/hir-ty/src/infer/path.rs @@ -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?"); diff --git a/crates/hir-ty/src/layout/tests.rs b/crates/hir-ty/src/layout/tests.rs index 546044fc13a..a8971fde3c2 100644 --- a/crates/hir-ty/src/layout/tests.rs +++ b/crates/hir-ty/src/layout/tests.rs @@ -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()) } diff --git a/crates/hir-ty/src/method_resolution.rs b/crates/hir-ty/src/method_resolution.rs index 8dd34bc3882..92a17fc3a99 100644 --- a/crates/hir-ty/src/method_resolution.rs +++ b/crates/hir-ty/src/method_resolution.rs @@ -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, diff --git a/crates/hir-ty/src/mir.rs b/crates/hir-ty/src/mir.rs index 140caad5456..7c1cbbdf53d 100644 --- a/crates/hir-ty/src/mir.rs +++ b/crates/hir-ty/src/mir.rs @@ -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); diff --git a/crates/hir-ty/src/mir/borrowck.rs b/crates/hir-ty/src/mir/borrowck.rs new file mode 100644 index 00000000000..c8729af86a9 --- /dev/null +++ b/crates/hir-ty/src/mir/borrowck.rs @@ -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 +} diff --git a/crates/hir-ty/src/mir/eval.rs b/crates/hir-ty/src/mir/eval.rs index 1ec32010a19..c5d843d9ebd 100644 --- a/crates/hir-ty/src/mir/eval.rs +++ b/crates/hir-ty/src/mir/eval.rs @@ -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> { diff --git a/crates/hir-ty/src/mir/lower.rs b/crates/hir-ty/src/mir/lower.rs index 936b56a0217..afa5275ac6b 100644 --- a/crates/hir-ty/src/mir/lower.rs +++ b/crates/hir-ty/src/mir/lower.rs @@ -6,31 +6,38 @@ use chalk_ir::{BoundVar, ConstData, DebruijnIndex, TyKind}; use hir_def::{ body::Body, expr::{ - Array, BindingAnnotation, ExprId, LabelId, Literal, MatchArm, Pat, PatId, RecordLitField, + Array, BindingAnnotation, BindingId, ExprId, LabelId, Literal, MatchArm, Pat, PatId, + RecordLitField, }, + lang_item::{LangItem, LangItemTarget}, layout::LayoutError, + path::Path, resolver::{resolver_for_expr, ResolveValueResult, ValueNs}, DefWithBodyId, EnumVariantId, HasModule, }; +use hir_expand::name::Name; use la_arena::ArenaMap; use crate::{ - consteval::ConstEvalError, db::HirDatabase, layout::layout_of_ty, mapping::ToChalk, - utils::generics, Adjust, AutoBorrow, CallableDefId, TyBuilder, TyExt, + consteval::ConstEvalError, db::HirDatabase, display::HirDisplay, infer::TypeMismatch, + inhabitedness::is_ty_uninhabited_from, layout::layout_of_ty, mapping::ToChalk, static_lifetime, + utils::generics, Adjust, Adjustment, AutoBorrow, CallableDefId, TyBuilder, TyExt, }; use super::*; +mod as_place; + #[derive(Debug, Clone, Copy)] struct LoopBlocks { begin: BasicBlockId, - end: BasicBlockId, + /// `None` for loops that are not terminating + end: Option<BasicBlockId>, } struct MirLowerCtx<'a> { result: MirBody, owner: DefWithBodyId, - binding_locals: ArenaMap<PatId, LocalId>, current_loop_blocks: Option<LoopBlocks>, discr_temp: Option<Place>, db: &'a dyn HirDatabase, @@ -43,13 +50,22 @@ pub enum MirLowerError { ConstEvalError(Box<ConstEvalError>), LayoutError(LayoutError), IncompleteExpr, - UnresolvedName, + UnresolvedName(String), + RecordLiteralWithoutPath, + UnresolvedMethod, + UnresolvedField, MissingFunctionDefinition, + TypeMismatch(TypeMismatch), + /// This should be never happen. Type mismatch should catch everything. TypeError(&'static str), NotSupported(String), ContinueWithoutLoop, BreakWithoutLoop, Loop, + /// Something that should never happen and is definitely a bug, but we don't want to panic if it happened + ImplementationError(&'static str), + LangItemNotFound(LangItem), + MutatingRvalue, } macro_rules! not_supported { @@ -58,6 +74,13 @@ macro_rules! not_supported { }; } +macro_rules! implementation_error { + ($x: expr) => {{ + ::stdx::never!("MIR lower implementation bug: {}", $x); + return Err(MirLowerError::ImplementationError($x)); + }}; +} + impl From<ConstEvalError> for MirLowerError { fn from(value: ConstEvalError) -> Self { match value { @@ -73,93 +96,89 @@ impl From<LayoutError> for MirLowerError { } } +impl MirLowerError { + fn unresolved_path(db: &dyn HirDatabase, p: &Path) -> Self { + Self::UnresolvedName(p.display(db).to_string()) + } +} + type Result<T> = std::result::Result<T, MirLowerError>; impl MirLowerCtx<'_> { fn temp(&mut self, ty: Ty) -> Result<LocalId> { if matches!(ty.kind(Interner), TyKind::Slice(_) | TyKind::Dyn(_)) { - not_supported!("unsized temporaries"); - } - Ok(self.result.locals.alloc(Local { mutability: Mutability::Not, ty })) - } - - fn lower_expr_as_place(&self, expr_id: ExprId) -> Option<Place> { - let adjustments = self.infer.expr_adjustments.get(&expr_id); - let mut r = self.lower_expr_as_place_without_adjust(expr_id)?; - for adjustment in adjustments.iter().flat_map(|x| x.iter()) { - match adjustment.kind { - Adjust::NeverToAny => return Some(r), - Adjust::Deref(None) => { - r.projection.push(ProjectionElem::Deref); - } - Adjust::Deref(Some(_)) => return None, - Adjust::Borrow(_) => return None, - Adjust::Pointer(_) => return None, - } - } - Some(r) - } - - fn lower_expr_as_place_without_adjust(&self, expr_id: ExprId) -> Option<Place> { - match &self.body.exprs[expr_id] { - Expr::Path(p) => { - let resolver = resolver_for_expr(self.db.upcast(), self.owner, expr_id); - let pr = resolver.resolve_path_in_value_ns(self.db.upcast(), p.mod_path())?; - let pr = match pr { - ResolveValueResult::ValueNs(v) => v, - ResolveValueResult::Partial(..) => return None, - }; - match pr { - ValueNs::LocalBinding(pat_id) => Some(self.binding_locals[pat_id].into()), - _ => None, - } - } - Expr::UnaryOp { expr, op } => match op { - hir_def::expr::UnaryOp::Deref => { - let mut r = self.lower_expr_as_place(*expr)?; - r.projection.push(ProjectionElem::Deref); - Some(r) - } - _ => None, - }, - _ => None, + implementation_error!("unsized temporaries"); } + Ok(self.result.locals.alloc(Local { ty })) } fn lower_expr_to_some_operand( &mut self, expr_id: ExprId, current: BasicBlockId, - ) -> Result<(Operand, BasicBlockId)> { + ) -> Result<Option<(Operand, BasicBlockId)>> { if !self.has_adjustments(expr_id) { match &self.body.exprs[expr_id] { Expr::Literal(l) => { let ty = self.expr_ty(expr_id); - return Ok((self.lower_literal_to_operand(ty, l)?, current)); + return Ok(Some((self.lower_literal_to_operand(ty, l)?, current))); } _ => (), } } - let (p, current) = self.lower_expr_to_some_place(expr_id, current)?; - Ok((Operand::Copy(p), current)) + let Some((p, current)) = self.lower_expr_as_place(current, expr_id, true)? else { + return Ok(None); + }; + Ok(Some((Operand::Copy(p), current))) } - fn lower_expr_to_some_place( + fn lower_expr_to_place_with_adjust( &mut self, expr_id: ExprId, - prev_block: BasicBlockId, - ) -> Result<(Place, BasicBlockId)> { - if let Some(p) = self.lower_expr_as_place(expr_id) { - return Ok((p, prev_block)); - } - let mut ty = self.expr_ty(expr_id); - if let Some(x) = self.infer.expr_adjustments.get(&expr_id) { - if let Some(x) = x.last() { - ty = x.target.clone(); - } + place: Place, + current: BasicBlockId, + adjustments: &[Adjustment], + ) -> Result<Option<BasicBlockId>> { + match adjustments.split_last() { + Some((last, rest)) => match &last.kind { + Adjust::NeverToAny => { + let temp = self.temp(TyKind::Never.intern(Interner))?; + self.lower_expr_to_place_with_adjust(expr_id, temp.into(), current, rest) + } + Adjust::Deref(_) => { + let Some((p, current)) = self.lower_expr_as_place_with_adjust(current, expr_id, true, adjustments)? else { + return Ok(None); + }; + self.push_assignment(current, place, Operand::Copy(p).into(), expr_id.into()); + Ok(Some(current)) + } + Adjust::Borrow(AutoBorrow::Ref(m) | AutoBorrow::RawPtr(m)) => { + let Some((p, current)) = self.lower_expr_as_place_with_adjust(current, expr_id, true, rest)? else { + return Ok(None); + }; + let bk = BorrowKind::from_chalk(*m); + self.push_assignment(current, place, Rvalue::Ref(bk, p), expr_id.into()); + Ok(Some(current)) + } + Adjust::Pointer(cast) => { + let Some((p, current)) = self.lower_expr_as_place_with_adjust(current, expr_id, true, rest)? else { + return Ok(None); + }; + self.push_assignment( + current, + place, + Rvalue::Cast( + CastKind::Pointer(cast.clone()), + Operand::Copy(p).into(), + last.target.clone(), + ), + expr_id.into(), + ); + Ok(Some(current)) + } + }, + None => self.lower_expr_to_place_without_adjust(expr_id, place, current), } - let place = self.temp(ty)?; - Ok((place.into(), self.lower_expr_to_place(expr_id, place.into(), prev_block)?)) } fn lower_expr_to_place( @@ -167,48 +186,9 @@ impl MirLowerCtx<'_> { expr_id: ExprId, place: Place, prev_block: BasicBlockId, - ) -> Result<BasicBlockId> { - if let Some(x) = self.infer.expr_adjustments.get(&expr_id) { - if x.len() > 0 { - let tmp = self.temp(self.expr_ty(expr_id))?; - let current = - self.lower_expr_to_place_without_adjust(expr_id, tmp.into(), prev_block)?; - let mut r = Place::from(tmp); - for adjustment in x { - match &adjustment.kind { - Adjust::NeverToAny => (), - Adjust::Deref(None) => { - r.projection.push(ProjectionElem::Deref); - } - Adjust::Deref(Some(_)) => not_supported!("overloaded dereference"), - Adjust::Borrow(AutoBorrow::Ref(m) | AutoBorrow::RawPtr(m)) => { - let tmp = self.temp(adjustment.target.clone())?; - self.push_assignment( - current, - tmp.into(), - Rvalue::Ref(BorrowKind::from_chalk(*m), r), - ); - r = tmp.into(); - } - Adjust::Pointer(cast) => { - let target = &adjustment.target; - let tmp = self.temp(target.clone())?; - self.push_assignment( - current, - tmp.into(), - Rvalue::Cast( - CastKind::Pointer(cast.clone()), - Operand::Copy(r).into(), - target.clone(), - ), - ); - r = tmp.into(); - } - } - } - self.push_assignment(current, place, Operand::Copy(r).into()); - return Ok(current); - } + ) -> Result<Option<BasicBlockId>> { + if let Some(adjustments) = self.infer.expr_adjustments.get(&expr_id) { + return self.lower_expr_to_place_with_adjust(expr_id, place, prev_block, adjustments); } self.lower_expr_to_place_without_adjust(expr_id, place, prev_block) } @@ -218,27 +198,41 @@ impl MirLowerCtx<'_> { expr_id: ExprId, place: Place, mut current: BasicBlockId, - ) -> Result<BasicBlockId> { + ) -> Result<Option<BasicBlockId>> { match &self.body.exprs[expr_id] { Expr::Missing => Err(MirLowerError::IncompleteExpr), Expr::Path(p) => { + let unresolved_name = || MirLowerError::unresolved_path(self.db, p); let resolver = resolver_for_expr(self.db.upcast(), self.owner, expr_id); let pr = resolver .resolve_path_in_value_ns(self.db.upcast(), p.mod_path()) - .ok_or(MirLowerError::UnresolvedName)?; + .ok_or_else(unresolved_name)?; let pr = match pr { ResolveValueResult::ValueNs(v) => v, ResolveValueResult::Partial(..) => { - return match self + if let Some(assoc) = self .infer .assoc_resolutions_for_expr(expr_id) - .ok_or(MirLowerError::UnresolvedName)? - .0 - //.ok_or(ConstEvalError::SemanticError("unresolved assoc item"))? { - hir_def::AssocItemId::ConstId(c) => self.lower_const(c, current, place), - _ => return Err(MirLowerError::UnresolvedName), - }; + match assoc.0 { + hir_def::AssocItemId::ConstId(c) => { + self.lower_const(c, current, place, expr_id.into())?; + return Ok(Some(current)) + }, + _ => not_supported!("associated functions and types"), + } + } else if let Some(variant) = self + .infer + .variant_resolution_for_expr(expr_id) + { + match variant { + VariantId::EnumVariantId(e) => ValueNs::EnumVariantId(e), + VariantId::StructId(s) => ValueNs::StructId(s), + VariantId::UnionId(_) => implementation_error!("Union variant as path"), + } + } else { + return Err(unresolved_name()); + } } }; match pr { @@ -246,14 +240,26 @@ impl MirLowerCtx<'_> { self.push_assignment( current, place, - Operand::Copy(self.binding_locals[pat_id].into()).into(), + Operand::Copy(self.result.binding_locals[pat_id].into()).into(), + expr_id.into(), ); - Ok(current) + Ok(Some(current)) + } + ValueNs::ConstId(const_id) => { + self.lower_const(const_id, current, place, expr_id.into())?; + Ok(Some(current)) } - ValueNs::ConstId(const_id) => self.lower_const(const_id, current, place), ValueNs::EnumVariantId(variant_id) => { let ty = self.infer.type_of_expr[expr_id].clone(); - self.lower_enum_variant(variant_id, current, place, ty, vec![]) + let current = self.lower_enum_variant( + variant_id, + current, + place, + ty, + vec![], + expr_id.into(), + )?; + Ok(Some(current)) } ValueNs::GenericParam(p) => { let Some(def) = self.owner.as_generic_def_id() else { @@ -277,12 +283,13 @@ impl MirLowerCtx<'_> { .intern(Interner), ) .into(), + expr_id.into(), ); - Ok(current) + Ok(Some(current)) } ValueNs::StructId(_) => { // It's probably a unit struct or a zero sized function, so no action is needed. - Ok(current) + Ok(Some(current)) } x => { not_supported!("unknown name {x:?} in value name space"); @@ -290,19 +297,18 @@ impl MirLowerCtx<'_> { } } Expr::If { condition, then_branch, else_branch } => { - let (discr, current) = self.lower_expr_to_some_operand(*condition, current)?; + let Some((discr, current)) = self.lower_expr_to_some_operand(*condition, current)? else { + return Ok(None); + }; let start_of_then = self.new_basic_block(); - let end = self.new_basic_block(); let end_of_then = self.lower_expr_to_place(*then_branch, place.clone(), start_of_then)?; - self.set_goto(end_of_then, end); - let mut start_of_else = end; - if let Some(else_branch) = else_branch { - start_of_else = self.new_basic_block(); - let end_of_else = - self.lower_expr_to_place(*else_branch, place, start_of_else)?; - self.set_goto(end_of_else, end); - } + let start_of_else = self.new_basic_block(); + let end_of_else = if let Some(else_branch) = else_branch { + self.lower_expr_to_place(*else_branch, place, start_of_else)? + } else { + Some(start_of_else) + }; self.set_terminator( current, Terminator::SwitchInt { @@ -310,26 +316,37 @@ impl MirLowerCtx<'_> { targets: SwitchTargets::static_if(1, start_of_then, start_of_else), }, ); - Ok(end) + Ok(self.merge_blocks(end_of_then, end_of_else)) } Expr::Let { pat, expr } => { - let (cond_place, current) = self.lower_expr_to_some_place(*expr, current)?; - let result = self.new_basic_block(); + let Some((cond_place, current)) = self.lower_expr_as_place(current, *expr, true)? else { + return Ok(None); + }; let (then_target, else_target) = self.pattern_match( current, None, cond_place, - self.expr_ty(*expr), + self.expr_ty_after_adjustments(*expr), *pat, BindingAnnotation::Unannotated, )?; - self.write_bytes_to_place(then_target, place.clone(), vec![1], TyBuilder::bool())?; - self.set_goto(then_target, result); + self.write_bytes_to_place( + then_target, + place.clone(), + vec![1], + TyBuilder::bool(), + MirSpan::Unknown, + )?; if let Some(else_target) = else_target { - self.write_bytes_to_place(else_target, place, vec![0], TyBuilder::bool())?; - self.set_goto(else_target, result); + self.write_bytes_to_place( + else_target, + place, + vec![0], + TyBuilder::bool(), + MirSpan::Unknown, + )?; } - Ok(result) + Ok(self.merge_blocks(Some(then_target), else_target)) } Expr::Unsafe { id: _, statements, tail } => { self.lower_block_to_place(None, statements, current, *tail, place) @@ -337,14 +354,18 @@ impl MirLowerCtx<'_> { Expr::Block { id: _, statements, tail, label } => { self.lower_block_to_place(*label, statements, current, *tail, place) } - Expr::Loop { body, label } => self.lower_loop(current, *label, |this, begin, _| { - let (_, block) = this.lower_expr_to_some_place(*body, begin)?; - this.set_goto(block, begin); + Expr::Loop { body, label } => self.lower_loop(current, *label, |this, begin| { + if let Some((_, block)) = this.lower_expr_as_place(begin, *body, true)? { + this.set_goto(block, begin); + } Ok(()) }), Expr::While { condition, body, label } => { - self.lower_loop(current, *label, |this, begin, end| { - let (discr, to_switch) = this.lower_expr_to_some_operand(*condition, begin)?; + self.lower_loop(current, *label, |this, begin| { + let Some((discr, to_switch)) = this.lower_expr_to_some_operand(*condition, begin)? else { + return Ok(()); + }; + let end = this.current_loop_end()?; let after_cond = this.new_basic_block(); this.set_terminator( to_switch, @@ -353,18 +374,71 @@ impl MirLowerCtx<'_> { targets: SwitchTargets::static_if(1, after_cond, end), }, ); - let (_, block) = this.lower_expr_to_some_place(*body, after_cond)?; - this.set_goto(block, begin); + if let Some((_, block)) = this.lower_expr_as_place(after_cond, *body, true)? { + this.set_goto(block, begin); + } Ok(()) }) } - Expr::For { .. } => not_supported!("for loop"), + &Expr::For { iterable, pat, body, label } => { + let into_iter_fn = self.resolve_lang_item(LangItem::IntoIterIntoIter)? + .as_function().ok_or(MirLowerError::LangItemNotFound(LangItem::IntoIterIntoIter))?; + let iter_next_fn = self.resolve_lang_item(LangItem::IteratorNext)? + .as_function().ok_or(MirLowerError::LangItemNotFound(LangItem::IteratorNext))?; + let option_some = self.resolve_lang_item(LangItem::OptionSome)? + .as_enum_variant().ok_or(MirLowerError::LangItemNotFound(LangItem::OptionSome))?; + let option = option_some.parent; + let into_iter_fn_op = Operand::const_zst( + TyKind::FnDef( + self.db.intern_callable_def(CallableDefId::FunctionId(into_iter_fn)).into(), + Substitution::from1(Interner, self.expr_ty(iterable)) + ).intern(Interner)); + let iter_next_fn_op = Operand::const_zst( + TyKind::FnDef( + self.db.intern_callable_def(CallableDefId::FunctionId(iter_next_fn)).into(), + Substitution::from1(Interner, self.expr_ty(iterable)) + ).intern(Interner)); + let &Some(iterator_ty) = &self.infer.type_of_for_iterator.get(&expr_id) else { + return Err(MirLowerError::TypeError("unknown for loop iterator type")); + }; + let ref_mut_iterator_ty = TyKind::Ref(Mutability::Mut, static_lifetime(), iterator_ty.clone()).intern(Interner); + let item_ty = &self.infer.type_of_pat[pat]; + let option_item_ty = TyKind::Adt(chalk_ir::AdtId(option.into()), Substitution::from1(Interner, item_ty.clone())).intern(Interner); + let iterator_place: Place = self.temp(iterator_ty.clone())?.into(); + let option_item_place: Place = self.temp(option_item_ty.clone())?.into(); + let ref_mut_iterator_place: Place = self.temp(ref_mut_iterator_ty)?.into(); + let Some(current) = self.lower_call_and_args(into_iter_fn_op, Some(iterable).into_iter(), iterator_place.clone(), current, false)? + else { + return Ok(None); + }; + self.push_assignment(current, ref_mut_iterator_place.clone(), Rvalue::Ref(BorrowKind::Mut { allow_two_phase_borrow: false }, iterator_place), expr_id.into()); + self.lower_loop(current, label, |this, begin| { + let Some(current) = this.lower_call(iter_next_fn_op, vec![Operand::Copy(ref_mut_iterator_place)], option_item_place.clone(), begin, false)? + else { + return Ok(()); + }; + let end = this.current_loop_end()?; + let (current, _) = this.pattern_matching_variant( + option_item_ty.clone(), + BindingAnnotation::Unannotated, + option_item_place.into(), + option_some.into(), + current, + pat.into(), + Some(end), + &[pat], &None)?; + if let Some((_, block)) = this.lower_expr_as_place(current, body, true)? { + this.set_goto(block, begin); + } + Ok(()) + }) + }, Expr::Call { callee, args, .. } => { - let callee_ty = self.expr_ty(*callee); + let callee_ty = self.expr_ty_after_adjustments(*callee); match &callee_ty.data(Interner).kind { chalk_ir::TyKind::FnDef(..) => { let func = Operand::from_bytes(vec![], callee_ty.clone()); - self.lower_call(func, args.iter().copied(), place, current) + self.lower_call_and_args(func, args.iter().copied(), place, current, self.is_uninhabited(expr_id)) } TyKind::Scalar(_) | TyKind::Tuple(_, _) @@ -394,24 +468,28 @@ impl MirLowerCtx<'_> { } Expr::MethodCall { receiver, args, .. } => { let (func_id, generic_args) = - self.infer.method_resolution(expr_id).ok_or(MirLowerError::UnresolvedName)?; + self.infer.method_resolution(expr_id).ok_or(MirLowerError::UnresolvedMethod)?; let ty = chalk_ir::TyKind::FnDef( CallableDefId::FunctionId(func_id).to_chalk(self.db), generic_args, ) .intern(Interner); let func = Operand::from_bytes(vec![], ty); - self.lower_call( + self.lower_call_and_args( func, iter::once(*receiver).chain(args.iter().copied()), place, current, + self.is_uninhabited(expr_id), ) } Expr::Match { expr, arms } => { - let (cond_place, mut current) = self.lower_expr_to_some_place(*expr, current)?; - let cond_ty = self.expr_ty(*expr); - let end = self.new_basic_block(); + let Some((cond_place, mut current)) = self.lower_expr_as_place(current, *expr, true)? + else { + return Ok(None); + }; + let cond_ty = self.expr_ty_after_adjustments(*expr); + let mut end = None; for MatchArm { pat, guard, expr } in arms.iter() { if guard.is_some() { not_supported!("pattern matching with guard"); @@ -424,8 +502,10 @@ impl MirLowerCtx<'_> { *pat, BindingAnnotation::Unannotated, )?; - let block = self.lower_expr_to_place(*expr, place.clone(), then)?; - self.set_goto(block, end); + if let Some(block) = self.lower_expr_to_place(*expr, place.clone(), then)? { + let r = end.get_or_insert_with(|| self.new_basic_block()); + self.set_goto(block, *r); + } match otherwise { Some(o) => current = o, None => { @@ -446,8 +526,7 @@ impl MirLowerCtx<'_> { let loop_data = self.current_loop_blocks.ok_or(MirLowerError::ContinueWithoutLoop)?; self.set_goto(current, loop_data.begin); - let otherwise = self.new_basic_block(); - Ok(otherwise) + Ok(None) } }, Expr::Break { expr, label } => { @@ -457,26 +536,33 @@ impl MirLowerCtx<'_> { match label { Some(_) => not_supported!("break with label"), None => { - let loop_data = - self.current_loop_blocks.ok_or(MirLowerError::BreakWithoutLoop)?; - self.set_goto(current, loop_data.end); - Ok(self.new_basic_block()) + let end = + self.current_loop_end()?; + self.set_goto(current, end); + Ok(None) } } } Expr::Return { expr } => { if let Some(expr) = expr { - current = self.lower_expr_to_place(*expr, return_slot().into(), current)?; + if let Some(c) = self.lower_expr_to_place(*expr, return_slot().into(), current)? { + current = c; + } else { + return Ok(None); + } } self.set_terminator(current, Terminator::Return); - Ok(self.new_basic_block()) + Ok(None) } Expr::Yield { .. } => not_supported!("yield"), - Expr::RecordLit { fields, .. } => { + Expr::RecordLit { fields, path, .. } => { let variant_id = self .infer .variant_resolution_for_expr(expr_id) - .ok_or(MirLowerError::UnresolvedName)?; + .ok_or_else(|| match path { + Some(p) => MirLowerError::UnresolvedName(p.display(self.db).to_string()), + None => MirLowerError::RecordLiteralWithoutPath, + })?; let subst = match self.expr_ty(expr_id).kind(Interner) { TyKind::Adt(_, s) => s.clone(), _ => not_supported!("Non ADT record literal"), @@ -487,9 +573,11 @@ impl MirLowerCtx<'_> { let mut operands = vec![None; variant_data.fields().len()]; for RecordLitField { name, expr } in fields.iter() { let field_id = - variant_data.field(name).ok_or(MirLowerError::UnresolvedName)?; - let op; - (op, current) = self.lower_expr_to_some_operand(*expr, current)?; + variant_data.field(name).ok_or(MirLowerError::UnresolvedField)?; + let Some((op, c)) = self.lower_expr_to_some_operand(*expr, current)? else { + return Ok(None); + }; + current = c; operands[u32::from(field_id.into_raw()) as usize] = Some(op); } self.push_assignment( @@ -501,15 +589,16 @@ impl MirLowerCtx<'_> { MirLowerError::TypeError("missing field in record literal"), )?, ), + expr_id.into(), ); - Ok(current) + Ok(Some(current)) } VariantId::UnionId(union_id) => { let [RecordLitField { name, expr }] = fields.as_ref() else { not_supported!("Union record literal with more than one field"); }; let local_id = - variant_data.field(name).ok_or(MirLowerError::UnresolvedName)?; + variant_data.field(name).ok_or(MirLowerError::UnresolvedField)?; let mut place = place; place .projection @@ -518,23 +607,6 @@ impl MirLowerCtx<'_> { } } } - Expr::Field { expr, name } => { - let (mut current_place, current) = self.lower_expr_to_some_place(*expr, current)?; - if let TyKind::Tuple(..) = self.expr_ty(*expr).kind(Interner) { - let index = name - .as_tuple_index() - .ok_or(MirLowerError::TypeError("named field on tuple"))?; - current_place.projection.push(ProjectionElem::TupleField(index)) - } else { - let field = self - .infer - .field_resolution(expr_id) - .ok_or(MirLowerError::UnresolvedName)?; - current_place.projection.push(ProjectionElem::Field(field)); - } - self.push_assignment(current, place, Operand::Copy(current_place).into()); - Ok(current) - } Expr::Await { .. } => not_supported!("await"), Expr::Try { .. } => not_supported!("? operator"), Expr::Yeet { .. } => not_supported!("yeet"), @@ -542,41 +614,51 @@ impl MirLowerCtx<'_> { Expr::Async { .. } => not_supported!("async block"), Expr::Const { .. } => not_supported!("anonymous const block"), Expr::Cast { expr, type_ref: _ } => { - let (x, current) = self.lower_expr_to_some_operand(*expr, current)?; + let Some((x, current)) = self.lower_expr_to_some_operand(*expr, current)? else { + return Ok(None); + }; let source_ty = self.infer[*expr].clone(); let target_ty = self.infer[expr_id].clone(); self.push_assignment( current, place, Rvalue::Cast(cast_kind(&source_ty, &target_ty)?, x, target_ty), + expr_id.into(), ); - Ok(current) + Ok(Some(current)) } Expr::Ref { expr, rawness: _, mutability } => { - let p; - (p, current) = self.lower_expr_to_some_place(*expr, current)?; + let Some((p, current)) = self.lower_expr_as_place(current, *expr, true)? else { + return Ok(None); + }; let bk = BorrowKind::from_hir(*mutability); - self.push_assignment(current, place, Rvalue::Ref(bk, p)); - Ok(current) + self.push_assignment(current, place, Rvalue::Ref(bk, p), expr_id.into()); + Ok(Some(current)) } Expr::Box { .. } => not_supported!("box expression"), - Expr::UnaryOp { expr, op } => match op { - hir_def::expr::UnaryOp::Deref => { - let (mut tmp, current) = self.lower_expr_to_some_place(*expr, current)?; - tmp.projection.push(ProjectionElem::Deref); - self.push_assignment(current, place, Operand::Copy(tmp).into()); - Ok(current) - } - hir_def::expr::UnaryOp::Not => { - let (op, current) = self.lower_expr_to_some_operand(*expr, current)?; - self.push_assignment(current, place, Rvalue::UnaryOp(UnOp::Not, op)); - Ok(current) - } - hir_def::expr::UnaryOp::Neg => { - let (op, current) = self.lower_expr_to_some_operand(*expr, current)?; - self.push_assignment(current, place, Rvalue::UnaryOp(UnOp::Neg, op)); - Ok(current) - } + Expr::Field { .. } | Expr::Index { .. } | Expr::UnaryOp { op: hir_def::expr::UnaryOp::Deref, .. } => { + let Some((p, current)) = self.lower_expr_as_place(current, expr_id, true)? else { + return Ok(None); + }; + self.push_assignment(current, place, Operand::Copy(p).into(), expr_id.into()); + Ok(Some(current)) + } + Expr::UnaryOp { expr, op: op @ (hir_def::expr::UnaryOp::Not | hir_def::expr::UnaryOp::Neg) } => { + let Some((operand, current)) = self.lower_expr_to_some_operand(*expr, current)? else { + return Ok(None); + }; + let operation = match op { + hir_def::expr::UnaryOp::Not => UnOp::Not, + hir_def::expr::UnaryOp::Neg => UnOp::Neg, + _ => unreachable!(), + }; + self.push_assignment( + current, + place, + Rvalue::UnaryOp(operation, operand), + expr_id.into(), + ); + Ok(Some(current)) }, Expr::BinaryOp { lhs, rhs, op } => { let op = op.ok_or(MirLowerError::IncompleteExpr)?; @@ -584,18 +666,23 @@ impl MirLowerCtx<'_> { if op.is_some() { not_supported!("assignment with arith op (like +=)"); } - let Some(lhs_place) = self.lower_expr_as_place(*lhs) else { - not_supported!("assignment to complex place"); + let Some((lhs_place, current)) = + self.lower_expr_as_place(current, *lhs, false)? + else { + return Ok(None); }; - let rhs_op; - (rhs_op, current) = self.lower_expr_to_some_operand(*rhs, current)?; - self.push_assignment(current, lhs_place, rhs_op.into()); - return Ok(current); + let Some((rhs_op, current)) = self.lower_expr_to_some_operand(*rhs, current)? else { + return Ok(None); + }; + self.push_assignment(current, lhs_place, rhs_op.into(), expr_id.into()); + return Ok(Some(current)); } - let lhs_op; - (lhs_op, current) = self.lower_expr_to_some_operand(*lhs, current)?; - let rhs_op; - (rhs_op, current) = self.lower_expr_to_some_operand(*rhs, current)?; + let Some((lhs_op, current)) = self.lower_expr_to_some_operand(*lhs, current)? else { + return Ok(None); + }; + let Some((rhs_op, current)) = self.lower_expr_to_some_operand(*rhs, current)? else { + return Ok(None); + }; self.push_assignment( current, place, @@ -612,34 +699,32 @@ impl MirLowerCtx<'_> { lhs_op, rhs_op, ), + expr_id.into(), ); - Ok(current) + Ok(Some(current)) } Expr::Range { .. } => not_supported!("range"), - Expr::Index { base, index } => { - let mut p_base; - (p_base, current) = self.lower_expr_to_some_place(*base, current)?; - let l_index = self.temp(self.expr_ty(*index))?; - current = self.lower_expr_to_place(*index, l_index.into(), current)?; - p_base.projection.push(ProjectionElem::Index(l_index)); - self.push_assignment(current, place, Operand::Copy(p_base).into()); - Ok(current) - } Expr::Closure { .. } => not_supported!("closure"), Expr::Tuple { exprs, is_assignee_expr: _ } => { - let r = Rvalue::Aggregate( - AggregateKind::Tuple(self.expr_ty(expr_id)), - exprs + let Some(values) = exprs .iter() .map(|x| { - let o; - (o, current) = self.lower_expr_to_some_operand(*x, current)?; - Ok(o) + let Some((o, c)) = self.lower_expr_to_some_operand(*x, current)? else { + return Ok(None); + }; + current = c; + Ok(Some(o)) }) - .collect::<Result<_>>()?, + .collect::<Result<Option<_>>>()? + else { + return Ok(None); + }; + let r = Rvalue::Aggregate( + AggregateKind::Tuple(self.expr_ty(expr_id)), + values, ); - self.push_assignment(current, place, r); - Ok(current) + self.push_assignment(current, place, r, expr_id.into()); + Ok(Some(current)) } Expr::Array(l) => match l { Array::ElementList { elements, .. } => { @@ -651,86 +736,54 @@ impl MirLowerCtx<'_> { )) } }; - let r = Rvalue::Aggregate( - AggregateKind::Array(elem_ty), - elements + let Some(values) = elements .iter() .map(|x| { - let o; - (o, current) = self.lower_expr_to_some_operand(*x, current)?; - Ok(o) + let Some((o, c)) = self.lower_expr_to_some_operand(*x, current)? else { + return Ok(None); + }; + current = c; + Ok(Some(o)) }) - .collect::<Result<_>>()?, + .collect::<Result<Option<_>>>()? + else { + return Ok(None); + }; + let r = Rvalue::Aggregate( + AggregateKind::Array(elem_ty), + values, ); - self.push_assignment(current, place, r); - Ok(current) + self.push_assignment(current, place, r, expr_id.into()); + Ok(Some(current)) } Array::Repeat { .. } => not_supported!("array repeat"), }, Expr::Literal(l) => { let ty = self.expr_ty(expr_id); let op = self.lower_literal_to_operand(ty, l)?; - self.push_assignment(current, place, op.into()); - Ok(current) + self.push_assignment(current, place, op.into(), expr_id.into()); + Ok(Some(current)) } Expr::Underscore => not_supported!("underscore"), } } - fn lower_block_to_place( - &mut self, - label: Option<LabelId>, - statements: &[hir_def::expr::Statement], - mut current: BasicBlockId, - tail: Option<ExprId>, - place: Place, - ) -> Result<BasicBlockId> { - if label.is_some() { - not_supported!("block with label"); - } - for statement in statements.iter() { - match statement { - hir_def::expr::Statement::Let { pat, initializer, else_branch, type_ref: _ } => { - match initializer { - Some(expr_id) => { - let else_block; - let init_place; - (init_place, current) = - self.lower_expr_to_some_place(*expr_id, current)?; - (current, else_block) = self.pattern_match( - current, - None, - init_place, - self.expr_ty(*expr_id), - *pat, - BindingAnnotation::Unannotated, - )?; - match (else_block, else_branch) { - (None, _) => (), - (Some(else_block), None) => { - self.set_terminator(else_block, Terminator::Unreachable); - } - (Some(else_block), Some(else_branch)) => { - let (_, b) = - self.lower_expr_to_some_place(*else_branch, else_block)?; - self.set_terminator(b, Terminator::Unreachable); - } - } - } - None => continue, - } - } - hir_def::expr::Statement::Expr { expr, has_semi: _ } => { - let ty = self.expr_ty(*expr); - let temp = self.temp(ty)?; - current = self.lower_expr_to_place(*expr, temp.into(), current)?; - } + fn push_field_projection(&self, place: &mut Place, expr_id: ExprId) -> Result<()> { + if let Expr::Field { expr, name } = &self.body[expr_id] { + if let TyKind::Tuple(..) = self.expr_ty_after_adjustments(*expr).kind(Interner) { + let index = name + .as_tuple_index() + .ok_or(MirLowerError::TypeError("named field on tuple"))?; + place.projection.push(ProjectionElem::TupleField(index)) + } else { + let field = + self.infer.field_resolution(expr_id).ok_or(MirLowerError::UnresolvedField)?; + place.projection.push(ProjectionElem::Field(field)); } + } else { + not_supported!("") } - match tail { - Some(tail) => self.lower_expr_to_place(tail, place, current), - None => Ok(current), - } + Ok(()) } fn lower_literal_to_operand(&mut self, ty: Ty, l: &Literal) -> Result<Operand> { @@ -779,9 +832,10 @@ impl MirLowerCtx<'_> { const_id: hir_def::ConstId, prev_block: BasicBlockId, place: Place, - ) -> Result<BasicBlockId> { + span: MirSpan, + ) -> Result<()> { let c = self.db.const_eval(const_id)?; - self.write_const_to_place(c, prev_block, place) + self.write_const_to_place(c, prev_block, place, span) } fn write_const_to_place( @@ -789,9 +843,10 @@ impl MirLowerCtx<'_> { c: Const, prev_block: BasicBlockId, place: Place, - ) -> Result<BasicBlockId> { - self.push_assignment(prev_block, place, Operand::Constant(c).into()); - Ok(prev_block) + span: MirSpan, + ) -> Result<()> { + self.push_assignment(prev_block, place, Operand::Constant(c).into(), span); + Ok(()) } fn write_bytes_to_place( @@ -800,9 +855,10 @@ impl MirLowerCtx<'_> { place: Place, cv: Vec<u8>, ty: Ty, - ) -> Result<BasicBlockId> { - self.push_assignment(prev_block, place, Operand::from_bytes(cv, ty).into()); - Ok(prev_block) + span: MirSpan, + ) -> Result<()> { + self.push_assignment(prev_block, place, Operand::from_bytes(cv, ty).into(), span); + Ok(()) } fn lower_enum_variant( @@ -812,6 +868,7 @@ impl MirLowerCtx<'_> { place: Place, ty: Ty, fields: Vec<Operand>, + span: MirSpan, ) -> Result<BasicBlockId> { let subst = match ty.kind(Interner) { TyKind::Adt(_, subst) => subst.clone(), @@ -821,36 +878,51 @@ impl MirLowerCtx<'_> { prev_block, place, Rvalue::Aggregate(AggregateKind::Adt(variant_id.into(), subst), fields), + span, ); Ok(prev_block) } - fn lower_call( + fn lower_call_and_args( &mut self, func: Operand, args: impl Iterator<Item = ExprId>, place: Place, mut current: BasicBlockId, - ) -> Result<BasicBlockId> { - let args = args + is_uninhabited: bool, + ) -> Result<Option<BasicBlockId>> { + let Some(args) = args .map(|arg| { - let temp; - (temp, current) = self.lower_expr_to_some_operand(arg, current)?; - Ok(temp) + if let Some((temp, c)) = self.lower_expr_to_some_operand(arg, current)? { + current = c; + Ok(Some(temp)) + } else { + Ok(None) + } }) - .collect::<Result<Vec<_>>>()?; - let b = self.result.basic_blocks.alloc(BasicBlock { - statements: vec![], - terminator: None, - is_cleanup: false, - }); + .collect::<Result<Option<Vec<_>>>>()? + else { + return Ok(None); + }; + self.lower_call(func, args, place, current, is_uninhabited) + } + + fn lower_call( + &mut self, + func: Operand, + args: Vec<Operand>, + place: Place, + current: BasicBlockId, + is_uninhabited: bool, + ) -> Result<Option<BasicBlockId>> { + let b = if is_uninhabited { None } else { Some(self.new_basic_block()) }; self.set_terminator( current, Terminator::Call { func, args, destination: place, - target: Some(b), + target: b, cleanup: None, from_hir_call: true, }, @@ -874,8 +946,28 @@ impl MirLowerCtx<'_> { self.infer[e].clone() } - fn push_assignment(&mut self, block: BasicBlockId, place: Place, rvalue: Rvalue) { - self.result.basic_blocks[block].statements.push(Statement::Assign(place, rvalue)); + fn expr_ty_after_adjustments(&self, e: ExprId) -> Ty { + let mut ty = None; + if let Some(x) = self.infer.expr_adjustments.get(&e) { + if let Some(x) = x.last() { + ty = Some(x.target.clone()); + } + } + ty.unwrap_or_else(|| self.expr_ty(e)) + } + + fn push_statement(&mut self, block: BasicBlockId, statement: Statement) { + self.result.basic_blocks[block].statements.push(statement); + } + + fn push_assignment( + &mut self, + block: BasicBlockId, + place: Place, + rvalue: Rvalue, + span: MirSpan, + ) { + self.push_statement(block, StatementKind::Assign(place, rvalue).with_span(span)); } /// It gets a `current` unterminated block, appends some statements and possibly a terminator to it to check if @@ -924,11 +1016,50 @@ impl MirLowerCtx<'_> { binding_mode, )? } - Pat::Or(_) => not_supported!("or pattern"), + Pat::Or(pats) => { + let then_target = self.new_basic_block(); + let mut finished = false; + for pat in &**pats { + let (next, next_else) = self.pattern_match( + current, + None, + cond_place.clone(), + cond_ty.clone(), + *pat, + binding_mode, + )?; + self.set_goto(next, then_target); + match next_else { + Some(t) => { + current = t; + } + None => { + finished = true; + break; + } + } + } + (then_target, (!finished).then_some(current)) + } Pat::Record { .. } => not_supported!("record pattern"), Pat::Range { .. } => not_supported!("range pattern"), Pat::Slice { .. } => not_supported!("slice pattern"), - Pat::Path(_) => not_supported!("path pattern"), + Pat::Path(_) => { + let Some(variant) = self.infer.variant_resolution_for_pat(pattern) else { + not_supported!("unresolved variant"); + }; + self.pattern_matching_variant( + cond_ty, + binding_mode, + cond_place, + variant, + current, + pattern.into(), + current_else, + &[], + &None, + )? + } Pat::Lit(l) => { let then_target = self.new_basic_block(); let else_target = current_else.unwrap_or_else(|| self.new_basic_block()); @@ -962,8 +1093,9 @@ impl MirLowerCtx<'_> { } (then_target, Some(else_target)) } - Pat::Bind { mode, name: _, subpat } => { - let target_place = self.binding_locals[pattern]; + Pat::Bind { id, subpat } => { + let target_place = self.result.binding_locals[*id]; + let mode = self.body.bindings[*id].mode; if let Some(subpat) = subpat { (current, current_else) = self.pattern_match( current, @@ -975,8 +1107,9 @@ impl MirLowerCtx<'_> { )? } if matches!(mode, BindingAnnotation::Ref | BindingAnnotation::RefMut) { - binding_mode = *mode; + binding_mode = mode; } + self.push_storage_live(*id, current)?; self.push_assignment( current, target_place.into(), @@ -990,6 +1123,7 @@ impl MirLowerCtx<'_> { cond_place, ), }, + pattern.into(), ); (current, current_else) } @@ -997,74 +1131,17 @@ impl MirLowerCtx<'_> { let Some(variant) = self.infer.variant_resolution_for_pat(pattern) else { not_supported!("unresolved variant"); }; - pattern_matching_dereference(&mut cond_ty, &mut binding_mode, &mut cond_place); - let subst = match cond_ty.kind(Interner) { - TyKind::Adt(_, s) => s, - _ => { - return Err(MirLowerError::TypeError( - "non adt type matched with tuple struct", - )) - } - }; - let fields_type = self.db.field_types(variant); - match variant { - VariantId::EnumVariantId(v) => { - let e = self.db.const_eval_discriminant(v)? as u128; - let next = self.new_basic_block(); - let tmp = self.discr_temp_place(); - self.push_assignment( - current, - tmp.clone(), - Rvalue::Discriminant(cond_place.clone()), - ); - let else_target = current_else.unwrap_or_else(|| self.new_basic_block()); - self.set_terminator( - current, - Terminator::SwitchInt { - discr: Operand::Copy(tmp), - targets: SwitchTargets::static_if(e, next, else_target), - }, - ); - let enum_data = self.db.enum_data(v.parent); - let fields = - enum_data.variants[v.local_id].variant_data.fields().iter().map( - |(x, _)| { - ( - PlaceElem::Field(FieldId { parent: v.into(), local_id: x }), - fields_type[x].clone().substitute(Interner, subst), - ) - }, - ); - self.pattern_match_tuple_like( - next, - Some(else_target), - args.iter().zip(fields).map(|(x, y)| (y.0, *x, y.1)), - *ellipsis, - &cond_place, - binding_mode, - )? - } - VariantId::StructId(s) => { - let struct_data = self.db.struct_data(s); - let fields = struct_data.variant_data.fields().iter().map(|(x, _)| { - ( - PlaceElem::Field(FieldId { parent: s.into(), local_id: x }), - fields_type[x].clone().substitute(Interner, subst), - ) - }); - self.pattern_match_tuple_like( - current, - current_else, - args.iter().zip(fields).map(|(x, y)| (y.0, *x, y.1)), - *ellipsis, - &cond_place, - binding_mode, - )? - } - VariantId::UnionId(_) => { - return Err(MirLowerError::TypeError("pattern matching on union")) - } - } + self.pattern_matching_variant( + cond_ty, + binding_mode, + cond_place, + variant, + current, + pattern.into(), + current_else, + args, + ellipsis, + )? } Pat::Ref { .. } => not_supported!("& pattern"), Pat::Box { .. } => not_supported!("box pattern"), @@ -1072,6 +1149,83 @@ impl MirLowerCtx<'_> { }) } + fn pattern_matching_variant( + &mut self, + mut cond_ty: Ty, + mut binding_mode: BindingAnnotation, + mut cond_place: Place, + variant: VariantId, + current: BasicBlockId, + span: MirSpan, + current_else: Option<BasicBlockId>, + args: &[PatId], + ellipsis: &Option<usize>, + ) -> Result<(BasicBlockId, Option<BasicBlockId>)> { + pattern_matching_dereference(&mut cond_ty, &mut binding_mode, &mut cond_place); + let subst = match cond_ty.kind(Interner) { + TyKind::Adt(_, s) => s, + _ => return Err(MirLowerError::TypeError("non adt type matched with tuple struct")), + }; + let fields_type = self.db.field_types(variant); + Ok(match variant { + VariantId::EnumVariantId(v) => { + let e = self.db.const_eval_discriminant(v)? as u128; + let next = self.new_basic_block(); + let tmp = self.discr_temp_place(); + self.push_assignment( + current, + tmp.clone(), + Rvalue::Discriminant(cond_place.clone()), + span, + ); + let else_target = current_else.unwrap_or_else(|| self.new_basic_block()); + self.set_terminator( + current, + Terminator::SwitchInt { + discr: Operand::Copy(tmp), + targets: SwitchTargets::static_if(e, next, else_target), + }, + ); + let enum_data = self.db.enum_data(v.parent); + let fields = + enum_data.variants[v.local_id].variant_data.fields().iter().map(|(x, _)| { + ( + PlaceElem::Field(FieldId { parent: v.into(), local_id: x }), + fields_type[x].clone().substitute(Interner, subst), + ) + }); + self.pattern_match_tuple_like( + next, + Some(else_target), + args.iter().zip(fields).map(|(x, y)| (y.0, *x, y.1)), + *ellipsis, + &cond_place, + binding_mode, + )? + } + VariantId::StructId(s) => { + let struct_data = self.db.struct_data(s); + let fields = struct_data.variant_data.fields().iter().map(|(x, _)| { + ( + PlaceElem::Field(FieldId { parent: s.into(), local_id: x }), + fields_type[x].clone().substitute(Interner, subst), + ) + }); + self.pattern_match_tuple_like( + current, + current_else, + args.iter().zip(fields).map(|(x, y)| (y.0, *x, y.1)), + *ellipsis, + &cond_place, + binding_mode, + )? + } + VariantId::UnionId(_) => { + return Err(MirLowerError::TypeError("pattern matching on union")) + } + }) + } + fn pattern_match_tuple_like( &mut self, mut current: BasicBlockId, @@ -1109,23 +1263,161 @@ impl MirLowerCtx<'_> { &mut self, prev_block: BasicBlockId, label: Option<LabelId>, - f: impl FnOnce(&mut MirLowerCtx<'_>, BasicBlockId, BasicBlockId) -> Result<()>, - ) -> Result<BasicBlockId> { + f: impl FnOnce(&mut MirLowerCtx<'_>, BasicBlockId) -> Result<()>, + ) -> Result<Option<BasicBlockId>> { if label.is_some() { not_supported!("loop with label"); } let begin = self.new_basic_block(); - let end = self.new_basic_block(); - let prev = mem::replace(&mut self.current_loop_blocks, Some(LoopBlocks { begin, end })); + let prev = + mem::replace(&mut self.current_loop_blocks, Some(LoopBlocks { begin, end: None })); self.set_goto(prev_block, begin); - f(self, begin, end)?; - self.current_loop_blocks = prev; - Ok(end) + f(self, begin)?; + let my = mem::replace(&mut self.current_loop_blocks, prev) + .ok_or(MirLowerError::ImplementationError("current_loop_blocks is corrupt"))?; + Ok(my.end) } fn has_adjustments(&self, expr_id: ExprId) -> bool { !self.infer.expr_adjustments.get(&expr_id).map(|x| x.is_empty()).unwrap_or(true) } + + fn merge_blocks( + &mut self, + b1: Option<BasicBlockId>, + b2: Option<BasicBlockId>, + ) -> Option<BasicBlockId> { + match (b1, b2) { + (None, None) => None, + (None, Some(b)) | (Some(b), None) => Some(b), + (Some(b1), Some(b2)) => { + let bm = self.new_basic_block(); + self.set_goto(b1, bm); + self.set_goto(b2, bm); + Some(bm) + } + } + } + + fn current_loop_end(&mut self) -> Result<BasicBlockId> { + let r = match self + .current_loop_blocks + .as_mut() + .ok_or(MirLowerError::ImplementationError("Current loop access out of loop"))? + .end + { + Some(x) => x, + None => { + let s = self.new_basic_block(); + self.current_loop_blocks + .as_mut() + .ok_or(MirLowerError::ImplementationError("Current loop access out of loop"))? + .end = Some(s); + s + } + }; + Ok(r) + } + + fn is_uninhabited(&self, expr_id: ExprId) -> bool { + is_ty_uninhabited_from(&self.infer[expr_id], self.owner.module(self.db.upcast()), self.db) + } + + /// This function push `StorageLive` statements for each binding in the pattern. + fn push_storage_live(&mut self, b: BindingId, current: BasicBlockId) -> Result<()> { + // Current implementation is wrong. It adds no `StorageDead` at the end of scope, and before each break + // and continue. It just add a `StorageDead` before the `StorageLive`, which is not wrong, but unneeeded in + // the proper implementation. Due this limitation, implementing a borrow checker on top of this mir will falsely + // allow this: + // + // ``` + // let x; + // loop { + // let y = 2; + // x = &y; + // if some_condition { + // break; // we need to add a StorageDead(y) above this to kill the x borrow + // } + // } + // use(x) + // ``` + // But I think this approach work for mutability analysis, as user can't write code which mutates a binding + // after StorageDead, except loops, which are handled by this hack. + let span = self.body.bindings[b] + .definitions + .first() + .copied() + .map(MirSpan::PatId) + .unwrap_or(MirSpan::Unknown); + let l = self.result.binding_locals[b]; + self.push_statement(current, StatementKind::StorageDead(l).with_span(span)); + self.push_statement(current, StatementKind::StorageLive(l).with_span(span)); + Ok(()) + } + + fn resolve_lang_item(&self, item: LangItem) -> Result<LangItemTarget> { + let crate_id = self.owner.module(self.db.upcast()).krate(); + self.db.lang_item(crate_id, item).ok_or(MirLowerError::LangItemNotFound(item)) + } + + fn lower_block_to_place( + &mut self, + label: Option<LabelId>, + statements: &[hir_def::expr::Statement], + mut current: BasicBlockId, + tail: Option<ExprId>, + place: Place, + ) -> Result<Option<Idx<BasicBlock>>> { + if label.is_some() { + not_supported!("block with label"); + } + for statement in statements.iter() { + match statement { + hir_def::expr::Statement::Let { pat, initializer, else_branch, type_ref: _ } => { + if let Some(expr_id) = initializer { + let else_block; + let Some((init_place, c)) = + self.lower_expr_as_place(current, *expr_id, true)? + else { + return Ok(None); + }; + current = c; + (current, else_block) = self.pattern_match( + current, + None, + init_place, + self.expr_ty_after_adjustments(*expr_id), + *pat, + BindingAnnotation::Unannotated, + )?; + match (else_block, else_branch) { + (None, _) => (), + (Some(else_block), None) => { + self.set_terminator(else_block, Terminator::Unreachable); + } + (Some(else_block), Some(else_branch)) => { + if let Some((_, b)) = + self.lower_expr_as_place(else_block, *else_branch, true)? + { + self.set_terminator(b, Terminator::Unreachable); + } + } + } + } + } + hir_def::expr::Statement::Expr { expr, has_semi: _ } => { + let Some((_, c)) = self.lower_expr_as_place(current, *expr, true)? else { + return Ok(None); + }; + current = c; + } + } + } + match tail { + Some(tail) => self.lower_expr_to_place(tail, place, current), + None => Ok(Some(current)), + } + } } fn pattern_matching_dereference( @@ -1161,9 +1453,20 @@ fn cast_kind(source_ty: &Ty, target_ty: &Ty) -> Result<CastKind> { } pub fn mir_body_query(db: &dyn HirDatabase, def: DefWithBodyId) -> Result<Arc<MirBody>> { + let _p = profile::span("mir_body_query").detail(|| match def { + DefWithBodyId::FunctionId(it) => db.function_data(it).name.to_string(), + DefWithBodyId::StaticId(it) => db.static_data(it).name.clone().to_string(), + DefWithBodyId::ConstId(it) => { + db.const_data(it).name.clone().unwrap_or_else(Name::missing).to_string() + } + DefWithBodyId::VariantId(it) => { + db.enum_data(it.parent).variants[it.local_id].name.to_string() + } + }); let body = db.body(def); let infer = db.infer(def); - Ok(Arc::new(lower_to_mir(db, def, &body, &infer, body.body_expr)?)) + let result = lower_to_mir(db, def, &body, &infer, body.body_expr)?; + Ok(Arc::new(result)) } pub fn mir_body_recover( @@ -1183,37 +1486,88 @@ pub fn lower_to_mir( // need to take this input explicitly. root_expr: ExprId, ) -> Result<MirBody> { + if let Some((_, x)) = infer.type_mismatches().next() { + return Err(MirLowerError::TypeMismatch(x.clone())); + } let mut basic_blocks = Arena::new(); let start_block = basic_blocks.alloc(BasicBlock { statements: vec![], terminator: None, is_cleanup: false }); let mut locals = Arena::new(); // 0 is return local - locals.alloc(Local { mutability: Mutability::Mut, ty: infer[root_expr].clone() }); - let mut create_local_of_path = |p: PatId| { - // FIXME: mutablity is broken - locals.alloc(Local { mutability: Mutability::Not, ty: infer[p].clone() }) - }; + locals.alloc(Local { ty: infer[root_expr].clone() }); + let mut binding_locals: ArenaMap<BindingId, LocalId> = ArenaMap::new(); // 1 to param_len is for params - let mut binding_locals: ArenaMap<PatId, LocalId> = - body.params.iter().map(|&x| (x, create_local_of_path(x))).collect(); + let param_locals: Vec<LocalId> = if let DefWithBodyId::FunctionId(fid) = owner { + let substs = TyBuilder::placeholder_subst(db, fid); + let callable_sig = db.callable_item_signature(fid.into()).substitute(Interner, &substs); + body.params + .iter() + .zip(callable_sig.params().iter()) + .map(|(&x, ty)| { + let local_id = locals.alloc(Local { ty: ty.clone() }); + if let Pat::Bind { id, subpat: None } = body[x] { + if matches!( + body.bindings[id].mode, + BindingAnnotation::Unannotated | BindingAnnotation::Mutable + ) { + binding_locals.insert(id, local_id); + } + } + local_id + }) + .collect() + } else { + if !body.params.is_empty() { + return Err(MirLowerError::TypeError("Unexpected parameter for non function body")); + } + vec![] + }; // and then rest of bindings - for (pat_id, _) in body.pats.iter() { - if !binding_locals.contains_idx(pat_id) { - binding_locals.insert(pat_id, create_local_of_path(pat_id)); + for (id, _) in body.bindings.iter() { + if !binding_locals.contains_idx(id) { + binding_locals.insert(id, locals.alloc(Local { ty: infer[id].clone() })); } } - let mir = MirBody { basic_blocks, locals, start_block, owner, arg_count: body.params.len() }; + let mir = MirBody { + basic_blocks, + locals, + start_block, + binding_locals, + param_locals, + owner, + arg_count: body.params.len(), + }; let mut ctx = MirLowerCtx { result: mir, db, infer, body, - binding_locals, owner, current_loop_blocks: None, discr_temp: None, }; - let b = ctx.lower_expr_to_place(root_expr, return_slot().into(), start_block)?; - ctx.result.basic_blocks[b].terminator = Some(Terminator::Return); + let mut current = start_block; + for (¶m, local) in body.params.iter().zip(ctx.result.param_locals.clone().into_iter()) { + if let Pat::Bind { id, .. } = body[param] { + if local == ctx.result.binding_locals[id] { + continue; + } + } + let r = ctx.pattern_match( + current, + None, + local.into(), + ctx.result.locals[local].ty.clone(), + param, + BindingAnnotation::Unannotated, + )?; + if let Some(b) = r.1 { + ctx.set_terminator(b, Terminator::Unreachable); + } + current = r.0; + } + if let Some(b) = ctx.lower_expr_to_place(root_expr, return_slot().into(), current)? { + ctx.result.basic_blocks[b].terminator = Some(Terminator::Return); + } Ok(ctx.result) } diff --git a/crates/hir-ty/src/mir/lower/as_place.rs b/crates/hir-ty/src/mir/lower/as_place.rs new file mode 100644 index 00000000000..09bcdd93be0 --- /dev/null +++ b/crates/hir-ty/src/mir/lower/as_place.rs @@ -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))) + } +} diff --git a/crates/hir-ty/src/mir/pretty.rs b/crates/hir-ty/src/mir/pretty.rs new file mode 100644 index 00000000000..ffc08b7e346 --- /dev/null +++ b/crates/hir-ty/src/mir/pretty.rs @@ -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); + } + } + } +} diff --git a/crates/hir-ty/src/tests/coercion.rs b/crates/hir-ty/src/tests/coercion.rs index 3e110abaf4b..b524922b6cf 100644 --- a/crates/hir-ty/src/tests/coercion.rs +++ b/crates/hir-ty/src/tests/coercion.rs @@ -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)) } "#, ); diff --git a/crates/hir-ty/src/tests/method_resolution.rs b/crates/hir-ty/src/tests/method_resolution.rs index 4b671449e15..e568e7013fa 100644 --- a/crates/hir-ty/src/tests/method_resolution.rs +++ b/crates/hir-ty/src/tests/method_resolution.rs @@ -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 } } diff --git a/crates/hir/src/diagnostics.rs b/crates/hir/src/diagnostics.rs index b30c664e24f..c257ee2ae3a 100644 --- a/crates/hir/src/diagnostics.rs +++ b/crates/hir/src/diagnostics.rs @@ -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; diff --git a/crates/hir/src/from_id.rs b/crates/hir/src/from_id.rs index 43276919567..aaaa7abf386 100644 --- a/crates/hir/src/from_id.rs +++ b/crates/hir/src/from_id.rs @@ -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 } } } diff --git a/crates/hir/src/has_source.rs b/crates/hir/src/has_source.rs index a28b2d577aa..9f6b5c0a9fc 100644 --- a/crates/hir/src/has_source.rs +++ b/crates/hir/src/has_source.rs @@ -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) + } +} diff --git a/crates/hir/src/lib.rs b/crates/hir/src/lib.rs index a5df94885f6..92b31031ca1 100644 --- a/crates/hir/src/lib.rs +++ b/crates/hir/src/lib.rs @@ -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() } } diff --git a/crates/hir/src/semantics.rs b/crates/hir/src/semantics.rs index 697f4b43bc7..2a0077cf505 100644 --- a/crates/hir/src/semantics.rs +++ b/crates/hir/src/semantics.rs @@ -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() { diff --git a/crates/hir/src/semantics/source_to_def.rs b/crates/hir/src/semantics/source_to_def.rs index ddfec20e3f9..f6f8c9a250f 100644 --- a/crates/hir/src/semantics/source_to_def.rs +++ b/crates/hir/src/semantics/source_to_def.rs @@ -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, diff --git a/crates/hir/src/source_analyzer.rs b/crates/hir/src/source_analyzer.rs index 118a7f8ea86..133fa810d66 100644 --- a/crates/hir/src/source_analyzer.rs +++ b/crates/hir/src/source_analyzer.rs @@ -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()), diff --git a/crates/ide-assists/src/handlers/convert_match_to_let_else.rs b/crates/ide-assists/src/handlers/convert_match_to_let_else.rs index 65c2479e9f2..745a870ab6b 100644 --- a/crates/ide-assists/src/handlers/convert_match_to_let_else.rs +++ b/crates/ide-assists/src/handlers/convert_match_to_let_else.rs @@ -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, diff --git a/crates/ide-assists/src/handlers/extract_function.rs b/crates/ide-assists/src/handlers/extract_function.rs index e04a1dabb2c..0b90c9ba34f 100644 --- a/crates/ide-assists/src/handlers/extract_function.rs +++ b/crates/ide-assists/src/handlers/extract_function.rs @@ -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 diff --git a/crates/ide-assists/src/handlers/inline_local_variable.rs b/crates/ide-assists/src/handlers/inline_local_variable.rs index ce44100e34b..e69d1a29677 100644 --- a/crates/ide-assists/src/handlers/inline_local_variable.rs +++ b/crates/ide-assists/src/handlers/inline_local_variable.rs @@ -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(); diff --git a/crates/ide-db/src/rename.rs b/crates/ide-db/src/rename.rs index 4179f1bd4f3..f710211c8cb 100644 --- a/crates/ide-db/src/rename.rs +++ b/crates/ide-db/src/rename.rs @@ -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())) } diff --git a/crates/ide-db/src/search.rs b/crates/ide-db/src/search.rs index bcdaac4cf82..6298ea1927d 100644 --- a/crates/ide-db/src/search.rs +++ b/crates/ide-db/src/search.rs @@ -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 { diff --git a/crates/ide-diagnostics/src/handlers/mutability_errors.rs b/crates/ide-diagnostics/src/handlers/mutability_errors.rs new file mode 100644 index 00000000000..9c79ceba01e --- /dev/null +++ b/crates/ide-diagnostics/src/handlers/mutability_errors.rs @@ -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); +} +"#, + ); + } +} diff --git a/crates/ide-diagnostics/src/lib.rs b/crates/ide-diagnostics/src/lib.rs index c8635ff8011..f6c9b79c30c 100644 --- a/crates/ide-diagnostics/src/lib.rs +++ b/crates/ide-diagnostics/src/lib.rs @@ -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, diff --git a/crates/ide/src/highlight_related.rs b/crates/ide/src/highlight_related.rs index c889eb930f3..d88ffd25c40 100644 --- a/crates/ide/src/highlight_related.rs +++ b/crates/ide/src/highlight_related.rs @@ -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); + } + } } } diff --git a/crates/ide/src/hover/render.rs b/crates/ide/src/hover/render.rs index 6a29ddf59e1..da725ce502b 100644 --- a/crates/ide/src/hover/render.rs +++ b/crates/ide/src/hover/render.rs @@ -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) } diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index f2b535bdc7e..078b66dd395 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs @@ -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)) } diff --git a/crates/ide/src/navigation_target.rs b/crates/ide/src/navigation_target.rs index 11d10d2b854..6aae82f9816 100644 --- a/crates/ide/src/navigation_target.rs +++ b/crates/ide/src/navigation_target.rs @@ -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); diff --git a/crates/ide/src/rename.rs b/crates/ide/src/rename.rs index c0237e1edd0..e10c4638102 100644 --- a/crates/ide/src/rename.rs +++ b/crates/ide/src/rename.rs @@ -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}")); @@ -1710,6 +1715,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); check( diff --git a/crates/ide/src/view_mir.rs b/crates/ide/src/view_mir.rs new file mode 100644 index 00000000000..a36aba58bc0 --- /dev/null +++ b/crates/ide/src/view_mir.rs @@ -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)) +} diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs index 4e08bd0a724..32ac9a42dec 100644 --- a/crates/rust-analyzer/src/handlers.rs +++ b/crates/rust-analyzer/src/handlers.rs @@ -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, diff --git a/crates/rust-analyzer/src/lsp_ext.rs b/crates/rust-analyzer/src/lsp_ext.rs index e33589cc536..c7b513db981 100644 --- a/crates/rust-analyzer/src/lsp_ext.rs +++ b/crates/rust-analyzer/src/lsp_ext.rs @@ -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 { diff --git a/crates/rust-analyzer/src/main_loop.rs b/crates/rust-analyzer/src/main_loop.rs index d1e38b33c7d..d2797690669 100644 --- a/crates/rust-analyzer/src/main_loop.rs +++ b/crates/rust-analyzer/src/main_loop.rs @@ -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) diff --git a/crates/syntax/src/ast/traits.rs b/crates/syntax/src/ast/traits.rs index bc7b5582318..3e43df2d0d5 100644 --- a/crates/syntax/src/ast/traits.rs +++ b/crates/syntax/src/ast/traits.rs @@ -134,3 +134,5 @@ impl Iterator for AttrDocCommentIter { }) } } + +impl<A: HasName, B: HasName> HasName for Either<A, B> {} diff --git a/crates/test-utils/src/minicore.rs b/crates/test-utils/src/minicore.rs index b6336e2216f..93ff76a040c 100644 --- a/crates/test-utils/src/minicore.rs +++ b/crates/test-utils/src/minicore.rs @@ -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; } diff --git a/docs/dev/lsp-extensions.md b/docs/dev/lsp-extensions.md index c3623a5cc46..de142203208 100644 --- a/docs/dev/lsp-extensions.md +++ b/docs/dev/lsp-extensions.md @@ -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` diff --git a/editors/code/package.json b/editors/code/package.json index effe4e279c7..90f7b9074c8 100644 --- a/editors/code/package.json +++ b/editors/code/package.json @@ -115,6 +115,11 @@ "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)", "category": "rust-analyzer (debug command)" diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts index 49a8ca4edba..70b91fe7dc8 100644 --- a/editors/code/src/commands.ts +++ b/editors/code/src/commands.ts @@ -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"); diff --git a/editors/code/src/lsp_ext.ts b/editors/code/src/lsp_ext.ts index f6f5124dc41..400cd207d41 100644 --- a/editors/code/src/lsp_ext.ts +++ b/editors/code/src/lsp_ext.ts @@ -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" ); diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts index 5987368e6e0..1eb01f30c1e 100644 --- a/editors/code/src/main.ts +++ b/editors/code/src/main.ts @@ -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 }, diff --git a/lib/la-arena/src/map.rs b/lib/la-arena/src/map.rs index b9d491da3c0..7fff2b09c97 100644 --- a/lib/la-arena/src/map.rs +++ b/lib/la-arena/src/map.rs @@ -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); |
